Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
159 commits
Select commit Hold shift + click to select a range
590fcc8
fix(cursor): stop shipping the sweezy-cursors.com packs
EtienneLescot Sep 25, 2026
ba7a7d0
fix(website): stop publishing the sweezy-cursors.com packs
EtienneLescot Sep 25, 2026
b64d6db
fix(website): drop the cursor theme label the recreation no longer draws
EtienneLescot Sep 25, 2026
97039a0
feat(cursor): add five original cursor themes
EtienneLescot Sep 25, 2026
2f1a731
feat(cursor): ship raster themes and 3D modeling direction
EtienneLescot Sep 25, 2026
52fc4ed
docs(cursor): distinguish 2D outlines from 3D materials
EtienneLescot Sep 25, 2026
856a309
feat(cursor): sculpt original cursor themes in 3D
EtienneLescot Sep 25, 2026
6919351
test(cursor): align model coverage with selectable themes
EtienneLescot Sep 26, 2026
b62e6e6
fix(editor): one bounds table in the model, and a reset on every slider
EtienneLescot Sep 25, 2026
70fd14d
fix(editor): keep typed text sizes on unmount; reset only from the bu…
EtienneLescot Sep 26, 2026
cce20db
test(editor): hold the speed and preset fixtures to the new bounds
EtienneLescot Sep 26, 2026
1f30e4d
feat(audio): a soft limiter and -16 LUFS loudness at export
EtienneLescot Sep 25, 2026
e8c3d4c
fix(audio): measure the recording's loudness again when Retry reloads it
EtienneLescot Sep 26, 2026
0ddc5ca
feat(audio): music beds start at -18 dB with 1 s fades and duck under…
EtienneLescot Sep 26, 2026
48714c6
fix(export): pass the bitrate to the encoder and dither the GIF
EtienneLescot Sep 26, 2026
80b4d1f
feat(export): named destinations, detailed settings under Advanced
EtienneLescot Sep 26, 2026
b073961
fix(export): own disclosure class, honest destination docs
EtienneLescot Sep 26, 2026
e484b9c
fix(ai-edition): silence cuts keep breath next to kept speech
EtienneLescot Sep 26, 2026
b69d121
feat(editor): new projects start from your look, and a preset never c…
EtienneLescot Sep 26, 2026
b16491d
fix(compositor): size cursor, shadow and corners by the screen, not t…
EtienneLescot Sep 26, 2026
9c1cb43
fix(compositor): give text annotations a symmetric 300 ms exit
EtienneLescot Sep 26, 2026
123d1e9
fix(compositor): keep pulse at its full duration in short regions
EtienneLescot Sep 26, 2026
220e894
fix(cli): take auto zoom and caption bounds from the editor's own paths
EtienneLescot Sep 26, 2026
6aa3c34
fix(hud): say what the system cursor turns off
EtienneLescot Sep 26, 2026
79849cd
fix(cli): measure the caption inset to the plate, not the region
EtienneLescot Sep 26, 2026
eea646e
fix(cli): place captions in the editor's bottom-anchored caption box
EtienneLescot Sep 26, 2026
d8b6b1e
fix(hud): no editable-cursor promise when capture falls back to the b…
EtienneLescot Sep 26, 2026
cd825dd
fix(zoom): grey out zoom levels that would blur the recording, and bo…
EtienneLescot Sep 26, 2026
68b5c3d
fix(zoom): step over disabled levels by keyboard, and limit a zoom by…
EtienneLescot Sep 26, 2026
707bcd6
fix(zoom): point the zoom row at the visible reason its levels are gr…
EtienneLescot Sep 26, 2026
5cd57f9
fix(text): embed the offered fonts so the render matches the picker
EtienneLescot Sep 26, 2026
2fc19a6
fix(text): make the shipped fonts win over installed ones of the same…
EtienneLescot Sep 26, 2026
dbd82e7
fix(compositor): tilted zooms that never roll and carry motion blur
EtienneLescot Sep 25, 2026
6317c6b
fix(compositor): mask the tilted blur trail, trail from the previous …
EtienneLescot Sep 26, 2026
fe1054c
fix(compositor): steep roll-free Left and Right, iso folded into Left
EtienneLescot Sep 26, 2026
ba62acd
fix(compositor): render every gradient stop at its offset, not just t…
EtienneLescot Sep 26, 2026
c71646e
feat(editor): curated backgrounds first, one-colour gradient behind C…
EtienneLescot Sep 26, 2026
ee32df3
fix(compositor): keep Linux gradients opaque when the first stop is at 0
EtienneLescot Sep 26, 2026
6296476
fix(compositor): premultiply translucent gradient stops
EtienneLescot Sep 26, 2026
0f1c42d
fix(editor): keep a hex draft in the Custom background rows
EtienneLescot Sep 26, 2026
e92c70b
feat(recording): keep notifications out on macOS, opt-in Hide desktop…
EtienneLescot Sep 26, 2026
dd7c500
fix(capture): build the macOS helper again, and own the cover's promi…
EtienneLescot Sep 26, 2026
3cbb5ff
feat(editor): text that cannot be made unreadable
EtienneLescot Sep 26, 2026
7f22913
fix(captions): offer turning captions on only when the transcript has…
EtienneLescot Sep 26, 2026
2ee1a97
fix(editor): defaults that look good without touching anything
EtienneLescot Sep 25, 2026
358389e
feat(editor): name the cursor size, click bounce and shadow instead o…
EtienneLescot Sep 26, 2026
5868677
fix(compositor): shrink the webcam toward its anchor and give auto-fo…
EtienneLescot Sep 25, 2026
6295427
fix(compositor): cache the follow view per region, settle past the la…
EtienneLescot Sep 26, 2026
576a641
fix(compositor): bound auto-follow cache to region
EtienneLescot Sep 26, 2026
51fc974
feat(format): fill a fixed format with a window that follows the cursor
EtienneLescot Sep 26, 2026
050bdbb
fix: stabilize audit integration checks
EtienneLescot Sep 26, 2026
19f7a0d
fix(editor): resolve contrast helper in main build
EtienneLescot Sep 26, 2026
4515917
fix(editor): keep slider header height stable when the reset button a…
EtienneLescot Sep 26, 2026
b150cf4
refactor(editor): drop explanatory hints, hide inoperative settings
EtienneLescot Sep 26, 2026
f114acb
style(timeline): candy colours for clips, pills and the pan/zoom bar
EtienneLescot Sep 26, 2026
da48ede
style(timeline): candy borders in their own colour, not darkened
EtienneLescot Sep 26, 2026
2dbf569
style(timeline): fainter candy glow on the light theme
EtienneLescot Sep 26, 2026
728d077
style(timeline): no border and no gloss line on the candy shapes
EtienneLescot Sep 26, 2026
5287621
style(timeline): tiny-pill handles in the lane colour
EtienneLescot Sep 26, 2026
fe04a3b
fix(captions): open caption settings in place of the transcript pane
EtienneLescot Sep 26, 2026
eab7eb3
feat(camera): show the camera background on the crop thumbnail
EtienneLescot Sep 26, 2026
cc295b3
feat(effects): roundness under padding, hidden and reset at zero padding
EtienneLescot Sep 26, 2026
2b3ebfe
feat(captions): pick the caption position from the camera-style ancho…
EtienneLescot Sep 26, 2026
2d6d198
refactor(editor): hide zoom levels that would blur instead of explain…
EtienneLescot Sep 26, 2026
41df656
fix(editor): drop the zoom level row when no preset is within reach
EtienneLescot Sep 26, 2026
547ccc7
fix(timeline): make the clip edit button hover stand out from the clip
EtienneLescot Sep 26, 2026
b14bebe
fix(cursor): preview each theme by its main arrow only, centered
EtienneLescot Sep 26, 2026
83fcf51
refactor(editor): remove the previous/next clip transport buttons
EtienneLescot Sep 26, 2026
43a6783
fix(zoom): put the custom level on the preset row, drop the blur hint
EtienneLescot Sep 26, 2026
5e6bbf2
feat(inspector): pick the right panel's fixed choices in one click
EtienneLescot Sep 26, 2026
5f3e6c3
feat(cursor): a size slider up to four times the default
EtienneLescot Sep 26, 2026
8ae975a
fix(compositor): Left back to the v1.13.0 iso, Right its mirror
EtienneLescot Sep 26, 2026
262326d
feat(cursor): sculpt the original themes' 3D arrow and hand
EtienneLescot Sep 26, 2026
d6b46ed
fix(compositor): motion-blur the framed screen as one object under a …
EtienneLescot Sep 26, 2026
2a8a757
fix(effects): steady padding drag, roundness kept through zero padding
EtienneLescot Sep 26, 2026
1cb19b1
refactor(effects): remove the Fit button
EtienneLescot Sep 26, 2026
625ac8e
fix(preview): keep the last frame up while an Auto format reshapes th…
EtienneLescot Sep 26, 2026
4ed5efa
fix(preview): drop a frame whose bitmap lands after a newer one's
EtienneLescot Sep 26, 2026
b469cb5
fix(compositor): resize the preview in place instead of rebuilding th…
EtienneLescot Sep 26, 2026
b4fda37
fix(compositor): record the cursor file when playback auto-advances t…
EtienneLescot Sep 26, 2026
f77e8ce
feat(annotations): place annotations anywhere on the frame, boxes tha…
EtienneLescot Sep 26, 2026
5a075b0
fix(annotations): keep a refitted annotation on the frame, convert on…
EtienneLescot Sep 26, 2026
8e386ef
fix(compositor): make the frame the container in block layouts
EtienneLescot Sep 26, 2026
94b8fa1
test(compositor): probe the frame body itself in the motion-blur trai…
EtienneLescot Sep 26, 2026
e271d00
fix(effects): hold the Roundness row through a drag up from 0 too
EtienneLescot Sep 26, 2026
d125fc8
fix(slider): end the drag on a cancelled touch too
EtienneLescot Sep 26, 2026
69268fd
style(timeline): the clip back in the brand's green, zooms in blue
EtienneLescot Sep 26, 2026
bd1fb09
style(edit-clip): draw the kept range as the clip's candy card
EtienneLescot Sep 26, 2026
088351e
refactor(editor): remove the transport seek bar
EtienneLescot Sep 26, 2026
f313d83
chore(website): regenerate recreation data for the new cursor hotspots
EtienneLescot Sep 26, 2026
4d071a7
feat(cursor): move click impact to the cursor settings, for every camera
EtienneLescot Sep 26, 2026
388804a
fix(migrate): lift a v2 zoom's click impact into the cursor setting
EtienneLescot Sep 26, 2026
e0f806a
fix(background): make the gradient motions visible at a glance
EtienneLescot Sep 26, 2026
e45b162
feat(zoom): give the 3D Orbit camera the zoom's focus mode
EtienneLescot Sep 26, 2026
97b15c0
fix(zoom): offer 3D Orbit with the cursor hidden when the focus is ma…
EtienneLescot Sep 26, 2026
3eea820
fix(camera): shrink half as much in a zoom, size from 15 to 50%
EtienneLescot Sep 26, 2026
7abf744
fix(compositor): keep the cursor's size at the zoom focus under Left …
EtienneLescot Sep 26, 2026
738b678
feat(cursor): pick the cursor types drawn as recorded, 3D under the s…
EtienneLescot Sep 26, 2026
09710c5
fix(cursor): arrow and hand by default, bigger centred cursor previews
EtienneLescot Sep 26, 2026
e8389ab
fix(cursor): read a cursor file again after a failed read
EtienneLescot Sep 26, 2026
f0b06f1
feat(background): animate image wallpapers with the same three motions
EtienneLescot Sep 26, 2026
acb473a
style(timeline): centre the transport in the header row
EtienneLescot Sep 26, 2026
1d36020
fix(layout): measure the padding from the device frame's outer edge
EtienneLescot Sep 26, 2026
2805d79
docs(compositor): drop the retired undistort pipeline from the comments
EtienneLescot Sep 26, 2026
7ebe84f
docs(compositor): name where OUT_W and OUT_H still place things
EtienneLescot Sep 26, 2026
0f1b4d4
fix(zoom): let 3D Orbit frame the screen's corners like the other cam…
EtienneLescot Sep 26, 2026
2042df9
test(compositor): pin the orbit's corner framing on both axes, both ways
EtienneLescot Sep 26, 2026
ab549d7
fix(cursor): scale the 3D cursor's click press to the named bounce le…
EtienneLescot Sep 26, 2026
a10c65e
fix(cursor): put click impact right under click bounce
EtienneLescot Sep 26, 2026
9fb20cf
fix(roundness): only square the corners at 0% padding without a frame
EtienneLescot Sep 26, 2026
20b7d23
feat(background): blur the wallpaper by an amount instead of a switch
EtienneLescot Sep 26, 2026
2217424
fix(background): carry an old blur switch through reverse migration a…
EtienneLescot Sep 27, 2026
36c8d39
fix(cursor): let the bounce level drive the 3D cursor's whole click g…
EtienneLescot Sep 27, 2026
e567741
fix(cursor): no click aim at None, fail the levels video on an ffmpeg…
EtienneLescot Sep 27, 2026
bad97f0
fix(zoom): frame the zoom window like a flat zoom under 3D Orbit
EtienneLescot Sep 27, 2026
94d8ea9
test(compositor): follow bg_blur's switch to an amount in the render …
EtienneLescot Sep 27, 2026
af30991
fix(cursor): dive the 3D cursor onto the click, in depth, over ~20 fr…
EtienneLescot Sep 27, 2026
b942783
test(compositor): measure the click ring past the tip the dive moves
EtienneLescot Sep 27, 2026
33affab
docs(website): answer the FAQ's Homebrew question with the official tap
EtienneLescot Sep 27, 2026
c7b4d08
fix(export): size a GIF from the source, not the hidden MP4 tier
EtienneLescot Sep 27, 2026
6c0dbbd
fix(homebrew): require macOS 13 in the cask, like the app
EtienneLescot Sep 27, 2026
272ab1f
feat(macos): record system audio from a Core Audio tap with Apple's p…
EtienneLescot Sep 24, 2026
05ba5dd
fix(macos): release a half-built system-audio tap, and say when it fa…
EtienneLescot Sep 25, 2026
2b17728
feat(rec): let auto-zoom after recording be turned off again
loburets Sep 26, 2026
118459f
fix(rec): bound the prefs read, explain the disabled row, reconcile a…
loburets Sep 26, 2026
ac8e623
fix(rec): hide the auto-zoom row under the system cursor
EtienneLescot Sep 27, 2026
7164b9e
fix(rec): let a newer prefs snapshot win over the failed-write re-read
EtienneLescot Sep 27, 2026
6af1936
fix(rec): leave the auto-zoom hand-off alone across a project switch
EtienneLescot Sep 27, 2026
74ffc41
fix(i18n): only check calls to translators, not every name starting w…
EtienneLescot Sep 27, 2026
fcfc691
fix(recording): offer no microphone on macOS 13 and 14
EtienneLescot Sep 27, 2026
03915a2
fix: ignore hidden cursor telemetry in auto-zoom
NoahSeeger Sep 22, 2026
3a6e375
fix: honor recorded cursor visibility in compositor
NoahSeeger Sep 22, 2026
3c38512
fix: preserve cursor visibility at clip offsets
NoahSeeger Sep 22, 2026
c4fb7ed
fix(compositor): hide every cursor layer while hidden, not a visible …
EtienneLescot Sep 27, 2026
016e97f
fix(auto-zoom): judge a press without its own mouse-up by the grace w…
EtienneLescot Sep 27, 2026
b9b559a
test(compositor): check the tilted cursor model follows the plane's p…
EtienneLescot Sep 27, 2026
b86449f
feat(toasts): give a toast a cross to close it, inside its top end co…
NICOLASGON Sep 15, 2026
159c982
fix(toasts): colour the close button from the theme tokens
EtienneLescot Sep 27, 2026
ec4c5a7
fix(toasts): keep the close button's border off when it is hovered
NICOLASGON Sep 16, 2026
16534f0
feat(website): redraw the scroll walkthrough in the editor's new dire…
EtienneLescot Sep 27, 2026
37c62a8
docs(blog): making of v2, a demo is never ugly
EtienneLescot Sep 27, 2026
23d5962
docs(design): align the design system with the editor's new direction
EtienneLescot Sep 27, 2026
a5f7156
docs(website): describe the current editor UI, in every locale
EtienneLescot Sep 27, 2026
619fb0e
docs(website): current speed limits and the 3D camera in the demo gui…
EtienneLescot Sep 27, 2026
9729492
docs: say auto zooms are placed after every take
EtienneLescot Sep 27, 2026
3c05d31
docs: restore the French pages' question marks and semicolons
EtienneLescot Sep 27, 2026
6c144fe
fix(compositor): drop unsolvable trail taps and keep depth of field o…
EtienneLescot Sep 27, 2026
41872b6
style(theme): set the light shell apart with a greyer canvas and visi…
EtienneLescot Sep 27, 2026
a7d03fd
docs: bring the computer-use e2e pass up to date with v2
EtienneLescot Sep 27, 2026
3688e89
docs(build): drop the note that says the macOS job is disabled
EtienneLescot Sep 27, 2026
2acb984
docs(website): describe v2's macOS permissions on the install page
EtienneLescot Sep 27, 2026
0a51741
fix(auto-zoom): keep the video's first and last seconds wide
EtienneLescot Sep 27, 2026
0da75de
fix(auto-zoom): drop the pointer's rest on Stop along with the Stop c…
EtienneLescot Sep 27, 2026
b639cf1
feat(auto-zoom): plan zooms as shots from recorded clicks
EtienneLescot Sep 27, 2026
274993a
docs(website): automatic zooms follow the recorded clicks, in every l…
EtienneLescot Sep 27, 2026
7342799
docs(website): say zooms land on clicks everywhere a page names them
EtienneLescot Sep 27, 2026
f167122
fix(compositor): keep the camera's corners until Full Camera reaches …
EtienneLescot Sep 27, 2026
85605a8
docs(website): German docs take the app's German labels
EtienneLescot Sep 27, 2026
e8b51be
docs(website): align macOS permission steps with the install page
EtienneLescot Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/update-homebrew-cask.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ jobs:
homepage "https://github.com/${REPO}"

