Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
164 commits
Select commit Hold shift + click to select a range
1f15dc3
feat: add Cap Pro Loom concierge migration
richiemcilroy Sep 21, 2026
8df6730
fix: register Loom migration emails
richiemcilroy Sep 21, 2026
1b6cb9d
fix: coordinate concierge import completion
richiemcilroy Sep 21, 2026
e9fb851
fix: cover Loom concierge link in importer fixture
richiemcilroy Sep 21, 2026
5260526
fix: recover interrupted Loom concierge imports
richiemcilroy Sep 21, 2026
0f4fe25
fix: validate Loom video IDs in server requests
richiemcilroy Sep 21, 2026
5b2bc8a
fix: account for long Loom concierge imports
richiemcilroy Sep 21, 2026
1a285b2
perf(desktop-gpui): add a timeline draw benchmark for the editor window
ayagmar Oct 13, 2026
c07fd65
perf(desktop-gpui): cache tessellated timeline waveforms between repa…
ayagmar Oct 13, 2026
6810d18
feat(mask): implement blur scissor logic and optimise rendering pipeline
viralcodex Oct 13, 2026
6a23daf
test(mask): add blur scissor pixel tests and utility function
viralcodex Oct 13, 2026
7e14896
Add initial implementation for mask scissor benchmarking
viralcodex Oct 13, 2026
d11c05f
perf(recording): blend the Linux Instant camera into the screen frame…
wleeaf Oct 13, 2026
7a7ab58
perf(desktop-gpui): rebuild only changed screenshot privacy masks
flamboh Oct 14, 2026
63593ed
fix(desktop-gpui): retain duplicate-id screenshot masks safely
flamboh Oct 14, 2026
a2b578c
fix(desktop-gpui): coalesce screenshot mask overlay builds
flamboh Oct 14, 2026
6d80438
fix(video-decode): read past Media Foundation samples that carry no f…
cesarfrancots Oct 14, 2026
ba1f2c2
refactor(rendering): drive the Media Foundation decoder from one requ…
cesarfrancots Oct 14, 2026
9d0ef5a
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 14, 2026
db5c227
perf(gpui): skip unused PCM preparation for video-only exports
EfeDurmaz16 Oct 14, 2026
0702ab6
test(gpui): benchmark complete video-only exports
EfeDurmaz16 Oct 14, 2026
b8175c1
Merge remote-tracking branch 'origin/main' into perf/gpui-video-only-…
EfeDurmaz16 Oct 14, 2026
b0fb820
fix(bench): stream export alpha validation in bounded chunks
EfeDurmaz16 Oct 14, 2026
f8742cd
Merge branch 'main' into blur-gpu-perf
viralcodex Oct 14, 2026
ad930c3
perf(video-decode): pipeline the Media Foundation GPU readback
cesarfrancots Oct 14, 2026
adb268d
fix(scripts): reject broken exports and start benchmarks inside their…
cesarfrancots Oct 14, 2026
80a038e
perf(rendering): reshape the keyboard overlay only when its text changes
Oct 13, 2026
4577e9d
perf(rendering): reshape captions only when their text or style changes
Oct 13, 2026
32eb6ca
test(rendering): check the keyboard layout cache against font and siz…
Oct 14, 2026
a496dab
test(rendering): check the caption layout cache against style and siz…
Oct 14, 2026
563c686
perf(editor): keep the preview updating while scrubbing
Oct 14, 2026
8d04dac
perf(desktop-gpui): repaint only the timeline and transport on hover
Oct 14, 2026
5ee0eb7
perf(editor): time the scrub benchmark from the final request
Oct 14, 2026
094120c
perf(desktop-gpui): let the hover bench yield between overloaded moves
Oct 14, 2026
4a842f7
fix(editor): catch early final frames and odd inputs in the scrub ben…
Oct 14, 2026
46fe21b
perf(desktop-gpui): note that hover no longer takes the full paused i…
Oct 14, 2026
8e8755f
test(editor): cover when a newer scrub request cancels the frame in f…
Oct 14, 2026
8e64a9d
perf(rendering): index auto-zoom cursor clusters
colinvkim Oct 14, 2026
e1ecebb
refactor(rendering): simplify zoom lookup patch
colinvkim Oct 15, 2026
65d938d
perf(export): encode MP4 export audio with AudioToolbox on macOS
vagxrth Oct 15, 2026
b412951
Merge remote-tracking branch 'origin/main' into perf/screenshot-mask-…
flamboh Oct 16, 2026
67cdf7b
Merge remote-tracking branch 'origin/main' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
3fa67a2
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
941824b
fix(video-decode): hold the Media Foundation sample until its surface…
cesarfrancots Oct 16, 2026
246c17d
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
d6990cc
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
ec11a9b
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
c962140
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
a7ac091
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
40e4b95
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
2f22c03
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
fccc679
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
e908f42
Merge remote-tracking branch 'origin/main' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
9bd0812
Merge remote-tracking branch 'origin/main' into perf/mf-pipelined-rea…
cesarfrancots Oct 16, 2026
0d5f379
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
8bed61a
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
65a4b7b
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 16, 2026
0c41812
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 16, 2026
04d9156
fix(desktop-gpui): keep the timeline position when a new clip reloads…
Zingzy Oct 17, 2026
ad4041d
Merge remote-tracking branch 'origin/main' into perf/mf-decoder-state…
cesarfrancots Oct 18, 2026
3074903
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 18, 2026
29336d4
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 18, 2026
1e10ac5
Merge branch 'perf/windows-media-pipeline' into perf/mf-decoder-state…
cesarfrancots Oct 18, 2026
878021f
Merge branch 'perf/mf-decoder-state-machine' into perf/mf-pipelined-r…
cesarfrancots Oct 18, 2026
46cb008
Merge branch 'main' into perf/gpui-zoom-cluster-lookup
richiemcilroy Oct 18, 2026
ca12b0b
Merge branch 'main' into perf/gpui-zoom-cluster-lookup
richiemcilroy Oct 18, 2026
482bda4
Merge commit 'bdcbcb033f441e4dc2b06a2a61c826f6e91ef295' into perf/mf-…
cesarfrancots Oct 18, 2026
7a29b0f
Merge commit '482bda438c17a37923678a919ab8006bbb7a87fa' into perf/mf-…
cesarfrancots Oct 18, 2026
8c4630d
Merge commit 'b9c3f08b45e9b774d4e1cbbf798641cedf1b1d47' into perf/mf-…
cesarfrancots Oct 18, 2026
4f2a22c
Merge commit '8c4630dd2d195e763b73d951482f06e57f553223' into perf/mf-…
cesarfrancots Oct 18, 2026
5aa476a
test(rendering): use the indexed cluster lookup in the idle-cursor au…
richiemcilroy Oct 18, 2026
62775a3
Merge branch 'main' into perf/screenshot-mask-cache
richiemcilroy Oct 18, 2026
bb48f6d
Merge branch 'main' into fix/editor-reload-keeps-timeline
richiemcilroy Oct 18, 2026
0812b97
perf(desktop-gpui): avoid decoding offscreen clip thumbnails
AksharP5 Oct 19, 2026
3bc8976
chore(export-benchmark): write runs to a per-process temp dir, drop n…
vagxrth Oct 19, 2026
bca5f2a
Merge remote-tracking branch 'origin/main' into perf/audiotoolbox-exp…
vagxrth Oct 19, 2026
6da638b
Merge branch 'main' into fix/editor-reload-keeps-timeline
richiemcilroy Oct 19, 2026
e4d4ada
Merge commit 'fe2d3a33818dbaff4eee4134b9e8503a9c8a7e1d' into perf/mf-…
cesarfrancots Oct 19, 2026
305c9d2
Merge commit 'e4d4adaf1b72378108e3d53e183f82037868b27e' into perf/mf-…
cesarfrancots Oct 19, 2026
fccb031
Merge commit '8ce135ec573a0009a7f0249edbe6472cb89729e1' into perf/mf-…
cesarfrancots Oct 19, 2026
1abc925
Merge commit 'fccb031ac58d77bf66daf31a0bbe751533f16601' into perf/mf-…
cesarfrancots Oct 19, 2026
e0c3ae2
Merge commit '4bcad56628947af9606c0756f1c797108ea3dbb9' into perf/mf-…
cesarfrancots Oct 19, 2026
f0da581
Merge commit 'e0c3ae2f32e3c914cdc5a72769d6ca9a1a662421' into perf/mf-…
cesarfrancots Oct 19, 2026
4f380cf
Merge branch 'main' into fix/editor-reload-keeps-timeline
richiemcilroy Oct 19, 2026
7509bd3
Merge branch 'main' into perf/screenshot-mask-cache
richiemcilroy Oct 19, 2026
1c15611
Merge branch 'main' into perf/gpui-video-only-export
richiemcilroy Oct 19, 2026
420398b
Merge branch 'main' into perf/linux-camera-compose-in-place
richiemcilroy Oct 19, 2026
d475bc0
Merge branch 'main' into blur-gpu-perf
richiemcilroy Oct 19, 2026
a0f688e
Merge branch 'main' into perf/audiotoolbox-export-aac
richiemcilroy Oct 19, 2026
1920c4c
Merge branch 'main' into perf/gpui-zoom-cluster-lookup
richiemcilroy Oct 19, 2026
80f0d9a
fix(gpui): follow the macOS Reduce Motion setting
richiemcilroy Oct 19, 2026
1487981
style: cargo fmt
richiemcilroy Oct 19, 2026
2e44f9c
Merge remote-tracking branch 'origin/main' into pr-2707-merge
richiemcilroy Oct 19, 2026
b7742b9
test(gpui): pin the clock in the same-second diagnostics report test
richiemcilroy Oct 19, 2026
57df8cd
Merge branch 'main' into perf/keyboard-overlay-shape-cache
richiemcilroy Oct 19, 2026
538f502
Merge branch 'main' into perf/caption-overlay-shape-cache
richiemcilroy Oct 19, 2026
4d1ff9d
Merge branch 'main' into perf/scrub-preview-frames
richiemcilroy Oct 19, 2026
a5c7fb6
Merge branch 'main' into perf/editor-section-invalidation
richiemcilroy Oct 19, 2026
a884fb6
chore: drop the benchmark notes and parity scripts
richiemcilroy Oct 19, 2026
5500a8c
Merge branch 'main' into perf/mf-decoder-state-machine
richiemcilroy Oct 19, 2026
c1b2b1a
Merge branch 'main' into perf/mf-pipelined-readback
richiemcilroy Oct 19, 2026
d6b1a8e
improve(editor): mix export audio from contiguous slices
kyzer1023 Oct 19, 2026
7beebec
improve(audio): inline the stereo export slice mix
kyzer1023 Oct 19, 2026
8fff39b
Merge pull request #2715 from CapSoftware/fix/gpui-diagnostics-test-c…
richiemcilroy Oct 20, 2026
860cb84
Merge pull request #2714 from CapSoftware/fix/gpui-follow-system-redu…
richiemcilroy Oct 20, 2026
5958ce1
Merge pull request #2623 from vagxrth/perf/audiotoolbox-export-aac
richiemcilroy Oct 20, 2026
da9c8d9
Merge pull request #2569 from EfeDurmaz16/perf/gpui-video-only-export
richiemcilroy Oct 20, 2026
d75f5dd
Merge pull request #2599 from colinvkim/perf/gpui-zoom-cluster-lookup
richiemcilroy Oct 20, 2026
cf85ef2
Merge pull request #2707 from AksharP5/perf/visible-clip-thumbnails
richiemcilroy Oct 20, 2026
ca1cec2
Merge pull request #2545 from cesarfrancots/perf/mf-decoder-state-mac…
richiemcilroy Oct 20, 2026
9632913
Merge pull request #2518 from wleeaf/perf/linux-camera-compose-in-place
richiemcilroy Oct 20, 2026
4b52128
Merge pull request #2490 from viralcodex/blur-gpu-perf
richiemcilroy Oct 20, 2026
b544adf
Merge pull request #2525 from notkainoa/perf/keyboard-overlay-shape-c…
richiemcilroy Oct 20, 2026
93cfc99
Merge pull request #2526 from notkainoa/perf/caption-overlay-shape-cache
richiemcilroy Oct 20, 2026
fc57ffd
Merge pull request #2664 from Zingzy/fix/editor-reload-keeps-timeline
richiemcilroy Oct 20, 2026
1e2349c
Merge remote-tracking branch 'origin/main' into pr2583
richiemcilroy Oct 20, 2026
0c0ef19
chore: drop the standalone mask-cache benchmark package
richiemcilroy Oct 20, 2026
e06647b
Merge remote-tracking branch 'origin/main' into pr2533
richiemcilroy Oct 20, 2026
d63bffa
Merge remote-tracking branch 'origin/main' into ed2535
richiemcilroy Oct 20, 2026
aa3c8fb
Merge remote-tracking branch 'origin/main' into ed2542
richiemcilroy Oct 20, 2026
bd38d91
perf(gpui): stop the display link while a window has nothing to draw
richiemcilroy Oct 20, 2026
aed6687
Merge pull request #2583 from cesarfrancots/perf/mf-pipelined-readback
richiemcilroy Oct 20, 2026
e2689c7
Merge pull request #2533 from flamboh/perf/screenshot-mask-cache
richiemcilroy Oct 20, 2026
4643e2b
style: format the gpui dependency test
richiemcilroy Oct 20, 2026
94a99fc
Merge pull request #2535 from notkainoa/perf/scrub-preview-frames
richiemcilroy Oct 20, 2026
4a7f1ea
Merge remote-tracking branch 'origin/main' into ed2542
richiemcilroy Oct 20, 2026
774119a
Merge remote-tracking branch 'origin/main' into perf/gpui-timeline-wa…
richiemcilroy Oct 20, 2026
76f950a
test(gpui): cover when the frame pacer stops and restarts frame requests
richiemcilroy Oct 20, 2026
23bd622
Merge pull request #2542 from notkainoa/perf/editor-section-invalidation
richiemcilroy Oct 20, 2026
38eaaac
Merge pull request #2486 from ayagmar/perf/gpui-timeline-waveform-pat…
richiemcilroy Oct 20, 2026
2a61427
Merge pull request #2725 from CapSoftware/perf/gpui-idle-frame-source
richiemcilroy Oct 20, 2026
0b42795
perf(recording): read the cursor image only on ticks that record an e…
richiemcilroy Oct 20, 2026
2ffe512
fix(gpui): don't draw a hidden window when it wakes from idle
richiemcilroy Oct 20, 2026
5ec4b84
fix(desktop-gpui): hold the automatic update prompt until recording o…
richiemcilroy Oct 20, 2026
ea15792
fix(recording): drop the original media backup once a plain Studio st…
richiemcilroy Oct 20, 2026
6e67426
Merge pull request #2728 from CapSoftware/perf/cursor-lookup-on-events
richiemcilroy Oct 20, 2026
b6a06a0
Merge pull request #2729 from CapSoftware/fix/gpui-pacer-occluded-wake
richiemcilroy Oct 20, 2026
ce71f7a
fix(desktop-gpui): let a manual check or channel change replace a hel…
richiemcilroy Oct 20, 2026
88e159d
fix(desktop-gpui): install the CLI from Settings with the shared cli-…
richiemcilroy Oct 20, 2026
4dc50f3
perf(recording): compare the cursor bitmap before encoding its TIFF w…
richiemcilroy Oct 20, 2026
9bd2a91
fix(gpui): end a frame quietly when its window has already closed
richiemcilroy Oct 20, 2026
aaa0568
fix(recording): keep the backup when it holds a legacy omitted track,…
richiemcilroy Oct 20, 2026
3e83084
fix(desktop-gpui): run the bundled CLI when the AppImage launcher pas…
richiemcilroy Oct 20, 2026
5b56697
fix(recording): read each cursor tick from one snapshot and cache onl…
richiemcilroy Oct 20, 2026
4ab155c
Merge pull request #2730 from CapSoftware/fix/update-prompt-waits-for…
richiemcilroy Oct 20, 2026
a13990e
Merge pull request #2734 from CapSoftware/fix/gpui-closed-window-frame
richiemcilroy Oct 20, 2026
78353ef
fix(recording): allow the macOS-only cursor sample variant elsewhere
richiemcilroy Oct 20, 2026
6e50e18
Merge pull request #2731 from CapSoftware/fix/retire-finalized-studio…
richiemcilroy Oct 20, 2026
f010991
Merge pull request #2732 from CapSoftware/fix/settings-cli-install-sh…
richiemcilroy Oct 20, 2026
45eef9a
perf(gpui): draw one frame per wake while a window is idle instead of…
richiemcilroy Oct 20, 2026
56ddd18
Merge pull request #2733 from CapSoftware/perf/cursor-fingerprint
richiemcilroy Oct 20, 2026
cb12972
Merge pull request #2740 from CapSoftware/perf/gpui-pacer-single-frames
richiemcilroy Oct 20, 2026
0da8f6d
0.6.1
richiemcilroy Oct 20, 2026
6da1d73
Merge main into Loom concierge migration
richiemcilroy Oct 20, 2026
7185eec
fix: show Loom concierge errors in production and keep open customer …
richiemcilroy Oct 20, 2026
d5e6a5d
test: cover Loom concierge migration against MySQL and S3
richiemcilroy Oct 20, 2026
19bfa8b
fix: use defined colours for Loom migration status and reset operator…
richiemcilroy Oct 20, 2026
2ed1092
chore: keep migration journal formatting consistent
richiemcilroy Oct 20, 2026
5efae8d
fix: lock Loom concierge completion, recover failed automatic imports…
richiemcilroy Oct 20, 2026
bead736
fix: make Loom migration status email read correctly for every status
richiemcilroy Oct 20, 2026
4e400a9
Merge pull request #2326 from CapSoftware/feature/loom-concierge-migr…
richiemcilroy Oct 20, 2026
8779caa
feat: move Loom concierge migration into the Loom import page
richiemcilroy Oct 20, 2026
c8b86c5
fix: bill Signed BAA in the customer's currency and surface real erro…
richiemcilroy Oct 20, 2026
e800d3a
Merge branch 'main' into cursor/export-audio-sample-slice-fb48
richiemcilroy Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/desktop-gpui/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions apps/desktop-gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ path = "../../crates/recording/examples/studio-finalization-benchmark.rs"

