拖拽片段交换 + 标准导出全套 + 预览/素材体验修复#172
Open
appergb wants to merge 10 commits into
Open
Conversation
Dragging a clip onto another track now swaps the two clips' positions (track + start frame) instead of overwriting/swallowing the destination, with a live preview of the displaced clip sliding into the vacated slot. - New lossless `swap_clip_positions` op + `EditCommand::SwapClips`; the op refuses (no change) if a swap would overlap a third clip, so nothing is ever destroyed. camelCase IPC DTO (`swapClips`/clipA/clipB) with a deserialize regression test (guards the recurring DTO camelCase trap). - Frontend dispatches the swap only on a single-clip cross-track drop that lands on exactly one existing clip; everything else keeps the prior move. The media drop-in indicator no longer floods the whole row: the full-width "new track" fill becomes a thin dashed insertion line and the clip-sized gray ghost is bolder, so the landing spot is always legible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Honest naming for the existing exporter and three new standard timeline-interchange formats, all in opentake-project as pure functions with Tauri command wrappers. - Rename: the existing `export_fcpxml` command always produced XMEML 4 (FCP7 XML); add an honest `export_xmeml` command and keep `export_fcpxml` as a thin deprecated alias. The XMEML output bytes are unchanged (it is a faithful 1:1 port of upstream XMLExporter.swift; no conformance bug found). - EDL (crates/opentake-project/src/edl.rs): CMX3600 edit decision list — TITLE / FCM, numbered events, reel/channel/transition, source+record timecodes at timeline fps (drop/non-drop per fps), `* FROM CLIP NAME:`. Video track only (format limitation documented in the output). - OTIO (crates/opentake-project/src/otio.rs): OpenTimelineIO JSON, shape validated against the reference samples (Timeline.1 → Stack.1 → Track.1 → Clip.1/Gap.1, RationalTime.1/TimeRange.1, ExternalReference.1 with file:// target_url + available_range). Hand-written via serde_json (no maintained GPL-compatible OTIO crate exists). - FCPXML modern (crates/opentake-project/src/fcpxml_modern.rs): native Final Cut Pro X FCPXML 1.10 — resources/assets/formats, library/event/ project/sequence/spine, asset-clip/title, adjust-transform/adjust-volume. Carries text overlays XMEML can't. Premiere does NOT import FCPXML (noted in code). - xmlnode.rs: small shared XML document tree for the FCPXML emitter. New Tauri commands: export_xmeml, export_edl, export_otio, export_fcpxml_modern (export_fcpxml kept as alias). All return Result<(), String> at the boundary. 103 new unit tests across the four modules; full workspace builds, clippy clean (-D warnings). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn the single "导出" / "Export" button into a format menu offering the four standard timeline-interchange formats, mirroring the existing subtitle popover. - web/src/lib/api.ts: add exportXmeml / exportEdl / exportOtio / exportFcpxmlModern path-only wrappers; keep exportFcpxml as a @deprecated alias for exportXmeml. All no-op outside Tauri. - web/src/components/shell/TitleBar.tsx: replace onExport (XMEML-only) with an INTERCHANGE_FORMATS-driven popup menu — XMEML (Premiere・ DaVinci・剪映), FCPXML (Final Cut Pro), OTIO (达芬奇・工业标准), EDL (CMX3600). Each opens the native save dialog with the right extension (.xml/.fcpxml/.otio/.edl) then calls its command + toasts. Extracted a shared useDismissable hook for both popovers. - web/src/i18n/dict.ts: zh-CN + en labels, dialog titles, filter names, and done/failed toasts for all four formats. - TitleBar.visual.test.ts: lock in the four formats → extension/command mapping and the popup-menu shape. web build (tsc + vite) green; 211 web tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ary, preload-on-select, opaque export menu Preview - Dropping media onto the timeline is an HTML5 drop (no pointerdown), so the media-preview→timeline switch never fired and the preview stayed on the dropped asset's standalone view. Clear the selected media on drop so the preview shows the timeline composite at the playhead. - Settle paused media to the final frame when a scrub ends, and re-run the paused sync when the timeline changes while paused, so a clip the scrub/edit just brought under the playhead no longer holds its source frame 0. Media loading - Library grid lazy-mounts each card's thumbnail via IntersectionObserver: a video entry with no cached poster otherwise loaded its full source file in a <video>, so dozens of cards decoded at once. Off-screen cards now stay a placeholder until scrolled into view. - New `preload_media` command warms the poster + timeline filmstrip sprite + waveform caches on a worker thread; the media panel fires it (best-effort) on select and drag-start, so preview and the subsequent timeline drop read from cache instead of decoding on the interaction path. Export menu - Define the missing `--bg-elevated` token: every popover (export / subtitle / clip context menu / swap-media picker) referenced it undefined and rendered with a transparent background that bled the panel behind it through. - Add a "Render to Video (MP4)" item to the export menu (greyed when the timeline is empty) so video render is reachable alongside the interchange formats. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…op on newer stable) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The export render device was created with `Limits::downlevel_defaults()`, whose `max_texture_dimension_2d` is 2048. The downscaled preview stays under that, but a full-resolution export (FHD is borderline, 2K/4K exceed it) made `Device::create_texture` fail with an uncaptured wgpu error that panics — so "export video" aborted (SIGABRT) the whole app. Request the adapter's real limits instead (Metal reports 16384), covering every realistic export size. Adds a 4K export integration test (renders 3840x2160) that SIGABRTs on the old device and passes on the fixed one. Existing exports only covered 720p (1280x720 < 2048), which is why CI never caught the crash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed frame The timeline preview stacked clips by DOM paint order (no z-index), which React reconciliation could shuffle as clips enter/leave during scrub — so the preview could disagree with the final composite. Pin each layer's z-index to its track order (lower index = higher layer, matching opentake-render's track-0-topmost blend), so the preview and the exported frame agree exactly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…reload
The Tauri asset protocol honors HTTP Range (verified in tauri-2.11.x
src/protocol/asset.rs: 206 Partial Content, Accept-Ranges, ~1 MiB chunks),
so WKWebView already streams <video> progressively from disk. The cold-click
slowness was the <video> defaulting to preload="auto" (eager buffering) with no
poster (blank/spinner until the first frame paints). Fix without a segment
pipeline:
- Preview <video> now uses preload="metadata" + an instant HI-RES first-frame
poster, so a cold click shows a sharp frame immediately and the rest streams
lazily during playback/pause. De-blurs (poster is up to 1920x1080, not the
120x68 grid thumbnail) and removes the blank/spinner.
- New preview_poster(mediaRef, timeSecs?) Tauri command + video_preview_poster
decode (shared decode_poster_to helper with video_poster). Cached as
{key}.preview[.{ms}].png, keyed separately from the grid {key}.thumb.png so
the two sizes never clobber. api.ts previewPoster() wrapper; Preview fetches
it on select (video only, cleared between items).
- Re-purpose preload_media: warm ONLY the hi-res preview poster (video). Drop
the heavy 240-frame filmstrip sprite + waveform warming — neither speeds
actual <video> playback, and the timeline loads them lazily itself when a clip
lands (TimelineContainer generateThumbnail/getWaveform).
- Page-aware media grid: a video card pre-warms its preview poster when it
scrolls into view (same IntersectionObserver gate, so far-out cards aren't
touched). Bound the in-memory thumbnail-path cache with a small LRU
(BoundedCache, 256) so a long scrolled library can't grow memory without
limit.
Gates: fmt --check, clippy --workspace -D warnings, test --workspace, web build,
web test (217) all green. New tests: preview-poster cache keying (Rust),
BoundedCache LRU (vitest). Preview is GPU/codec-dependent — needs real-app
visual verification for sharpness + instant first-frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (upstream parity) - New-track insertion indicator is now a solid YELLOW line (1:1 with upstream's NSColor.systemYellow), replacing the dashed white hint. - Snap feedback: on macOS a clip-edge / media-drop snap fires a light trackpad "alignment" haptic via a new `snap_haptic` command (NSHapticFeedbackManager through objc2-app-kit, dispatched on the main thread, best-effort). Other platforms get a short quiet tick sound. Deduped — fires once per fresh engagement, mirroring upstream's SnapEngine haptic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… tick) Per the user, the playhead should magnetize to the nearest clip start/end while scrubbing — both on the timeline ruler and the preview scrub bar — with the same snap tick as clip drags. (A slight, deliberate enhancement over the upstream code, which scrubs the playhead freely.) - Timeline ruler scrub uses the existing sticky snap engine (findSnapDelta) over clip-edge targets only (not the playhead itself), with its own scrubSnapRef so it doesn't fight the clip-move snap. - Preview scrub bar uses a new frame-based snapFrameToEdge (~0.25s threshold), since the bar carries no timeline zoom. Unit-tested. - Both fire maybeSnapFeedback on engage (deduped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
基于真机测试反馈的一批剪辑体验修复 + 导出能力扩展。4 个 commit,本地五项关卡全绿(
cargo fmt --check/clippy -D warnings/cargo test --workspace/pnpm build/pnpm test212)。拖拽交互(commit 1)
swap_clip_positionsop +EditCommand::SwapClips(换位后若与第三个片段重叠则拒绝、零丢失)+ camelCase IPC DTO(附反序列化回归测试)。拖动时实时预览被挤走的片段滑入让出的槽位。仅在「单片段跨轨且落点恰好压住一个片段」触发,其余保持原 move 行为。标准导出全套(commit 2–3)
export_fcpxml产物其实是 XMEML 4(FCP7 XML,Premiere/达芬奇/剪映可导入)——新增诚实的export_xmeml,旧名保留为弃用别名。预览 / 素材 / 导出 UI 修复(commit 4)
previewMediaId,预览切到时间线合成。<video>里加载整段源文件,几十张同时解码;现在滚到可见才加载。preload_media命令(worker 线程预热 poster + 时间线 filmstrip sprite + 波形缓存),媒体面板在选中 / 拖拽开始时 best-effort 触发,预览和落点都走缓存、不在交互路径上解码。--bg-elevatedtoken(所有弹层之前引用未定义→背景透明、透出后面面板);导出菜单加"渲染为视频(MP4)"项(空时间线置灰)。Test plan
cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspace(含 swap op 5 + IPC camelCase + 各导出格式 51 测试)pnpm -C web build+pnpm -C web test(212)tauri buildrelease 打包成功🤖 Generated with Claude Code