Commit Graph

36 Commits

Author SHA1 Message Date
Skyler Lehmkuhl 6b8a1f1386 SVG export: emit text layers as real glyph outlines
SVG export silently dropped Text layers (they fell through the layer_to_svg
catch-all) while the dialog implied only raster/video/effect were excluded, so
title/caption text vanished from a "lossless" export with a success message.

Emit text as actual glyph-outline <path>s: lay the text out with the same
parley path the renderer uses, then extract each positioned glyph's outline
with skrifa (an OutlinePen that maps points into document space — Y flip,
synthetic-italic skew, variable-font normalized coords). Result is
font-independent and needs no <text>/@font-face. Vello rasterizes glyphs on the
GPU and doesn't expose the path, but the skrifa outline API it uses is directly
callable and parley's glyph IDs are real font GIDs, so the outlines match.

Synthetic bold is not applied (rare). Adds a skrifa dep pinned to parley's 0.43.
2026-07-09 07:10:59 -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 fd582828c2 Add text layers
Introduce editable text layers: a resizable text box with editable text,
font size, color, font family, and alignment.

Core:
- New TextLayer/TextContent (text_layer.rs), wired into AnyLayer/LayerType
  and all the exhaustive match sites; structured so content can be keyframed
  later via content_at().
- fonts.rs: thread-local parley FontContext with three bundled fonts
  (Liberation Sans/Serif/Mono, SIL OFL), system-font enumeration consolidated
  to base families, document-embedded fonts, glyph/caret/selection geometry,
  and a background preloader for the picker fonts.
- Rendering via parley layout + Scene::draw_glyphs (renderer.rs); text
  composites through the vector path.
- Actions: CreateTextClipAction (vector-layer branch, undoable),
  SetTextContentAction, ResizeTextBoxAction.
- .beam font embedding: MediaKind::Font rows (content-hash dedupe) written on
  save and registered on load, with bundled-default fallback.
- VectorClip content bounds include text boxes so text-only clips are
  selectable/draggable.

Editor:
- Text tool: click empty/raster/video to create a top-level text layer, or a
  vector layer to create+enter a clip containing the text; click an existing
  box to edit it.
- Hybrid in-place editing: a hidden egui TextEdit drives input/IME/caret while
  the text and caret/selection render in Vello; empty just-created layers are
  removed on commit.
- Selection outline + 8 resize handles (re-wrap text) with hover cursors;
  factored the corner/edge resize-cursor mapping shared with the Transform tool.
- Info panel: edit text, size, color, alignment, box size, and a font-family
  picker that previews each entry in its own font (fonts preloaded in the
  background to avoid hitches).

