blob: a54eb429795ee774f0c6f42270b43feb9a907503 [file] [log] [blame]
kjellanderd2b63cf2017-06-30 10:04:591#!/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
Mirko Bonadei67f88a02019-07-25 16:38:5410from __future__ import absolute_import
11from __future__ import division
12from __future__ import print_function
Paulina Hensmanede87962018-10-10 13:48:3013import json
kjellanderd2b63cf2017-06-30 10:04:5914import optparse
15import os
Magnus Jedvert3e169ac2018-08-24 12:44:5916import shutil
kjellanderd2b63cf2017-06-30 10:04:5917import subprocess
18import sys
Magnus Jedvert3e169ac2018-08-24 12:44:5919import tempfile
kjellanderd2b63cf2017-06-30 10:04:5920
kjellanderd2b63cf2017-06-30 10:04:5921SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
22
23# Chrome browsertests will throw away stderr; avoid that output gets lost.
24sys.stderr = sys.stdout
25
26
27def _ParseArgs():
Mirko Bonadei8cc66952020-10-30 09:13:4528 """Registers the command-line options."""
29 usage = 'usage: %prog [options]'
30 parser = optparse.OptionParser(usage=usage)
kjellanderd2b63cf2017-06-30 10:04:5931
Mirko Bonadei8cc66952020-10-30 09:13:4532 parser.add_option('--label',
33 type='string',
34 default='MY_TEST',
35 help=('Label of the test, used to identify different '
36 'tests. Default: %default'))
37 parser.add_option('--ref_video',
38 type='string',
39 help='Reference video to compare with (YUV).')
40 parser.add_option('--test_video',
41 type='string',
42 help=('Test video to be compared with the reference '
43 'video (YUV).'))
44 parser.add_option('--frame_analyzer',
45 type='string',
46 help='Path to the frame analyzer executable.')
47 parser.add_option('--aligned_output_file',
48 type='string',
49 help='Path for output aligned YUV or Y4M file.')
50 parser.add_option('--vmaf', type='string', help='Path to VMAF executable.')
51 parser.add_option('--vmaf_model',
52 type='string',
53 help='Path to VMAF model.')
54 parser.add_option('--vmaf_phone_model',
55 action='store_true',
56 help='Whether to use phone model in VMAF.')
57 parser.add_option(
58 '--yuv_frame_width',
59 type='int',
60 default=640,
61 help='Width of the YUV file\'s frames. Default: %default')
62 parser.add_option(
63 '--yuv_frame_height',
64 type='int',
65 default=480,
66 help='Height of the YUV file\'s frames. Default: %default')
67 parser.add_option('--chartjson_result_file',
68 type='str',
69 default=None,
70 help='Where to store perf results in chartjson format.')
71 options, _ = parser.parse_args()
kjellanderd2b63cf2017-06-30 10:04:5972
Mirko Bonadei8cc66952020-10-30 09:13:4573 if not options.ref_video:
74 parser.error('You must provide a path to the reference video!')
75 if not os.path.exists(options.ref_video):
76 parser.error('Cannot find the reference video at %s' %
77 options.ref_video)
kjellanderd2b63cf2017-06-30 10:04:5978
Mirko Bonadei8cc66952020-10-30 09:13:4579 if not options.test_video:
80 parser.error('You must provide a path to the test video!')
81 if not os.path.exists(options.test_video):
82 parser.error('Cannot find the test video at %s' % options.test_video)
kjellanderd2b63cf2017-06-30 10:04:5983
Mirko Bonadei8cc66952020-10-30 09:13:4584 if not options.frame_analyzer:
85 parser.error(
86 'You must provide the path to the frame analyzer executable!')
87 if not os.path.exists(options.frame_analyzer):
88 parser.error('Cannot find frame analyzer executable at %s!' %
89 options.frame_analyzer)
Paulina Hensman12c62b92018-09-28 13:14:0790
Mirko Bonadei8cc66952020-10-30 09:13:4591 if options.vmaf and not options.vmaf_model:
92 parser.error('You must provide a path to a VMAF model to use VMAF.')
Paulina Hensman12c62b92018-09-28 13:14:0793
Mirko Bonadei8cc66952020-10-30 09:13:4594 return options
95
kjellanderd2b63cf2017-06-30 10:04:5996
97def _DevNull():
Mirko Bonadei8cc66952020-10-30 09:13:4598 """On Windows, sometimes the inherited stdin handle from the parent process
kjellanderd2b63cf2017-06-30 10:04:5999 fails. Workaround this by passing null to stdin to the subprocesses commands.
100 This function can be used to create the null file handler.
101 """
Mirko Bonadei8cc66952020-10-30 09:13:45102 return open(os.devnull, 'r')
kjellanderd2b63cf2017-06-30 10:04:59103
Paulina Hensman12c62b92018-09-28 13:14:07104
Paulina Hensmana6471eb2018-10-05 12:34:33105def _RunFrameAnalyzer(options, yuv_directory=None):
Mirko Bonadei8cc66952020-10-30 09:13:45106 """Run frame analyzer to compare the videos and print output."""
107 cmd = [
108 options.frame_analyzer,
109 '--label=%s' % options.label,
110 '--reference_file=%s' % options.ref_video,
111 '--test_file=%s' % options.test_video,
112 '--width=%d' % options.yuv_frame_width,
113 '--height=%d' % options.yuv_frame_height,
114 ]
115 if options.chartjson_result_file:
116 cmd.append('--chartjson_result_file=%s' %
117 options.chartjson_result_file)
118 if options.aligned_output_file:
119 cmd.append('--aligned_output_file=%s' % options.aligned_output_file)
120 if yuv_directory:
121 cmd.append('--yuv_directory=%s' % yuv_directory)
122 frame_analyzer = subprocess.Popen(cmd,
123 stdin=_DevNull(),
124 stdout=sys.stdout,
125 stderr=sys.stderr)
126 frame_analyzer.wait()
127 if frame_analyzer.returncode != 0:
128 print('Failed to run frame analyzer.')
129 return frame_analyzer.returncode
Paulina Hensman12c62b92018-09-28 13:14:07130
131
Paulina Hensmanede87962018-10-10 13:48:30132def _RunVmaf(options, yuv_directory, logfile):
Mirko Bonadei8cc66952020-10-30 09:13:45133 """ Run VMAF to compare videos and print output.
Paulina Hensman12c62b92018-09-28 13:14:07134
Paulina Hensman12c62b92018-09-28 13:14:07135 The yuv_directory is assumed to have been populated with a reference and test
136 video in .yuv format, with names according to the label.
137 """
Mirko Bonadei8cc66952020-10-30 09:13:45138 cmd = [
139 options.vmaf,
140 'yuv420p',
141 str(options.yuv_frame_width),
142 str(options.yuv_frame_height),
143 os.path.join(yuv_directory, "ref.yuv"),
144 os.path.join(yuv_directory, "test.yuv"),
145 options.vmaf_model,
146 '--log',
147 logfile,
148 '--log-fmt',
149 'json',
150 ]
151 if options.vmaf_phone_model:
152 cmd.append('--phone-model')
Paulina Hensman12c62b92018-09-28 13:14:07153
Mirko Bonadei8cc66952020-10-30 09:13:45154 vmaf = subprocess.Popen(cmd,
155 stdin=_DevNull(),
156 stdout=sys.stdout,
157 stderr=sys.stderr)
158 vmaf.wait()
159 if vmaf.returncode != 0:
160 print('Failed to run VMAF.')
161 return 1
Paulina Hensmana6471eb2018-10-05 12:34:33162
Mirko Bonadei8cc66952020-10-30 09:13:45163 # Read per-frame scores from VMAF output and print.
164 with open(logfile) as f:
165 vmaf_data = json.load(f)
166 vmaf_scores = []
167 for frame in vmaf_data['frames']:
168 vmaf_scores.append(frame['metrics']['vmaf'])
169 print('RESULT VMAF: %s=' % options.label, vmaf_scores)
Paulina Hensmanede87962018-10-10 13:48:30170
Mirko Bonadei8cc66952020-10-30 09:13:45171 return 0
Paulina Hensman12c62b92018-09-28 13:14:07172
173
kjellanderd2b63cf2017-06-30 10:04:59174def main():
Mirko Bonadei8cc66952020-10-30 09:13:45175 """The main function.
kjellanderd2b63cf2017-06-30 10:04:59176
177 A simple invocation is:
Paulina Hensmanb671d462018-09-14 09:32:00178 ./webrtc/rtc_tools/compare_videos.py
kjellanderd2b63cf2017-06-30 10:04:59179 --ref_video=<path_and_name_of_reference_video>
180 --test_video=<path_and_name_of_test_video>
181 --frame_analyzer=<path_and_name_of_the_frame_analyzer_executable>
Magnus Jedvert3e169ac2018-08-24 12:44:59182
Paulina Hensman12c62b92018-09-28 13:14:07183 Running vmaf requires the following arguments:
184 --vmaf, --vmaf_model, --yuv_frame_width, --yuv_frame_height
kjellanderd2b63cf2017-06-30 10:04:59185 """
Mirko Bonadei8cc66952020-10-30 09:13:45186 options = _ParseArgs()
kjellanderd2b63cf2017-06-30 10:04:59187
Mirko Bonadei8cc66952020-10-30 09:13:45188 if options.vmaf:
189 try:
190 # Directory to save temporary YUV files for VMAF in frame_analyzer.
191 yuv_directory = tempfile.mkdtemp()
192 _, vmaf_logfile = tempfile.mkstemp()
Paulina Hensman12c62b92018-09-28 13:14:07193
Mirko Bonadei8cc66952020-10-30 09:13:45194 # Run frame analyzer to compare the videos and print output.
195 if _RunFrameAnalyzer(options, yuv_directory=yuv_directory) != 0:
196 return 1
Paulina Hensman12c62b92018-09-28 13:14:07197
Mirko Bonadei8cc66952020-10-30 09:13:45198 # Run VMAF for further video comparison and print output.
199 return _RunVmaf(options, yuv_directory, vmaf_logfile)
200 finally:
201 shutil.rmtree(yuv_directory)
202 os.remove(vmaf_logfile)
203 else:
204 return _RunFrameAnalyzer(options)
kjellanderd2b63cf2017-06-30 10:04:59205
Mirko Bonadei8cc66952020-10-30 09:13:45206 return 0
207
kjellanderd2b63cf2017-06-30 10:04:59208
209if __name__ == '__main__':
Mirko Bonadei8cc66952020-10-30 09:13:45210 sys.exit(main())