feat: 素材选取评分注入随机噪声,降低成片查重率 (#1620)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m30s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m58s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m49s
CI/CD Pipeline / Validate - Style (push) Successful in 6m35s
CI/CD Pipeline / Integration Tests (push) Successful in 6m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m58s
CI/CD Pipeline / Validate - Security (push) Successful in 8m18s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 8m53s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m33s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m35s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 5m6s
CI/CD Pipeline / Unit Tests (push) Successful in 14m24s
CI/CD Pipeline / Production Browser E2E (push) Failing after 349h30m4s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 349h40m28s
CI/CD Pipeline / Canary Release to Production (push) Failing after 349h30m5s
CI/CD Pipeline / Deploy Production (push) Failing after 349h30m5s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 349h44m27s
CI/CD Pipeline / CI Gate (push) Failing after 349h30m6s
CI/CD Pipeline / PR Build Web Image (push) Failing after 349h44m30s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 349h30m6s
CI/CD Pipeline / PR Build API Image (push) Failing after 349h44m30s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 349h44m34s
CI/CD Pipeline / Build Production Web Image (push) Failing after 349h30m6s
CI/CD Pipeline / Build Production API Image (push) Failing after 349h30m6s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 350h14m47s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 350h18m48s
CI/CD Pipeline / Frontend Lint (push) Failing after 350h18m50s

Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
This commit was merged in pull request #1620.
This commit is contained in:
2026-09-02 01:40:34 +08:00
committed by auto-approve-bot
parent c66e73e5b0
commit 2c5600cfc9
6 changed files with 248 additions and 31 deletions
@@ -48,7 +48,7 @@ from packages.adapters.sqlalchemy_impl.template_repository import (
SQLAlchemyTemplateRepository,
)
from packages.domain.plan_generator_utils import _calc_random_start_time
from packages.domain.smart_match import score_asset
from packages.domain.smart_match import SCORE_RANDOM_NOISE_MAX, score_asset
from packages.shared.mediakit_client import get_mediakit_client
from .dependencies import get_draft_plan_id, get_editor_services
@@ -744,14 +744,15 @@ def create_clips_from_assets_editor(
clip_duration = 0.0
start_time: float | None = None
# 动态按使用次数排序:优先选使用最少的素材,同次数随机打散
asset_use_counts = {
aid: len(used_segments.get(aid, []))
for aid in asset_ids
}
asset_use_counts = {aid: len(used_segments.get(aid, [])) for aid in asset_ids}
# 排序键:smart_match 评分(注入随机噪声)→ 使用次数 → 纯随机。
# 噪声让得分接近的素材排名每次浮动,避免同一批素材反复选出相同组合,
# 从素材组合层面降低成片查重率;分差 > SCORE_RANDOM_NOISE_MAX 时排名稳定,
# 质量差距显著的素材仍保持优先级。
sorted_candidates = sorted(
asset_ids,
key=lambda aid: (
-asset_smart_scores.get(aid, 0.0),
-(asset_smart_scores.get(aid, 0.0) + random.uniform(0.0, SCORE_RANDOM_NOISE_MAX)),
asset_use_counts.get(aid, 0),
random.random(),
),
@@ -847,15 +848,15 @@ def create_clips_from_assets_editor(
duplicate_warning = f"查重率 {dup_rate:.1f}% 超过50%,建议更换素材或模板"
logger.warning(
"from-assets 成片查重率超标: plan_id=%s dup_rate=%.1f%%",
plan_id, dup_rate,
plan_id,
dup_rate,
)
# 7. 素材耗尽提示
exhaustion_warning = None
if all_assets_exhausted and created_count < len(segments):
exhaustion_warning = (
"素材可切区间不足,部分片段使用了复用素材。"
"建议:1) 补充更多素材到素材库 2) 使用不同的素材组合生成"
"素材可切区间不足,部分片段使用了复用素材。" "建议:1) 补充更多素材到素材库 2) 使用不同的素材组合生成"
)
# 8. 立即返回响应
@@ -1022,9 +1023,11 @@ def _update_mediakit_recommendations_async( # pragma: no cover
if cid != clip_id_inner and cid not in updated_clip_ids:
segs.append((c.start_time, c.start_time + c.duration))
segs.extend(updated_segments.get(asset_id_inner, []))
# 并入历史已用区间
def _norm(segs_in):
return {(round(float(a), 3), round(float(b), 3)) for a, b in segs_in}
return list(_norm(segs) | _norm(historical_segments.get(asset_id_inner, [])))
# 优先使用 SceneChange 策略
@@ -1035,7 +1038,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
scene_segments = _build_scene_segments(scene_changes, asset_total)
logger.info(
"后台任务: 素材场景检测完成: asset_id=%s scenes=%d",
asset_id, len(scene_segments),
asset_id,
len(scene_segments),
)
# SceneChange 未获得有效结果 → 尝试 analyze_videos 作为 fallback
@@ -1047,7 +1051,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
scene_segments = [(rec_start, asset_total)]
logger.info(
"后台任务: 使用 analyze_videos fallback: asset_id=%s start=%.2f",
asset_id, rec_start,
asset_id,
rec_start,
)
if not scene_segments: