Commit a62be560 authored by 翟艳秋(20软)'s avatar 翟艳秋(20软)

feat: 添加说话人选择功能; 修复vad结果仍可能过长导致ASR故障的问题; 暂时废弃混合音频的功能

parent eb8368db
......@@ -2,6 +2,8 @@
/build
/missing_packages
/aborted_icons
/docx
/无障碍电影制作工具(无黑窗口).zip
/无障碍电影制作工具(有黑窗口).zip
/log
/__pycache__
/.idea
/无障碍电影制作工具
/无障碍电影制作工具.zip
\ No newline at end of file
......@@ -3,7 +3,6 @@ __pycache__/
dataset/hysxm
!dataset/test.wav
!dataset/test_vad.wav
models/
lm/
log/
audios_cache/
......
import argparse
import datetime
import shutil
import time
import paddle
......@@ -49,6 +50,7 @@ def predict_long_audio_with_paddle(wav_path, pre_time, book_name, sheet_name, st
asr_executor = ASRExecutor()
start = time.time()
# 分割长音频
# print("start vad at time:", datetime.datetime.now())
audios_path, time_stamps = crop_audio_vad(wav_path)
texts = ''
narratages = []
......@@ -56,6 +58,7 @@ def predict_long_audio_with_paddle(wav_path, pre_time, book_name, sheet_name, st
# 已检测到字幕
subtitle_detected = False
# 执行识别
# print("start asr at time:", datetime.datetime.now())
for i, audio_path in enumerate(audios_path):
print("{}开始处理{}".format(paddle.get_device(), audio_path))
# 标识当前语音识别的进度
......@@ -91,6 +94,7 @@ def predict_long_audio_with_paddle(wav_path, pre_time, book_name, sheet_name, st
save_path = os.path.join(os.path.dirname(wav_path), 'crop_audio')
if os.path.exists(save_path):
shutil.rmtree(save_path)
# print("task ends at time:", datetime.datetime.now())
if __name__ == "__main__":
......
......@@ -3,6 +3,7 @@ import contextlib
import os
import wave
import numpy as np
import webrtcvad
......@@ -131,9 +132,25 @@ def crop_audio_vad(audio_path, aggressiveness=1, frame_duration_ms=30):
save_path = os.path.join(os.path.dirname(audio_path), 'crop_audio')
if not os.path.exists(save_path):
os.makedirs(save_path)
# 获取vad分割得到各段音频的时间戳,并写到本地
for i, segment in enumerate(segments):
path = os.path.join(save_path, '%s_%d.wav' % (os.path.basename(audio_path)[:-4], i))
write_wave(path, segment[0], sample_rate)
audios_path.append(path)
time_stamps.append(segment[1:])
# 小于180s的音频段可直接写到本地
if segment[2] - segment[1] <= 180:
path = os.path.join(save_path, '%s_%d.wav' % (os.path.basename(audio_path)[:-4], i))
write_wave(path, segment[0], sample_rate)
time_stamps.append(segment[1:])
audios_path.append(path)
# 大于180s的音频段,以180s为单位进行分割
else:
print("abnormal")
split_len = int(np.ceil(segment[2] - segment[1]) / 180)
start = 0
start_time, end_time = segment[1], segment[2]
for j in range(split_len):
path = os.path.join(save_path, '%s_%d_%d.wav' % (os.path.basename(audio_path)[:-4], i, j))
write_wave(path, segment[0, start: min(start + 32000 * 180, len(segment[0]))], sample_rate)
time_stamps.append([start_time, min(start_time + 180, end_time)])
audios_path.append(path)
start = start + 32000 * 180
start_time = start_time + 180
return audios_path, time_stamps
......@@ -12,3 +12,7 @@
- split_wav.py:对视频中的音频进行提取、切分等操作
- PaddlePaddle_DeepSpeech2文件夹:使用PaddleSpeech获取音频对应文本
3、文档
- 存放在docs文件夹中。
- **内容**:主要是上述界面文件和功能模块中相关函数的功能和使用方法的介绍。
"""基于ASR的旁白区间检测算法
总体流程如下:
- 首先提取视频中的音频;
- 使用VAD将音频分割,并存储到本地的临时文件夹中,同时获取每段音频的对应时间戳;
- 使用ASR识别每段音频,即可获取字幕及对应时间戳等信息;
- 根据字幕间的时间差判断能否插入旁白以及旁白的推荐字数。
外部调用方式:
.. code-block:: python
from detect_with_asr import detect_with_asr
detect_with_asr(video_path, book_path, start_time, end_time, state)
"""
import os
import sys
import shutil
import time
......@@ -8,13 +24,13 @@ from openpyxl.styles import PatternFill, Alignment
from split_wav import *
def create_sheet(path, sheet_name, value):
def create_sheet(path: str, sheet_name: str, value: list):
"""根据给定的表头,初始化表格
:param path: [str], 表格(book)的存储位置
:param sheet_name: [str], 表(sheet)的名字
:param value: [list], 表头内容为['起始时间','终止时间','字幕','建议','旁边解说脚本']
:return: None
Args:
path (str): 表格(book)的存储位置
sheet_name (str): 表(sheet)的名字
value (list): 表头内容为['起始时间','终止时间','字幕','建议','旁白解说脚本']
"""
index = len(value)
workbook = openpyxl.Workbook()
......@@ -29,13 +45,13 @@ def create_sheet(path, sheet_name, value):
workbook.save(path)
def write_to_sheet(path, sheet_name, value):
def write_to_sheet(path: str, sheet_name: str, value: list):
"""向已存在的表格中写入数据
:param path: 表格存储位置
:param sheet_name: excel表内的表名
:param value: 插入数据
:return:
Args:
path (str): 表格(book)的存储位置
sheet_name (str): excel表内的表(sheet)的名字
value (list): 要插入表内的数据
"""
index = len(value)
workbook = openpyxl.load_workbook(path)
......@@ -50,16 +66,19 @@ def write_to_sheet(path, sheet_name, value):
workbook.save(path)
def detect_with_asr(video_path, book_path, start_time=0, end_time=-1, state=None):
"""使用ASR检测视频中的字幕并推荐旁白
def detect_with_asr(video_path: str, book_path: str, start_time=0, end_time=-1, state=None):
"""使用ASR检测视频中的字幕并推荐旁白区间
:param video_path: 待处理视频地址
:param book_path: 旁白表格输出地址
:param start_time: 视频实际开始时间
:param end_time: 视频实际结束时间
:param state: 用于通信的状态关键字
:return:
Args:
video_path (str): 待处理视频地址
book_path (str): 旁白表格输出地址
start_time (int, optional): 视频实际开始时间. Defaults to 0.
end_time (int, optional): 视频实际结束时间. Defaults to -1.
state (_type_, optional): 用于通信的状态关键字. Defaults to None.
"""
if state is None:
state = [None]
# print("extract audio from the video at time: ", datetime.datetime.now())
# 临时存储各种中间产物的文件夹
tmp_root = os.path.join(os.path.dirname(video_path), 'tmp')
print(tmp_root)
......@@ -72,6 +91,7 @@ def detect_with_asr(video_path, book_path, start_time=0, end_time=-1, state=None
# 提取出视频中的音频,分割后提取出其中的人声部分并存储
audio_path = extract_audio(video_path, tmp_root, start_time, end_time)
# print("create sheet at time: ", datetime.datetime.now())
# xlsx中的表格名为“旁白插入位置建议”
if os.path.exists(book_path):
os.remove(book_path)
......@@ -89,9 +109,10 @@ def detect_with_asr(video_path, book_path, start_time=0, end_time=-1, state=None
if __name__ == '__main__':
start_time = time.time()
# 给定待处理的视频路径
video_path = 'D:/heelo/zhanlang.rmvb'
detect_with_asr(video_path, "zhanlang.xlsx", 50, 5154)
print("处理视频 {} 需要时长为{} ".format(os.path.basename(video_path), time.time() - start_time))
pass
# start_time = time.time()
# # 给定待处理的视频路径
# video_path = 'D:/Downloads/芳H.[更多请关注公众号:吃瓜族].mp4'
#
# detect_with_asr(video_path, "fanghua.xlsx", 0, 8122, [None])
# print("处理视频 {} 需要时长为{} ".format(os.path.basename(video_path), time.time() - start_time))
"""基于OCR的旁白区间检测算法
总体流程如下:
- 首先检测得到字幕的上下边界位置,框定大概的字幕检测范围;
- 视频每秒取3帧,并使用OCR获取字幕以及时间戳等信息;
- 根据字幕间的时间差判断能否插入旁白以及旁白的推荐字数。
外部调用方式:
.. code-block:: python
from detect_with_ocr import detect_with_ocr
detect_with_ocr(video_path, book_path, start_time, end_time, state)
"""
import os
import cv2
......@@ -5,6 +19,9 @@ import numpy as np
from paddleocr import PaddleOCR
import difflib
import re
from typing import Tuple, Union
from detect_with_asr import create_sheet, write_to_sheet
# 字幕的上下边界
......@@ -17,18 +34,19 @@ ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False, use_gpu=False)
normal_speed = 4
def get_position(video_path, start_time):
def get_position(video_path: str, start_time: float) -> Tuple[float, float]:
"""根据对视频中的画面进行分析,确定字幕的位置,以便后续的字幕识别
:param start_time: 视频实际开始时间
:param video_path: 视频存储路径
:return: [float,float], 字幕在整个画面中的实际上下边界位置
Args:
video_path (str): 视频存储路径
start_time (float): 视频开始时间
Returns:
Tuple[float, float]: 字幕在整个画面中的上下边界位置
"""
video = cv2.VideoCapture(video_path)
subtitle_position = {}
fps = video.get(cv2.CAP_PROP_FPS)
if video.get(cv2.CAP_PROP_FRAME_COUNT) / fps > 10 * 60:
start_time = start_time + 300
start = int(start_time * fps)
cnt = 0
txt_cnt = 0
......@@ -89,23 +107,29 @@ def get_position(video_path, start_time):
return up_bounding + height, down_bounding + height
def erasePunc(txt):
def erasePunc(txt: str) -> str:
"""去除字符串中的非中文字符
:param txt: 待处理字符串
:return: [str], 处理后的字符串
Args:
txt (str): 待处理字符串
Returns:
str: 处理后的字符串
"""
pattern = re.compile(r'[^\u4e00-\u9fa5]')
txt = re.sub(pattern, '', txt)
return txt
def string_similar(s1, s2):
def string_similar(s1: str, s2: str) -> float:
"""比较字符串s1和s2的相似度,主要用于减少输出文件中相似字幕的重复
:param s1: 第一个字符串
:param s2: 第二个字符串
:return: [float], 字符串间的相似度
Args:
s1 (str): 第一个字符串
s2 (str): 第二个字符串
Returns:
float: 字符串间的相似度
"""
# 去除非中文字符后,再比较相似度
s1 = erasePunc(s1)
......@@ -113,11 +137,14 @@ def string_similar(s1, s2):
return difflib.SequenceMatcher(None, s1, s2).quick_ratio()
def normalize(text):
def normalize(text: str) -> str:
"""规范化处理文本中的一些标点符号
:param text: 待处理字符串
:return: 处理后的字符串
Args:
text (str): 待处理字符串
Returns:
str: 处理后的字符串
"""
# 将英文标点转换为中文标点
E_pun = u',.!?()[]:;'
......@@ -133,15 +160,18 @@ def normalize(text):
return text
def detect_subtitle(img):
""" 检测当前画面得到字幕信息
def detect_subtitle(img: np.ndarray) -> Union[str, None]:
"""检测当前画面得到字幕信息
Args:
img (np.ndarray): 当前画面
:param img: 当前画面
:return: [str|None], 字幕信息
Returns:
Union[str, None]: 字幕信息(没有字幕时返回None)
"""
subTitle = ''
height = down_b - up_b
img = img[int(up_b - height * 0.6):int(down_b + height * 0.6)]
img = img[int(up_b - height * 0.7):int(down_b + height * 0.7)]
# img = cv2.resize(img, (int(img.shape[1] * 0.5), int(img.shape[0] * 0.5)))
res = ocr.ocr(img, cls=True)
sorted(res, key=lambda text: text[0][0][1])
......@@ -149,6 +179,9 @@ def detect_subtitle(img):
return None
possible_txt = []
for x in res:
# cv2.imshow("cut", img)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
rect, (txt, confidence) = x
font_size = rect[2][1] - rect[0][1]
mid = (rect[0][0] + rect[1][0]) / 2
......@@ -169,17 +202,19 @@ def detect_subtitle(img):
return None
def process_video(video_path, begin, end, book_path, sheet_name, state):
""" 处理视频,主要完成对字幕的捕捉以及根据字幕分析得出旁白可能位置的任务
def process_video(video_path: str, begin: float, end: float, book_path: str, sheet_name: str, state=None):
"""处理视频,主要完成对字幕的捕捉以及根据字幕分析得出旁白可能位置的任务
:param video_path: 待处理视频的路径
:param begin: 电影的实际开始位置(秒)
:param end: 电影除演职表外的实际结束位置(秒)
:param book_path: 输出表格地址
:param sheet_name: 输出表格中的表名
:param state: 用于通信的状态关键字
:return:
Args:
video_path (str): 待处理视频的路径
begin (float): 电影的实际开始位置(秒)
end (float): 电影除演职表外的实际结束位置(秒)
book_path (str): 输出表格地址
sheet_name (str): 输出表格中的表名
state (optional): 用于通信的状态关键字. Defaults to None.
"""
if state is None:
state = [None]
video = cv2.VideoCapture(video_path)
fps = video.get(cv2.CAP_PROP_FPS)
lastSubTitle = None
......@@ -249,16 +284,18 @@ def process_video(video_path, begin, end, book_path, sheet_name, state):
lastSubTitle = subTitle
def detect_with_ocr(video_path, book_path, start_time, end_time, state):
""" 使用ocr检测视频获取字幕并输出旁白推荐
def detect_with_ocr(video_path: str, book_path: str, start_time: float, end_time: float, state=None):
"""使用ocr检测视频获取字幕并输出旁白推荐
:param video_path: 待处理视频地址
:param book_path: 表格存储位置
:param start_time: 视频实际开始时间
:param end_time: 视频实际结束时见
:param state: 用于通信的状态关键字
:return:
Args:
video_path (str): 待处理视频地址
book_path (str): 表格存储位置
start_time (float): 视频实际开始时间
end_time (float): 视频实际结束时间
state (optional): 用于通信的状态关键字. Defaults to None.
"""
if state is None:
state = [None]
if os.path.exists(book_path):
os.remove(book_path)
book_name_xlsx = book_path
......@@ -266,11 +303,15 @@ def detect_with_ocr(video_path, book_path, start_time, end_time, state):
# 获取字幕在画面中的上下边界,方便在后续视频遍历过程中直接对字幕对应区域进行分析
global up_b, down_b
up_b, down_b = get_position(video_path, start_time)
# print("get the bounding of the narratage at time: ", datetime.datetime.now())
# 此处start_time + 300是为了节省用户调整视频开始时间的功夫(强行跳过前5分钟)
up_b, down_b = get_position(video_path, start_time + 300)
# 获取并构建输出信息
table_head = [["起始时间", "终止时间", "字幕", '建议', '解说脚本']]
# print("create sheet at time: ", datetime.datetime.now())
create_sheet(book_name_xlsx, sheet_name_xlsx, table_head)
# print("process the total video at time: ", datetime.datetime.now())
process_video(video_path, start_time, end_time, book_name_xlsx, sheet_name_xlsx, state)
......
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 88819ba55fd335140cf4004c89998be8
tags: 645f666f9bcd5a90fca523b33c5a78b7
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html>
<html class="writer-html5" lang="zh-CN" >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>概览:模块代码 &mdash; accessibility movie v1.3.1 文档</title>
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="../_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="../" id="documentation_options" src="../_static/documentation_options.js"></script>
<script src="../_static/jquery.js"></script>
<script src="../_static/underscore.js"></script>
<script src="../_static/doctools.js"></script>
<script src="../_static/translations.js"></script>
<script src="../_static/js/theme.js"></script>
<link rel="index" title="索引" href="../genindex.html" />
<link rel="search" title="搜索" href="../search.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="../index.html" class="icon icon-home"> accessibility movie
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="../search.html" method="get">
<input type="text" name="q" placeholder="在文档中搜索" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">API</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../main_gui.html">main_gui</a></li>
<li class="toctree-l1"><a class="reference internal" href="../narratage_detection.html">narratage_detection</a></li>
<li class="toctree-l1"><a class="reference internal" href="../judge_subtitle.html">judge_subtitle</a></li>
<li class="toctree-l1"><a class="reference internal" href="../detect_with_ocr.html">detect_with_ocr</a></li>
<li class="toctree-l1"><a class="reference internal" href="../detect_with_asr.html">detect_with_asr</a></li>
<li class="toctree-l1"><a class="reference internal" href="../speech_synthesis.html">speech_synthesis</a></li>
<li class="toctree-l1"><a class="reference internal" href="../split_wav.html">split_wav</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="../index.html">accessibility movie</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="../index.html" class="icon icon-home"></a> &raquo;</li>
<li>概览:模块代码</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<h1>代码可用的所有模块</h1>
<ul><li><a href="detect_with_asr.html">detect_with_asr</a></li>
<li><a href="detect_with_ocr.html">detect_with_ocr</a></li>
<li><a href="judge_subtitle.html">judge_subtitle</a></li>
<li><a href="main_gui.html">main_gui</a></li>
<li><a href="narratage_detection.html">narratage_detection</a></li>
<li><a href="speech_synthesis.html">speech_synthesis</a></li>
<li><a href="split_wav.html">split_wav</a></li>
</ul>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>&#169; 版权所有 2022, WiltonPoker.</p>
</div>
利用 <a href="https://www.sphinx-doc.org/">Sphinx</a> 构建,使用了
<a href="https://github.com/readthedocs/sphinx_rtd_theme">主题</a>
<a href="https://readthedocs.org">Read the Docs</a>开发.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
detect\_with\_asr
========================
.. automodule:: detect_with_asr
:members:
:undoc-members:
:show-inheritance:
detect\_with\_ocr
========================
.. automodule:: detect_with_ocr
:members:
:undoc-members:
:show-inheritance:
.. accessibility movie documentation master file, created by
sphinx-quickstart on Wed Apr 27 09:58:18 2022.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to accessibility movie's documentation!
===============================================
.. toctree::
:maxdepth: 1
:caption: Get Started
:hidden:
:glob:
.. toctree::
:maxdepth: 2
:caption: API
:glob:
main_gui
narratage_detection
judge_subtitle
detect_with_ocr
detect_with_asr
speech_synthesis
split_wav
judge\_subtitle
======================
.. automodule:: judge_subtitle
:members:
:undoc-members:
:show-inheritance:
main\_gui
================
.. automodule:: main_gui
:members:
:undoc-members:
:show-inheritance:
accessibility_movie
===================
.. toctree::
:maxdepth: 4
detect_with_asr
detect_with_ocr
judge_subtitle
main_gui
narratage_detection
speech_synthesis
split_wav
narratage\_detection
===========================
.. automodule:: narratage_detection
:members:
:undoc-members:
:show-inheritance:
speech\_synthesis
========================
.. automodule:: speech_synthesis
:members:
:undoc-members:
:show-inheritance:
split\_wav
=================
.. automodule:: split_wav
:members:
:undoc-members:
:show-inheritance:
This diff is collapsed.
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment