blob: 79790439b176daac191727cd0c217c946d52579d [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
10import optparse
11import os
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000012import shutil
kjellanderd2b63cf2017-06-30 03:04:59 -070013import subprocess
14import sys
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000015import tempfile
kjellanderd2b63cf2017-06-30 03:04:59 -070016
17
18SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
19
20# Chrome browsertests will throw away stderr; avoid that output gets lost.
21sys.stderr = sys.stdout
22
23
24def _ParseArgs():
25 """Registers the command-line options."""
26 usage = 'usage: %prog [options]'
27 parser = optparse.OptionParser(usage=usage)
28
29 parser.add_option('--label', type='string', default='MY_TEST',
30 help=('Label of the test, used to identify different '
31 'tests. Default: %default'))
32 parser.add_option('--ref_video', type='string',
33 help='Reference video to compare with (YUV).')
34 parser.add_option('--test_video', type='string',
35 help=('Test video to be compared with the reference '
36 'video (YUV).'))
37 parser.add_option('--frame_analyzer', type='string',
38 help='Path to the frame analyzer executable.')
Paulina Hensmanb671d462018-09-14 11:32:00 +020039 parser.add_option('--aligned_output_file', type='string',
40 help='Path for output aligned YUV or Y4M file.')
Paulina Hensman12c62b92018-09-28 15:14:07 +020041 parser.add_option('--vmaf', type='string',
42 help='Path to VMAF executable.')
43 parser.add_option('--vmaf_model', type='string',
44 help='Path to VMAF model.')
45 parser.add_option('--vmaf_phone_model', action='store_true',
46 help='Whether to use phone model in VMAF.')
kjellanderd2b63cf2017-06-30 03:04:59 -070047 parser.add_option('--barcode_decoder', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000048 help=('Path to the barcode decoder script. By default, we '
49 'will assume we can find it in barcode_tools/'
50 'relative to this directory.'))
kjellanderd2b63cf2017-06-30 03:04:59 -070051 parser.add_option('--ffmpeg_path', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000052 help=('The path to where the ffmpeg executable is located. '
53 'If omitted, it will be assumed to be present in the '
54 'PATH with the name ffmpeg[.exe].'))
kjellanderd2b63cf2017-06-30 03:04:59 -070055 parser.add_option('--zxing_path', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000056 help=('The path to where the zxing executable is located. '
57 'If omitted, it will be assumed to be present in the '
58 'PATH with the name zxing[.exe].'))
kjellanderd2b63cf2017-06-30 03:04:59 -070059 parser.add_option('--stats_file_ref', type='string', default='stats_ref.txt',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000060 help=('Path to the temporary stats file to be created and '
61 'used for the reference video file. '
62 'Default: %default'))
kjellanderd2b63cf2017-06-30 03:04:59 -070063 parser.add_option('--stats_file_test', type='string',
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000064 default='stats_test.txt',
65 help=('Path to the temporary stats file to be created and '
66 'used for the test video file. Default: %default'))
kjellanderd2b63cf2017-06-30 03:04:59 -070067 parser.add_option('--stats_file', type='string',
68 help=('DEPRECATED'))
69 parser.add_option('--yuv_frame_width', type='int', default=640,
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000070 help='Width of the YUV file\'s frames. Default: %default')
kjellanderd2b63cf2017-06-30 03:04:59 -070071 parser.add_option('--yuv_frame_height', type='int', default=480,
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000072 help='Height of the YUV file\'s frames. Default: %default')
Edward Lemur2e5966b2018-01-30 15:33:02 +010073 parser.add_option('--chartjson_result_file', type='str', default=None,
74 help='Where to store perf results in chartjson format.')
kjellanderd2b63cf2017-06-30 03:04:59 -070075 options, _ = parser.parse_args()
76
Magnus Jedvert3e169ac2018-08-24 12:44:59 +000077 if options.stats_file:
78 options.stats_file_test = options.stats_file
79 print ('WARNING: Using deprecated switch --stats_file. '
80 'The new flag is --stats_file_test.')
81
kjellanderd2b63cf2017-06-30 03:04:59 -070082 if not options.ref_video:
83 parser.error('You must provide a path to the reference video!')
84 if not os.path.exists(options.ref_video):
85 parser.error('Cannot find the reference video at %s' % options.ref_video)
86
87 if not options.test_video:
88 parser.error('You must provide a path to the test video!')
89 if not os.path.exists(options.test_video):
90 parser.error('Cannot find the test video at %s' % options.test_video)
91
92 if not options.frame_analyzer:
93 parser.error('You must provide the path to the frame analyzer executable!')
94 if not os.path.exists(options.frame_analyzer):
95 parser.error('Cannot find frame analyzer executable at %s!' %
96 options.frame_analyzer)
Paulina Hensman12c62b92018-09-28 15:14:07 +020097
98 if options.vmaf and not options.vmaf_model:
99 parser.error('You must provide a path to a VMAF model to use VMAF.')
100
kjellanderd2b63cf2017-06-30 03:04:59 -0700101 return options
102
103def _DevNull():
104 """On Windows, sometimes the inherited stdin handle from the parent process
105 fails. Workaround this by passing null to stdin to the subprocesses commands.
106 This function can be used to create the null file handler.
107 """
108 return open(os.devnull, 'r')
109
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000110def DecodeBarcodesInVideo(options, path_to_decoder, video, stat_file):
111 # Run barcode decoder on the test video to identify frame numbers.
112 png_working_directory = tempfile.mkdtemp()
113 cmd = [
114 sys.executable,
115 path_to_decoder,
116 '--yuv_file=%s' % video,
117 '--yuv_frame_width=%d' % options.yuv_frame_width,
118 '--yuv_frame_height=%d' % options.yuv_frame_height,
119 '--stats_file=%s' % stat_file,
120 '--png_working_dir=%s' % png_working_directory,
121 ]
122 if options.zxing_path:
123 cmd.append('--zxing_path=%s' % options.zxing_path)
124 if options.ffmpeg_path:
125 cmd.append('--ffmpeg_path=%s' % options.ffmpeg_path)
126
127
128 barcode_decoder = subprocess.Popen(cmd, stdin=_DevNull(),
129 stdout=sys.stdout, stderr=sys.stderr)
130 barcode_decoder.wait()
131
132 shutil.rmtree(png_working_directory)
133 if barcode_decoder.returncode != 0:
134 print 'Failed to run barcode decoder script.'
135 return 1
136 return 0
137
Paulina Hensman12c62b92018-09-28 15:14:07 +0200138
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200139def _RunFrameAnalyzer(options, yuv_directory=None):
Paulina Hensman12c62b92018-09-28 15:14:07 +0200140 """Run frame analyzer to compare the videos and print output."""
141 cmd = [
142 options.frame_analyzer,
143 '--label=%s' % options.label,
144 '--reference_file=%s' % options.ref_video,
145 '--test_file=%s' % options.test_video,
146 '--stats_file_ref=%s' % options.stats_file_ref,
147 '--stats_file_test=%s' % options.stats_file_test,
148 '--width=%d' % options.yuv_frame_width,
149 '--height=%d' % options.yuv_frame_height,
150 ]
151 if options.chartjson_result_file:
152 cmd.append('--chartjson_result_file=%s' % options.chartjson_result_file)
153 if options.aligned_output_file:
154 cmd.append('--aligned_output_file=%s' % options.aligned_output_file)
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200155 if yuv_directory:
156 cmd.append('--yuv_directory=%s' % yuv_directory)
Paulina Hensman12c62b92018-09-28 15:14:07 +0200157 frame_analyzer = subprocess.Popen(cmd, stdin=_DevNull(),
158 stdout=sys.stdout, stderr=sys.stderr)
159 frame_analyzer.wait()
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200160 if frame_analyzer.returncode != 0:
161 print 'Failed to run frame analyzer.'
Paulina Hensman12c62b92018-09-28 15:14:07 +0200162 return frame_analyzer.returncode
163
164
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200165def _RunVmaf(options, yuv_directory):
Paulina Hensman12c62b92018-09-28 15:14:07 +0200166 """ Run VMAF to compare videos and print output.
167
168 The provided vmaf directory is assumed to contain a c++ wrapper executable
169 and a model.
170
171 The yuv_directory is assumed to have been populated with a reference and test
172 video in .yuv format, with names according to the label.
173 """
174 cmd = [
175 options.vmaf,
176 'yuv420p',
177 str(options.yuv_frame_width),
178 str(options.yuv_frame_height),
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200179 os.path.join(yuv_directory, "ref.yuv"),
180 os.path.join(yuv_directory, "test.yuv"),
Paulina Hensman12c62b92018-09-28 15:14:07 +0200181 options.vmaf_model,
182 ]
183 if options.vmaf_phone_model:
184 cmd.append('--phone-model')
185
186 vmaf = subprocess.Popen(cmd, stdin=_DevNull(),
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200187 stdout=subprocess.PIPE, stderr=sys.stderr)
Paulina Hensman12c62b92018-09-28 15:14:07 +0200188 vmaf.wait()
189 if vmaf.returncode != 0:
190 print 'Failed to run VMAF.'
191 return 1
192 output = vmaf.stdout.read()
193 # Extract score from VMAF output.
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200194 try:
195 score = float(output.split('\n')[2].split()[3])
196 except (ValueError, IndexError):
197 print 'Error in VMAF output (expected "VMAF score = [float]" on line 3):'
198 print output
199 return 1
200
Paulina Hensman12c62b92018-09-28 15:14:07 +0200201 print 'RESULT Vmaf: %s= %f' % (options.label, score)
202 return 0
203
204
kjellanderd2b63cf2017-06-30 03:04:59 -0700205def main():
206 """The main function.
207
208 A simple invocation is:
Paulina Hensmanb671d462018-09-14 11:32:00 +0200209 ./webrtc/rtc_tools/compare_videos.py
kjellanderd2b63cf2017-06-30 03:04:59 -0700210 --ref_video=<path_and_name_of_reference_video>
211 --test_video=<path_and_name_of_test_video>
212 --frame_analyzer=<path_and_name_of_the_frame_analyzer_executable>
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000213
Paulina Hensman12c62b92018-09-28 15:14:07 +0200214 Running vmaf requires the following arguments:
215 --vmaf, --vmaf_model, --yuv_frame_width, --yuv_frame_height
216
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000217 Notice that the prerequisites for barcode_decoder.py also applies to this
218 script. The means the following executables have to be available in the PATH:
219 * zxing
220 * ffmpeg
kjellanderd2b63cf2017-06-30 03:04:59 -0700221 """
222 options = _ParseArgs()
223
Magnus Jedvert3e169ac2018-08-24 12:44:59 +0000224 if options.barcode_decoder:
225 path_to_decoder = options.barcode_decoder
226 else:
227 path_to_decoder = os.path.join(SCRIPT_DIR, 'barcode_tools',
228 'barcode_decoder.py')
229
230 if DecodeBarcodesInVideo(options, path_to_decoder,
231 options.ref_video, options.stats_file_ref) != 0:
232 return 1
233 if DecodeBarcodesInVideo(options, path_to_decoder,
234 options.test_video, options.stats_file_test) != 0:
235 return 1
236
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200237 if options.vmaf:
238 try:
239 # Directory to save temporary YUV files for VMAF in frame_analyzer.
240 yuv_directory = tempfile.mkdtemp()
Paulina Hensman12c62b92018-09-28 15:14:07 +0200241
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200242 # Run frame analyzer to compare the videos and print output.
243 if _RunFrameAnalyzer(options, yuv_directory=yuv_directory) != 0:
244 return 1
Paulina Hensman12c62b92018-09-28 15:14:07 +0200245
Paulina Hensman1c60ff52018-10-01 12:59:42 +0200246 # Run VMAF for further video comparison and print output.
247 return _RunVmaf(options, yuv_directory)
248 finally:
249 shutil.rmtree(yuv_directory)
250 else:
251 return _RunFrameAnalyzer(options)
kjellanderd2b63cf2017-06-30 03:04:59 -0700252
kjellanderd2b63cf2017-06-30 03:04:59 -0700253 return 0
254
255if __name__ == '__main__':
256 sys.exit(main())