Skip to content

feat(swap-media): 实现 SwapMedia 编辑命令,支持替换 clip 媒体 (#101)#121

Open
cuic19053-hue wants to merge 7 commits into
appergb:mainfrom
cuic19053-hue:feat-101-swap-media
Open

feat(swap-media): 实现 SwapMedia 编辑命令,支持替换 clip 媒体 (#101)#121
cuic19053-hue wants to merge 7 commits into
appergb:mainfrom
cuic19053-hue:feat-101-swap-media

Conversation

@cuic19053-hue

Copy link
Copy Markdown
Contributor

What

解决 #101:媒体替换(SwapMedia)

How

后端

  • 新增 EditCommand::SwapMedia 变体,替换 clip 的 media_ref
  • 校验新媒体存在于 manifest
  • 若新媒体时长 < clip duration,自动截断
  • 保留所有编辑属性(transform/crop/keyframe tracks/grade/masks/effects/fade)

前端

  • 新增 swapMedia EditRequest 类型 + action
  • Inspector 新增「替换媒体」入口

Testing

  • pnpm tsc --noEmit 通过
  • pnpm build 通过
  • Rust 代码 IDE 诊断 0 错误(本机无 cargo,待 CI 验证)

Limitation

issue #101 还提到 SaveAsMedia 和 ExtractAudio,这两项依赖 ffmpeg 编码管线(crates/opentake-encode/src/preset.rs 未完成),留作后续。

Closes #101

@cuic19053-hue cuic19053-hue requested a review from appergb as a code owner June 23, 2026 18:57

@appergb appergb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cuic19053-hue 自动审核结论:请修改(REQUEST_CHANGES)。SwapMedia 命令端到端接线 OK、不碰 #91 区,但三处违背 1:1 上游(issue #101 明确『校验类型一致』):

  1. 缺类型一致校验(CRITICAL):swap_media 不校验新素材 kind,前端对所有非 text 候选传 mediaType: item.type——视频 clip 可被换到音频素材并静默改类型(留在视频轨),完整性违规。上游 isAssetCompatibleWithPendingSwap 要求 clip.mediaType == asset.type,不符则 toast + 保持 pending。请加严格类型一致校验。
  2. 自创 truncate-to-fit + trim_end clamp(HIGH):上游 replaceClipMediaRef 默认 resetTrim=false,只改 mediaRef,保留 trim/speed/keyframes/transform、不改时长。本 PR 的 steps 7-8(按新媒体长度截断 + clamp trim_end)无上游依据,会在换短素材时改变 clip 时序。请删除该逻辑。
  3. 缺链接组级联(HIGH):上游对『同链接组、共享同一旧 media』的 clip 一起换;Inspector 可用性条件应为『非 text 且单链组』(spec 5.10 frontend-UI-1to1-SPEC.md:665),当前只 gate 非 text。

请按上游 1:1 修正后 rebase 到含 #119 的 main。

@appergb appergb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cuic19053-hue Sonnet 4.6 复审:仍需修改。CI 双绿,但有正确性/完整性阻塞:

  1. [CRITICAL] 缺类型一致校验。 crates/opentake-ops/src/command.rs swap_media() 只读 new_asset.duration,未校验 new_asset.kind == clip.media_type → 视频 clip 可被静默换成音频素材并留在视频轨。上游 EditorViewModel+MediaSwap.swift:32guard clip.mediaType == asset.type。前端 Inspector.tsx 候选过滤 m.type !== 'text' 应改为 m.type === clip.mediaType
  2. [HIGH] 自创 truncate/trim_end clamp 无上游依据。 swap_media() 第 7、8 步按新媒体长度截断 final_duration + clamp trim_end——上游 replaceClipMediaRef(resetTrim:false)(ClipMutations.swift:462)默认只改 mediaRef,保留 trim/speed/keyframes/transform、不改时长。请删除这两步。
  3. [HIGH] 缺链接组级联。 transact 闭包只更新单个 clip_id;上游 linkedClipIdsSharingMedia(anchor:)(ClipMutations.swift:467)批量替换同 linkGroupId 且共享旧 mediaRef 的整组。前端入口还缺 singleLinkGroup gate(上游 TimelineView.swift:741)。
  4. 触碰 #91 媒体重写区:Inspector.tsx 新增 import useMediaStores.items,碰 store/mediaStore*。请改用既有非媒体区入口或与 #91 协调。

把 1–3 按上游改对、4 避开媒体区后重提。

cuic19053-hue and others added 6 commits June 24, 2026 23:28
后端:
- 新增 EditCommand::SwapMedia 变体,替换 clip 的 media_ref
- 校验新媒体存在于 manifest,若时长不足自动截断 duration + 调整 trim_end
- 保留所有编辑属性(transform/crop/keyframe tracks/grade/masks/effects/fade)
- media_type 隐含 source_clip_type(spec "sync media_type" 场景)
- 新增 EditRequest::SwapMedia DTO + into_command 映射
- 6 个单元测试:等长替换/较短截断/媒体不存在/同步 media_type/clip 不存在/undo

前端:
- types.ts 新增 swapMedia EditRequest 变体
- editActions.ts 新增 swapMedia(clipId, mediaRef, options?) action
- Inspector 新增「替换媒体」section + 内联媒体选择器
- i18n 中英文翻译

Closes appergb#101
@cuic19053-hue

Copy link
Copy Markdown
Contributor Author

复审反馈已全部处理(2 个 commit:18a76e1 + 02d484e):

1. [CRITICAL] 类型一致性校验 — swap_media() 现在拒绝 clip.media_type != asset.kind(视频 clip 只能换视频素材)。前端候选过滤从 m.type !== 'text' 改为 m.type === clip.mediaType。

2. [HIGH] 删除自创 truncate/trim_end clamp — SwapMedia 命令缩减为 { clip_id, media_ref } 两字段,对齐上游
eplaceClipMediaRef(resetTrim=false):只改 mediaRef,保留 trim/speed/keyframes/transform/crop/grade/masks/effects/fade,不改时长。

3. [HIGH] 链接组级联 + 前端 gate — 后端 ransact 批量替换同 linkGroupId 且共享旧 mediaRef 的整组。前端加 singleLinkGroup gate(SPEC §5.10, frontend-UI-1to1-SPEC.md:665「非 text 且单链组」):clip 属于多 clip 链接组时隐藏 Swap Media 入口(避免级联意外)。

4. 避开 mediaStore 区 — SwapMediaSection 提取到独立文件 web/src/components/inspector/SwapMediaSection.tsx,Inspector.tsx 不再 import useMediaStore。mediaStore 依赖隔离在单文件内,#91 重写时只需更新这一处。

8 个单元测试覆盖:类型不符拒绝、同 mediaRef no-op、短素材保留 duration/trim、链接组级联、不同 mediaRef 不级联、undo 恢复、未知 clip 拒绝、未知 mediaRef 拒绝。

请复审。

@cuic19053-hue

Copy link
Copy Markdown
Contributor Author

@appergb 请求重新审查。此前反馈已全部修复,CI 双绿(Rust ✅ Web ✅,commit \�4d92d6\):

  1. 类型校验:\swap_media\ 检查 \clip.media_type == asset.kind\,不符则拒绝
  2. 删 truncate/clamp:仅改 \mediaRef\,保留 trim/speed/keyframes/transform(不再 truncate-to-fit 或 clamp trim_end)
  3. 链接组级联:同 \linkGroupId\ 且共享旧 \mediaRef\ 的整组批量替换
  4. EditRequest 精简:\SwapMedia\ 缩为 { clip_id, media_ref }\ 2 字段
  5. Inspector filter:\m.type === clip.mediaType\ 严格类型匹配 + \swapMedia\ 2-arg 调用

请 re-review,谢谢!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[backend] Swap Media / Save as Media / Extract Audio 命令缺失

2 participants