Skip to content

Commit

Permalink
fix: Stride for screenshot + capture. Pretty name in export.
Browse files Browse the repository at this point in the history
  • Loading branch information
richiemcilroy committed Oct 17, 2024
1 parent d013ff1 commit 1c0637b
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 27 deletions.
57 changes: 51 additions & 6 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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

#[tauri::command]
Expand All @@ -1032,6 +1070,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 @@ -1041,6 +1085,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
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 1c0637b

Please sign in to comment.