Skip to content

Commit

Permalink
cargo fmt
Browse files Browse the repository at this point in the history
Signed-off-by: David Anyatonwu <davidanyatonwu@gmail.com>
  • Loading branch information
onyedikachi-david committed Feb 7, 2025
1 parent af7482d commit 743d040
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 67 deletions.
23 changes: 14 additions & 9 deletions apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
create_editor_instance_impl, get_video_metadata, notifications, windows::ShowCapWindow, AuthStore, RenderProgress, VideoType
create_editor_instance_impl, get_video_metadata, notifications, windows::ShowCapWindow,
AuthStore, RenderProgress, VideoType,
};
use cap_project::{ProjectConfiguration, XY};
use std::path::PathBuf;
Expand Down Expand Up @@ -105,30 +106,34 @@ pub async fn export_video(
match result {
Ok(_) => {
ShowCapWindow::PrevRecordings.show(&app).ok();

// Send the appropriate notification based on export format
notifications::send_notification(
&app,
match project.export.format {
cap_project::ExportFormat::Gif => notifications::NotificationType::GifSaved,
cap_project::ExportFormat::Mp4 => notifications::NotificationType::VideoSaved,
}
},
);

Ok(output_path)
}
Err(e) => {
sentry::capture_message(&e.to_string(), sentry::Level::Error);

// Send the appropriate error notification based on export format
notifications::send_notification(
&app,
match project.export.format {
cap_project::ExportFormat::Gif => notifications::NotificationType::GifSaveFailed,
cap_project::ExportFormat::Mp4 => notifications::NotificationType::VideoSaveFailed,
}
cap_project::ExportFormat::Gif => {
notifications::NotificationType::GifSaveFailed
}
cap_project::ExportFormat::Mp4 => {
notifications::NotificationType::VideoSaveFailed
}
},
);

Err(e.to_string())
}
}
Expand Down
18 changes: 10 additions & 8 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,20 +669,22 @@ async fn copy_file_to_path(app: AppHandle, src: String, dst: String) -> Result<(

let is_screenshot = src.contains("screenshots/");
let src_path = std::path::Path::new(&src);

if !src_path.exists() {
return Err(format!("Source file {} does not exist", src));
}

// Check if either source or destination is a GIF
let is_gif = src_path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.eq_ignore_ascii_case("gif"))
.unwrap_or(false) ||
std::path::Path::new(&dst).extension()
let is_gif = src_path
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.eq_ignore_ascii_case("gif"))
.unwrap_or(false);
.unwrap_or(false)
|| std::path::Path::new(&dst)
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.eq_ignore_ascii_case("gif"))
.unwrap_or(false);