auto_updates false
depends_on macos: ">= :big_sur"
depends_on macos: ">= :ventura"

app "Openscreen.app"

Expand Down
38 changes: 22 additions & 16 deletions AGENTS.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ A raw capture goes in and comes out ready to post — on X, Reddit, YouTube, a d
- Record microphone and system audio.
- Webcam overlay with picture-in-picture, eight snap positions, adjustable roundness, mirroring, and shape options.
- Auto or manual zooms with adjustable depth, duration, easing, and pixel-precise position; auto-zoom follows your cursor as you work.
- Custom cursor size, smoothing, and click effects, with cursor themes and post-recording path smoothing.
- Custom cursor size, smoothing, and click effects, with post-recording path smoothing.
- Automatic captions for voiceovers, transcribed on-device with no upload (works offline), with an editable transcript you can cut from and optional subtitle translation.
- AI editing assistant: describe the edit you want in chat and it applies to the timeline — cuts, zooms, speed ramps, annotations, camera framing. Bring your own key (Claude, OpenAI, Gemini, Mistral, OpenRouter, MiniMax, or any OpenAI-compatible endpoint); nothing is enabled by default.
- Wallpapers, solid colors, gradients, or your own background image.
Expand Down Expand Up @@ -176,7 +176,7 @@ You may need to grant screen recording permissions depending on your desktop env
Everything in the editor and export is the same on macOS, Windows, and Linux: zooms, backgrounds, motion blur, crop/trim/speed, blur regions, annotations, auto-captions, AI editing, projects, export, and all languages. All three now record through a native capture pipeline; the remaining differences are narrower than they used to be:

