"""自由创作·上传素材探测(移植自 jimeng-clone utils/media_utils.py)。 ffprobe 取视频/音频时长、ffmpeg 抽视频首帧缩略图。生产镜像已带 ffmpeg (_generate_video_poster 在用)。全部 best-effort:探测失败返回 None,由调用方决定拒绝或放行。 """ import subprocess import tempfile from pathlib import Path # 参考视频/音频对外仍写「2-15 秒」。15 秒成片常被探测成 15.01–15.04(帧率取整 / AAC 对齐),给半秒容差。 REF_DURATION_MIN = 1.95 REF_DURATION_MAX = 15.5 def duration_in_ref_range(seconds: float) -> bool: return REF_DURATION_MIN <= seconds <= REF_DURATION_MAX def probe_duration(file_path: str) -> float | None: """ffprobe 取媒体时长(秒)。失败返回 None。""" try: proc = subprocess.run( [ "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(file_path), ], capture_output=True, timeout=30, ) if proc.returncode != 0: return None return float(proc.stdout.decode().strip()) except Exception: # noqa: BLE001 return None def extract_video_poster(file_path: str) -> bytes | None: """ffmpeg 抽视频首帧 jpg 字节。失败返回 None。""" try: with tempfile.TemporaryDirectory(prefix="airshelf-freeprobe-") as tmp: poster_path = Path(tmp) / "poster.jpg" proc = subprocess.run( ["ffmpeg", "-y", "-ss", "0", "-i", str(file_path), "-frames:v", "1", "-q:v", "3", str(poster_path)], capture_output=True, timeout=60, ) if proc.returncode != 0 or not poster_path.exists(): return None data = poster_path.read_bytes() return data or None except Exception: # noqa: BLE001 return None