feat: 素材片段区间持久化去重 + 区间用尽自动轮回 #1543
Reference in New Issue
Block a user
Delete Branch "feat/persist-used-segments"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
片段级去重(used_segments)此前只在单次 from-assets 调用内存中生效,每次调用
used_segments = {}空字典起步(clips.py),用完就丢。PR #1522 做过的跨任务去重在 #1524/#1525 重构中丢失。导致的真实问题:
metadata.used_time_ranges schema
存储在素材 metadata(
assets.classification_resultJSON,即 domainAsset.metadata)中:start/end:片段在素材内的起止时间(秒,float,保留 3 位小数)plan_id:使用该区间的剪辑计划 IDcreated_at:记录写入时间(ISO8601 UTC)generation_use_count/last_used_at/review_status共存,互不影响改动文件
1.
apps/api/app/services/asset_segment_tracker.py(新增,171 行)片段级使用记录追踪服务。所有函数不自行 commit,事务由调用方控制:
get_used_segments(db, asset_ids):批量聚合素材 metadata 中的历史区间,返回{asset_id: [(start, end), ...]},格式与_calc_random_start_time的used_segments参数一致,可直接传入。一次 IN 查询,O(N)。record_used_segments(db, asset_id, start, end, plan_id):追加一条区间记录(保留 metadata 其他字段)。remove_used_segment(db, asset_id, start, end, plan_id, tolerance=0.5):按 plan_id + 起止时间(±0.5s 容差)匹配删除一条记录,供 MediaKit 移动片段后更新。reset_used_segments(db, asset_id):清空单个素材的区间记录(轮回重置),其他 metadata 字段不动。make_reset_callback(db, used_segments):构造给_calc_random_start_time的回调,同时清空持久化记录和内存 dict。2.
apps/api/app/api/routes/templates_editor/clips.pycreate_clips_from_assets_editor:used_segments = {}→get_used_segments(db, unique_asset_ids),读取跨任务历史区间_calc_random_start_time传入on_exhausted=reset_cb(区间用尽自动轮回)record_used_segments(...)写入 metadata;不 commit,与replace_all_clips_transactional共用同一请求级 session 和事务,任一步失败整体回滚,不留脏数据_update_mediakit_recommendations_async:MediaKit 更新片段 start_time 成功后,remove_used_segment(旧区间,按 plan_id+旧 start 匹配)+record_used_segments(新区间)并 commit;异步任务失败静默,不影响已创建片段3.
packages/domain/plan_generator_utils.py_calc_random_start_time新增可选参数on_exhausted: Callable[[str], None]:100 次随机都找不到空闲区间时调用(轮回重置钩子),回调后再随机重试一次;仍失败才走原降级逻辑(素材末尾/0)。不传回调时行为与之前完全一致(向后兼容)。4. 测试
tests/unit/test_asset_segment_tracker.py(新增,14 用例):聚合/追加/删除/容差匹配/plan_id 校验/轮回重置/回调双清/on_exhausted 触发与不触发tests/unit/test_editor_clips_random_start.py:既有 from-assets 测试 mock 新 tracker 函数(保持原断言意图),fake_calc 兼容 on_exhausted 参数不动的地方
smart_match.py、POST /assets/smart-match(素材级评分)mark_asset_used_for_generation(素材级计数)_calc_random_start_time核心避开逻辑(100 次尝试 + 重叠判定),只加了重置钩子验收对照
后续 PR2(不在本 PR)
批量生成 count>1 时为每个 task 克隆独立 plan 并对 clip 重新随机起点,解决 N 条视频片段区间一模一样。
🚀 预览环境已部署
代码审查结果 - PR #1543
⚠️ 问题(1个需要修改)
_update_mediakit_recommendations_async函数中,只有当remove_used_segment返回True时才会调用record_used_segments。如果remove_used_segment返回False(例如:由于时间 tolerance 不匹配、旧数据无 plan_id 导致匹配失败,或记录不存在),record_used_segments会被跳过。但是,plan_svc.update_clip已经成功修改了片段的起始时间,且后续会执行db.commit()。这会导致数据库中片段位置已更新,但素材 metadata 中的区间记录未更新。remove_used_segment是否成功找到并删除旧记录,只要plan_svc.update_clip成功且未抛出异常,都应调用record_used_segments记录新的区间。建议修改逻辑为:先尝试remove,然后无条件执行record(除非 remove 抛出异常导致回滚)。💡 建议(2个可选)
_read_ranges函数在json.loads失败时仅返回空字典,没有记录日志。如果 metadata 中存在脏数据导致解析失败,这将难以排查。建议在except块中增加logger.warning记录解析失败的原始数据或 asset_id。remove_used_segment目前的逻辑是if match and not removed: removed = True; continue,这意味着每次调用只会删除一条匹配的记录。如果 metadata 中存在重复的历史区间(虽然正常流程不应产生),该函数无法完全清理,会导致残留数据。建议改为删除所有匹配的记录,或者确认业务逻辑上绝对不会有重复记录。✅ 格式检查通过 | ❌ 逻辑审查需修改 | ⚠️ 建议关注性能
🤖 由 AI 代码审查机器人自动生成 | 2026-08-29 12:44:16 | 模型:
🗑️ 预览环境已清理
PR #1543 已关闭或合并,对应的预览环境已被清理。