vsr v1.0.0

This commit is contained in:
YaoFANGUK
2023-12-08 17:12:26 +08:00
commit 5e048175d3
312 changed files with 70534 additions and 0 deletions

371
gui.py Normal file
View File

@@ -0,0 +1,371 @@
# -*- coding: utf-8 -*-
"""
@Author : Fang Yao
@Time : 2021/4/1 6:07 下午
@FileName: gui.py
@desc: 字幕提取器图形化界面
"""
import backend.main
import os
import configparser
import PySimpleGUI as sg
import cv2
from threading import Thread
import multiprocessing
class SubtitleRemoverGUI:
def __init__(self):
# 初次运行检查运行环境是否正常
from paddle import fluid
fluid.install_check.run_check()
self.font = 'Arial 10'
self.theme = 'LightBrown12'
sg.theme(self.theme)
self.icon = os.path.join(os.path.dirname(__file__), 'design', 'vsr.ico')
self.screen_width, self.screen_height = sg.Window.get_screen_size()
self.subtitle_config_file = os.path.join(os.path.dirname(__file__), 'subtitle.ini')
print(self.screen_width, self.screen_height)
# 设置视频预览区域大小
self.video_preview_width = 960
self.video_preview_height = self.video_preview_width * 9 // 16
# 默认组件大小
self.horizontal_slider_size = (120, 20)
self.output_size = (100, 10)
self.progressbar_size = (60, 20)
# 分辨率低于1080
if self.screen_width // 2 < 960:
self.video_preview_width = 640
self.video_preview_height = self.video_preview_width * 9 // 16
self.horizontal_slider_size = (60, 20)
self.output_size = (58, 10)
self.progressbar_size = (28, 20)
# 字幕提取器布局
self.layout = None
# 字幕提取其窗口
self.window = None
# 视频路径
self.video_path = None
# 视频cap
self.video_cap = None
# 视频的帧率
self.fps = None
# 视频的帧数
self.frame_count = None
# 视频的宽
self.frame_width = None
# 视频的高
self.frame_height = None
# 设置字幕区域高宽
self.xmin = None
self.xmax = None
self.ymin = None
self.ymax = None
# 字幕提取器
self.sr = None
def run(self):
# 创建布局
self._create_layout()
# 创建窗口
self.window = sg.Window(title='Video Subtitle Remover', layout=self.layout,
icon=self.icon)
while True:
# 循环读取事件
event, values = self.window.read(timeout=10)
# 处理【打开】事件
self._file_event_handler(event, values)
# 处理【滑动】事件
self._slide_event_handler(event, values)
# 处理【运行】事件
self._run_event_handler(event, values)
# 如果关闭软件,退出
if event == sg.WIN_CLOSED:
break
# 更新进度条
if self.sr is not None:
self.window['-PROG-'].update(self.sr.progress_total)
if self.sr.preview_frame is not None:
self.window['-DISPLAY-'].update(data=cv2.imencode('.png', self._img_resize(self.sr.preview_frame))[1].tobytes())
if self.sr.isFinished:
# 1) 打开修改字幕滑块区域按钮
self.window['-Y-SLIDER-'].update(disabled=False)
self.window['-X-SLIDER-'].update(disabled=False)
self.window['-Y-SLIDER-H-'].update(disabled=False)
self.window['-X-SLIDER-W-'].update(disabled=False)
# 2) 打开【运行】、【打开】和【识别语言】按钮
self.window['-RUN-'].update(disabled=False)
self.window['-FILE-'].update(disabled=False)
self.window['-FILE_BTN-'].update(disabled=False)
self.sr = None
if len(self.video_paths) >= 1:
# 1) 关闭修改字幕滑块区域按钮
self.window['-Y-SLIDER-'].update(disabled=True)
self.window['-X-SLIDER-'].update(disabled=True)
self.window['-Y-SLIDER-H-'].update(disabled=True)
self.window['-X-SLIDER-W-'].update(disabled=True)
# 2) 关闭【运行】、【打开】和【识别语言】按钮
self.window['-RUN-'].update(disabled=True)
self.window['-FILE-'].update(disabled=True)
self.window['-FILE_BTN-'].update(disabled=True)
def _create_layout(self):
"""
创建字幕提取器布局
"""
garbage = os.path.join(os.path.dirname(__file__), 'output')
if os.path.exists(garbage):
import shutil
shutil.rmtree(garbage, True)
self.layout = [
# 显示视频预览
[sg.Image(size=(self.video_preview_width, self.video_preview_height), background_color='black',
key='-DISPLAY-')],
# 打开按钮 + 快进快退条
[sg.Input(key='-FILE-', visible=False, enable_events=True),
sg.FilesBrowse(button_text='Open', file_types=((
'All Files', '*.*'), ('mp4', '*.mp4'),
('flv', '*.flv'),
('wmv', '*.wmv'),
('avi', '*.avi')),
key='-FILE_BTN-', size=(10, 1), font=self.font),
sg.Slider(size=self.horizontal_slider_size, range=(1, 1), key='-SLIDER-', orientation='h',
enable_events=True, font=self.font,
disable_number_display=True),
],
# 输出区域
[sg.Output(size=self.output_size, font=self.font),
sg.Frame(title='Vertical', font=self.font, key='-FRAME1-',
layout=[[
sg.Slider(range=(0, 0), orientation='v', size=(10, 20),
disable_number_display=True,
enable_events=True, font=self.font,
pad=((10, 10), (20, 20)),
default_value=0, key='-Y-SLIDER-'),
sg.Slider(range=(0, 0), orientation='v', size=(10, 20),
disable_number_display=True,
enable_events=True, font=self.font,
pad=((10, 10), (20, 20)),
default_value=0, key='-Y-SLIDER-H-'),
]], pad=((15, 5), (0, 0))),
sg.Frame(title='Horizontal', font=self.font, key='-FRAME2-',
layout=[[
sg.Slider(range=(0, 0), orientation='v', size=(10, 20),
disable_number_display=True,
pad=((10, 10), (20, 20)),
enable_events=True, font=self.font,
default_value=0, key='-X-SLIDER-'),
sg.Slider(range=(0, 0), orientation='v', size=(10, 20),
disable_number_display=True,
pad=((10, 10), (20, 20)),
enable_events=True, font=self.font,
default_value=0, key='-X-SLIDER-W-'),
]], pad=((15, 5), (0, 0)))
],
# 运行按钮 + 进度条
[sg.Button(button_text='Run', key='-RUN-',
font=self.font, size=(20, 1)),
sg.ProgressBar(100, orientation='h', size=self.progressbar_size, key='-PROG-', auto_size_text=True)
],
]
def _file_event_handler(self, event, values):
"""
当点击打开按钮时:
1打开视频文件将画布显示视频帧
2获取视频信息初始化进度条滑块范围
"""
if event == '-FILE-':
self.video_paths = values['-FILE-'].split(';')
self.video_path = self.video_paths[0]
if self.video_path != '':
self.video_cap = cv2.VideoCapture(self.video_path)
if self.video_cap is None:
return
if self.video_cap.isOpened():
ret, frame = self.video_cap.read()
if ret:
for video in self.video_paths:
print(f"Open Video Success{video}")
# 获取视频的帧数
self.frame_count = self.video_cap.get(cv2.CAP_PROP_FRAME_COUNT)
# 获取视频的高度
self.frame_height = self.video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# 获取视频的宽度
self.frame_width = self.video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)
# 获取视频的帧率
self.fps = self.video_cap.get(cv2.CAP_PROP_FPS)
# 调整视频帧大小,使播放器能够显示
resized_frame = self._img_resize(frame)
# resized_frame = cv2.resize(src=frame, dsize=(self.video_preview_width, self.video_preview_height))
# 显示视频帧
self.window['-DISPLAY-'].update(data=cv2.imencode('.png', resized_frame)[1].tobytes())
# 更新视频进度条滑块range
self.window['-SLIDER-'].update(range=(1, self.frame_count))
self.window['-SLIDER-'].update(1)
# 预设字幕区域位置
y_p, h_p, x_p, w_p = self.parse_subtitle_config()
y = self.frame_height * y_p
h = self.frame_height * h_p
x = self.frame_width * x_p
w = self.frame_width * w_p
# 更新视频字幕位置滑块range
# 更新Y-SLIDER范围
self.window['-Y-SLIDER-'].update(range=(0, self.frame_height), disabled=False)
# 更新Y-SLIDER默认值
self.window['-Y-SLIDER-'].update(y)
# 更新X-SLIDER范围
self.window['-X-SLIDER-'].update(range=(0, self.frame_width), disabled=False)
# 更新X-SLIDER默认值
self.window['-X-SLIDER-'].update(x)
# 更新Y-SLIDER-H范围
self.window['-Y-SLIDER-H-'].update(range=(0, self.frame_height - y))
# 更新Y-SLIDER-H默认值
self.window['-Y-SLIDER-H-'].update(h)
# 更新X-SLIDER-W范围
self.window['-X-SLIDER-W-'].update(range=(0, self.frame_width - x))
# 更新X-SLIDER-W默认值
self.window['-X-SLIDER-W-'].update(w)
self._update_preview(frame, (y, h, x, w))
def _run_event_handler(self, event, values):
"""
当点击运行按钮时:
1) 禁止修改字幕滑块区域
2) 禁止再次点击【运行】和【打开】按钮
3) 设定字幕区域位置
"""
if event == '-RUN-':
if self.video_cap is None:
print('Please Open Video First')
else:
# 1) 禁止修改字幕滑块区域
self.window['-Y-SLIDER-'].update(disabled=True)
self.window['-X-SLIDER-'].update(disabled=True)
self.window['-Y-SLIDER-H-'].update(disabled=True)
self.window['-X-SLIDER-W-'].update(disabled=True)
# 2) 禁止再次点击【运行】、【打开】和【识别语言】按钮
self.window['-RUN-'].update(disabled=True)
self.window['-FILE-'].update(disabled=True)
self.window['-FILE_BTN-'].update(disabled=True)
# 3) 设定字幕区域位置
self.xmin = int(values['-X-SLIDER-'])
self.xmax = int(values['-X-SLIDER-'] + values['-X-SLIDER-W-'])
self.ymin = int(values['-Y-SLIDER-'])
self.ymax = int(values['-Y-SLIDER-'] + values['-Y-SLIDER-H-'])
if self.ymax > self.frame_height:
self.ymax = self.frame_height
if self.xmax > self.frame_width:
self.xmax = self.frame_width
print(f"{'SubtitleArea'}({self.ymin},{self.ymax},{self.xmin},{self.xmax})")
subtitle_area = (self.ymin, self.ymax, self.xmin, self.xmax)
y_p = self.ymin / self.frame_height
h_p = (self.ymax - self.ymin) / self.frame_height
x_p = self.xmin / self.frame_width
w_p = (self.xmax - self.xmin) / self.frame_width
self.set_subtitle_config(y_p, h_p, x_p, w_p)
def task():
while self.video_paths:
video_path = self.video_paths.pop()
self.sr = backend.main.SubtitleRemover(video_path, subtitle_area)
self.sr.run()
Thread(target=task, daemon=True).start()
self.video_cap.release()
self.video_cap = None
def _slide_event_handler(self, event, values):
"""
当滑动视频进度条/滑动字幕选择区域滑块时:
1) 判断视频是否存在,如果存在则显示对应的视频帧
2) 绘制rectangle
"""
if event == '-SLIDER-' or event == '-Y-SLIDER-' or event == '-Y-SLIDER-H-' or event == '-X-SLIDER-' or event \
== '-X-SLIDER-W-':
if self.video_cap is not None and self.video_cap.isOpened():
frame_no = int(values['-SLIDER-'])
self.video_cap.set(cv2.CAP_PROP_POS_FRAMES, frame_no)
ret, frame = self.video_cap.read()
if ret:
self.window['-Y-SLIDER-H-'].update(range=(0, self.frame_height-values['-Y-SLIDER-']))
self.window['-X-SLIDER-W-'].update(range=(0, self.frame_width-values['-X-SLIDER-']))
# 画字幕框
y = int(values['-Y-SLIDER-'])
h = int(values['-Y-SLIDER-H-'])
x = int(values['-X-SLIDER-'])
w = int(values['-X-SLIDER-W-'])
self._update_preview(frame, (y, h, x, w))
def _update_preview(self, frame, y_h_x_w):
y, h, x, w = y_h_x_w
# 画字幕框
draw = cv2.rectangle(img=frame, pt1=(int(x), int(y)), pt2=(int(x) + int(w), int(y) + int(h)),
color=(0, 255, 0), thickness=3)
# 调整视频帧大小,使播放器能够显示
resized_frame = self._img_resize(draw)
# 显示视频帧
self.window['-DISPLAY-'].update(data=cv2.imencode('.png', resized_frame)[1].tobytes())
def _img_resize(self, image):
top, bottom, left, right = (0, 0, 0, 0)
height, width = image.shape[0], image.shape[1]
# 对长短不想等的图片,找到最长的一边
longest_edge = height
# 计算短边需要增加多少像素宽度使其与长边等长
if width < longest_edge:
dw = longest_edge - width
left = dw // 2
right = dw - left
else:
pass
# 给图像增加边界
constant = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=[0, 0, 0])
return cv2.resize(constant, (self.video_preview_width, self.video_preview_height))
def set_subtitle_config(self, y, h, x, w):
# 写入配置文件
with open(self.subtitle_config_file, mode='w', encoding='utf-8') as f:
f.write('[AREA]\n')
f.write(f'Y = {y}\n')
f.write(f'H = {h}\n')
f.write(f'X = {x}\n')
f.write(f'W = {w}\n')
def parse_subtitle_config(self):
y_p, h_p, x_p, w_p = .78, .21, .05, .9
# 如果配置文件不存在,则写入配置文件
if not os.path.exists(self.subtitle_config_file):
self.set_subtitle_config(y_p, h_p, x_p, w_p)
return y_p, h_p, x_p, w_p
else:
try:
config = configparser.ConfigParser()
config.read(self.subtitle_config_file, encoding='utf-8')
conf_y_p, conf_h_p, conf_x_p, conf_w_p = float(config['AREA']['Y']), float(config['AREA']['H']), float(config['AREA']['X']), float(config['AREA']['W'])
return conf_y_p, conf_h_p, conf_x_p, conf_w_p
except Exception:
self.set_subtitle_config(y_p, h_p, x_p, w_p)
return y_p, h_p, x_p, w_p
if __name__ == '__main__':
try:
multiprocessing.set_start_method("spawn")
# 运行图形化界面
subtitleRemoverGUI = SubtitleRemoverGUI()
subtitleRemoverGUI.run()
except Exception as e:
print(f'[{type(e)}] {e}')
import traceback
traceback.print_exc()
msg = traceback.format_exc()
err_log_path = os.path.join(os.path.expanduser('~'), 'VSR-Error-Message.log')
with open(err_log_path, 'w', encoding='utf-8') as f:
f.writelines(msg)
import platform
if platform.system() == 'Windows':
os.system('pause')
else:
input()