P0 批量生成降重失效:N个视频共用同一plan、片段同组同序、封面相同、查重率60%+ #1743

Open
opened 2026-09-06 16:14:27 +08:00 by xiaoxia · 1 comment
Owner

问题现象

用户批量生成 3 个视频(batch_id f3859c81cb804d4b9ee913aae8f92e82),staging 实测:

  1. 3 个视频封面相同(仅标题文字不同);
  2. 成片与前端预览不一致(预览中变体有差异,成片片段完全同组同序);
  3. 降重手段全失效:成品查重率 60.1% / 33.3% / 0.0%;worker 日志确认 3 任务渲染片段 asset_id 完全相同、顺序相同(953a0bf1 / 9e0be16f / 2fd510a7,每段 5.3s、speed=0.7229)。

根因(已实查代码+DB+worker日志)

  1. 3 个任务 source_edit_plan_id 全部兜底关联同一个 plan(a2989c05,"9/6-草稿")。
    • apps/api/app/api/routes/generation_tasks.py 克隆块条件 if count > 1 and request.source_edit_plan_id::前端批量 POST 未带 source_edit_plan_id(storedSourceEditPlanId 丢失/为空),克隆块整个跳过,零变体 plan 落库、也无 500;
    • 随后任务循环里的兜底块(~528-548行)if not task.source_edit_plan_id and request.template_id: 按 template_id+user_id 查最新 plan,count=3 个任务全部关联同一个最新 plan,无任何批量感知。
  2. 现有克隆逻辑先天降重不足EditPlanService.clone_plan_for_variant(edit_plan_service.py:462)复制片段时 asset_id/duration/clip_type/order 原样不变,只重算 start_time。即使触发,变体间仍是同一批素材、同一顺序。
  3. worker _render_from_edit_plan(generation.py:715-751)有 plan 即按 plan clips 渲染,渲染期唯一随机性是成片后 2-5% random_edge_crop;plan 相同则成片同源。

单视频降重手段清单(全部发生在 plan/clips 生成阶段,批量必须逐项对齐)

  • packages/domain/plan_generator_utils.py
    • distribute_assets(:164):random.shuffle 素材分配;
    • pick_scene_aware_start(:86):random.shuffle 镜头段,同素材多次生成选不同镜头;
    • _calc_random_start_time(:406)/pick_start_in_scene_segment(:55):起点随机 + 历史已用区间避让;
    • playback_speed 随机(clips 配置);
    • record_used_segments 跨任务区间记录。
  • 渲染期:random_edge_crop 2-5%(仅服务外部平台,力度最小)。
  • 批次内查重机制(batch_id)已具备,但同源片段 pHash 中心区拦不住。

修复要求(总原则:多视频 = 单视频逻辑 × N,只是 1 条和多条的区别)

  1. 批量正式生成为每个变体产出独立 plan,走与单视频完全相同的选片流程
    • 任务 0 保留源 plan(保留用户编辑结果);
    • 任务 1..N-1 禁止兜底共用最新 plan:每个变体新建独立 plan,调用单视频同一套 plan 生成/选片入口(素材打乱 distribute_assets + 镜头洗牌 pick_scene_aware_start + 起点随机 + playback_speed 随机 + record_used_segments 跨变体避让),即把单视频 plan 生成完整重跑 N-1 次,而不是 clone clips;
    • clone_plan_for_variant 的"只改起点不换素材"逻辑替换为完整重新选片。
  2. 兜底块加批量感知:count>1 时绝不允许 N 个任务关联同一 plan_id;无法为变体生成 plan 时直接 4xx/5xx 中断并提示,不得静默共用。
  3. 批次内查重前置:同一 batch_id 变体选片完成后做片段组合重叠检查(同 asset 同区间占比建议阈值 20%),超阈值自动重选;渲染后批次内查重率超阈值的任务自动重渲一次。
  4. 封面独立:每个变体从自己的成片抽帧,抽帧时间点带变体随机,避免都抽 frame_0;cover_urls 按变体独立落库。
  5. 现有 titles[]/voice_library_ids[]/cover_urls[] 变体级配置机制保留。
  6. 单测必须覆盖:N=3 各变体 plan 不同、片段 asset 组合/顺序显著不同、批次重叠检查、封面独立;API 向后兼容。

