优化视频复刻

This commit is contained in:
Azmat@qq.com
2026-08-27 17:58:48 +08:00
parent d2b786a3cf
commit b3bf159e08
10 changed files with 545 additions and 64 deletions
+82
View File
@@ -1344,3 +1344,85 @@ def _fail_digest_task(task, reservation, message: str) -> None:
release_credit(reservation=reservation, reason=message[:200])
except Exception: # noqa: BLE001
pass
# --------------------------------------------------------------------------- #
# 复用入口:视频复刻·商品 拿分镜稿
# --------------------------------------------------------------------------- #
def digest_asset_video(*, asset, task, model_config_id=None) -> tuple[str, dict]:
"""把一条已入库的参考视频拆成中文分镜稿,供「视频复刻·商品」当提示词骨架。
与「提炼提示词」页共用同一份 SKILL.md、同一个 Gemini 3.1 Pro 和同一套质检,
保证两处产出一致;区别只在于这里不另建 VIDEO_DIGEST 任务、也不单独扣积分——
复刻本来就是一次收费,拆解是它的内部工序。模型调用的审计仍记在传入的复刻
task 上(AIModelAttempt),出问题查得到。
"""
from apps.ai.services import execute_routed_text_request
primary = asset.files.filter(is_primary=True).first() or asset.files.first()
if primary is None or not primary.object_key:
raise VideoDigestError("参考视频没有可用文件,请重新上传")
suffix = Path(primary.object_key).suffix.lower()
if suffix not in ALLOWED_SUFFIXES:
suffix = ".mp4"
model_config = resolve_digest_model_config(preferred_id=model_config_id)
if model_config is None:
raise VideoDigestError("视频复刻需要 Gemini 3.1 Pro(会看图),当前没有启用,请联系管理员")
local_path = ""
try:
local_path = _download_source_to_temp(primary.object_key, suffix)
base_name = (asset.name or "参考视频").rsplit(".", 1)[0]
upload = _FileFromPath(local_path, f"{base_name}{suffix}")
video, frames, duration, extras = digest_input_from_upload(upload)
extras = extras or {}
logger.info(
"replace digest using %s:%s input=%s duration=%.1fs frames=%s",
model_config.provider.name,
model_config.name,
"native_video" if video is not None else "frames",
duration,
len(frames),
)
messages = build_digest_messages(
frames,
duration,
video=video,
aspect_ratio=ratio_label(int(extras.get("width") or 0), int(extras.get("height") or 0)),
file_title=title_from_filename(str(extras.get("file_name") or "")),
)
routed = execute_routed_text_request(
task=task,
primary_model=model_config,
messages=messages,
streaming=True,
structured_output=False,
business_operation="video_digest",
temperature=0.4,
validate_text=lambda text: validate_digest_text(
text, duration=duration, frame_count=len(frames) or (24 if video else 0)
),
extra_body={"max_tokens": DIGEST_MAX_TOKENS},
request_summary={
"duration_seconds": round(duration, 2),
"frame_count": len(frames),
"input": "native_video" if video is not None else "frames",
"for": "video_replace",
},
allow_retry=False,
allow_fallback=False,
)
_text, _response, digest = routed.value
meta = {
"digest_model": model_config.name,
"digest_input": "native_video" if video is not None else "frames",
"digest_frames": len(frames),
"digest_duration": round(duration, 2),
"digest_shots": shot_count(digest),
"digest_ratio": ratio_label(int(extras.get("width") or 0), int(extras.get("height") or 0)),
}
return digest, meta
finally:
if local_path:
Path(local_path).unlink(missing_ok=True)