diff --git a/config.py b/config.py index 41dadd6..696e3c6 100644 --- a/config.py +++ b/config.py @@ -20,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 @@ -94,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_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'] @@ -136,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/controller/api/collector_blueprint.py b/controller/api/collector_blueprint.py index 315c6e9..432d705 100644 --- a/controller/api/collector_blueprint.py +++ b/controller/api/collector_blueprint.py @@ -4,7 +4,8 @@ import platform import psutil from flask import Blueprint, jsonify -from config import DANMAKU_EXEC, FFMPEG_EXEC, BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, VIDEO_OUTPUT_DIR +from config import DANMAKU_EXEC, FFMPEG_EXEC, BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, \ + VIDEO_OUTPUT_DIR, HANDBRAKE_EXEC from util.system import check_exec from workflow.bilibili import IS_LIVING, IS_UPLOADING, IS_ENCODING @@ -60,6 +61,7 @@ def collect_basic_status(): }, 'exec': { 'ffmpeg': check_exec(FFMPEG_EXEC), + 'handbrake': check_exec(HANDBRAKE_EXEC), 'danmaku': check_exec(DANMAKU_EXEC), }, 'system': { diff --git a/templates/index.html b/templates/index.html index b5bc675..e76ea76 100644 --- a/templates/index.html +++ b/templates/index.html @@ -50,6 +50,10 @@ FFMPEG状态 + + HANDBRAKE状态 + + 弹幕工具状态 @@ -84,29 +88,18 @@ 命令 {{ config.ffmpeg.exec }} - - HEVC - + + + + + + + + - - - - - - - - - - - - - - - - - - + +
HANDBRAKE
VAAPI
嘤伟达GPU
嘤特尔GPU
视频比特率{{ config.ffmpeg.bitrate }}
GOP{{ config.ffmpeg.gop }}命令{{ config.ffmpeg.exec }}
@@ -255,6 +248,7 @@ basic: { exec: { ffmpeg: false, + handbrake: false, danmaku: false, }, system: { @@ -319,12 +313,12 @@ }, ffmpeg: { exec: "", - hevc: false, - nvidia_gpu: false, - intel_gpu: false, - vaapi: false, - bitrate: "", - gop: "", + }, + handbrake: { + exec: "", + preset_file: "", + preset: "", + encopt: "", }, recorder: { bili_dir: "", diff --git a/workflow/video.py b/workflow/video.py index cbb88f1..6661ce2 100644 --- a/workflow/video.py +++ b/workflow/video.py @@ -1,11 +1,12 @@ +import json import os import subprocess +import warnings from datetime import datetime, timedelta from typing import IO -from config import FFMPEG_EXEC, FFMPEG_USE_HEVC, VIDEO_BITRATE, FFMPEG_USE_NVIDIA_GPU, VIDEO_CLIP_EACH_SEC, \ - VIDEO_CLIP_OVERFLOW_SEC, \ - FFMPEG_USE_INTEL_GPU, VIDEO_OUTPUT_DIR, VIDEO_GOP, FFMPEG_USE_VAAPI +from config import VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC, VIDEO_OUTPUT_DIR, \ + FFMPEG_EXEC, HANDBRAKE_EXEC, HANDBRAKE_PRESET_FILE, HANDBRAKE_PRESET, HANDBRAKE_ENCOPT from . import LOGGER @@ -25,143 +26,78 @@ def get_video_real_duration(filename): def encode_video_with_subtitles(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_VAAPI: - process = get_encode_hevc_process_use_vaapi(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_VAAPI: - process = get_encode_process_use_vaapi(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) - handle_ffmpeg_output(process.stdout) - process.wait() - return [new_fullpath] + create_dt = datetime.fromtimestamp(base_ts) + _duration_str = get_video_real_duration(orig_filename) + duration = duration_str_to_float(_duration_str) + current_sec = 0 + _video_parts = [] + while current_sec < duration: + if (current_sec + VIDEO_CLIP_OVERFLOW_SEC * 2) > duration: + print("[-]Less than 2 overflow sec, skip") + break + current_dt = (create_dt + timedelta(seconds=current_sec)).strftime("%Y%m%d_%H%M_") + process = get_encode_process_use_handbrake(orig_filename, + subtitles, + os.path.join(VIDEO_OUTPUT_DIR, "{}.mp4".format(current_dt)), + current_sec, + VIDEO_CLIP_EACH_SEC + VIDEO_CLIP_OVERFLOW_SEC) + handle_handbrake_output(process.stdout) + process.wait() + _video_parts.append({ + "base_path": VIDEO_OUTPUT_DIR, + "file": "{}.mp4".format(current_dt), + }) + current_sec += VIDEO_CLIP_EACH_SEC -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, start_time: int, stop_time: int): + 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", + HANDBRAKE_EXEC, *_common_handbrake_setting(), + "--preset-import-file", HANDBRAKE_PRESET_FILE, "--preset", HANDBRAKE_PRESET, + "--start-at", "seconds:{}".format(start_time), "--stop-at", "seconds:{}".format(stop_time), + "-i", orig_filename, "-x", HANDBRAKE_ENCOPT, + "--ssa-file", ",".join(i for i in subtitles), + "--ssa-offset", "{}000".format(start_time), + "--ssa-burn", ",".join("%d" % (i+1) for i in range(len(subtitles))), + "-o", new_filename ], stdout=subprocess.PIPE) 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 - ], stdout=subprocess.PIPE) - return encode_process - -def get_encode_process_use_vaapi(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use VAAPI Acceleration") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi", "-i", orig_filename, "-vf", - "hwmap=mode=read+write+direct,format=nv12," + - "".join("subtitles=%s," % i for i in subtitles) + "hwmap", - "-c:v", "h264_vaapi", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], stdout=subprocess.PIPE) - 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 - ], stdout=subprocess.PIPE) - 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 - ], stdout=subprocess.PIPE) - return encode_process - - -def get_encode_hevc_process_use_vaapi(orig_filename: str, subtitles: list[str], new_filename: str): - print("[+]Use VAAPI Acceleration") - encode_process = subprocess.Popen([ - FFMPEG_EXEC, *_common_ffmpeg_setting(), - "-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi", "-i", orig_filename, "-vf", - "hwmap=mode=read+write+direct,format=nv12," + - "".join("subtitles=%s," % i for i in subtitles) + "hwmap", - "-c:v", "hevc_vaapi", - *_common_ffmpeg_params(), - # "-t", "10", - new_filename - ], stdout=subprocess.PIPE) - 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 - ], stdout=subprocess.PIPE) - 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 - ], stdout=subprocess.PIPE) - return encode_process +def handle_handbrake_output(stdout: IO[bytes]): + out_time = "0:0:0.0" + speed = "0" + if stdout is None: + print("[!]STDOUT is null") + return + json_body = "" + json_start = False + _i = 0 + 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": + out_time = "ETA: {Hours:02d}:{Minutes:02d}:{Seconds:02d}".format_map(status_payload["Working"]) + speed = "{Rate:.2f}FPS".format_map(status_payload["Working"]) + _i += 1 + if _i % 300 == 150: + LOGGER.debug("[>]Speed:{}@{}".format(out_time, speed)) + elif status_payload["State"] == "WORKDONE": + break + continue + if line.startswith(b"Progress:"): + json_start = True + json_body = "{" + LOGGER.debug("[ ]Speed:{}@{}".format(out_time, speed)) def handle_ffmpeg_output(stdout: IO[bytes]) -> str: @@ -195,6 +131,7 @@ def duration_str_to_float(duration_str) -> float: def quick_split_video(file): + warnings.warn("已过时", DeprecationWarning) if not os.path.isfile(file): raise FileNotFoundError(file) file_name = os.path.split(file)[-1] @@ -235,10 +172,9 @@ def _common_ffmpeg_setting(): ) -def _common_ffmpeg_params(): +def _common_handbrake_setting(): 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" + "--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", "--align-av" ) diff --git a/workflow/worker.py b/workflow/worker.py index f66e75c..b125085 100644 --- a/workflow/worker.py +++ b/workflow/worker.py @@ -26,15 +26,9 @@ def do_workflow(video_file, danmaku_base_file, *danmaku_files): print("弹幕文件", danmaku_file, "异常") continue print(result) - file_need_split = encode_video_with_subtitles(video_file, result, start_ts) - _video_parts = [] - for file in file_need_split: - _video_parts += quick_split_video(file) + _video_parts = encode_video_with_subtitles(video_file, result, start_ts) # clean files for file in result: if os.path.isfile(file): os.remove(file) - for file in file_need_split: - if os.path.isfile(file): - os.remove(file) return _video_parts