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

add doc and fix the bugs

parent f90ee73d
...@@ -7,3 +7,4 @@ chineseocr_usage.py ...@@ -7,3 +7,4 @@ chineseocr_usage.py
easyOCR_usage.py easyOCR_usage.py
dist dist
build build
log
from PyQt5.QtCore import Qt, QRect, QPointF
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtWidgets import QSlider, QWidget, QVBoxLayout, QProxyStyle, QStyle, QStyleOptionSlider
class SliderStyle(QProxyStyle):
def subControlRect(self, control, option, subControl, widget=None):
rect = super(SliderStyle, self).subControlRect(
control, option, subControl, widget)
if subControl == QStyle.SC_SliderHandle:
if option.orientation == Qt.Horizontal:
# 高度1/3
radius = int(widget.height() / 3)
offset = int(radius / 3)
if option.state & QStyle.State_MouseOver:
x = min(rect.x() - offset, widget.width() - radius)
x = x if x >= 0 else 0
else:
radius = offset
x = min(rect.x(), widget.width() - radius)
rect = QRect(x, int((rect.height() - radius) / 2),
radius, radius)
else:
# 宽度1/3
radius = int(widget.width() / 3)
offset = int(radius / 3)
if option.state & QStyle.State_MouseOver:
y = min(rect.y() - offset, widget.height() - radius)
y = y if y >= 0 else 0
else:
radius = offset
y = min(rect.y(), widget.height() - radius)
rect = QRect(int((rect.width() - radius) / 2),
y, radius, radius)
return rect
return rect
class PaintQSlider(QSlider):
def __init__(self, *args, **kwargs):
super(PaintQSlider, self).__init__(*args, **kwargs)
# 设置代理样式,主要用于计算和解决鼠标点击区域
self.setStyle(SliderStyle())
def paintEvent(self, _):
option = QStyleOptionSlider()
self.initStyleOption(option)
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 中间圆圈的位置
rect = self.style().subControlRect(
QStyle.CC_Slider, option, QStyle.SC_SliderHandle, self)
# 画中间白色线条
painter.setPen(Qt.white)
painter.setBrush(Qt.white)
if self.orientation() == Qt.Horizontal:
y = self.height() / 2
painter.drawLine(QPointF(0, y), QPointF(self.width(), y))
else:
x = self.width() / 2
painter.drawLine(QPointF(x, 0), QPointF(x, self.height()))
# 画圆
painter.setPen(Qt.NoPen)
if option.state & QStyle.State_MouseOver: # 双重圆
# 半透明大圆
r = rect.height() / 2
painter.setBrush(QColor(255, 255, 255, 100))
painter.drawRoundedRect(rect, r, r)
# 实心小圆(上下左右偏移4)
rect = rect.adjusted(4, 4, -4, -4)
r = rect.height() / 2
painter.setBrush(QColor(255, 255, 255, 255))
painter.drawRoundedRect(rect, r, r)
# 绘制文字
painter.setPen(Qt.white)
if self.orientation() == Qt.Horizontal: # 在上方绘制文字
x, y = rect.x(), rect.y() - rect.height() - 2
else: # 在左侧绘制文字
x, y = rect.x() - rect.width() - 2, rect.y()
painter.drawText(
x, y, rect.width(), rect.height(),
Qt.AlignCenter, str(self.value())
)
else: # 实心圆
r = rect.height() / 2
painter.setBrush(Qt.white)
painter.drawRoundedRect(rect, r, r)
# class Window(QWidget):
# def __init__(self, *args, **kwargs):
# super(Window, self).__init__(*args, **kwargs)
# self.setAttribute(Qt.WA_StyledBackground, True)
# layout = QVBoxLayout(self)
# layout.addWidget(PaintQSlider(Qt.Vertical, self, minimumWidth=90))
# layout.addWidget(PaintQSlider(Qt.Horizontal, self, minimumHeight=90))
# if __name__ == '__main__':
# import sys
# from PyQt5.QtWidgets import QApplication
# app = QApplication(sys.argv)
# w = Window()
# w.setStyleSheet('QWidget {background: gray;}')
# w.show()
# sys.exit(app.exec_())
\ No newline at end of file
# accessibility_movie_2
二期的无障碍电影制作工具,主要包含旁白区间检测和旁白及字幕导出两个功能; 二期主要提升用户体验、产品功能与易用性,设计成熟的操作界面进行人机交互,另外为了方便用户使用,使用pyinstaller进行打包。
注意要自行下载mp4的解码器,不然无法播放视频
\ No newline at end of file
"""旁白音频合成对话框
技术实现步骤:
1. dialog发送点击信号(把所有信息都带上,放到一个list里,不然太长了)
2. synthesis绑定该信号,并触发自身槽函数
3. 开启多线程,进行视频合成
4. 注意,dialog组件和synthesis组件都绑定到mainwindow上,信号就是方便他们交互的。
"""
import sys import sys
import os import os
from PyQt5.QtCore import *; from PyQt5.QtCore import *;
...@@ -7,14 +16,6 @@ from PyQt5.QtWidgets import *; ...@@ -7,14 +16,6 @@ from PyQt5.QtWidgets import *;
from assemble_dialog_ui import Ui_Dialog from assemble_dialog_ui import Ui_Dialog
from utils import validate_and_get_filepath, replace_path_suffix from utils import validate_and_get_filepath, replace_path_suffix
"""
视频合成 技术实现步骤:
1、dialog发送点击信号(把所有信息都带上,放到一个list里,不然太长了)
2、synthesis绑定该信号,并触发自身槽函数
3、开启多线程,进行视频合成
4、注意,dialog组件和synthesis组件都绑定到mainwindow上,信号就是方便他们交互的。
"""
class Assemble_Dialog(QDialog, Ui_Dialog): class Assemble_Dialog(QDialog, Ui_Dialog):
# 开始合成信号,传参分别是video_path,audio_dir, sheet_path,speed_info, caption_path, speaker_info # 开始合成信号,传参分别是video_path,audio_dir, sheet_path,speed_info, caption_path, speaker_info
...@@ -28,8 +29,8 @@ class Assemble_Dialog(QDialog, Ui_Dialog): ...@@ -28,8 +29,8 @@ class Assemble_Dialog(QDialog, Ui_Dialog):
self.setWindowTitle("合成") self.setWindowTitle("合成")
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("开始合成") self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("开始合成")
self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消") self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消")
self.pushButton.clicked.connect(self.openFile) self.pushButton.clicked.connect(self.open_file)
self.pushButton_2.clicked.connect(self.openTableFile) self.pushButton_2.clicked.connect(self.open_table_file)
# 注意,不能直接绑定到buttonBox上。 # 注意,不能直接绑定到buttonBox上。
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(self.start_assemble) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(self.start_assemble)
self.lineEdit_3.setText(projectContext.speaker_info) self.lineEdit_3.setText(projectContext.speaker_info)
...@@ -37,19 +38,28 @@ class Assemble_Dialog(QDialog, Ui_Dialog): ...@@ -37,19 +38,28 @@ class Assemble_Dialog(QDialog, Ui_Dialog):
# self.show.connect(self.init_self_slot) # self.show.connect(self.init_self_slot)
def init_self(self): def init_self(self):
"""使用工程配置信息初始化语音合成界面
"""
# print("self.projectContext.speaker_info", self.projectContext.speaker_info) # print("self.projectContext.speaker_info", self.projectContext.speaker_info)
self.lineEdit.setText(self.projectContext.video_path) self.lineEdit.setText(self.projectContext.video_path)
self.lineEdit_2.setText(self.projectContext.excel_path) self.lineEdit_2.setText(self.projectContext.excel_path)
self.lineEdit_3.setText(self.projectContext.speaker_info) self.lineEdit_3.setText(self.projectContext.speaker_info)
self.lineEdit_4.setText(self.projectContext.speaker_speed) self.lineEdit_4.setText(self.projectContext.speaker_speed)
def openFile(self): def open_file(self):
"""选择视频文件
"""
file_info = QFileDialog.getOpenFileNames(self, '选择视频', os.getcwd(), "All Files(*);;Text Files(*.txt)") file_info = QFileDialog.getOpenFileNames(self, '选择视频', os.getcwd(), "All Files(*);;Text Files(*.txt)")
file_name, ok = validate_and_get_filepath(file_info) file_name, ok = validate_and_get_filepath(file_info)
if ok and file_name != "": if ok and file_name != "":
self.lineEdit.setText(file_name) self.lineEdit.setText(file_name)
def openTableFile(self): def open_table_file(self):
"""选择旁白表格文件
"""
now_path = os.path.join(os.getcwd(), self.lineEdit.text()) now_path = os.path.join(os.getcwd(), self.lineEdit.text())
#todo: 方法提取出来放到utils里 #todo: 方法提取出来放到utils里
now_path = now_path.replace(os.path.splitext(now_path)[-1], ".xlsx") now_path = now_path.replace(os.path.splitext(now_path)[-1], ".xlsx")
...@@ -62,6 +72,10 @@ class Assemble_Dialog(QDialog, Ui_Dialog): ...@@ -62,6 +72,10 @@ class Assemble_Dialog(QDialog, Ui_Dialog):
self.lineEdit_2.setText(file_name) self.lineEdit_2.setText(file_name)
def start_assemble(self): def start_assemble(self):
"""开始语音合成
读取当前界面中的用户输入,并通过信号发送到主界面调用旁白音频合成算法
"""
print("start_assemble") print("start_assemble")
video_path = self.lineEdit.text() video_path = self.lineEdit.text()
# 默认 输出的音频是工程目录+/output # 默认 输出的音频是工程目录+/output
......
"""常用的一些常量
类型如下:
- Content类是界面中字幕+旁白表格,标注常用列的编号及表格对应名称
- Aside类是界面中的旁白表格,标注常用列的编号、表格对应名称和语速选项
- Subtitle类是界面中的字幕表格,标注时间戳对应列编号和表格对应名称
- Pathes类提供常用资源的路径,如说话人信息文件
"""
import os import os
...@@ -29,8 +37,6 @@ class Subtitle: ...@@ -29,8 +37,6 @@ class Subtitle:
TimeFormatColumns = [0, 1] TimeFormatColumns = [0, 1]
dir_path = os.path.dirname(os.path.abspath(__file__))
class Pathes: class Pathes:
dir_path = os.path.dirname(os.path.abspath(__file__))
speaker_conf_path = os.path.join(dir_path, "res/speakers.json") speaker_conf_path = os.path.join(dir_path, "res/speakers.json")
"""新建工程界面相关响应
新建工程界面相关响应如下:
- chooese_root: 选择存放工程的文件夹的函数
- create_project: 点击“确认”时调用,用于确认输入信息正确性,从而正确地新建工程
- close_dialog: 点击“取消”时调用,用于关闭对话框
"""
import os import os
from PyQt5.QtCore import *; from PyQt5.QtCore import *;
from PyQt5.QtGui import *; from PyQt5.QtGui import *;
...@@ -17,12 +26,19 @@ class Create_Dialog(QDialog, Ui_Dialog): ...@@ -17,12 +26,19 @@ class Create_Dialog(QDialog, Ui_Dialog):
self.cancel.clicked.connect(self.close_dialog) self.cancel.clicked.connect(self.close_dialog)
def choose_root(self): def choose_root(self):
"""选择存放新工程的文件夹
"""
root_info = QFileDialog.getExistingDirectory(self, "选择工程文件夹", os.getcwd()) root_info = QFileDialog.getExistingDirectory(self, "选择工程文件夹", os.getcwd())
if len(self.root_input.text()) != 0 and len(root_info) == 0: if len(self.root_input.text()) != 0 and len(root_info) == 0:
return return
self.root_input.setText(root_info) self.root_input.setText(root_info)
def create_project(self): def create_project(self):
"""创建新的工程
读取用户输入,进行合理性判断,存在错误时弹窗提示,否则在该路径下创建以工程名命名的文件夹,并通过信号传递工程路径,对主界面进行更新,然后自动关闭该界面。
"""
self.project_name = self.name_input.text() self.project_name = self.name_input.text()
self.dir_path = self.root_input.text() self.dir_path = self.root_input.text()
if os.path.exists(self.dir_path) and len(self.project_name) > 0: if os.path.exists(self.dir_path) and len(self.project_name) > 0:
...@@ -40,4 +56,7 @@ class Create_Dialog(QDialog, Ui_Dialog): ...@@ -40,4 +56,7 @@ class Create_Dialog(QDialog, Ui_Dialog):
self.prompt_dialog.show_with_msg("请输入合法文件夹路径") self.prompt_dialog.show_with_msg("请输入合法文件夹路径")
def close_dialog(self): def close_dialog(self):
"""关闭窗口
"""
self.close() self.close()
\ No newline at end of file
import cv2
if __name__ == '__main__':
video_path = 'D:/mystudy/Eagle/accessibility_movie_1/test37second.mp4'
video = cv2.VideoCapture(video_path)
fps = video.get(cv2.CAP_PROP_FPS)
frame_cnt = video.get(cv2.CAP_PROP_FRAME_COUNT)
duration = frame_cnt / fps
print("fps:", fps, type(fps))
print("frame_cnt:", frame_cnt, type(frame_cnt))
print("duration:", duration, type(duration))
\ No newline at end of file
"""旁白区间检测界面相关响应
旁白区间检测界面相关响应如下:
- init_self: 初始化当前界面
- open_file: 选择待处理的视频
- open_table_file: 选择字幕和旁白输出的表格在本地的存放位置
- start_detect: 确认是否开始旁白区间检测
"""
import sys import sys
import os import os
from PyQt5.QtCore import *; from PyQt5.QtCore import *;
...@@ -19,23 +29,34 @@ class Detect_Dialog(QDialog, Ui_Dialog): ...@@ -19,23 +29,34 @@ class Detect_Dialog(QDialog, Ui_Dialog):
self.setWindowTitle("检测") self.setWindowTitle("检测")
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("开始检测") self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("开始检测")
self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消") self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消")
self.pushButton.clicked.connect(self.openFile) self.pushButton.clicked.connect(self.open_file)
self.pushButton_2.clicked.connect(self.openTableFile) self.pushButton_2.clicked.connect(self.open_table_file)
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(self.start_detect) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(self.start_detect)
self.prompt_dialog = Prompt_Dialog() self.prompt_dialog = Prompt_Dialog()
def init_self(self): def init_self(self):
"""根据工程配置信息初始化界面
"""
self.lineEdit.setText(self.projectContext.video_path) self.lineEdit.setText(self.projectContext.video_path)
self.lineEdit_2.setText(self.projectContext.excel_path) self.lineEdit_2.setText(self.projectContext.excel_path)
def openFile(self): def open_file(self):
"""选择待处理的视频
根据工程当前选定的视频为起始点打开一个窗口供用户进行视频选择,在该窗口中只显示mp4、rmvb、mkv和avi格式的视频文件,避免用户选择到非视频文件。
"""
root_dir = os.getcwd() if self.projectContext.video_path is None else os.path.dirname(self.projectContext.video_path) root_dir = os.getcwd() if self.projectContext.video_path is None else os.path.dirname(self.projectContext.video_path)
file_info = QFileDialog.getOpenFileNames(self, '选择视频', root_dir, "Video Files(*.mp4 *.rmvb *mkv *avi)") file_info = QFileDialog.getOpenFileNames(self, '选择视频', root_dir, "Video Files(*.mp4 *.rmvb *mkv *avi)")
file_name, ok = validate_and_get_filepath(file_info) file_name, ok = validate_and_get_filepath(file_info)
if ok and file_name != "": if ok and file_name != "":
self.lineEdit.setText(file_name) self.lineEdit.setText(file_name)
def openTableFile(self): def open_table_file(self):
"""选择旁白和字幕输出表格的路径
以视频路径为基础构造表格路径,以此为基点打开一个选择文件路径的窗口,在该窗口中默认保存文件后缀为xlsx,在确认文件路径正确后,在表格中更新对应文本框中的内容。
"""
now_path = os.path.join(os.getcwd(), self.lineEdit.text()) now_path = os.path.join(os.getcwd(), self.lineEdit.text())
now_path = now_path.replace(os.path.splitext(now_path)[-1], ".xlsx") now_path = now_path.replace(os.path.splitext(now_path)[-1], ".xlsx")
print("path:", now_path) print("path:", now_path)
...@@ -47,12 +68,12 @@ class Detect_Dialog(QDialog, Ui_Dialog): ...@@ -47,12 +68,12 @@ class Detect_Dialog(QDialog, Ui_Dialog):
self.lineEdit_2.setText(file_name) self.lineEdit_2.setText(file_name)
def start_detect(self): def start_detect(self):
# 发出一个信号,开始检测了 """开始检测
# 版本1.0:(当前版本)
# 让主窗口接受,新起进程检测并和主线程交互 对视频和表格路径判空后,将视频和表格路径通过信号传递给主界面,从而在主界面中新起线程启动旁白区间检测
# 版本2.0:
# 在主窗口启动时,就启动一个QThread,专门接收该信号并进行检测, [todo] 方法2: 在主窗口启动时,就启动一个QThread,专门接收该信号并进行检测,发出该信号后,由QThread和主窗口同时接收,然后让他俩通过信号交互即可。
# 发出该信号后,由QThread和主窗口同时接收,然后让他俩通过信号交互即可。 """
if self.lineEdit.text()!="" and self.lineEdit_2.text()!="": if self.lineEdit.text()!="" and self.lineEdit_2.text()!="":
self.start_detect_signal.emit(self.lineEdit.text(), self.lineEdit_2.text()) self.start_detect_signal.emit(self.lineEdit.text(), self.lineEdit_2.text())
else: else:
......
...@@ -34,9 +34,11 @@ from main_window import MainWindow, Element ...@@ -34,9 +34,11 @@ from main_window import MainWindow, Element
up_b, down_b = 0, 0 up_b, down_b = 0, 0
# 初始化ocr工具 # 初始化ocr工具
ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False, use_gpu=False) paddle_dir = "res/.paddleocr/2.3.0.1/ocr/"
# ocr = EasyOCR() cur_cls_model_dir = paddle_dir + "cls/ch_ppocr_mobile_v2.0_cls_infer"
# ocr = ChineseOCR() cur_det_model_dir = paddle_dir + "det/ch/ch_PP-OCRv2_det_infer"
cur_rec_model_dir = paddle_dir + "rec/ch/ch_PP-OCRv2_rec_infer"
ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False, use_gpu=False, cls_model_dir=cur_cls_model_dir, det_model_dir=cur_det_model_dir, rec_model_dir=cur_rec_model_dir)
# 正常语速为4字/秒 # 正常语速为4字/秒
normal_speed = 4 normal_speed = 4
...@@ -181,14 +183,14 @@ def normalize(text: str) -> str: ...@@ -181,14 +183,14 @@ def normalize(text: str) -> str:
return text return text
def detect_subtitle(img: np.ndarray) -> Union[str, None]: def detect_subtitle(img: np.ndarray) -> Tuple[Union[str, None], float]:
"""检测当前画面得到字幕信息 """检测当前画面得到字幕信息
Args: Args:
img (np.ndarray): 当前画面 img (np.ndarray): 当前画面
Returns: Returns:
Union[str, None]: 字幕信息(没有字幕时返回None) Tuple[Union[str, None]]: 字幕信息(没有字幕时返回None)和置信度
""" """
subTitle = '' subTitle = ''
height = down_b - up_b height = down_b - up_b
...@@ -214,16 +216,18 @@ def detect_subtitle(img: np.ndarray) -> Union[str, None]: ...@@ -214,16 +216,18 @@ def detect_subtitle(img: np.ndarray) -> Union[str, None]:
gradient = np.arctan(abs((rect[1][1] - rect[0][1]) / (rect[1][0] - rect[0][0]))) gradient = np.arctan(abs((rect[1][1] - rect[0][1]) / (rect[1][0] - rect[0][0])))
# log.append("文本:{},置信度:{},中心点:{},斜率:{},字体大小:{}".format(txt, confidence, mid / img.shape[1], gradient, # log.append("文本:{},置信度:{},中心点:{},斜率:{},字体大小:{}".format(txt, confidence, mid / img.shape[1], gradient,
# font_size)) 置信度>0.7 & 斜率<0.1 & 字幕偏移量<=25 & 字幕中心在画面宽的0.4-0.6之间 # font_size)) 置信度>0.7 & 斜率<0.1 & 字幕偏移量<=25 & 字幕中心在画面宽的0.4-0.6之间
# print("文本:{},置信度:{},中心点:{},斜率:{},字体大小:{}".format(txt, confidence, mid / img.shape[1], gradient, font_size)) print("文本:{},置信度:{},中心点:{},斜率:{},字体大小:{}".format(txt, confidence, mid / img.shape[1], gradient, font_size))
# print("差距:{}".format(abs(rect[0][1] - 30) + abs(img.shape[0] - rect[2][1] - 30))) print("字体大小差距: {}", format(height - font_size))
print("高度中心:{}".format((rect[0][1] + rect[1][1])/2/img.shape[0]))
conf_thred1 = 0.7 conf_thred1 = 0.7
conf_thred2 = 0.85 conf_thred2 = 0.85
# conf_thred1 = 0.1 # conf_thred1 = 0.1
# conf_thred2 = 0.4 # conf_thred2 = 0.4
# conf_thred1 = 0.5 # conf_thred1 = 0.5
# conf_thred2 = 0.7 # conf_thred2 = 0.7
if confidence > conf_thred1 and gradient < 0.1 and 0.4 < mid / img.shape[1] < 0.6 and \ if (rect[0][1] + rect[1][1])/2/img.shape[0] > 0.5 or (rect[0][1] + rect[1][1])/2/img.shape[0] <= 0.1:
abs(rect[0][1] - 30) + abs(img.shape[0] - rect[2][1] - 30) <= font_size - 10: continue
if confidence > conf_thred1 and gradient < 0.1 and 0.4 < mid / img.shape[1] < 0.6:
subTitle += txt subTitle += txt
conf = max(conf,confidence) conf = max(conf,confidence)
# possible_txt.append([txt, mid/img.shape[1]]) # possible_txt.append([txt, mid/img.shape[1]])
......
# 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)
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
assemble\_dialog module
=======================
.. automodule:: assemble_dialog
:members:
:undoc-members:
:show-inheritance:
assemble\_dialog\_ui module
===========================
.. automodule:: assemble_dialog_ui
:members:
:undoc-members:
:show-inheritance:
import sys
from os.path import abspath, dirname
sys.path.insert(0, dirname(dirname(dirname(abspath(__file__)))))
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = '无障碍电影制作系统'
copyright = '2023, 翟艳秋'
author = '翟艳秋'
# The full version, including alpha/beta/rc tags
release = 'v2.1.0'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.apidoc',
'sphinx.ext.viewcode',
'sphinx.ext.napoleon',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.mathjax',
'rst2pdf.pdfbuilder',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'zh_CN'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
htmlhelp_basename = 'PYTHON doc'
# -- Options for LATEX output -------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
'preamble': '''
\\hypersetup{unicode=true}
\\usepackage{CJKutf8}
\\AtBeginDocument{\\begin{CJK}{UTF8}{gbsn}}
\\AtEndDocument{\\end{CJK}}
''',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Accessibility.tex', 'PYTHON Documentation',
'xxxx', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'PYTHON', 'PYTHON ',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'PYTHON', 'PYTHON',
author, 'PYTHON', 'One line description of project.',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
\ No newline at end of file
constant module
===============
.. automodule:: constant
:members:
:undoc-members:
:show-inheritance:
create\_dialog module
=====================
.. automodule:: create_dialog
:members:
:undoc-members:
:show-inheritance:
create\_dialog\_ui module
=========================
.. automodule:: create_dialog_ui
:members:
:undoc-members:
:show-inheritance:
detect\_dialog module
=====================
.. automodule:: detect_dialog
:members:
:undoc-members:
:show-inheritance:
detect\_dialog\_ui module
=========================
.. automodule:: detect_dialog_ui
:members:
:undoc-members:
:show-inheritance:
detect\_with\_asr module
========================
.. automodule:: detect_with_asr
:members:
:undoc-members:
:show-inheritance:
detect\_with\_ocr module
========================
.. automodule:: detect_with_ocr
:members:
:undoc-members:
:show-inheritance:
.. Assistant Production System of Narration For Barrier-Free Films documentation master file, created by
sphinx-quickstart on Sun Jun 18 22:25:10 2023.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Assistant Production System of Narration For Barrier-Free Films's documentation!
===========================================================================================
.. toctree::
:maxdepth: 1
:caption: Get Started
:hidden:
:glob:
.. toctree::
:maxdepth: 2
:caption: UI
:glob:
main_window_ui
assemble_dialog_ui
create_dialog_ui
detect_dialog_ui
operation_dialog_ui
prompt_dialog_ui
setting_dialog_ui
start
.. toctree::
:maxdepth: 2
:caption: funcs_for_ui
:glob:
main_window
assemble_dialog
create_dialog
detect_dialog
operation_dialog
prompt_dialog
setting_dialog
start
.. toctree::
:maxdepth: 2
:caption: API
:glob:
constant
detect_with_asr
detect_with_ocr
judge_subtitle
management
myvideoslider
myVideoWidget
mywidgetcontents
narratage_detection
render
speech_synthesis
split_wav
synthesis
utils
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
judge\_subtitle module
======================
.. automodule:: judge_subtitle
:members:
:undoc-members:
:show-inheritance:
main\_window module
===================
.. automodule:: main_window
:members:
:undoc-members:
:show-inheritance:
main\_window\_ui module
=======================
.. automodule:: main_window_ui
:members:
:undoc-members:
:show-inheritance:
management module
=================
.. automodule:: management
:members:
:undoc-members:
:show-inheritance:
accessibility_movie_2
=====================
.. toctree::
:maxdepth: 4
assemble_dialog
assemble_dialog_ui
constant
create_dialog
create_dialog_ui
detect_dialog
detect_dialog_ui
detect_with_asr
detect_with_ocr
judge_subtitle
main_window
main_window_ui
management
myVideoWidget
myvideoslider
mywidgetcontents
narratage_detection
operation_dialog
operation_dialog_ui
prompt_dialog
prompt_dialog_ui
render
setting_dialog
setting_dialog_ui
speech_synthesis
split_wav
start
synthesis
utils
myVideoWidget module
====================
.. automodule:: myVideoWidget
:members:
:undoc-members:
:show-inheritance:
myvideoslider module
====================
.. automodule:: myvideoslider
:members:
:undoc-members:
:show-inheritance:
mywidgetcontents module
=======================
.. automodule:: mywidgetcontents
:members:
:undoc-members:
:show-inheritance:
narratage\_detection module
===========================
.. automodule:: narratage_detection
:members:
:undoc-members:
:show-inheritance:
operation\_dialog module
========================
.. automodule:: operation_dialog
:members:
:undoc-members:
:show-inheritance:
operation\_dialog\_ui module
============================
.. automodule:: operation_dialog_ui
:members:
:undoc-members:
:show-inheritance:
prompt\_dialog module
=====================
.. automodule:: prompt_dialog
:members:
:undoc-members:
:show-inheritance:
prompt\_dialog\_ui module
=========================
.. automodule:: prompt_dialog_ui
:members:
:undoc-members:
:show-inheritance:
render module
=============
.. automodule:: render
:members:
:undoc-members:
:show-inheritance:
setting\_dialog module
======================
.. automodule:: setting_dialog
:members:
:undoc-members:
:show-inheritance:
setting\_dialog\_ui module
==========================
.. automodule:: setting_dialog_ui
:members:
:undoc-members:
:show-inheritance:
speech\_synthesis module
========================
.. automodule:: speech_synthesis
:members:
:undoc-members:
:show-inheritance:
split\_wav module
=================
.. automodule:: split_wav
:members:
:undoc-members:
:show-inheritance:
start module
============
.. automodule:: start
:members:
:undoc-members:
:show-inheritance:
synthesis module
================
.. automodule:: synthesis
:members:
:undoc-members:
:show-inheritance:
utils module
============
.. automodule:: utils
:members:
:undoc-members:
:show-inheritance:
"""主界面相关响应
.. code-block:: python
from main_window import MainWindow
mainWindow = MainWindow(project_path)
mainWindow.show()
"""
import time import time
import os import os
...@@ -32,6 +41,7 @@ import re ...@@ -32,6 +41,7 @@ import re
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
EXIT_CODE_REBOOT = -12345678 EXIT_CODE_REBOOT = -12345678
renew_signal = pyqtSignal(str) renew_signal = pyqtSignal(str)
def __init__(self, project_path): def __init__(self, project_path):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.setupUi(self) self.setupUi(self)
...@@ -43,7 +53,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -43,7 +53,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# todo:后续改成QThread的组件 # todo:后续改成QThread的组件
self.synthesis = SynthesisProcessor() self.synthesis = SynthesisProcessor()
self.synthesis.show_warning_signal.connect(self.show_warning_msg_box) self.synthesis.show_warning_signal.connect(self.show_warning_msg_box)
self.synthesis.synthesis_callback_signal.connect(self.deal_synthesis_callback_slot) self.synthesis.synthesis_callback_signal.connect(
self.deal_synthesis_callback_slot)
# 检测对话框 # 检测对话框
self.detect_dialog = Detect_Dialog(self.projectContext) self.detect_dialog = Detect_Dialog(self.projectContext)
self.detect_dialog.start_detect_signal.connect(self.start_detect) self.detect_dialog.start_detect_signal.connect(self.start_detect)
...@@ -59,7 +70,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -59,7 +70,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 工程导出相关组件 # 工程导出相关组件
self.export = ExportProcessor() self.export = ExportProcessor()
self.export.show_warning_signal.connect(self.show_warning_msg_box) self.export.show_warning_signal.connect(self.show_warning_msg_box)
self.export.export_callback_signal.connect(self.deal_export_callback_slot) self.export.export_callback_signal.connect(
self.deal_export_callback_slot)
# 设置框 # 设置框
self.setting_dialog = Setting_Dialog(self.projectContext) self.setting_dialog = Setting_Dialog(self.projectContext)
...@@ -67,7 +79,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -67,7 +79,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 提示框 # 提示框
self.prompt_dialog = Prompt_Dialog() self.prompt_dialog = Prompt_Dialog()
self.prompt_dialog.setModal(True) self.prompt_dialog.setModal(True)
self.continue_detect_prompt_dialog = Prompt_Dialog(self.continue_detect) self.continue_detect_prompt_dialog = Prompt_Dialog(
self.continue_detect)
# 操作框 # 操作框
self.operation_dialog = Operation_Dialog(self) self.operation_dialog = Operation_Dialog(self)
...@@ -88,13 +101,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -88,13 +101,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.export_timer.timeout.connect(self.check_if_export_over_slot) self.export_timer.timeout.connect(self.check_if_export_over_slot)
self.video_timer = QTimer() self.video_timer = QTimer()
self.video_timer.timeout.connect(self.change_videotime_label_slot) self.video_timer.timeout.connect(self.change_videotime_label_slot)
self.video_timer.start(5000) # todo 作为参数配置 self.video_timer.start(1000) # todo 作为参数配置
self.refresh_tab_timer = QTimer() self.refresh_tab_timer = QTimer()
self.refresh_tab_timer.timeout.connect(self.refresh_tab_slot) self.refresh_tab_timer.timeout.connect(self.refresh_tab_slot)
"""状态栏相关空间
"""
状态栏相关空间
""" """
self.statusbarLabel = QLabel() self.statusbarLabel = QLabel()
self.statusbarLabel.setText(" 休息中") self.statusbarLabel.setText(" 休息中")
...@@ -112,8 +124,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -112,8 +124,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.statusbar.addPermanentWidget(self.progressBar, stretch=9) self.statusbar.addPermanentWidget(self.progressBar, stretch=9)
self.statusbar.addPermanentWidget(self.progressLabel, stretch=1) self.statusbar.addPermanentWidget(self.progressLabel, stretch=1)
""" """菜单栏
菜单栏
""" """
self.setting.triggered.connect(self.show_setting_dialog) # 设置 self.setting.triggered.connect(self.show_setting_dialog) # 设置
self.action_3.triggered.connect(self.show_detect_dialog) self.action_3.triggered.connect(self.show_detect_dialog)
...@@ -138,25 +150,24 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -138,25 +150,24 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# self.action_view_history.triggered.connect(self.view_history_slot) # self.action_view_history.triggered.connect(self.view_history_slot)
self.action_operate.triggered.connect(self.show_operate_dialog) self.action_operate.triggered.connect(self.show_operate_dialog)
self.action_operate.setEnabled(False) self.action_operate.setEnabled(False)
self.action_insert_aside_from_now.triggered.connect(self.insert_aside_from_now_slot) self.action_insert_aside_from_now.triggered.connect(
self.insert_aside_from_now_slot)
self.action_insert_aside_from_now.setEnabled(False) self.action_insert_aside_from_now.setEnabled(False)
self.insert_aside_from_now_btn.clicked.connect(self.insert_aside_from_now_slot) self.insert_aside_from_now_btn.clicked.connect(
self.insert_aside_from_now_slot)
self.insert_aside_from_now_btn.setEnabled(False) self.insert_aside_from_now_btn.setEnabled(False)
"""
视频相关信息
"""
# 视频时长,全局变量
self.video_duration = None
"""视频预览相关信息
"""
self.video_duration = None # 视频时长,全局变量
self.sld_video_pressed = False # 判断当前进度条识别否被鼠标点击 self.sld_video_pressed = False # 判断当前进度条识别否被鼠标点击
self.videoFullScreen = False # 判断当前widget是否全屏 self.videoFullScreen = False # 判断当前widget是否全屏
self.videoFullScreenWidget = myVideoWidget() # 创建一个全屏的widget self.videoFullScreenWidget = myVideoWidget() # 创建一个全屏的widget
self.player = QMediaPlayer() self.player = QMediaPlayer()
self.player.setVideoOutput(self.wgt_video) # 视频播放输出的widget,就是上面定义的 self.player.setVideoOutput(self.wgt_video) # 视频播放输出的widget,就是上面定义的
self.player.durationChanged.connect(self.player_change_slot) self.player.durationChanged.connect(self.player_change_slot)
# self.btn_open.clicked.connect(self.open_excel) # 打开excel文件按钮
self.btn_play.clicked.connect(self.playVideo) # play self.btn_play.clicked.connect(self.playVideo) # play
# self.btn_stop.clicked.connect(self.pauseVideo) # pause
self.player.positionChanged.connect( self.player.positionChanged.connect(
self.changeSlide) # change Slide self.changeSlide) # change Slide
self.videoFullScreenWidget.doubleClickedItem.connect( self.videoFullScreenWidget.doubleClickedItem.connect(
...@@ -171,15 +182,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -171,15 +182,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.sld_audio.valueChanged.connect(self.volumeChange) # 控制声音播放 self.sld_audio.valueChanged.connect(self.volumeChange) # 控制声音播放
self.kd_slider.valueChanged.connect(self.scale_change_slot) self.kd_slider.valueChanged.connect(self.scale_change_slot)
""" """旁白音频预览
旁白音频预览相关信息
""" """
self.audio_player = QMediaPlayer() self.audio_player = QMediaPlayer()
# self.audio_player.stateChanged.connect(self.release_audio_file)
"""表格相关信息
"""
表格相关信息
""" """
self.curTab = 0 self.curTab = 0
# 设置表格每一列的宽度 # 设置表格每一列的宽度
...@@ -188,8 +197,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -188,8 +197,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.zm_tableWidget.setColumnCount(len(subtitle_header)) self.zm_tableWidget.setColumnCount(len(subtitle_header))
self.zm_tableWidget.setHorizontalHeaderLabels(subtitle_header) self.zm_tableWidget.setHorizontalHeaderLabels(subtitle_header)
zm_tableHeader = self.zm_tableWidget.horizontalHeader() zm_tableHeader = self.zm_tableWidget.horizontalHeader()
zm_tableHeader.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) zm_tableHeader.setSectionResizeMode(
zm_tableHeader.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) 0, QtWidgets.QHeaderView.ResizeToContents)
zm_tableHeader.setSectionResizeMode(
1, QtWidgets.QHeaderView.ResizeToContents)
zm_tableHeader.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) zm_tableHeader.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
content_header = self.projectContext.contentHeader content_header = self.projectContext.contentHeader
...@@ -198,23 +209,29 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -198,23 +209,29 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 允许打开上下文菜单 # 允许打开上下文菜单
self.all_tableWidget.setContextMenuPolicy(Qt.CustomContextMenu) self.all_tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)
# 绑定事件 # 绑定事件
self.all_tableWidget.customContextMenuRequested.connect(self.generateMenu) self.all_tableWidget.customContextMenuRequested.connect(
self.generateMenu)
all_tableHead = self.all_tableWidget.horizontalHeader() all_tableHead = self.all_tableWidget.horizontalHeader()
all_tableHead.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) all_tableHead.setSectionResizeMode(
0, QtWidgets.QHeaderView.ResizeToContents)
all_tableHead.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) all_tableHead.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
all_tableHead.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) all_tableHead.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
all_tableHead.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) all_tableHead.setSectionResizeMode(
3, QtWidgets.QHeaderView.ResizeToContents)
aside_header = self.projectContext.aside_header aside_header = self.projectContext.aside_header
self.pb_tableWidget.setColumnCount(len(aside_header)) self.pb_tableWidget.setColumnCount(len(aside_header))
self.pb_tableWidget.setHorizontalHeaderLabels(aside_header) self.pb_tableWidget.setHorizontalHeaderLabels(aside_header)
pb_tableHead = self.pb_tableWidget.horizontalHeader() pb_tableHead = self.pb_tableWidget.horizontalHeader()
pb_tableHead.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) pb_tableHead.setSectionResizeMode(
pb_tableHead.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) 0, QtWidgets.QHeaderView.ResizeToContents)
pb_tableHead.setSectionResizeMode(
1, QtWidgets.QHeaderView.ResizeToContents)
# pb_tableHead.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) # pb_tableHead.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
pb_tableHead.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) pb_tableHead.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
pb_tableHead.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) pb_tableHead.setSectionResizeMode(
3, QtWidgets.QHeaderView.ResizeToContents)
self.all_tableWidget.resizeRowsToContents() self.all_tableWidget.resizeRowsToContents()
self.pb_tableWidget.resizeRowsToContents() self.pb_tableWidget.resizeRowsToContents()
self.zm_tableWidget.resizeRowsToContents() self.zm_tableWidget.resizeRowsToContents()
...@@ -229,10 +246,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -229,10 +246,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.zm_tableWidget.itemDoubleClicked.connect(self.change_video_time) self.zm_tableWidget.itemDoubleClicked.connect(self.change_video_time)
self.all_tableWidget.itemDoubleClicked.connect(self.change_video_time) self.all_tableWidget.itemDoubleClicked.connect(self.change_video_time)
self.all_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.all_tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.all_tableWidget.itemDoubleClicked.connect(self.all_item_changed_by_double_clicked_slot) self.all_tableWidget.itemDoubleClicked.connect(
self.all_item_changed_by_double_clicked_slot)
self.pb_tableWidget.itemDoubleClicked.connect(self.writeHistory) self.pb_tableWidget.itemDoubleClicked.connect(self.writeHistory)
self.pb_tableWidget.itemDoubleClicked.connect(self.pb_item_changed_by_double_clicked_slot) self.pb_tableWidget.itemDoubleClicked.connect(
self.pb_item_changed_by_double_clicked_slot)
self.pb_tableWidget.itemChanged.connect(self.rewriteHistory) self.pb_tableWidget.itemChanged.connect(self.rewriteHistory)
# todo 现在只在【旁白】tab上双击修改,会保存表格到本地,【字幕旁白】tab上不行。(【字幕旁白】tab上无法修改旁白) # todo 现在只在【旁白】tab上双击修改,会保存表格到本地,【字幕旁白】tab上不行。(【字幕旁白】tab上无法修改旁白)
self.pb_tableWidget.itemChanged.connect(self.write2ProjectFromAside) self.pb_tableWidget.itemChanged.connect(self.write2ProjectFromAside)
...@@ -244,7 +263,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -244,7 +263,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 在进行redo_undo时,会触发itemchange,但是这时候不能覆写历史。但是需要写入project。(注意命名思路:在进行redo的时候,会有两步操作,写入history和写入project。我们只希望他不写入history,所以命名中要带有history) # 在进行redo_undo时,会触发itemchange,但是这时候不能覆写历史。但是需要写入project。(注意命名思路:在进行redo的时候,会有两步操作,写入history和写入project。我们只希望他不写入history,所以命名中要带有history)
self.can_write_history = True self.can_write_history = True
self.previewed_audio = {}
self.is_video_playing = False self.is_video_playing = False
# 表格中的内容是否被更改,需要刷新 # 表格中的内容是否被更改,需要刷新
...@@ -264,13 +282,21 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -264,13 +282,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 更新工程信息 # 更新工程信息
self.projectContext.Init(project_path) self.projectContext.Init(project_path)
self.update_ui() self.update_ui()
# 打印到log文件中 # 打印到log文件中
t = RunThread(funcName=make_print_to_file, args=os.path.join(os.getcwd(), 'log'), name="logging") t = RunThread(funcName=make_print_to_file, args=os.path.join(os.getcwd(), 'log'), name="logging")
print(t) print(t)
make_print_to_file(os.path.join(os.getcwd(),'log')) make_print_to_file(os.path.join(os.getcwd(), 'log'))
def generateMenu(self, pos): def generateMenu(self, pos):
print("pos",pos) """当用户右击字幕旁白表格时,弹出菜单项
Args:
pos : 用户右击的位置
"""
if self.curTab != 0:
return
print("pos", pos)
# 获取点击行号 # 获取点击行号
for i in self.all_tableWidget.selectionModel().selection().indexes(): for i in self.all_tableWidget.selectionModel().selection().indexes():
...@@ -287,14 +313,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -287,14 +313,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 被阻塞 # 被阻塞
action = menu.exec(screenPos) action = menu.exec(screenPos)
if action == item1: if action == item1:
print('选择了第1个菜单项',self.all_tableWidget.item(rowNum,0).text() print('选择了第1个菜单项', self.all_tableWidget.item(rowNum, 0).text(), self.all_tableWidget.item(
,self.all_tableWidget.item(rowNum,1).text() rowNum, 1).text(), self.all_tableWidget.item(rowNum, 2).text())
,self.all_tableWidget.item(rowNum,2).text())
self.del_line_operation_slot(rowNum + 1) self.del_line_operation_slot(rowNum + 1)
return return
# 重写关闭Mmainwindow窗口 # 重写关闭Mmainwindow窗口
def closeEvent(self, event): def closeEvent(self, event):
"""在用户试图关闭主界面窗口,弹窗提醒用户避免误触
Args:
event : 关闭操作
"""
# buttonBox = QtWidgets.QMessageBox() # buttonBox = QtWidgets.QMessageBox()
# btn_save_and_close = buttonBox.addButton("保存并退出", QtWidgets.QMessageBox.YesRole) # btn_save_and_close = buttonBox.addButton("保存并退出", QtWidgets.QMessageBox.YesRole)
# btn_not_save_and_close = buttonBox.addButton("不保存并退出", QtWidgets.QMessageBox.YesRole) # btn_not_save_and_close = buttonBox.addButton("不保存并退出", QtWidgets.QMessageBox.YesRole)
...@@ -312,30 +342,54 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -312,30 +342,54 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 重写改变窗口大小事件 # 重写改变窗口大小事件
def resizeEvent(self, *args, **kwargs): def resizeEvent(self, *args, **kwargs):
"""重写改变窗口大小事件,同时调整时间戳的长度
"""
super().resizeEvent(*args, **kwargs) super().resizeEvent(*args, **kwargs)
position = self.kd_slider.value() position = self.kd_slider.value()
self.scale_change_slot(position) self.scale_change_slot(position)
def show_detect_dialog(self): def show_detect_dialog(self):
"""弹出旁白区间检测相关信息填写窗口
"""
self.detect_dialog.init_self() self.detect_dialog.init_self()
self.detect_dialog.show() self.detect_dialog.show()
def show_assemble_dialog(self): def show_assemble_dialog(self):
"""弹出旁白音频合成相关信息填写窗口
"""
self.assemble_dialog.init_self() self.assemble_dialog.init_self()
self.assemble_dialog.show() self.assemble_dialog.show()
def show_setting_dialog(self): def show_setting_dialog(self):
"""弹出设置窗口
"""
self.setting_dialog.showDialog() self.setting_dialog.showDialog()
def show_create_dialog(self): def show_create_dialog(self):
"""弹出新建工程窗口
"""
self.create_dialog.show() self.create_dialog.show()
def show_warning_msg_box(self, msg: str): def show_warning_msg_box(self, msg: str):
"""弹出警示信息
Args:
msg (str): 警告信息
"""
replp = QtWidgets.QMessageBox.question(self, u'警告', msg, replp = QtWidgets.QMessageBox.question(self, u'警告', msg,
QtWidgets.QMessageBox.Yes) QtWidgets.QMessageBox.Yes)
# 在已打开一个工程的情况下打开或新建另一个工程,初始化界面 def init_project(self, project_path: str):
def init_project(self, project_path): """在已打开一个工程的情况下打开或新建另一个工程,初始化界面
Args:
project_path (str): 新工程路径
"""
if self.projectContext.project_base_dir is not None: if self.projectContext.project_base_dir is not None:
# 如果目前界面中正在处理其他工程,需要让用户确认是否退出当前界面 # 如果目前界面中正在处理其他工程,需要让用户确认是否退出当前界面
replp = QtWidgets.QMessageBox.question(self, u'警告', u'是否退出当前工程?', replp = QtWidgets.QMessageBox.question(self, u'警告', u'是否退出当前工程?',
...@@ -359,35 +413,49 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -359,35 +413,49 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.projectContext.Init(project_path) self.projectContext.Init(project_path)
self.update_ui() self.update_ui()
# 导入视频 # 导入视频
def import_slot(self): def import_slot(self):
"""导入视频,并对主界面中的相关控件进行对应更新
"""
video_path = self.openVideoFile().path() video_path = self.openVideoFile().path()
print("[import_slot] video_path=" + video_path) print("[import_slot] video_path=" + video_path)
if video_path == "" or video_path == None: if video_path == "" or video_path == None:
return return
if video_path[0] == '/': if video_path[0] == '/' and ":" in video_path:
video_path = video_path[1:] video_path = video_path[1:]
self.projectContext.video_path = video_path self.projectContext.video_path = video_path
excel_name = os.path.splitext(os.path.basename(video_path))[0] excel_name = os.path.splitext(os.path.basename(video_path))[0]
self.projectContext.excel_path = os.path.join(self.projectContext.project_base_dir, excel_name + ".xlsx") self.projectContext.excel_path = os.path.join(
self.projectContext.project_base_dir, excel_name + ".xlsx")
self.action_export.setEnabled(True) self.action_export.setEnabled(True)
self.action_operate.setEnabled(True) self.action_operate.setEnabled(True)
self.action_insert_aside_from_now.setEnabled(True) self.action_insert_aside_from_now.setEnabled(True)
self.insert_aside_from_now_btn.setEnabled(True) self.insert_aside_from_now_btn.setEnabled(True)
# todo: 后续这段代码公共的可以抽出来 # todo: 后续这段代码公共的可以抽出来
# 打开某个工程 # 打开某个工程
def open_project_slot(self): def open_project_slot(self):
project_path = QFileDialog.getExistingDirectory(self, "选择工程文件夹", os.getcwd()) """打开工程,并初始化界面
通过选择文件夹界面确认待打开的工程路径,如果未选择则无事发生,否则就根据选择的工程文件夹路径使用init_project函数更新界面
"""
project_path = QFileDialog.getExistingDirectory(
self, "选择工程文件夹", os.getcwd())
# print("[import_slot] project_path=" + project_path) # print("[import_slot] project_path=" + project_path)
if project_path == "" or project_path == None: if project_path == "" or project_path == None:
return return
self.init_project(project_path) self.init_project(project_path)
def update_ui(self): def update_ui(self):
"""更新交互界面中的空间
1. 如果表格路径不为None,则将表格内容导入到界面中;
2. 如果这个工程之前没有检测的视频,那么就需要将导入视频的按钮设置为可点,否则就直接导入对应的视频;
3. 如果之前的检测任务尚未完成,可弹窗提示用户是否需要继续检测。
"""
if self.projectContext.project_base_dir is None: if self.projectContext.project_base_dir is None:
return return
self.action_export.setEnabled(True) self.action_export.setEnabled(True)
...@@ -403,28 +471,44 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -403,28 +471,44 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.import_movie.setEnabled(True) self.import_movie.setEnabled(True)
else: else:
if not os.path.exists(video_path): if not os.path.exists(video_path):
self.prompt_dialog.show_dialog_signal.emit(f"该工程原检测的视频对应路径{video_path}目前已失效,请导入新的视频重新开始检测或移动视频到对应位置,并重新启动工程") self.prompt_dialog.show_dialog_signal.emit(
f"该工程原检测的视频对应路径{video_path}目前已失效,请导入新的视频重新开始检测或移动视频到对应位置,并重新启动工程")
self.import_movie.setEnabled(True) self.import_movie.setEnabled(True)
return
else: else:
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(video_path))) # 选取视频文件 self.player.setMedia(QMediaContent(
QUrl.fromLocalFile(video_path))) # 选取视频文件
self.playVideo() # 播放视频 self.playVideo() # 播放视频
self.action_insert_aside_from_now.setEnabled(True) self.action_insert_aside_from_now.setEnabled(True)
self.insert_aside_from_now_btn.setEnabled(True) self.insert_aside_from_now_btn.setEnabled(True)
if self.projectContext.detected == True and self.projectContext.nd_process < 1: if self.projectContext.detected == True and self.projectContext.nd_process < 1:
self.continue_detect_prompt_dialog.setModal(True) self.continue_detect_prompt_dialog.setModal(True)
self.continue_detect_prompt_dialog.show_with_msg("您的上次任务未完成,是否继续检测?") self.continue_detect_prompt_dialog.show_with_msg(
"您的上次任务未完成,是否继续检测?")
def continue_detect(self): def continue_detect(self):
print(f"继续检测,video_path={self.projectContext.video_path}, book_path={self.projectContext.excel_path}") """继续旁白区间检测任务
self.start_detect(self.projectContext.video_path, self.projectContext.excel_path)
重新调用start_detect函数继续进行旁白区间检测任务
"""
print(
f"继续检测,video_path={self.projectContext.video_path}, book_path={self.projectContext.excel_path}")
self.start_detect(self.projectContext.video_path,
self.projectContext.excel_path)
def start_detect(self, video_path, book_path): def start_detect(self, video_path: str, book_path: str):
"""检测旁白 """开始旁白区间检测
绑定到旁白推荐tab栏中的“开始检测”按钮上。 Args:
video_path (str): 待检测视频路径
book_path (str): 表格路径
绑定到旁白区间检测tab栏中的“开始检测”按钮上。
首先检测各种输入的合理性,然后更新UI中组件的状态,使用多线程调用旁白区间检测函数和进度条更新函数。 首先检测各种输入的合理性,然后更新UI中组件的状态,使用多线程调用旁白区间检测函数和进度条更新函数。
函数运行过程中实时监测函数的运行情况,如果发现函数报错,则中断线程,弹出报错窗口,否则等待函数正常结束,并更新UI中的组件。 函数运行过程中实时监测函数的运行情况,如果发现函数报错,则中断线程,弹出报错窗口,否则等待函数正常结束,并更新UI中的组件。
""" """
# 检查是否已有线程在运行旁白检测 # 检查是否已有线程在运行旁白检测
...@@ -444,21 +528,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -444,21 +528,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
elif not os.path.exists(os.path.dirname(book_path)): elif not os.path.exists(os.path.dirname(book_path)):
self.show_warning_msg_box("请重新确认表格存放路径是否正确") self.show_warning_msg_box("请重新确认表格存放路径是否正确")
return return
# todo:
# if not check_timePoint(startTime.get()):
# self.show_warning_msg_box("请确认开始时间是否正确")
# return
# elif not check_timePoint(endTime.get()):
# self.show_warning_msg_box("请确认结束时间是否正确")
# return
# self.projectContext.Init(os.path.dirname(
# book_path), os.path.basename(video_path))
if self.projectContext.excel_path is None: if self.projectContext.excel_path is None:
self.projectContext.excel_path = book_path self.projectContext.excel_path = book_path
# 获取视频的时长等信息,初始化开始结束时间 # 获取视频的时长等信息,初始化开始结束时间
startTime = "00:01:00" startTime = "00:00:20"
video = cv2.VideoCapture(video_path) video = cv2.VideoCapture(video_path)
fps = video.get(cv2.CAP_PROP_FPS) fps = video.get(cv2.CAP_PROP_FPS)
...@@ -511,16 +586,35 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -511,16 +586,35 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.detect_timer.start(5000) self.detect_timer.start(5000)
def check_if_detect_over_slot(self): def check_if_detect_over_slot(self):
"""确认旁白区间检测任务是否完成
"""
self.check_if_over("检测") self.check_if_over("检测")
def check_if_synthesis_over_slot(self): def check_if_synthesis_over_slot(self):
"""确认旁白音频合成任务是否完成
"""
self.check_if_over("合成") self.check_if_over("合成")
def check_if_export_over_slot(self): def check_if_export_over_slot(self):
"""确认自动渲染导出任务是否完成
"""
self.check_if_over("导出") self.check_if_over("导出")
# type = 检测 或 合成 或 导出 # type = 检测 或 合成 或 导出
def check_if_over(self, type): def check_if_over(self, type: str):
"""确认传入的待检测任务是否完成
Args:
type (str): 待判断的任务类型,分为"检测"、"合成"和"导出"
首先更新任务状态和对应的进度条百分比;
接下来根据待判断的任务类型确认任务是否已完成,如果任务完成了就停掉对应的timer;
如果发现线程中途退出了,说明有异常出现会弹出窗口提醒用户联系开发者处理。
"""
alive = True alive = True
print(self.state) print(self.state)
if self.state != [None]: if self.state != [None]:
...@@ -551,6 +645,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -551,6 +645,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.projectContext.nd_process = 1 self.projectContext.nd_process = 1
def deal_synthesis_callback_slot(self, threads, state): def deal_synthesis_callback_slot(self, threads, state):
"""实现旁白音频合成任务状态在界面中的实时显示,更新界面中的对应变量,每5s更新一次任务状态
Args:
threads : 执行旁白音频合成的线程
state (list) : 任务执行状态
"""
self.statusbarLabel.setText(" 准备合成:") self.statusbarLabel.setText(" 准备合成:")
self.progressBar.setValue(0) self.progressBar.setValue(0)
self.state = self.synthesis.state self.state = self.synthesis.state
...@@ -558,124 +658,142 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -558,124 +658,142 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.synthesis_timer.start(5000) self.synthesis_timer.start(5000)
def deal_export_callback_slot(self, threads, state): def deal_export_callback_slot(self, threads, state):
"""实现自动渲染导出任务状态在界面中的实时显示,更新界面中的对应变量,每5s更新一次任务状态
Args:
threads : 执行自动渲染导出的线程
state (list) : 任务执行状态
"""
self.statusbarLabel.setText(" 准备导出:") self.statusbarLabel.setText(" 准备导出:")
self.progressBar.setValue(0) self.progressBar.setValue(0)
self.state = self.export.state self.state = self.export.state
self.threads = self.export.threads self.threads = self.export.threads
self.export_timer.start(1000) self.export_timer.start(1000)
""" def emit_scale_change_slot(self):
刻度相关 """根据刻度改变时间轴的长度
原期望效果:在最左刻度时,恰好打满【时间轴区】,当最右刻度时,时间轴上每一刻度对应的时间是1s. 获取刻度的值,并据此更新时间轴的长度
目前的期望效果:在最左刻度时,时间轴区中一格刻度显示为6s左右(即一条长段为1分钟);在最右刻度时,时间轴中一格刻度显示为0.2s。
""" """
def emit_scale_change_slot(self):
position = self.kd_slider.value() position = self.kd_slider.value()
self.scale_change_slot(position) self.scale_change_slot(position)
def scale_change_slot(self, position): def scale_change_slot(self, position: int):
"""根据刻度的当前选值改变时间轴长度
Args:
position (int): 目前刻度的选值
原期望效果:在最左刻度时,恰好打满【时间轴区】,当最右刻度时,时间轴上每一刻度对应的时间是1s。
目前的期望效果:在最左刻度时,时间轴区中一格刻度显示为6s左右(即一条长段为1分钟);在最右刻度时,时间轴中一格刻度显示为0.2s。
"""
if self.player.duration() == 0: if self.player.duration() == 0:
area_width = self.scrollArea.width()
self.sld_video.resize(int(area_width + 10),
self.sld_video.height())
self.scrollAreaWidgetContents.resize(int(area_width + 20), self.scrollAreaWidgetContents.height())
return return
area_width = self.scrollArea.width() area_width = self.scrollArea.width()
# max_sld_video_size = 50 * (self.player.duration() / 1000) # 50 * 视频秒数
# min_sld_video_size = self.player.duration() / (6 * 1000)
max_sld_video_size = 10 * (self.player.duration() / 1000) # 10 * 视频秒数 max_sld_video_size = 10 * (self.player.duration() / 1000) # 10 * 视频秒数
min_sld_video_size = area_width min_sld_video_size = area_width
magnification = round( magnification = round(
position / self.kd_slider.maximum() * 100) # [1,100] position / self.kd_slider.maximum() * 100) # [1,100]
now_sld_video_size = min_sld_video_size + \ now_sld_video_size = min_sld_video_size + \
((magnification - 1) * (max_sld_video_size - min_sld_video_size) / 99) # float ((magnification - 1) * (max_sld_video_size - min_sld_video_size) / 99) # float
if min_sld_video_size > max_sld_video_size: if min_sld_video_size > max_sld_video_size:
now_sld_video_size = min_sld_video_size - 10 now_sld_video_size = min_sld_video_size - 10
print("before====") print("before====")
print("self.sld_video", self.sld_video.size()) print("self.sld_video", self.sld_video.size())
print("self.scrollAreaWidgetContents", print("self.scrollAreaWidgetContents",
self.scrollAreaWidgetContents.size()) self.scrollAreaWidgetContents.size())
print("self.sld_video.maximum()", self.sld_video.maximum())
print("self.sld_video", self.sld_video.size())
self.sld_video.resize(int(now_sld_video_size + 10), self.sld_video.resize(int(now_sld_video_size + 10),
self.sld_video.height()) self.sld_video.height())
print("self.sld_video.maximum()", self.sld_video.maximum())
self.scrollAreaWidgetContents.resize( self.scrollAreaWidgetContents.resize(
int(now_sld_video_size + 20), self.scrollAreaWidgetContents.height()) int(now_sld_video_size + 20), self.scrollAreaWidgetContents.height())
print("after====") print("after====")
print("self.sld_video", self.sld_video.size()) print("self.sld_video", self.sld_video.size())
print("self.scrollAreaWidgetContents", self.scrollAreaWidgetContents.size()) print("self.scrollAreaWidgetContents",
# print("min_sld_video_size", min_sld_video_size, "magnification", magnification, "max_sld_video_size", max_sld_video_size) self.scrollAreaWidgetContents.size())
# print("now_sld_video_size", now_sld_video_size)
# print("计算", ((magnification-1) * (max_sld_video_size - min_sld_video_size) / 99))
""" def volumeChange(self, position: int):
video相关 """根据当前音量条调整音量
"""
def volumeChange(self, position): Args:
position (int): 当前音量条的选值
"""
volume = round(position/self.sld_audio.maximum()*100) volume = round(position/self.sld_audio.maximum()*100)
print("vlume %f" % volume) print("vlume %f" % volume)
self.player.setVolume(volume) self.player.setVolume(volume)
self.lab_audio.setText("音量:"+str(volume)+"%") self.lab_audio.setText("音量:" + str(volume) + "%")
# postion 取值[0,self.sld_video.maximum()] # postion 取值[0,self.sld_video.maximum()]
def clickedSlider(self, position): def clickedSlider(self, position: int):
"""点击时间轴时,对应切换视频播放进度
Args:
position (int): 浮标在时间轴上的位置
根据当前浮标在时间轴上的位置对应切换视频的播放进度,并更新视频播放进度标签
"""
if self.player.duration() > 0: # 开始播放后才允许进行跳转 if self.player.duration() > 0: # 开始播放后才允许进行跳转
self.init_previewed_audio()
video_position = int( video_position = int(
(position / self.sld_video.maximum()) * self.player.duration()) (position / self.sld_video.maximum()) * self.player.duration())
self.player.setPosition(video_position) self.player.setPosition(video_position)
self.lab_video.setText(utils.transfer_second_to_time(str(round(video_position/1000,2)))) self.lab_video.setText(utils.transfer_second_to_time(
str(round(video_position/1000, 2))))
else: else:
self.sld_video.setValue(0) self.sld_video.setValue(0)
# position 取值[0, self.sld_video.maximum()] # position 取值[0, self.sld_video.maximum()]
def moveSlider(self, position): def moveSlider(self, position: int):
"""移动时间轴上的浮标时,对应切换视频播放进度
Args:
position (int): 浮标在时间轴上的位置
根据当前浮标在时间轴上的位置切换对应视频时间
"""
self.sld_video_pressed = True self.sld_video_pressed = True
if self.player.duration() > 0: # 开始播放后才允许进行跳转 if self.player.duration() > 0: # 开始播放后才允许进行跳转
self.init_previewed_audio()
video_position = int( video_position = int(
(position / self.sld_video.maximum()) * self.player.duration()) (position / self.sld_video.maximum()) * self.player.duration())
self.player.setPosition(video_position) self.player.setPosition(video_position)
self.lab_video.setText(utils.transfer_second_to_time(str(round(video_position/1000,2)))) self.lab_video.setText(utils.transfer_second_to_time(
str(round(video_position/1000, 2))))
def pressSlider(self): def pressSlider(self):
"""更新按压时间轴的状态,此时用户按住了时间轴上的浮标
"""
self.sld_video_pressed = True self.sld_video_pressed = True
def releaseSlider(self): def releaseSlider(self):
"""更新按压时间轴的状态,此时用户释放了时间轴上的浮标
"""
self.sld_video_pressed = False self.sld_video_pressed = False
# position 取值[0,总时长] def changeSlide(self, position: int):
def changeSlide(self, position): """根据目前视频的播放进度更新时间轴上的浮标位置和对应的时间戳
if not self.sld_video_pressed: # 进度条被鼠标点击时不更新
self.vidoeLength = self.player.duration()+0.1
self.sld_video.setValue(round((position/self.vidoeLength)*self.sld_video.maximum()))
self.lab_video.setText(utils.transfer_second_to_time(str(round(position/1000,2))))
Args:
position (int): 视频播放进度,取值[0,总时长]
""" """
# 播放音频 if not self.sld_video_pressed: # 进度条被鼠标点击时不更新
# 0、视频必须在播放中 self.vidoeLength = self.player.duration() + 0.1
if self.is_video_playing == False: self.sld_video.setValue(
return round((position/self.vidoeLength)*self.sld_video.maximum()))
# 1、先找到要播放的音频(一个电影最多2000条字幕或旁白) self.lab_video.setText(utils.transfer_second_to_time(
# todo: 验证2k条的旁白,O(n)找到待播放音频,会不会很慢 str(round(position/1000, 2))))
audio_path = None
for i in range(len(self.projectContext.aside_list)-1, -1, -1):
if position/1000 > float(self.projectContext.aside_list[i].st_time_sec):
audio_path = os.path.dirname(self.projectContext.excel_path) + (
"/tmp/%.2f.wav" % float(self.projectContext.aside_list[i].st_time_sec))
break
# 2、如果找到了该音频并且该次预览中没有播放过,则新起一个线程播放
if audio_path != None and os.path.exists(audio_path) and os.path.basename(audio_path) not in self.previewed_audio:
t = RunThread(funcName=self.play_audio,
args=(audio_path, self.previewed_audio),
name="play_audio")
t.start()
self.all_threads.append(t)
print("previewed_audio:", self.previewed_audio)
"""
# 一条语音的最长播放时间是10秒 def play_audio(self, path: str):
def play_audio(self, path, previewed_audio): """播放旁白音频
Args:
path (str): 待播放旁白音频的路径
"""
# 如果没有该音频,则直接return # 如果没有该音频,则直接return
if not os.path.exists(path): if not os.path.exists(path):
return return
...@@ -684,11 +802,16 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -684,11 +802,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.audio_player.setMedia(content) self.audio_player.setMedia(content)
self.audio_player.play() self.audio_player.play()
print("播放", path) print("播放", path)
previewed_audio[os.path.basename(path)] = 1
def openVideoFile(self): def openVideoFile(self):
path = QFileDialog.getOpenFileUrl(self, "选择待导入视频", QUrl(os.getcwd()), "Video Files(*.mp4 *.rmvb *mkv *avi *.);; 所有文件(*.*)")[0] """导入视频
print(path.url()[8:])
会弹出一个选择视频的界面,该界面以工程所在文件夹为界面起点,用户可切换至其他位置选择待处理的视频,界面中只显示mp4、rmvb、mkv和avi格式的文件(用户也可以切换至“所有文件”选择其他格式的文件)。
"""
path = QFileDialog.getOpenFileUrl(self, "选择待导入视频", QUrl(
os.getcwd()), "Video Files(*.mp4 *.rmvb *mkv *avi *.);; 所有文件(*.*)")[0]
# print(path.url()[8:])
if len(path.url()) == 0 or not os.path.exists(path.url()[8:]): if len(path.url()) == 0 or not os.path.exists(path.url()[8:]):
return path return path
self.player.setMedia(QMediaContent(path)) # 选取视频文件 self.player.setMedia(QMediaContent(path)) # 选取视频文件
...@@ -699,57 +822,63 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -699,57 +822,63 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return path return path
# 在初始化工程时 # 在初始化工程时
def init_ProjectContext_VideoPath(self, path): def init_ProjectContext_VideoPath(self, path: str):
"""初始化工程的待处理视频路径
Args:
path (str): 视频路径
"""
# video_dir = os.path.dirname(path) # video_dir = os.path.dirname(path)
# video_name = os.path.basenampath) # self.projectContext.Init(video_dir, video_name) # video_name = os.path.basenampath) # self.projectContext.Init(video_dir, video_name)
self.projectContext.setVideoPath(path) self.projectContext.setVideoPath(path)
def init_previewed_audio(self):
self.previewed_audio = {}
def playVideo(self): def playVideo(self):
"""播放视频
"""
if not self.player.media().isNull(): if not self.player.media().isNull():
# 正在播放中,那就是暂停 # 正在播放中,那就是暂停
if self.is_video_playing is True: if self.is_video_playing is True:
self.player.pause() self.player.pause()
self.is_video_playing = False self.is_video_playing = False
self.btn_play.setIcon(QIcon('res\images\播放.svg')) self.btn_play.setIcon(QIcon('res\images\播放.svg'))
self.init_previewed_audio()
else: else:
self.player.play() self.player.play()
self.is_video_playing = True self.is_video_playing = True
self.btn_play.setIcon(QIcon("res\images\暂停.svg")) self.btn_play.setIcon(QIcon("res\images\暂停.svg"))
self.init_previewed_audio()
def videoDoubleClicked(self, text): def videoDoubleClicked(self, text):
"""双击视频进行全屏转换
"""
if self.player.duration() > 0: # 开始播放后才允许进行全屏操作 if self.player.duration() > 0: # 开始播放后才允许进行全屏操作
if self.videoFullScreen: if self.videoFullScreen:
self.player.setVideoOutput(self.wgt_video) self.player.setVideoOutput(self.wgt_video)
self.playVideo()
self.videoFullScreenWidget.hide() self.videoFullScreenWidget.hide()
self.videoFullScreen = False self.videoFullScreen = False
else: else:
self.videoFullScreenWidget.show() self.videoFullScreenWidget.show()
self.player.setVideoOutput(self.videoFullScreenWidget) self.player.setVideoOutput(self.videoFullScreenWidget)
self.playVideo()
self.videoFullScreenWidget.setFullScreen(1) self.videoFullScreenWidget.setFullScreen(1)
self.videoFullScreen = True self.videoFullScreen = True
def open_excel_with_project_path(self): def open_excel_with_project_path(self):
"""打开工程下的表格,并使用该内容初始化界面中的表格
"""
self.projectContext.load_excel_from_path() self.projectContext.load_excel_from_path()
self.all_tableWidget_idx = 0 self.all_tableWidget_idx = 0
self.pb_tableWidget_idx = 0 self.pb_tableWidget_idx = 0
self.zm_tableWidget_idx = 0 self.zm_tableWidget_idx = 0
self.set_table_to_window() self.set_table_to_window()
def set_table_to_window(self, need_refresh_all = True, user_operation = False): def set_table_to_window(self, need_refresh_all=True, user_operation=False):
""" """使用表格内容初始化主界面中的表格
把projectContext里的内容呈现在UI上。
在初始化工程时,need_refresh_all = True,代表是从无到有的加载,决定了表格更新的起始坐标。 Args:
user_operation代表是否是用户双击进行的操作,此时需要initial_ing置为true。 need_refresh_all (bool, optional): 在初始化工程时,need_refresh_all = True,代表是从无到有的加载,决定了表格更新的起始坐标。Defaults to True.
user_operation=False的情况:因为添加一行而进行的被迫修改 user_operation (bool, optional): user_operation代表是否是用户双击进行的操作,此时需要initial_ing置为true。置为False是:因为添加一行而进行的被迫修改
user_operation=True的情况:用户双击操作表格,通过【操作表格】进行增删改,通过【在此处添加旁白】来添加一行 置为True时:用户双击操作表格,通过【操作表格】进行增删改,通过【在此处添加旁白】来添加一行。Defaults to False.
initial_ing为true时,不会生成音频,不会写入历史记录
""" """
if not user_operation: if not user_operation:
self.projectContext.initial_ing = True self.projectContext.initial_ing = True
...@@ -783,9 +912,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -783,9 +912,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# self.zm_tableWidget.resizeRowsToContents() # self.zm_tableWidget.resizeRowsToContents()
if not user_operation: if not user_operation:
# initial_ing为true时,不会生成音频,不会写入历史记录
self.projectContext.initial_ing = False self.projectContext.initial_ing = False
def setElememtToTable(self, table: QTableWidget, elem: Element, idx: int): def setElememtToTable(self, table: QTableWidget, elem: Element, idx: int):
"""在表中添加一行
Args:
table (QTableWidget): 待操作的表格
elem (Element): 待添加的信息
idx (int): 行号(从0开始)
"""
elem_list = elem.to_list() elem_list = elem.to_list()
time_format_col_list = [] time_format_col_list = []
if table.objectName() == constant.Content.ObjectName: if table.objectName() == constant.Content.ObjectName:
...@@ -803,7 +940,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -803,7 +940,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
btn.clicked.connect(self.audio_preview_slot) btn.clicked.connect(self.audio_preview_slot)
table.setCellWidget(idx, col, btn) table.setCellWidget(idx, col, btn)
for j in range(len(elem_list)): for j in range(len(elem_list)):
text = elem_list[j] text = elem_list[j]
if type(text) == str and '插入旁白,推荐' in text: if type(text) == str and '插入旁白,推荐' in text:
...@@ -814,7 +950,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -814,7 +950,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
text = text[0] text = text[0]
# text = text[text.index('推荐'):] # text = text[text.index('推荐'):]
# 需要格式化成hh:mm:ss格式 # 需要格式化成hh:mm:ss格式
if j in time_format_col_list and type(text)==str and len(text) != 0: if j in time_format_col_list and type(text) == str and len(text) != 0:
text = utils.transfer_second_to_time(text) text = utils.transfer_second_to_time(text)
if table.objectName() == constant.Aside.ObjectName and j == constant.Aside.SpeedColumnNumber: if table.objectName() == constant.Aside.ObjectName and j == constant.Aside.SpeedColumnNumber:
qcombo = QtWidgets.QComboBox() qcombo = QtWidgets.QComboBox()
...@@ -831,8 +967,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -831,8 +967,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 只有Content页的字幕列和 Aside页的字幕列 可编辑 # 只有Content页的字幕列和 Aside页的字幕列 可编辑
def audio_preview_slot(self): def audio_preview_slot(self):
"""音频预览,会同步播放视频,并更新视频信息相关组件
"""
btn = self.sender() btn = self.sender()
# 法1:按照物理位置。这样的结果不太对 # 法1:按照物理位置。这样的结果不太对
idx = self.pb_tableWidget.indexAt(btn.pos()) idx = self.pb_tableWidget.indexAt(btn.pos())
...@@ -853,8 +991,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -853,8 +991,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return return
# 2、如果找到了该音频,则新起一个线程播放 # 2、如果找到了该音频,则新起一个线程播放
if audio_path != None and os.path.exists(audio_path): if audio_path != None and os.path.exists(audio_path):
print(audio_path)
t = RunThread(funcName=self.play_audio, t = RunThread(funcName=self.play_audio,
args=(audio_path, self.previewed_audio), args=(audio_path, ),
name="play_audio") name="play_audio")
t.start() t.start()
self.all_threads.append(t) self.all_threads.append(t)
...@@ -866,6 +1005,16 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -866,6 +1005,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.prompt_dialog.show_with_msg("暂无音频可供预览,请重新生成") self.prompt_dialog.show_with_msg("暂无音频可供预览,请重新生成")
def checkIfTableItemCanChange(self, table: QTableWidget, i: int, j: int): def checkIfTableItemCanChange(self, table: QTableWidget, i: int, j: int):
"""确认单元格是否可编辑
Args:
table (QTableWidget): 待操作的表格
i (int): 单元格对应行
j (int): 单元格对应列
Returns:
bool: True(可编辑) or False(不可编辑)
"""
if table.objectName() == self.all_tableWidget.objectName(): if table.objectName() == self.all_tableWidget.objectName():
return True return True
if table.objectName() == self.pb_tableWidget.objectName() and j in constant.Aside.ActivateColumns: if table.objectName() == self.pb_tableWidget.objectName() and j in constant.Aside.ActivateColumns:
...@@ -873,6 +1022,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -873,6 +1022,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return False return False
def save_project(self): def save_project(self):
"""保存工程
"""
err_info = self.projectContext.save_project(True) err_info = self.projectContext.save_project(True)
if err_info == None: if err_info == None:
self.prompt_dialog.show_dialog_signal.emit("保存工程成功") self.prompt_dialog.show_dialog_signal.emit("保存工程成功")
...@@ -881,6 +1033,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -881,6 +1033,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
"保存工程失败!\n错误为:" + err_info) "保存工程失败!\n错误为:" + err_info)
def change_video_time(self, item): def change_video_time(self, item):
"""根据双击起始时间,切换视频当前播放位置
Args:
item : 双击的单元格
"""
if item is None: if item is None:
print("WARNING!!!") print("WARNING!!!")
return return
...@@ -889,21 +1046,34 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -889,21 +1046,34 @@ class MainWindow(QMainWindow, Ui_MainWindow):
print("row, col = %s, %s" % (row, col)) print("row, col = %s, %s" % (row, col))
text = item.text() # 获取内容 text = item.text() # 获取内容
self.init_previewed_audio()
# 停下旁白预览 # 停下旁白预览
self.audio_player.setMedia(QMediaContent()) self.audio_player.setMedia(QMediaContent())
if self.checkIfVideoTimeCanChange(row, col): if self.checkIfVideoTimeCanChange(col):
self.video_timer.stop() self.video_timer.stop()
self.video_timer.start(1000) # 双击的时候,就重启计时器,避免他跳转回video.position的地方去。 # 双击的时候,就重启计时器,避免他跳转回video.position的地方去。
self.video_timer.start(1000)
sec_float = utils.trans_to_seconds(text) sec_float = utils.trans_to_seconds(text)
self.player.setPosition(int(float(sec_float)*1000)) self.player.setPosition(int(float(sec_float)*1000))
def checkIfVideoTimeCanChange(self, row, col): def checkIfVideoTimeCanChange(self, col: int) -> bool:
"""检查双击的单元格是否为起始时间列中的单元格
Args:
col (int): 列号
Returns:
bool: True(是时间格式的单元格) or False(不是时间格式的单元格)
"""
if col == constant.Aside.StartTimeColumn: if col == constant.Aside.StartTimeColumn:
return True return True
return False return False
def writeHistory(self, item): def writeHistory(self, item):
"""保存在主界面表格中对旁白文本的修改
Args:
item : 被修改的单元格
"""
if self.can_write_history == False: if self.can_write_history == False:
self.can_write_history = True self.can_write_history = True
return return
...@@ -919,22 +1089,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -919,22 +1089,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if col == constant.Aside.AsideColumnNumber: if col == constant.Aside.AsideColumnNumber:
self.projectContext.history_push(row, text, text) self.projectContext.history_push(row, text, text)
# def writeHistoryFromContent(self, item):
# print("writeHistoryFromContent")
# if item is None:
# print("WRONG!!!! item Is None")
# return
# else:
# row = item.row() # 获取行数
# col = item.column() # 获取列数 注意是column而不是col哦
# text = item.text() # 获取内容
# if col == constant.Content.ActivateColumn:
#
# self.projectContext.history_push(row, text, text)
def generate_audio_slot(self, item): def generate_audio_slot(self, item):
""" """生成临时旁白音频
在set表格的时候(初始化),不会触发。只有双击修改的时候才会触发
Args:
item : 被选中的单元格
在set表格的时候(初始化),不会触发。只有双击修改或切换语速时才会触发
""" """
if self.projectContext.initial_ing == True: if self.projectContext.initial_ing == True:
return return
...@@ -960,9 +1122,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -960,9 +1122,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 合成这一段语音 # 合成这一段语音
self.do_generate_audio_by_aside_row(int(row)) self.do_generate_audio_by_aside_row(int(row))
def do_generate_audio_by_aside_row(self, row): def do_generate_audio_by_aside_row(self, row: int):
""" """根据行号生成对应旁白文本的临时音频
传入pb_tableWidget的一个下标,生成对应音频
Args:
row (int): 旁白表格中的行号
传入pb_tableWidget中的行号,生成对应音频
""" """
from speech_synthesis import speech_synthesis, Speaker, choose_speaker from speech_synthesis import speech_synthesis, Speaker, choose_speaker
audio_dir = os.path.dirname(self.projectContext.excel_path) audio_dir = os.path.dirname(self.projectContext.excel_path)
...@@ -998,14 +1164,31 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -998,14 +1164,31 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.all_threads.append(t) self.all_threads.append(t)
def is_user_editing(self): def is_user_editing(self):
"""确认是否为编辑状态
Returns:
bool: True(处于编辑状态) or False(处于非编辑状态)
"""
return self.user_editing_content or self.user_editing_aside return self.user_editing_content or self.user_editing_aside
def set_user_edit(self, val:bool): def set_user_edit(self, val: bool):
"""设置用户编辑状态
Args:
val (bool): True or False
将用户编辑状态设置为统一状态
"""
print("set_user_edit: ", val) print("set_user_edit: ", val)
self.user_editing_content = val self.user_editing_content = val
self.user_editing_aside = val self.user_editing_aside = val
def rewriteHistory(self, item): def rewriteHistory(self, item):
"""旁白发生变化时,记录对应变化为history,方便进行“撤销”和“重做”
Args:
item : 旁白表格中发生变化的单元格
"""
if self.projectContext.initial_ing == True: if self.projectContext.initial_ing == True:
return return
if self.is_user_editing() == False: if self.is_user_editing() == False:
...@@ -1073,6 +1256,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1073,6 +1256,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# self.projectContext.history_push(opt.row, opt.old_str, text) # self.projectContext.history_push(opt.row, opt.old_str, text)
def write2ProjectFromAside(self, item): def write2ProjectFromAside(self, item):
"""将表格中修改的内容更新至工程中
Args:
item: 修改的单元格
"""
if self.projectContext.initial_ing == True: if self.projectContext.initial_ing == True:
return return
if self.is_user_editing() == False: if self.is_user_editing() == False:
...@@ -1090,29 +1278,22 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1090,29 +1278,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 更新【字幕旁白】这个tab里的字,如果是语速,那就更新语速这一列,如果是旁白,那就更新旁白这一列 # 更新【字幕旁白】这个tab里的字,如果是语速,那就更新语速这一列,如果是旁白,那就更新旁白这一列
print("行号", row) print("行号", row)
print("开始时间", self.projectContext.aside_list[row].st_time_sec) print("开始时间", self.projectContext.aside_list[row].st_time_sec)
idx = self.projectContext.aside_subtitle_2contentId(self.projectContext.aside_list[row]) idx = self.projectContext.aside_subtitle_2contentId(
self.projectContext.aside_list[row])
print("对应index", idx) print("对应index", idx)
if col == constant.Aside.AsideColumnNumber: if col == constant.Aside.AsideColumnNumber:
self.all_tableWidget.setItem(int(idx), constant.Content.AsideColumnNumber, QTableWidgetItem(text)) self.all_tableWidget.setItem(
int(idx), constant.Content.AsideColumnNumber, QTableWidgetItem(text))
self.projectContext.refresh_aside(row, text) self.projectContext.refresh_aside(row, text)
elif col == constant.Aside.SpeedColumnNumber: elif col == constant.Aside.SpeedColumnNumber:
self.all_tableWidget.setItem(int(idx), constant.Content.SpeedColumnNumber, QTableWidgetItem(text)) self.all_tableWidget.setItem(
int(idx), constant.Content.SpeedColumnNumber, QTableWidgetItem(text))
self.projectContext.refresh_aside_speed(row, text) self.projectContext.refresh_aside_speed(row, text)
# def write2ProjectFromContent(self, item):
# print("write2ProjectFromContent")
# if item is None:
# print("WRONG!!!! item Is None")
# return
# else:
# row = item.row() # 获取行数
# col = item.column() # 获取列数 注意是column而不是col哦
# text = item.text() # 获取内容
#
# if col == constant.Content.ActivateColumn:
# self.projectContext.refresh_element(row, text)
def undo_slot(self): def undo_slot(self):
"""撤销之前对表格内容的修改操作
"""
self.can_write_history = False self.can_write_history = False
self.user_editing_aside = True self.user_editing_aside = True
record = self.projectContext.history_pop() record = self.projectContext.history_pop()
...@@ -1125,6 +1306,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1125,6 +1306,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.action_redo.setEnabled(True) self.action_redo.setEnabled(True)
def redo_slot(self): def redo_slot(self):
"""重做之前撤销的对表格内容的修改操作
"""
self.can_write_history = False self.can_write_history = False
self.user_editing_aside = True self.user_editing_aside = True
record = self.projectContext.history_redo() record = self.projectContext.history_redo()
...@@ -1136,8 +1320,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1136,8 +1320,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.pb_tableWidget.setItem( self.pb_tableWidget.setItem(
row, constant.Aside.AsideColumnNumber, item) row, constant.Aside.AsideColumnNumber, item)
# debug用的,现在暂时没啥用
def view_history_slot(self): def view_history_slot(self):
"""查看历史操作
debug用的,现在暂时没啥用
"""
print("=="*10) print("=="*10)
print("self.sld_video.maximumSize", self.sld_video.maximumSize(), print("self.sld_video.maximumSize", self.sld_video.maximumSize(),
"self.sld_video.maximum", self.sld_video.maximum()) "self.sld_video.maximum", self.sld_video.maximum())
...@@ -1151,22 +1339,31 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1151,22 +1339,31 @@ class MainWindow(QMainWindow, Ui_MainWindow):
print("=="*10) print("=="*10)
def change_videotime_label_slot(self): def change_videotime_label_slot(self):
"""改变视频的当前播放时间
"""
# print("in [change_videotime_label_slot]") # print("in [change_videotime_label_slot]")
if self.player.duration() > 0: # 开始播放后才执行 if self.player.duration() > 0: # 开始播放后才执行
self.change_videotime_label() self.change_videotime_label()
self.change_table_select_rows() self.change_table_select_rows()
def change_videotime_label(self): def change_videotime_label(self):
position = self.player.position()/1000 """根据当前视频的播放时间对应改变时间标签
duration = self.player.duration()/1000
"""
position = self.player.position() / 1000
duration = self.player.duration() / 1000
cur_time = utils.transfer_second_to_time(str(position))[:8] cur_time = utils.transfer_second_to_time(str(position))[:8]
duration_time = utils.transfer_second_to_time(str(duration))[:8] duration_time = utils.transfer_second_to_time(str(duration))[:8]
self.label_2.setText(cur_time + "/" + duration_time) self.label_2.setText(cur_time + "/" + duration_time)
# video_timer触发timeout信号的时候,会执行该函数,修改高亮行
def change_table_select_rows(self): def change_table_select_rows(self):
"""根据视频播放时间改变当前表格的高亮行
video_timer触发timeout信号的时候,会执行该函数,修改高亮行
"""
cur_time = self.player.position() / 1000 cur_time = self.player.position() / 1000
if self.curTab == 0: if self.curTab == 0:
all_elements = self.projectContext.all_elements all_elements = self.projectContext.all_elements
...@@ -1188,20 +1385,32 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1188,20 +1385,32 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# break # break
def player_change_slot(self, new_duration: int): def player_change_slot(self, new_duration: int):
# 在【打开工程】操作的时候,设置sld_video的最大值为电影秒数。 """改变视频的时长标签
Args:
new_duration (int): 视频总时长
在【打开工程】操作的时候,设置sld_video的最大值为电影秒数。
"""
print("打开工程:self.player.duration()", self.player.duration()) print("打开工程:self.player.duration()", self.player.duration())
print("设置最大值为:", self.player.duration()/1000 + 1) print("设置最大值为:", self.player.duration()/1000 + 1)
self.sld_video.setMaximum(self.player.duration()/1000 + 1) self.sld_video.setMaximum(self.player.duration()/1000 + 1)
def refresh_tab_slot(self): def refresh_tab_slot(self):
""" """刷新整个表格
刷新整个表格
将表格内容更新至界面中,并保存当前工程内容
""" """
self.set_table_to_window(need_refresh_all=False) self.set_table_to_window(need_refresh_all=False)
self.projectContext.save_project(False) self.projectContext.save_project(False)
def export_all(self): def export_all(self):
# 存放合成音频的文件夹被命名为output """导出无障碍电影视频
存放合成音频的文件夹被命名为output,通过export对象中的export_slot函数完成自动渲染导出,如果此时尚未有合成音频,会弹窗提示用户。
"""
output_dir = os.path.join(self.projectContext.project_base_dir, "output") output_dir = os.path.join(self.projectContext.project_base_dir, "output")
if os.path.exists(output_dir) and len(os.listdir(output_dir)) > 0: if os.path.exists(output_dir) and len(os.listdir(output_dir)) > 0:
self.export.export_slot(self.projectContext.video_path, output_dir) self.export.export_slot(self.projectContext.video_path, output_dir)
...@@ -1209,9 +1418,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1209,9 +1418,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.prompt_dialog.show_with_msg("暂时无合成音频,请至少生成一条\n 旁白音频后再尝试导出") self.prompt_dialog.show_with_msg("暂时无合成音频,请至少生成一条\n 旁白音频后再尝试导出")
def show_operate_dialog(self): def show_operate_dialog(self):
"""展示“操作表格”界面
"""
self.operation_dialog.show() self.operation_dialog.show()
def insert_aside_from_now_slot(self): def insert_aside_from_now_slot(self):
"""在当前位置插入旁白
根据当前时间找到表格中合适插入的位置,然后在对应位置添加旁白
"""
if self.player.duration() == 0 or self.projectContext.project_base_dir in [None, ""]: if self.player.duration() == 0 or self.projectContext.project_base_dir in [None, ""]:
self.prompt_dialog.show_with_msg("插入失败!未检测到视频或工程!") self.prompt_dialog.show_with_msg("插入失败!未检测到视频或工程!")
return return
...@@ -1222,13 +1439,19 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1222,13 +1439,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 其实end_time目前是没啥用的,可以删掉了 # 其实end_time目前是没啥用的,可以删掉了
print("cur_lens", len(self.projectContext.all_elements)) print("cur_lens", len(self.projectContext.all_elements))
if idx < len(self.projectContext.all_elements) - 1: if idx < len(self.projectContext.all_elements) - 1:
self.add_line_operation_slot(idx, str(cur_time), self.projectContext.all_elements[idx+1].st_time_sec, self.add_line_operation_slot(idx, str(cur_time), self.projectContext.all_elements[idx+1].st_time_sec, "", "插入旁白,推荐字数为0", "", self.projectContext.speaker_speed)
"", "插入旁白,推荐字数为0", "",self.projectContext.speaker_speed)
else: else:
self.add_line_operation_slot(idx, str(cur_time), str(cur_time+1), self.add_line_operation_slot(idx, str(cur_time), str(cur_time+1), "", "插入旁白,推荐字数为0", "", self.projectContext.speaker_speed)
"", "插入旁白,推荐字数为0", "",self.projectContext.speaker_speed)
def calculate_element_row(self, cur_time: float):
"""确认表格中适合cur_time的插入位置
def calculate_element_row(self, cur_time): Args:
cur_time (float): 想要插入旁白的当前时间
Returns:
int: 合适位置所在的行号
"""
idx = 0 idx = 0
while idx < len(self.projectContext.all_elements): while idx < len(self.projectContext.all_elements):
if float(cur_time) < float(self.projectContext.all_elements[idx].st_time_sec): if float(cur_time) < float(self.projectContext.all_elements[idx].st_time_sec):
...@@ -1236,7 +1459,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1236,7 +1459,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
idx += 1 idx += 1
return idx return idx
def add_line_operation_slot(self, row, start_time, end_time, subtitle, suggest, aside, speed): def add_line_operation_slot(self, row: int, start_time: str, end_time: str, subtitle: str, suggest: str, aside: str, speed: str):
"""添加一行信息
Args:
row (int): 在该行后添加信息
start_time (str): 起始时间
end_time (str): 终止时间
subtitle (str): 字幕文本
suggest (str): 建议
aside (str): 旁白文本
speed (str): 旁白语速
"""
# 注意,这里需要用同一对象,不能生成多个Element # 注意,这里需要用同一对象,不能生成多个Element
new_element = Element(start_time, end_time, subtitle, suggest, aside, speed) new_element = Element(start_time, end_time, subtitle, suggest, aside, speed)
self.projectContext.all_elements.insert(int(row), new_element) self.projectContext.all_elements.insert(int(row), new_element)
...@@ -1261,8 +1495,21 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1261,8 +1495,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.refresh_tab_slot() self.refresh_tab_slot()
self.prompt_dialog.show_with_msg("操作成功!!请查看变化") self.prompt_dialog.show_with_msg("操作成功!!请查看变化")
# 只需要改all_elements就可以了,因为是同一对象 def mod_line_operation_slot(self, row: int, start_time: str, end_time: str, subtitle: str, suggest: str, aside: str, speed: str):
def mod_line_operation_slot(self, row, start_time, end_time, subtitle, suggest, aside, speed): """修改一行
Args:
row (int): 行号
start_time (str): 起始时间
end_time (str): 终止时间
subtitle (str): 字幕文本
suggest (str): 建议
aside (str): 旁白文本
speed (str): 旁白语速
只需要改all_elements就可以了,因为是同一对象
"""
# 保留原来的element # 保留原来的element
to_be_modify_element = copy.deepcopy(self.projectContext.all_elements[int(row) - 1]) to_be_modify_element = copy.deepcopy(self.projectContext.all_elements[int(row) - 1])
...@@ -1293,7 +1540,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1293,7 +1540,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.prompt_dialog.show_with_msg("操作成功!!请查看变化") self.prompt_dialog.show_with_msg("操作成功!!请查看变化")
# 只有row起作用 # 只有row起作用
def del_line_operation_slot(self, row, start_time=0, end_time=0, subtitle="", suggest="", aside="", speed=""): def del_line_operation_slot(self, row: int, start_time="0", end_time="0", subtitle="", suggest="", aside="", speed=""):
"""删除一行
Args:
row (int): 行号
start_time (str, optional): 起始时间. Defaults to "0".
end_time (str, optional): 终止时间. Defaults to "0".
subtitle (str, optional): 字幕文本. Defaults to "".
suggest (str, optional): 建议. Defaults to "".
aside (str, optional): 旁白文本. Defaults to "".
speed (str, optional): 旁白语速. Defaults to "".
"""
to_be_delete_element = self.projectContext.all_elements[int(row) - 1] to_be_delete_element = self.projectContext.all_elements[int(row) - 1]
if self.projectContext.all_elements[int(row)-1].suggest is not None \ if self.projectContext.all_elements[int(row)-1].suggest is not None \
and "插入旁白,推荐字数为" in self.projectContext.all_elements[int(row)-1].suggest: and "插入旁白,推荐字数为" in self.projectContext.all_elements[int(row)-1].suggest:
...@@ -1301,7 +1559,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1301,7 +1559,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if to_be_delete_element.equalTo(self.projectContext.aside_list[i]): if to_be_delete_element.equalTo(self.projectContext.aside_list[i]):
self.pb_tableWidget_idx = i self.pb_tableWidget_idx = i
# 将对应音频删除 # 将对应音频删除
wav_path = self.projectContext.project_base_dir + '/tmp/%.2f.wav' % float(self.projectContext.aside_list[i].st_time_sec) wav_path = self.projectContext.project_base_dir + \
'/tmp/%.2f.wav' % float(
self.projectContext.aside_list[i].st_time_sec)
if os.path.exists(wav_path): if os.path.exists(wav_path):
os.remove(wav_path) os.remove(wav_path)
self.projectContext.aside_list.pop(i) self.projectContext.aside_list.pop(i)
...@@ -1319,6 +1579,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1319,6 +1579,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.prompt_dialog.show_with_msg("操作成功!!请查看变化") self.prompt_dialog.show_with_msg("操作成功!!请查看变化")
def pb_item_changed_by_double_clicked_slot(self, item): def pb_item_changed_by_double_clicked_slot(self, item):
"""双击后修改旁白文本
Args:
item : 双击选中的单元格
"""
if item is None: if item is None:
return return
row = item.row() # 获取行数 row = item.row() # 获取行数
...@@ -1331,19 +1596,26 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1331,19 +1596,26 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.user_editing_aside = True self.user_editing_aside = True
def all_item_changed_by_double_clicked_slot(self, item): def all_item_changed_by_double_clicked_slot(self, item):
"""暂时没有用,原本是打算用来做双击编辑的
"""
if item is None: if item is None:
return return
row = item.row() # 获取行数 row = item.row() # 获取行数
col = item.column() # 获取列数 注意是column而不是col哦 col = item.column() # 获取列数 注意是column而不是col哦
text = item.text() # 获取内容 text = item.text() # 获取内容
if col not in constant.Content.ActivateColumns: if col not in constant.Content.ActivateColumns:
return return
print("已经set user_editing_content=True") print("已经set user_editing_content=True")
self.user_editing_content = True self.user_editing_content = True
def getTabId(self, x): def getTabId(self, x):
"""切换显示在界面中的表格,同时高亮当前时间在表格中的对应行
Args:
x (str): 用户点击的表格编号(0、1、2)
"""
self.curTab = int(x) self.curTab = int(x)
# 切换的时候,自动高亮一行 # 切换的时候,自动高亮一行
cur_time = self.player.position() / 1000 cur_time = self.player.position() / 1000
...@@ -1361,16 +1633,21 @@ class MainWindow(QMainWindow, Ui_MainWindow): ...@@ -1361,16 +1633,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
break break
def change_audio_speed(self): def change_audio_speed(self):
"""换语速
首先定位到待切换语速的那一行,释放当前播放的音频文件,并替换对应旁白文本的语速,同时更新字幕旁白表格中的语速,然后自动生成新的音频。
"""
combo = self.sender() combo = self.sender()
idx = self.pb_tableWidget.indexAt(combo.pos()) idx = self.pb_tableWidget.indexAt(combo.pos())
row = idx.row() row = idx.row()
print("index:", row) print("index:", row)
# 将audio_player的资源置空 # 将audio_player的资源置空
print("释放当前播放的音频文件")
self.audio_player.setMedia(QMediaContent()) self.audio_player.setMedia(QMediaContent())
self.projectContext.aside_list[row].speed = combo.currentText() self.projectContext.aside_list[row].speed = combo.currentText()
# 更新字幕旁白表格里对应行的语速 # 更新字幕旁白表格里对应行的语速
all_idx = self.projectContext.aside_subtitle_2contentId(self.projectContext.aside_list[row]) all_idx = self.projectContext.aside_subtitle_2contentId(
self.projectContext.aside_list[row])
self.projectContext.all_elements[int(all_idx)].speed = combo.currentText() self.projectContext.all_elements[int(all_idx)].speed = combo.currentText()
self.all_tableWidget.setItem(int(all_idx), constant.Content.SpeedColumnNumber, QTableWidgetItem(combo.currentText())) self.all_tableWidget.setItem(int(all_idx), constant.Content.SpeedColumnNumber, QTableWidgetItem(combo.currentText()))
self.do_generate_audio_by_aside_row(row) self.do_generate_audio_by_aside_row(int(row))
\ No newline at end of file \ No newline at end of file
...@@ -273,6 +273,7 @@ class Ui_MainWindow(object): ...@@ -273,6 +273,7 @@ class Ui_MainWindow(object):
self.sld_video.setTickInterval(1) self.sld_video.setTickInterval(1)
self.sld_video.setObjectName("sld_video") self.sld_video.setObjectName("sld_video")
self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.scrollArea.setGeometry(QtCore.QRect(0, 0, 820, 42))
self.zm_slider_layout.addWidget(self.scrollArea) self.zm_slider_layout.addWidget(self.scrollArea)
self.lab_video = QtWidgets.QLabel(self.verticalWidget_2) self.lab_video = QtWidgets.QLabel(self.verticalWidget_2)
self.lab_video.setMaximumSize(QtCore.QSize(16777215, 50)) self.lab_video.setMaximumSize(QtCore.QSize(16777215, 50))
...@@ -358,8 +359,6 @@ class Ui_MainWindow(object): ...@@ -358,8 +359,6 @@ class Ui_MainWindow(object):
self.action_3.setObjectName("action_3") self.action_3.setObjectName("action_3")
self.action_4 = QtWidgets.QAction(MainWindow) self.action_4 = QtWidgets.QAction(MainWindow)
self.action_4.setObjectName("action_4") self.action_4.setObjectName("action_4")
self.action_refresh_tab = QtWidgets.QAction(MainWindow)
self.action_refresh_tab.setObjectName("action_refresh_tab")
self.action_operate = QtWidgets.QAction(MainWindow) self.action_operate = QtWidgets.QAction(MainWindow)
self.action_operate.setObjectName("action_operate") self.action_operate.setObjectName("action_operate")
self.action_export = QtWidgets.QAction(MainWindow) self.action_export = QtWidgets.QAction(MainWindow)
...@@ -386,7 +385,6 @@ class Ui_MainWindow(object): ...@@ -386,7 +385,6 @@ class Ui_MainWindow(object):
self.menu_3.addAction(self.action_3) self.menu_3.addAction(self.action_3)
self.menu_3.addAction(self.action_4) self.menu_3.addAction(self.action_4)
self.menu_3.addSeparator() self.menu_3.addSeparator()
self.menu_3.addAction(self.action_refresh_tab)
self.menubar.addAction(self.menu.menuAction()) self.menubar.addAction(self.menu.menuAction())
self.menubar.addAction(self.menu_2.menuAction()) self.menubar.addAction(self.menu_2.menuAction())
self.menubar.addAction(self.menu_3.menuAction()) self.menubar.addAction(self.menu_3.menuAction())
...@@ -419,7 +417,6 @@ class Ui_MainWindow(object): ...@@ -419,7 +417,6 @@ class Ui_MainWindow(object):
self.action_redo.setText(_translate("MainWindow", "重做")) self.action_redo.setText(_translate("MainWindow", "重做"))
self.action_3.setText(_translate("MainWindow", "旁白区间检测")) self.action_3.setText(_translate("MainWindow", "旁白区间检测"))
self.action_4.setText(_translate("MainWindow", "旁白音频合成")) self.action_4.setText(_translate("MainWindow", "旁白音频合成"))
self.action_refresh_tab.setText(_translate("MainWindow", "刷新且保存表格"))
self.action_operate.setText(_translate("MainWindow", "操作表格")) self.action_operate.setText(_translate("MainWindow", "操作表格"))
self.action_export.setText(_translate("MainWindow", "导出")) self.action_export.setText(_translate("MainWindow", "导出"))
self.action_insert_aside_from_now.setText(_translate("MainWindow", "当前位置插入旁白")) self.action_insert_aside_from_now.setText(_translate("MainWindow", "当前位置插入旁白"))
......
...@@ -9,7 +9,7 @@ import openpyxl ...@@ -9,7 +9,7 @@ import openpyxl
import constant import constant
from openpyxl.styles import PatternFill, Alignment from openpyxl.styles import PatternFill, Alignment
from utils import replace_path_suffix from utils import replace_path_suffix, transfer_second_to_time
from speech_synthesis import Speaker from speech_synthesis import Speaker
class RunThread(threading.Thread): class RunThread(threading.Thread):
"""复写线程类,用于解决主线程无法捕捉子线程中异常的问题 """复写线程类,用于解决主线程无法捕捉子线程中异常的问题
...@@ -31,7 +31,7 @@ class RunThread(threading.Thread): ...@@ -31,7 +31,7 @@ class RunThread(threading.Thread):
self.exc_traceback = '' self.exc_traceback = ''
def run(self): # Overwrite run() method, put what you want the thread do here def run(self): # Overwrite run() method, put what you want the thread do here
"""运行线程,捕捉错误并更新参数 """运行线程,当运行过程中发生错误时捕捉错误信息,并更新参数,再抛出错误,从而在主界面中提醒用户出现异常
""" """
try: try:
...@@ -41,7 +41,8 @@ class RunThread(threading.Thread): ...@@ -41,7 +41,8 @@ class RunThread(threading.Thread):
self.exception = e self.exception = e
self.exc_traceback = ''.join( self.exc_traceback = ''.join(
traceback.format_exception(*sys.exc_info())) # 在改成员变量中记录异常信息 traceback.format_exception(*sys.exc_info())) # 在改成员变量中记录异常信息
print(self.exc_traceback) print("error_info", self.exc_traceback)
# raise e
def _run(self): def _run(self):
"""运行函数,并合理抛出异常 """运行函数,并合理抛出异常
...@@ -123,6 +124,7 @@ class ProjectContext: ...@@ -123,6 +124,7 @@ class ProjectContext:
self.duration = 0 self.duration = 0
# 一些常量 # 一些常量
self.header = ["起始时间", "终止时间", "字幕", '建议', '解说脚本', "语速"] self.header = ["起始时间", "终止时间", "字幕", '建议', '解说脚本', "语速"]
self.write_header = ["起始时间", "起始时间(转换后)", "终止时间", "终止时间(转换后)", "字幕", '建议', '解说脚本', "语速"]
self.aside_header = ['起始时间', '推荐字数', '解说脚本',"语速", "预览音频"] self.aside_header = ['起始时间', '推荐字数', '解说脚本',"语速", "预览音频"]
self.subtitle_header = ["起始时间", "终止时间", "字幕"] self.subtitle_header = ["起始时间", "终止时间", "字幕"]
...@@ -229,10 +231,10 @@ class ProjectContext: ...@@ -229,10 +231,10 @@ class ProjectContext:
if need_save_new: if need_save_new:
new_excel_path = replace_path_suffix(self.excel_path, datetime.datetime.now().strftime('%Y%m%d%H%M%S')+'.xlsx') new_excel_path = replace_path_suffix(self.excel_path, datetime.datetime.now().strftime('%Y%m%d%H%M%S')+'.xlsx')
err_info = save_excel_to_to_path(self.all_elements, new_excel_path, self.header, self.excel_sheet_name) err_info = save_excel_to_path(self.all_elements, new_excel_path, self.write_header, self.excel_sheet_name)
if err_info != None: if err_info != None:
return err_info return err_info
err_info = save_excel_to_to_path(self.all_elements, self.excel_path, self.header, self.excel_sheet_name) err_info = save_excel_to_path(self.all_elements, self.excel_path, self.write_header, self.excel_sheet_name)
if err_info != None: if err_info != None:
return err_info return err_info
return None return None
...@@ -240,17 +242,17 @@ class ProjectContext: ...@@ -240,17 +242,17 @@ class ProjectContext:
def refresh_aside(self, row, aside: str)->None: def refresh_aside(self, row, aside: str)->None:
self.aside_list[int(row)].aside = aside self.aside_list[int(row)].aside = aside
if not self.initial_ing: if not self.initial_ing:
save_excel_to_to_path(self.all_elements, self.excel_path, self.header, self.excel_sheet_name) save_excel_to_path(self.all_elements, self.excel_path, self.write_header, self.excel_sheet_name)
def refresh_aside_speed(self, row, speed: str)->None: def refresh_aside_speed(self, row, speed: str)->None:
self.aside_list[int(row)].speed = speed self.aside_list[int(row)].speed = speed
if not self.initial_ing: if not self.initial_ing:
save_excel_to_to_path(self.all_elements, self.excel_path, self.header, self.excel_sheet_name) save_excel_to_path(self.all_elements, self.excel_path, self.write_header, self.excel_sheet_name)
def refresh_element(self, row, aside: str): def refresh_element(self, row, aside: str):
self.all_elements[int(row)].aside = aside self.all_elements[int(row)].aside = aside
if not self.initial_ing: if not self.initial_ing:
save_excel_to_to_path(self.all_elements, self.excel_path, self.header, self.excel_sheet_name) save_excel_to_path(self.all_elements, self.excel_path, self.write_header, self.excel_sheet_name)
# 加载整个工程,填充到ProjectContext上下文中 # 加载整个工程,填充到ProjectContext上下文中
def load_project(self): def load_project(self):
...@@ -330,6 +332,7 @@ class ProjectContext: ...@@ -330,6 +332,7 @@ class ProjectContext:
"""获取所有说话人的名字、性别及年龄段等信息 """获取所有说话人的名字、性别及年龄段等信息
用于显示在人机交互界面上,方便用户了解说话人并进行选择 用于显示在人机交互界面上,方便用户了解说话人并进行选择
""" """
f = open(constant.Pathes.speaker_conf_path, encoding="utf-8") f = open(constant.Pathes.speaker_conf_path, encoding="utf-8")
content = json.load(f) content = json.load(f)
...@@ -345,6 +348,7 @@ class ProjectContext: ...@@ -345,6 +348,7 @@ class ProjectContext:
"""初始化说话人信息 """初始化说话人信息
相关配置文件为"speaker.json",如果有信息需要修改,请在speaker.json中修改 相关配置文件为"speaker.json",如果有信息需要修改,请在speaker.json中修改
""" """
f = open("./res/speakers.json", encoding="utf-8") f = open("./res/speakers.json", encoding="utf-8")
content = json.load(f) content = json.load(f)
...@@ -365,7 +369,7 @@ class ProjectContext: ...@@ -365,7 +369,7 @@ class ProjectContext:
return speaker return speaker
raise ValueError raise ValueError
def save_excel_to_to_path(all_element, new_excel_path, header, excel_sheet_name): def save_excel_to_path(all_element, new_excel_path, header, excel_sheet_name):
def save_excel_thread(all_element, new_excel_path, header, excel_sheet_name): def save_excel_thread(all_element, new_excel_path, header, excel_sheet_name):
backup_path = None backup_path = None
if os.path.exists(new_excel_path): if os.path.exists(new_excel_path):
...@@ -412,17 +416,18 @@ def write_to_sheet(path: str, sheet_name: str, valuelist: list): ...@@ -412,17 +416,18 @@ def write_to_sheet(path: str, sheet_name: str, valuelist: list):
sheet = workbook.get_sheet_by_name(sheet_name) sheet = workbook.get_sheet_by_name(sheet_name)
for value_element in valuelist: for value_element in valuelist:
value = value_element.to_list() value = value_element.to_list()
index = len(value)
# 把None换成空串 # 把None换成空串
value = ["" if x == None else x for x in value] value = ["" if x == None else x for x in value]
value.insert(1, transfer_second_to_time(value[0])) if value[0] != "" else value.insert(1, "")
value.insert(3, transfer_second_to_time(value[2])) if value[2] != "" else value.insert(3, "")
index = len(value)
cur_row = sheet.max_row cur_row = sheet.max_row
# print("cur_row:", cur_row)
for j in range(0, index): for j in range(0, index):
sheet.cell(row=cur_row + 1, column=j + 1, value=str(value[j])) sheet.cell(row=cur_row + 1, column=j + 1, value=str(value[j]))
if value[j] == '' or '插入旁白' in str(value[j]): if value[j] == '' or '插入旁白' in str(value[j]):
sheet.cell(row=cur_row + 1, column=j + 1).fill = PatternFill(fill_type='solid', fgColor='ffff00') sheet.cell(row=cur_row + 1, column=j + 1).fill = PatternFill(fill_type='solid', fgColor='ffff00')
if j == 2: if j == 4:
sheet.cell(row=cur_row + 1, column=j + 1).alignment = Alignment(wrapText=True) sheet.cell(row=cur_row + 1, column=j + 1).alignment = Alignment(wrapText=True)
workbook.save(path) workbook.save(path)
...@@ -441,8 +446,11 @@ def create_sheet(path: str, sheet_name: str, value: list): ...@@ -441,8 +446,11 @@ def create_sheet(path: str, sheet_name: str, value: list):
sheet = workbook.active sheet = workbook.active
sheet.title = sheet_name sheet.title = sheet_name
# 将字幕对应的那一列扩宽一些 # 将字幕对应的那一列扩宽一些
sheet.column_dimensions['C'].width = 50 sheet.column_dimensions['B'].width = 25
sheet.column_dimensions['D'].width = 30 sheet.column_dimensions['D'].width = 25
sheet.column_dimensions['E'].width = 50
sheet.column_dimensions['F'].width = 30
for i in range(0, index): for i in range(0, index):
for j in range(0, len(value[i])): for j in range(0, len(value[i])):
sheet.cell(row=i + 1, column=j + 1, value=str(value[i][j])) sheet.cell(row=i + 1, column=j + 1, value=str(value[i][j]))
...@@ -452,12 +460,14 @@ def read_sheet(book_path: str, sheet_name: str = "") -> dict: ...@@ -452,12 +460,14 @@ def read_sheet(book_path: str, sheet_name: str = "") -> dict:
"""读表 """读表
从表格中读出所有的内容,用dict保存(表格的格式固定,第一行为表头(起始时间|终止时间|字幕|建议|解说脚本)) 从表格中读出所有的内容,用dict保存(表格的格式固定,第一行为表头(起始时间|终止时间|字幕|建议|解说脚本))
Args: Args:
book_path (str): 表格的存储路径 book_path (str): 表格的存储路径
sheet_name (str, optional): 想要读取的表在excel表格中的名字. Defaults to "". sheet_name (str, optional): 想要读取的表在excel表格中的名字. Defaults to "".
Returns: Returns:
dict: 表格中的所有内容,key为该列表头,value为列中的数据 dict: 表格中的所有内容,key为该列表头,value为列中的数据
""" """
workbook = openpyxl.load_workbook(book_path) workbook = openpyxl.load_workbook(book_path)
sheet = workbook.active sheet = workbook.active
......
import sys
from PyQt5.QtWidgets import (QWidget, QTableWidget, QHBoxLayout, QApplication, QTableWidgetItem, QAbstractItemView)
from PyQt5 import QtCore
class myTableWidget(QWidget):
def __init__(self):
super(myTableWidget,self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("QTableWidget演示")
self.resize(430, 230);
layout = QHBoxLayout()
tablewidget = QTableWidget()
tablewidget.setRowCount(4)
tablewidget.setColumnCount(6)
layout.addWidget(tablewidget)
tablewidget.setHorizontalHeaderLabels(['时间','文字'])
nameItem = QTableWidgetItem("0:00")
tablewidget.setItem(0,0,nameItem)
ageItem = QTableWidgetItem("24")
tablewidget.setItem(0,1,ageItem)
jgItem = QTableWidgetItem("北京")
jgItem.setFlags(QtCore.Qt.ItemIsEnabled)
tablewidget.setItem(0,2,jgItem)
# 禁止编辑
# tablewidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 整行选择
tablewidget.setSelectionBehavior(QAbstractItemView.SelectRows)
# 调整列和行
tablewidget.resizeColumnsToContents()
tablewidget.resizeRowsToContents()
# tablewidget.horizontalHeader().setVisible(False)
# tablewidget.verticalHeader().setVisible(False)
tablewidget.setVerticalHeaderLabels(["a","b"])
# 隐藏表格线
tablewidget.setShowGrid(False)
self.setLayout(layout)
tablewidget.itemClicked.connect(self.show_data)
def show_data(self, Item):
# 如果单元格对象为空
if Item is None:
return
else:
row = Item.row() # 获取行数
col = Item.column() # 获取列数 注意是column而不是col哦
text = Item.text() # 获取内容
# 输出测试
print('row = ', row)
print('col =', col)
print('text = ', text)
if __name__ == '__main__':
app = QApplication(sys.argv)
example = myTableWidget()
example.show()
sys.exit(app.exec_())
...@@ -49,18 +49,31 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -49,18 +49,31 @@ class Operation_Dialog(QDialog, Ui_Dialog):
self.adddel_change_slot() self.adddel_change_slot()
def pick_start_pos_slot(self): def pick_start_pos_slot(self):
time = utils.transfer_second_to_time( """选择当前时间为起始时间
str(self.mainWindow.player.position()/1000))
"""
time = utils.transfer_second_to_time(str(self.mainWindow.player.position()/1000))
st_time = QTime.fromString(time, "hh:mm:ss.zzz") st_time = QTime.fromString(time, "hh:mm:ss.zzz")
print("st_time", st_time.hour(), st_time.minute(), st_time.second(), st_time.msec()) print("st_time", st_time.hour(), st_time.minute(), st_time.second(), st_time.msec())
self.timeEdit.setTime(st_time) self.timeEdit.setTime(st_time)
def pick_end_pos_slot(self): def pick_end_pos_slot(self):
"""选择当前时间为终止时间
"""
time = utils.transfer_second_to_time( time = utils.transfer_second_to_time(
str(self.mainWindow.player.position()/1000)) str(self.mainWindow.player.position()/1000))
self.timeEdit_2.setTime(QTime.fromString(time, "hh:mm:ss.zzz")) self.timeEdit_2.setTime(QTime.fromString(time, "hh:mm:ss.zzz"))
def zmpb_change_slot(self): def zmpb_change_slot(self):
"""对界面中相关组件进行“可选”或“不可选”的更改
更改如下:
- “删除一行”时,不做任何更改
- 一般将所有的用户可修改控件置为可选
- 如果用户选择的是“字幕”,则将推荐字数、旁白文本和语速置为不可更改模式
- 如果用户选择的是“旁白”,就将终止时间、字幕文本和推荐字数置为不可更改模式
"""
# 如果是删除,则直接return # 如果是删除,则直接return
if self.comboBox_2.currentText() == "删除一行": if self.comboBox_2.currentText() == "删除一行":
return return
...@@ -80,6 +93,13 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -80,6 +93,13 @@ class Operation_Dialog(QDialog, Ui_Dialog):
# 如果是删除,则只需要【行数】即可 # 如果是删除,则只需要【行数】即可
def adddel_change_slot(self): def adddel_change_slot(self):
"""针对三种模式,分别对修改界面中相关控件进行“可选”和“不可选”的切换
进行了如下更改:
- 增加一行和修改一行时使用zmpb_change_slot直接进行切换
- 删除一行时,将除行号之外的其他可编辑控件均置为不可操作模式
"""
if self.comboBox_2.currentText() in ["增加一行", "修改一行"]: if self.comboBox_2.currentText() in ["增加一行", "修改一行"]:
self.zmpb_change_slot() self.zmpb_change_slot()
else: else:
...@@ -88,8 +108,10 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -88,8 +108,10 @@ class Operation_Dialog(QDialog, Ui_Dialog):
for timeEdit in self.timeEdits: for timeEdit in self.timeEdits:
timeEdit.setEnabled(False) timeEdit.setEnabled(False)
# 修改完后需要重新检测
def remake_slot(self): def remake_slot(self):
"""用户完成某一操作的修改后,将界面中的控件重新置为初始可选状态
"""
self.buttonBox.setEnabled(False) self.buttonBox.setEnabled(False)
self.set_all_user_component_status(True) self.set_all_user_component_status(True)
self.pickStartPos.setEnabled(True) self.pickStartPos.setEnabled(True)
...@@ -98,7 +120,12 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -98,7 +120,12 @@ class Operation_Dialog(QDialog, Ui_Dialog):
self.zmpb_change_slot() self.zmpb_change_slot()
self.adddel_change_slot() self.adddel_change_slot()
def check_validate_slot(self): def check_validate_slot(self) -> bool:
"""确认输入的待更改信息是否正确
Returns:
bool: True or False
"""
# 校验行数 # 校验行数
# todo 用tableWidget还是用all_element? # todo 用tableWidget还是用all_element?
rowCount = self.mainWindow.all_tableWidget.rowCount() rowCount = self.mainWindow.all_tableWidget.rowCount()
...@@ -200,6 +227,11 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -200,6 +227,11 @@ class Operation_Dialog(QDialog, Ui_Dialog):
self.pushButton_3.setEnabled(False) self.pushButton_3.setEnabled(False)
def set_all_user_component_status(self, status: bool): def set_all_user_component_status(self, status: bool):
"""将所有用户可操作控件置为同一状态
Args:
status (bool): 想要切换的状态,True or False.
"""
for lineEdit in self.lineEdits: for lineEdit in self.lineEdits:
lineEdit.setEnabled(status) lineEdit.setEnabled(status)
for timeEdit in self.timeEdits: for timeEdit in self.timeEdits:
...@@ -209,6 +241,9 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -209,6 +241,9 @@ class Operation_Dialog(QDialog, Ui_Dialog):
self.comboBox_3.setEnabled(status) self.comboBox_3.setEnabled(status)
def start_operation_slot(self): def start_operation_slot(self):
"""执行用户提交的操作
"""
start_time, end_time = ["%02d:%02d:%02d.%03d" % (x.time().hour(), x.time().minute(), x.time().second(), x.time().msec()) for x in self.timeEdits] start_time, end_time = ["%02d:%02d:%02d.%03d" % (x.time().hour(), x.time().minute(), x.time().second(), x.time().msec()) for x in self.timeEdits]
row, subtitle, suggest, aside = [ row, subtitle, suggest, aside = [
x.text() for x in self.lineEdits] x.text() for x in self.lineEdits]
...@@ -246,6 +281,9 @@ class Operation_Dialog(QDialog, Ui_Dialog): ...@@ -246,6 +281,9 @@ class Operation_Dialog(QDialog, Ui_Dialog):
row, start_time, end_time, subtitle, suggest, aside, speed) row, start_time, end_time, subtitle, suggest, aside, speed)
def fill_row_info_slot(self): def fill_row_info_slot(self):
"""自动填入该行的相关信息
"""
text = self.lineEdit.text() text = self.lineEdit.text()
print("fill_row_info_slot, text:", text) print("fill_row_info_slot, text:", text)
if text is not None: if text is not None:
......
def f(a,b,c,d):
print(a,b,c,d)
# f([1,2,3))
data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
name, shares, price, date = data
print(name)
from enum import Enum
class Operation(Enum):
Modify = 0
Delete = 1
# print(Operation.Delete)
print(Operation(('修改', "xiugai")))
\ No newline at end of file
...@@ -29,6 +29,12 @@ class Prompt_Dialog(QDialog, Ui_Dialog): ...@@ -29,6 +29,12 @@ class Prompt_Dialog(QDialog, Ui_Dialog):
self.show_dialog_signal.connect(self.show_with_msg) self.show_dialog_signal.connect(self.show_with_msg)
if ok_func != None: if ok_func != None:
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(ok_func) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).clicked.connect(ok_func)
def show_with_msg(self, msg):
def show_with_msg(self, msg: str):
"""在弹窗中展示提示语句
Args:
msg (str): 提示语句
"""
self.label.setText(msg) self.label.setText(msg)
self.show() self.show()
\ No newline at end of file
...@@ -32,15 +32,33 @@ ...@@ -32,15 +32,33 @@
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>60</x> <x>50</x>
<y>30</y> <y>40</y>
<width>291</width> <width>300</width>
<height>71</height> <height>71</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text"> <property name="text">
<string>请输入文字</string> <string>请输入文字</string>
</property> </property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget> </widget>
</widget> </widget>
<resources/> <resources/>
......
...@@ -20,6 +20,7 @@ class Ui_Dialog(object): ...@@ -20,6 +20,7 @@ class Ui_Dialog(object):
self.buttonBox.setObjectName("buttonBox") self.buttonBox.setObjectName("buttonBox")
self.label = QtWidgets.QLabel(Dialog) self.label = QtWidgets.QLabel(Dialog)
self.label.setGeometry(QtCore.QRect(60, 30, 291, 71)) self.label.setGeometry(QtCore.QRect(60, 30, 291, 71))
self.label.setWordWrap(True)
self.label.setObjectName("label") self.label.setObjectName("label")
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
......
paddlepaddle==2.2.0
paddleocr==2.3.0.1
qt_material
soundfile
azure-cognitiveservices-speech==1.19.0 azure-cognitiveservices-speech==1.19.0
openpyxl cn2an==0.5.16
opencv-python Flask==2.0.2
PyQt5 flask_cors==3.0.10
\ No newline at end of file ijson==3.2.1
LAC==2.1.2
librosa==0.8.1
numpy==1.19.3
onnxruntime==1.14.0
opencv_python==4.4.0.46
openpyxl==3.0.9
paddle==1.0.2
paddleocr==2.3.0.1
paddlepaddle==2.2.0
paddlespeech==0.1.0
parakeet==0.24
Pillow==9.5.0
pyaudio==0.2.13
pyclipper==1.3.0
pydub==0.25.1
PyQt5==5.15.9
PyYAML==6.0
qt_material==2.12
resampy==0.2.2
scipy==1.3.0
Shapely==1.8.0
SoundFile==0.10.2
tqdm==4.62.3
visualdl==2.2.1
webrtcvad==2.0.10
yacs==0.1.8
zhconv==1.4.3
...@@ -41,6 +41,11 @@ class Setting_Dialog(QDialog, Ui_Dialog): ...@@ -41,6 +41,11 @@ class Setting_Dialog(QDialog, Ui_Dialog):
self.pushButton.clicked.connect(self.play_audio_slot) self.pushButton.clicked.connect(self.play_audio_slot)
def content_fresh(self): def content_fresh(self):
"""刷新界面中的内容
将工程信息中的说话人信息、说话人语速更新到界面中,如果未选择则初始化为第一个选项
"""
if self.projectContext.speaker_info is None: if self.projectContext.speaker_info is None:
self.comboBox.setCurrentIndex(0) self.comboBox.setCurrentIndex(0)
else: else:
...@@ -51,16 +56,26 @@ class Setting_Dialog(QDialog, Ui_Dialog): ...@@ -51,16 +56,26 @@ class Setting_Dialog(QDialog, Ui_Dialog):
self.comboBox_2.setCurrentIndex(self.speed_li_2.index(self.projectContext.speaker_speed)) self.comboBox_2.setCurrentIndex(self.speed_li_2.index(self.projectContext.speaker_speed))
def speaker_change_slot(self): def speaker_change_slot(self):
"""切换说话人
将当前的说话人设置为工程的说话人,并保存到配置文件中
"""
self.projectContext.speaker_info = self.comboBox.currentText() self.projectContext.speaker_info = self.comboBox.currentText()
self.projectContext.save_conf() self.projectContext.save_conf()
# print("self.projectContext.speaker_info:", self.projectContext.speaker_info) # print("self.projectContext.speaker_info:", self.projectContext.speaker_info)
def speed_change_slot(self): def speed_change_slot(self):
"""切换语速
将当前的语速设置为工程的语速,并保存到配置文件中
"""
self.projectContext.speaker_speed = self.comboBox_2.currentText() self.projectContext.speaker_speed = self.comboBox_2.currentText()
self.projectContext.save_conf() self.projectContext.save_conf()
def play_audio_slot(self): def play_audio_slot(self):
"""播放说话人的样例音频 """新起一个线程来播放说话人的样例音频
根据用户选择的说话人更新对应样例音频 根据用户选择的说话人更新对应样例音频
停止上一个说话人的音频 停止上一个说话人的音频
...@@ -75,13 +90,21 @@ class Setting_Dialog(QDialog, Ui_Dialog): ...@@ -75,13 +90,21 @@ class Setting_Dialog(QDialog, Ui_Dialog):
audioPlayed = winsound.PlaySound(chosenSpeaker.voice_example, winsound.SND_ASYNC) audioPlayed = winsound.PlaySound(chosenSpeaker.voice_example, winsound.SND_ASYNC)
thread_it(f, name="playAudio") thread_it(f, name="playAudio")
# 重写关闭窗口事件,使得可以停止播放样例视频
def closeEvent(self, event): def closeEvent(self, event):
"""重写关闭窗口事件,使得可以停止播放样例视频
Args:
event: 关闭窗口事件
"""
global audioPlayed global audioPlayed
winsound.PlaySound(audioPlayed, winsound.SND_PURGE) winsound.PlaySound(audioPlayed, winsound.SND_PURGE)
event.accept() event.accept()
def showDialog(self): def showDialog(self):
"""刷新信息并展示窗口
"""
self.content_fresh() self.content_fresh()
self.show() self.show()
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
from speech_synthesis import ss_and_export from speech_synthesis import ss_and_export
ss_and_export(video_path, sheet_path, audio_dir, speed, caption_path, speaker_name, state) ss_and_export(video_path, sheet_path, audio_dir, speed, caption_path, speaker_name, state)
""" """
import json import json
import os import os
...@@ -25,6 +26,7 @@ import numpy as np ...@@ -25,6 +26,7 @@ import numpy as np
from azure.cognitiveservices.speech import SpeechConfig, SpeechSynthesizer, ResultReason, AudioDataStream from azure.cognitiveservices.speech import SpeechConfig, SpeechSynthesizer, ResultReason, AudioDataStream
from azure.cognitiveservices.speech.audio import AudioOutputConfig from azure.cognitiveservices.speech.audio import AudioOutputConfig
import openpyxl import openpyxl
import shutil
tmp_file = 'tmp.wav' tmp_file = 'tmp.wav'
adjusted_wav_path = "adjusted.wav" adjusted_wav_path = "adjusted.wav"
...@@ -58,7 +60,7 @@ def init_speakers(): ...@@ -58,7 +60,7 @@ def init_speakers():
相关配置文件为"speaker.json",如果有信息需要修改,请在speaker.json中修改 相关配置文件为"speaker.json",如果有信息需要修改,请在speaker.json中修改
""" """
f = open("speakers.json", encoding="utf-8") f = open("./res/speakers.json", encoding="utf-8")
content = json.load(f) content = json.load(f)
global speakers global speakers
for speaker_info in content["speaker_details"]: for speaker_info in content["speaker_details"]:
...@@ -82,12 +84,15 @@ def choose_speaker(speaker_name: str) -> Speaker: ...@@ -82,12 +84,15 @@ def choose_speaker(speaker_name: str) -> Speaker:
def speech_synthesis(text: str, output_file: str, speaker: Speaker, speed: float = 1.0): def speech_synthesis(text: str, output_file: str, speaker: Speaker, speed: float = 1.0):
"""用于合成讲解音频并输出 """用于合成讲解音频并输出
分为两步走:第一步在项目文件夹生成tmp.wav;第二步在output_file路径下生成调整音量和语速后的音频 分为两步走:第一步在项目文件夹生成tmp.wav;第二步在output_file路径下生成调整音量和语速后的音频
Args: Args:
text (str): 解说文本 text (str): 解说文本
output_file (str): 输出文件路径 output_file (str): 输出文件路径
speaker (Speaker): 说话人 speaker (Speaker): 说话人
speed (float, optional): 指定的音频语速. Defaults to 1.0. speed (float, optional): 指定的音频语速. Defaults to 1.0.
""" """
speech_config = SpeechConfig( speech_config = SpeechConfig(
subscription="db34d38d2d3447d482e0f977c66bd624", subscription="db34d38d2d3447d482e0f977c66bd624",
...@@ -144,12 +149,14 @@ def read_sheet(book_path: str, sheet_name: str = "") -> dict: ...@@ -144,12 +149,14 @@ def read_sheet(book_path: str, sheet_name: str = "") -> dict:
"""读表 """读表
从表格中读出所有的内容,用dict保存(表格的格式固定,第一行为表头(起始时间|终止时间|字幕|建议|解说脚本)) 从表格中读出所有的内容,用dict保存(表格的格式固定,第一行为表头(起始时间|终止时间|字幕|建议|解说脚本))
Args: Args:
book_path (str): 表格的存储路径 book_path (str): 表格的存储路径
sheet_name (str, optional): 想要读取的表在excel表格中的名字. Defaults to "". sheet_name (str, optional): 想要读取的表在excel表格中的名字. Defaults to "".
Returns: Returns:
dict: 表格中的所有内容,key为该列表头,value为列中的数据 dict: 表格中的所有内容,key为该列表头,value为列中的数据
""" """
workbook = openpyxl.load_workbook(book_path) workbook = openpyxl.load_workbook(book_path)
sheet = workbook.active sheet = workbook.active
...@@ -324,7 +331,9 @@ def ss_and_export(video_path: str, sheet_path: str, output_dir: str, speed: floa ...@@ -324,7 +331,9 @@ def ss_and_export(video_path: str, sheet_path: str, output_dir: str, speed: floa
# 音频输出位置路径 # 音频输出位置路径
root_path = output_dir root_path = output_dir
# 如果文件夹不存在,则新建文件夹 # 如果文件夹已存在,就删除已有的文件夹,避免用户之前已有的一些中间结果
if os.path.exists(root_path):
shutil.rmtree(root_path)
if not os.path.exists(root_path): if not os.path.exists(root_path):
os.mkdir(root_path) os.mkdir(root_path)
...@@ -358,8 +367,6 @@ def ss_and_export(video_path: str, sheet_path: str, output_dir: str, speed: floa ...@@ -358,8 +367,6 @@ def ss_and_export(video_path: str, sheet_path: str, output_dir: str, speed: floa
if os.path.exists(tmp_file): if os.path.exists(tmp_file):
time.sleep(1) time.sleep(1)
os.remove(tmp_file) os.remove(tmp_file)
# os.remove(origin_wav_path)
# os.remove(adjusted_wav_path)
state[0] = 1.00 state[0] = 1.00
...@@ -372,10 +379,3 @@ if __name__ == '__main__': ...@@ -372,10 +379,3 @@ if __name__ == '__main__':
caption_file = os.path.join(output_dir, os.path.basename(video_path) + ".srt") caption_file = os.path.join(output_dir, os.path.basename(video_path) + ".srt")
speaker_name = '晓秋' speaker_name = '晓秋'
ss_and_export(video_path, sheet_path, output_dir, speed, caption_file, speaker_name) ss_and_export(video_path, sheet_path, output_dir, speed, caption_file, speaker_name)
\ No newline at end of file
# import pprint
# d = read_sheet("./test37second.xlsx")
# pprint.pprint(d)
# init_speakers()
# speaker_name = "晓秋"
# speaker = choose_speaker(speaker_name)
# speech_synthesis("今天我们讲解的电影是何以笙箫默,它讲述了", r"D:\AddCaption\cur_version\accessibility_movie_2\test.wav", speaker, 0.5)
\ No newline at end of file
...@@ -12,6 +12,11 @@ os.environ['PYQTGRAPH_QT_LIB'] = 'PyQt5' ...@@ -12,6 +12,11 @@ os.environ['PYQTGRAPH_QT_LIB'] = 'PyQt5'
project_path = None project_path = None
def change_project_path(path): def change_project_path(path):
"""用于切换当前工程路径
Args:
path (str): 用户选择或新建的工程路径
"""
global project_path global project_path
project_path = path project_path = path
......
"""各种工具函数
工具函数描述如下:
- check_sheet_content: 检查表头是否符合预期
- get_progress_with_cmd: 获取ffmpeg处理视频的进度
- get_sheetHead: 获取表头
- replace_path_suffix: 替换路径后缀
- stop_thread: 停止线程
- trans_to_seconds: 将"hh:mm:ss.xxx"格式的时间转换为秒
- transfer_second_to_time: 将秒转换为"hh:mm:ss.xxx"格式的时间
- validate_and_get_filepath: 确认传入参数正确性,并返回文件路径
"""
import os, sys import os, sys
import openpyxl import openpyxl
import subprocess import subprocess
...@@ -6,8 +20,17 @@ import threading ...@@ -6,8 +20,17 @@ import threading
import time import time
import inspect import inspect
import ctypes import ctypes
from typing import Tuple
def validate_and_get_filepath(file_info) -> Tuple[str, bool]:
"""确认传入参数正确性,并返回文件路径
Args:
file_info: 文件选择窗口中返回的文件信息
def validate_and_get_filepath(file_info): Returns:
Tuple[str, bool]: 文件路径和传入信息是否包含路径
"""
if type(file_info[0]) == str: if type(file_info[0]) == str:
return file_info[0], True return file_info[0], True
# 兼容 空 # 兼容 空
...@@ -19,7 +42,7 @@ def trans_to_seconds(timePoint: str) -> float: ...@@ -19,7 +42,7 @@ def trans_to_seconds(timePoint: str) -> float:
"""将用户输入的时间字符串转换为秒数 """将用户输入的时间字符串转换为秒数
Args: Args:
timePoint (str): 时间字符串 timePoint (str): "hh:mm:ss.xxx"格式的时间字符串
Returns: Returns:
float: 时间字符串对应秒数 float: 时间字符串对应秒数
...@@ -33,8 +56,13 @@ def trans_to_seconds(timePoint: str) -> float: ...@@ -33,8 +56,13 @@ def trans_to_seconds(timePoint: str) -> float:
return time_in_seconds return time_in_seconds
def transfer_second_to_time(sec: str) -> str: def transfer_second_to_time(sec: str) -> str:
""" """将秒数转换为"hh:mm:ss.xxx"格式的时间字符串
输入xxxx.xxx秒,输出xx:xx:xx.xxx的格式
Args:
sec (str): 待转换的描述
Returns:
str: "hh:mm:ss.xxx"格式的时间字符串
""" """
duration = int(float(sec)) duration = int(float(sec))
hour = int(duration/3600) hour = int(duration/3600)
...@@ -44,7 +72,16 @@ def transfer_second_to_time(sec: str) -> str: ...@@ -44,7 +72,16 @@ def transfer_second_to_time(sec: str) -> str:
time = "%02d:%02d:%02d.%03d" % (hour, minutes, seconds, msec) time = "%02d:%02d:%02d.%03d" % (hour, minutes, seconds, msec)
return time return time
def replace_path_suffix(path, new_suffix): def replace_path_suffix(path: str, new_suffix: str) -> str:
"""替换文件路径后缀
Args:
path (str): 原文件路径
new_suffix (str): 新的后缀
Returns:
str: 新的文件路径
"""
return path.replace(os.path.splitext(path)[-1], new_suffix) return path.replace(os.path.splitext(path)[-1], new_suffix)
def check_sheet_content(book_path: str) -> bool: def check_sheet_content(book_path: str) -> bool:
...@@ -86,11 +123,21 @@ def get_sheetHead(book_path: str) -> list: ...@@ -86,11 +123,21 @@ def get_sheetHead(book_path: str) -> list:
return sheet_head return sheet_head
def get_progress_with_cmd(cmd: str, state=None): def get_progress_with_cmd(cmd: str, state=None):
"""获取ffmpeg命令行的执行进度
Args:
cmd (str): 用于后期处理的ffmpeg命令行
state (optional): 用于同步主界面中进度条的信号量. Defaults to None.
"""
if state is None: if state is None:
state = [0] state = [0]
pre = state[0] pre = state[0]
process = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, startup = subprocess.STARTUPINFO()
stderr=subprocess.STDOUT, encoding='utf-8', text=True) startup.dwFlags = subprocess.STARTF_USESHOWWINDOW
startup.wShowWindow = subprocess.SW_HIDE
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, encoding='utf-8', text=True,
startupinfo=startup)
for line in process.stdout: for line in process.stdout:
print(line) print(line)
duration_res = re.search(r'\sDuration: (?P<duration>\S+)', line) duration_res = re.search(r'\sDuration: (?P<duration>\S+)', line)
...@@ -126,46 +173,12 @@ def _async_raise(tid, exctype): ...@@ -126,46 +173,12 @@ def _async_raise(tid, exctype):
def stop_thread(thread): def stop_thread(thread):
"""杀死线程
Args:
thread: 待杀死的线程
"""
_async_raise(thread.ident, SystemExit) _async_raise(thread.ident, SystemExit)
if __name__ == '__main__': if __name__ == '__main__':
x = transfer_second_to_time("12000.923") pass
print(x) \ No newline at end of file
x = transfer_second_to_time("79.925")
print(x)
y = trans_to_seconds("1:00:00.92")
print(y)
z = transfer_second_to_time("1200.923")
print(z)
import time
strtime = "1:00:00.92"
# time.strptime(strtime, "%H:%M:%S")
import re
print("------------"*10)
# tests = ["00:12:34a", "01:12:34", "10:12:34.567", "12:12:34.89", "24:12:34.8", "2:34.2", "12:34.", "12:34.0", "02:34.0", "00:34.0"]
tests = ["01:12:34", "10:12:34.567", "12:12:34.89", "24:12:34.8", "2:34.2", "12:12:34.", "01:12:34.0", "02:02:34.0", "00:00:34.0"]
for s in tests:
x = re.match("^(([0-1]\d)|(2[0-4])):[0-5]\d:[0-5]\d(.\d{1,2})?$", s)
print("当前=", s)
print(x)
if x:
print(x.group())
print(x.group(1))
print("---------------------------------")
# print(re.match("^(([0-1]\d)|(2[0-4])):[0-5]\d$", "a12:34"))
# print(re.match("^(([0-1]\d)|(2[0-4])):[0-5]\d$", "a"))
# print(re.match("[^abc]", "plain"))
# print("------------")
# q = re.match("p|(pl)ain", "plain")
# print(q)
# print(q.group())
# print("------------")
# qq = re.match("ya(msen|nsen|nsem)", "yansen’s blog")
# print(qq)
# print(qq.group())
# print(qq.group(1))
\ No newline at end of file
...@@ -46,7 +46,7 @@ exe = EXE( ...@@ -46,7 +46,7 @@ exe = EXE(
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
upx=True, upx=True,
console=True, console=False,
disable_windowed_traceback=False, disable_windowed_traceback=False,
argv_emulation=False, argv_emulation=False,
target_arch=None, target_arch=None,
......
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