Files
kino-app/backend/transcode.py
T
2026-04-29 16:01:20 +00:00

75 lines
2.3 KiB
Python

"""HLS transcoding via ffmpeg. Background-runs and updates DB."""
import asyncio
import logging
import shutil
from pathlib import Path
from typing import Optional
logger = logging.getLogger("kino.transcode")
async def transcode_to_hls(
source: Path,
out_dir: Path,
on_status,
):
"""
Run ffmpeg to produce HLS playlist + segments.
`on_status(status, error=None)` is awaited at start/end with status one of
'running'|'done'|'failed'.
Uses stream-copy where possible (fast, no re-encode).
"""
if not source.is_file():
await on_status("failed", error=f"Source missing: {source}")
return
out_dir.mkdir(parents=True, exist_ok=True)
playlist = out_dir / "playlist.m3u8"
cmd = [
"ffmpeg", "-y",
"-i", str(source),
"-c:v", "copy",
"-c:a", "copy",
"-bsf:v", "h264_mp4toannexb",
"-f", "hls",
"-hls_time", "6",
"-hls_list_size", "0",
"-hls_playlist_type", "vod",
"-hls_segment_filename", str(out_dir / "seg_%04d.ts"),
str(playlist),
]
await on_status("running")
try:
proc = await asyncio.create_subprocess_exec(
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
)
_stdout, stderr = await proc.communicate()
if proc.returncode != 0:
err = stderr.decode("utf-8", errors="ignore")[-500:]
logger.error(f"ffmpeg failed: {err}")
await on_status("failed", error=err[:200])
# cleanup partial files
shutil.rmtree(out_dir, ignore_errors=True)
return
await on_status("done")
except FileNotFoundError:
await on_status("failed", error="ffmpeg not installed")
except Exception as e:
logger.exception("transcode crashed")
await on_status("failed", error=str(e))
shutil.rmtree(out_dir, ignore_errors=True)
def srt_to_vtt(srt_text: str) -> str:
"""Convert SRT subtitles to WebVTT format (simple, no styling)."""
lines = srt_text.replace("\r\n", "\n").split("\n")
out = ["WEBVTT", ""]
for line in lines:
# Replace SRT timestamp commas with VTT dots
if "-->" in line:
out.append(line.replace(",", "."))
else:
out.append(line)
return "\n".join(out)