From e3655b94f1688df1ab39a557837b0589fc823dbf Mon Sep 17 00:00:00 2001 From: PJ Date: Thu, 17 Oct 2024 21:26:59 +0100 Subject: [PATCH 1/4] log errors with opening camera feed --- Cargo.lock | 4 ++-- apps/desktop/src-tauri/Cargo.toml | 4 ++-- apps/desktop/src-tauri/src/lib.rs | 11 +++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d56f9c7..5dbb7909 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1489,7 +1489,7 @@ dependencies = [ [[package]] name = "desktop" -version = "0.3.0-beta.5.7" +version = "0.3.0-beta.5.7.1" dependencies = [ "anyhow", "axum", @@ -1518,7 +1518,7 @@ dependencies = [ "mp4", "nix 0.29.0", "nokhwa 0.10.3 (git+https://github.com/CapSoftware/nokhwa?branch=brendonovich-fork)", - "nokhwa-bindings-macos 0.2.0 (git+https://github.com/Brendonovich/nokhwa?rev=2de5a760d5f1)", + "nokhwa-bindings-macos 0.2.0 (git+https://github.com/CapSoftware/nokhwa?branch=brendonovich-fork)", "num-traits", "objc", "objc2-app-kit", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 3c86be50..efba493b 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "desktop" -version = "0.3.0-beta.5.7" +version = "0.3.0-beta.5.7.1" description = "Beautiful, shareable screen recordings." authors = ["you"] edition = "2021" @@ -83,7 +83,7 @@ global-hotkey = "0.5.5" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.24.0" core-foundation = "0.10.0" -nokhwa-bindings-macos = { git = "https://github.com/Brendonovich/nokhwa", rev = "2de5a760d5f1" } +nokhwa-bindings-macos = { git = "https://github.com/CapSoftware/nokhwa", branch = "brendonovich-fork" } objc2-app-kit = { version = "0.2.2", features = ["NSWindow", "NSResponder"] } cocoa = "0.26.0" objc = "0.2.7" diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index e98f39fc..bbcc4e09 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -162,11 +162,14 @@ impl App { if self.camera_feed.is_none() { self.camera_feed = CameraFeed::init(&camera_label, self.camera_tx.clone()) .await + .map_err(|error| eprintln!("{error}")) + .ok(); + } else if let Some(camera_feed) = self.camera_feed.as_mut() { + camera_feed + .switch_cameras(&camera_label) + .await + .map_err(|error| eprintln!("{error}")) .ok(); - } - - if let Some(camera_feed) = self.camera_feed.as_mut() { - camera_feed.switch_cameras(&camera_label).await.ok(); } } None => { From 1c0637bed01ecbcae355e6b101a458fef0e568da Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:14:51 +0100 Subject: [PATCH 2/4] fix: Stride for screenshot + capture. Pretty name in export. --- apps/desktop/src-tauri/src/lib.rs | 57 +++++++++++++++++-- apps/desktop/src/routes/editor/Header.tsx | 3 +- .../routes/editor/editorInstanceContext.ts | 1 + apps/desktop/src/utils/tauri.ts | 2 +- crates/media/src/sources/screen_capture.rs | 54 +++++++++++------- 5 files changed, 90 insertions(+), 27 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index e98f39fc..1b753ba9 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -534,11 +534,22 @@ async fn create_screenshot( e.to_string() })?; - // Use image crate to save the frame as an image file - let width = rgb_frame.width() as u32; - let height = rgb_frame.height() as u32; - let data = rgb_frame.data(0); - let img = image::RgbImage::from_raw(width, height, data.to_vec()) + let width = rgb_frame.width() as usize; + let height = rgb_frame.height() as usize; + let bytes_per_pixel = 3; + let src_stride = rgb_frame.stride(0) as usize; + let dst_stride = width * bytes_per_pixel; + + let mut img_buffer = vec![0u8; height * dst_stride]; + + for y in 0..height { + let src_slice = + &rgb_frame.data(0)[y * src_stride..y * src_stride + dst_stride]; + let dst_slice = &mut img_buffer[y * dst_stride..(y + 1) * dst_stride]; + dst_slice.copy_from_slice(src_slice); + } + + let img = image::RgbImage::from_raw(width as u32, height as u32, img_buffer) .ok_or("Failed to create image from frame data")?; println!("Saving image to {:?}", output); @@ -575,7 +586,33 @@ async fn create_thumbnail(input: PathBuf, output: PathBuf, size: (u32, u32)) -> e.to_string() })?; - let thumbnail = img.thumbnail(size.0, size.1); + let width = img.width() as usize; + let height = img.height() as usize; + let bytes_per_pixel = 3; + let src_stride = width * bytes_per_pixel; + + let rgb_img = img.to_rgb8(); + let img_buffer = rgb_img.as_raw(); + + let mut corrected_buffer = vec![0u8; height * src_stride]; + + for y in 0..height { + let src_slice = &img_buffer[y * src_stride..(y + 1) * src_stride]; + let dst_slice = &mut corrected_buffer[y * src_stride..(y + 1) * src_stride]; + dst_slice.copy_from_slice(src_slice); + } + + let corrected_img = + image::RgbImage::from_raw(width as u32, height as u32, corrected_buffer) + .ok_or("Failed to create corrected image")?; + + let thumbnail = image::imageops::resize( + &corrected_img, + size.0, + size.1, + image::imageops::FilterType::Lanczos3, + ); + thumbnail .save_with_format(&output, image::ImageFormat::Png) .map_err(|e| { @@ -1022,6 +1059,7 @@ struct SerializedEditorInstance { saved_project_config: ProjectConfiguration, recordings: ProjectRecordings, path: PathBuf, + pretty_name: String, } #[tauri::command] @@ -1032,6 +1070,12 @@ async fn create_editor_instance( ) -> Result { let editor_instance = upsert_editor_instance(&app, video_id).await; + // Load the RecordingMeta to get the pretty name + let meta = RecordingMeta::load_for_project(&editor_instance.project_path) + .map_err(|e| format!("Failed to load recording meta: {}", e))?; + + println!("Pretty name: {}", meta.pretty_name); + Ok(SerializedEditorInstance { frames_socket_url: format!("ws://localhost:{}{FRAMES_WS_PATH}", editor_instance.ws_port), recording_duration: editor_instance.recordings.duration(), @@ -1041,6 +1085,7 @@ async fn create_editor_instance( }, recordings: editor_instance.recordings, path: editor_instance.project_path.clone(), + pretty_name: meta.pretty_name, }) } diff --git a/apps/desktop/src/routes/editor/Header.tsx b/apps/desktop/src/routes/editor/Header.tsx index 2af5f783..8bbbffe5 100644 --- a/apps/desktop/src/routes/editor/Header.tsx +++ b/apps/desktop/src/routes/editor/Header.tsx @@ -47,7 +47,7 @@ import { DEFAULT_PROJECT_CONFIG } from "./projectConfig"; import { createMutation } from "@tanstack/solid-query"; function ExportButton() { - const { videoId, project } = useEditorContext(); + const { videoId, project, prettyName } = useEditorContext(); const [state, setState] = createStore< | { open: false; type: "idle" } @@ -65,6 +65,7 @@ function ExportButton() { onClick={() => { save({ filters: [{ name: "mp4 filter", extensions: ["mp4"] }], + defaultPath: `~/Desktop/${prettyName()}.mp4`, }).then((p) => { if (!p) return; diff --git a/apps/desktop/src/routes/editor/editorInstanceContext.ts b/apps/desktop/src/routes/editor/editorInstanceContext.ts index 61d51bac..d423ed6c 100644 --- a/apps/desktop/src/routes/editor/editorInstanceContext.ts +++ b/apps/desktop/src/routes/editor/editorInstanceContext.ts @@ -41,5 +41,6 @@ export const [EditorInstanceContextProvider, useEditorInstanceContext] = videoId: props.videoId, latestFrame, presets: createPresets(), + prettyName: () => editorInstance()?.prettyName ?? "Cap Recording", }; }, null!); diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index 332c5852..d8a8f2d9 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -403,7 +403,7 @@ export type RequestRestartRecording = null export type RequestStartRecording = null export type RequestStopRecording = null export type ScreenCaptureTarget = ({ variant: "window" } & CaptureWindow) | { variant: "screen" } -export type SerializedEditorInstance = { framesSocketUrl: string; recordingDuration: number; savedProjectConfig: ProjectConfiguration; recordings: ProjectRecordings; path: string } +export type SerializedEditorInstance = { framesSocketUrl: string; recordingDuration: number; savedProjectConfig: ProjectConfiguration; recordings: ProjectRecordings; path: string; prettyName: string } export type SharingMeta = { id: string; link: string } export type ShowCapturesPanel = null export type TimelineConfiguration = { segments: TimelineSegment[] } diff --git a/crates/media/src/sources/screen_capture.rs b/crates/media/src/sources/screen_capture.rs index 7c4c441a..baac0c3e 100644 --- a/crates/media/src/sources/screen_capture.rs +++ b/crates/media/src/sources/screen_capture.rs @@ -184,10 +184,14 @@ impl PipelineSourceTask for ScreenCaptureSource { match capturer.get_next_frame() { Ok(Frame::BGRA(frame)) => { + if frame.height == 0 || frame.width == 0 { + continue; + } + let raw_timestamp = RawNanoseconds(frame.display_time); match clock.timestamp_for(raw_timestamp) { None => { - eprintln!("Clock is currently stopped. Dropping frames.") + eprintln!("Clock is currently stopped. Dropping frames."); } Some(timestamp) => { let mut buffer = FFVideo::new( @@ -197,32 +201,44 @@ impl PipelineSourceTask for ScreenCaptureSource { ); buffer.set_pts(Some(timestamp)); - let bytes_per_pixel = 4; // For BGRA format + let bytes_per_pixel = 4; let width_in_bytes = frame.width as usize * bytes_per_pixel; - let src_stride = width_in_bytes; - let dst_stride = buffer.stride(0) as usize; let height = frame.height as usize; let src_data = &frame.data; - let dst_data = buffer.data_mut(0); - // Ensure we don't go out of bounds - if src_data.len() < src_stride * height - || dst_data.len() < dst_stride * height - { + let src_stride = src_data.len() / height; + let dst_stride = buffer.stride(0) as usize; + + if src_data.len() < src_stride * height { eprintln!("Frame data size mismatch."); - break; + continue; + } + + if src_stride < width_in_bytes { + eprintln!( + "Source stride is less than expected width in bytes." + ); + continue; } - // Copy data line by line considering strides - for y in 0..height { - let src_offset = y * src_stride; - let dst_offset = y * dst_stride; - // Copy only the width_in_bytes to avoid overwriting - dst_data[dst_offset..dst_offset + width_in_bytes] - .copy_from_slice( - &src_data[src_offset..src_offset + width_in_bytes], - ); + if buffer.data(0).len() < dst_stride * height { + eprintln!("Destination data size mismatch."); + continue; + } + + { + let dst_data = buffer.data_mut(0); + + for y in 0..height { + let src_offset = y * src_stride; + let dst_offset = y * dst_stride; + dst_data[dst_offset..dst_offset + width_in_bytes] + .copy_from_slice( + &src_data + [src_offset..src_offset + width_in_bytes], + ); + } } if let Err(_) = output.send(buffer) { From 2caea37f0c8aa5dd7107360f600ba27646774565 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:54:56 +0100 Subject: [PATCH 3/4] version: 0.3.0-beta.5.7.1 --- apps/web/content/changelog/9.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/web/content/changelog/9.mdx diff --git a/apps/web/content/changelog/9.mdx b/apps/web/content/changelog/9.mdx new file mode 100644 index 00000000..6532c38e --- /dev/null +++ b/apps/web/content/changelog/9.mdx @@ -0,0 +1,14 @@ +--- +title: Performance improvements + upgrades +app: Cap Desktop +publishedAt: "2024-10-17" +version: 0.3.0-beta.5.7.1 +image: +--- + +- New and improved rendering engine +- Fixed severe memory leak issue, which would eventually crash the app +- Fixed stride calculation issue on frames (fixing scrambled frame data) +- Fixed issue where editor instance resources would not be released after editor is closed +- Better window selection names +- On record, the selected window will now be brought to focus From adb7f501687ca30b47b2cf359e65da8b5966891f Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:02:03 +0100 Subject: [PATCH 4/4] chore: Release copy text --- apps/web/content/changelog/9.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/content/changelog/9.mdx b/apps/web/content/changelog/9.mdx index 6532c38e..1a759121 100644 --- a/apps/web/content/changelog/9.mdx +++ b/apps/web/content/changelog/9.mdx @@ -7,8 +7,9 @@ image: --- - New and improved rendering engine +- New settings option for hiding dock icon +- Better window selection names +- On record, the selected window will now be brought to focus - Fixed severe memory leak issue, which would eventually crash the app - Fixed stride calculation issue on frames (fixing scrambled frame data) - Fixed issue where editor instance resources would not be released after editor is closed -- Better window selection names -- On record, the selected window will now be brought to focus