集成:Codex 多功能分支 + 本轮编辑阻断修复(删除/拖入落点/蓝选/预览/菜单/递归导入)#141
Closed
appergb wants to merge 52 commits into
Closed
Conversation
Add FolderTile + breadcrumb + in-library drag-to-folder so users can organize assets hierarchically (mirrors upstream FolderTileView). Backend (src-tauri/src/media.rs): - MediaItemDto gains folderId (mirrors MediaManifestEntry.folder_id) - MediaListDto gains folders: Vec<MediaFolder> (mirrors manifest.folders) - get_media / import_* now return the folder tree in one round-trip Frontend: - types.ts: MediaItem.folderId, new MediaFolder interface, MediaList.folders, EditRequest gains createFolder + moveToFolder variants - editActions.ts: createFolder() + moveToFolder() helpers - mediaStore.ts: folders field + setCatalog setter; refreshMedia pulls both - uiStore.ts: mediaPanelCurrentFolderId + setMediaPanelCurrentFolderId - dict.ts: 9 new i18n keys (zh-CN + en) for folder UX - MediaPanel.tsx: MediaTab filters by current folder, renders breadcrumb, "New Folder" button, and a grid of FolderTile + MediaCard. FolderTile supports double-click-to-enter and HTML5 drag-drop (asset -> folder reparents via moveToFolder). Closes #58.
…:1 calibration (#40) Settings: - Restructure from single-page scroll to sidebar + detail layout (mirrors upstream SettingsView.swift): 180px sidebar with icon+label rows and an active capsule on the left edge. - 8 pane entries (General, Appearance, Import, AI, MCP Instructions, Storage, Notifications, About) — the 7-pane scope from the issue, with General and Appearance kept as separate panes (upstream merges them under "general" but OpenTake's existing split is preserved). - New MCPInstructionsPane: surfaces the built-in MCP server URL (http://127.0.0.1:19789/mcp) with copy button, plus one-line install commands for Claude Code, Codex, Cursor, and Claude Desktop. Mirrors upstream Help/MCPInstructionsPane.swift, consolidated into Settings per the issue. - New StoragePane: cache + search-index fields (simplified placeholder; runtime statistics require Rust commands not yet wired). - New NotificationsPane: generation-complete toggle (front-end-only for now). Home (1:1 calibration with upstream ProjectCard.swift / HomeView.swift): - ProjectCard: hover scale 1.02 -> 1.03 (match upstream). - ProjectCard: title moved inside the thumbnail with a 60px bottom gradient overlay (upstream pattern), replacing the below-card title. - ProjectCard: relative time (today / yesterday / N days ago / N weeks ago / N months ago) replaces the raw path display, using the existing RecentProject.openedAt timestamp. - ProjectCard: delete button rounded to a circle (upstream glassEffect pattern). - NewProjectCard: hover scale 1.02 -> 1.03. i18n: 30+ new keys (zh-CN + en) for MCP, Storage, Notifications, and relative-time strings. Closes #40.
…card) (#39) Adds an end-to-end "extract audio" path so users can save a video's soundtrack as a standalone audio file from the media panel. Backend (Rust): - opentake-media: `MediaEngine::extract_audio` + `extract_audio_file` helper drive ffmpeg via the existing `ffmpeg_path()` CLI wrapper. `-y -i <in> -vn` plus codec args picked by output extension: .m4a/.aac → AAC 192k, .mp3 → libmp3lame 192k, .wav → pcm_s16le. - src-tauri/media: new `extract_audio` Tauri command resolves a media id to its `MediaSource::External` absolute path, validates the file exists, then delegates to the engine. Returns the output path. - src-tauri/lib: register `media::extract_audio` in `generate_handler!`. Frontend (React/TS): - api.ts: `extractAudio(mediaId, outPath)` wrapper; rejects outside Tauri (no ffmpeg available). - MediaPanel.tsx: MediaCard gains a star-shaped "Extract Audio" button on the top-left, shown only when hovering a video that carries an audio track. Click opens a native save dialog (m4a/mp3/wav filters), invokes `extract_audio`, and surfaces a transient success/failure feedback message. `stopPropagation`+`preventDefault` keep the click from selecting the card. - i18n dict.ts: 6 new keys (zh-CN + en) for the button title, hint, success, failure, and no-audio messages. Closes #39.
Closes #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)
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 #94.
…oast Address review feedback on PR #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.
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 项测试全通过
后端: - 新增 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 #101
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 #97
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 #98.
# Conflicts: # src-tauri/src/media.rs # web/src/components/media/MediaPanel.tsx # web/src/i18n/dict.ts # web/src/lib/types.ts # web/src/store/editActions.ts
…0624 # Conflicts: # web/src/components/timeline/TimelineContainer.tsx
added 21 commits
June 24, 2026 11:21
…0624 # Conflicts: # web/src/i18n/dict.ts
…0624 # Conflicts: # web/src/store/editActions.ts
…0624 # Conflicts: # crates/opentake-ops/src/command.rs
# Conflicts: # src-tauri/src/lib.rs # src-tauri/src/media.rs # web/src/components/media/MediaPanel.tsx # web/src/i18n/dict.ts # web/src/lib/api.ts
# Conflicts: # web/src/components/inspector/Inspector.tsx
# Conflicts: # web/src/components/timeline/TimelineContainer.tsx # web/src/lib/clip.ts
# Conflicts: # crates/opentake-ops/src/command.rs # web/src/components/timeline/clipRenderer.ts # web/src/components/timeline/timelineCanvas.ts
# Conflicts: # web/src/components/media/MediaPanel.tsx # web/src/i18n/dict.ts
…ponsive scrub + menu position
Completes the Codex integration's unfinished editing fixes (the user-reported
blockers) and a few media/home polish items.
- Delete reliability: filter the selection to clip ids that still exist before
RemoveClips/RippleDelete (one stale id made the core reject the whole batch, so
⌫ silently did nothing), and force a mirror refresh after delete on Tauri.
- Drop placement: media dragged from the panel now lands AT the cursor — start
frame = drop X, on the track under drop Y — via addMediaToTimelineAt; if that
track would overlap an existing clip it falls to another free compatible track,
or opens a new one, instead of appending to the end / overwriting. The drop is
handled in TimelineContainer (which owns the geometry); TimelineRegion keeps
only the drag-over affordance (no double add).
- Selection highlight: clips now get a clear blue 2px outline (the old near-white
border read as grey and was easy to miss).
- Preview scrub: replace the 140ms trailing debounce with a live frame + ~12fps
rate gate, so dragging the playhead updates the picture immediately instead of
waiting until you stop — still bounded, no per-frame composite storm.
- Context menu: appears at the cursor (clientX/clientY + viewport-edge flip) and
no longer calls onClose() during render (moved to an effect).
- Folder import: recursive=true so the whole directory tree is imported.
- Generate button: shows an "AI generation coming soon" toast (backend still stub)
instead of doing nothing.
- macOS chrome: traffic lights nudged down (trafficLightPosition {x:18,y:24}) and
--titlebar-safe-top/left bumped so the sidebar title clears them.
- docs/UNFINISHED-EDITING-AND-MEDIA.md: full audio/video editing + media/MCP gap
list (what's done / partial / not done) for the remaining work.
…tters, popups not white, delete surfaces errors Root-causes from a focused investigation of the broken integration build: - Pause jumps back to a random time: after Pause flips isPlaying=false, a still- queued rAF tick (the cancel is a passive effect, runs a beat later) read the now-master AUDIO element's advanced currentTime and wrote a FUTURE frame into the playhead. Guard tick with `if (!isPlaying) return;` so the straggler can't move the playhead. (Regression from 35d9eab muting video + promoting audio to clock master.) - Dragging the timeline stutters: the live 80ms rate-gate fired a full ffmpeg+wgpu+PNG composite ~12×/sec during a scrub and hogged the main thread. Revert to the resource-light 140ms trailing debounce (a proper smooth scrub needs the streaming engine #53). (Regression from abab0c0.) - Popups render white instead of dark/transparent: `--bg-elevated` (and `--bg-hover`) were used by the clip context menu, toast and text panel but never defined in tokens.css, so the background fell back to transparent/white. Define them. - Delete does nothing / silently fails: wrap removeClips / rippleDelete in try/catch so the selection is always cleared and any backend rejection is shown as a toast instead of being swallowed by the fire-and-forget `void` caller. Selection (symptom: "can't select") was already restored in abab0c0 — current HEAD has audioVolumeKfHit/onContextMenu intact. The "audio on a no-audio project" case (ffprobe reporting an empty audio stream → auto-linked audio clip) is left for a follow-up to avoid breaking real video-with-audio linking.
…actually apply
THE root cause of "the whole editing suite is broken". serde's enum-level
`#[serde(rename_all = "camelCase")]` renames variant NAMES but NOT the fields of
struct variants. So every EditRequest variant with a multi-word field
(`clip_ids`, `clip_id`, `at_frame`, `track_index`, `offset_frames`, …) expected
snake_case JSON while the front end sends camelCase — the `command` arg failed to
deserialize ("missing field `clip_ids`") and the action silently did nothing.
Only single-word-field commands (AddClips{entries}, MoveClips{moves},
TrimClips{edits}) worked, which is why drag-in/move/trim looked fine but Delete,
Split-at-playhead, Duplicate, Inspector SetClipProperties, all keyframe ops,
Link/Unlink, SwapMedia and folder ops never applied. This bug predates the
integration (main has it too) and was long misdiagnosed as "selection not
working".
Fix: add `#[serde(rename_all = "camelCase")]` to every struct variant of
EditRequest (verified: enum-level does not propagate to variant fields). Add a
regression test deserializing camelCase removeClips/splitClip/insertClips/
rippleDeleteClips.
This was referenced Jun 24, 2026
Owner
Author
|
关闭:不以整包合 main。理由:
|
cuic19053-hue
pushed a commit
to cuic19053-hue/OpenTake
that referenced
this pull request
Jun 24, 2026
…ector/…) + blue selection + recursive folder import + traffic-light position
THE root cause of "the whole editing suite is broken": serde's enum-level
`#[serde(rename_all = "camelCase")]` renames variant NAMES but NOT the fields of
struct variants, so every EditRequest variant with a multi-word field (clip_ids,
clip_id, at_frame, track_index, offset_frames, …) expected snake_case while the
front end sends camelCase — the `command` arg failed to deserialize ("missing
field `clip_ids`") and the action silently did nothing. Only single-word-field
commands (AddClips/MoveClips/TrimClips) worked, which is why drag-in/move/trim
looked fine but Delete, Split-at-playhead, Duplicate, Inspector SetClipProperties,
all keyframe ops, Link/Unlink and folder ops never applied. Long misdiagnosed as
"selection not working".
- src-tauri/src/commands.rs: add `#[serde(rename_all = "camelCase")]` to every
EditRequest struct variant + a regression test deserializing camelCase
removeClips/splitClip/insertClips/rippleDeleteClips.
- clipRenderer: selected clips get a clear blue 2px outline (old near-white
border read as grey).
- mediaActions: import folder recursively so nested media is imported.
- tauri.conf/tokens: nudge the macOS traffic lights down + widen the titlebar
safe area so the sidebar title clears them.
Self-contained subset extracted from the integration branch (appergb#141); the buggy
feature PRs and the preview/playback rewrite (appergb#142) are tracked separately.
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.
这是什么
Codex 的集成分支
codex/integration-20260624(把 #77/78/79/105/108/120/121/122/123 等功能分支合在一起)+ 本轮我完成 Codex 未做完的、用户点名的编辑阻断修复。领先 main 49 个提交。✅ 本轮完成的用户反馈修复(
abab0c0)docs/UNFINISHED-EDITING-AND-MEDIA.md附完整音视频编辑/媒体/MCP 未完成清单。本分支打包了多个仍处 CHANGES_REQUESTED 的 PR,以下问题随之带入,需在合 main 前定夺:
extract_audio命令out_path路径穿越漏洞(src-tauri/src/media.rs)+ 缺验收测试;且碰 [CRITICAL][media] 素材/媒体管理系统数据流错误,需删除并按剪映/CapCut 完全重写 #91 媒体区。kind==media_type类型校验(视频可被静默换成音频)、自创截断逻辑、缺链接组级联。duplicate.rs无条件清空 link_group_id,A/V 联动对复制后丢链接。建议
本轮的编辑阻断修复(删除/拖入/蓝选/预览/菜单/递归)都是干净、可单独受益的。但整条集成分支体量大且夹带上述未修 Bug,建议 owner 评估:① 接受这些为后续项、整体合入;或 ② 先把本轮编辑修复单独拆出合并,功能 PR 继续由 Codex 修好再逐个合。
本地验证:
tsc✅ · 52 前端测试 ✅ ·vite build✅(Rust 侧由 CI 跑)。🤖 Generated with Claude Code