Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
- target: aarch64-apple-darwin
runner: macos-latest-xlarge
- target: x86_64-pc-windows-msvc
runner: windows-latest
runner: windows-latest-8-cores
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cap-desktop"
version = "0.4.1"
version = "0.4.2"
description = "Beautiful screen recordings, owned by you."
authors = ["you"]
edition = "2024"
Expand Down
8 changes: 8 additions & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2223,6 +2223,13 @@ async fn reset_microphone_permissions(_app: AppHandle) -> Result<(), ()> {
Ok(())
}

#[tauri::command]
#[specta::specta]
#[instrument(skip(app))]
async fn clear_presets(app: AppHandle) -> Result<(), String> {
presets::PresetsStore::clear(&app)
}

#[tauri::command]
#[specta::specta]
#[instrument(skip(app))]
Expand Down Expand Up @@ -2604,6 +2611,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
hotkeys::set_hotkey,
reset_camera_permissions,
reset_microphone_permissions,
clear_presets,
is_camera_window_open,
seek_to,
get_display_frame_for_cropping,
Expand Down
26 changes: 17 additions & 9 deletions apps/desktop/src-tauri/src/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,28 @@ pub struct Preset {
impl PresetsStore {
fn get(app: &AppHandle<Wry>) -> Result<Option<Self>, String> {
match app.store("store").map(|s| s.get("presets")) {
Ok(Some(store)) => {
// Handle potential deserialization errors gracefully
match serde_json::from_value(store) {
Ok(settings) => Ok(Some(settings)),
Err(_) => {
error!("Failed to deserialize presets store");
Ok(None)
}
Ok(Some(store)) => match serde_json::from_value(store.clone()) {
Ok(settings) => Ok(Some(settings)),
Err(e) => {
error!(
"Failed to deserialize presets store: {}. Raw value: {}",
e,
serde_json::to_string_pretty(&store).unwrap_or_default()
);
Ok(None)
}
}
},
_ => Ok(None),
}
}

pub fn clear(app: &AppHandle<Wry>) -> Result<(), String> {
let store = app.store("store").map_err(|e| e.to_string())?;
store.delete("presets");
store.save().map_err(|e| e.to_string())?;
Ok(())
}

pub fn get_default_preset(app: &AppHandle<Wry>) -> Result<Option<Preset>, String> {
let Some(this) = Self::get(app)? else {
return Ok(None);
Expand Down
21 changes: 9 additions & 12 deletions apps/desktop/src/routes/editor/ConfigSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1262,7 +1262,7 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {
photoUrl.replace("file://", ""),
);

debouncedSetProject(rawPath);
setWallpaperSource(rawPath);
} catch (_err) {
toast.error("Failed to set wallpaper");
}
Expand Down Expand Up @@ -1324,17 +1324,14 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {

let fileInput!: HTMLInputElement;

// Optimize the debounced set project function
const debouncedSetProject = (wallpaperPath: string) => {
const setWallpaperSource = (wallpaperPath: string) => {
const resumeHistory = projectHistory.pause();
queueMicrotask(() => {
batch(() => {
setProject("background", "source", {
type: "wallpaper",
path: wallpaperPath,
} as const);
resumeHistory();
});
batch(() => {
setProject("background", "source", {
type: "wallpaper",
path: wallpaperPath,
} as const);
resumeHistory();
});
};

Expand Down Expand Up @@ -1600,7 +1597,7 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {

// Get the raw path without any URL prefixes

debouncedSetProject(wallpaper.rawPath);
setWallpaperSource(wallpaper.rawPath);

ensurePaddingForBackground();
} catch (_err) {
Expand Down
17 changes: 1 addition & 16 deletions apps/desktop/src/routes/editor/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,7 @@ export function PlayerContent() {
end: segment.end,
text: segment.text,
})),
settings: {
enabled: captionsStore.state.settings.enabled,
font: captionsStore.state.settings.font,
size: captionsStore.state.settings.size,
color: captionsStore.state.settings.color,
backgroundColor: captionsStore.state.settings.backgroundColor,
backgroundOpacity: captionsStore.state.settings.backgroundOpacity,
position: captionsStore.state.settings.position,
italic: captionsStore.state.settings.italic,
outline: captionsStore.state.settings.outline,
outlineColor: captionsStore.state.settings.outlineColor,
exportWithSubtitles:
captionsStore.state.settings.exportWithSubtitles,
highlightColor: captionsStore.state.settings.highlightColor,
fadeDuration: captionsStore.state.settings.fadeDuration,
},
settings: { ...captionsStore.state.settings },
};

// Update the project with captions data
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/editor/PresetsDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function PresetsDropdown() {
setShowSettings(false);
const normalizedConfig = normalizeProject({
...preset.config,
timeline: project.timeline,
timeline: project.timeline ?? null,
clips: project.clips,
});
setProject(reconcile(normalizedConfig));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,14 @@ export function BackgroundSettingsPopover() {
setProject("background", "source", source);
};

// Debounced set project for history
const debouncedSetProject = (wallpaperPath: string) => {
const setWallpaperSource = (wallpaperPath: string) => {
const resumeHistory = projectHistory.pause();
queueMicrotask(() => {
batch(() => {
setProject("background", "source", {
type: "wallpaper",
path: wallpaperPath,
} as const);
resumeHistory();
});
batch(() => {
setProject("background", "source", {
type: "wallpaper",
path: wallpaperPath,
} as const);
resumeHistory();
});
};

Expand Down Expand Up @@ -319,7 +316,7 @@ export function BackgroundSettingsPopover() {
(w) => w.url === photoUrl,
);
if (wallpaper) {
debouncedSetProject(wallpaper.rawPath);
setWallpaperSource(wallpaper.rawPath);
ensurePaddingForBackground();
}
}}
Expand Down
23 changes: 14 additions & 9 deletions apps/desktop/src/utils/createPresets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,28 @@ export function createPresets() {
return {
query,
createPreset: async (preset: CreatePreset) => {
const config = { ...preset.config };
// @ts-expect-error we reeeally don't want the timeline in the preset
config.timeline = undefined;
config.clips = undefined;
const config = {
...preset.config,
timeline: null,
clips: [],
};

await updatePresets((store) => {
store.presets.push({ name: preset.name, config });
store.default = preset.default ? store.presets.length : store.default;
store.default = preset.default
? store.presets.length - 1
: store.default;
});
},
deletePreset: (index: number) =>
updatePresets((store) => {
store.presets.splice(index, 1);
store.default =
index > store.presets.length - 1
? store.presets.length - 1
: store.default;
if (store.default === null) return;
if (index === store.default) {
store.default = store.presets.length > 0 ? 0 : null;
} else if (index < store.default) {
store.default = store.default - 1;
}
}),
setDefault: (index: number) =>
updatePresets((store) => {
Expand Down
15 changes: 9 additions & 6 deletions apps/desktop/src/utils/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ async resetCameraPermissions() : Promise<null> {
async resetMicrophonePermissions() : Promise<null> {
return await TAURI_INVOKE("reset_microphone_permissions");
},
async clearPresets() : Promise<null> {
return await TAURI_INVOKE("clear_presets");
},
async isCameraWindowOpen() : Promise<boolean> {
return await TAURI_INVOKE("is_camera_window_open");
},
Expand Down Expand Up @@ -380,15 +383,15 @@ export type AnnotationType = "arrow" | "circle" | "rectangle" | "text" | "mask"
export type AppTheme = "system" | "light" | "dark"
export type AspectRatio = "wide" | "vertical" | "square" | "classic" | "tall"
export type Audio = { duration: number; sample_rate: number; channels: number; start_time: number }
export type AudioConfiguration = { mute: boolean; improve: boolean; micVolumeDb?: number; micStereoMode?: StereoMode; systemVolumeDb?: number }
export type AudioConfiguration = { mute: boolean; improve: boolean; micVolumeDb: number; micStereoMode: StereoMode; systemVolumeDb: number }
export type AudioInputLevelChange = number
export type AudioMeta = { path: string; start_time?: number | null; device_id?: string | null }
export type AuthSecret = { api_key: string } | { token: string; expires: number }
export type AuthStore = { secret: AuthSecret; user_id: string | null; plan: Plan | null; intercom_hash: string | null; organizations?: Organization[] }
export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; roundingType?: CornerStyle; inset: number; crop: Crop | null; shadow?: number; advancedShadow?: ShadowConfiguration | null; border?: BorderConfiguration | null }
export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; roundingType: CornerStyle; inset: number; crop: Crop | null; shadow: number; advancedShadow: ShadowConfiguration | null; border: BorderConfiguration | null }
export type BackgroundSource = { type: "wallpaper"; path: string | null } | { type: "image"; path: string | null } | { type: "color"; value: [number, number, number]; alpha?: number } | { type: "gradient"; from: [number, number, number]; to: [number, number, number]; angle?: number }
export type BorderConfiguration = { enabled: boolean; width: number; color: [number, number, number]; opacity: number }
export type Camera = { hide: boolean; mirror: boolean; position: CameraPosition; size: number; zoomSize: number | null; rounding?: number; shadow?: number; advancedShadow?: ShadowConfiguration | null; shape?: CameraShape; roundingType?: CornerStyle }
export type Camera = { hide: boolean; mirror: boolean; position: CameraPosition; size: number; zoomSize: number | null; rounding: number; shadow: number; advancedShadow: ShadowConfiguration | null; shape: CameraShape; roundingType: CornerStyle }
export type CameraInfo = { device_id: string; model_id: ModelIDType | null; display_name: string }
export type CameraPosition = { x: CameraXPosition; y: CameraYPosition }
export type CameraPreviewShape = "round" | "square" | "full"
Expand All @@ -398,7 +401,7 @@ export type CameraXPosition = "left" | "center" | "right"
export type CameraYPosition = "top" | "bottom"
export type CaptionData = { segments: CaptionSegment[]; settings: CaptionSettings | null }
export type CaptionSegment = { id: string; start: number; end: number; text: string; words?: CaptionWord[] }
export type CaptionSettings = { enabled: boolean; font: string; size: number; color: string; backgroundColor: string; backgroundOpacity: number; position?: string; italic: boolean; fontWeight?: number; outline: boolean; outlineColor: string; exportWithSubtitles: boolean; highlightColor?: string; fadeDuration?: number; lingerDuration?: number; wordTransitionDuration?: number; activeWordHighlight?: boolean }
export type CaptionSettings = { enabled: boolean; font: string; size: number; color: string; backgroundColor: string; backgroundOpacity: number; position: string; italic: boolean; fontWeight: number; outline: boolean; outlineColor: string; exportWithSubtitles: boolean; highlightColor: string; fadeDuration: number; lingerDuration: number; wordTransitionDuration: number; activeWordHighlight: boolean }
export type CaptionWord = { text: string; start: number; end: number }
export type CaptionsData = { segments: CaptionSegment[]; settings: CaptionSettings }
export type CaptureDisplay = { id: DisplayId; name: string; refresh_rate: number }
Expand All @@ -414,7 +417,7 @@ export type CurrentRecording = { target: CurrentRecordingTarget; mode: Recording
export type CurrentRecordingChanged = null
export type CurrentRecordingTarget = { window: { id: WindowId; bounds: LogicalBounds | null } } | { screen: { id: DisplayId } } | { area: { screen: DisplayId; bounds: LogicalBounds } }
export type CursorAnimationStyle = "slow" | "mellow" | "custom"
export type CursorConfiguration = { hide?: boolean; hideWhenIdle?: boolean; hideWhenIdleDelay?: number; size: number; type: CursorType; animationStyle: CursorAnimationStyle; tension: number; mass: number; friction: number; raw?: boolean; motionBlur?: number; useSvg?: boolean }
export type CursorConfiguration = { hide: boolean; hideWhenIdle: boolean; hideWhenIdleDelay: number; size: number; type: CursorType; animationStyle: CursorAnimationStyle; tension: number; mass: number; friction: number; raw: boolean; motionBlur: number; useSvg: boolean }
export type CursorMeta = { imagePath: string; hotspot: XY<number>; shape?: string | null }
export type CursorType = "auto" | "pointer" | "circle"
export type Cursors = { [key in string]: string } | { [key in string]: CursorMeta }
Expand Down Expand Up @@ -483,7 +486,7 @@ export type PostDeletionBehaviour = "doNothing" | "reopenRecordingWindow"
export type PostStudioRecordingBehaviour = "openEditor" | "showOverlay"
export type Preset = { name: string; config: ProjectConfiguration }
export type PresetsStore = { presets: Preset[]; default: number | null }
export type ProjectConfiguration = { aspectRatio: AspectRatio | null; background: BackgroundConfiguration; camera: Camera; audio: AudioConfiguration; cursor: CursorConfiguration; hotkeys: HotkeysConfiguration; timeline?: TimelineConfiguration | null; captions?: CaptionsData | null; clips?: ClipConfiguration[]; annotations?: Annotation[] }
export type ProjectConfiguration = { aspectRatio: AspectRatio | null; background: BackgroundConfiguration; camera: Camera; audio: AudioConfiguration; cursor: CursorConfiguration; hotkeys: HotkeysConfiguration; timeline: TimelineConfiguration | null; captions: CaptionsData | null; clips: ClipConfiguration[]; annotations: Annotation[] }
export type ProjectRecordingsMeta = { segments: SegmentRecordings[] }
export type RecordingAction = "Started" | "InvalidAuthentication" | "UpgradeRequired"
export type RecordingDeleted = { path: string }
Expand Down
2 changes: 1 addition & 1 deletion apps/media-server/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("GET /", () => {
expect(data).toEqual({
name: "@cap/media-server",
version: "1.0.0",
endpoints: ["/health", "/audio/check", "/audio/extract"],
endpoints: ["/health", "/audio/status", "/audio/check", "/audio/extract"],
});
});
});
Expand Down
17 changes: 10 additions & 7 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ impl Renderer {
)?);
let mut max_duration = recordings.duration();

// Check camera duration if it exists
if let Some(camera_path) = meta.camera_path()
&& let Ok(camera_duration) =
recordings.get_source_duration(&recording_meta.path(&camera_path))
Expand Down Expand Up @@ -143,16 +142,22 @@ impl Renderer {
break;
}
}
let frame = frame_renderer
match frame_renderer
.render(
current.segment_frames,
current.uniforms,
&current.cursor,
&mut layers,
)
.await
.unwrap();
(self.frame_cb)(frame);
{
Ok(frame) => {
(self.frame_cb)(frame);
}
Err(e) => {
tracing::error!(error = %e, "Failed to render frame in editor");
}
}

let _ = current.finished.send(());
}
Expand Down Expand Up @@ -182,17 +187,15 @@ impl RendererHandle {
}

pub async fn stop(&self) {
// Send a stop message to the renderer
let (tx, rx) = oneshot::channel();
if self
.tx
.send(RendererMessage::Stop { finished: tx })
.await
.is_err()
{
println!("Failed to send stop message to renderer");
tracing::warn!("Failed to send stop message to renderer");
}
// Wait for the renderer to acknowledge the stop
let _ = rx.await;
}
}
Loading
Loading