验收标准(staging E2E)

  • N=3 批量生成:3 成片片段 asset 组合不同、顺序不同、封面不同,批次内查重率全部 <20%
  • N=1 单视频零回归(预览/正式生成行为与现在一致);
  • 变体力所不及(素材极少)时明确报错提示,不允许退回同源成片。

参考核查报告:项目目录《批量生成降重失效核查_20260906.md》。

## 问题现象 用户批量生成 3 个视频(batch_id `f3859c81cb804d4b9ee913aae8f92e82`),staging 实测: 1. **3 个视频封面相同**(仅标题文字不同); 2. **成片与前端预览不一致**(预览中变体有差异,成片片段完全同组同序); 3. **降重手段全失效**:成品查重率 **60.1% / 33.3% / 0.0%**;worker 日志确认 3 任务渲染片段 asset_id 完全相同、顺序相同(953a0bf1 / 9e0be16f / 2fd510a7,每段 5.3s、speed=0.7229)。 ## 根因(已实查代码+DB+worker日志) 1. **3 个任务 source_edit_plan_id 全部兜底关联同一个 plan**(a2989c05,"9/6-草稿")。 - `apps/api/app/api/routes/generation_tasks.py` 克隆块条件 `if count > 1 and request.source_edit_plan_id:`:前端批量 POST 未带 source_edit_plan_id(storedSourceEditPlanId 丢失/为空),克隆块整个跳过,零变体 plan 落库、也无 500; - 随后任务循环里的兜底块(~528-548行)`if not task.source_edit_plan_id and request.template_id:` 按 template_id+user_id 查最新 plan,**count=3 个任务全部关联同一个最新 plan**,无任何批量感知。 2. **现有克隆逻辑先天降重不足**:`EditPlanService.clone_plan_for_variant`(edit_plan_service.py:462)复制片段时 asset_id/duration/clip_type/order **原样不变,只重算 start_time**。即使触发,变体间仍是同一批素材、同一顺序。 3. worker `_render_from_edit_plan`(generation.py:715-751)有 plan 即按 plan clips 渲染,渲染期唯一随机性是成片后 2-5% random_edge_crop;plan 相同则成片同源。 ## 单视频降重手段清单(全部发生在 plan/clips 生成阶段,批量必须逐项对齐) - `packages/domain/plan_generator_utils.py` - `distribute_assets`(:164):random.shuffle 素材分配; - `pick_scene_aware_start`(:86):random.shuffle 镜头段,同素材多次生成选不同镜头; - `_calc_random_start_time`(:406)/`pick_start_in_scene_segment`(:55):起点随机 + 历史已用区间避让; - playback_speed 随机(clips 配置); - record_used_segments 跨任务区间记录。 - 渲染期:random_edge_crop 2-5%(仅服务外部平台,力度最小)。 - 批次内查重机制(batch_id)已具备,但同源片段 pHash 中心区拦不住。 ## 修复要求(总原则:多视频 = 单视频逻辑 × N,只是 1 条和多条的区别) 1. **批量正式生成为每个变体产出独立 plan,走与单视频完全相同的选片流程**: - 任务 0 保留源 plan(保留用户编辑结果); - 任务 1..N-1 禁止兜底共用最新 plan:每个变体新建独立 plan,调用单视频同一套 plan 生成/选片入口(素材打乱 distribute_assets + 镜头洗牌 pick_scene_aware_start + 起点随机 + playback_speed 随机 + record_used_segments 跨变体避让),即把单视频 plan 生成完整重跑 N-1 次,而不是 clone clips; - `clone_plan_for_variant` 的"只改起点不换素材"逻辑替换为完整重新选片。 2. **兜底块加批量感知**:count>1 时绝不允许 N 个任务关联同一 plan_id;无法为变体生成 plan 时直接 4xx/5xx 中断并提示,不得静默共用。 3. **批次内查重前置**:同一 batch_id 变体选片完成后做片段组合重叠检查(同 asset 同区间占比建议阈值 20%),超阈值自动重选;渲染后批次内查重率超阈值的任务自动重渲一次。 4. **封面独立**:每个变体从自己的成片抽帧,抽帧时间点带变体随机,避免都抽 frame_0;cover_urls 按变体独立落库。 5. 现有 titles[]/voice_library_ids[]/cover_urls[] 变体级配置机制保留。 6. 单测必须覆盖:N=3 各变体 plan 不同、片段 asset 组合/顺序显著不同、批次重叠检查、封面独立;API 向后兼容。 ## 验收标准(staging E2E) - N=3 批量生成:3 成片片段 asset 组合不同、顺序不同、封面不同,**批次内查重率全部 <20%**; - N=1 单视频零回归(预览/正式生成行为与现在一致); - 变体力所不及(素材极少)时明确报错提示,不允许退回同源成片。 参考核查报告:项目目录《批量生成降重失效核查_20260906.md》。
Author
Owner

