From 0b5eb6b1b8fcfcf9fb8d88d855eb7d9c547e6fcb Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sat, 28 Jan 2023 21:55:23 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E4=BD=BF=E7=94=A8HandBrake?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E6=8A=98=E8=85=BEffmpeg=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + config.py | 66 +++++++++-------- danmaku_workflow.py | 162 ++++++++++++++---------------------------- danmaku_xml_helper.py | 2 - 4 files changed, 94 insertions(+), 139 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a4ebe0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build/ +/dist/ +/venv/ diff --git a/config.py b/config.py index 6132e75..696e3c6 100644 --- a/config.py +++ b/config.py @@ -3,8 +3,12 @@ import os.path # [danmaku] +# use_danmu2ass +DANMAKU_USE_DANMU2ASS = False +# use_danmakufactory +DANMAKU_USE_DANMAKUFACTORY = True # exec -DANMAKU_FACTORY_EXEC = "DanmakuFactory" +DANMAKU_EXEC = "DanmakuFactory" # speed DANMAKU_SPEED = 12 # font @@ -16,18 +20,15 @@ VIDEO_RESOLUTION = "1280x720" # [ffmpeg] # exec FFMPEG_EXEC = "ffmpeg" -# hevc -FFMPEG_USE_HEVC = False -# nvidia_gpu -FFMPEG_USE_NVIDIA_GPU = False -# intel_gpu -FFMPEG_USE_INTEL_GPU = False -# vaapi -FFMPEG_USE_VAAPI = False -# bitrate -VIDEO_BITRATE = "2.5M" -# gop -VIDEO_GOP = 60 +# [handbrake] +# exec +HANDBRAKE_EXEC = "HandBrakeCli" +# preset_file +HANDBRAKE_PRESET_FILE = "handbrake.json" +# preset +HANDBRAKE_PRESET = "NvEnc" +# encopt +HANDBRAKE_ENCOPT = "" # [video] # enabled VIDEO_ENABLED = False @@ -66,8 +67,11 @@ def load_config(): config.read("config.ini", encoding="utf-8") if config.has_section("danmaku"): section = config['danmaku'] - global DANMAKU_FACTORY_EXEC, DANMAKU_SPEED, DANMAKU_FONT_NAME, VIDEO_RESOLUTION, DANMAKU_FONT_SIZE - DANMAKU_FACTORY_EXEC = section.get('exec', DANMAKU_FACTORY_EXEC) + global DANMAKU_EXEC, DANMAKU_SPEED, DANMAKU_FONT_NAME, VIDEO_RESOLUTION, DANMAKU_FONT_SIZE, \ + DANMAKU_USE_DANMU2ASS, DANMAKU_USE_DANMAKUFACTORY + DANMAKU_USE_DANMU2ASS = section.getboolean('use_danmu2ass', DANMAKU_USE_DANMU2ASS) + DANMAKU_USE_DANMAKUFACTORY = section.getboolean('use_danmakufactory', DANMAKU_USE_DANMAKUFACTORY) + DANMAKU_EXEC = section.get('exec', DANMAKU_EXEC) DANMAKU_SPEED = section.getfloat('speed', DANMAKU_SPEED) DANMAKU_FONT_NAME = section.get('font', DANMAKU_FONT_NAME) DANMAKU_FONT_SIZE = section.getint('font_size', DANMAKU_FONT_SIZE) @@ -87,15 +91,15 @@ def load_config(): VIDEO_CLIP_OVERFLOW_SEC = section.getfloat('overflow_sec', VIDEO_CLIP_OVERFLOW_SEC) if config.has_section("ffmpeg"): section = config['ffmpeg'] - global FFMPEG_EXEC, FFMPEG_USE_HEVC, FFMPEG_USE_NVIDIA_GPU, FFMPEG_USE_INTEL_GPU, VIDEO_BITRATE, VIDEO_CRF, \ - VIDEO_GOP, FFMPEG_USE_VAAPI + global FFMPEG_EXEC FFMPEG_EXEC = section.get('exec', FFMPEG_EXEC) - FFMPEG_USE_HEVC = section.getboolean('hevc', FFMPEG_USE_HEVC) - FFMPEG_USE_NVIDIA_GPU = section.getboolean('nvidia_gpu', FFMPEG_USE_NVIDIA_GPU) - FFMPEG_USE_INTEL_GPU = section.getboolean('intel_gpu', FFMPEG_USE_INTEL_GPU) - FFMPEG_USE_VAAPI = section.getboolean('vaapi', FFMPEG_USE_VAAPI) - VIDEO_BITRATE = section.get('bitrate', VIDEO_BITRATE) - VIDEO_GOP = section.getfloat('gop', VIDEO_GOP) + if config.has_section("handbrake"): + section = config['handbrake'] + global HANDBRAKE_EXEC, HANDBRAKE_PRESET_FILE, HANDBRAKE_PRESET, HANDBRAKE_ENCOPT + HANDBRAKE_EXEC = section.get('exec', HANDBRAKE_EXEC) + HANDBRAKE_PRESET_FILE = section.get('preset_file', HANDBRAKE_PRESET_FILE) + HANDBRAKE_PRESET = section.get('preset', HANDBRAKE_PRESET) + HANDBRAKE_ENCOPT = section.get('encopt', HANDBRAKE_ENCOPT) if config.has_section("recorder"): global BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, VIDEO_OUTPUT_DIR section = config['recorder'] @@ -108,7 +112,9 @@ def load_config(): def get_config(): config = { 'danmaku': { - 'exec': DANMAKU_FACTORY_EXEC, + 'exec': DANMAKU_EXEC, + 'use_danmu2ass': DANMAKU_USE_DANMU2ASS, + 'use_danmakufactory': DANMAKU_USE_DANMAKUFACTORY, 'speed': DANMAKU_SPEED, 'font': DANMAKU_FONT_NAME, 'font_size': DANMAKU_FONT_SIZE, @@ -127,12 +133,12 @@ def get_config(): }, 'ffmpeg': { 'exec': FFMPEG_EXEC, - 'hevc': FFMPEG_USE_HEVC, - 'nvidia_gpu': FFMPEG_USE_NVIDIA_GPU, - 'intel_gpu': FFMPEG_USE_INTEL_GPU, - 'vaapi': FFMPEG_USE_VAAPI, - 'bitrate': VIDEO_BITRATE, - 'gop': VIDEO_GOP, + }, + 'handbrake': { + 'exec': HANDBRAKE_EXEC, + 'preset_file': HANDBRAKE_PRESET_FILE, + 'preset': HANDBRAKE_PRESET, + 'encopt': HANDBRAKE_ENCOPT, }, 'recorder': { 'bili_dir': BILILIVE_RECORDER_DIRECTORY, diff --git a/danmaku_workflow.py b/danmaku_workflow.py index 7b1061a..462f1cc 100644 --- a/danmaku_workflow.py +++ b/danmaku_workflow.py @@ -1,4 +1,5 @@ # 工作流 +import json import os.path import platform import subprocess @@ -13,9 +14,10 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QFrame, QVBoxLayout, QPushButton, \ QSizePolicy, QMessageBox from danmaku_xml_helper import get_file_start, diff_danmaku_files, NoDanmakuException -from config import load_config, FFMPEG_EXEC, DANMAKU_FACTORY_EXEC, FFMPEG_USE_INTEL_GPU, FFMPEG_USE_NVIDIA_GPU, \ - VIDEO_BITRATE, VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC, VIDEO_RESOLUTION, DANMAKU_SPEED, DANMAKU_FONT_NAME, \ - VIDEO_OUTPUT_DIR, VIDEO_GOP, FFMPEG_USE_HEVC, DANMAKU_FONT_SIZE +from config import load_config, \ + DANMAKU_EXEC, DANMAKU_SPEED, DANMAKU_FONT_NAME, DANMAKU_FONT_SIZE, \ + VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC, VIDEO_RESOLUTION, VIDEO_OUTPUT_DIR, \ + FFMPEG_EXEC, HANDBRAKE_EXEC, HANDBRAKE_PRESET_FILE, HANDBRAKE_PRESET, HANDBRAKE_ENCOPT class Job: @@ -313,21 +315,8 @@ class WorkerThread(QThread): def encode_video_with_subtitles(self, orig_filename: str, subtitles: list[str], base_ts: float): new_filename = base_ts_to_filename(base_ts, False) new_fullpath = os.path.join(VIDEO_OUTPUT_DIR, new_filename) - if FFMPEG_USE_HEVC: - if FFMPEG_USE_NVIDIA_GPU: - process = get_encode_hevc_process_use_nvenc(orig_filename, subtitles, new_fullpath) - elif FFMPEG_USE_INTEL_GPU: - process = get_encode_hevc_process_use_intel(orig_filename, subtitles, new_fullpath) - else: - process = get_encode_hevc_process_use_cpu(orig_filename, subtitles, new_fullpath) - else: - if FFMPEG_USE_NVIDIA_GPU: - process = get_encode_process_use_nvenc(orig_filename, subtitles, new_fullpath) - elif FFMPEG_USE_INTEL_GPU: - process = get_encode_process_use_intel(orig_filename, subtitles, new_fullpath) - else: - process = get_encode_process_use_cpu(orig_filename, subtitles, new_fullpath) - self.handle_ffmpeg_output(process.stdout) + process = get_encode_process_use_handbrake(orig_filename, subtitles, new_fullpath) + self.handle_handbrake_output(process.stdout) process.wait() return [new_fullpath] @@ -362,6 +351,33 @@ class WorkerThread(QThread): current_sec += VIDEO_CLIP_EACH_SEC return True + def handle_handbrake_output(self, stdout: Optional[IO[bytes]]): + if stdout is None: + print("[!]STDOUT is null") + return + json_body = "" + json_start = False + while True: + line = stdout.readline() + if line == b"": + break + if json_start: + json_body += line.strip().decode("UTF-8") + if line.startswith(b"}"): + json_start = False + status_payload = json.loads(json_body) + if status_payload["State"] == "WORKING": + self.app.processCurTime.emit("ETA: {Hours:02d}:{Minutes:02d}:{Seconds:02d}".format_map(status_payload["Working"])) + self.app.processSpeed.emit("{Rate:.2f}FPS".format_map(status_payload["Working"])) + elif status_payload["State"] == "WORKDONE": + break + continue + if line.startswith(b"Progress:"): + json_start = True + json_body = "{" + self.app.processSpeed.emit("") + self.app.processCurTime.emit("") + def handle_ffmpeg_output(self, stdout: Optional[IO[bytes]]) -> str: out_time = "0:0:0.0" if stdout is None: @@ -403,7 +419,7 @@ def base_ts_to_filename(start_ts: float, is_mp4=False) -> str: def danmaku_to_subtitle(file: Union[os.PathLike[str], str], time_shift: float): new_subtitle_name = md5(file.encode("utf-8")).hexdigest() + ".ass" process = subprocess.Popen(( - DANMAKU_FACTORY_EXEC, "--ignore-warnings", + DANMAKU_EXEC, "--ignore-warnings", "-r", str(VIDEO_RESOLUTION), "-s", str(DANMAKU_SPEED), "-f", "5", "-S", str(DANMAKU_FONT_SIZE), "-N", str(DANMAKU_FONT_NAME), "--showmsgbox", "FALSE", "-O", "255", "-L", "1", "-D", "0", @@ -413,85 +429,15 @@ def danmaku_to_subtitle(file: Union[os.PathLike[str], str], time_shift: float): return new_subtitle_name -def get_encode_process_use_nvenc(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use Nvidia NvEnc Acceleration") +def get_encode_process_use_handbrake(orig_filename: str, subtitles: list[str], new_filename: str): + print("[+]Use HandBrakeCli") encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-i", orig_filename, "-vf", - ",".join("subtitles=%s" % i for i in subtitles) + ",hwupload_cuda", - "-c:v", "h264_nvenc", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], **subprocess_args(True)) - return encode_process - - -def get_encode_process_use_intel(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use Intel QSV Acceleration") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-hwaccel", "qsv", "-i", orig_filename, "-vf", - ",".join("subtitles=%s" % i for i in subtitles), - "-c:v", "h264_qsv", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], **subprocess_args(True)) - return encode_process - - -def get_encode_process_use_cpu(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use CPU Encode") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-i", orig_filename, "-vf", - ",".join("subtitles=%s" % i for i in subtitles), - "-c:v", "h264", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], **subprocess_args(True)) - return encode_process - - -def get_encode_hevc_process_use_nvenc(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use Nvidia NvEnc Acceleration") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-i", orig_filename, "-vf", - ",".join("subtitles=%s" % i for i in subtitles) + ",hwupload_cuda", - "-c:v", "hevc_nvenc", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], **subprocess_args(True)) - return encode_process - - -def get_encode_hevc_process_use_intel(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use Intel QSV Acceleration") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-hwaccel", "qsv", "-i", orig_filename, "-vf", - ",".join("subtitles=%s" % i for i in subtitles), - "-c:v", "hevc_qsv", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], **subprocess_args(True)) - return encode_process - - -def get_encode_hevc_process_use_cpu(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use CPU Encode") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-i", orig_filename, "-vf", - ",".join("subtitles=%s" % i for i in subtitles), - "-c:v", "hevc", - *_common_ffmpeg_params(), - # "-t", "10", + HANDBRAKE_EXEC, *_common_handbrake_setting(), + "--preset-import-file", HANDBRAKE_PRESET_FILE, "--preset", HANDBRAKE_PRESET, + "-i", orig_filename, "-x", HANDBRAKE_ENCOPT, + "--ssa-file", ",".join(i for i in subtitles), + "--ssa-burn", ",".join("%d" % (i+1) for i in range(len(subtitles))), + "-o", new_filename ], **subprocess_args(True)) return encode_process @@ -573,12 +519,23 @@ def is_linux() -> bool: def check_all_prerequisite(): - if not check_exec(DANMAKU_FACTORY_EXEC): + if not check_exec(DANMAKU_EXEC): input("弹幕处理工具不存在") exit(1) if not check_exec(FFMPEG_EXEC): input("FFMPEG工具不存在") exit(1) + if not check_exec(HANDBRAKE_EXEC): + input("HANDBRAKE工具不存在") + exit(1) + + +def _common_handbrake_setting(): + return ( + "--json", + "--crop-mode", "none", "--no-comb-detect", "--no-bwdif", "--no-decomb", "--no-detelecine", "--no-hqdn3d", + "--no-nlmeans", "--no-chroma-smooth", "--no-unsharp", "--no-lapsharp", "--no-deblock", "--no-optimize" + ) def _common_ffmpeg_setting(): @@ -587,15 +544,6 @@ def _common_ffmpeg_setting(): ) -def _common_ffmpeg_params(): - return ( - "-f", "mp4", "-b:v", VIDEO_BITRATE, "-c:a", "aac", - "-preset:v", "fast", "-profile:v", "main", "-avoid_negative_ts", "1", - "-qmin", "18", "-qmax", "38", "-g:v", str(VIDEO_GOP), - "-fflags", "+genpts", "-shortest" - ) - - def main(): check_all_prerequisite() app = QApplication(sys.argv) diff --git a/danmaku_xml_helper.py b/danmaku_xml_helper.py index a61815b..fe83f8f 100644 --- a/danmaku_xml_helper.py +++ b/danmaku_xml_helper.py @@ -5,8 +5,6 @@ from typing import Union from bs4 import BeautifulSoup -from config import DANMAKU_FACTORY_EXEC, VIDEO_RESOLUTION, DANMAKU_SPEED, DANMAKU_FONT_NAME - class NoDanmakuException(Exception): ...