Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-export-metadata-display
Browse files Browse the repository at this point in the history
  • Loading branch information
onyedikachi-david authored Jan 13, 2025
2 parents 9eea216 + d748872 commit 3ce758e
Show file tree
Hide file tree
Showing 43 changed files with 2,222 additions and 1,402 deletions.
34 changes: 33 additions & 1 deletion Cargo.lock

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

15 changes: 5 additions & 10 deletions apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
get_video_metadata, upsert_editor_instance, windows::ShowCapWindow, RenderProgress,
VideoRecordingMetadata, VideoType,
general_settings::GeneralSettingsStore, get_video_metadata, upsert_editor_instance,
windows::ShowCapWindow, RenderProgress, VideoRecordingMetadata, VideoType,
};
use cap_project::ProjectConfiguration;
use std::path::PathBuf;
Expand Down Expand Up @@ -44,10 +44,8 @@ pub async fn export_video(
.unwrap_or(screen_metadata.duration),
);

// Calculate total frames with ceiling to ensure we don't exceed 100%
let total_frames = ((duration * 30.0).ceil() as u32).max(1);

let editor_instance = upsert_editor_instance(&app, video_id.clone()).await;
let total_frames = editor_instance.get_total_frames();

let output_path = editor_instance.meta().output_path();

Expand All @@ -72,6 +70,7 @@ pub async fn export_video(
}

