fix(media): reject no-audio files + purge poisoned waveform cache (fix #150)#154
Open
cuic19053-hue wants to merge 7 commits into
Open
fix(media): reject no-audio files + purge poisoned waveform cache (fix #150)#154cuic19053-hue wants to merge 7 commits into
cuic19053-hue wants to merge 7 commits into
Conversation
* feat(timeline): copy / cut / paste clips (⌘C / ⌘X / ⌘V) (appergb#94) Adds the standard clipboard shortcuts that were completely missing. Only ⌘C/⌘X/⌘V were absent — the unmodified C/V already switch tools (razor / pointer), and the mod-prefixed branch had no handlers. Frontend only: - clipboardStore: new Zustand store holding deep snapshots of the selected clips plus the source first-frame, so a paste can re-place the group relative to the current playhead. UI-only, never persisted. - editActions: copyClips / cutClips / pasteClipsAtPlayhead. - copy: snapshot selected clips + their track index + min startFrame. - cut: copy then deleteSelectedClips. - paste: offset each clip's startFrame by `activeFrame - sourceFirstFrame`, clear addLinkedAudio so the paste stands alone (mirrors upstream `pasteClipsAtPlayhead` link re-reflection), and select the new clips. Clips whose source track no longer exists are skipped. - useKeyboardShortcuts: wire ⌘C / ⌘X / ⌘V inside the existing `if (mod)` block — no conflict with the unmodified C/V tool switches. - i18n: 4 new keys (zh-CN + en) for copy / cut / paste / clipboardEmpty. Closes appergb#94. * fix(appergb#94): rebase onto main + linkGroup re-mapping + empty-clipboard toast Address review feedback on PR appergb#105: 1. Rebased onto latest main (resolved import conflict: kept both trimToPlayheadEdits and useClipboardStore). 2. copyClips now expands link groups: if a selected clip has a linkGroupId, all linked companions are included in the clipboard (mirrors upstream copyClips), so a paste reproduces the video+audio pair. 3. pasteClipsAtPlayhead now re-establishes link groups after addClips: clips that shared a linkGroupId in the clipboard are re-linked via linkClips, preserving video+audio linkage. 4. Empty-clipboard paste now shows a toast (edit.clipboardEmpty) instead of silently doing nothing. Added toast mechanism to uiStore + Toast component in App.tsx. --------- Co-authored-by: baiqing <lbx12309@icloud.com>
* feat(swap-media): 实现 SwapMedia 编辑命令,支持替换 clip 媒体 (appergb#101) 后端: - 新增 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 * style: fix cargo fmt in command.rs and tests (appergb#101) * fix: correct cargo fmt in command_apply.rs (appergb#101) * fix: align trailing comment with 43 spaces (appergb#101) * chore: trim playback whitespace * fix(swap-media): simplify DTO to 2-arg + frontend type-consistency filter (review appergb#121) * fix(swap-media): singleLinkGroup gate + extract SwapMediaSection out of Inspector (appergb#101) --------- Co-authored-by: baiqing <lbx12309@icloud.com>
* feat(inspector): live sampling + missing fields (crop/fade/flip) (appergb#97) Backend (opentake-ops + src-tauri): - Extend ClipProperties with crop, fade_in/out_frames, fade_in/out_interpolation, flip_horizontal, flip_vertical - set_clip_properties writes new fields; fade clamps to clip duration; flip_* writes to transform.flip_* - ClipPropertiesDto mirrors fields with serde camelCase - 5 unit tests: crop sets+clears track, fade frames+interp, fade clamps, flip writes to transform, multiple fields at once Frontend (web): - clip.ts: 1:1 port of Rust Clip::*_at sampling methods (opacity/volume/ rotation/size/topLeft/crop), fadeMultiplier, db<->linear, generic sampleKeyframeTrack with number/AnimPair/Crop lerp - Inspector.tsx: read activeFrame from uiStore; show sampled values at playhead; switch to ReadOnlyValue + AnimatedHint when a track is active - 4 new sections: Position (top-left x/y), Crop (4 edge insets 0-1), Flip (2 checkboxes), Fade (in/out frames + interpolation selects) - Fade section appears on both video and audio tabs - types.ts: extend ClipPropertiesReq with camelCase fields - dict.ts: i18n keys for new sections (zh-CN + en) Closes appergb#97 * style: fix cargo fmt import in command.rs (appergb#97) * fix: add ..Default::default() for new ClipProperties fields (appergb#97) * fix(appergb#97): use clip.opacity/volume for editable fields, sampled* only for animated (review appergb#122)
* feat(appergb#93): add clip right-click context menu Closes appergb#93. - New ClipContextMenu component with Split / Delete / Link-Unlink - TimelineContainer: onContextMenu hit-tests the clip, selects it if needed, and opens the menu; closes on outside click or Escape - i18n: contextMenu.split/delete/link/unlink (zh-CN + en) * fix(appergb#93): menu cursor positioning + viewport flip; remove render-phase onClose() Blocking items from review: 1. Menu now follows cursor (x/y from onContextMenu -> ClipContextMenu left/top) with useLayoutEffect viewport-boundary flip (right/bottom overflow -> open left/up). 2. Removed onClose() call during render; clip-missing now returns null and reports close via useEffect (no parent setState mid-render). Minor items: - Added disabled placeholder items: Swap Media / Save as Media / Extract Audio. - Replaced key={i} with stable key={item.id}. - Replaced imperative onMouseEnter/Leave DOM mutation with CSS :hover. * fix(timeline): remove duplicate context menu handler * feat(inspector/swap-media): gate + picker modal for Swap Media entry Wire the Swap Media context-menu action in ClipContextMenu.tsx: - Availability gate: enabled only when the clip is non-text AND alone in its link group (SPEC §5.10 "非 text 且单链组" = upstream TimelineView.menu). Multi-clip link groups (e.g. linked A/V pairs) stay disabled to avoid desyncing partners. - On click, opens a media-picker modal pre-filtered by strict type equality (item.type === clip.mediaType, mirroring upstream isAssetCompatibleWithPendingSwap). Backend re-validates as a safety net. New files: - web/src/components/timeline/SwapMediaPicker.tsx: modal list of compatible library assets; calls edit.swapMedia() on selection; shows backend error message (e.g. type-mismatch refusal) inline; Esc-to-close. New helpers / state: - web/src/lib/clip.ts: isSingleLinkGroup(clip, timeline) helper. - web/src/store/uiStore.ts: pendingSwapClipId + setPendingSwapClipId. Touched: - web/src/components/timeline/ClipContextMenu.tsx: gate + open picker. - web/src/components/timeline/TimelineContainer.tsx: render SwapMediaPicker. - web/src/i18n/dict.ts: swapMedia.noCandidates (zh + en). - web/src/lib/types.ts: swapMedia EditRequest variant. - web/src/store/editActions.ts: 2-arg swapMedia wrapper around editApply. Pairs with feat-101-swap-media (the backend `replaceClipMediaRef( resetTrim=false)` route). tsc --noEmit + pnpm build green. * fix(appergb#93): bind onContextMenu to content canvas (TS6133 unused) --------- Co-authored-by: baiqing <lbx12309@icloud.com>
* feat(timeline): 吸附迟滞+多探针 / 链接 offset 角标 / 音量橡皮筋 (appergb#99) 1. 吸附迟滞 + 多探针 - snap.ts: findSnapDelta 扩展接受 currentlySnapped + probeOffsets, 返回 probeOffset,支持 sticky band 跨 pointer 事件保持 - TimelineContainer.tsx: 新增 snapStateRef 跨事件保持吸附状态; onPointerMove move 分支收集所有 companions 的 start+end 作为探针组, 改用 findSnapDelta(不再传 null);onPointerUp 清空 snapStateRef 2. 链接 offset 角标 - clip.ts: 新增 linkOffsetForClip 计算链接组内帧偏移(相对 lead clip) - clipRenderer.ts: 新增 drawOffsetBadge 绘制红色圆角徽章 "+N"/"-N" - timelineCanvas.ts: clip 绘制参数增加 linkOffset,调用 drawOffsetBadge 3. 音量橡皮筋 - clipRenderer.ts: 新增 drawVolumeEnvelope 绘制 volumeTrack 折线 + kf 圆点 (半径 5px,黄色填充白色边框);拖拽时 ghost dot 跟随光标 - hitTest.ts: 新增 audioVolumeKfHit 命中测试(8px 容差) - TimelineContainer.tsx: 新增 audioVolumeKf DragState + 拖拽逻辑; Cmd+click 空白处调 stampKeyframe - editActions.ts: moveKeyframe / stampKeyframe 实现为前端 wrapper (read-modify-write over setKeyframes,因后端仅暴露 SetKeyframes) 验证:pnpm tsc --noEmit 通过;pnpm build 通过;52 项测试全通过 * fix(pr-120): offset badge top-right + move drag excludes playhead (review appergb#120) Two PR appergb#120 review request-changes fixes, both for spec 5.7 / 5.4 1:1 port correctness: 1. drawOffsetBadge anchored to the right edge of the clip, just inside the right trim handle (ClipRenderer.swift:640-644). The old top-left position sat on top of the color strip and label, and the new width-guard reserves room for the trim handle so the badge never overlaps it. 2. Move drag no longer includes the playhead in the snap target set. The old collectTargets(timeline, excluded, activeFrame) made moving clips stick to the playhead, which felt like a bug. Pass null (the same exclusion the trim path uses) so a move only snaps to other clip edges and the playhead stays a passive reference. pnpm tsc --noEmit + pnpm build + pnpm test 52/52 green.
* feat(timeline): drag-drop new track + Option/Alt-drag duplicate (appergb#98) Backend (Rust): - Add `opentake_ops::ops::duplicate::duplicate_clips` — deep-copies each clip (keyframe tracks / grade / chroma / masks / effects / text / transform / crop / fades via `Clip: Clone`), mints a fresh id, shifts `start_frame` by `offset_frames`, lands on `target_track_indexes[i]`, clears `link_group_id`, and clears the destination range overwrite-style first (mirrors `move_clips`). 11 unit tests cover original retention, link-group clearing, keyframe deep copy, grade/masks/effects deep copy, multi-track targets, relative spacing, overwrite blocking, frame clamping, missing-clip skip, incompatible-track skip, and text/transform. - Add `EditCommand::DuplicateClips` variant + `duplicate_clips_cmd` apply dispatch with validation (empty ids / length mismatch / missing clips) and the standard transact wrapper (snapshot -> mutate -> commit-if-changed -> version++). 7 command-level tests (creates copy, deep copies keyframes, clears link_group_id, missing clip errors, length mismatch errors, empty ids errors, undoable). - Add `EditRequest::DuplicateClips` DTO in `src-tauri/src/commands.rs` + `into_command` mapping (direct field pass-through). Frontend (React + TypeScript): - Add `duplicateClips` variant to `EditRequest` in `types.ts`. - Add `duplicateClips()` action in `editActions.ts` (applyAndRefresh). - `TimelineContainer.tsx`: add `isDuplicate` flag (Alt key detection at pointer-down), `DropTarget` discriminated union (`existing` | `newTrack`), `newTrackTypeFor` helper (audio -> "audio", else -> "video"). `onPointerMove` computes `dropTarget` (existing track via `trackAt`, or `newTrack` when below the last track bottom). `onPointerUp` branches: newTrack -> `edit.insertTrack` -> `forceRefresh` -> `edit.duplicateClips`/`moveClips` with the new track index; existing track -> group-floor-clamped move or duplicate. - `timelineCanvas.ts`: extend `DragPaint` move variant with `isDuplicate?` and `newTrackType?`. Render a dashed new-track drop indicator below the last track; render the ghost at the new-track Y when `newTrackType` is set; pass `isDuplicate` to `drawClip`. - `clipRenderer.ts`: add `isDuplicate?` to `DrawOpts`; draw a yellow "+" badge in the top-right corner when `ghost && isDuplicate` so the user sees the gesture will copy rather than move. Closes appergb#98. * style: fix cargo fmt in duplicate.rs (appergb#98) * fix: correct cargo fmt in duplicate.rs - split long assert lines (appergb#98) * fix: split long assert/assert_eq lines for cargo fmt (appergb#98) * fix: compile errors - rotation_track type + borrow conflict (appergb#98) * style: fix cargo fmt - import wrap + assert_eq single line (appergb#98) * style: fix import wrapping for cargo fmt (appergb#98) * fix: clippy errors - remove redundant clone on Copy types (appergb#98) * fix(appergb#98): implement groupCounts/groupRemap for link group remapping (review appergb#123)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
#150:部分音频文件(probe 显示 has_audio=true 但 ffmpeg 解不出 PCM)导致时间轴显示"纯绿实线"。
根因:
extract_pcm仅在 ffmpeg 退出码非 0 时报错,exit 0 但 stdout 为空时静默返回空 Vec →DSP 层产出全 1.0 静音 → 缓存层视作合法文件永久保留 → 前端命中 length>0 但每桶都是 1.0,渲染成贴底 1px。
改动
crates/opentake-media/src/decode/pcm.rs:ffmpeg 退出 0 + stdout 空视为无音频轨crates/opentake-media/src/waveform/store.rs:load 时检测"全 1.0"文件并重新生成测试
AI 协作声明
本 PR 由 cuic19053-hue 主导,Trae (Claude) 辅助 root cause + patch + 描述。
所有 AI 输出已逐行 review;架构与最终决策由人类作者负责。