feat(hardcode-subs): add subtitle burn-in helper
This commit is contained in:
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env -S python3.12
|
||||
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def hardcode_subs(
|
||||
video_path: Path,
|
||||
subtitle_path: Path,
|
||||
lang: str,
|
||||
save_path: Path | None = None,
|
||||
resolution: str | None = None,
|
||||
quality: float | None = None,
|
||||
font_size: int = 40,
|
||||
) -> Path:
|
||||
if not save_path:
|
||||
save_path = video_path.with_name(f"{video_path.stem}.{lang}.hardcoded.mp4")
|
||||
vf_filters = [f"subtitles='{subtitle_path}:force_style=Fontsize={font_size}'"]
|
||||
if resolution:
|
||||
resolution_map = {"1080p": 1920, "720p": 1280, "480p": 854}
|
||||
max_width = resolution_map.get(resolution)
|
||||
if max_width:
|
||||
vf_filters.append(f"scale='min({max_width},iw)':-2")
|
||||
vf = ",".join(vf_filters)
|
||||
crf = str(quality) if quality else "35"
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-hwaccel",
|
||||
"videotoolbox",
|
||||
"-i",
|
||||
str(video_path),
|
||||
"-vf",
|
||||
vf,
|
||||
"-c:v",
|
||||
"h264_videotoolbox",
|
||||
"-crf",
|
||||
crf,
|
||||
"-c:a",
|
||||
"copy",
|
||||
str(save_path),
|
||||
]
|
||||
logger.info(f"Hardcoding subtitles into video: {save_path.name}")
|
||||
subprocess.run(cmd, check=True)
|
||||
return save_path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Hardcode subtitles into a video.", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("video", type=Path, help="Path to the video file")
|
||||
parser.add_argument("subtitle", type=Path, help="Path to the subtitle file")
|
||||
parser.add_argument("--out", "-o", type=Path, help="Path to the output video file")
|
||||
parser.add_argument("--lang", default="en", help="Language code for subtitles")
|
||||
parser.add_argument(
|
||||
"--resolution",
|
||||
choices=["1080p", "720p", "480p"],
|
||||
help="Set output video resolution (max width)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quality",
|
||||
"-q",
|
||||
type=float,
|
||||
default=35,
|
||||
help="Set CRF quality factor (lower is better, default 35)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--font-size",
|
||||
type=int,
|
||||
default=40,
|
||||
help="Font size for hardcoded subtitles",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
||||
args = parse_args()
|
||||
if not args.video.exists():
|
||||
raise FileNotFoundError(f"Video file not found: {args.video}")
|
||||
if not args.subtitle.exists():
|
||||
raise FileNotFoundError(f"Subtitle file not found: {args.subtitle}")
|
||||
|
||||
save_path = args.out or args.video.with_name(f"{args.video.stem}.{args.lang}.hardcoded.mp4")
|
||||
hardcoded_video = hardcode_subs(
|
||||
video_path=args.video,
|
||||
subtitle_path=args.subtitle,
|
||||
lang=args.lang,
|
||||
save_path=save_path,
|
||||
resolution=args.resolution,
|
||||
quality=args.quality,
|
||||
font_size=args.font_size,
|
||||
)
|
||||
logging.info(f"Hardcoded video saved at: {hardcoded_video}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user