- **Native recording**: macOS (ScreenCaptureKit), Windows (Windows Graphics Capture), and Linux (PipeWire via the ScreenCast portal) all record through a native pipeline for higher quality and clean window-level capture. On Linux the browser pipeline stays as an automatic fallback if the helper isn't available.
- **Custom cursors**: on macOS and Windows the real cursor is captured with shape, type, and clicks. Linux captures position and cursor shape through the portal, so cursor themes and the editable cursor overlay work there too. Click effects work on Linux as well, but not through the portal — Wayland exposes no portal for mouse buttons, so the capture helper reads the left button from evdev, which needs your user in the `input` group. Without that, recording is unaffected and every cursor sample is simply a move.
- **Custom cursors**: on macOS and Windows the real cursor is captured with shape, type, and clicks. Linux captures position and cursor shape through the portal, so the editable cursor overlay works there too. Click effects work on Linux as well, but not through the portal — Wayland exposes no portal for mouse buttons, so the capture helper reads the left button from evdev, which needs your user in the `input` group. Without that, recording is unaffected and every cursor sample is simply a move.
- **Webcam**: every platform saves the webcam as a separate file next to the screen recording. Windows captures it natively in the recording helper; macOS and Linux record it in the app. It works as a picture-in-picture overlay on all three.
- **System audio** support varies by OS:
- **macOS**: works on every supported version. On macOS 14.2+ you'll be prompted to grant audio capture permission.
Expand Down
24 changes: 24 additions & 0 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,30 @@ distributed by their own registries, not redistributed inside our binaries.
so nothing of PipeWire's ships inside our installers beyond the compiled
result of its headers (inline functions and struct layouts).

