Compare commits

...

6 Commits

3 changed files with 133 additions and 60 deletions

View File

@ -8,21 +8,44 @@ DANMAKU_FACTORY_EXEC = "DanmakuFactory"
# speed # speed
DANMAKU_SPEED = 12 DANMAKU_SPEED = 12
# font # font
DEFAULT_FONT_NAME = "Sarasa Term SC" DANMAKU_FONT_NAME = "Sarasa Term SC"
# font_size
DANMAKU_FONT_SIZE = 40
# resolution # resolution
VIDEO_RESOLUTION = "1280x720" VIDEO_RESOLUTION = "1280x720"
# [ffmpeg] # [ffmpeg]
# exec # exec
FFMPEG_EXEC = "ffmpeg" FFMPEG_EXEC = "ffmpeg"
# hevc
FFMPEG_USE_HEVC = False
# nvidia_gpu # nvidia_gpu
FFMPEG_USE_NVIDIA_GPU = True FFMPEG_USE_NVIDIA_GPU = False
# intel_gpu # intel_gpu
FFMPEG_USE_INTEL_GPU = True FFMPEG_USE_INTEL_GPU = False
# vaapi
FFMPEG_USE_VAAPI = False
# bitrate # bitrate
VIDEO_BITRATE = "2.5M" VIDEO_BITRATE = "2.5M"
# crf
VIDEO_CRF = 28
# gop
VIDEO_GOP = 60
# [video] # [video]
# enabled
VIDEO_ENABLED = False
# title # title
VIDEO_TITLE = "【永恒de草薙直播录播】直播于 {}" VIDEO_TITLE = "【永恒de草薙直播录播】直播于 {}"
# desc
VIDEO_DESC = "弹幕来源B站直播\r\n\r\n" + \
"原主播永恒de草薙\r\n往期节目单查询https://comment.sc.jerryyan.top\r\n\r\n" + \
"好多人这时候就开始问了,在哪直播呀,哎。对吧…咱来啦啊。在哔哩哔哩啊," \
"无论你是用网页百度搜索哔哩哔哩官网或者是用手机的APP下载一个哔哩哔哩" \
"都能找到原主播。大概每天晚七点半左右吧…一般都是往左。然后的话呢搜索永恒de草薙就能找到他。" \
"那么今天的话呢今天的录播也就发完了…他是本期的主播永恒。咱明天同一时间不见不散…拜拜!!"
# tid
VIDEO_TID = 17
# tags
VIDEO_TAGS = "永恒de草薙,三国,三国战记,直播录像,录播,怀旧,街机"
# [clip] # [clip]
# each_sec # each_sec
VIDEO_CLIP_EACH_SEC = 6000 VIDEO_CLIP_EACH_SEC = 6000
@ -45,15 +68,20 @@ def load_config():
config.read("config.ini", encoding="utf-8") config.read("config.ini", encoding="utf-8")
if config.has_section("danmaku"): if config.has_section("danmaku"):
section = config['danmaku'] section = config['danmaku']
global DANMAKU_FACTORY_EXEC, DANMAKU_SPEED, DEFAULT_FONT_NAME, VIDEO_RESOLUTION global DANMAKU_FACTORY_EXEC, DANMAKU_SPEED, DANMAKU_FONT_NAME, VIDEO_RESOLUTION, DANMAKU_FONT_SIZE
DANMAKU_FACTORY_EXEC = section.get('exec', DANMAKU_FACTORY_EXEC) DANMAKU_FACTORY_EXEC = section.get('exec', DANMAKU_FACTORY_EXEC)
DANMAKU_SPEED = section.getfloat('speed', DANMAKU_SPEED) DANMAKU_SPEED = section.getfloat('speed', DANMAKU_SPEED)
DEFAULT_FONT_NAME = section.get('font', DEFAULT_FONT_NAME) DANMAKU_FONT_NAME = section.get('font', DANMAKU_FONT_NAME)
DANMAKU_FONT_SIZE = section.getint('font_size', DANMAKU_FONT_SIZE)
VIDEO_RESOLUTION = section.get('resolution', VIDEO_RESOLUTION) VIDEO_RESOLUTION = section.get('resolution', VIDEO_RESOLUTION)
if config.has_section("video"): if config.has_section("video"):
section = config['video'] section = config['video']
global VIDEO_TITLE global VIDEO_ENABLED, VIDEO_TITLE, VIDEO_DESC, VIDEO_TID, VIDEO_TAGS
VIDEO_ENABLED = section.getboolean('enabled', VIDEO_ENABLED)
VIDEO_TITLE = section.get('title', VIDEO_TITLE) VIDEO_TITLE = section.get('title', VIDEO_TITLE)
VIDEO_DESC = section.get('desc', VIDEO_DESC)
VIDEO_TID = section.getint('tid', VIDEO_TID)
VIDEO_TAGS = section.get('tags', VIDEO_TAGS)
if config.has_section("clip"): if config.has_section("clip"):
section = config['clip'] section = config['clip']
global VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC global VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC
@ -61,11 +89,16 @@ def load_config():
VIDEO_CLIP_OVERFLOW_SEC = section.getfloat('overflow_sec', VIDEO_CLIP_OVERFLOW_SEC) VIDEO_CLIP_OVERFLOW_SEC = section.getfloat('overflow_sec', VIDEO_CLIP_OVERFLOW_SEC)
if config.has_section("ffmpeg"): if config.has_section("ffmpeg"):
section = config['ffmpeg'] section = config['ffmpeg']
global FFMPEG_EXEC, FFMPEG_USE_NVIDIA_GPU, FFMPEG_USE_INTEL_GPU, VIDEO_BITRATE global FFMPEG_EXEC, FFMPEG_USE_HEVC, FFMPEG_USE_NVIDIA_GPU, FFMPEG_USE_INTEL_GPU, VIDEO_BITRATE, VIDEO_CRF, \
VIDEO_GOP, FFMPEG_USE_VAAPI
FFMPEG_EXEC = section.get('exec', 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_NVIDIA_GPU = section.getboolean('nvidia_gpu', FFMPEG_USE_NVIDIA_GPU)
FFMPEG_USE_INTEL_GPU = section.getboolean('intel_gpu', FFMPEG_USE_INTEL_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_BITRATE = section.get('bitrate', VIDEO_BITRATE)
VIDEO_CRF = section.getfloat('crf', VIDEO_CRF)
VIDEO_GOP = section.getfloat('gop', VIDEO_GOP)
if config.has_section("recorder"): if config.has_section("recorder"):
global BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, VIDEO_OUTPUT_DIR global BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, VIDEO_OUTPUT_DIR
section = config['recorder'] section = config['recorder']
@ -80,18 +113,30 @@ def get_config():
'danmaku': { 'danmaku': {
'exec': DANMAKU_FACTORY_EXEC, 'exec': DANMAKU_FACTORY_EXEC,
'speed': DANMAKU_SPEED, 'speed': DANMAKU_SPEED,
'font': DEFAULT_FONT_NAME, 'font': DANMAKU_FONT_NAME,
'font_size': DANMAKU_FONT_SIZE,
'resolution': VIDEO_RESOLUTION, 'resolution': VIDEO_RESOLUTION,
}, },
'video': {
'enabled': VIDEO_ENABLED,
'title': VIDEO_TITLE,
'desc': VIDEO_DESC,
'tid': VIDEO_TID,
'tags': VIDEO_TAGS,
},
'clip': { 'clip': {
'each_sec': VIDEO_CLIP_EACH_SEC, 'each_sec': VIDEO_CLIP_EACH_SEC,
'overflow_sec': VIDEO_CLIP_OVERFLOW_SEC, 'overflow_sec': VIDEO_CLIP_OVERFLOW_SEC,
}, },
'ffmpeg': { 'ffmpeg': {
'exec': FFMPEG_EXEC, 'exec': FFMPEG_EXEC,
'hevc': FFMPEG_USE_HEVC,
'nvidia_gpu': FFMPEG_USE_NVIDIA_GPU, 'nvidia_gpu': FFMPEG_USE_NVIDIA_GPU,
'intel_gpu': FFMPEG_USE_INTEL_GPU, 'intel_gpu': FFMPEG_USE_INTEL_GPU,
'vaapi': FFMPEG_USE_VAAPI,
'bitrate': VIDEO_BITRATE, 'bitrate': VIDEO_BITRATE,
'crf': VIDEO_CRF,
'gop': VIDEO_GOP,
}, },
'recorder': { 'recorder': {
'bili_dir': BILILIVE_RECORDER_DIRECTORY, 'bili_dir': BILILIVE_RECORDER_DIRECTORY,

View File

@ -3,7 +3,6 @@ import os.path
import platform import platform
import subprocess import subprocess
import sys import sys
import threading
import traceback import traceback
from hashlib import md5 from hashlib import md5
from typing import Optional, IO, Union from typing import Optional, IO, Union
@ -15,8 +14,8 @@ from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QFrame, QVBoxLayout,
QSizePolicy, QMessageBox QSizePolicy, QMessageBox
from danmaku_xml_helper import get_file_start, diff_danmaku_files, NoDanmakuException 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, \ 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, DEFAULT_FONT_NAME, \ VIDEO_BITRATE, VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC, VIDEO_RESOLUTION, DANMAKU_SPEED, DANMAKU_FONT_NAME, \
VIDEO_OUTPUT_DIR VIDEO_OUTPUT_DIR, VIDEO_CRF, VIDEO_GOP, FFMPEG_USE_HEVC, DANMAKU_FONT_SIZE
class Job: class Job:
@ -304,17 +303,24 @@ class WorkerThread(QThread):
# 压制 # 压制
files_need_split = self.encode_video_with_subtitles(job.video, job.subtitles, base_start_ts) files_need_split = self.encode_video_with_subtitles(job.video, job.subtitles, base_start_ts)
for _f in files_need_split: for _f in files_need_split:
self.quick_split_video(_f) need_delete = self.quick_split_video(_f)
for _f in job.subtitles: if need_delete:
if os.path.isfile(_f):
os.remove(_f) os.remove(_f)
for _f in files_need_split: for _f in job.subtitles:
if os.path.isfile(_f): if os.path.isfile(_f):
os.remove(_f) os.remove(_f)
def encode_video_with_subtitles(self, orig_filename: str, subtitles: list[str], base_ts: float): 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_filename = base_ts_to_filename(base_ts, False)
new_fullpath = os.path.join(VIDEO_OUTPUT_DIR, new_filename) 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: if FFMPEG_USE_NVIDIA_GPU:
process = get_encode_process_use_nvenc(orig_filename, subtitles, new_fullpath) process = get_encode_process_use_nvenc(orig_filename, subtitles, new_fullpath)
elif FFMPEG_USE_INTEL_GPU: elif FFMPEG_USE_INTEL_GPU:
@ -333,6 +339,9 @@ class WorkerThread(QThread):
create_dt = datetime.strptime(_create_dt, "%Y%m%d_%H%M") create_dt = datetime.strptime(_create_dt, "%Y%m%d_%H%M")
duration = self.get_video_real_duration(file) duration = self.get_video_real_duration(file)
current_sec = 0 current_sec = 0
if duration < VIDEO_CLIP_EACH_SEC:
print("[-]Less than each sec, skip")
return False
while current_sec < duration: while current_sec < duration:
if (current_sec + VIDEO_CLIP_OVERFLOW_SEC * 2) > duration: if (current_sec + VIDEO_CLIP_OVERFLOW_SEC * 2) > duration:
print("[-]Less than 2 overflow sec, skip") print("[-]Less than 2 overflow sec, skip")
@ -343,7 +352,7 @@ class WorkerThread(QThread):
split_process = subprocess.Popen([ split_process = subprocess.Popen([
FFMPEG_EXEC, "-y", "-hide_banner", "-progress", "-", "-loglevel", "error", FFMPEG_EXEC, "-y", "-hide_banner", "-progress", "-", "-loglevel", "error",
"-ss", str(current_sec), "-ss", str(current_sec),
"-i", file, "-c", "copy", "-f", "mp4", "-i", file, "-c:v", "copy", "-f", "mp4", "-c:a", "aac",
"-t", str(VIDEO_CLIP_EACH_SEC + VIDEO_CLIP_OVERFLOW_SEC), "-t", str(VIDEO_CLIP_EACH_SEC + VIDEO_CLIP_OVERFLOW_SEC),
"-fflags", "+genpts", "-shortest", "-movflags", "faststart", "-fflags", "+genpts", "-shortest", "-movflags", "faststart",
os.path.join(VIDEO_OUTPUT_DIR, "{}.mp4".format(current_dt)) os.path.join(VIDEO_OUTPUT_DIR, "{}.mp4".format(current_dt))
@ -351,6 +360,7 @@ class WorkerThread(QThread):
self.handle_ffmpeg_output(split_process.stdout) self.handle_ffmpeg_output(split_process.stdout)
split_process.wait() split_process.wait()
current_sec += VIDEO_CLIP_EACH_SEC current_sec += VIDEO_CLIP_EACH_SEC
return True
def handle_ffmpeg_output(self, stdout: Optional[IO[bytes]]) -> str: def handle_ffmpeg_output(self, stdout: Optional[IO[bytes]]) -> str:
out_time = "0:0:0.0" out_time = "0:0:0.0"
@ -377,18 +387,6 @@ class WorkerThread(QThread):
return out_time return out_time
class NvencWorkerThread(QThread):
...
class IntelWorkerThread(QThread):
...
class SplitWorkerThread(QThread):
...
def duration_str_to_float(duration_str) -> float: def duration_str_to_float(duration_str) -> float:
_duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1) _duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1)
return _duration.total_seconds() return _duration.total_seconds()
@ -407,7 +405,7 @@ def danmaku_to_subtitle(file: Union[os.PathLike[str], str], time_shift: float):
process = subprocess.Popen(( process = subprocess.Popen((
DANMAKU_FACTORY_EXEC, "--ignore-warnings", DANMAKU_FACTORY_EXEC, "--ignore-warnings",
"-r", str(VIDEO_RESOLUTION), "-s", str(DANMAKU_SPEED), "-f", "5", "-r", str(VIDEO_RESOLUTION), "-s", str(DANMAKU_SPEED), "-f", "5",
"-S", "40", "-N", str(DEFAULT_FONT_NAME), "--showmsgbox", "FALSE", "-S", str(DANMAKU_FONT_SIZE), "-N", str(DANMAKU_FONT_NAME), "--showmsgbox", "FALSE",
"-O", "255", "-L", "1", "-D", "0", "-O", "255", "-L", "1", "-D", "0",
"-o", "ass", new_subtitle_name, "-i", file, "-t", str(time_shift) "-o", "ass", new_subtitle_name, "-i", file, "-t", str(time_shift)
), **subprocess_args(True)) ), **subprocess_args(True))
@ -421,7 +419,7 @@ def get_encode_process_use_nvenc(orig_filename: str, subtitles: list[str], new_f
FFMPEG_EXEC, *_common_ffmpeg_setting(), FFMPEG_EXEC, *_common_ffmpeg_setting(),
"-i", orig_filename, "-vf", "-i", orig_filename, "-vf",
",".join("subtitles=%s" % i for i in subtitles) + ",hwupload_cuda", ",".join("subtitles=%s" % i for i in subtitles) + ",hwupload_cuda",
"-c:v", "h264_nvenc", "-rc:v", "vbr", "-c:v", "h264_nvenc",
*_common_ffmpeg_params(), *_common_ffmpeg_params(),
# "-t", "10", # "-t", "10",
new_filename new_filename
@ -430,24 +428,12 @@ def get_encode_process_use_nvenc(orig_filename: str, subtitles: list[str], new_f
def get_encode_process_use_intel(orig_filename: str, subtitles: list[str], new_filename: str): def get_encode_process_use_intel(orig_filename: str, subtitles: list[str], new_filename: str):
if platform.system().lower() == "windows":
print("[+]Use Intel QSV Acceleration") print("[+]Use Intel QSV Acceleration")
encode_process = subprocess.Popen([ encode_process = subprocess.Popen([
FFMPEG_EXEC, *_common_ffmpeg_setting(), FFMPEG_EXEC, *_common_ffmpeg_setting(),
"-hwaccel", "qsv", "-i", orig_filename, "-vf", "-hwaccel", "qsv", "-i", orig_filename, "-vf",
",".join("subtitles=%s" % i for i in subtitles), ",".join("subtitles=%s" % i for i in subtitles),
"-c:v", "h264_qsv", "-rc:v", "vbr", "-c:v", "h264_qsv",
*_common_ffmpeg_params(),
# "-t", "10",
new_filename
], **subprocess_args(True))
else:
print("[+]Use Intel VAAPI Acceleration")
encode_process = subprocess.Popen([
FFMPEG_EXEC, *_common_ffmpeg_setting(),
"-hwaccel", "vaapi", "-i", orig_filename, "-vf",
",".join("subtitles=%s" % i for i in subtitles) + ",hwupload",
"-c:v", "h264_vaapi", "-rc:v", "vbr",
*_common_ffmpeg_params(), *_common_ffmpeg_params(),
# "-t", "10", # "-t", "10",
new_filename new_filename
@ -469,6 +455,48 @@ def get_encode_process_use_cpu(orig_filename: str, subtitles: list[str], new_fil
return encode_process 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",
new_filename
], **subprocess_args(True))
return encode_process
# Create a set of arguments which make a ``subprocess.Popen`` (and # Create a set of arguments which make a ``subprocess.Popen`` (and
# variants) call work with or without Pyinstaller, ``--noconsole`` or # variants) call work with or without Pyinstaller, ``--noconsole`` or
# not, on Windows and Linux. Typical use:: # not, on Windows and Linux. Typical use::
@ -561,9 +589,9 @@ def _common_ffmpeg_setting():
def _common_ffmpeg_params(): def _common_ffmpeg_params():
return ( return (
"-f", "mp4", "-b:v", VIDEO_BITRATE, "-c:a", "copy", "-f", "mp4", "-b:v", VIDEO_BITRATE, "-c:a", "aac",
"-preset:v", "fast", "-profile:v", "main", "-avoid_negative_ts", "1", "-preset:v", "fast", "-profile:v", "main", "-avoid_negative_ts", "1",
"-qmin", "12", "-qmax", "34", "-crf", "26", "-g:v", "60", "-qmin", "18", "-qmax", "38", "-crf", str(VIDEO_CRF), "-g:v", str(VIDEO_GOP),
"-fflags", "+genpts", "-shortest", "-movflags", "faststart" "-fflags", "+genpts", "-shortest", "-movflags", "faststart"
) )

View File

@ -5,7 +5,7 @@ from typing import Union
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from config import DANMAKU_FACTORY_EXEC, VIDEO_RESOLUTION, DANMAKU_SPEED, DEFAULT_FONT_NAME from config import DANMAKU_FACTORY_EXEC, VIDEO_RESOLUTION, DANMAKU_SPEED, DANMAKU_FONT_NAME
class NoDanmakuException(Exception): class NoDanmakuException(Exception):