if !is_screenshot && !is_gif {
if !is_valid_mp4(src_path) {
Expand Down Expand Up @@ -1543,7 +1545,7 @@ async fn save_file_dialog(
} else if file_type == "screenshot" {
"png"
} else {
"mp4" // default for recordings
"mp4" // default for recordings
};

let name = match extension {
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src-tauri/src/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ impl NotificationType {
"Unable to copy screenshot to clipboard. Please try again",
true,
),
NotificationType::GifSaved => ("GIF Export Complete", "GIF exported successfully", false),
NotificationType::GifSaved => {
("GIF Export Complete", "GIF exported successfully", false)
}
NotificationType::GifSaveFailed => (
"GIF Export Failed",
"Unable to export GIF. Please try again",
Expand Down
88 changes: 43 additions & 45 deletions crates/export/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ use cap_media::{
};
use cap_project::{ProjectConfiguration, RecordingMeta, XY};
use cap_rendering::{
ProjectUniforms, RecordingSegmentDecoders, RenderSegment, RenderVideoConstants, RenderedFrame,
SegmentVideoPaths, ProjectRecordings,
ProjectRecordings, ProjectUniforms, RecordingSegmentDecoders, RenderSegment,
RenderVideoConstants, RenderedFrame, SegmentVideoPaths,
};
use futures::FutureExt;
use gifski::{self, Settings, progress::NoProgress, Collector};
use gifski::{self, progress::NoProgress, Collector, Settings};
use image::{ImageBuffer, Rgba};
use imgref::Img;
use rayon::prelude::*;
use rgb::RGBA8;
use std::{fs::File, path::PathBuf, sync::Arc, time::Duration};
use rayon::prelude::*;

#[derive(thiserror::Error, Debug)]
pub enum ExportError {
Expand Down Expand Up @@ -358,7 +358,7 @@ where
let recordings = ProjectRecordings::new(&self.meta);
let duration = get_duration(&recordings, &self.meta, &self.project);
let total_frames = (self.fps as f64 * duration).ceil() as u32;

println!(
"Final export duration: {} seconds ({} frames at {}fps)",
duration, total_frames, self.fps
Expand All @@ -378,13 +378,13 @@ where

let output_path_clone = output_path.clone();
let on_progress = self.on_progress;

// Create a channel for collecting frames
let (frame_tx, frame_rx) = std::sync::mpsc::sync_channel(32); // Increased buffer for better throughput

// Clone on_progress for the receive task
let on_progress_receive = on_progress.clone();

// Spawn a task to receive frames and collect them
let receive_task = tokio::spawn(async move {
let mut frames = Vec::with_capacity(total_frames as usize);
Expand All @@ -394,58 +394,55 @@ where
on_progress_receive(frame_number + 1);
}
// Send all collected frames through the sync channel
frame_tx.send(frames).map_err(|e| {
ExportError::Other(format!("Failed to send frames: {}", e))
})?;
frame_tx
.send(frames)
.map_err(|e| ExportError::Other(format!("Failed to send frames: {}", e)))?;
Ok::<_, ExportError>(())
});

let encoder_thread = tokio::task::spawn_blocking(move || {
let (collector, writer) = gifski::new(settings)
.map_err(|e| ExportError::Other(e.to_string()))?;
let (collector, writer) =
gifski::new(settings).map_err(|e| ExportError::Other(e.to_string()))?;

let writer_handle = std::thread::spawn(move || {
println!("Starting to write final GIF file to disk...");
writer.write(
File::create(&output_path_clone).map_err(ExportError::IO)?,
&mut NoProgress{},
)
.map_err(|e| ExportError::Other(e.to_string()))
writer
.write(
File::create(&output_path_clone).map_err(ExportError::IO)?,
&mut NoProgress {},
)
.map_err(|e| ExportError::Other(e.to_string()))
});

// Receive all frames at once
let frames = frame_rx.recv().map_err(|e|
ExportError::Other(format!("Failed to receive frames: {}", e)))?;
let frames = frame_rx
.recv()
.map_err(|e| ExportError::Other(format!("Failed to receive frames: {}", e)))?;

println!("Processing {} frames in parallel", frames.len());

// Process frames in parallel using rayon
let chunk_size = frames.len() / 10; // Split into 10 chunks for progress
let chunk_size = frames.len() / 10; // Split into 10 chunks for progress
let chunks = frames.par_chunks(chunk_size.max(1));

// Process all chunks first
chunks.enumerate().try_for_each(|(chunk_idx, chunk)| {
chunk.iter().try_for_each(|(frame, frame_number)| {
let rgba_pixels: Vec<RGBA8> = frame.data
let rgba_pixels: Vec<RGBA8> = frame
.data
.chunks_exact(4)
.map(|chunk| RGBA8::new(chunk[0], chunk[1], chunk[2], chunk[3]))
.collect();

let pixels = Img::new(
rgba_pixels,
frame.width as usize,
frame.height as usize,
);


let pixels = Img::new(rgba_pixels, frame.width as usize, frame.height as usize);

let timestamp = *frame_number as f64 / fps as f64;

collector.add_frame_rgba(
*frame_number as usize,
pixels,
timestamp,
).map_err(|e| ExportError::Other(e.to_string()))

collector
.add_frame_rgba(*frame_number as usize, pixels, timestamp)
.map_err(|e| ExportError::Other(e.to_string()))
})?;

Ok::<_, ExportError>(())
})?;

Expand All @@ -455,30 +452,31 @@ where
}

println!("Completed parallel GIF encoding of {} frames", frames.len());

// GIF assembly phase: total_frames + 11
on_progress(total_frames + 11);
println!("Starting final GIF assembly and optimization...");

drop(collector);

// Writing to disk phase: total_frames + 12
on_progress(total_frames + 12);
println!("Writing optimized GIF to disk (this may take a moment)...");

let result = writer_handle.join()

let result = writer_handle
.join()
.map_err(|_| ExportError::Other("GIF writer thread panicked".into()))?;

// Complete phase: total_frames + 13
on_progress(total_frames + 13);
println!("GIF file has been successfully written to disk");
result
});

// Wait for all tasks to complete
let (video_result, receive_result, encoder_result) =
let (video_result, receive_result, encoder_result) =
tokio::join!(render_video_task, receive_task, encoder_thread);

video_result?;
receive_result?;
encoder_result?;
Expand Down
6 changes: 2 additions & 4 deletions crates/rendering/src/project_recordings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ impl ProjectRecordings {
let display = Video::new(&meta.path(&segment.display.path))
.expect("Failed to read display video");
let camera = segment.camera.as_ref().map(|camera| {
Video::new(&meta.path(&camera.path))
.expect("Failed to read camera video")
Video::new(&meta.path(&camera.path)).expect("Failed to read camera video")
});
let audio = segment
.audio
Expand All @@ -102,8 +101,7 @@ impl ProjectRecordings {
let display = Video::new(&meta.path(&s.display.path))
.expect("Failed to read display video");
let camera = s.camera.as_ref().map(|camera| {
Video::new(&meta.path(&camera.path))
.expect("Failed to read camera video")
Video::new(&meta.path(&camera.path)).expect("Failed to read camera video")
});
let audio = s
.audio
Expand Down

0 comments on commit 743d040

Please sign in to comment.