## Fonts — Inter, Lora, Oswald, Caveat, IBM Plex Mono

- **Components**: the Regular and Bold `.ttf` of each family, under
`resources/fonts/`.
- **Used by**: the native compositor, which draws captions and annotations with
these files and nothing else (it registers them privately at start and never
reads the fonts installed on the machine), and the editor's font picker.
- **License**: **SIL Open Font License 1.1** — <https://openfontlicense.org>.
Each family's copyright notice and licence text ship beside its files as
`<Family>-OFL.txt`, as the licence requires.
- **Unmodified**: every file is redistributed exactly as upstream publishes it,
which is also why the Reserved Font Names "Lora" and "Plex" are kept.
- **Upstream**:
- Inter — Copyright 2016 The Inter Project Authors. `extras/ttf/` of the v4.1
release, <https://github.com/rsms/inter/releases/tag/v4.1>.
- Lora 3.021 — Copyright 2011 The Lora Project Authors. `fonts/ttf/` of
<https://github.com/cyrealtype/Lora-Cyrillic> at `2d53b449b6`.
- Oswald 4.103 — Copyright 2016 The Oswald Project Authors. `fonts/ttf/` of
<https://github.com/googlefonts/OswaldFont> at `89795261ac`.
- Caveat 2.000 — Copyright 2014 The Caveat Project Authors. `fonts/ttf/` of
<https://github.com/googlefonts/caveat> at `59745e818e`.
- IBM Plex Mono 2.3 — Copyright © 2017 IBM Corp. `ofl/ibmplexmono/` of
<https://github.com/google/fonts> at `23e54b51dd`.