[dependencies]
cap-audio = { path = "../../crates/audio" }
cap-cli-install = { path = "../../crates/cli-install" }
cap-gpui-kernels = { path = "kernels" }
# gpui — the wingleeio/zed fork Comet ships. The pinned rev carries f596cde
# ("destination alpha on transparent windows: Porter-Duff OVER, not additive"),
Expand Down Expand Up @@ -209,10 +210,11 @@ x11rb = "0.13"
objc2 = "0.5.2"
# `NSVisualEffectView` is the pre-macOS-26 window-material fallback. Features
# only -- the versions stay pinned, or a second objc2 universe gets built.
objc2-app-kit = { version = "0.2.2", features = ["NSAppearance", "NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard", "NSVisualEffectView"] }
objc2-app-kit = { version = "0.2.2", features = ["NSAppearance", "NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard", "NSVisualEffectView", "NSWorkspace", "NSAccessibility"] }
# NSRect/NSPoint/NSSize for `setFrame:display:`. Already in the tree as
# objc2-app-kit's own dependency, so this adds nothing to the build.
objc2-foundation = "0.2.2"
objc2-foundation = { version = "0.2.2", features = ["NSNotification", "NSOperation", "NSString", "block2"] }
block2 = "0.5.1"
raw-window-handle = "0.6"
core-foundation = "0.10"
core-video = { version = "0.5.2", features = ["metal"] }
Expand Down
128 changes: 128 additions & 0 deletions apps/desktop-gpui/examples/export-benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! Run: cargo run --release --example export-benchmark -- <project.cap> [WIDTH] [HEIGHT] [FPS] [RUNS]