let exporter = cap_export::Exporter::new(
&app,
modified_project,
output_path.clone(),
move |frame_index| {
Expand All @@ -91,11 +90,7 @@ pub async fn export_video(
e.to_string()
})?;

let result = if use_custom_muxer {
exporter.export_with_custom_muxer().await
} else {
exporter.export_with_ffmpeg_cli().await
};
let result = exporter.export_with_custom_muxer().await;

match result {
Ok(_) => {
Expand Down
39 changes: 39 additions & 0 deletions apps/desktop/src-tauri/src/general_settings.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use cap_project::Resolution;
use serde::{Deserialize, Serialize};
use serde_json::json;
use specta::Type;
Expand Down Expand Up @@ -25,6 +26,8 @@ pub struct GeneralSettingsStore {
pub has_completed_startup: bool,
#[serde(default)]
pub theme: AppTheme,
#[serde(default)]
pub recording_config: Option<RecordingConfig>,
}

#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize, Type)]
Expand All @@ -36,6 +39,25 @@ pub enum AppTheme {
Dark,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
#[serde(rename_all = "camelCase")]
pub struct RecordingConfig {
pub fps: u32,
pub resolution: Resolution,
}

impl Default for RecordingConfig {
fn default() -> Self {
Self {
fps: 30,
resolution: Resolution {
width: 1920,
height: 1080,
},
}
}
}

fn true_b() -> bool {
true
}
Expand Down Expand Up @@ -78,3 +100,20 @@ pub fn init(app: &AppHandle) {
app.manage(GeneralSettingsState::new(store));
println!("GeneralSettingsState managed");
}

#[tauri::command]
#[specta::specta]
pub async fn get_recording_config(app: AppHandle) -> Result<RecordingConfig, String> {
let settings = GeneralSettingsStore::get(&app)?;
Ok(settings
.and_then(|s| s.recording_config)
.unwrap_or_default())
}

#[tauri::command]
#[specta::specta]
pub async fn set_recording_config(app: AppHandle, config: RecordingConfig) -> Result<(), String> {
GeneralSettingsStore::update(&app, |settings| {
settings.recording_config = Some(config);
})
}
62 changes: 54 additions & 8 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ use cap_media::feeds::{AudioInputFeed, AudioInputSamplesSender};
use cap_media::frame_ws::WSFrame;
use cap_media::sources::CaptureScreen;
use cap_media::{feeds::CameraFeed, sources::ScreenCaptureTarget};
use cap_project::{Content, ProjectConfiguration, RecordingMeta, SharingMeta};
use cap_project::{Content, ProjectConfiguration, RecordingMeta, Resolution, SharingMeta};
use cap_recording::RecordingOptions;
use cap_rendering::ProjectRecordings;
use clipboard_rs::common::RustImage;
use clipboard_rs::{Clipboard, ClipboardContext};
use general_settings::GeneralSettingsStore;
use general_settings::{GeneralSettingsStore, RecordingConfig};
use mp4::Mp4Reader;
// use display::{list_capture_windows, Bounds, CaptureTarget, FPS};
use notifications::NotificationType;
Expand Down Expand Up @@ -91,14 +91,20 @@ pub enum VideoType {
Camera,
}

#[derive(Serialize, Deserialize, specta::Type)]
enum UploadResult {
#[derive(Serialize, Deserialize, specta::Type, Debug)]
pub enum UploadResult {
Success(String),
NotAuthenticated,
PlanCheckFailed,
UpgradeRequired,
}

#[derive(Serialize, Deserialize, specta::Type, Debug)]
pub struct VideoRecordingMetadata {
pub duration: f64,
pub size: f64,
}

#[derive(Clone, Serialize, Deserialize, specta::Type)]
pub struct PreCreatedVideo {
id: String,
Expand Down Expand Up @@ -377,9 +383,20 @@ type MutableState<'a, T> = State<'a, Arc<RwLock<T>>>;

#[tauri::command]
#[specta::specta]
async fn get_recording_options(state: MutableState<'_, App>) -> Result<RecordingOptions, ()> {
async fn get_recording_options(
app: AppHandle,
state: MutableState<'_, App>,
) -> Result<RecordingOptions, ()> {
let mut state = state.write().await;

// Load settings from disk if they exist
if let Ok(Some(settings)) = GeneralSettingsStore::get(&app) {
if let Some(config) = settings.recording_config {
state.start_recording_options.fps = config.fps;
state.start_recording_options.output_resolution = Some(config.resolution);
}
}

// If there's a saved audio input but no feed, initialize it
if let Some(audio_input_name) = state.start_recording_options.audio_input_name() {
if state.audio_input_feed.is_none() {
Expand All @@ -401,15 +418,28 @@ async fn get_recording_options(state: MutableState<'_, App>) -> Result<Recording
#[tauri::command]
#[specta::specta]
async fn set_recording_options(
app: AppHandle,
state: MutableState<'_, App>,
options: RecordingOptions,
) -> Result<(), String> {
// Update in-memory state
state
.write()
.await
.set_start_recording_options(options)
.set_start_recording_options(options.clone())
.await?;

// Update persistent settings
GeneralSettingsStore::update(&app, |settings| {
settings.recording_config = Some(RecordingConfig {
fps: options.fps,
resolution: options.output_resolution.unwrap_or_else(|| Resolution {
width: 1920,
height: 1080,
}),
});
})?;

Ok(())
}

Expand Down Expand Up @@ -910,6 +940,7 @@ async fn copy_video_to_clipboard(
Ok(())
}


#[derive(Serialize, Deserialize, specta::Type)]
pub struct VideoRecordingMetadata {
duration: f64,
Expand Down Expand Up @@ -1865,6 +1896,7 @@ pub async fn run() {
global_message_dialog,
show_window,
write_clipboard_string,
get_editor_total_frames,
])
.events(tauri_specta::collect_events![
RecordingOptionsChanged,
Expand Down Expand Up @@ -1998,11 +2030,13 @@ pub async fn run() {
audio_input_feed: None,
start_recording_options: RecordingOptions {
capture_target: ScreenCaptureTarget::Screen(CaptureScreen {
id: 1,
name: "Default".to_string(),
id: 0,
name: String::new(),
}),
camera_label: None,
audio_input_name: None,
fps: 30,
output_resolution: None,
},
current_recording: None,
pre_created_video: None,
Expand Down Expand Up @@ -2362,3 +2396,15 @@ trait EventExt: tauri_specta::Event {
}

impl<T: tauri_specta::Event> EventExt for T {}

#[tauri::command(async)]
#[specta::specta]
async fn get_editor_total_frames(app: AppHandle, video_id: String) -> Result<u32, String> {
let editor_instances = app.state::<EditorInstancesState>();
let instances = editor_instances.lock().await;

let instance = instances
.get(&video_id)
.ok_or_else(|| "Editor instance not found".to_string())?;
Ok(instance.get_total_frames())
}
16 changes: 8 additions & 8 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ pub fn list_cameras() -> Vec<String> {
pub async fn start_recording(app: AppHandle, state: MutableState<'_, App>) -> Result<(), String> {
let mut state = state.write().await;

// Get the recording config
let config = GeneralSettingsStore::get(&app)?
.and_then(|s| s.recording_config)
.unwrap_or_default();

// Update the recording options with the configured FPS
state.start_recording_options.fps = config.fps;

let id = uuid::Uuid::new_v4().to_string();

let recording_dir = app
Expand Down Expand Up @@ -152,7 +160,6 @@ pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Res

let now = Instant::now();
let completed_recording = current_recording.stop().await.map_err(|e| e.to_string())?;
println!("stopped recording in {:?}", now.elapsed());

if let Some(window) = CapWindowId::InProgressRecording.get(&app) {
window.hide().unwrap();
Expand All @@ -175,14 +182,7 @@ pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Res
};

let display_screenshot = screenshots_dir.join("display.jpg");
let now = Instant::now();
create_screenshot(display_output_path, display_screenshot.clone(), None).await?;
println!("created screenshot in {:?}", now.elapsed());

// let thumbnail = screenshots_dir.join("thumbnail.png");
// let now = Instant::now();
// create_thumbnail(display_screenshot, thumbnail, (100, 100)).await?;
// println!("created thumbnail in {:?}", now.elapsed());

let recording_dir = completed_recording.recording_dir.clone();

Expand Down
Loading

0 comments on commit 3ce758e

Please sign in to comment.