完成视频复刻和优化
This commit is contained in:
@@ -123,7 +123,7 @@ def _upload_asset_to_group(request, *, team, group: FreeAssetGroup, image_only:
|
||||
file_id = uuid.uuid4()
|
||||
|
||||
if kind != "image":
|
||||
from apps.ai.media_probe import extract_video_poster, probe_duration
|
||||
from apps.ai.media_probe import duration_in_ref_range, extract_video_poster, probe_duration
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="airshelf-fc-lib-") as tmp:
|
||||
tmp_path = Path(tmp) / f"in{suffix}"
|
||||
@@ -131,7 +131,7 @@ def _upload_asset_to_group(request, *, team, group: FreeAssetGroup, image_only:
|
||||
duration = probe_duration(str(tmp_path))
|
||||
if duration is None:
|
||||
return Response({"detail": "媒体文件解析失败,请更换文件"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not (2 <= duration <= 15):
|
||||
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)
|
||||
if kind == "video":
|
||||
|
||||
@@ -116,6 +116,73 @@ def reference_review_state(asset: Asset) -> str:
|
||||
return "unsubmitted"
|
||||
|
||||
|
||||
def _unsubmitted_review_qs(*, team=None):
|
||||
qs = Asset.objects.filter(
|
||||
category__in=Asset.REVIEW_CATEGORIES,
|
||||
review_status="",
|
||||
is_deleted=False,
|
||||
)
|
||||
if team is not None:
|
||||
qs = qs.filter(team=team)
|
||||
return qs
|
||||
|
||||
|
||||
def _processing_review_qs(*, team=None):
|
||||
qs = Asset.objects.filter(
|
||||
category__in=Asset.REVIEW_CATEGORIES,
|
||||
review_status="processing",
|
||||
is_deleted=False,
|
||||
)
|
||||
if team is not None:
|
||||
qs = qs.filter(team=team)
|
||||
return qs
|
||||
|
||||
|
||||
def submit_unsubmitted_reviews(*, team=None, limit: int = 25) -> int:
|
||||
"""把未送审的人脸资产自动丢给火山,不用人去后台点「批量送审」。"""
|
||||
submitted = 0
|
||||
cap = max(1, min(int(limit), 50))
|
||||
for asset in _unsubmitted_review_qs(team=team).order_by("created_at")[:cap]:
|
||||
if submit_asset_for_review(asset):
|
||||
submitted += 1
|
||||
return submitted
|
||||
|
||||
|
||||
def poll_processing_reviews(*, team=None, limit: int = 80) -> dict[str, str]:
|
||||
cap = max(1, min(int(limit), 200))
|
||||
out: dict[str, str] = {}
|
||||
for asset in _processing_review_qs(team=team).order_by("updated_at")[:cap]:
|
||||
out[str(asset.id)] = poll_asset_review(asset)
|
||||
return out
|
||||
|
||||
|
||||
def drain_platform_reviews(*, submit_limit: int = 25, poll_limit: int = 80) -> dict:
|
||||
"""全平台:未送审自动送 + 审核中自动拉状态。worker 兜底循环,人不在后台页也会跑。"""
|
||||
submitted = submit_unsubmitted_reviews(limit=submit_limit)
|
||||
statuses = poll_processing_reviews(limit=poll_limit)
|
||||
return {"submitted": submitted, "polled": len(statuses), "statuses": statuses}
|
||||
|
||||
|
||||
def ensure_review_drain_loop() -> None:
|
||||
"""第一次有人轮询审核时,顺手把 worker 兜底循环拉起来(没有 beat 也能自转)。"""
|
||||
import sys
|
||||
|
||||
from django.conf import settings as dj_settings
|
||||
from django.core.cache import cache
|
||||
|
||||
if getattr(dj_settings, "CELERY_TASK_ALWAYS_EAGER", False) or "test" in sys.argv:
|
||||
return
|
||||
if not cache.add("asset-review-drain-kick", "1", timeout=300):
|
||||
return
|
||||
try:
|
||||
from apps.ai.tasks import drain_asset_reviews_task
|
||||
|
||||
drain_asset_reviews_task.delay()
|
||||
except Exception: # noqa: BLE001
|
||||
cache.delete("asset-review-drain-kick")
|
||||
logger.warning("kick asset review drain loop failed", exc_info=True)
|
||||
|
||||
|
||||
def poll_asset_review(asset: Asset) -> str:
|
||||
"""查单个真人资产审核状态并更新 review_status。返回最新状态。
|
||||
只在状态变化时落库(保留 updated_at 作为「进入 processing 的时刻」);processing 超时兜底为 failed。"""
|
||||
@@ -144,11 +211,8 @@ def poll_asset_review(asset: Asset) -> str:
|
||||
|
||||
|
||||
def poll_team_reviews(team) -> dict:
|
||||
"""轮询该团队所有「审核中」真人资产,更新状态。返回 {asset_id: status}。"""
|
||||
out: dict[str, str] = {}
|
||||
pending = Asset.objects.filter(
|
||||
team=team, category__in=Asset.REVIEW_CATEGORIES, review_status="processing", is_deleted=False
|
||||
)
|
||||
for asset in pending:
|
||||
out[str(asset.id)] = poll_asset_review(asset)
|
||||
return out
|
||||
"""先把本团队未送审的人脸资产送出去,再轮询审核中状态。
|
||||
前端基础资产趴定时打这里:用户不用去后台点送审,也不用等管理员。"""
|
||||
ensure_review_drain_loop()
|
||||
submit_unsubmitted_reviews(team=team, limit=20)
|
||||
return poll_processing_reviews(team=team, limit=80)
|
||||
|
||||
@@ -890,3 +890,40 @@ class ReviewScopeTests(TestCase):
|
||||
with patch("apps.assets.review.assets_client.is_enabled", return_value=False):
|
||||
out = review.poll_team_reviews(team)
|
||||
self.assertEqual(set(out.keys()), set(keep.values()))
|
||||
|
||||
def test_poll_team_reviews_auto_submits_unsubmitted(self):
|
||||
from apps.assets import review
|
||||
|
||||
_, team = _mk_team("usub", "TeamUSub")
|
||||
empty = Asset.objects.create(
|
||||
team=team, name="待审角色", asset_type="image", source="ai_generated",
|
||||
category=Asset.Category.PERSON, review_status="",
|
||||
)
|
||||
with patch("apps.assets.review.submit_asset_for_review", return_value=True) as sub, patch(
|
||||
"apps.assets.review.ensure_review_drain_loop"
|
||||
):
|
||||
review.poll_team_reviews(team)
|
||||
sub.assert_called()
|
||||
self.assertEqual(sub.call_args.args[0].id, empty.id)
|
||||
|
||||
def test_drain_platform_reviews_submits_then_polls(self):
|
||||
from apps.assets import review
|
||||
|
||||
_, team = _mk_team("drain", "TeamDrain")
|
||||
Asset.objects.create(
|
||||
team=team, name="未送", asset_type="image", source="ai_generated",
|
||||
category=Asset.Category.PERSON, review_status="",
|
||||
)
|
||||
proc = Asset.objects.create(
|
||||
team=team, name="审中", asset_type="image", source="ai_generated",
|
||||
category=Asset.Category.PERSON, review_status="processing",
|
||||
)
|
||||
with patch("apps.assets.review.submit_asset_for_review", return_value=True) as sub, patch(
|
||||
"apps.assets.review.poll_asset_review", return_value="active"
|
||||
) as polled:
|
||||
out = review.drain_platform_reviews()
|
||||
self.assertEqual(out["submitted"], 1)
|
||||
self.assertEqual(out["polled"], 1)
|
||||
self.assertEqual(out["statuses"][str(proc.id)], "active")
|
||||
sub.assert_called_once()
|
||||
polled.assert_called_once()
|
||||
|
||||
Reference in New Issue
Block a user