Files
transcode/tc.py
2026-03-18 23:10:35 +01:00

123 lines
5.3 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
import argparse
import sys
import time
import subprocess
from pathlib import Path
from core import BaseTranscoder
# 将当前目录加入 path 以引入 core
sys.path.append(str(Path(__file__).resolve().parent))
VIDEO_DEFAULTS = {
"hevc_nvenc": ["-preset", "p7", "-tune", "uhq", "-rc", "vbr_hq", "-cq", "24", "-spatial-aq", "1", "-multipass", "2", "-pix_fmt", "p010le"],
"av1_nvenc": ["-preset", "p7", "-tune", "uhq", "-rc", "vbr", "-cq", "32", "-multipass", "2", "-pix_fmt", "p010le"],
"libx265": ["-preset", "slow", "-crf", "24", "-pix_fmt", "yuv420p10le", "-x265-params", "aq-mode=3:aq-strength=0.8:psy-rd=1.0"],
"libsvtav1": ["-preset", "4", "-crf", "35", "-pix_fmt", "yuv420p10le", "-svtav1-params", "tune=0:enable-qm=1"],
"libvvenc": ["-preset", "medium", "-qpa", "0", "-qp", "24", "-pix_fmt", "yuv420p10le"],
}
AUDIO_DEFAULTS = {
"fdkaac": ["-m", "4"],
"opus": ["-c:a", "libopus", "-b:a", "128k", "-vbr", "on"],
}
class MediaTranscoder(BaseTranscoder):
def __init__(self, args):
super().__init__(args, VIDEO_DEFAULTS, AUDIO_DEFAULTS)
def execute(self):
print(f"--- 初始化转码任务 ---")
self.orig_info = self.get_media_info(self.input_path)
if not self.orig_info:
raise ValueError("无法读取输入文件元数据。")
start_time = time.time()
try:
if self.aencoder == "fdkaac":
self._encode_with_external_audio()
else:
self._encode_standard()
except subprocess.CalledProcessError as e:
self._cleanup()
self._send_notification(error=f"转码失败,退出码: {e.returncode}", extra_title="转码")
sys.exit(f"\n转码失败,退出码: {e.returncode}")
self._cleanup()
encode_time = time.time() - start_time
new_info = self.get_media_info(self.output_path)
vmaf_score, psnr_score = "N/A", "N/A"
if self.run_metrics and new_info:
vmaf_score, psnr_score = self._run_vmaf_psnr(new_info)
log_content = self._generate_log_content(
encode_time, new_info, vmaf_score, psnr_score
)
with open(self.log_path, "a", encoding="utf-8") as f:
f.write(log_content)
self._send_notification(encode_time=encode_time, new_info=new_info,
vmaf=vmaf_score, full_text=log_content, extra_title="转码")
print(f"\n转码完成!耗时: {encode_time:.2f}")
print(f"输出文件: {self.output_path}")
def _encode_with_external_audio(self):
print("\n[Audio] 提取音频流并使用外部 fdkaac 编码...")
temp_wav = self.out_dir / f"temp_{self.input_path.stem}.wav"
temp_m4a = self.out_dir / f"temp_{self.input_path.stem}.m4a"
self.temp_files.extend([temp_wav, temp_m4a])
subprocess.run(["ffmpeg", "-y", "-i", str(self.input_path), "-vn", "-c:a", "pcm_s16le", str(temp_wav)], check=True)
subprocess.run(["fdkaac"] + self.a_params + [str(temp_wav), "-o", str(temp_m4a)], check=True)
print("\n[Video] 编码视频并混流...")
cmd = [
"ffmpeg", "-y", "-i", str(self.input_path), "-i", str(temp_m4a),
"-map", "0:v:0", "-map", "1:a:0", "-c:v", self.vencoder,
]
if self.vf_string:
cmd.extend(["-vf", self.vf_string])
cmd.extend(self.v_params)
cmd.extend(["-c:a", "copy", str(self.output_path)])
subprocess.run(cmd, check=True)
def _encode_standard(self):
print("\n[Transcode] 使用 ffmpeg 同时处理视音频...")
cmd = ["ffmpeg", "-y", "-i", str(self.input_path), "-c:v", self.vencoder]
if self.vf_string:
cmd.extend(["-vf", self.vf_string])
cmd.extend(self.v_params)
cmd.extend(self.a_params)
cmd.extend([str(self.output_path)])
subprocess.run(cmd, check=True)
def main():
parser = argparse.ArgumentParser(description="可配置视频转码脚本")
parser.add_argument("-i", "--input", required=True, help="输入视频文件")
parser.add_argument("-d", "--outdir", help="指定输出目录 (默认与输入文件同目录)")
parser.add_argument("-cv", "--vencoder", choices=VIDEO_DEFAULTS.keys(), default="libsvtav1", help="视频编码器 (默认 libsvtav1)")
parser.add_argument("-ca", "--aencoder", choices=AUDIO_DEFAULTS.keys(), default="opus", help="音频编码器 (默认 opus)")
parser.add_argument("--vargs", type=str, default="", help="覆盖默认视频编码参数")
parser.add_argument("--aargs", type=str, default="", help="覆盖默认音频编码参数")
parser.add_argument("--fps", type=str, default="", help="指定帧率 (例如 60, 30000/1001)")
parser.add_argument("--res", type=str, default="", help="指定分辨率格式与scale滤镜相同 (例如 1920:1080)")
parser.add_argument("--metrics", action=argparse.BooleanOptionalAction, default=True, help="结束后运行 VMAF/PSNR")
parser.add_argument("--notify", choices=["none", "mail", "notify"], default="mail", help="完成后的通知方式 (默认 mail)")
try:
transcoder = MediaTranscoder(parser.parse_args())
transcoder.execute()
except Exception as e:
sys.exit(f"发生错误: {e}")
if __name__ == "__main__":
main()