补充发现(2026-09-06 17:40 核查):智能选素材「每次只选同样几个素材」根因

用户反馈:每次智能选择素材都优先选同样几个,生成视频查重率高。已实查代码+staging DB,确认 3 个独立缺陷,请在本工单一并修复:

1. smart-match 评分排序零随机,同分素材每次选出同一批(主因)

packages/domain/smart_match.py smart_select_assets Step4:scored.sort(key=lambda r: r.score, reverse=True) 纯得分降序,没有任何随机噪声_diversity_select 桶内也按固定得分顺序取。

  • 实测用户素材库 11 个视频全部 quality_score=NULL(按 50 计)、use_count=0、创建时间接近 → 评分几乎完全同分(时长 5-30s 的全是满分 88 分);
  • Python sort 稳定排序 + DB 返回顺序固定 → 每次点智能匹配,同分档返回的 Top N 永远是同一批素材、同一顺序
  • 注意:from-assets 片段分配用的 SCORE_RANDOM_NOISE_MAX=20 噪声只加在 apps/api/app/api/routes/templates_editor/clips.py:772 的片段候选排序上,smart-match 选素材环节完全没用上
  • DB 实证:两批任务的 asset_ids 各自内部 3 个任务完全相同(中断批 5375faa7/0c1bfa83/f3c398f6;重渲批 668cf0d4/69ea3060/0c1bfa83)。

要求:smart-match 排序注入与 from-assets 同款随机噪声(score + random.uniform(0, SCORE_RANDOM_NOISE_MAX)),让同分/近分素材每次选出的组合不同;分差 >20 的高质量素材仍保持优先级。

2. 「未使用偏好」和「高频排除」机制实际失效

  • score_asset 的 unused_bonus 读 metadata.generation_use_count,但 staging DB 实测:11 个视频(含已被 3 个成片任务引用的 668cf0d4/69ea3060/0c1bfa83)use_cnt 全部为 0。worker generation.py:863 完成后有 mark_asset_used_for_generation 回写逻辑,但实际没生效(15:44 容器重启前日志已不可查,需排查:是 update 未提交、asset_ids 为空,还是该路径未覆盖 edit_plan 渲染分支)。
  • smart-match 的「最近5个视频出现>3次排除」高频保护(assets.py:~620)同样依赖该计数,计数不涨 → 保护永不触发。
  • 要求:排查并修复 generation_use_count 回写(注意应按成片实际渲染的 clips asset_id 计数,而不是请求传入的 asset_ids——本次 3 任务 asset_ids 含 0c1bfa83 但 plan clips 实际没用它);补计数回写的单测/验证。

3. 智能匹配素材池太小,N 段视频只给 N 个素材

前端 useSmartMatch.ts computeLimitFromSegmentslimit = max(片段数, ceil(总时长/15s))。3 段模板 limit=3 → 智能匹配只返回 3 个素材,from-assets 片段分配只能在这 3 个里挑,素材组合多样性先天受限。

  • 要求:limit 至少给到 片段数 × 3(或总时长/10s,取大值,钳到 200),给片段分配和批量变体留足候选池;与 #1743 变体独立选片联动——每个变体的素材组合必须来自更大的候选池并独立随机。

