Commit Graph

17 Commits

Author SHA1 Message Date
Skyler Lehmkuhl 6e6feaddf5 Fix FLAC export end-to-end (real backend path) + smart tag defaults
The prior audio-tags commit put real FLAC + metadata into export/audio_exporter.rs
— which turned out to be dead code (declared, never called; whole file was
EngineController::start_export_audio → daw-backend's export_audio, which still
routed FLAC to the erroring hound stub — hence "not implemented in daw-backend".

Move the work to where export actually happens:
- daw-backend/src/audio/export.rs: real ffmpeg FLAC (16-bit S16 / 24-bit S32,
  skipping the trailing empty flush packet the FLAC muxer rejects); apply_metadata
  on MP3/AAC/FLAC output; RIFF LIST/INFO chunk appended to WAV. New metadata field
  on the backend ExportSettings, threaded from the UI in run_audio_export. Tests
  assert real fLaC magic + round-tripped tags, and a valid WAV INFO chunk.
- Delete the dead export/audio_exporter.rs (removes the duplicate FLAC impl).

Smart tag defaults (filled only when empty, never clobbering edits):
- Year → current civil year, computed from the system clock with i64 math (no
  date crate; correct past 2038/2106 — tests cover post-i32/u32 timestamps).
- Artist → last-used value, else the OS username ($USER/%USERNAME%).
- Album → last-used value.
Last-used Artist/Album persist in AppConfig and prefill next export.
2026-07-09 13:09:54 -04:00
Skyler Lehmkuhl 15bdf80ec1 Audio export: real FLAC + tag metadata for all formats
- FLAC is now real FLAC via ffmpeg, not WAV bytes in a .flac file. 16-bit uses
  S16, 24-bit uses S32 (ffmpeg's flac encoder emits bits_per_raw_sample=24).
  The flush emits a trailing empty packet that the FLAC muxer rejects as
  "invalid data" — it's skipped.

- Tag metadata (title/artist/album/genre/year/track/comment) written into every
  format via each container's native tags: ID3v2 (MP3), MP4 atoms (M4A), Vorbis
  comments (FLAC) set through ffmpeg's output metadata; RIFF LIST/INFO appended
  to the hound-written WAV (with a fixed-up RIFF size). New AudioMetadata type
  on AudioExportSettings; dialog gains a Tags section and defaults Title to the
  project name.

Tests: FLAC is a real fLaC container with round-tripped tags; WAV keeps a valid
RIFF with a working INFO chunk.
2026-07-09 12:52:37 -04:00
Skyler Lehmkuhl c373af461e Add animated GIF export
New export format alongside audio/image/video/SVG. GIF is multi-frame like
video but palette-quantized with no audio, so it reuses the per-frame RGBA
render/readback path (render_frame_to_gpu_rgba) and streams frames to a
background encoder.

- core: GifExportSettings (resolution, framerate, loop, transparency, fit,
  time range) with centisecond-quantized frame delay + tests.
- gif_exporter: encoder pipeline. Per-frame NeuQuant quantization is the
  dominant cost and is per-frame independent, so it's fanned out across a
  worker pool (cores-1, capped 8); a writer thread reorders and LZW-encodes
  sequentially. Uses the `gif` crate directly (already resolved via `image`).
- orchestrator: start_gif_export + render_next_gif_frame (one frame per egui
  update), wired into is_exporting/has_pending_progress/cancel.
- dialog: GIF tab + settings; main.rs: handle ExportResult::Gif and pump frames.
- Cargo: opt-level=3 for gif/color_quant/weezl in the dev profile so debug
  builds aren't crippled by unoptimized NeuQuant loops.

Together these cut a 10s GIF export from ~1:43 to ~3s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 06:02:47 -04:00
Skyler Lehmkuhl f13a127c9d Fit modal dialogs to the phone screen on mobile
Add mobile::dialog_width(ctx, desired) — a min()-clamp that's a no-op on wide
desktop screens. Clamp the Export and Export Progress modals and the Sample
Import window to it. Render Preferences as an egui::Modal (dim backdrop, centered,
no title bar) with a screen-fit scroll height when mobile_active(); desktop keeps
its window. Extracted the preferences body into render_body() shared by both paths.
2026-07-01 06:28:04 -04:00
Skyler Lehmkuhl 1fa4d744be Add SVG vector import and export
Export (lightningbeam-core/svg_export.rs): document_to_svg walks vector
layers/groups at a given frame and emits <path> per fill (solid or
gradient via <defs>) and per stroked edge. Wired into the export dialog
as an "SVG" tab; written synchronously. Raster/video/effect layers are
skipped (vector-only, lossless), structured for a later rasterize pass.

Import (lightningbeam-editor/svg_import.rs): import_svg parses via usvg,
bakes each path's absolute transform into geometry, converts segments to
cubic edges, and maps solid/linear/radial paint to ShapeColor/
ShapeGradient. .svg is detected in the Ctrl+I Import handler and added as
a new vector layer (keyframe at the playhead). file_types gains
FileType::Vector + VECTOR_EXTENSIONS.

Tests: svg_export::export_tests (core) and svg_import::tests (editor).
2026-06-26 16:40:39 -04:00
Skyler Lehmkuhl 5bc117c518 export: add Fit mode to image export too
ImageExportSettings gains `fit: ExportFitMode` (default Letterbox) + a "Fit"
dropdown in advanced image settings; the image render path uses it instead of the
hardcoded Letterbox. Image export supports a resolution override, so the mode
matters when the override aspect differs from the document.
2026-06-26 14:42:05 -04:00
Skyler Lehmkuhl fffcf0679c aspect ratio: unify video placement + add export fit modes
A) Imported video had a different aspect ratio depending on how it was placed:
direct import used a uniform scale + center (preserve aspect), while the
asset-library timeline drag used an independent scale_x/scale_y (stretch to fill).
Add Transform::fit_centered (uniform scale, centered, aspect-preserving) and route
both paths through it, so a clip looks identical however it's added.

B) Exported video was stretched when the export resolution's aspect differed from
the document's (base_transform was always scale_non_uniform). Add
ExportFitMode {Stretch, Letterbox (default), Crop} on VideoExportSettings + a "Fit"
dropdown in advanced export settings, and a shared export_base_transform() helper:
Letterbox = uniform fit centered (black bars), Crop = uniform fill centered (trim),
Stretch = the old distort-to-fill. Threaded through the software, HDR, and
zero-copy export render paths (image export is doc-sized → identity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 06:54:49 -04:00
Skyler Lehmkuhl 33dec6f327 export: 10-bit HDR video frame path + UI (Stage B pt 2)
Wire HDR frame production end-to-end (encoder side was pt 1):
- linear_to_pq.wgsl: linear scene HDR (BT.709, white=1.0) → BT.2020 primaries →
  PQ (203-nit) or HLG OETF → gamma-encoded R'G'B'. Inverse of the nv12 decode, so
  a decode→encode round-trip is the identity.
- hdr_frame.rs (isolated module): runs that pass into an Rgba16Float target, does a
  synchronous readback (f16-decoded on CPU), then BT.2020 R'G'B'→Y'CbCr limited
  4:2:0 10-bit pack → YUV420P10LE planes. Rgba16Float avoids the 16BIT_NORM device
  feature dependency.
- video_exporter::render_frame_to_yuv10_hdr: composite + HDR encode, returning
  10-bit planes; lazily builds the pipeline.
- orchestrator: HDR uses a synchronous 1-frame-per-call path (the async RGBA
  pipeline is 8-bit only); zero-copy is skipped for HDR.
- dialog: "Dynamic range" dropdown (SDR / HDR10 PQ / HLG).

Software HEVC Main10 path; favors correctness over throughput. Compiles; runtime
verification needs a GPU + HDR-capable player.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:47:35 -04:00
Skyler Lehmkuhl 9411145ce9 export: H.264 color range toggle (fix dark/oversaturated output)
The zero-copy VAAPI encoder emitted full-range BT.709 NV12 but wrote no color
tags, so players assumed limited range and stretched it — the H.264 output
looked dark and oversaturated (preview and VP9/software were fine).

- Rgba2Nv12 takes `full_range` and applies the matching Y/chroma scale+offset
  (limited 16-235 / full 0-255) via a uniform; the encoder sets color_range +
  bt709 colorspace/primaries/transfer tags to match. ffprobe-confirmed.
- New ColorRange { Limited, Full } on VideoExportSettings (default Limited, the
  universally-correct choice; serde(default) so saved settings still load),
  surfaced as a "Color range" dropdown in advanced export settings for H.264.

The swscale software fallback still emits Limited regardless of the toggle
(Full only affects the VAAPI zero-copy path).
2026-06-26 00:38:19 -04:00
Skyler Lehmkuhl 83057b754a Clean up build warnings
Resolve all compiler warnings across daw-backend, lightningbeam-core, and
lightningbeam-editor:

- Delete dead code: the superseded CPU raster tools in raster_tool.rs
  (EffectBrush/Smudge/Gradient/Transform/Warp/Liquify/Selection — replaced by
  the GPU path), plus orphaned helpers and never-read struct fields.
- Mechanical fixes: drop unused imports/variables/mut, underscore unused params,
  `drop(&x)` -> `let _ = x`, deprecated egui::Rounding -> CornerRadius, snake_case
  rename, elided-lifetime Cow<'_, [u8]>.
- Keep the WIP CSS theming system (theme.rs/theme_render.rs) under
  #[allow(dead_code)] rather than deleting it.

Editor checks warning-free; 293 core tests pass.
2026-06-21 16:48:59 -04:00
Skyler Lehmkuhl 78e296ffde Improve export performance 2026-03-09 13:39:56 -04:00
Skyler Lehmkuhl a18a335c60 Export images 2026-03-09 11:22:51 -04:00
Skyler Lehmkuhl 93a29192fd Split export dialog into simple/advanced 2026-02-16 08:15:13 -05:00
Skyler Lehmkuhl e03d12009f fix broken mp3/aac export 2026-02-16 07:53:23 -05:00
Skyler Lehmkuhl 777d3ef6be Clean up build warnings 2026-02-14 11:07:32 -05:00
Skyler Lehmkuhl 06246bba93 Video export 2025-12-07 13:17:21 -05:00
Skyler Lehmkuhl 727d782190 initial audio export 2025-12-03 17:54:15 -05:00