use std::{
path::PathBuf,
sync::{Arc, atomic::AtomicBool},
time::Instant,
};

use cap_export::{
ExporterBase,
mp4::{ExportCompression, Mp4ExportSettings},
};
use cap_project::XY;

#[cfg(unix)]
fn cpu_secs() -> f64 {
let mut usage = std::mem::MaybeUninit::<libc::rusage>::zeroed();
unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) };
let usage = unsafe { usage.assume_init() };
let secs = |time: libc::timeval| time.tv_sec as f64 + time.tv_usec as f64 / 1_000_000.0;
secs(usage.ru_utime) + secs(usage.ru_stime)
}

#[cfg(windows)]
fn cpu_secs() -> f64 {
use windows_sys::Win32::{
Foundation::FILETIME,
System::Threading::{GetCurrentProcess, GetProcessTimes},
};

let empty = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let (mut creation, mut exit, mut kernel, mut user) = (empty, empty, empty, empty);
unsafe {
GetProcessTimes(
GetCurrentProcess(),
&mut creation,
&mut exit,
&mut kernel,
&mut user,
)
};
let secs = |time: FILETIME| {
((u64::from(time.dwHighDateTime) << 32) | u64::from(time.dwLowDateTime)) as f64
/ 10_000_000.0
};
secs(kernel) + secs(user)
}