## OpenScreen native helpers

`wgc-capture` (Windows Graphics Capture), the ScreenCaptureKit helper (macOS),
Expand Down
100 changes: 90 additions & 10 deletions crates/compositor-view-napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,56 @@ pub fn segmentation_runtime_available() -> bool {
openscreen_compositor::segmentation::runtime_available()
}

pub struct SegmentFrameTask {
model_path: String,
rgba: Vec<u8>,
}

impl Task for SegmentFrameTask {
type Output = Vec<u8>;
type JsValue = Buffer;

fn compute(&mut self) -> Result<Self::Output> {
let rgb: Vec<u8> = self
.rgba
.chunks_exact(4)
.flat_map(|p| [p[0], p[1], p[2]])
.collect();
// ponytail: une session ONNX chargée par appel (quelques dizaines de ms), parce qu'il
// n'y a qu'un appel par caméra et par ouverture du panneau. À garder en cache si un
// appelant se met à segmenter en continu.
let mut segmenter = openscreen_compositor::segmentation::Segmenter::load(
std::path::Path::new(&self.model_path),
)
.map_err(|e| Error::from_reason(format!("{e:#}")))?;
segmenter
.run(&rgb)
.map(<[u8]>::to_vec)
.map_err(|e| Error::from_reason(format!("{e:#}")))
}

fn resolve(&mut self, _env: Env, mask: Self::Output) -> Result<Self::JsValue> {
Ok(mask.into())
}
}

