1
- use std:: { collections:: HashMap , path:: PathBuf } ;
1
+ use std:: { collections:: HashMap , path:: PathBuf , task :: Poll } ;
2
2
3
3
use once_cell:: sync:: Lazy ;
4
4
use parking_lot:: Mutex ;
5
+ use poll_promise:: Promise ;
5
6
6
7
// FFmpeg 5.1 "Riemann" is from 2022-07-22.
7
8
// It's simply the oldest I tested manually as of writing. We might be able to go lower.
8
9
// However, we also know that FFmpeg 4.4 is already no longer working.
9
10
pub const FFMPEG_MINIMUM_VERSION_MAJOR : u32 = 5 ;
10
11
pub const FFMPEG_MINIMUM_VERSION_MINOR : u32 = 1 ;
11
12
13
+ pub type FfmpegVersionResult = Result < FFmpegVersion , FFmpegVersionParseError > ;
14
+
12
15
/// A successfully parsed `FFmpeg` version.
13
16
#[ derive( Clone , Debug , PartialEq , Eq ) ]
14
17
pub struct FFmpegVersion {
@@ -78,61 +81,31 @@ impl FFmpegVersion {
78
81
/// version string. Since version strings can get pretty wild, we don't want to fail in this case.
79
82
///
80
83
/// Internally caches the result per path together with its modification time to re-run/parse the version only if the file has changed.
81
- pub fn for_executable ( path : Option < & std:: path:: Path > ) -> Result < Self , FFmpegVersionParseError > {
82
- type VersionMap = HashMap <
83
- PathBuf ,
84
- (
85
- Option < std:: time:: SystemTime > ,
86
- Result < FFmpegVersion , FFmpegVersionParseError > ,
87
- ) ,
88
- > ;
89
- static CACHE : Lazy < Mutex < VersionMap > > = Lazy :: new ( || Mutex :: new ( HashMap :: new ( ) ) ) ;
90
-
84
+ pub fn for_executable_poll ( path : Option < & std:: path:: Path > ) -> Poll < FfmpegVersionResult > {
91
85
re_tracing:: profile_function!( ) ;
92
86
93
- // Retrieve file modification time first.
94
- let modification_time = if let Some ( path) = path {
95
- path. metadata ( )
96
- . map_err ( |err| {
97
- FFmpegVersionParseError :: RetrieveFileModificationTime ( err. to_string ( ) )
98
- } ) ?
99
- . modified ( )
100
- . ok ( )
101
- } else {
102
- None
103
- } ;
104
-
105
- // Check first if we already have the version cached.
106
- let mut cache = CACHE . lock ( ) ;
107
- let cache_key = path. unwrap_or ( std:: path:: Path :: new ( "ffmpeg" ) ) ;
108
- if let Some ( cached) = cache. get ( cache_key) {
109
- if modification_time == cached. 0 {
110
- return cached. 1 . clone ( ) ;
111
- }
112
- }
113
-
114
- // Run FFmpeg (or whatever was passed to us) to get the version.
115
- let raw_version = if let Some ( path) = path {
116
- ffmpeg_sidecar:: version:: ffmpeg_version_with_path ( path)
117
- } else {
118
- ffmpeg_sidecar:: version:: ffmpeg_version ( )
119
- }
120
- . map_err ( |err| FFmpegVersionParseError :: RunFFmpeg ( err. to_string ( ) ) ) ?;
121
-
122
- let version = if let Some ( version) = Self :: parse ( & raw_version) {
123
- Ok ( version)
124
- } else {
125
- Err ( FFmpegVersionParseError :: ParseVersion {
126
- raw_version : raw_version. clone ( ) ,
127
- } )
128
- } ;
87
+ let modification_time = file_modification_time ( path) ?;
88
+ VersionCache :: global ( |cache| {
89
+ cache
90
+ . version ( path, modification_time)
91
+ . poll ( )
92
+ . map ( |r| r. clone ( ) )
93
+ } )
94
+ }
129
95
130
- cache. insert (
131
- cache_key. to_path_buf ( ) ,
132
- ( modification_time, version. clone ( ) ) ,
133
- ) ;
96
+ /// Like [`Self::for_executable_poll`], but blocks until the version is ready.
97
+ ///
98
+ /// WARNING: this blocks for half a second on Mac the first time this is called with a given path, maybe more on other platforms.
99
+ pub fn for_executable_blocking ( path : Option < & std:: path:: Path > ) -> FfmpegVersionResult {
100
+ re_tracing:: profile_function!( ) ;
134
101
135
- version
102
+ let modification_time = file_modification_time ( path) ?;
103
+ VersionCache :: global ( |cache| {
104
+ cache
105
+ . version ( path, modification_time)
106
+ . block_until_ready ( )
107
+ . clone ( )
108
+ } )
136
109
}
137
110
138
111
/// Returns true if this version is compatible with Rerun's minimum requirements.
@@ -143,6 +116,72 @@ impl FFmpegVersion {
143
116
}
144
117
}
145
118
119
+ fn file_modification_time (
120
+ path : Option < & std:: path:: Path > ,
121
+ ) -> Result < Option < std:: time:: SystemTime > , FFmpegVersionParseError > {
122
+ Ok ( if let Some ( path) = path {
123
+ path. metadata ( )
124
+ . map_err ( |err| FFmpegVersionParseError :: RetrieveFileModificationTime ( err. to_string ( ) ) ) ?
125
+ . modified ( )
126
+ . ok ( )
127
+ } else {
128
+ None
129
+ } )
130
+ }
131
+
132
+ #[ derive( Default ) ]
133
+ struct VersionCache (
134
+ HashMap < PathBuf , ( Option < std:: time:: SystemTime > , Promise < FfmpegVersionResult > ) > ,
135
+ ) ;
136
+
137
+ impl VersionCache {
138
+ fn global < R > ( f : impl FnOnce ( & mut Self ) -> R ) -> R {
139
+ static CACHE : Lazy < Mutex < VersionCache > > = Lazy :: new ( || Mutex :: new ( VersionCache :: default ( ) ) ) ;
140
+ f ( & mut CACHE . lock ( ) )
141
+ }
142
+
143
+ fn version (
144
+ & mut self ,
145
+ path : Option < & std:: path:: Path > ,
146
+ modification_time : Option < std:: time:: SystemTime > ,
147
+ ) -> & Promise < FfmpegVersionResult > {
148
+ let Self ( cache) = self ;
149
+
150
+ let cache_key = path. unwrap_or ( std:: path:: Path :: new ( "ffmpeg" ) ) . to_path_buf ( ) ;
151
+
152
+ match cache. entry ( cache_key) {
153
+ std:: collections:: hash_map:: Entry :: Occupied ( entry) => & entry. into_mut ( ) . 1 ,
154
+ std:: collections:: hash_map:: Entry :: Vacant ( entry) => {
155
+ let path = path. map ( |path| path. to_path_buf ( ) ) ;
156
+ let version =
157
+ Promise :: spawn_thread ( "ffmpeg_version" , move || ffmpeg_version ( path. as_ref ( ) ) ) ;
158
+ & entry. insert ( ( modification_time, version) ) . 1
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ fn ffmpeg_version (
165
+ path : Option < & std:: path:: PathBuf > ,
166
+ ) -> Result < FFmpegVersion , FFmpegVersionParseError > {
167
+ re_tracing:: profile_function!( "ffmpeg_version_with_path" ) ;
168
+
169
+ let raw_version = if let Some ( path) = path {
170
+ ffmpeg_sidecar:: version:: ffmpeg_version_with_path ( path)
171
+ } else {
172
+ ffmpeg_sidecar:: version:: ffmpeg_version ( )
173
+ }
174
+ . map_err ( |err| FFmpegVersionParseError :: RunFFmpeg ( err. to_string ( ) ) ) ?;
175
+
176
+ if let Some ( version) = FFmpegVersion :: parse ( & raw_version) {
177
+ Ok ( version)
178
+ } else {
179
+ Err ( FFmpegVersionParseError :: ParseVersion {
180
+ raw_version : raw_version. clone ( ) ,
181
+ } )
182
+ }
183
+ }
184
+
146
185
#[ cfg( test) ]
147
186
mod tests {
148
187
use crate :: decode:: ffmpeg_h264:: FFmpegVersion ;
0 commit comments