工作流,更新双GPU加速
This commit is contained in:
parent
a43b47c0b8
commit
12504b9cf5
107
config.py
Normal file
107
config.py
Normal file
@ -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()
|
@ -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()
|
||||
|
@ -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="以此为标准")
|
||||
|
Loading…
x
Reference in New Issue
Block a user