Skip to content

Commit

Permalink
Merge branch 'CapSoftware:main' into windows
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsEeleeya authored Oct 17, 2024
2 parents b667fd4 + adb7f50 commit 0896d4c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 35 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -83,7 +83,7 @@ nokhwa = { git = "https://github.com/CapSoftware/nokhwa", branch = "brendonovich
[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"
Expand Down
68 changes: 58 additions & 10 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,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 => {
Expand Down Expand Up @@ -536,11 +539,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);

Expand Down Expand Up @@ -577,7 +591,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| {
Expand Down Expand Up @@ -1024,6 +1064,7 @@ struct SerializedEditorInstance {
saved_project_config: ProjectConfiguration,
recordings: ProjectRecordings,
path: PathBuf,
pretty_name: String,
}

#[tauri::command]
Expand All @@ -1034,6 +1075,12 @@ async fn create_editor_instance(
) -> Result<SerializedEditorInstance, String> {
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(),
Expand All @@ -1043,6 +1090,7 @@ async fn create_editor_instance(
},
recordings: editor_instance.recordings,
path: editor_instance.project_path.clone(),
pretty_name: meta.pretty_name,
})
}

Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/routes/editor/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -65,6 +65,7 @@ function ExportButton() {
onClick={() => {
save({
filters: [{ name: "mp4 filter", extensions: ["mp4"] }],
defaultPath: `~/Desktop/${prettyName()}.mp4`,
}).then((p) => {
if (!p) return;

Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/routes/editor/editorInstanceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ export const [EditorInstanceContextProvider, useEditorInstanceContext] =
videoId: props.videoId,
latestFrame,
presets: createPresets(),
prettyName: () => editorInstance()?.prettyName ?? "Cap Recording",
};
}, null!);
2 changes: 1 addition & 1 deletion apps/desktop/src/utils/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] }
Expand Down
15 changes: 15 additions & 0 deletions apps/web/content/changelog/9.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
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
- 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
54 changes: 35 additions & 19 deletions crates/media/src/sources/screen_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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) {
Expand Down

0 comments on commit 0896d4c

Please sign in to comment.