xref: /aosp_15_r20/external/webrtc/rtc_tools/compare_videos.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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