diff --git a/config.py b/config.py new file mode 100644 index 0000000..93e6d8d --- /dev/null +++ b/config.py @@ -0,0 +1,107 @@ +import configparser +import os.path + + +# [danmaku] +# exec +DANMAKU_FACTORY_EXEC = "DanmakuFactory" +# speed +DANMAKU_SPEED = 12 +# font +DEFAULT_FONT_NAME = "Sarasa Term SC" +# resolution +VIDEO_RESOLUTION = "1280x720" +# [ffmpeg] +# exec +FFMPEG_EXEC = "ffmpeg" +# nvidia_gpu +FFMPEG_USE_NVIDIA_GPU = True +# intel_gpu +FFMPEG_USE_INTEL_GPU = True +# bitrate +VIDEO_BITRATE = "2.5M" +# [video] +# title +VIDEO_TITLE = "【永恒de草薙直播录播】直播于 {}" +# [clip] +# each_sec +VIDEO_CLIP_EACH_SEC = 6000 +# overflow_sec +VIDEO_CLIP_OVERFLOW_SEC = 5 +# [recorder] +# bili_dir +BILILIVE_RECORDER_DIRECTORY = "./" +# xigua_dir +XIGUALIVE_RECORDER_DIRECTORY = "./" + + +def load_config(): + if not os.path.exists("config.ini"): + write_config() + return False + config = configparser.ConfigParser() + config.read("config.ini", encoding="utf-8") + if config.has_section("danmaku"): + section = config['danmaku'] + global DANMAKU_FACTORY_EXEC, DANMAKU_SPEED, DEFAULT_FONT_NAME, VIDEO_RESOLUTION + DANMAKU_FACTORY_EXEC = section.get('exec', DANMAKU_FACTORY_EXEC) + DANMAKU_SPEED = section.getfloat('speed', DANMAKU_SPEED) + DEFAULT_FONT_NAME = section.get('font', DEFAULT_FONT_NAME) + VIDEO_RESOLUTION = section.get('resolution', VIDEO_RESOLUTION) + if config.has_section("video"): + section = config['video'] + global VIDEO_TITLE + VIDEO_TITLE = section.get('title', VIDEO_TITLE) + if config.has_section("clip"): + section = config['clip'] + global VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC + VIDEO_CLIP_EACH_SEC = section.getfloat('each_sec', VIDEO_CLIP_EACH_SEC) + 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_NVIDIA_GPU, FFMPEG_USE_INTEL_GPU, VIDEO_BITRATE + FFMPEG_EXEC = section.get('exec', FFMPEG_EXEC) + FFMPEG_USE_NVIDIA_GPU = section.getboolean('nvidia_gpu', FFMPEG_USE_NVIDIA_GPU) + FFMPEG_USE_INTEL_GPU = section.getboolean('intel_gpu', FFMPEG_USE_INTEL_GPU) + VIDEO_BITRATE = section.get('bitrate', VIDEO_BITRATE) + if config.has_section("recorder"): + global BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY + section = config['recorder'] + BILILIVE_RECORDER_DIRECTORY = section.get('bili_dir', BILILIVE_RECORDER_DIRECTORY) + XIGUALIVE_RECORDER_DIRECTORY = section.get('xigua_dir', XIGUALIVE_RECORDER_DIRECTORY) + return True + + +def get_config(): + config = { + 'danmaku': { + 'exec': DANMAKU_FACTORY_EXEC, + 'speed': DANMAKU_SPEED, + 'font': DEFAULT_FONT_NAME, + 'resolution': VIDEO_RESOLUTION, + }, + 'clip': { + 'each_sec': VIDEO_CLIP_EACH_SEC, + 'overflow_sec': VIDEO_CLIP_OVERFLOW_SEC, + }, + 'ffmpeg': { + 'exec': FFMPEG_EXEC, + 'nvidia_gpu': FFMPEG_USE_NVIDIA_GPU, + 'intel_gpu': FFMPEG_USE_INTEL_GPU, + 'bitrate': VIDEO_BITRATE, + }, + } + return config + + +def write_config(): + config = configparser.ConfigParser() + _config = get_config() + for _i in _config: + config[_i] = _config[_i] + with open("config.ini", "w", encoding="utf-8") as f: + config.write(f) + return True + + +load_config() diff --git a/danmaku_workflow.py b/danmaku_workflow.py index 78196ee..534cbd8 100644 --- a/danmaku_workflow.py +++ b/danmaku_workflow.py @@ -1,22 +1,19 @@ # 工作流 -import datetime import os.path +import platform import subprocess import sys -from typing import Optional, IO, AnyStr +import threading +from typing import Optional, IO, Union +from datetime import datetime, timedelta from PyQt5 import QtGui 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 - -DANMAKU_PATH = "workspace" -DANMAKU_FACTORY_CLI = "DFCLI.exe" -FFMPEG_TOOL = "ffmpeg.exe" -SPLIT_PATH = "split" -SPLIT_TOOL = "quick_split_video.exe" -HOME_PATH = os.path.realpath(".") + QSizePolicy, QMessageBox, QProgressBar +from danmaku_xml_helper import get_file_start, diff_danmaku_files, danmaku_to_subtitle +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 class Job: @@ -55,11 +52,14 @@ class WorkLabel(QLabel): self.adjustSize() def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None: - if self.workVideo is None or self.finished: + if self.finished: + self.finished = False + elif self.workVideo is None: self.parent().labelDestroy.emit(self) return self.deleteLater() - self.workVideo = None - self.workDanmaku = [] + else: + self.workVideo = None + self.workDanmaku = [] self.init_ui() def init_ui(self): @@ -149,19 +149,26 @@ class HomePage(QWidget): labelDestroy = pyqtSignal(WorkLabel) processCurTime = pyqtSignal(str) processSpeed = pyqtSignal(str) + processCurTime2 = pyqtSignal(str) + processSpeed2 = pyqtSignal(str) def __init__(self): super(HomePage, self).__init__() self.layout = None self.labels: list[WorkLabel] = [] - self.worker: Optional[WorkerThread] = None - self.btn_start: Optional[QPushButton] = None + self.worker: WorkerThread + self.btn_start: QPushButton self.showMessageBox.connect(self.on_show_message_box_info) self.labelDestroy.connect(self.on_label_destroy) self.processCurTime.connect(self.on_process_cur_time_change) self.processSpeed.connect(self.on_process_speed_change) + self.processCurTime2.connect(self.on_process_cur_time2_change) + self.processSpeed2.connect(self.on_process_speed2_change) self.process_cur_time = "-" self.process_speed = "-" + self.process_cur_time2 = "" + self.process_speed2 = "" + self.cur_clip_duration = 0 self.init_ui() def init_ui(self): @@ -225,21 +232,46 @@ class HomePage(QWidget): self.btn_start.setDisabled(True) self.btn_start.setText("正在处理") - def on_process_cur_time_change(self, s: str)->None: + def on_process_cur_time_change(self, s: str) -> None: + if self.process_cur_time == s: + return self.process_cur_time = s self.update_btn_process_text() - def on_process_speed_change(self, s: str)->None: + def on_process_speed_change(self, s: str) -> None: + if self.process_speed == s: + return self.process_speed = s self.update_btn_process_text() + def on_process_cur_time2_change(self, s: str) -> None: + if self.process_cur_time2 == s: + return + self.process_cur_time2 = s + self.update_btn_process_text() + + def on_process_speed2_change(self, s: str) -> None: + if self.process_speed2 == s: + return + self.process_speed2 = s + self.update_btn_process_text() + def update_btn_process_text(self): - self.btn_start.setText("{}@{}".format(self.process_cur_time, self.process_speed)) + _t = [] + if self.process_cur_time != "" and self.process_speed != "": + _t.append("{}@{}".format(self.process_cur_time, self.process_speed)) + if self.process_cur_time2 != "" and self.process_speed2 != "": + _t.append("{}@{}".format(self.process_cur_time2, self.process_speed2)) + if len(_t) == 0: + self.btn_start.setText("Working") + else: + self.btn_start.setText("|".join(_t)) def on_worker_stop(self): self.btn_start.setDisabled(False) self.btn_start.setText("开始") self.worker = None + self.handle_do() class WorkerThread(QThread): @@ -248,89 +280,257 @@ class WorkerThread(QThread): self.app = app self.label = label + def get_video_real_duration(self, filename) -> float: + ffmpeg_process = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-i", filename, "-c", "copy", "-f", + "null", "-" + ], **subprocess_args(True)) + _duration_str = self.handle_ffmpeg_output(ffmpeg_process.stdout) + ffmpeg_process.wait() + return duration_str_to_float(_duration_str) + def run(self) -> None: self.label.start_running() job = self.label.get_job() if job.type == Job.DANMAKU_ENCODE: self.run_danmaku_encode(job) elif job.type == Job.PURE_SPLIT: - self.run_pure_split(job) + self.quick_split_video(job.video) self.label.stop_running() def run_danmaku_encode(self, job: Job): - os.chdir(DANMAKU_PATH) base_danmaku = job.danmaku.pop(0) time_shift = 0 base_start_ts = get_file_start(base_danmaku) - new_subtitle_name = os.path.basename(base_danmaku) + ".ass" - subprocess.Popen(( - DANMAKU_FACTORY_CLI, - "-r", "1280x720", "-s", "10", "-f", "5", - "-S", "40", "-N", "\"Sarasa Term SC\"", "--showmsgbox", "FALSE", - "-O", "255", "-L", "1", "-D", "0", - "-o", "ass", new_subtitle_name, "-i", base_danmaku, "-t", str(time_shift) - ), **subprocess_args()).wait() - print(new_subtitle_name) + new_subtitle_name = danmaku_to_subtitle(base_danmaku, time_shift) job.subtitles.append(new_subtitle_name) for danmaku in job.danmaku: time_shift = diff_danmaku_files(base_danmaku, danmaku) - new_subtitle_name = os.path.basename(danmaku) + ".ass" - subprocess.Popen(( - DANMAKU_FACTORY_CLI, - "-r", "1280x720", "-s", "10", "-f", "5", - "-S", "40", "-N", "\"Sarasa Term SC\"", "--showmsgbox", "FALSE", - "-O", "255", "-L", "1", "-D", "0", - "-o", "ass", new_subtitle_name, "-i", danmaku, "-t", str(time_shift) - ), **subprocess_args()).wait() - print(new_subtitle_name) + new_subtitle_name = danmaku_to_subtitle(danmaku, time_shift) job.subtitles.append(new_subtitle_name) # 压制 - base_start = datetime.datetime.fromtimestamp(base_start_ts) - new_file_name = base_start.strftime("%Y%m%d_%H%M.flv") - encode_process = subprocess.Popen([ - FFMPEG_TOOL, "-hide_banner", "-progress", "-", "-v", "0", "-y", - "-i", job.video, "-vf", ",".join("subtitles=%s" % i for i in job.subtitles) + ",hwupload_cuda", - "-c:a", "copy", "-c:v", "h264_nvenc", "-f", "mp4", - "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", - "-b:v", "2.5M", "-bufsize:v", "5M", "-rc:v", "vbr_hq", "-bf:v", "3", - "-qmin", "10", "-qmax", "52", "-crf", "16", - # "-t", "10", - os.path.join(HOME_PATH, SPLIT_PATH, new_file_name) - ], **subprocess_args(True)) - self.handle_ffmpeg_output(encode_process.stdout) - encode_process.wait() + split_files = self.multi_gpu_encode_video_with_subtitles(job.video, job.subtitles, base_start_ts) + for file in split_files: + self.quick_split_video(file) for _f in job.subtitles: if os.path.isfile(_f): os.remove(_f) - os.chdir(HOME_PATH) - os.chdir(SPLIT_PATH) - split_process = subprocess.Popen([ - SPLIT_TOOL, new_file_name - ], **subprocess_args(True)) - self.handle_ffmpeg_output(split_process.stdout) - split_process.wait() - os.chdir(HOME_PATH) - def run_pure_split(self, job: Job): - os.chdir(SPLIT_PATH) - split_process = subprocess.Popen([ - SPLIT_TOOL, job.video - ], **subprocess_args(True)) - self.handle_ffmpeg_output(split_process.stdout) - split_process.wait() - os.chdir(HOME_PATH) + def encode_video_with_subtitles(self, orig_filename: str, subtitles: list[str], new_filename: str): + if FFMPEG_USE_NVIDIA_GPU: + print("[+]Use Nvidia NvEnc Acceleration") + encode_process = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-i", orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles) + ",hwupload_cuda", + "-c:a", "copy", "-c:v", "h264_nvenc", + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename + ], stdout=subprocess.PIPE) + elif FFMPEG_USE_INTEL_GPU: + if platform.system().lower() == "windows": + print("[+]Use Intel QSV Acceleration") + encode_process = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-hwaccel", "qsv", "-i", orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles), + "-c:a", "copy", "-c:v", "h264_qsv", + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename + ], stdout=subprocess.PIPE) + else: + print("[+]Use Intel VAAPI Acceleration") + encode_process = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-hwaccel", "vaapi", "-i", orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles) + ",hwupload", + "-c:a", "copy", "-c:v", "h264_vaapi", + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename + ], stdout=subprocess.PIPE) + else: + print("[+]Use CPU Encode") + encode_process = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-i", orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles), + "-c:a", "copy", "-c:v", "h264", + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename + ], stdout=subprocess.PIPE) + self.handle_ffmpeg_output(encode_process.stdout) + return encode_process.wait() - def handle_ffmpeg_output(self, stderr: IO[bytes]) -> None: + def multi_gpu_encode_video_with_subtitles(self, orig_filename: str, subtitles: list[str], base_ts: float): + new_filename = base_ts_to_filename(base_ts) + if not (FFMPEG_USE_NVIDIA_GPU and FFMPEG_USE_INTEL_GPU): + print("[!]Not Enabled Both GPU") + self.encode_video_with_subtitles(orig_filename, subtitles, new_filename) + return [new_filename] + duration = self.get_video_real_duration(orig_filename) + print("[>]Duration:", duration) + if duration > (VIDEO_CLIP_EACH_SEC * 5): + # qsv 压制前2段,剩余交由nvenc压制 + new_filename0 = base_ts_to_filename(base_ts) + new_filename1 = base_ts_to_filename(base_ts + VIDEO_CLIP_EACH_SEC * 2) + print("[+]Use Intel QSV Acceleration") + encode_process0 = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-hwaccel", "qsv", "-t", str(VIDEO_CLIP_EACH_SEC * 2 + (VIDEO_CLIP_OVERFLOW_SEC * 0.8)), "-i", + orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles), + "-c:a", "copy", "-c:v", "h264_qsv", + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename0 + ], **subprocess_args(True)) + print("[+]Use Nvidia NvEnc Acceleration") + encode_process1 = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-ss", str(VIDEO_CLIP_EACH_SEC * 2), "-copyts", "-i", orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles), + "-c:a", "copy", "-c:v", "h264_nvenc", "-ss", str(VIDEO_CLIP_EACH_SEC * 2), + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename1 + ], **subprocess_args(True)) + threading.Thread(target=self.handle_ffmpeg_output, args=(encode_process0.stdout, True,)).start() + threading.Thread(target=self.handle_ffmpeg_output, args=(encode_process1.stdout,)).start() + encode_process0.wait() + encode_process1.wait() + return [new_filename0, new_filename1] + elif duration > (VIDEO_CLIP_EACH_SEC * 3): + # 至少也要能切2片,才用双GPU加速 + new_filename0 = base_ts_to_filename(base_ts, True) + new_filename1 = base_ts_to_filename(base_ts + VIDEO_CLIP_EACH_SEC) + print("[+]Use Intel QSV Acceleration") + encode_process0 = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-hwaccel", "qsv", "-t", str(VIDEO_CLIP_EACH_SEC + (VIDEO_CLIP_OVERFLOW_SEC * 0.8)), "-i", + orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles), + "-c:a", "copy", "-c:v", "h264_qsv", + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename0 + ], **subprocess_args(True)) + print("[+]Use Nvidia NvEnc Acceleration") + encode_process1 = subprocess.Popen([ + FFMPEG_EXEC, "-hide_banner", "-progress", "-", "-loglevel", "error", "-y", + "-ss", str(VIDEO_CLIP_EACH_SEC), "-copyts", "-i", orig_filename, "-vf", + ",".join("subtitles=%s" % i for i in subtitles), + "-c:a", "copy", "-c:v", "h264_nvenc", "-ss", str(VIDEO_CLIP_EACH_SEC), + "-f", "mp4", "-preset:v", "fast", "-profile:v", "high", "-level", "4.1", + "-b:v", VIDEO_BITRATE, "-rc:v", "vbr", "-tune:v", "hq", + "-qmin", "10", "-qmax", "32", "-crf", "16", + # "-t", "10", + new_filename1 + ], **subprocess_args(True)) + threading.Thread(target=self.handle_ffmpeg_output, args=(encode_process0.stdout, True,)).start() + threading.Thread(target=self.handle_ffmpeg_output, args=(encode_process1.stdout,)).start() + encode_process0.wait() + encode_process1.wait() + return [new_filename1] + else: + self.encode_video_with_subtitles(orig_filename, subtitles, new_filename) + return [new_filename] + + def quick_split_video(self, file): + if not os.path.isfile(file): + raise FileNotFoundError(file) + file_name = os.path.split(file)[-1] + _create_dt = os.path.splitext(file_name)[0] + create_dt = datetime.strptime(_create_dt, "%Y%m%d_%H%M") + duration = self.get_video_real_duration(file) + current_sec = 0 + while current_sec < duration: + current_dt = (create_dt + timedelta(seconds=current_sec)).strftime("%Y%m%d_%H%M_") + print("CUR_DT", current_dt) + print("BIAS_T", current_sec) + split_process = subprocess.Popen([ + FFMPEG_EXEC, "-y", "-hide_banner", "-progress", "-", "-loglevel", "error", + "-ss", str(current_sec), + "-i", file_name, "-c", "copy", "-f", "mp4", + "-t", str(VIDEO_CLIP_EACH_SEC + VIDEO_CLIP_OVERFLOW_SEC), + "{}.mp4".format(current_dt) + ], **subprocess_args(True)) + self.handle_ffmpeg_output(split_process.stdout) + current_sec += VIDEO_CLIP_EACH_SEC + + def handle_ffmpeg_output(self, stdout: Optional[IO[bytes]], second=False) -> str: + out_time = "0:0:0.0" + if stdout is None: + return out_time + speed = "0" while True: - line = stderr.readline() + line = stdout.readline() if line == b"": break + if line.strip() == b"progress=end": + # 处理完毕 + break if line.startswith(b"out_time="): - cur_time = line.replace(b"out_time=", b"").decode() - self.app.processCurTime.emit(cur_time.strip()) + out_time = line.replace(b"out_time=", b"").decode().strip() + if second: + self.app.processCurTime2.emit(out_time) + else: + self.app.processCurTime.emit(out_time) if line.startswith(b"speed="): - speed = line.replace(b"speed=", b"").decode() - self.app.processSpeed.emit(speed.strip()) + speed = line.replace(b"speed=", b"").decode().strip() + if second: + self.app.processSpeed2.emit(speed) + else: + self.app.processSpeed.emit(speed) + if second: + self.app.processSpeed2.emit("") + self.app.processCurTime2.emit("") + else: + self.app.processSpeed.emit("") + self.app.processCurTime.emit("") + return out_time + + +class NvencWorkerThread(QThread): + ... + + +class IntelWorkerThread(QThread): + ... + + +class SplitWorkerThread(QThread): + ... + + +def duration_str_to_float(duration_str) -> float: + _duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1) + return _duration.total_seconds() + + +def base_ts_to_filename(start_ts: float, is_mp4=False) -> str: + base_start = datetime.fromtimestamp(start_ts) + if is_mp4: + return base_start.strftime("%Y%m%d_%H%M.mp4") + else: + return base_start.strftime("%Y%m%d_%H%M.flv") # Create a set of arguments which make a ``subprocess.Popen`` (and @@ -384,34 +584,38 @@ def subprocess_args(include_stdout=True): return ret +def check_exec(name: Union[os.PathLike[str], str]) -> bool: + if is_windows(): + check_process = subprocess.Popen([ + "where.exe", name + ], stdout=subprocess.PIPE) + check_process.wait() + return len(check_process.stdout.readlines()) > 0 + elif is_linux(): + check_process = subprocess.Popen([ + "which", name + ]) + check_process.wait() + return check_process.returncode == 0 + else: + return False + + +def is_windows() -> bool: + return platform.system().lower() == "windows" + + +def is_linux() -> bool: + return platform.system().lower() == "linux" + + def check_all_prerequisite(): - if not os.path.isdir(DANMAKU_PATH): - os.mkdir(DANMAKU_PATH) - if not os.path.isdir(SPLIT_PATH): - os.mkdir(SPLIT_PATH) - os.chdir(DANMAKU_PATH) - validate_process = subprocess.Popen([ - "where.exe", DANMAKU_FACTORY_CLI - ], **subprocess_args(True)) - if len(validate_process.stdout.readlines()) == 0: + if not check_exec(DANMAKU_FACTORY_EXEC): input("弹幕处理工具不存在") exit(1) - os.chdir(HOME_PATH) - os.chdir(SPLIT_PATH) - validate_process = subprocess.Popen([ - "where.exe", SPLIT_TOOL - ], **subprocess_args(True)) - if len(validate_process.stdout.readlines()) == 0: - input("视频分割工具不存在") - exit(1) - os.chdir(HOME_PATH) - validate_process = subprocess.Popen([ - "where.exe", FFMPEG_TOOL - ], **subprocess_args(True)) - if len(validate_process.stdout.readlines()) == 0: + if not check_exec(FFMPEG_EXEC): input("FFMPEG工具不存在") exit(1) - os.chdir(HOME_PATH) def main(): @@ -422,4 +626,5 @@ def main(): if __name__ == '__main__': + load_config() main() diff --git a/danmaku_xml_helper.py b/danmaku_xml_helper.py index fe83f8f..0fb6c46 100644 --- a/danmaku_xml_helper.py +++ b/danmaku_xml_helper.py @@ -1,10 +1,14 @@ import datetime import os import argparse +import subprocess +from hashlib import md5 from typing import Union from bs4 import BeautifulSoup +from config import DANMAKU_FACTORY_EXEC, VIDEO_RESOLUTION, DANMAKU_SPEED, DEFAULT_FONT_NAME + class NoDanmakuException(Exception): ... @@ -39,6 +43,19 @@ def diff_danmaku_files(base_file: Union[os.PathLike[str], str], file: Union[os.P return get_file_start(file) - get_file_start(base_file) +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", + "-r", str(VIDEO_RESOLUTION), "-s", str(DANMAKU_SPEED), "-f", "5", + "-S", "40", "-N", str(DEFAULT_FONT_NAME), "--showmsgbox", "FALSE", + "-O", "255", "-L", "1", "-D", "0", + "-o", "ass", new_subtitle_name, "-i", file, "-t", str(time_shift) + )) + process.wait() + return new_subtitle_name + + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("base", help="以此为标准")