diff --git a/apps/desktop/src-tauri/src/export.rs b/apps/desktop/src-tauri/src/export.rs index a7ae71de..4fad366a 100644 --- a/apps/desktop/src-tauri/src/export.rs +++ b/apps/desktop/src-tauri/src/export.rs @@ -31,12 +31,6 @@ pub async fn export_video( } }; - // Get FPS from general settings - let fps = GeneralSettingsStore::get(&app)? - .and_then(|s| s.recording_config) - .unwrap_or_default() - .fps; - // Get camera metadata if it exists let camera_metadata = get_video_metadata(app.clone(), video_id.clone(), Some(VideoType::Camera)) @@ -50,10 +44,8 @@ pub async fn export_video( .unwrap_or(screen_metadata.duration), ); - // Use configured FPS for output video - let total_frames = (duration * fps as f64).round() as u32; - 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(); diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index f6558086..d0cb1448 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -1880,6 +1880,7 @@ pub async fn run() { global_message_dialog, show_window, write_clipboard_string, + get_editor_total_frames, ]) .events(tauri_specta::collect_events![ RecordingOptionsChanged, @@ -2379,3 +2380,15 @@ trait EventExt: tauri_specta::Event { } impl EventExt for T {} + +#[tauri::command(async)] +#[specta::specta] +async fn get_editor_total_frames(app: AppHandle, video_id: String) -> Result { + let editor_instances = app.state::(); + 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()) +} diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index bb8a12dc..bc10c24a 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -160,6 +160,9 @@ async showWindow(window: ShowCapWindow) : Promise { }, async writeClipboardString(text: string) : Promise { return await TAURI_INVOKE("write_clipboard_string", { text }); +}, +async getEditorTotalFrames(videoId: string) : Promise { + return await TAURI_INVOKE("get_editor_total_frames", { videoId }); } } @@ -284,7 +287,7 @@ export type TimelineSegment = { recordingSegment: number | null; timescale: numb export type UploadMode = { Initial: { pre_created_video: PreCreatedVideo | null } } | "Reupload" export type UploadProgress = { stage: string; progress: number; message: string } export type UploadResult = { Success: string } | "NotAuthenticated" | "PlanCheckFailed" | "UpgradeRequired" -export type Video = { duration: number; width: number; height: number } +export type Video = { duration: number; width: number; height: number; fps: number } export type VideoRecordingMetadata = { duration: number; size: number } export type VideoType = "screen" | "output" | "camera" export type XY = { x: T; y: T } diff --git a/crates/editor/src/editor_instance.rs b/crates/editor/src/editor_instance.rs index 431b9982..da8985d0 100644 --- a/crates/editor/src/editor_instance.rs +++ b/crates/editor/src/editor_instance.rs @@ -35,6 +35,7 @@ pub struct EditorInstance { ), ws_shutdown: Arc>>>, pub segments: Arc>, + pub total_frames: u32, } impl EditorInstance { @@ -59,14 +60,17 @@ impl EditorInstance { if !project_path.exists() { println!("Video path {} not found!", project_path.display()); - // return Err(format!("Video path {} not found!", path.display())); panic!("Video path {} not found!", project_path.display()); } let meta = cap_project::RecordingMeta::load_for_project(&project_path).unwrap(); - let recordings = ProjectRecordings::new(&meta); + // Calculate total frames based on actual video duration and fps + let duration = recordings.duration(); + let fps = recordings.segments[0].display.fps(); + let total_frames = (duration * fps as f64).round() as u32; + let render_options = RenderOptions { screen_size: XY::new( recordings.segments[0].display.width, @@ -115,6 +119,7 @@ impl EditorInstance { project_config: watch::channel(meta.project_config()), ws_shutdown: Arc::new(StdMutex::new(Some(ws_shutdown))), segments: Arc::new(segments), + total_frames, }); this.state.lock().await.preview_task = @@ -271,6 +276,10 @@ impl EditorInstance { } }) } + + pub fn get_total_frames(&self) -> u32 { + self.total_frames + } } impl Drop for EditorInstance { diff --git a/crates/rendering/src/project_recordings.rs b/crates/rendering/src/project_recordings.rs index b6205744..fa5eab24 100644 --- a/crates/rendering/src/project_recordings.rs +++ b/crates/rendering/src/project_recordings.rs @@ -9,6 +9,7 @@ pub struct Video { pub duration: f64, pub width: u32, pub height: u32, + pub fps: u32, } impl Video { @@ -26,12 +27,20 @@ impl Video { .video() .map_err(|e| format!("Failed to get video decoder: {}", e))?; + let rate = stream.avg_frame_rate(); + let fps = rate.numerator() as f64 / rate.denominator() as f64; + Ok(Video { width: video_decoder.width(), height: video_decoder.height(), duration: input.duration() as f64 / 1_000_000.0, + fps: fps.round() as u32, }) } + + pub fn fps(&self) -> u32 { + self.fps + } } #[derive(Debug, Clone, Copy, Serialize, Type)]