undo the media format convertion action

This commit is contained in:
2026-09-13 17:30:28 +08:00
parent 53e947c506
commit 9c78bb9b04

View File

@@ -1,11 +1,10 @@
import json
import logging
import shutil
import subprocess
import tempfile
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional
from .audio_metrics import (
AudioAnalysisError,
@@ -86,134 +85,6 @@ def _ensure_webm_cues_front(filepath: Path) -> None:
tmp.unlink(missing_ok=True)
_IOS_VIDEO_CODECS = {"h264", "hevc"}
_IOS_AUDIO_CODECS = {"aac", "mp3", "ac3", "eac3", "alac"}
def _ffprobe_bin() -> Optional[str]:
ffprobe = shutil.which("ffprobe")
if ffprobe:
return ffprobe
ffmpeg = shutil.which("ffmpeg")
if ffmpeg:
candidate = str(Path(ffmpeg).with_name("ffprobe"))
if Path(candidate).is_file():
return candidate
return None
def _probe_stream_codecs(filepath: Path) -> Tuple[Optional[str], Optional[str]]:
ffprobe = _ffprobe_bin()
if ffprobe is None:
return None, None
completed = subprocess.run(
[
ffprobe,
"-hide_banner",
"-loglevel",
"error",
"-show_streams",
"-of",
"json",
str(filepath),
],
capture_output=True,
text=True,
timeout=60,
)
if completed.returncode != 0:
return None, None
try:
payload = json.loads(completed.stdout or "{}")
except json.JSONDecodeError:
return None, None
video_codec = None
audio_codec = None
for stream in payload.get("streams") or []:
codec_type = stream.get("codec_type")
codec_name = stream.get("codec_name")
if codec_type == "video" and video_codec is None and codec_name not in {"mjpeg", "png"}:
video_codec = codec_name
elif codec_type == "audio" and audio_codec is None:
audio_codec = codec_name
return video_codec, audio_codec
def _needs_ios_compatible_transcode(video_codec: Optional[str], audio_codec: Optional[str]) -> bool:
if video_codec is None and audio_codec is None:
return False
video_ok = video_codec is None or video_codec in _IOS_VIDEO_CODECS
audio_ok = audio_codec is None or audio_codec in _IOS_AUDIO_CODECS
return not (video_ok and audio_ok)
def _ensure_ios_compatible_media(filepath: Path) -> None:
"""Transcode AV1/VP9/Opus etc. to H.264/AAC so iOS AVPlayer can show video."""
video_codec, audio_codec = _probe_stream_codecs(filepath)
if not _needs_ios_compatible_transcode(video_codec, audio_codec):
return
ffmpeg = shutil.which("ffmpeg")
if ffmpeg is None:
raise RuntimeError(
f"iOS cannot play {video_codec}/{audio_codec}; ffmpeg is required to transcode {filepath.name}"
)
tmp = filepath.with_suffix(".ios_tmp.mp4")
command = [
ffmpeg,
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
str(filepath),
"-map",
"0:v:0",
"-map",
"0:a:0?",
"-vf",
r"scale=w=min(1920\,iw):h=-2",
"-c:v",
"libx264",
"-preset",
"veryfast",
"-crf",
"23",
"-pix_fmt",
"yuv420p",
"-c:a",
"aac",
"-b:a",
"128k",
"-ac",
"2",
"-movflags",
"+faststart",
str(tmp),
]
logger.info(
"Transcoding %s for iOS playback (%s/%s -> h264/aac)",
filepath.name,
video_codec,
audio_codec,
)
try:
completed = subprocess.run(command, capture_output=True, text=True, timeout=7200)
if completed.returncode != 0 or not tmp.is_file() or tmp.stat().st_size <= 0:
message = (completed.stderr or "").strip() or "unknown ffmpeg error"
raise RuntimeError(
f"Could not transcode {filepath.name} from {video_codec}/{audio_codec} to H.264/AAC: {message[-2000:]}"
)
tmp.replace(filepath)
except Exception:
tmp.unlink(missing_ok=True)
raise
video_codec, audio_codec = _probe_stream_codecs(filepath)
if _needs_ios_compatible_transcode(video_codec, audio_codec):
raise RuntimeError(
f"Transcoded {filepath.name} is still not iOS-compatible ({video_codec}/{audio_codec})"
)
logger.info("iOS-compatible transcode finished: %s", filepath.name)
class VideoProcessor:
def __init__(
@@ -240,7 +111,6 @@ class VideoProcessor:
work_path: Optional[Path] = None
try:
_ensure_webm_cues_front(media_path)
_ensure_ios_compatible_media(media_path)
if self.transcriber.available:
work_path = self.settings.work_dir / f"{video_hash}-{uuid.uuid4().hex}.wav"
extract_audio(media_path, work_path)