@@ -17,157 +17,174 @@ def sample_video(
17
17
import_path : str ,
18
18
skip_subfolders = False ,
19
19
video_sample_interval = constants .VIDEO_SAMPLE_INTERVAL ,
20
- video_start_time : T .Optional [str ] = None ,
21
20
video_duration_ratio = constants .VIDEO_DURATION_RATIO ,
21
+ video_start_time : T .Optional [str ] = None ,
22
22
skip_sample_errors : bool = False ,
23
23
rerun : bool = False ,
24
24
) -> None :
25
- if not os .path .exists (video_import_path ):
26
- raise exceptions .MapillaryFileNotFoundError (
27
- f"Video file or directory not found: { video_import_path } "
28
- )
29
-
30
25
if os .path .isdir (video_import_path ):
31
26
video_list = utils .get_video_file_list (
32
27
video_import_path , skip_subfolders , abs_path = True
33
28
)
34
29
video_dir = video_import_path
35
- else :
30
+ LOG .debug (f"Found %d videos in %s" , len (video_list ), video_dir )
31
+ elif os .path .isfile (video_import_path ):
36
32
video_list = [video_import_path ]
37
33
video_dir = os .path .dirname (video_import_path )
38
-
39
- LOG .debug (f"Found { len (video_list )} videos in { video_import_path } " )
34
+ else :
35
+ raise exceptions .MapillaryFileNotFoundError (
36
+ f"Video file or directory not found: { video_import_path } "
37
+ )
40
38
41
39
if rerun :
42
40
for video_path in video_list :
43
41
relpath = os .path .relpath (video_path , video_dir )
44
42
video_sample_path = os .path .join (import_path , relpath )
45
- LOG .info (f"Removing the sample directory { video_sample_path } " )
43
+ LOG .info (f"Removing the sample directory %s" , video_sample_path )
46
44
if os .path .isdir (video_sample_path ):
47
45
shutil .rmtree (video_sample_path )
48
46
elif os .path .isfile (video_sample_path ):
49
47
os .remove (video_sample_path )
50
48
51
- for video_path in video_list :
52
- video_start_time_dt : T . Optional [ datetime . datetime ] = None
53
- if video_start_time is not None :
49
+ video_start_time_dt : T . Optional [ datetime . datetime ] = None
50
+ if video_start_time is not None :
51
+ try :
54
52
video_start_time_dt = types .map_capture_time_to_datetime (video_start_time )
53
+ except ValueError as ex :
54
+ raise exceptions .MapillaryBadParameterError (str (ex ))
55
55
56
+ for video_path in video_list :
56
57
relpath = os .path .relpath (video_path , video_dir )
57
58
video_sample_path = os .path .join (import_path , relpath )
58
59
if os .path .exists (video_sample_path ):
59
60
LOG .warning (
60
- f"Skip sampling video { os .path .basename (video_path )} as it has been sampled in { video_sample_path } "
61
+ f"Skip sampling video %s as it has been sampled in %s" ,
62
+ os .path .basename (video_path ),
63
+ video_sample_path ,
61
64
)
62
65
continue
63
66
64
- # extract frames in the temporary folder and then rename it
65
- now = datetime .datetime .utcnow ()
66
- video_sample_path_temporary = os .path .join (
67
- os .path .dirname (video_sample_path ),
68
- f"{ os .path .basename (video_sample_path )} .{ os .getpid ()} .{ int (now .timestamp ())} " ,
69
- )
70
- os .makedirs (video_sample_path_temporary )
71
67
try :
72
- ffmpeg . extract_frames (
68
+ _sample_single_video (
73
69
video_path ,
74
- video_sample_path_temporary ,
75
- video_sample_interval ,
70
+ video_sample_path ,
71
+ sample_interval = video_sample_interval ,
72
+ duration_ratio = video_duration_ratio ,
73
+ start_time = video_start_time_dt ,
76
74
)
77
- if video_start_time_dt is None :
78
- video_start_time_dt = extract_video_start_time (video_path )
79
- insert_video_frame_timestamp (
80
- os .path .basename (video_path ),
81
- video_sample_path_temporary ,
82
- video_start_time_dt ,
83
- video_sample_interval ,
84
- video_duration_ratio ,
85
- )
86
- except (
87
- exceptions .MapillaryFileNotFoundError ,
88
- exceptions .MapillaryFFmpegNotFoundError ,
89
- ):
90
- raise
75
+ except ffmpeg .FFmpegNotFoundError as ex :
76
+ # fatal errors
77
+ raise exceptions .MapillaryFFmpegNotFoundError (str (ex )) from ex
91
78
except Exception :
92
79
if skip_sample_errors :
93
- LOG .warning (f"Skipping the error sampling { video_path } " , exc_info = True )
94
- else :
95
- raise
96
- else :
97
- LOG .debug (f"Renaming { video_sample_path_temporary } to { video_sample_path } " )
98
- try :
99
- os .rename (video_sample_path_temporary , video_sample_path )
100
- except IOError :
101
- # video_sample_path might have been created by another process during the sampling
102
80
LOG .warning (
103
- f"Skip the error renaming { video_sample_path } to { video_sample_path } " ,
104
- exc_info = True ,
81
+ f"Skipping the error sampling %s" , video_path , exc_info = True
105
82
)
106
- finally :
107
- if os .path .isdir (video_sample_path_temporary ):
108
- shutil .rmtree (video_sample_path_temporary )
83
+ else :
84
+ raise
109
85
110
86
111
- def extract_video_start_time (video_path : str ) -> datetime .datetime :
87
+ def _sample_single_video (
88
+ video_path : str ,
89
+ sample_path : str ,
90
+ sample_interval : float ,
91
+ duration_ratio : float ,
92
+ start_time : T .Optional [datetime .datetime ] = None ,
93
+ ) -> None :
94
+ # extract frames in the temporary folder and then rename it
95
+ now = datetime .datetime .utcnow ()
96
+ tmp_sample_path = os .path .join (
97
+ os .path .dirname (sample_path ),
98
+ f"{ os .path .basename (sample_path )} .{ os .getpid ()} .{ int (now .timestamp ())} " ,
99
+ )
100
+ os .makedirs (tmp_sample_path )
101
+ LOG .debug ("Sampling in the temporary sample path %s" , tmp_sample_path )
102
+
103
+ try :
104
+ if start_time is None :
105
+ duration , video_creation_time = extract_duration_and_creation_time (
106
+ video_path
107
+ )
108
+ start_time = video_creation_time - datetime .timedelta (seconds = duration )
109
+ ffmpeg .extract_frames (
110
+ video_path ,
111
+ tmp_sample_path ,
112
+ sample_interval ,
113
+ )
114
+ insert_video_frame_timestamp (
115
+ os .path .basename (video_path ),
116
+ tmp_sample_path ,
117
+ start_time ,
118
+ sample_interval = sample_interval ,
119
+ duration_ratio = duration_ratio ,
120
+ )
121
+ if os .path .isdir (sample_path ):
122
+ shutil .rmtree (sample_path )
123
+ os .rename (tmp_sample_path , sample_path )
124
+ finally :
125
+ if os .path .isdir (tmp_sample_path ):
126
+ LOG .debug ("Cleaning up the temporary sample path %s" , tmp_sample_path )
127
+ shutil .rmtree (tmp_sample_path )
128
+
129
+
130
+ def extract_duration_and_creation_time (
131
+ video_path : str ,
132
+ ) -> T .Tuple [float , datetime .datetime ]:
112
133
streams = ffmpeg .probe_video_streams (video_path )
113
134
if not streams :
114
- raise exceptions .MapillaryVideoError (
115
- f"Failed to find video streams in { video_path } "
116
- )
135
+ raise exceptions .MapillaryVideoError (f"No video streams found in { video_path } " )
117
136
137
+ # TODO: we should use the one with max resolution
118
138
if 2 <= len (streams ):
119
139
LOG .warning (
120
- "Found more than one (%s) video streams -- will use the first stream " ,
140
+ "Found %d video streams -- will use the first one " ,
121
141
len (streams ),
122
142
)
123
-
124
143
stream = streams [0 ]
125
144
126
- duration_str = stream [ "duration" ]
145
+ duration_str = stream . get ( "duration" )
127
146
try :
128
- duration = float (duration_str )
147
+ # cast for type checking
148
+ duration = float (T .cast (str , duration_str ))
129
149
except (TypeError , ValueError ) as exc :
130
150
raise exceptions .MapillaryVideoError (
131
151
f"Failed to find video stream duration { duration_str } from video { video_path } "
132
152
) from exc
133
-
134
153
LOG .debug ("Extracted video duration: %s" , duration )
135
154
136
155
time_string = stream .get ("tags" , {}).get ("creation_time" )
137
156
if time_string is None :
138
157
raise exceptions .MapillaryVideoError (
139
158
f"Failed to find video creation_time in { video_path } "
140
159
)
141
-
142
160
try :
143
- video_end_time = datetime .datetime .strptime (time_string , TIME_FORMAT )
161
+ creation_time = datetime .datetime .strptime (time_string , TIME_FORMAT )
144
162
except ValueError :
145
163
try :
146
- video_end_time = datetime .datetime .strptime (time_string , TIME_FORMAT_2 )
164
+ creation_time = datetime .datetime .strptime (time_string , TIME_FORMAT_2 )
147
165
except ValueError :
148
166
raise exceptions .MapillaryVideoError (
149
167
f"Failed to parse { time_string } as { TIME_FORMAT } or { TIME_FORMAT_2 } "
150
168
)
169
+ LOG .debug ("Extracted video creation time: %s" , creation_time )
151
170
152
- LOG .debug ("Extracted video end time (creation time): %s" , video_end_time )
153
-
154
- return video_end_time - datetime .timedelta (seconds = duration )
171
+ return duration , creation_time
155
172
156
173
157
174
def insert_video_frame_timestamp (
158
175
video_basename : str ,
159
- video_sampling_path : str ,
176
+ sample_path : str ,
160
177
start_time : datetime .datetime ,
161
- sample_interval : float = constants . VIDEO_SAMPLE_INTERVAL ,
162
- duration_ratio : float = constants . VIDEO_DURATION_RATIO ,
178
+ sample_interval : float ,
179
+ duration_ratio : float ,
163
180
) -> None :
164
- for image in utils .get_image_file_list (video_sampling_path , abs_path = True ):
181
+ for image in utils .get_image_file_list (sample_path , abs_path = True ):
165
182
idx = ffmpeg .extract_idx_from_frame_filename (
166
183
video_basename ,
167
184
os .path .basename (image ),
168
185
)
169
186
if idx is None :
170
- LOG .warning (f"Unabele to extract timestamp from the sample image { image } " )
187
+ LOG .warning (f"Unabele to find the sample index from %s" , image )
171
188
continue
172
189
173
190
seconds = idx * sample_interval * duration_ratio
0 commit comments