后端: - free_video.py: 提交/轮询/收藏/软删全链路,复用 AITask(新增 is_deleted/is_favorited, migration 0022) - video_pricing.py: 按时长 token 计费(×1.10 buffer+clamp); video_errors.py 错误归一; media_probe.py 时长探测 - catalog/volcano: Seedance free-video 模型接入+seed(migration 0023) - 素材库: FreeAssetGroup/FreeAsset(火山 Assets API 引用登记, migration 0009)+ 上传/轮询/删除接口 - settings: FREE_VIDEO_MAX_CONCURRENT 团队并发闸(默认3); CELERY_TASK_ALWAYS_EAGER 本地联调开关(生产恒关) - 测试: test_free_video.py 新增; billing/products/projects tests 配套调整 前端: - /free-create 页面+components/free-create/ 全套(输入栏/@mention 素材引用/生成卡/视频详情弹窗/素材库弹窗) - api.ts/types.ts 扩展 free-video 与 free-assets 接口; 路由/侧边栏入口接入 bug/: 测试清单 (11)(12) 与截图归档 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
47 lines
1.6 KiB
Python
47 lines
1.6 KiB
Python
"""自由创作·上传素材探测(移植自 jimeng-clone utils/media_utils.py)。
|
|
|
|
ffprobe 取视频/音频时长、ffmpeg 抽视频首帧缩略图。生产镜像已带 ffmpeg
|
|
(_generate_video_poster 在用)。全部 best-effort:探测失败返回 None,由调用方决定拒绝或放行。
|
|
"""
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
|
|
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
|