Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(73)

Side by Side Diff: webrtc/tools/run_video_analysis.py

Issue 2746413002: Improve error handling for ffmpeg operations (Closed)
Patch Set: add custom exceptions Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. 2 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3 # 3 #
4 # Use of this source code is governed by a BSD-style license 4 # Use of this source code is governed by a BSD-style license
5 # that can be found in the LICENSE file in the root of the source 5 # that can be found in the LICENSE file in the root of the source
6 # tree. An additional intellectual property rights grant can be found 6 # tree. An additional intellectual property rights grant can be found
7 # in the file PATENTS. All contributing project authors may 7 # in the file PATENTS. All contributing project authors may
8 # be found in the AUTHORS file in the root of the source tree. 8 # be found in the AUTHORS file in the root of the source tree.
9 9
10 import optparse 10 import optparse
11 import os 11 import os
12 import subprocess 12 import subprocess
13 import sys 13 import sys
14 import time 14 import time
15 import glob 15 import glob
16 import re 16 import re
17 import shutil
17 18
18 # Used to time-stamp output files and directories 19 # Used to time-stamp output files and directories
19 CURRENT_TIME = time.strftime("%d_%m_%Y-%H:%M:%S") 20 CURRENT_TIME = time.strftime("%d_%m_%Y-%H:%M:%S")
20 21
22 class Error(Exception):
23 pass
24
25 class FfmpegError(Error):
26 pass
27
28 class MagewellError(Error):
29 pass
30
21 def _ParseArgs(): 31 def _ParseArgs():
22 """Registers the command-line options.""" 32 """Registers the command-line options."""
23 usage = 'usage: %prog [options]' 33 usage = 'usage: %prog [options]'
24 parser = optparse.OptionParser(usage=usage) 34 parser = optparse.OptionParser(usage=usage)
25 35
26 parser.add_option('--frame_width', type='string', default='1280', 36 parser.add_option('--frame_width', type='string', default='1280',
27 help='Width of the recording. Default: %default') 37 help='Width of the recording. Default: %default')
28 parser.add_option('--frame_height', type='string', default='720', 38 parser.add_option('--frame_height', type='string', default='720',
29 help='Height of the recording. Default: %default') 39 help='Height of the recording. Default: %default')
30 parser.add_option('--framerate', type='string', default='60', 40 parser.add_option('--framerate', type='string', default='60',
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after
173 magewell_usb_ports.append(directory) 183 magewell_usb_ports.append(directory)
174 184
175 test_magewell_path = str(test_magewell_device).split('/') 185 test_magewell_path = str(test_magewell_device).split('/')
176 for directory in test_magewell_path: 186 for directory in test_magewell_path:
177 187
178 # Find the folder with pattern "N-N", e.g. "4-3" or \ 188 # Find the folder with pattern "N-N", e.g. "4-3" or \
179 # "[USB bus ID]-[USB port]" 189 # "[USB bus ID]-[USB port]"
180 if re.match(r'^\d-\d$', directory): 190 if re.match(r'^\d-\d$', directory):
181 magewell_usb_ports.append(directory) 191 magewell_usb_ports.append(directory)
182 192
183 print '\nResetting USB ports where magewell devices are connected...' 193 try:
194 # Abort early if no devices are found.
195 if len(magewell_usb_ports) == 0:
196 raise MagewellError('No magewell devices found.')
184 197
185 # Use the USB bus and port ID (e.g. 4-3) to unbind and bind the USB devices 198 print '\nResetting USB ports where magewell devices are connected...'
186 # (i.e. soft eject and insert). 199
187 try: 200 # Use the USB bus and port ID (e.g. 4-3) to unbind and bind the USB devices
201 # (i.e. soft eject and insert).
188 for usb_port in magewell_usb_ports: 202 for usb_port in magewell_usb_ports:
189 echo_cmd = ['echo', usb_port] 203 echo_cmd = ['echo', usb_port]
190 unbind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/unbind'] 204 unbind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/unbind']
191 bind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/bind'] 205 bind_cmd = ['sudo', 'tee', '/sys/bus/usb/drivers/usb/bind']
192 206
193 # TODO(jansson) Figure out a way to call on echo once for bind & unbind 207 # TODO(jansson) Figure out a way to call on echo once for bind & unbind
194 # if possible. 208 # if possible.
195 echo_unbind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE) 209 echo_unbind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
196 unbind = subprocess.Popen(unbind_cmd, stdin=echo_unbind.stdout) 210 unbind = subprocess.Popen(unbind_cmd, stdin=echo_unbind.stdout)
197 echo_unbind.stdout.close() 211 echo_unbind.stdout.close()
198 unbind.communicate() 212 unbind.communicate()
199 unbind.wait() 213 unbind.wait()
200 214
201 echo_bind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE) 215 echo_bind = subprocess.Popen(echo_cmd, stdout=subprocess.PIPE)
202 bind = subprocess.Popen(bind_cmd, stdin=echo_bind.stdout) 216 bind = subprocess.Popen(bind_cmd, stdin=echo_bind.stdout)
203 echo_bind.stdout.close() 217 echo_bind.stdout.close()
204 bind.communicate() 218 bind.communicate()
205 bind.wait() 219 bind.wait()
206 except OSError as e: 220 except MagewellError:
kjellander_webrtc 2017/03/14 14:39:58 catching only this and just re-throwing it doesn't
janssonWebRTC 2017/03/14 16:15:15 Done.
207 print 'Error while resetting magewell devices: ' + e
208 raise 221 raise
209 222 else:
210 print 'Reset done!\n' 223 print 'Reset done!\n'
211 224
212 225
213 def StartRecording(options, record_paths): 226 def StartRecording(options, ref_file_location, test_file_location):
214 """Starts recording from the two specified video devices. 227 """Starts recording from the two specified video devices.
215 228
216 Args: 229 Args:
217 options(object): Contains all the provided command line options. 230 options(object): Contains all the provided command line options.
218 record_paths(dict): key: value pair with reference and test file 231 record_paths(dict): key: value pair with reference and test file
219 absolute paths. 232 absolute paths.
220 """ 233 """
221 ref_file_name = '%s_%s_ref.%s' % (options.app_name, CURRENT_TIME, 234 ref_file_name = '%s_%s_ref.%s' % (options.app_name, CURRENT_TIME,
222 options.video_container) 235 options.video_container)
223 ref_file_location = os.path.join(record_paths['ref_rec_location'], 236 ref_file = os.path.join(ref_file_location, ref_file_name)
224 ref_file_name)
225 237
226 test_file_name = '%s_%s_test.%s' % (options.app_name, CURRENT_TIME, 238 test_file_name = '%s_%s_test.%s' % (options.app_name, CURRENT_TIME,
227 options.video_container) 239 options.video_container)
228 test_file_location = os.path.join(record_paths['test_rec_location'], 240 test_file = os.path.join(test_file_location, test_file_name)
229 test_file_name)
230 241
231 # Reference video recorder command line. 242 # Reference video recorder command line.
232 ref_cmd = [ 243 ref_cmd = [
233 options.ffmpeg, 244 options.ffmpeg,
234 '-v', 'error', 245 '-v', 'error',
235 '-s', options.frame_width + 'x' + options.frame_height, 246 '-s', options.frame_width + 'x' + options.frame_height,
236 '-framerate', options.framerate, 247 '-framerate', options.framerate,
237 '-f', options.recording_api, 248 '-f', options.recording_api,
238 '-i', options.ref_video_device, 249 '-i', options.ref_video_device,
239 '-pix_fmt', options.pixel_format, 250 '-pix_fmt', options.pixel_format,
240 '-s', options.frame_width + 'x' + options.frame_height, 251 '-s', options.frame_width + 'x' + options.frame_height,
241 '-t', options.ref_duration, 252 '-t', options.ref_duration,
242 '-framerate', options.framerate, 253 '-framerate', options.framerate,
243 ref_file_location 254 ref_file
244 ] 255 ]
245 256
246 # Test video recorder command line. 257 # Test video recorder command line.
247 test_cmd = [ 258 test_cmd = [
248 options.ffmpeg, 259 options.ffmpeg,
249 '-v', 'error', 260 '-v', 'error',
250 '-s', options.frame_width + 'x' + options.frame_height, 261 '-s', options.frame_width + 'x' + options.frame_height,
251 '-framerate', options.framerate, 262 '-framerate', options.framerate,
252 '-f', options.recording_api, 263 '-f', options.recording_api,
253 '-i', options.test_video_device, 264 '-i', options.test_video_device,
254 '-pix_fmt', options.pixel_format, 265 '-pix_fmt', options.pixel_format,
255 '-s', options.frame_width + 'x' + options.frame_height, 266 '-s', options.frame_width + 'x' + options.frame_height,
256 '-t', options.test_duration, 267 '-t', options.test_duration,
257 '-framerate', options.framerate, 268 '-framerate', options.framerate,
258 test_file_location 269 test_file
259 ] 270 ]
260 print 'Trying to record from reference recorder...' 271 try:
261 ref_recorder = subprocess.Popen(ref_cmd, stderr=sys.stderr) 272 print 'Trying to record from reference recorder...'
273 ref_recorder = subprocess.Popen(ref_cmd, stderr=sys.stderr)
262 274
263 # Start the 2nd recording a little later to ensure the 1st one has started. 275 # Start the 2nd recording a little later to ensure the 1st one has started.
264 # TODO(jansson) Check that the ref_recorder output file exists rather than 276 # TODO(jansson) Check that the ref_recorder output file exists rather than
265 # using sleep. 277 # using sleep.
266 time.sleep(options.time_between_recordings) 278 time.sleep(options.time_between_recordings)
267 print 'Trying to record from test recorder...' 279 print 'Trying to record from test recorder...'
268 test_recorder = subprocess.Popen(test_cmd, stderr=sys.stderr) 280 test_recorder = subprocess.Popen(test_cmd, stderr=sys.stderr)
269 test_recorder.wait() 281 test_recorder.wait()
270 ref_recorder.wait() 282 ref_recorder.wait()
271 283
272 # ffmpeg does not abort when it fails, need to check return code. 284 # ffmpeg does not abort when it fails, need to check return code.
273 assert ref_recorder.returncode == 0, ( 285 if ref_recorder.returncode != 0 or test_recorder.returncode != 0:
274 'Ref recording failed, check ffmpeg output and device: %s'
275 % options.ref_video_device)
276 assert test_recorder.returncode == 0, (
277 'Test recording failed, check ffmpeg output and device: %s'
278 % options.test_video_device)
279 286
280 print 'Ref file recorded to: ' + os.path.abspath(ref_file_location) 287 # Cleanup recording directories.
281 print 'Test file recorded to: ' + os.path.abspath(test_file_location) 288 shutil.rmtree(ref_file_location)
282 print 'Recording done!\n' 289 shutil.rmtree(test_file_location)
283 return FlipAndCropRecordings(options, test_file_name, 290 raise FfmpegError('Recording failed, check ffmpeg output.')
284 record_paths['test_rec_location'], ref_file_name, 291 except FfmpegError:
kjellander_webrtc 2017/03/14 14:39:58 Same here.
janssonWebRTC 2017/03/14 16:15:15 Done.
285 record_paths['ref_rec_location']) 292 raise
293 else:
294 print 'Ref file recorded to: ' + os.path.abspath(ref_file)
295 print 'Test file recorded to: ' + os.path.abspath(test_file)
296 print 'Recording done!\n'
297 return FlipAndCropRecordings(options, test_file_name, test_file_location,
298 ref_file_name, ref_file_location)
kjellander_webrtc 2017/03/14 14:39:58 It's either alight with the above ( or use 4 space
janssonWebRTC 2017/03/14 16:15:15 Done.
299
286 300
287 301
288 def FlipAndCropRecordings(options, test_file_name, test_file_location, 302 def FlipAndCropRecordings(options, test_file_name, test_file_location,
289 ref_file_name, ref_file_location): 303 ref_file_name, ref_file_location):
290 """Performs a horizontal flip of the reference video to match the test video. 304 """Performs a horizontal flip of the reference video to match the test video.
291 305
292 This is done to the match orientation and then crops the ref and test videos 306 This is done to the match orientation and then crops the ref and test videos
293 using the options.test_crop_parameters and options.ref_crop_parameters. 307 using the options.test_crop_parameters and options.ref_crop_parameters.
294 308
295 Args: 309 Args:
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
327 test_video_crop_cmd = [ 341 test_video_crop_cmd = [
328 options.ffmpeg, 342 options.ffmpeg,
329 '-v', 'error', 343 '-v', 'error',
330 '-s', options.frame_width + 'x' + options.frame_height, 344 '-s', options.frame_width + 'x' + options.frame_height,
331 '-i', os.path.join(test_file_location, test_file_name), 345 '-i', os.path.join(test_file_location, test_file_name),
332 '-vf', options.test_crop_parameters, 346 '-vf', options.test_crop_parameters,
333 '-c:a', 'copy', 347 '-c:a', 'copy',
334 cropped_test_file 348 cropped_test_file
335 ] 349 ]
336 350
337 ref_crop = subprocess.Popen(ref_video_crop_cmd)
338 ref_crop.wait()
339 print 'Ref file cropped to: ' + cropped_ref_file
340
341 try: 351 try:
352 ref_crop = subprocess.Popen(ref_video_crop_cmd)
353 ref_crop.wait()
342 test_crop = subprocess.Popen(test_video_crop_cmd) 354 test_crop = subprocess.Popen(test_video_crop_cmd)
343 test_crop.wait() 355 test_crop.wait()
356
357 # ffmpeg does not abort when it fails, need to check return code.
358 if ref_crop.returncode != 0 or test_crop.returncode != 0:
359
360 # Cleanup recording directories.
361 shutil.rmtree(ref_file_location)
362 shutil.rmtree(test_file_location)
363 raise FfmpegError('Cropping failed, check ffmpeg output')
364 except FfmpegError:
kjellander_webrtc 2017/03/14 14:39:58 No point with this raise (if you would have a fina
janssonWebRTC 2017/03/14 16:15:15 Done.
365 raise
366 else:
367 print 'Ref file cropped to: ' + cropped_ref_file
344 print 'Test file cropped to: ' + cropped_test_file 368 print 'Test file cropped to: ' + cropped_test_file
345 print 'Cropping done!\n' 369 print 'Cropping done!\n'
346 370
347 # Need to return these so they can be used by other parts. 371 # Need to return these so they can be used by other parts.
348 cropped_recordings = { 372 cropped_recordings = {
349 'cropped_test_file' : cropped_test_file, 373 'cropped_test_file' : cropped_test_file,
350 'cropped_ref_file' : cropped_ref_file 374 'cropped_ref_file' : cropped_ref_file
351 } 375 }
352
353 return cropped_recordings 376 return cropped_recordings
354 except subprocess.CalledProcessError as e:
355 print 'Something went wrong during cropping: ' + e
356 raise
357 377
358 378
359 def CompareVideos(options, recording_result): 379 def CompareVideos(options, recording_result):
360 """Runs the compare_video.py script from src/webrtc/tools using the file path. 380 """Runs the compare_video.py script from src/webrtc/tools using the file path.
361 381
362 Uses the path from recording_result and writes the output to a file named 382 Uses the path from recording_result and writes the output to a file named
363 <options.app_name + '_' + CURRENT_TIME + '_result.txt> in the reference video 383 <options.app_name + '_' + CURRENT_TIME + '_result.txt> in the reference video
364 recording folder taken from recording_result. 384 recording folder taken from recording_result.
365 385
366 Args: 386 Args:
367 options(object): Contains all the provided command line options. 387 options(object): Contains all the provided command line options.
368 recording_files_and_time(dict): key: value pair with the path to cropped 388 recording_files_and_time(dict): key: value pair with the path to cropped
369 test and reference video files 389 test and reference video files
370 """ 390 """
371 print 'Starting comparison...' 391 print 'Starting comparison...'
372 print 'Grab a coffee, this might take a few minutes...' 392 print 'Grab a coffee, this might take a few minutes...'
373 cropped_ref_file = recording_result['cropped_ref_file'] 393 cropped_ref_file = recording_result['cropped_ref_file']
374 cropped_test_file = recording_result['cropped_test_file'] 394 cropped_test_file = recording_result['cropped_test_file']
375 compare_videos_script = os.path.abspath(options.compare_videos_script) 395 compare_videos_script = os.path.abspath(options.compare_videos_script)
376 rec_path = os.path.abspath(os.path.join( 396 rec_path = os.path.abspath(os.path.join(
377 os.path.dirname(recording_result['cropped_ref_file']))) 397 os.path.dirname(recording_result['cropped_ref_file'])))
378 result_file_name = os.path.join(rec_path, '%s_%s_result.txt') % ( 398 result_file_name = os.path.join(rec_path, '%s_%s_result.txt') % (
379 options.app_name, CURRENT_TIME) 399 options.app_name, CURRENT_TIME)
380 400
381 # Find the crop dimensions (950 and 420) in the ref crop parameter string: 401 # Find the crop dimensions (e.g. 950 and 420) in the ref crop parameter
382 # 'hflip, crop=950:420:130:56' 402 # string: 'hflip, crop=950:420:130:56'
383 for param in options.ref_crop_parameters.split('crop'): 403 for param in options.ref_crop_parameters.split('crop'):
384 if param[0] == '=': 404 if param[0] == '=':
385 crop_width = param.split(':')[0].split('=')[1] 405 crop_width = param.split(':')[0].split('=')[1]
386 crop_height = param.split(':')[1] 406 crop_height = param.split(':')[1]
387 407
388 compare_cmd = [ 408 compare_cmd = [
389 sys.executable, 409 sys.executable,
390 compare_videos_script, 410 compare_videos_script,
391 '--ref_video', cropped_ref_file, 411 '--ref_video', cropped_ref_file,
392 '--test_video', cropped_test_file, 412 '--test_video', cropped_test_file,
393 '--frame_analyzer', os.path.abspath(options.frame_analyzer), 413 '--frame_analyzer', os.path.abspath(options.frame_analyzer),
394 '--zxing_path', options.zxing_path, 414 '--zxing_path', options.zxing_path,
395 '--ffmpeg_path', options.ffmpeg, 415 '--ffmpeg_path', options.ffmpeg,
396 '--stats_file_ref', os.path.join(os.path.dirname(cropped_ref_file), 416 '--stats_file_ref', os.path.join(os.path.dirname(cropped_ref_file),
397 cropped_ref_file + '_stats.txt'), 417 cropped_ref_file + '_stats.txt'),
398 '--stats_file_test', os.path.join(os.path.dirname(cropped_test_file), 418 '--stats_file_test', os.path.join(os.path.dirname(cropped_test_file),
399 cropped_test_file + '_stats.txt'), 419 cropped_test_file + '_stats.txt'),
400 '--yuv_frame_height', crop_height, 420 '--yuv_frame_height', crop_height,
401 '--yuv_frame_width', crop_width 421 '--yuv_frame_width', crop_width
402 ] 422 ]
403 423
404 try: 424 try:
405 with open(result_file_name, 'w') as f: 425 with open(result_file_name, 'w') as f:
406 compare_video_recordings = subprocess.Popen(compare_cmd, stdout=f) 426 compare_video_recordings = subprocess.Popen(compare_cmd, stdout=f)
407 compare_video_recordings.wait() 427 compare_video_recordings.wait()
408 print 'Result recorded to: ' + os.path.abspath(result_file_name) 428 except OSError as e:
409 print 'Comparison done!'
410 except subprocess.CalledProcessError as e:
411 print 'Something went wrong when trying to compare videos: ' + e 429 print 'Something went wrong when trying to compare videos: ' + e
412 raise 430 raise
431 else:
432 print 'Result recorded to: ' + os.path.abspath(result_file_name)
433 print 'Comparison done!'
413 434
414 435
415 def main(): 436 def main():
416 """The main function. 437 """The main function.
417 438
418 A simple invocation is: 439 A simple invocation is:
419 ./run_video_analysis.py \ 440 ./run_video_analysis.py \
420 --app_name AppRTCMobile \ 441 --app_name AppRTCMobile \
421 --ffmpeg ./ffmpeg --ref_video_device=/dev/video0 \ 442 --ffmpeg ./ffmpeg --ref_video_device=/dev/video0 \
422 --test_video_device=/dev/video1 \ 443 --test_video_device=/dev/video1 \
(...skipping 12 matching lines...) Expand all
435 /tmp/ref/cropped_AppRTCMobile_<recording date and time>_ref.yuv 456 /tmp/ref/cropped_AppRTCMobile_<recording date and time>_ref.yuv
436 /tmp/test/cropped_AppRTCMobile_<recording date and time>_ref.yuv 457 /tmp/test/cropped_AppRTCMobile_<recording date and time>_ref.yuv
437 458
438 # Comparison metrics from cropped test and ref videos. 459 # Comparison metrics from cropped test and ref videos.
439 /tmp/test/AppRTCMobile_<recording date and time>_result.text 460 /tmp/test/AppRTCMobile_<recording date and time>_result.text
440 461
441 """ 462 """
442 options = _ParseArgs() 463 options = _ParseArgs()
443 RestartMagewellDevices(options.ref_video_device, options.test_video_device) 464 RestartMagewellDevices(options.ref_video_device, options.test_video_device)
444 record_paths = CreateRecordingDirs(options) 465 record_paths = CreateRecordingDirs(options)
445 recording_result = StartRecording(options, record_paths) 466 recording_result = StartRecording(options, record_paths['ref_rec_location'],
467 record_paths['test_rec_location'])
446 468
447 # Do not require compare_video.py script to run, no metrics will be generated. 469 # Do not require compare_video.py script to run, no metrics will be generated.
448 if options.compare_videos_script: 470 if options.compare_videos_script:
449 CompareVideos(options, recording_result) 471 CompareVideos(options, recording_result)
450 else: 472 else:
451 print ('Skipping compare videos step due to compare_videos flag were not ' 473 print ('Skipping compare videos step due to compare_videos flag were not '
452 'passed.') 474 'passed.')
453 475
454 476
455 if __name__ == '__main__': 477 if __name__ == '__main__':
456 sys.exit(main()) 478 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698