async fn run(project_path: PathBuf, size: XY<u32>, fps: u32, runs: u32) -> anyhow::Result<()> {
let output_dir = tempfile::tempdir()?;
for run in 0..runs {
let output_path = output_dir.path().join(format!("run-{run}.mp4"));
let cancel = Arc::new(AtomicBool::new(false));
let started = Instant::now();
let cpu_started = cpu_secs();
let base = ExporterBase::builder(project_path.clone())
.with_output_path(output_path.clone())
.build_for_mp4(cancel)
.await
.map_err(|error| anyhow::anyhow!("{error}"))?;
let total = base.total_frames(fps);
let prepared_ms = started.elapsed().as_secs_f64() * 1000.0;
let path = Mp4ExportSettings {
fps,
resolution_base: size,
compression: ExportCompression::Maximum,
custom_bpp: None,
force_ffmpeg_decoder: false,
optimize_filesize: false,
}
.export_prepared(base, |_| true)
.await
.map_err(|error| anyhow::anyhow!("{error}"))?;
let wall = started.elapsed().as_secs_f64();
let cpu = cpu_secs() - cpu_started;
let bytes = std::fs::metadata(&path).map(|meta| meta.len()).unwrap_or(0);
println!(
"{}",
serde_json::json!({
"run": run,
"frames": total,
"width": size.x,
"height": size.y,
"fps": fps,
"prepareMs": prepared_ms,
"wallSecs": wall,
"exportFps": f64::from(total) / wall,
"cpuSecs": cpu,
"bytes": bytes,
})
);
}
Ok(())
}

fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "warn".into()),
)
.with_writer(std::io::stderr)
.init();

let mut arguments = std::env::args().skip(1);
let project_path = arguments
.next()
.map(PathBuf::from)
.ok_or_else(|| anyhow::anyhow!("Pass a studio .cap project path"))?;
let mut number = |default: u32| {
arguments
.next()
.and_then(|value| value.parse().ok())
.unwrap_or(default)
};
let size = XY::new(number(1920), number(1080));
let fps = number(60);
let runs = number(3).max(1);

tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.thread_stack_size(16 * 1024 * 1024)
.enable_all()
.build()?
.block_on(run(project_path, size, fps, runs))
}
209 changes: 209 additions & 0 deletions apps/desktop-gpui/examples/video-only-preparation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use std::{
collections::HashMap,
path::PathBuf,
sync::{
Arc,
atomic::{AtomicU32, AtomicU64, Ordering},
},
time::Instant,
};

use cap_export::{
ExporterBase, gif::GifExportSettings, make_cursor_only_project, mov::MovExportSettings,
};
use cap_project::{ProjectConfiguration, XY};

#[derive(Clone, Copy, Debug)]
enum Mode {
Baseline,
VideoOnly,
}

#[derive(Clone, Copy, Debug)]
enum Format {
Gif,
CursorMov,
}

