feat(#2035): semantic tags + quality score + AI caption+embedding
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 55s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 59s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m38s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m35s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m21s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 55s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 59s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m38s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m35s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m21s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
- Fix: generation_tasks.py passes clip_ai_tags_by_asset to pick_narrative_assets so AI tags (weight 2.0) actually participate in narrative mode selection - Feat: quality_score auto-computation via AssetAnalyzer on ingest (worker.calculate_asset_price celery task; fallback 50.0 on failure) - Feat: smart_match adds ai_semantic dimension (20% weight) using Jaccard similarity between asset AI tags (scene/objects/action) and script tags - Feat: atom_clip caption (10-30 Chinese chars) via Doubao Vision, saved to asset_atom_clips.caption (Text column, migration 085) - Feat: atom_clip embedding vector via Doubao embeddings API, saved to asset_atom_clips.embedding (JSON column) - Chore: remove dead calculate_quality_score_real wrapper - Tests: 20 new unit tests covering caption parsing, ai_semantic scoring, narrative AI tag propagation, score weight changes; update existing tests for new fallback dict shape and reweighted dimensions - Fail-open: tagging/embedding/quality failures never block main flow
This commit is contained in:
@@ -134,10 +134,11 @@ def _ensure_library_has_ready_video_assets(assets) -> None:
|
||||
def _select_assets_from_library(
|
||||
assets: list,
|
||||
mode: str,
|
||||
count: int,
|
||||
count: int = 0,
|
||||
rng=None,
|
||||
script_tags: list | None = None,
|
||||
tag_names_by_id: dict | None = None,
|
||||
db=None,
|
||||
) -> list[str]:
|
||||
"""根据选取模式从素材库中选取 ready 状态的视频素材 ID。
|
||||
|
||||
@@ -158,6 +159,42 @@ def _select_assets_from_library(
|
||||
if not ready_video_assets:
|
||||
return []
|
||||
|
||||
# #2035:加载片段级 AI 标签,供叙事模式 AI 加权和 smart 模式语义匹配使用。
|
||||
# 失败降级为空(不影响选片主流程)。
|
||||
clip_ai_tags_by_asset: dict[str, list[dict]] = {}
|
||||
ai_tags_by_asset: dict[str, dict] = {} # asset_id → 聚合后的 ai_tags dict(取首个有 has_text 的片段;合并 scene/objects/action 去重)
|
||||
try:
|
||||
if db is not None:
|
||||
from packages.adapters.sqlalchemy_impl.models import AssetAtomClipModel
|
||||
ready_ids = [a.id for a in ready_video_assets]
|
||||
clip_rows = (
|
||||
db.query(AssetAtomClipModel.asset_id, AssetAtomClipModel.ai_tags)
|
||||
.filter(AssetAtomClipModel.asset_id.in_(ready_ids))
|
||||
.filter(AssetAtomClipModel.ai_tags.isnot(None))
|
||||
.all()
|
||||
)
|
||||
agg: dict[str, dict] = {}
|
||||
for asset_id, ai_tags in clip_rows:
|
||||
if not isinstance(ai_tags, dict):
|
||||
continue
|
||||
clip_ai_tags_by_asset.setdefault(asset_id, []).append(ai_tags)
|
||||
# 聚合:合并 scene/objects/action 去重
|
||||
agg.setdefault(asset_id, {"scene": [], "objects": [], "action": [], "shot": "", "has_text": False})
|
||||
for key in ("scene", "objects", "action"):
|
||||
for v in ai_tags.get(key) or []:
|
||||
v = str(v).strip()
|
||||
if v and v not in agg[asset_id][key]:
|
||||
agg[asset_id][key].append(v)
|
||||
if ai_tags.get("has_text") is True:
|
||||
agg[asset_id]["has_text"] = True
|
||||
if not agg[asset_id]["shot"] and ai_tags.get("shot"):
|
||||
agg[asset_id]["shot"] = ai_tags["shot"]
|
||||
ai_tags_by_asset = agg
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning("[选片] 加载片段 AI 标签失败,降级不使用语义匹配", exc_info=True)
|
||||
clip_ai_tags_by_asset = {}
|
||||
ai_tags_by_asset = {}
|
||||
|
||||
# 叙事模式(#1970 PR3):文案标签命中池优先;无任何命中时完全降级为现有随机逻辑。
|
||||
if script_tags:
|
||||
from packages.domain.narrative_match import pick_narrative_assets
|
||||
@@ -167,6 +204,7 @@ def _select_assets_from_library(
|
||||
ready_video_assets,
|
||||
script_tags=script_tags,
|
||||
tag_names_by_id=tag_names_by_id,
|
||||
clip_ai_tags_by_asset=clip_ai_tags_by_asset,
|
||||
limit=limit,
|
||||
rng=rng,
|
||||
)
|
||||
@@ -177,7 +215,16 @@ def _select_assets_from_library(
|
||||
# 评分维度:质量分(40%) + 时长适配(30%) + 新鲜度(20%) + 未使用加分(10%)
|
||||
# 排序注入随机噪声(#1743):同分素材每次选出不同组合,从素材组合层面降重
|
||||
limit = count if count > 0 else None
|
||||
results = smart_select_assets(ready_video_assets, limit=limit, kind="video", rng=rng)
|
||||
# #2035:给 smart_select_assets 传入文案标签和 AI 标签映射,启用语义维度
|
||||
norm_script = {t.strip().lower() for t in (script_tags or []) if t and t.strip()}
|
||||
results = smart_select_assets(
|
||||
ready_video_assets,
|
||||
limit=limit,
|
||||
kind="video",
|
||||
rng=rng,
|
||||
script_tags=norm_script if norm_script else None,
|
||||
ai_tags_by_asset=ai_tags_by_asset or None,
|
||||
)
|
||||
return [r.asset.id for r in results]
|
||||
|
||||
# 默认 all 模式:返回全部 ready 视频素材
|
||||
@@ -396,6 +443,7 @@ def create_generation_task(
|
||||
count=request.asset_select_count,
|
||||
script_tags=narrative_script_tags or None,
|
||||
tag_names_by_id=_tag_index,
|
||||
db=db,
|
||||
)
|
||||
elif project_id and not resolved_asset_ids and (request.asset_select_mode in ("smart",) or narrative_script_tags):
|
||||
# 项目级模式:未指定 asset_ids 且选择了 smart 模式(或叙事模式按标签匹配)时自动选取
|
||||
@@ -410,6 +458,7 @@ def create_generation_task(
|
||||
count=request.asset_select_count,
|
||||
script_tags=narrative_script_tags or None,
|
||||
tag_names_by_id=_tag_index,
|
||||
db=db,
|
||||
)
|
||||
if not resolved_asset_ids:
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user