Compare commits

..

6 Commits

5 changed files with 192 additions and 127 deletions

View File

@ -22,15 +22,18 @@ DANMAKU_OPACITY = 100
# [ffmpeg] # [ffmpeg]
# exec # exec
FFMPEG_EXEC = "ffmpeg" FFMPEG_EXEC = "ffmpeg"
# [handbrake] # hevc
# exec FFMPEG_USE_HEVC = False
HANDBRAKE_EXEC = "HandBrakeCli" # nvidia_gpu
# preset_file FFMPEG_USE_NVIDIA_GPU = False
HANDBRAKE_PRESET_FILE = "handbrake.json" # intel_gpu
# preset FFMPEG_USE_INTEL_GPU = False
HANDBRAKE_PRESET = "NvEnc" # vaapi
# encopt FFMPEG_USE_VAAPI = False
HANDBRAKE_ENCOPT = "" # crf
VIDEO_CRF = 28
# gop
VIDEO_GOP = 60
# [video] # [video]
# enabled # enabled
VIDEO_ENABLED = False VIDEO_ENABLED = False
@ -94,15 +97,15 @@ 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 global FFMPEG_EXEC, FFMPEG_USE_HEVC, FFMPEG_USE_NVIDIA_GPU, FFMPEG_USE_INTEL_GPU, VIDEO_CRF, \
VIDEO_GOP, FFMPEG_USE_VAAPI
FFMPEG_EXEC = section.get('exec', FFMPEG_EXEC) FFMPEG_EXEC = section.get('exec', FFMPEG_EXEC)
if config.has_section("handbrake"): FFMPEG_USE_HEVC = section.getboolean('hevc', FFMPEG_USE_HEVC)
section = config['handbrake'] FFMPEG_USE_NVIDIA_GPU = section.getboolean('nvidia_gpu', FFMPEG_USE_NVIDIA_GPU)
global HANDBRAKE_EXEC, HANDBRAKE_PRESET_FILE, HANDBRAKE_PRESET, HANDBRAKE_ENCOPT FFMPEG_USE_INTEL_GPU = section.getboolean('intel_gpu', FFMPEG_USE_INTEL_GPU)
HANDBRAKE_EXEC = section.get('exec', HANDBRAKE_EXEC) FFMPEG_USE_VAAPI = section.getboolean('vaapi', FFMPEG_USE_VAAPI)
HANDBRAKE_PRESET_FILE = section.get('preset_file', HANDBRAKE_PRESET_FILE) VIDEO_CRF = section.getfloat('crf', VIDEO_CRF)
HANDBRAKE_PRESET = section.get('preset', HANDBRAKE_PRESET) VIDEO_GOP = section.getfloat('gop', VIDEO_GOP)
HANDBRAKE_ENCOPT = section.get('encopt', HANDBRAKE_ENCOPT)
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']
@ -137,12 +140,12 @@ def get_config():
}, },
'ffmpeg': { 'ffmpeg': {
'exec': FFMPEG_EXEC, 'exec': FFMPEG_EXEC,
}, 'hevc': FFMPEG_USE_HEVC,
'handbrake': { 'nvidia_gpu': FFMPEG_USE_NVIDIA_GPU,
'exec': HANDBRAKE_EXEC, 'intel_gpu': FFMPEG_USE_INTEL_GPU,
'preset_file': HANDBRAKE_PRESET_FILE, 'vaapi': FFMPEG_USE_VAAPI,
'preset': HANDBRAKE_PRESET, 'crf': VIDEO_CRF,
'encopt': HANDBRAKE_ENCOPT, 'gop': VIDEO_GOP,
}, },
'recorder': { 'recorder': {
'bili_dir': BILILIVE_RECORDER_DIRECTORY, 'bili_dir': BILILIVE_RECORDER_DIRECTORY,

View File

@ -4,8 +4,7 @@ import platform
import psutil import psutil
from flask import Blueprint, jsonify from flask import Blueprint, jsonify
from config import DANMAKU_EXEC, FFMPEG_EXEC, BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, \ from config import DANMAKU_EXEC, FFMPEG_EXEC, BILILIVE_RECORDER_DIRECTORY, XIGUALIVE_RECORDER_DIRECTORY, VIDEO_OUTPUT_DIR
VIDEO_OUTPUT_DIR, HANDBRAKE_EXEC
from util.system import check_exec from util.system import check_exec
from workflow.bilibili import IS_LIVING, IS_UPLOADING, IS_ENCODING from workflow.bilibili import IS_LIVING, IS_UPLOADING, IS_ENCODING
@ -61,7 +60,6 @@ def collect_basic_status():
}, },
'exec': { 'exec': {
'ffmpeg': check_exec(FFMPEG_EXEC), 'ffmpeg': check_exec(FFMPEG_EXEC),
'handbrake': check_exec(HANDBRAKE_EXEC),
'danmaku': check_exec(DANMAKU_EXEC), 'danmaku': check_exec(DANMAKU_EXEC),
}, },
'system': { 'system': {

View File

@ -50,10 +50,6 @@
<td>FFMPEG状态</td> <td>FFMPEG状态</td>
<td :class="collector.basic.exec.ffmpeg ? 'success' : 'warning'"></td> <td :class="collector.basic.exec.ffmpeg ? 'success' : 'warning'"></td>
</tr> </tr>
<tr>
<td>HANDBRAKE状态</td>
<td :class="collector.basic.exec.handbrake ? 'success' : 'warning'"></td>
</tr>
<tr> <tr>
<td>弹幕工具状态</td> <td>弹幕工具状态</td>
<td :class="collector.basic.exec.danmaku ? 'success' : 'warning'"></td> <td :class="collector.basic.exec.danmaku ? 'success' : 'warning'"></td>
@ -88,30 +84,29 @@
<td>命令</td> <td>命令</td>
<td>{{ config.ffmpeg.exec }}</td> <td>{{ config.ffmpeg.exec }}</td>
</tr> </tr>
</tbody>
</table>
<table class="current-config">
<thead>
<tr class="table-header">
<td colspan="2">HANDBRAKE</td>
</tr>
</thead>
<tbody>
<tr> <tr>
<td>命令</td> <td>HEVC</td>
<td>{{ config.handbrake.exec }}</td> <td :class="{warning: !config.ffmpeg.hevc, success: config.ffmpeg.hevc}"></td>
</tr> </tr>
<tr> <tr>
<td>预设文件</td> <td>VAAPI</td>
<td>{{ config.handbrake.preset_file }}</td> <td :class="{warning: !config.ffmpeg.vaapi, success: config.ffmpeg.vaapi}"></td>
</tr> </tr>
<tr> <tr>
<td>预设使用</td> <td>嘤伟达GPU</td>
<td>{{ config.handbrake.preset }}</td> <td :class="{warning: !config.ffmpeg.nvidia_gpu, success: config.ffmpeg.nvidia_gpu}"></td>
</tr> </tr>
<tr> <tr>
<td>编码器参数</td> <td>嘤特尔GPU</td>
<td>{{ config.handbrake.encopt }}</td> <td :class="{warning: !config.ffmpeg.intel_gpu, success: config.ffmpeg.intel_gpu}"></td>
</tr>
<tr>
<td>视频CRF</td>
<td>{{ config.ffmpeg.crf }}</td>
</tr>
<tr>
<td>GOP</td>
<td>{{ config.ffmpeg.gop }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -264,7 +259,6 @@
basic: { basic: {
exec: { exec: {
ffmpeg: false, ffmpeg: false,
handbrake: false,
danmaku: false, danmaku: false,
}, },
system: { system: {
@ -330,12 +324,12 @@
}, },
ffmpeg: { ffmpeg: {
exec: "", exec: "",
}, hevc: false,
handbrake: { nvidia_gpu: false,
exec: "", intel_gpu: false,
preset_file: "", vaapi: false,
preset: "", crf: "",
encopt: "", gop: "",
}, },
recorder: { recorder: {
bili_dir: "", bili_dir: "",

View File

@ -1,12 +1,11 @@
import json
import os import os
import subprocess import subprocess
import warnings
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import IO from typing import IO
from config import VIDEO_CLIP_EACH_SEC, VIDEO_CLIP_OVERFLOW_SEC, VIDEO_OUTPUT_DIR, \ from config import FFMPEG_EXEC, FFMPEG_USE_HEVC, VIDEO_CRF, FFMPEG_USE_NVIDIA_GPU, VIDEO_CLIP_EACH_SEC, \
FFMPEG_EXEC, HANDBRAKE_EXEC, HANDBRAKE_PRESET_FILE, HANDBRAKE_PRESET, HANDBRAKE_ENCOPT VIDEO_CLIP_OVERFLOW_SEC, \
FFMPEG_USE_INTEL_GPU, VIDEO_OUTPUT_DIR, VIDEO_GOP, FFMPEG_USE_VAAPI
from . import LOGGER from . import LOGGER
@ -26,78 +25,143 @@ def get_video_real_duration(filename):
def encode_video_with_subtitles(orig_filename: str, subtitles: list[str], base_ts: float): def encode_video_with_subtitles(orig_filename: str, subtitles: list[str], base_ts: float):
create_dt = datetime.fromtimestamp(base_ts) new_filename = base_ts_to_filename(base_ts, False)
_duration_str = get_video_real_duration(orig_filename) new_fullpath = os.path.join(VIDEO_OUTPUT_DIR, new_filename)
duration = duration_str_to_float(_duration_str) if FFMPEG_USE_HEVC:
current_sec = 0 if FFMPEG_USE_NVIDIA_GPU:
_video_parts = [] process = get_encode_hevc_process_use_nvenc(orig_filename, subtitles, new_fullpath)
while current_sec < duration: elif FFMPEG_USE_VAAPI:
if (current_sec + VIDEO_CLIP_OVERFLOW_SEC * 2) > duration: process = get_encode_hevc_process_use_vaapi(orig_filename, subtitles, new_fullpath)
print("[-]Less than 2 overflow sec, skip") elif FFMPEG_USE_INTEL_GPU:
break process = get_encode_hevc_process_use_intel(orig_filename, subtitles, new_fullpath)
current_dt = (create_dt + timedelta(seconds=current_sec)).strftime("%Y%m%d_%H%M_") else:
process = get_encode_process_use_handbrake(orig_filename, process = get_encode_hevc_process_use_cpu(orig_filename, subtitles, new_fullpath)
subtitles, else:
os.path.join(VIDEO_OUTPUT_DIR, "{}.mp4".format(current_dt)), if FFMPEG_USE_NVIDIA_GPU:
current_sec, process = get_encode_process_use_nvenc(orig_filename, subtitles, new_fullpath)
VIDEO_CLIP_EACH_SEC + VIDEO_CLIP_OVERFLOW_SEC) elif FFMPEG_USE_VAAPI:
handle_handbrake_output(process.stdout) process = get_encode_process_use_vaapi(orig_filename, subtitles, new_fullpath)
process.wait() elif FFMPEG_USE_INTEL_GPU:
_video_parts.append({ process = get_encode_process_use_intel(orig_filename, subtitles, new_fullpath)
"base_path": VIDEO_OUTPUT_DIR, else:
"file": "{}.mp4".format(current_dt), process = get_encode_process_use_cpu(orig_filename, subtitles, new_fullpath)
}) handle_ffmpeg_output(process.stdout)
current_sec += VIDEO_CLIP_EACH_SEC process.wait()
return [new_fullpath]
def get_encode_process_use_handbrake(orig_filename: str, subtitles: list[str], new_filename: str, start_time: int, stop_time: int): def get_encode_process_use_nvenc(orig_filename: str, subtitles: list[str], new_filename: str):
print("[+]Use HandBrakeCli") print("[+]Use Nvidia NvEnc Acceleration")
encode_process = subprocess.Popen([ encode_process = subprocess.Popen([
HANDBRAKE_EXEC, *_common_handbrake_setting(), FFMPEG_EXEC, *_common_ffmpeg_setting(),
"--preset-import-file", HANDBRAKE_PRESET_FILE, "--preset", HANDBRAKE_PRESET, "-i", orig_filename, "-vf",
"--start-at", "seconds:{}".format(start_time), "--stop-at", "seconds:{}".format(stop_time), ",".join("subtitles=%s" % i for i in subtitles) + ",hwupload_cuda",
"-i", orig_filename, "-x", HANDBRAKE_ENCOPT, "-c:v", "h264_nvenc", "-preset:v", "p7",
"--ssa-file", ",".join(i for i in subtitles), *_common_ffmpeg_params(),
"--ssa-offset", "{}000".format(start_time), # "-t", "10",
"--ssa-burn", ",".join("%d" % (i+1) for i in range(len(subtitles))),
"-o",
new_filename new_filename
], stdout=subprocess.PIPE) ], stdout=subprocess.PIPE)
return encode_process 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 handle_handbrake_output(stdout: IO[bytes]):
out_time = "0:0:0.0" def get_encode_process_use_vaapi(orig_filename: str, subtitles: list[str], new_filename: str):
speed = "0" print("[+]Use VAAPI Acceleration")
if stdout is None: encode_process = subprocess.Popen([
print("[!]STDOUT is null") FFMPEG_EXEC, *_common_ffmpeg_setting(),
return "-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi", "-i", orig_filename, "-vf",
json_body = "" "hwmap=mode=read+write+direct,format=nv12," +
json_start = False "".join("subtitles=%s," % i for i in subtitles) + "hwmap",
_i = 0 "-c:v", "h264_vaapi",
while True: *_common_ffmpeg_params(),
line = stdout.readline() # "-t", "10",
if line == b"": new_filename
break ], stdout=subprocess.PIPE)
if json_start: return encode_process
json_body += line.strip().decode("UTF-8")
if line.startswith(b"}"):
json_start = False def get_encode_process_use_cpu(orig_filename: str, subtitles: list[str], new_filename: str):
status_payload = json.loads(json_body) print("[+]Use CPU Encode")
if status_payload["State"] == "WORKING": encode_process = subprocess.Popen([
out_time = "ETA: {Hours:02d}:{Minutes:02d}:{Seconds:02d}".format_map(status_payload["Working"]) FFMPEG_EXEC, *_common_ffmpeg_setting(),
speed = "{Rate:.2f}FPS".format_map(status_payload["Working"]) "-i", orig_filename, "-vf",
_i += 1 ",".join("subtitles=%s" % i for i in subtitles),
if _i % 300 == 150: "-c:v", "h264",
LOGGER.debug("[>]Speed:{}@{}".format(out_time, speed)) *_common_ffmpeg_params(),
elif status_payload["State"] == "WORKDONE": # "-t", "10",
break new_filename
continue ], stdout=subprocess.PIPE)
if line.startswith(b"Progress:"): return encode_process
json_start = True
json_body = "{"
LOGGER.debug("[ ]Speed:{}@{}".format(out_time, speed)) 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", "-preset:v", "p7",
*_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_ffmpeg_output(stdout: IO[bytes]) -> str: def handle_ffmpeg_output(stdout: IO[bytes]) -> str:
@ -131,7 +195,6 @@ def duration_str_to_float(duration_str) -> float:
def quick_split_video(file): def quick_split_video(file):
warnings.warn("已过时", DeprecationWarning)
if not os.path.isfile(file): if not os.path.isfile(file):
raise FileNotFoundError(file) raise FileNotFoundError(file)
file_name = os.path.split(file)[-1] file_name = os.path.split(file)[-1]
@ -172,9 +235,10 @@ def _common_ffmpeg_setting():
) )
def _common_handbrake_setting(): def _common_ffmpeg_params():
return ( return (
"--json", "-vsync", "1", "-async", "1", "-avoid_negative_ts", "1",
"--crop-mode", "none", "--no-comb-detect", "--no-bwdif", "--no-decomb", "--no-detelecine", "--no-hqdn3d", "-f", "mp4", "-c:a", "aac",
"--no-nlmeans", "--no-chroma-smooth", "--no-unsharp", "--no-lapsharp", "--no-deblock", "--align-av" "-crf", str(VIDEO_CRF), "-g:v", str(VIDEO_GOP),
"-fflags", "+genpts", "-shortest"
) )

View File

@ -26,9 +26,15 @@ def do_workflow(video_file, danmaku_base_file, *danmaku_files):
print("弹幕文件", danmaku_file, "异常") print("弹幕文件", danmaku_file, "异常")
continue continue
print(result) print(result)
_video_parts = encode_video_with_subtitles(video_file, result, start_ts) 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)
# clean files # clean files
for file in result: for file in result:
if os.path.isfile(file): if os.path.isfile(file):
os.remove(file) os.remove(file)
for file in file_need_split:
if os.path.isfile(file):
os.remove(file)
return _video_parts return _video_parts