@@ -45,9 +45,9 @@ use cap_rendering::{ProjectRecordingsMeta, RenderedFrame};
45
45
use clipboard_rs:: common:: RustImage ;
46
46
use clipboard_rs:: { Clipboard , ClipboardContext } ;
47
47
use editor_window:: { EditorInstances , WindowEditorInstance } ;
48
+ use ffmpeg:: ffi:: AV_TIME_BASE ;
48
49
use general_settings:: GeneralSettingsStore ;
49
50
use kameo:: { Actor , actor:: ActorRef } ;
50
- use mp4:: Mp4Reader ;
51
51
use notifications:: NotificationType ;
52
52
use png:: { ColorType , Encoder } ;
53
53
use recording:: InProgressRecording ;
@@ -64,7 +64,7 @@ use std::{
64
64
collections:: BTreeMap ,
65
65
fs:: File ,
66
66
future:: Future ,
67
- io:: { BufReader , BufWriter } ,
67
+ io:: BufWriter ,
68
68
marker:: PhantomData ,
69
69
path:: { Path , PathBuf } ,
70
70
process:: Command ,
@@ -421,11 +421,6 @@ async fn create_screenshot(
421
421
println ! ( "Creating screenshot: input={input:?}, output={output:?}, size={size:?}" ) ;
422
422
423
423
let result: Result < ( ) , String > = tokio:: task:: spawn_blocking ( move || -> Result < ( ) , String > {
424
- ffmpeg:: init ( ) . map_err ( |e| {
425
- eprintln ! ( "Failed to initialize ffmpeg: {e}" ) ;
426
- e. to_string ( )
427
- } ) ?;
428
-
429
424
let mut ictx = ffmpeg:: format:: input ( & input) . map_err ( |e| {
430
425
eprintln ! ( "Failed to create input context: {e}" ) ;
431
426
e. to_string ( )
@@ -584,11 +579,11 @@ async fn copy_file_to_path(app: AppHandle, src: String, dst: String) -> Result<(
584
579
return Err ( format ! ( "Source file {src} does not exist" ) ) ;
585
580
}
586
581
587
- if !is_screenshot && !is_gif && !is_valid_mp4 ( src_path) {
582
+ if !is_screenshot && !is_gif && !is_valid_video ( src_path) {
588
583
let mut attempts = 0 ;
589
584
while attempts < 10 {
590
585
std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) ;
591
- if is_valid_mp4 ( src_path) {
586
+ if is_valid_video ( src_path) {
592
587
break ;
593
588
}
594
589
attempts += 1 ;
@@ -631,8 +626,8 @@ async fn copy_file_to_path(app: AppHandle, src: String, dst: String) -> Result<(
631
626
continue ;
632
627
}
633
628
634
- if !is_screenshot && !is_gif && !is_valid_mp4 ( std:: path:: Path :: new ( & dst) ) {
635
- last_error = Some ( "Destination file is not a valid MP4 " . to_string ( ) ) ;
629
+ if !is_screenshot && !is_gif && !is_valid_video ( std:: path:: Path :: new ( & dst) ) {
630
+ last_error = Some ( "Destination file is not a valid" . to_string ( ) ) ;
636
631
let _ = tokio:: fs:: remove_file ( & dst) . await ;
637
632
attempts += 1 ;
638
633
tokio:: time:: sleep ( tokio:: time:: Duration :: from_secs ( 1 ) ) . await ;
@@ -682,16 +677,15 @@ async fn copy_file_to_path(app: AppHandle, src: String, dst: String) -> Result<(
682
677
Err ( last_error. unwrap_or_else ( || "Maximum retry attempts exceeded" . to_string ( ) ) )
683
678
}
684
679
685
- pub fn is_valid_mp4 ( path : & std:: path:: Path ) -> bool {
686
- if let Ok ( file) = std:: fs:: File :: open ( path) {
687
- let file_size = match file. metadata ( ) {
688
- Ok ( metadata) => metadata. len ( ) ,
689
- Err ( _) => return false ,
690
- } ;
691
- let reader = std:: io:: BufReader :: new ( file) ;
692
- Mp4Reader :: read_header ( reader, file_size) . is_ok ( )
693
- } else {
694
- false
680
+ pub fn is_valid_video ( path : & std:: path:: Path ) -> bool {
681
+ match ffmpeg:: format:: input ( path) {
682
+ Ok ( input_context) => {
683
+ // Check if we have at least one video stream
684
+ input_context
685
+ . streams ( )
686
+ . any ( |stream| stream. parameters ( ) . medium ( ) == ffmpeg:: media:: Type :: Video )
687
+ }
688
+ Err ( _) => false ,
695
689
}
696
690
}
697
691
@@ -877,23 +871,19 @@ async fn get_video_metadata(path: PathBuf) -> Result<VideoRecordingMetadata, Str
877
871
let recording_meta = RecordingMeta :: load_for_project ( & path) . map_err ( |v| v. to_string ( ) ) ?;
878
872
879
873
fn get_duration_for_path ( path : PathBuf ) -> Result < f64 , String > {
880
- let reader = BufReader :: new (
881
- File :: open ( & path) . map_err ( |e| format ! ( "Failed to open video file: {e}" ) ) ?,
882
- ) ;
883
- let file_size = path
884
- . metadata ( )
885
- . map_err ( |e| format ! ( "Failed to get file metadata: {e}" ) ) ?
886
- . len ( ) ;
887
-
888
- let current_duration = match Mp4Reader :: read_header ( reader, file_size) {
889
- Ok ( mp4) => mp4. duration ( ) . as_secs_f64 ( ) ,
890
- Err ( e) => {
891
- println ! ( "Failed to read MP4 header: {e}. Falling back to default duration." ) ;
892
- 0.0_f64
893
- }
894
- } ;
874
+ let input =
875
+ ffmpeg:: format:: input ( & path) . map_err ( |e| format ! ( "Failed to open video file: {e}" ) ) ?;
876
+
877
+ let raw_duration = input. duration ( ) ;
878
+ if raw_duration <= 0 {
879
+ return Err ( format ! (
880
+ "Unknown or invalid duration for video file: {:?}" ,
881
+ path
882
+ ) ) ;
883
+ }
895
884
896
- Ok ( current_duration)
885
+ let duration = raw_duration as f64 / AV_TIME_BASE as f64 ;
886
+ Ok ( duration)
897
887
}
898
888
899
889
let display_paths = match & recording_meta. inner {
@@ -915,7 +905,10 @@ async fn get_video_metadata(path: PathBuf) -> Result<VideoRecordingMetadata, Str
915
905
let duration = display_paths
916
906
. into_iter ( )
917
907
. map ( get_duration_for_path)
918
- . sum :: < Result < _ , _ > > ( ) ?;
908
+ . try_fold ( 0f64 , |acc, item| -> Result < f64 , String > {
909
+ let d = item?;
910
+ Ok ( acc + d)
911
+ } ) ?;
919
912
920
913
let ( width, height) = ( 1920 , 1080 ) ;
921
914
let fps = 30 ;
@@ -1841,6 +1834,12 @@ type LoggingHandle = tracing_subscriber::reload::Handle<Option<DynLoggingLayer>,
1841
1834
1842
1835
#[ cfg_attr( mobile, tauri:: mobile_entry_point) ]
1843
1836
pub async fn run ( recording_logging_handle : LoggingHandle ) {
1837
+ ffmpeg:: init ( )
1838
+ . map_err ( |e| {
1839
+ error ! ( "Failed to initialize ffmpeg: {e}" ) ;
1840
+ } )
1841
+ . ok ( ) ;
1842
+
1844
1843
let tauri_context = tauri:: generate_context!( ) ;
1845
1844
1846
1845
let specta_builder = tauri_specta:: Builder :: new ( )
0 commit comments