1*d9f75844SAndroid Build Coastguard Worker#!/usr/bin/env python 2*d9f75844SAndroid Build Coastguard Worker# Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3*d9f75844SAndroid Build Coastguard Worker# 4*d9f75844SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license 5*d9f75844SAndroid Build Coastguard Worker# that can be found in the LICENSE file in the root of the source 6*d9f75844SAndroid Build Coastguard Worker# tree. An additional intellectual property rights grant can be found 7*d9f75844SAndroid Build Coastguard Worker# in the file PATENTS. All contributing project authors may 8*d9f75844SAndroid Build Coastguard Worker# be found in the AUTHORS file in the root of the source tree. 9*d9f75844SAndroid Build Coastguard Worker 10*d9f75844SAndroid Build Coastguard Workerfrom __future__ import absolute_import 11*d9f75844SAndroid Build Coastguard Workerfrom __future__ import division 12*d9f75844SAndroid Build Coastguard Workerfrom __future__ import print_function 13*d9f75844SAndroid Build Coastguard Workerimport json 14*d9f75844SAndroid Build Coastguard Workerimport optparse 15*d9f75844SAndroid Build Coastguard Workerimport os 16*d9f75844SAndroid Build Coastguard Workerimport shutil 17*d9f75844SAndroid Build Coastguard Workerimport subprocess 18*d9f75844SAndroid Build Coastguard Workerimport sys 19*d9f75844SAndroid Build Coastguard Workerimport tempfile 20*d9f75844SAndroid Build Coastguard Worker 21*d9f75844SAndroid Build Coastguard WorkerSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 22*d9f75844SAndroid Build Coastguard Worker 23*d9f75844SAndroid Build Coastguard Worker# Chrome browsertests will throw away stderr; avoid that output gets lost. 24*d9f75844SAndroid Build Coastguard Workersys.stderr = sys.stdout 25*d9f75844SAndroid Build Coastguard Worker 26*d9f75844SAndroid Build Coastguard Worker 27*d9f75844SAndroid Build Coastguard Workerdef _ParseArgs(): 28*d9f75844SAndroid Build Coastguard Worker """Registers the command-line options.""" 29*d9f75844SAndroid Build Coastguard Worker usage = 'usage: %prog [options]' 30*d9f75844SAndroid Build Coastguard Worker parser = optparse.OptionParser(usage=usage) 31*d9f75844SAndroid Build Coastguard Worker 32*d9f75844SAndroid Build Coastguard Worker parser.add_option('--label', 33*d9f75844SAndroid Build Coastguard Worker type='string', 34*d9f75844SAndroid Build Coastguard Worker default='MY_TEST', 35*d9f75844SAndroid Build Coastguard Worker help=('Label of the test, used to identify different ' 36*d9f75844SAndroid Build Coastguard Worker 'tests. Default: %default')) 37*d9f75844SAndroid Build Coastguard Worker parser.add_option('--ref_video', 38*d9f75844SAndroid Build Coastguard Worker type='string', 39*d9f75844SAndroid Build Coastguard Worker help='Reference video to compare with (YUV).') 40*d9f75844SAndroid Build Coastguard Worker parser.add_option('--test_video', 41*d9f75844SAndroid Build Coastguard Worker type='string', 42*d9f75844SAndroid Build Coastguard Worker help=('Test video to be compared with the reference ' 43*d9f75844SAndroid Build Coastguard Worker 'video (YUV).')) 44*d9f75844SAndroid Build Coastguard Worker parser.add_option('--frame_analyzer', 45*d9f75844SAndroid Build Coastguard Worker type='string', 46*d9f75844SAndroid Build Coastguard Worker help='Path to the frame analyzer executable.') 47*d9f75844SAndroid Build Coastguard Worker parser.add_option('--aligned_output_file', 48*d9f75844SAndroid Build Coastguard Worker type='string', 49*d9f75844SAndroid Build Coastguard Worker help='Path for output aligned YUV or Y4M file.') 50*d9f75844SAndroid Build Coastguard Worker parser.add_option('--vmaf', type='string', help='Path to VMAF executable.') 51*d9f75844SAndroid Build Coastguard Worker parser.add_option('--vmaf_model', 52*d9f75844SAndroid Build Coastguard Worker type='string', 53*d9f75844SAndroid Build Coastguard Worker help='Path to VMAF model.') 54*d9f75844SAndroid Build Coastguard Worker parser.add_option('--vmaf_phone_model', 55*d9f75844SAndroid Build Coastguard Worker action='store_true', 56*d9f75844SAndroid Build Coastguard Worker help='Whether to use phone model in VMAF.') 57*d9f75844SAndroid Build Coastguard Worker parser.add_option( 58*d9f75844SAndroid Build Coastguard Worker '--yuv_frame_width', 59*d9f75844SAndroid Build Coastguard Worker type='int', 60*d9f75844SAndroid Build Coastguard Worker default=640, 61*d9f75844SAndroid Build Coastguard Worker help='Width of the YUV file\'s frames. Default: %default') 62*d9f75844SAndroid Build Coastguard Worker parser.add_option( 63*d9f75844SAndroid Build Coastguard Worker '--yuv_frame_height', 64*d9f75844SAndroid Build Coastguard Worker type='int', 65*d9f75844SAndroid Build Coastguard Worker default=480, 66*d9f75844SAndroid Build Coastguard Worker help='Height of the YUV file\'s frames. Default: %default') 67*d9f75844SAndroid Build Coastguard Worker parser.add_option('--chartjson_result_file', 68*d9f75844SAndroid Build Coastguard Worker type='str', 69*d9f75844SAndroid Build Coastguard Worker default=None, 70*d9f75844SAndroid Build Coastguard Worker help='Where to store perf results in chartjson format.') 71*d9f75844SAndroid Build Coastguard Worker options, _ = parser.parse_args() 72*d9f75844SAndroid Build Coastguard Worker 73*d9f75844SAndroid Build Coastguard Worker if not options.ref_video: 74*d9f75844SAndroid Build Coastguard Worker parser.error('You must provide a path to the reference video!') 75*d9f75844SAndroid Build Coastguard Worker if not os.path.exists(options.ref_video): 76*d9f75844SAndroid Build Coastguard Worker parser.error('Cannot find the reference video at %s' % 77*d9f75844SAndroid Build Coastguard Worker options.ref_video) 78*d9f75844SAndroid Build Coastguard Worker 79*d9f75844SAndroid Build Coastguard Worker if not options.test_video: 80*d9f75844SAndroid Build Coastguard Worker parser.error('You must provide a path to the test video!') 81*d9f75844SAndroid Build Coastguard Worker if not os.path.exists(options.test_video): 82*d9f75844SAndroid Build Coastguard Worker parser.error('Cannot find the test video at %s' % options.test_video) 83*d9f75844SAndroid Build Coastguard Worker 84*d9f75844SAndroid Build Coastguard Worker if not options.frame_analyzer: 85*d9f75844SAndroid Build Coastguard Worker parser.error( 86*d9f75844SAndroid Build Coastguard Worker 'You must provide the path to the frame analyzer executable!') 87*d9f75844SAndroid Build Coastguard Worker if not os.path.exists(options.frame_analyzer): 88*d9f75844SAndroid Build Coastguard Worker parser.error('Cannot find frame analyzer executable at %s!' % 89*d9f75844SAndroid Build Coastguard Worker options.frame_analyzer) 90*d9f75844SAndroid Build Coastguard Worker 91*d9f75844SAndroid Build Coastguard Worker if options.vmaf and not options.vmaf_model: 92*d9f75844SAndroid Build Coastguard Worker parser.error('You must provide a path to a VMAF model to use VMAF.') 93*d9f75844SAndroid Build Coastguard Worker 94*d9f75844SAndroid Build Coastguard Worker return options 95*d9f75844SAndroid Build Coastguard Worker 96*d9f75844SAndroid Build Coastguard Worker 97*d9f75844SAndroid Build Coastguard Workerdef _DevNull(): 98*d9f75844SAndroid Build Coastguard Worker """On Windows, sometimes the inherited stdin handle from the parent process 99*d9f75844SAndroid Build Coastguard Worker fails. Workaround this by passing null to stdin to the subprocesses commands. 100*d9f75844SAndroid Build Coastguard Worker This function can be used to create the null file handler. 101*d9f75844SAndroid Build Coastguard Worker """ 102*d9f75844SAndroid Build Coastguard Worker return open(os.devnull, 'r') 103*d9f75844SAndroid Build Coastguard Worker 104*d9f75844SAndroid Build Coastguard Worker 105*d9f75844SAndroid Build Coastguard Workerdef _RunFrameAnalyzer(options, yuv_directory=None): 106*d9f75844SAndroid Build Coastguard Worker """Run frame analyzer to compare the videos and print output.""" 107*d9f75844SAndroid Build Coastguard Worker cmd = [ 108*d9f75844SAndroid Build Coastguard Worker options.frame_analyzer, 109*d9f75844SAndroid Build Coastguard Worker '--label=%s' % options.label, 110*d9f75844SAndroid Build Coastguard Worker '--reference_file=%s' % options.ref_video, 111*d9f75844SAndroid Build Coastguard Worker '--test_file=%s' % options.test_video, 112*d9f75844SAndroid Build Coastguard Worker '--width=%d' % options.yuv_frame_width, 113*d9f75844SAndroid Build Coastguard Worker '--height=%d' % options.yuv_frame_height, 114*d9f75844SAndroid Build Coastguard Worker ] 115*d9f75844SAndroid Build Coastguard Worker if options.chartjson_result_file: 116*d9f75844SAndroid Build Coastguard Worker cmd.append('--chartjson_result_file=%s' % 117*d9f75844SAndroid Build Coastguard Worker options.chartjson_result_file) 118*d9f75844SAndroid Build Coastguard Worker if options.aligned_output_file: 119*d9f75844SAndroid Build Coastguard Worker cmd.append('--aligned_output_file=%s' % options.aligned_output_file) 120*d9f75844SAndroid Build Coastguard Worker if yuv_directory: 121*d9f75844SAndroid Build Coastguard Worker cmd.append('--yuv_directory=%s' % yuv_directory) 122*d9f75844SAndroid Build Coastguard Worker frame_analyzer = subprocess.Popen(cmd, 123*d9f75844SAndroid Build Coastguard Worker stdin=_DevNull(), 124*d9f75844SAndroid Build Coastguard Worker stdout=sys.stdout, 125*d9f75844SAndroid Build Coastguard Worker stderr=sys.stderr) 126*d9f75844SAndroid Build Coastguard Worker frame_analyzer.wait() 127*d9f75844SAndroid Build Coastguard Worker if frame_analyzer.returncode != 0: 128*d9f75844SAndroid Build Coastguard Worker print('Failed to run frame analyzer.') 129*d9f75844SAndroid Build Coastguard Worker return frame_analyzer.returncode 130*d9f75844SAndroid Build Coastguard Worker 131*d9f75844SAndroid Build Coastguard Worker 132*d9f75844SAndroid Build Coastguard Workerdef _RunVmaf(options, yuv_directory, logfile): 133*d9f75844SAndroid Build Coastguard Worker """ Run VMAF to compare videos and print output. 134*d9f75844SAndroid Build Coastguard Worker 135*d9f75844SAndroid Build Coastguard Worker The yuv_directory is assumed to have been populated with a reference and test 136*d9f75844SAndroid Build Coastguard Worker video in .yuv format, with names according to the label. 137*d9f75844SAndroid Build Coastguard Worker """ 138*d9f75844SAndroid Build Coastguard Worker cmd = [ 139*d9f75844SAndroid Build Coastguard Worker options.vmaf, 140*d9f75844SAndroid Build Coastguard Worker 'yuv420p', 141*d9f75844SAndroid Build Coastguard Worker str(options.yuv_frame_width), 142*d9f75844SAndroid Build Coastguard Worker str(options.yuv_frame_height), 143*d9f75844SAndroid Build Coastguard Worker os.path.join(yuv_directory, "ref.yuv"), 144*d9f75844SAndroid Build Coastguard Worker os.path.join(yuv_directory, "test.yuv"), 145*d9f75844SAndroid Build Coastguard Worker options.vmaf_model, 146*d9f75844SAndroid Build Coastguard Worker '--log', 147*d9f75844SAndroid Build Coastguard Worker logfile, 148*d9f75844SAndroid Build Coastguard Worker '--log-fmt', 149*d9f75844SAndroid Build Coastguard Worker 'json', 150*d9f75844SAndroid Build Coastguard Worker ] 151*d9f75844SAndroid Build Coastguard Worker if options.vmaf_phone_model: 152*d9f75844SAndroid Build Coastguard Worker cmd.append('--phone-model') 153*d9f75844SAndroid Build Coastguard Worker 154*d9f75844SAndroid Build Coastguard Worker vmaf = subprocess.Popen(cmd, 155*d9f75844SAndroid Build Coastguard Worker stdin=_DevNull(), 156*d9f75844SAndroid Build Coastguard Worker stdout=sys.stdout, 157*d9f75844SAndroid Build Coastguard Worker stderr=sys.stderr) 158*d9f75844SAndroid Build Coastguard Worker vmaf.wait() 159*d9f75844SAndroid Build Coastguard Worker if vmaf.returncode != 0: 160*d9f75844SAndroid Build Coastguard Worker print('Failed to run VMAF.') 161*d9f75844SAndroid Build Coastguard Worker return 1 162*d9f75844SAndroid Build Coastguard Worker 163*d9f75844SAndroid Build Coastguard Worker # Read per-frame scores from VMAF output and print. 164*d9f75844SAndroid Build Coastguard Worker with open(logfile) as f: 165*d9f75844SAndroid Build Coastguard Worker vmaf_data = json.load(f) 166*d9f75844SAndroid Build Coastguard Worker vmaf_scores = [] 167*d9f75844SAndroid Build Coastguard Worker for frame in vmaf_data['frames']: 168*d9f75844SAndroid Build Coastguard Worker vmaf_scores.append(frame['metrics']['vmaf']) 169*d9f75844SAndroid Build Coastguard Worker print('RESULT VMAF: %s=' % options.label, vmaf_scores) 170*d9f75844SAndroid Build Coastguard Worker 171*d9f75844SAndroid Build Coastguard Worker return 0 172*d9f75844SAndroid Build Coastguard Worker 173*d9f75844SAndroid Build Coastguard Worker 174*d9f75844SAndroid Build Coastguard Workerdef main(): 175*d9f75844SAndroid Build Coastguard Worker """The main function. 176*d9f75844SAndroid Build Coastguard Worker 177*d9f75844SAndroid Build Coastguard Worker A simple invocation is: 178*d9f75844SAndroid Build Coastguard Worker ./webrtc/rtc_tools/compare_videos.py 179*d9f75844SAndroid Build Coastguard Worker --ref_video=<path_and_name_of_reference_video> 180*d9f75844SAndroid Build Coastguard Worker --test_video=<path_and_name_of_test_video> 181*d9f75844SAndroid Build Coastguard Worker --frame_analyzer=<path_and_name_of_the_frame_analyzer_executable> 182*d9f75844SAndroid Build Coastguard Worker 183*d9f75844SAndroid Build Coastguard Worker Running vmaf requires the following arguments: 184*d9f75844SAndroid Build Coastguard Worker --vmaf, --vmaf_model, --yuv_frame_width, --yuv_frame_height 185*d9f75844SAndroid Build Coastguard Worker """ 186*d9f75844SAndroid Build Coastguard Worker options = _ParseArgs() 187*d9f75844SAndroid Build Coastguard Worker 188*d9f75844SAndroid Build Coastguard Worker if options.vmaf: 189*d9f75844SAndroid Build Coastguard Worker try: 190*d9f75844SAndroid Build Coastguard Worker # Directory to save temporary YUV files for VMAF in frame_analyzer. 191*d9f75844SAndroid Build Coastguard Worker yuv_directory = tempfile.mkdtemp() 192*d9f75844SAndroid Build Coastguard Worker _, vmaf_logfile = tempfile.mkstemp() 193*d9f75844SAndroid Build Coastguard Worker 194*d9f75844SAndroid Build Coastguard Worker # Run frame analyzer to compare the videos and print output. 195*d9f75844SAndroid Build Coastguard Worker if _RunFrameAnalyzer(options, yuv_directory=yuv_directory) != 0: 196*d9f75844SAndroid Build Coastguard Worker return 1 197*d9f75844SAndroid Build Coastguard Worker 198*d9f75844SAndroid Build Coastguard Worker # Run VMAF for further video comparison and print output. 199*d9f75844SAndroid Build Coastguard Worker return _RunVmaf(options, yuv_directory, vmaf_logfile) 200*d9f75844SAndroid Build Coastguard Worker finally: 201*d9f75844SAndroid Build Coastguard Worker shutil.rmtree(yuv_directory) 202*d9f75844SAndroid Build Coastguard Worker os.remove(vmaf_logfile) 203*d9f75844SAndroid Build Coastguard Worker else: 204*d9f75844SAndroid Build Coastguard Worker return _RunFrameAnalyzer(options) 205*d9f75844SAndroid Build Coastguard Worker 206*d9f75844SAndroid Build Coastguard Worker return 0 207*d9f75844SAndroid Build Coastguard Worker 208*d9f75844SAndroid Build Coastguard Worker 209*d9f75844SAndroid Build Coastguard Workerif __name__ == '__main__': 210*d9f75844SAndroid Build Coastguard Worker sys.exit(main()) 211