#[derive(Debug)]
struct Args {
project: PathBuf,
output: PathBuf,
mode: Mode,
format: Format,
width: u32,
height: u32,
fps: u32,
build_only: bool,
force_ffmpeg: bool,
}

impl Args {
fn parse() -> Result<Self, Box<dyn std::error::Error>> {
let mut arguments = std::env::args().skip(1);
let mut values = HashMap::new();
let mut build_only = false;
let mut force_ffmpeg = false;
while let Some(argument) = arguments.next() {
match argument.as_str() {
"--build-only" if !build_only => build_only = true,
"--force-ffmpeg" if !force_ffmpeg => force_ffmpeg = true,
"--project" | "--output" | "--mode" | "--format" | "--width" | "--height"
| "--fps" => {
let value = arguments
.next()
.ok_or_else(|| format!("Missing value for {argument}"))?;
if values.insert(argument.clone(), value).is_some() {
return Err(format!("Duplicate argument: {argument}").into());
}
}
"--help" | "-h" => {
println!(
"Required: --project PATH --output PATH --mode baseline|video-only --format gif|cursor-mov\nOptional: --width 640 --height 360 --fps 15 --build-only --force-ffmpeg"
);
std::process::exit(0);
}
_ => return Err(format!("Unknown or duplicate argument: {argument}").into()),
}
}
let project = PathBuf::from(values.remove("--project").ok_or("--project is required")?);
let output = PathBuf::from(values.remove("--output").ok_or("--output is required")?);
let mode = match values
.remove("--mode")
.ok_or("--mode is required")?
.as_str()
{
"baseline" => Mode::Baseline,
"video-only" => Mode::VideoOnly,
_ => return Err("--mode must be baseline or video-only".into()),
};
let format = match values
.remove("--format")
.ok_or("--format is required")?
.as_str()
{
"gif" => Format::Gif,
"cursor-mov" => Format::CursorMov,
_ => return Err("--format must be gif or cursor-mov".into()),
};
let width = values
.remove("--width")
.map(|value| value.parse::<u32>())
.transpose()?
.unwrap_or(640);
let height = values
.remove("--height")
.map(|value| value.parse::<u32>())
.transpose()?
.unwrap_or(360);
let fps = values
.remove("--fps")
.map(|value| value.parse::<u32>())
.transpose()?
.unwrap_or(15);
if width == 0 || height == 0 || fps == 0 {
return Err("Width, height, and FPS must be positive".into());
}
Ok(Self {
project,
output,
mode,
format,
width,
height,
fps,
build_only,
force_ffmpeg,
})
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse()?;
let extension = match args.format {
Format::Gif => "gif",
Format::CursorMov => "mov",
};
if args.output.extension().and_then(|value| value.to_str()) != Some(extension) {
return Err(format!("Output must end with .{extension}").into());
}
if args.output.exists() {
return Err("Refusing to overwrite an existing output".into());
}
let mut config = ProjectConfiguration::load(&args.project)?;
if matches!(args.format, Format::CursorMov) {
config = make_cursor_only_project(config);
}
let started = Instant::now();
let builder = ExporterBase::builder(args.project.clone())
.with_config(config)
.with_output_path(args.output.clone())
.with_force_ffmpeg_decoder(args.force_ffmpeg);
let base = match args.mode {
Mode::Baseline => builder.build().await?,
Mode::VideoOnly => builder.build_video_only().await?,
};
let build_secs = started.elapsed().as_secs_f64();
let expected_frames = base.total_frames(args.fps);
if args.build_only {
println!(
"{}",
serde_json::json!({
"scope": "export_builder_only", "mode": format!("{:?}", args.mode),
"format": format!("{:?}", args.format), "build_secs": build_secs,
"expected_frames": expected_frames, "project": args.project,
})
);
drop(base);
return Ok(());
}
let frames = Arc::new(AtomicU32::new(0));
let first_progress_us = Arc::new(AtomicU64::new(u64::MAX));
let count = frames.clone();
let first = first_progress_us.clone();
let on_progress = move |_frame: u32| {
count.fetch_add(1, Ordering::Relaxed);
let _ = first.compare_exchange(
u64::MAX,
started.elapsed().as_micros() as u64,
Ordering::Relaxed,
Ordering::Relaxed,
);
true
};
let resolution_base = XY::new(args.width, args.height);
let output = match args.format {
Format::Gif => {
GifExportSettings {
fps: args.fps,
resolution_base,
quality: None,
}
.export(base, on_progress)
.await?
}
Format::CursorMov => {
MovExportSettings {
fps: args.fps,
resolution_base,
cursor_only: true,
}
.export(base, on_progress)
.await?
}
};
println!(
"{}",
serde_json::json!({
"scope": "gpui_equivalent_export", "mode": format!("{:?}", args.mode),
"format": format!("{:?}", args.format), "build_secs": build_secs,
"total_secs": started.elapsed().as_secs_f64(),
"first_progress_us": first_progress_us.load(Ordering::Relaxed),
"progress_frames": frames.load(Ordering::Relaxed),
"expected_frames": expected_frames, "output": output,
"output_bytes": std::fs::metadata(&output)?.len(),
})
);
Ok(())
}
Loading
Loading