Deps: add parley (git, pinned to match vello's peniko); bundle Liberation
fonts under lightningbeam-core/assets/fonts. Gitignore the local
.cargo/config.toml used to select a machine's ffmpeg.
2026-06-27 18:14:47 -04:00
Skyler Lehmkuhl 3e4f29c297 Wire zero-copy VAAPI H.264 into video+audio export
When exporting H.264 with audio, try the gpu-video-encoder ZeroCopyEncoder:
render each frame to RGBA and hardware-encode it into a VAAPI surface
inline, on the encoder's own VAAPI-capable wgpu device — no GPU->CPU
readback, no swscale, no software-encoder thread. Falls back to the
existing software path verbatim when VAAPI/the device is unavailable
(non-Linux, non-H264, or init failure), so it's additive.

- VideoExportState gains zero_copy: Option<ZeroCopyVideo> (encoder + its own
  vello renderer + ExportGpuResources + a reused RGBA target, all on the
  encoder's device).
- start_video_with_audio_export builds it for H.264 and skips spawning the
  software encoder thread when present.
- render_next_video_frame routes to a zero-copy arm that reuses
  render_frame_to_gpu_rgba on the encoder's device, then encode_rgba; on the
  last frame finish() writes the temp .mp4 and sets video_progress=Complete
  so the existing mux runs. video_thread=None makes the mux join a no-op.

Separate export device (vs modifying the eframe device) keeps this contained
to export. Video-only export stays on the software path for now. Runtime
verification (an actual H.264 export) is pending — cannot run the editor in
the dev container.
2026-06-25 15:44:50 -04:00
Skyler Lehmkuhl 5917ce7921 Add gpu-video-encoder crate: zero-copy VAAPI encode (validated)
New workspace crate isolating the unsafe GPU<->encoder interop for
zero-copy hardware video encoding. Every link is validated by a test on
real Intel/Mesa/iHD hardware:

- nv12: GPU RGBA->NV12 compute (BT.709 full-range), byte-exact vs a CPU
  reference.
- vaapi: VAAPI hwcontext + h264_vaapi encode (CPU-fed NV12 -> valid H.264),
  and DRM-PRIME surface layout probing.
- vk_device: a custom wgpu Vulkan device that adds
  VK_EXT_image_drm_format_modifier (+ external-memory fd/dma-buf) via the
  wgpu-hal device-from-raw path, so a tiled VAAPI surface can be imported.
- dmabuf: import a VAAPI NV12 surface's tiled DMA-BUF as two aliasing wgpu
  textures (Y=R8, UV=RG8) at the plane offsets.
- zerocopy test: render values via Vulkan straight into the VAAPI surface
  and read them back 100% correct -- proving the GPU writes into the
  encoder surface with no CPU copy.

Not yet wired into the editor; real-frame render + encode-from-surface +
fallback wiring follow. Linux-only (libva); other platforms fall back.
2026-06-23 19:07:37 -04:00
Skyler Lehmkuhl ce151ffd61 Pack video into .beam and stream frames + audio from the blob
Video was the only media type always kept external (VideoClip.file_path),
so a project with video wasn't self-contained. Now video packs into the
SQLite container under the same large-media policy as audio (pack < 2 GB
unless the user chose Reference), and both the frames and the embedded audio
track decode by streaming directly from the blob — no temp files.

- New crate ffmpeg-blob-io: an AVIOContext-over-Read+Seek shim (BlobInput)
  that lets ffmpeg demux from an arbitrary byte source. Isolates all the
  unsafe FFI + ffmpeg ABI coupling (version-pinned =8.0.0/=8.0.1). Manual
  Drop teardown order; AVSEEK_SIZE restores the read position (FFmpeg assumes
  a size query doesn't move it — required for MP4 moov-at-end).
- Schema/save/load: VideoClip.media_id; save_beam packs/references video as
  MediaKind::Video (keyed by clip id); load resolves packed vs referenced and
  reports missing sources. A packed clip points its linked video-audio pool
  entry's media_id at the video row so the audio streams from the same blob.
- Frames: video.rs VideoSource{Path,Packed} threaded through new/seek/scan/
  probe/thumbnails (a fresh BlobReader per open); editor builds the source
  from current_file_path (now set before register_loaded_videos).
- Audio: VideoAudioReader::open_source via BlobInput; the disk_reader
  StreamSource block on packed video-audio is removed; the engine's existing
  factory activation routes it unchanged.

Tests: ffmpeg-blob-io AVIO unit tests (WAV via Cursor, seek, open/drop loop);
core packed_video_stream (blob->AVIO->Input) and beam_archive video round-trip;
daw-backend open_source test (compiles; links/runs only off-container).
Runtime-verified: a packed video plays frames + audio after the source file
is removed.
2026-06-22 09:17:10 -04:00
Skyler Lehmkuhl 3d7cff9ad0 Stream audio & video from .beam container; waveform LOD pyramid + persistence
Migrate the .beam container to SQLite and stream media from it instead of
decoding whole files into RAM on import/load.

Container & large files:
- SQLite .beam container (beam_archive) with in-place transactional saves and an
  incremental BlobReader; supports both packed (chunked blobs) and referenced
  (external path) media, with a user preference + first-import prompt for files
  over the large-media threshold.

Audio streaming:
- Stream packed compressed audio on load via an inversion-of-control blob factory
  (AudioBlobSourceFactory): daw-backend defines the trait, core implements it
  over BlobReader, so the audio engine stays container-agnostic.
- Bulk-activate disk streaming for all loaded clips after SetProject.
- Sample-accurate compressed seek (SeekMode::Accurate; Coarse mislands on VBR).

Video:
- Video frames decoded/streamed on demand; thumbnails generated asynchronously
  on a dedicated decoder so import/load never blocks the UI.
- The video's audio track is streamed on demand via an ffmpeg VideoAudioReader
  as a separate editable AudioClip (no /tmp WAV extraction).

Waveform overview:
- Streaming min/max LOD pyramid (waveform_pyramid), bounded memory, configurable
  floor B; serialized into the container and restored on load (or generated in
  the background from the packed blob when absent), so no re-decode on reload.
- GPU min/max upload path; integer-LOD textureLoad fixes zoom-dependent wobble.
2026-06-17 13:52:38 -04:00
Skyler Lehmkuhl fa40173562 Add automatable volume and pan control to default instruments 2026-03-23 23:27:05 -04:00
Skyler Lehmkuhl b8f847e167 Add drawing tablet input support 2026-03-11 10:58:30 -04:00
Skyler Lehmkuhl 26f06da5bf Add gradient support to vector graphics 2026-03-10 00:57:47 -04:00
Skyler Lehmkuhl 75e35b0ac6 Don't dump json into text clipboard 2026-03-02 07:30:09 -05:00
Skyler Lehmkuhl f8df4d1232 Add webcam support in video editor 2026-03-01 06:25:43 -05:00
Skyler Lehmkuhl 4c34c8a17d Add snapping for vector editing 2026-02-25 03:29:42 -05:00
Skyler Lehmkuhl 0b4aee51d7 update build yaml 2026-02-24 14:44:17 -05:00
Skyler Lehmkuhl 4a13ce0684 Merge branch 'rust-ui' of https://git.skyler.io/skyler/Lightningbeam into rust-ui 2026-02-24 14:40:19 -05:00
Skyler Lehmkuhl 8301fbfa16 windows build fixes 2026-02-24 14:40:16 -05:00
Skyler Lehmkuhl 196d27bf15 update build yaml 2026-02-24 13:43:52 -05:00
Skyler Lehmkuhl 1cb09c7211 Debug DCEL issues 2026-02-24 08:26:17 -05:00
Skyler Lehmkuhl 99f8dcfcf4 Change vector drawing primitive from shape to doubly-connected edge graph 2026-02-23 21:29:58 -05:00
Skyler Lehmkuhl 7e3f18c95b Add amp sim 2026-02-21 09:43:03 -05:00
Skyler Lehmkuhl c66487b25e Add script node 2026-02-19 09:29:14 -05:00
Skyler Lehmkuhl 2a94ac0f69 Merge branch 'rust-ui' of https://git.skyler.io/skyler/Lightningbeam into rust-ui 2026-02-16 10:06:00 -05:00
Skyler Lehmkuhl 6c10112a16 Fix build on Windows 2026-02-16 10:05:39 -05:00
Skyler Lehmkuhl 0ff651f4a5 Use forked egui to fix wayland/ibus bug 2026-02-16 04:05:59 -05:00
Skyler Lehmkuhl 0bd933fd45 Group nodes 2026-02-16 03:33:32 -05:00
Skyler Lehmkuhl f924b4c0cd Stream audio instead of loading the whole thing into memory 2026-02-11 19:07:48 -05:00
Skyler Lehmkuhl 8ac5f52f28 Render audio waveforms on gpu 2026-02-11 14:38:58 -05:00
Skyler Lehmkuhl fc58f29ccd Record audio 2026-02-11 02:28:37 -05:00
Skyler Lehmkuhl 5fbb2c078b Use audio engine as source of truth for playback time 2025-11-28 11:36:33 -05:00
Skyler Lehmkuhl 9204308033 Transform shapes 2025-11-18 05:08:33 -05:00
Skyler Lehmkuhl 67724c944c Select and move shapes 2025-11-18 00:22:28 -05:00
Skyler Lehmkuhl afda2d9d4f CSS improvements, light and dark mode 2025-11-17 07:42:40 -05:00
Skyler Lehmkuhl 1324cae7e3 Render shape on stage 2025-11-16 02:40:06 -05:00
Skyler Lehmkuhl 08232454a7 Add stage pane with scrolling 2025-11-16 00:01:07 -05:00
Skyler Lehmkuhl 48da21e062 Toolbar 2025-11-13 18:12:21 -05:00
Skyler Lehmkuhl bf007e774e Add Rust desktop UI with Blender-style pane system
Implemented foundational pane system using eframe/egui:
- Workspace structure with lightningbeam-core and lightningbeam-editor
- Layout data structures matching existing JSON schema
- All 8 predefined layouts (Animation, Video Editing, Audio/DAW, etc.)
- Recursive pane rendering with visual dividers
- Layout switcher menu
- Color-coded pane types for visualization

Foundation complete for interactive pane operations (resize, split, join).
2025-11-12 06:13:00 -05:00