/// Masque du sujet pour UNE image, sans vue : la vignette de recadrage du panneau caméra s'en
/// sert pour montrer le fond choisi sur sa propre frame.
///
/// `rgba` est une frame RGBA8 déjà réduite à la taille du modèle (`segmentation::MODEL_WIDTH` x
/// `MODEL_HEIGHT`), soit le `getImageData` d'un canvas tel quel. Le retour a le format de
/// `Segmenter::run` : un octet par pixel, 0 = fond, 255 = sujet.
///
/// `AsyncTask`, comme `remux_seekable` : charger la session prend plus longtemps que l'inférence,
/// et ce temps n'a rien à faire sur le thread principal de Node.
#[napi]
pub fn segment_frame(model_path: String, rgba: Buffer) -> AsyncTask<SegmentFrameTask> {
AsyncTask::new(SegmentFrameTask {
model_path,
rgba: rgba.to_vec(),
})
}

#[napi]
pub fn create_view(
rect: CompositorViewRect,
Expand Down Expand Up @@ -198,7 +248,7 @@ pub fn read_frame(id: i32, since_gen: f64) -> Result<Option<FramePacket>> {
}

/// Param live (inspector). Le type de valeur route vers le bon setter :
/// bool = switch (backgroundBlur…), number = slider (shadow/roundness/motionBlur),
/// bool = switch (webcamMirror…), number = slider (shadow/roundness/motionBlur/backgroundBlur),
/// string = sélection (backgroundColor "#rrggbb").
#[napi]
pub fn set_param(id: i32, key: String, value: Either3<bool, f64, String>) {
Expand Down Expand Up @@ -442,6 +492,9 @@ pub struct ExportParamsInput {
/// "h264" | "h265". Toute autre valeur (ex. "vp9", pas d'équivalent matériel AMF) fait
/// échouer l'export avec un message clair plutôt que de silencieusement retomber sur h264.
pub codec: Option<String>,
/// Débit vidéo visé, en bits/s, calculé par l'app d'après la taille et la cadence.
/// Absent ou nul → le repli du pipeline, qui ignore la cadence.
pub bitrate: Option<u32>,
}

/// Export multiclip mesuré (worker libuv). Rend la vraie timeline (clips + trims) en un MP4.
Expand Down Expand Up @@ -496,6 +549,7 @@ impl Task for ExportMultiTask {
export_params.height = height;
if let Some(p) = &self.params {
export_params.fps = p.fps;
export_params.bit_rate = p.bitrate.filter(|&b| b > 0).map(i64::from);
if let Some(codec) = &p.codec {
export_params.codec = match codec.as_str() {
"h264" => pipeline::ExportCodec::H264,
Expand Down Expand Up @@ -579,21 +633,18 @@ pub fn export_multi(
}

/// Sortie GIF native (slice 1) — taille, cadence, compteur de loop, dithering.
/// Tout optionnel : absent → 854×480, 12 fps, boucle infinie, pas de
/// dithering. Les défauts sont choisis pour un export « petit / net » :
/// GIF est un format 256-couleurs, 12 fps est la cadence historique de
/// `gif.js` côté renderer, et 854×480 tient confortablement dans la
/// palette 8 bits sans banding visible sur du contenu de présentation.
/// Tout optionnel : absent → 854×480, 12 fps, boucle infinie, dithering
/// Floyd-Steinberg. GIF est un format 256-couleurs et 12 fps est la cadence
/// historique de `gif.js` côté renderer.
#[napi(object)]
pub struct GifParamsInput {
pub width: Option<u32>,
pub height: Option<u32>,
pub fps: Option<u32>,
/// Compteur de loop GIF : `None` ou `0` = infini, sinon `n` boucles.
pub loop_count: Option<u16>,
/// Floyd-Steinberg error diffusion avant quantification. Off par
/// défaut (qualité acceptable sans, et ça double تقريبًا le coût
/// CPU du quantize par frame).
/// Floyd-Steinberg error diffusion à la quantification. Actif par défaut :
/// sans lui, un fond en dégradé se découpe en bandes (cf. `GifExportParams`).
pub dither: Option<bool>,
}

Expand Down Expand Up @@ -759,7 +810,7 @@ pub fn export_gif(
height: p.height,
fps: p.fps,
loop_count: p.loop_count,
dither: p.dither.unwrap_or(false),
dither: p.dither.unwrap_or(GifExportParams::default().dither),
})
.unwrap_or_default();
Ok(AsyncTask::new(ExportGifTask {
Expand Down Expand Up @@ -824,3 +875,32 @@ pub fn remux_seekable(input_path: String, output_path: String) -> AsyncTask<Remu
output_path,
})
}

pub struct LoudnessGainTask {
path: String,
}

impl Task for LoudnessGainTask {
type Output = f64;
type JsValue = f64;

fn compute(&mut self) -> Result<Self::Output> {
Ok(f64::from(openscreen_compositor::audio::loudness_gain_db(&self.path)))
}

fn resolve(&mut self, _env: Env, out: Self::Output) -> Result<Self::JsValue> {
Ok(out)
}
}

/// Le gain de normalisation de loudness (dB) que l'export applique à ce fichier voix — le
/// même nombre, par la même fonction, pour que la preview de l'éditeur joue la voix au
/// niveau où l'export l'écrira. Voir `openscreen_compositor::audio::loudness_gain_db`.
///
/// `AsyncTask` : la mesure décode tout l'audio du fichier, ce qui se compte en secondes sur
/// un long enregistrement. Le résultat est mis en cache dans le processus, donc l'export qui
/// suit ne le refait pas.
#[napi]
pub fn loudness_gain_db(path: String) -> AsyncTask<LoudnessGainTask> {
AsyncTask::new(LoudnessGainTask { path })
}
Loading
Loading