Skip to content

feat(export): save clip/range as media (#48)#216

Open
appergb wants to merge 1 commit into
mainfrom
feat/save-clip-as-media
Open

feat(export): save clip/range as media (#48)#216
appergb wants to merge 1 commit into
mainfrom
feat/save-clip-as-media

Conversation

@appergb

@appergb appergb commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Wires up the Save as Media right-click action (#48 tail, HANDOFF §3.5): render a single clip or a marked timeline range to a file on disk and import it back into the media library, so the result appears in the panel and is undoable.

Changes

Backend (src-tauri/src/export.rs)

  • New export_range command: renders a single clip (clip_id) or a marked range (in_frame..out_frame) of the whole-timeline composite.
  • Extends run_export_with_control with an optional frame_range parameter (full-timeline export passes None → unchanged behavior); the frame loop, progress reporting, and audio mixdown are all sliced to the requested range.
  • Reuses the SAME composite_frame / MediaResolver pixel path as the preview and full-timeline export → a saved clip drags back onto the timeline pixel-identical.
  • Audio-only clips (media_type = audio) and explicit AudioWav format skip the GPU: mix down via opentake_media::encode::mix → hand-written 44-byte WAV header (no ffmpeg child for raw PCM).
  • Output lands in <cache_root>/saves/save_<id>_<in>_<out>.{mp4,wav}, then import_one brings it into the manifest (same path as capture_frame_to_media).
  • Progress streams via the existing export://progress event; cancel via the existing cancel_export flag.

IPC registration (src-tauri/src/lib.rs)

  • export::export_range added to generate_handler!.

Frontend

  • web/src/lib/types.ts: ExportFormat = "video" | "audioWav" (mirror of Rust enum, camelCase wire tag pinned by test).
  • web/src/lib/api.ts: saveClipAsMedia(clipId, inFrame, outFrame, format?, trackIndex?) IPC wrapper; returns null outside Tauri.
  • web/src/components/timeline/ClipContextMenu.tsx: "Save as Media" menu item (was a disabled placeholder) now wired — resolves clip span + track index, picks audioWav for audio clips, subscribes to progress, toasts success/failure.
  • web/src/i18n/dict.ts: media.saveAsMediaSaved / media.saveAsMediaFailed toast copy (zh + en).

Acceptance

  • Video clip with color grade + text → Save as Media → re-dragged clip looks identical (same RenderPlan)
  • Audio-only clip → exports .wav (auto-downgrade VideoAudioWav)
  • Marked range (clip_id=null) → whole-timeline composite for that span
  • Output appears in media panel + undoable (via import_one manifest mutation)

Local verification (all green)

cargo fmt --all --check                                  ✓
cargo clippy --workspace --all-targets -- -D warnings    ✓
cargo clippy -p opentake-tauri --no-default-features --all-targets -- -D warnings  ✓
cargo test --workspace                                   ✓ (36 export tests, incl. 7 new)
pnpm -C web build                                        ✓
pnpm -C web test                                         ✓ (437 passed)

New unit tests: export_format_defaults_to_video, export_format_parses_audio_wav_tag (IPC camelCase pin), slice_pcm_* (3), write_wav_s16le_writes_valid_riff_header.

Closes #48.

- export_range command: single clip or marked range local render
- reuses run_export_with_control + composite_frame (same RenderPlan)
- output to project media/ + import_media (appears in panel, undoable)
- audio-only clip → wav via encode::mix
- UI: ClipContextMenu 'Save as Media' wired
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.

时间线片段编辑收尾:选中/Delete 验证 + 切割 + 片段右键菜单(Copy/Swap Media/Save as Media/AI Edit)

1 participant