fixed bugs

This commit is contained in:
2026-09-13 12:58:45 +08:00
parent f810cfedd4
commit 5328898971
7 changed files with 233 additions and 61 deletions

View File

@@ -481,9 +481,13 @@ def create_app(
media_path = service_settings.videos_dir / row["stored_filename"]
if not media_path.is_file():
raise HTTPException(status_code=404, detail="Stored video file is missing.")
media_type = row.get("content_type") or "video/mp4"
if not str(media_type).lower().startswith("video/"):
guessed = mimetypes.guess_type(str(row.get("stored_filename") or ""))[0]
media_type = guessed if guessed and guessed.lower().startswith("video/") else "video/mp4"
return FileResponse(
media_path,
media_type=row["content_type"],
media_type=media_type,
headers={"Accept-Ranges": "bytes", "Cache-Control": "public, max-age=3600"},
)

View File

@@ -1,10 +1,11 @@
import json
import logging
import shutil
import subprocess
import tempfile
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple
from .audio_metrics import (
AudioAnalysisError,
@@ -85,6 +86,126 @@ 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 _probe_stream_codecs(filepath: Path) -> Tuple[Optional[str], Optional[str]]:
ffprobe = shutil.which("ffprobe")
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:
logger.warning(
"ffmpeg not found; skipping iOS-compatible transcode for %s (%s/%s)",
filepath.name,
video_codec,
audio_codec,
)
return
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",
"scale='min(1920,iw)':-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 and tmp.is_file() and tmp.stat().st_size > 0:
tmp.replace(filepath)
logger.info("iOS-compatible transcode finished: %s", filepath.name)
return
logger.warning(
"iOS-compatible transcode failed for %s: rc=%d stderr=%s",
filepath.name,
completed.returncode,
(completed.stderr or "")[-400:],
)
except (subprocess.TimeoutExpired, OSError) as exc:
logger.warning("iOS-compatible transcode error for %s: %s", filepath.name, exc)
finally:
tmp.unlink(missing_ok=True)
class VideoProcessor:
def __init__(
self,
@@ -110,6 +231,7 @@ 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)