diff --git a/sentence_api/processing.py b/sentence_api/processing.py index efaa675..eb1e00e 100644 --- a/sentence_api/processing.py +++ b/sentence_api/processing.py @@ -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)