完善视频提炼和视频复刻完善提交测试
This commit is contained in:
@@ -728,7 +728,7 @@ class VideoDigestView(APIView):
|
||||
if upload is None and not reuse_task_id:
|
||||
return Response({"detail": "请先上传参考视频"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
require_worker_task("apps.ai.tasks.run_video_digest_task")
|
||||
from .video_digest import VideoDigestError, submit_team_digest
|
||||
from .video_digest import VideoDigestError, VideoDigestInProgress, submit_team_digest
|
||||
|
||||
team = get_current_team(request.user)
|
||||
try:
|
||||
@@ -739,6 +739,9 @@ class VideoDigestView(APIView):
|
||||
reuse_task_id=reuse_task_id,
|
||||
model_config_id=request.data.get("model_config_id") or None,
|
||||
)
|
||||
except VideoDigestInProgress as exc:
|
||||
# 409 + 在跑的那条:前端据此直接切到进行中状态,而不是弹个错就完了
|
||||
return Response({"detail": str(exc), "inflight": exc.job}, status=status.HTTP_409_CONFLICT)
|
||||
except VideoDigestError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except Exception as exc: # noqa: BLE001 — 模型/网络失败:走统一安全文案
|
||||
@@ -866,11 +869,21 @@ class VideoReplaceView(APIView):
|
||||
# 商品复刻的拆解在 worker 里跑;worker 还是旧镜像的话消息会被静默丢弃 → 永久「提炼中」。
|
||||
if str((request.data or {}).get("replace_mode") or "") == "product":
|
||||
require_worker_task("apps.ai.tasks.run_video_replace_digest_task")
|
||||
from .video_replace import serialize_video_replace_task, submit_video_replace
|
||||
from .video_replace import (
|
||||
VideoReplaceInProgress,
|
||||
serialize_video_replace_task,
|
||||
submit_video_replace,
|
||||
)
|
||||
|
||||
team = get_current_team(request.user)
|
||||
try:
|
||||
task = submit_video_replace(team=team, user=request.user, params=request.data or {})
|
||||
except VideoReplaceInProgress as exc:
|
||||
# 409 + 在跑的那条:前端据此直接切到进行中状态,而不是弹个错就完了
|
||||
return Response(
|
||||
{"detail": str(exc), "inflight": serialize_video_replace_task(exc.task)},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
except ValueError as exc:
|
||||
message = str(exc)
|
||||
internal_kind = (
|
||||
@@ -906,11 +919,16 @@ class VideoReplaceView(APIView):
|
||||
qs = _free_video_list_queryset(team, include_replace=True).order_by("-created_at")
|
||||
total = qs.count()
|
||||
tasks = list(qs[offset : offset + page_size])
|
||||
from .video_replace import get_inflight_video_replace
|
||||
|
||||
running = get_inflight_video_replace(team) if offset == 0 else None
|
||||
return Response(
|
||||
{
|
||||
"results": [serialize_video_replace_task(t) for t in tasks],
|
||||
"total": total,
|
||||
"has_more": offset + page_size < total,
|
||||
# 首页才带:前端刷新后据此恢复「进行中」,不必自己在列表里翻状态
|
||||
"inflight": serialize_video_replace_task(running) if running is not None else None,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1059,6 +1077,13 @@ _FREE_REF_VIDEO_TYPES = {"video/mp4", "video/quicktime"}
|
||||
_FREE_REF_AUDIO_TYPES = {"audio/mpeg", "audio/wav", "audio/x-wav", "audio/wave"}
|
||||
_FREE_REF_IMAGE_MAX = 30 * 1024 * 1024
|
||||
_FREE_REF_VIDEO_MAX = 50 * 1024 * 1024
|
||||
# 视频复刻·商品:参考视频只喂给提炼模型(不发火山),没有 15 秒的硬限制,
|
||||
# 按「提炼提示词」页那套放宽到 60 秒 / 200MB。自由创作和角色复刻仍走上面的 15 秒。
|
||||
# 拿这个 purpose 传上来的长视频,若被拿去自由创作/角色复刻,下游 build_content_items
|
||||
# 与 submit_video_replace 各自还有 15 秒校验兜底,越不过去。
|
||||
_REPLACE_SOURCE_PURPOSE = "video_replace_product"
|
||||
_REPLACE_SOURCE_VIDEO_MAX = 200 * 1024 * 1024
|
||||
_REPLACE_SOURCE_DURATION_MAX = 60.5
|
||||
_FREE_REF_AUDIO_MAX = 15 * 1024 * 1024
|
||||
|
||||
|
||||
@@ -1088,6 +1113,7 @@ class FreeVideoUploadView(APIView):
|
||||
team = get_current_team(request.user)
|
||||
content_type = (upload.content_type or "").lower()
|
||||
size = upload.size or 0
|
||||
long_source = str(request.data.get("purpose") or "").strip() == _REPLACE_SOURCE_PURPOSE
|
||||
|
||||
if content_type in _FREE_REF_IMAGE_TYPES:
|
||||
kind, asset_type, suffix = "image", Asset.Type.IMAGE, {"image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp"}[content_type]
|
||||
@@ -1095,8 +1121,12 @@ class FreeVideoUploadView(APIView):
|
||||
return Response({"detail": "图片大小不能超过 30MB"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
elif content_type in _FREE_REF_VIDEO_TYPES:
|
||||
kind, asset_type, suffix = "video", Asset.Type.VIDEO, ".mp4" if content_type == "video/mp4" else ".mov"
|
||||
if size > _FREE_REF_VIDEO_MAX:
|
||||
return Response({"detail": "视频大小不能超过 50MB"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
video_max = _REPLACE_SOURCE_VIDEO_MAX if long_source else _FREE_REF_VIDEO_MAX
|
||||
if size > video_max:
|
||||
return Response(
|
||||
{"detail": f"视频大小不能超过 {video_max // 1024 // 1024}MB"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
elif content_type in _FREE_REF_AUDIO_TYPES:
|
||||
kind, asset_type, suffix = "audio", Asset.Type.AUDIO, ".mp3" if content_type == "audio/mpeg" else ".wav"
|
||||
if size > _FREE_REF_AUDIO_MAX:
|
||||
@@ -1132,9 +1162,16 @@ class FreeVideoUploadView(APIView):
|
||||
duration = probe_duration(str(tmp_path))
|
||||
if duration is None:
|
||||
return Response({"detail": "媒体文件解析失败,请更换文件"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not duration_in_ref_range(duration):
|
||||
label = "视频" if kind == "video" else "音频"
|
||||
return Response({"detail": f"{label}时长需在 2-15 秒之间"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
from .media_probe import REF_DURATION_MIN
|
||||
|
||||
if long_source and kind == "video":
|
||||
in_range = REF_DURATION_MIN <= duration <= _REPLACE_SOURCE_DURATION_MAX
|
||||
range_hint = "视频时长需在 2-60 秒之间"
|
||||
else:
|
||||
in_range = duration_in_ref_range(duration)
|
||||
range_hint = f"{'视频' if kind == 'video' else '音频'}时长需在 2-15 秒之间"
|
||||
if not in_range:
|
||||
return Response({"detail": range_hint}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if kind == "video":
|
||||
poster_bytes = extract_video_poster(str(tmp_path))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user