验证数据

  • 重渲批 3 任务 asset_ids 完全相同:668cf0d4(IMG_2277)/69ea3060(IMG_2282)/0c1bfa83(IMG_2286);plan a2989c05 clips 实际用 668cf0d4×2 + 69ea3060×1。
  • 库内 11 个视频 quality_score 全 NULL、generation_use_count 全 0。
## 补充发现(2026-09-06 17:40 核查):智能选素材「每次只选同样几个素材」根因 用户反馈:每次智能选择素材都优先选同样几个,生成视频查重率高。已实查代码+staging DB,确认 **3 个独立缺陷**,请在本工单一并修复: ### 1. smart-match 评分排序零随机,同分素材每次选出同一批(主因) `packages/domain/smart_match.py` `smart_select_assets` Step4:`scored.sort(key=lambda r: r.score, reverse=True)` **纯得分降序,没有任何随机噪声**;`_diversity_select` 桶内也按固定得分顺序取。 - 实测用户素材库 11 个视频全部 quality_score=NULL(按 50 计)、use_count=0、创建时间接近 → 评分几乎完全同分(时长 5-30s 的全是满分 88 分); - Python sort 稳定排序 + DB 返回顺序固定 → **每次点智能匹配,同分档返回的 Top N 永远是同一批素材、同一顺序**。 - 注意:from-assets 片段分配用的 `SCORE_RANDOM_NOISE_MAX=20` 噪声只加在 `apps/api/app/api/routes/templates_editor/clips.py:772` 的片段候选排序上,**smart-match 选素材环节完全没用上**。 - DB 实证:两批任务的 asset_ids 各自内部 3 个任务完全相同(中断批 5375faa7/0c1bfa83/f3c398f6;重渲批 668cf0d4/69ea3060/0c1bfa83)。 **要求**:smart-match 排序注入与 from-assets 同款随机噪声(score + random.uniform(0, SCORE_RANDOM_NOISE_MAX)),让同分/近分素材每次选出的组合不同;分差 >20 的高质量素材仍保持优先级。 ### 2. 「未使用偏好」和「高频排除」机制实际失效 - `score_asset` 的 unused_bonus 读 `metadata.generation_use_count`,但 staging DB 实测:11 个视频(含已被 3 个成片任务引用的 668cf0d4/69ea3060/0c1bfa83)**use_cnt 全部为 0**。worker `generation.py:863` 完成后有 `mark_asset_used_for_generation` 回写逻辑,但实际没生效(15:44 容器重启前日志已不可查,需排查:是 update 未提交、asset_ids 为空,还是该路径未覆盖 edit_plan 渲染分支)。 - smart-match 的「最近5个视频出现>3次排除」高频保护(assets.py:~620)同样依赖该计数,计数不涨 → 保护永不触发。 - **要求**:排查并修复 generation_use_count 回写(注意应按**成片实际渲染的 clips asset_id** 计数,而不是请求传入的 asset_ids——本次 3 任务 asset_ids 含 0c1bfa83 但 plan clips 实际没用它);补计数回写的单测/验证。 ### 3. 智能匹配素材池太小,N 段视频只给 N 个素材 前端 `useSmartMatch.ts` `computeLimitFromSegments`:`limit = max(片段数, ceil(总时长/15s))`。3 段模板 limit=3 → 智能匹配只返回 3 个素材,from-assets 片段分配只能在这 3 个里挑,素材组合多样性先天受限。 - **要求**:limit 至少给到 片段数 × 3(或总时长/10s,取大值,钳到 200),给片段分配和批量变体留足候选池;与 #1743 变体独立选片联动——每个变体的素材组合必须来自更大的候选池并独立随机。 ### 验证数据 - 重渲批 3 任务 asset_ids 完全相同:668cf0d4(IMG_2277)/69ea3060(IMG_2282)/0c1bfa83(IMG_2286);plan a2989c05 clips 实际用 668cf0d4×2 + 69ea3060×1。 - 库内 11 个视频 quality_score 全 NULL、generation_use_count 全 0。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: xiaoxia/xiaoxia-saas#1743