blob: 1102b3a8c1b29787edc468f07b109c6ace1d9c15 [file] [log] [blame]
kjellanderd2b63cf2017-06-30 03:04:59 -07001#!/usr/bin/env python
2# Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3#
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
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS. All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
9
Paulina Hensmanede87962018-10-10 15:48:30 +020010import json
kjellanderd2b63cf2017-06-30 03:04:59 -070011import optparse
12import os
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000013import shutil
kjellanderd2b63cf2017-06-30 03:04:59 -070014import subprocess
15import sys
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000016import tempfile
kjellanderd2b63cf2017-06-30 03:04:59 -070017
18
19SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
20
21# Chrome browsertests will throw away stderr; avoid that output gets lost.
22sys.stderr = sys.stdout
23
24
25def _ParseArgs():
26 """Registers the command-line options."""
27 usage = 'usage: %prog [options]'
28 parser = optparse.OptionParser(usage=usage)
29
30 parser.add_option('--label', type='string', default='MY_TEST',
31 help=('Label of the test, used to identify different '
32 'tests. Default: %default'))
33 parser.add_option('--ref_video', type='string',
34 help='Reference video to compare with (YUV).')
35 parser.add_option('--test_video', type='string',
36 help=('Test video to be compared with the reference '
37 'video (YUV).'))
38 parser.add_option('--frame_analyzer', type='string',
39 help='Path to the frame analyzer executable.')
Paulina Hensmanb671d462018-09-14 11:32:00 +020040 parser.add_option('--aligned_output_file', type='string',
41 help='Path for output aligned YUV or Y4M file.')
Paulina Hensman12c62b92018-09-28 15:14:07 +020042 parser.add_option('--vmaf', type='string',
43 help='Path to VMAF executable.')
44 parser.add_option('--vmaf_model', type='string',
45 help='Path to VMAF model.')
46 parser.add_option('--vmaf_phone_model', action='store_true',
47 help='Whether to use phone model in VMAF.')
kjellanderd2b63cf2017-06-30 03:04:59 -070048 parser.add_option('--barcode_decoder', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000049 help=('Path to the barcode decoder script. By default, we '
50 'will assume we can find it in barcode_tools/'
51 'relative to this directory.'))
kjellanderd2b63cf2017-06-30 03:04:59 -070052 parser.add_option('--ffmpeg_path', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000053 help=('The path to where the ffmpeg executable is located. '
54 'If omitted, it will be assumed to be present in the '
55 'PATH with the name ffmpeg[.exe].'))
kjellanderd2b63cf2017-06-30 03:04:59 -070056 parser.add_option('--zxing_path', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000057 help=('The path to where the zxing executable is located. '
58 'If omitted, it will be assumed to be present in the '
59 'PATH with the name zxing[.exe].'))
kjellanderd2b63cf2017-06-30 03:04:59 -070060 parser.add_option('--stats_file_ref', type='string', default='stats_ref.txt',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000061 help=('Path to the temporary stats file to be created and '
62 'used for the reference video file. '
63 'Default: %default'))
kjellanderd2b63cf2017-06-30 03:04:59 -070064 parser.add_option('--stats_file_test', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000065 default='stats_test.txt',
66 help=('Path to the temporary stats file to be created and '
67 'used for the test video file. Default: %default'))
kjellanderd2b63cf2017-06-30 03:04:59 -070068 parser.add_option('--stats_file', type='string',
69 help=('DEPRECATED'))
70 parser.add_option('--yuv_frame_width', type='int', default=640,
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000071 help='Width of the YUV file\'s frames. Default: %default')
kjellanderd2b63cf2017-06-30 03:04:59 -070072 parser.add_option('--yuv_frame_height', type='int', default=480,
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000073 help='Height of the YUV file\'s frames. Default: %default')
Edward Lemur2e5966b2018-01-30 15:33:02 +010074 parser.add_option('--chartjson_result_file', type='str', default=None,
75 help='Where to store perf results in chartjson format.')
kjellanderd2b63cf2017-06-30 03:04:59 -070076 options, _ = parser.parse_args()
77
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000078 if options.stats_file:
79 options.stats_file_test = options.stats_file
80 print ('WARNING: Using deprecated switch --stats_file. '
81 'The new flag is --stats_file_test.')
82
kjellanderd2b63cf2017-06-30 03:04:59 -070083 if not options.ref_video:
84 parser.error('You must provide a path to the reference video!')
85 if not os.path.exists(options.ref_video):
86 parser.error('Cannot find the reference video at %s' % options.ref_video)
87
88 if not options.test_video:
89 parser.error('You must provide a path to the test video!')
90 if not os.path.exists(options.test_video):
91 parser.error('Cannot find the test video at %s' % options.test_video)
92
93 if not options.frame_analyzer:
94 parser.error('You must provide the path to the frame analyzer executable!')
95 if not os.path.exists(options.frame_analyzer):
96 parser.error('Cannot find frame analyzer executable at %s!' %
97 options.frame_analyzer)
Paulina Hensman12c62b92018-09-28 15:14:07 +020098
99 if options.vmaf and not options.vmaf_model:
100 parser.error('You must provide a path to a VMAF model to use VMAF.')
101
kjellanderd2b63cf2017-06-30 03:04:59 -0700102 return options
103
104def _DevNull():
105 """On Windows, sometimes the inherited stdin handle from the parent process
106 fails. Workaround this by passing null to stdin to the subprocesses commands.
107 This function can be used to create the null file handler.
108 """
109 return open(os.devnull, 'r')
110
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000111def DecodeBarcodesInVideo(options, path_to_decoder, video, stat_file):
112 # Run barcode decoder on the test video to identify frame numbers.
113 png_working_directory = tempfile.mkdtemp()
114 cmd = [
115 sys.executable,
116 path_to_decoder,
117 '--yuv_file=%s' % video,
118 '--yuv_frame_width=%d' % options.yuv_frame_width,
119 '--yuv_frame_height=%d' % options.yuv_frame_height,
120 '--stats_file=%s' % stat_file,
121 '--png_working_dir=%s' % png_working_directory,
122 ]
123 if options.zxing_path:
124 cmd.append('--zxing_path=%s' % options.zxing_path)
125 if options.ffmpeg_path:
126 cmd.append('--ffmpeg_path=%s' % options.ffmpeg_path)
127
128
129 barcode_decoder = subprocess.Popen(cmd, stdin=_DevNull(),
130 stdout=sys.stdout, stderr=sys.stderr)
131 barcode_decoder.wait()
132
133 shutil.rmtree(png_working_directory)
134 if barcode_decoder.returncode != 0:
135 print 'Failed to run barcode decoder script.'
136 return 1
137 return 0
138
Paulina Hensman12c62b92018-09-28 15:14:07 +0200139
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200140def _RunFrameAnalyzer(options, yuv_directory=None):
Paulina Hensman12c62b92018-09-28 15:14:07 +0200141 """Run frame analyzer to compare the videos and print output."""
142 cmd = [
143 options.frame_analyzer,
144 '--label=%s' % options.label,
145 '--reference_file=%s' % options.ref_video,
146 '--test_file=%s' % options.test_video,
147 '--stats_file_ref=%s' % options.stats_file_ref,
148 '--stats_file_test=%s' % options.stats_file_test,
149 '--width=%d' % options.yuv_frame_width,
150 '--height=%d' % options.yuv_frame_height,
151 ]
152 if options.chartjson_result_file:
153 cmd.append('--chartjson_result_file=%s' % options.chartjson_result_file)
154 if options.aligned_output_file:
155 cmd.append('--aligned_output_file=%s' % options.aligned_output_file)
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200156 if yuv_directory:
157 cmd.append('--yuv_directory=%s' % yuv_directory)
Paulina Hensman12c62b92018-09-28 15:14:07 +0200158 frame_analyzer = subprocess.Popen(cmd, stdin=_DevNull(),
159 stdout=sys.stdout, stderr=sys.stderr)
160 frame_analyzer.wait()
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200161 if frame_analyzer.returncode != 0:
162 print 'Failed to run frame analyzer.'
Paulina Hensman12c62b92018-09-28 15:14:07 +0200163 return frame_analyzer.returncode
164
165
Paulina Hensmanede87962018-10-10 15:48:30 +0200166def _RunVmaf(options, yuv_directory, logfile):
Paulina Hensman12c62b92018-09-28 15:14:07 +0200167 """ Run VMAF to compare videos and print output.
168
Paulina Hensman12c62b92018-09-28 15:14:07 +0200169 The yuv_directory is assumed to have been populated with a reference and test
170 video in .yuv format, with names according to the label.
171 """
172 cmd = [
173 options.vmaf,
174 'yuv420p',
175 str(options.yuv_frame_width),
176 str(options.yuv_frame_height),
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200177 os.path.join(yuv_directory, "ref.yuv"),
178 os.path.join(yuv_directory, "test.yuv"),
Paulina Hensman12c62b92018-09-28 15:14:07 +0200179 options.vmaf_model,
Paulina Hensmanede87962018-10-10 15:48:30 +0200180 '--log',
181 logfile,
182 '--log-fmt',
183 'json',
Paulina Hensman12c62b92018-09-28 15:14:07 +0200184 ]
185 if options.vmaf_phone_model:
186 cmd.append('--phone-model')
187
188 vmaf = subprocess.Popen(cmd, stdin=_DevNull(),
Paulina Hensmanede87962018-10-10 15:48:30 +0200189 stdout=sys.stdout, stderr=sys.stderr)
Paulina Hensman12c62b92018-09-28 15:14:07 +0200190 vmaf.wait()
191 if vmaf.returncode != 0:
192 print 'Failed to run VMAF.'
193 return 1
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200194
Paulina Hensmanede87962018-10-10 15:48:30 +0200195 # Read per-frame scores from VMAF output and print.
196 with open(logfile) as f:
197 vmaf_data = json.load(f)
198 vmaf_scores = []
199 for frame in vmaf_data['frames']:
200 vmaf_scores.append(frame['metrics']['vmaf'])
201 print 'RESULT VMAF: %s=' % options.label, vmaf_scores
202
Paulina Hensman12c62b92018-09-28 15:14:07 +0200203 return 0
204
205
kjellanderd2b63cf2017-06-30 03:04:59 -0700206def main():
207 """The main function.
208
209 A simple invocation is:
Paulina Hensmanb671d462018-09-14 11:32:00 +0200210 ./webrtc/rtc_tools/compare_videos.py
kjellanderd2b63cf2017-06-30 03:04:59 -0700211 --ref_video=<path_and_name_of_reference_video>
212 --test_video=<path_and_name_of_test_video>
213 --frame_analyzer=<path_and_name_of_the_frame_analyzer_executable>
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000214
Paulina Hensman12c62b92018-09-28 15:14:07 +0200215 Running vmaf requires the following arguments:
216 --vmaf, --vmaf_model, --yuv_frame_width, --yuv_frame_height
217
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000218 Notice that the prerequisites for barcode_decoder.py also applies to this
219 script. The means the following executables have to be available in the PATH:
220 * zxing
221 * ffmpeg
kjellanderd2b63cf2017-06-30 03:04:59 -0700222 """
223 options = _ParseArgs()
224
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000225 if options.barcode_decoder:
226 path_to_decoder = options.barcode_decoder
227 else:
228 path_to_decoder = os.path.join(SCRIPT_DIR, 'barcode_tools',
229 'barcode_decoder.py')
230
231 if DecodeBarcodesInVideo(options, path_to_decoder,
232 options.ref_video, options.stats_file_ref) != 0:
233 return 1
234 if DecodeBarcodesInVideo(options, path_to_decoder,
235 options.test_video, options.stats_file_test) != 0:
236 return 1
237
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200238 if options.vmaf:
239 try:
240 # Directory to save temporary YUV files for VMAF in frame_analyzer.
241 yuv_directory = tempfile.mkdtemp()
Paulina Hensmanede87962018-10-10 15:48:30 +0200242 _, vmaf_logfile = tempfile.mkstemp()
Paulina Hensman12c62b92018-09-28 15:14:07 +0200243
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200244 # Run frame analyzer to compare the videos and print output.
245 if _RunFrameAnalyzer(options, yuv_directory=yuv_directory) != 0:
246 return 1
Paulina Hensman12c62b92018-09-28 15:14:07 +0200247
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200248 # Run VMAF for further video comparison and print output.
Paulina Hensmanede87962018-10-10 15:48:30 +0200249 return _RunVmaf(options, yuv_directory, vmaf_logfile)
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200250 finally:
251 shutil.rmtree(yuv_directory)
Paulina Hensmanede87962018-10-10 15:48:30 +0200252 os.remove(vmaf_logfile)
Paulina Hensmana6471eb2018-10-05 14:34:33 +0200253 else:
254 return _RunFrameAnalyzer(options)
kjellanderd2b63cf2017-06-30 03:04:59 -0700255
kjellanderd2b63cf2017-06-30 03:04:59 -0700256 return 0
257
258if __name__ == '__main__':
259 sys.exit(main())