[P0] 查重优化: 跨视频片段避让 — 生成前注入已用区间 #1670

Closed
opened 2026-09-03 20:50:13 +08:00 by xiaoxia · 0 comments
Owner

背景

当前系统素材选择层面已有避让机制(smart_match.pygeneration_use_count + unused_bonus + 随机噪声),但片段截取层面完全没有跨视频避让

每次生成新视频时,_calc_random_start_timeused_segments 从空 {} 开始,纯随机取起始时间,不关心之前的视频已经使用了该素材的哪些时间段。导致同一批素材(如 13 个)生成的多个视频(如 3 个),虽然片段顺序不同(#1663 已合并),但同一素材被截取的时间段高度重叠,画面相似度高,查重率居高不下。

edit_plan_clips 表中已有 asset_id + start_time + duration 数据,但没有任何代码在生成新视频时查询这些数据。

根因

  • used_segments 只在单次生成内部生效(避免同一素材在同一次生成中被截取两次)
  • 不跨视频:每次新视频生成时 used_segments = {}
  • 结果:同一素材在不同视频中被反复截取相近的时间段

关键约束:预览与正式生成 1:1

预览视频和最终视频必须保持 1:1 一致性。因此跨视频片段避让必须同时覆盖两个场景

  1. 预览生成(preview):用户在编辑器中点击预览时,素材分配也要查询已有视频的已用区间并避让
  2. 正式生成(final generation):正式提交生成时,同样查询并避让

两者共用同一套 used_segments 注入逻辑,确保预览看到的效果就是最终产出的效果。

实现要点:在 PlanGeneratorService 中,无论是 preview 还是 final 路径,只要调用 _distribute_assets(),都必须先查询已有视频的已用区间并注入。不应存在 preview 跳过避让的分支。

任务

1. 新增 Repository 方法

EditPlanClipRepository 中新增方法:

list_used_segments_by_user(user_id, *, limit_recent=50) -> dict[str, list[tuple[float, float]]]

查询该用户最近 N 个视频的 edit_plan_clips 记录,按 asset_id 聚合返回 {asset_id: [(start_time, start_time+duration), ...]}

2. 注入到素材分配流程(preview + final 共用)

PlanGeneratorService._distribute_assets() 中:

  • 预览和正式生成都必须调用上述方法获取已有 used_segments
  • 将已有区间传入 distribute_assets()_distribute_one_take() 等分配函数
  • _calc_random_start_time 已有 used_segments 避让逻辑,无需改动,只需注入数据

3. 避让策略

  • 优先选取未被使用过的区间
  • 如果所有区间都被使用过(素材反复使用),优先选取使用次数最少的区间
  • 不阻塞生成:如果避让查询失败或无数据,回退到当前纯随机行为

4. 查询范围

  • user_id 查询(跨项目),因为用户可能在多个项目中使用同一批素材
  • 限制最近 50 个视频,避免大库性能问题
  • 只查询 status=completed 的 clip

5. 单元测试

  • 测试 list_used_segments_by_user 正确聚合
  • 测试注入已有区间后,新视频避开已用区间
  • 测试 preview 和 final 路径都触发了避让查询
  • 测试避让失败时回退到纯随机

涉及文件

  • packages/adapters/sqlalchemy_impl/edit_plan_clip_repository.py — 新增查询方法
  • apps/api/app/services/plan_generator_service.py_distribute_assets 注入已有区间(preview + final)
  • packages/domain/plan_generator_utils.py — 可能需要调整函数签名传入外部 used_segments

技术债务约束(铁律)

  • 新代码必须有单元测试覆盖
  • preview 和 final 共用避让逻辑,不存在分支差异
  • 避让查询失败时不阻塞生成,回退到当前行为
  • API 不变,纯内部逻辑改造
  • staging 先验证:同一批素材生成多次,对比 clips 的 start_time 不再重叠

优先级

P0 - 最高优先级,直接决定查重率是否真正降低

依赖

  • 无硬依赖,可独立实施
  • 与 #1657(分片指纹存储)并行推进,#1657 完成后检测精度提升,本 Issue 的避让效果更容易验证
## 背景 当前系统素材选择层面已有避让机制(`smart_match.py` 中 `generation_use_count` + `unused_bonus` + 随机噪声),但**片段截取层面完全没有跨视频避让**。 每次生成新视频时,`_calc_random_start_time` 的 `used_segments` 从空 `{}` 开始,纯随机取起始时间,不关心之前的视频已经使用了该素材的哪些时间段。导致同一批素材(如 13 个)生成的多个视频(如 3 个),虽然片段顺序不同(#1663 已合并),但同一素材被截取的**时间段高度重叠**,画面相似度高,查重率居高不下。 `edit_plan_clips` 表中已有 `asset_id` + `start_time` + `duration` 数据,但没有任何代码在生成新视频时查询这些数据。 ## 根因 - `used_segments` 只在单次生成内部生效(避免同一素材在同一次生成中被截取两次) - 不跨视频:每次新视频生成时 `used_segments = {}` - 结果:同一素材在不同视频中被反复截取相近的时间段 ## 关键约束:预览与正式生成 1:1 预览视频和最终视频必须保持 **1:1 一致性**。因此跨视频片段避让**必须同时覆盖两个场景**: 1. **预览生成**(preview):用户在编辑器中点击预览时,素材分配也要查询已有视频的已用区间并避让 2. **正式生成**(final generation):正式提交生成时,同样查询并避让 两者共用同一套 `used_segments` 注入逻辑,确保预览看到的效果就是最终产出的效果。 **实现要点**:在 `PlanGeneratorService` 中,无论是 preview 还是 final 路径,只要调用 `_distribute_assets()`,都必须先查询已有视频的已用区间并注入。不应存在 preview 跳过避让的分支。 ## 任务 ### 1. 新增 Repository 方法 在 `EditPlanClipRepository` 中新增方法: ``` list_used_segments_by_user(user_id, *, limit_recent=50) -> dict[str, list[tuple[float, float]]] ``` 查询该用户最近 N 个视频的 `edit_plan_clips` 记录,按 `asset_id` 聚合返回 `{asset_id: [(start_time, start_time+duration), ...]}`。 ### 2. 注入到素材分配流程(preview + final 共用) 在 `PlanGeneratorService._distribute_assets()` 中: - **预览和正式生成都必须**调用上述方法获取已有 `used_segments` - 将已有区间传入 `distribute_assets()` → `_distribute_one_take()` 等分配函数 - `_calc_random_start_time` 已有 `used_segments` 避让逻辑,无需改动,只需注入数据 ### 3. 避让策略 - 优先选取未被使用过的区间 - 如果所有区间都被使用过(素材反复使用),优先选取使用次数最少的区间 - 不阻塞生成:如果避让查询失败或无数据,回退到当前纯随机行为 ### 4. 查询范围 - 按 `user_id` 查询(跨项目),因为用户可能在多个项目中使用同一批素材 - 限制最近 50 个视频,避免大库性能问题 - 只查询 `status=completed` 的 clip ### 5. 单元测试 - 测试 `list_used_segments_by_user` 正确聚合 - 测试注入已有区间后,新视频避开已用区间 - 测试 preview 和 final 路径都触发了避让查询 - 测试避让失败时回退到纯随机 ## 涉及文件 - `packages/adapters/sqlalchemy_impl/edit_plan_clip_repository.py` — 新增查询方法 - `apps/api/app/services/plan_generator_service.py` — `_distribute_assets` 注入已有区间(preview + final) - `packages/domain/plan_generator_utils.py` — 可能需要调整函数签名传入外部 `used_segments` ## 技术债务约束(铁律) - 新代码必须有单元测试覆盖 - preview 和 final 共用避让逻辑,不存在分支差异 - 避让查询失败时不阻塞生成,回退到当前行为 - API 不变,纯内部逻辑改造 - staging 先验证:同一批素材生成多次,对比 clips 的 start_time 不再重叠 ## 优先级 P0 - 最高优先级,直接决定查重率是否真正降低 ## 依赖 - 无硬依赖,可独立实施 - 与 #1657(分片指纹存储)并行推进,#1657 完成后检测精度提升,本 Issue 的避让效果更容易验证
xiaoxia added this to the 技术债务与优化 milestone 2026-09-03 20:50:13 +08:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: xiaoxia/xiaoxia-saas#1670