blob: 16eb2f5c46ed54e3afed27dee3a4ee8923888267 [file] [log] [blame]
Rucha Katakwar222c5312022-03-21 14:17:01 -07001# Copyright 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Utility functions for processing video recordings.
15"""
16# Each item in this list corresponds to quality levels defined per
17# CamcorderProfile. For Video ITS, we will currently test below qualities
18# only if supported by the camera device.
leslieshaw5b54ee52023-08-29 13:22:30 -070019
20
Carlos Chanfb27a5a2024-07-18 18:04:56 +000021import dataclasses
Rucha Katakwarb6847db2022-03-24 16:49:13 -070022import logging
Leslie Shaw5755b4d2024-08-30 16:21:01 -070023import math
Rucha Katakwarb6847db2022-03-24 16:49:13 -070024import os.path
Jonathan Liu76f51682022-11-12 00:27:47 +000025import re
Rucha Katakwarb6847db2022-03-24 16:49:13 -070026import subprocess
Rucha Katakwar31a54502022-08-10 16:32:30 -070027import error_util
leslieshaw21c49e512023-08-25 13:47:28 -070028import image_processing_utils
Rucha Katakwarb6847db2022-03-24 16:49:13 -070029
30
Kailiang Chen6526b3a2024-01-25 11:28:58 -080031COLORSPACE_HDR = 'bt2020'
Jonathan Liu76f51682022-11-12 00:27:47 +000032HR_TO_SEC = 3600
Kailiang Chen6526b3a2024-01-25 11:28:58 -080033INDEX_FIRST_SUBGROUP = 1
Jonathan Liu76f51682022-11-12 00:27:47 +000034MIN_TO_SEC = 60
35
Rucha Katakwar089e6092022-03-30 11:19:26 -070036ITS_SUPPORTED_QUALITIES = (
Rucha Katakwar41133f92022-03-22 13:44:09 -070037 'HIGH',
38 '2160P',
39 '1080P',
40 '720P',
41 '480P',
42 'CIF',
43 'QCIF',
44 'QVGA',
45 'LOW',
46 'VGA'
Rucha Katakwar222c5312022-03-21 14:17:01 -070047)
Rucha Katakwar0ced2c32022-08-01 14:54:42 -070048
leslieshaw6fb2c072023-04-06 16:38:54 -070049LOW_RESOLUTION_SIZES = (
50 '176x144',
51 '192x144',
52 '352x288',
53 '384x288',
54 '320x240',
55)
Rucha Katakwarb6847db2022-03-24 16:49:13 -070056
leslieshaw6fb2c072023-04-06 16:38:54 -070057LOWEST_RES_TESTED_AREA = 640*360
leslieshaw99fd75b2023-01-09 15:01:19 -080058
leslieshaw541e2972023-02-28 11:06:07 -080059VIDEO_QUALITY_SIZE = {
Clemenz Portmann703010e2023-07-21 16:39:18 -070060 # '480P', '1080P', HIGH' & 'LOW' are not included as they are DUT-dependent
leslieshaw541e2972023-02-28 11:06:07 -080061 '2160P': '3840x2160',
leslieshaw541e2972023-02-28 11:06:07 -080062 '720P': '1280x720',
leslieshaw541e2972023-02-28 11:06:07 -080063 'VGA': '640x480',
64 'CIF': '352x288',
65 'QVGA': '320x240',
66 'QCIF': '176x144',
67}
68
69
Carlos Chanfb27a5a2024-07-18 18:04:56 +000070@dataclasses.dataclass
71class CommonPreviewSizeData:
72 """Class to store smallest and largest common sizes of preview and video."""
73 smallest_size: str
74 smallest_quality: str
75 largest_size: str
76 largest_quality: str
77
78
79def get_preview_video_sizes_union(cam, camera_id, min_area=0):
80 """Returns largest and smallest common size and quality of preview and video.
Clemenz Portmannfb236352024-06-10 10:13:40 -070081
82 Args:
83 cam: camera object.
84 camera_id: str; camera ID.
Carlos Chanfb27a5a2024-07-18 18:04:56 +000085 min_area: int; Optional filter to eliminate smaller sizes (ex. 640*480).
Clemenz Portmannfb236352024-06-10 10:13:40 -070086
87 Returns:
Carlos Chanfb27a5a2024-07-18 18:04:56 +000088 common_size_quality, CommonPreviewSizeData class
Clemenz Portmannfb236352024-06-10 10:13:40 -070089 """
Carlos Chanfb27a5a2024-07-18 18:04:56 +000090 supported_preview_sizes = set(cam.get_all_supported_preview_sizes(camera_id))
Clemenz Portmannfb236352024-06-10 10:13:40 -070091 supported_video_qualities = cam.get_supported_video_qualities(camera_id)
92 logging.debug('Supported video profiles & IDs: %s', supported_video_qualities)
93
leslieshaw541e2972023-02-28 11:06:07 -080094 # Make dictionary on video quality and size according to compatibility
95 supported_video_size_to_quality = {}
96 for quality in supported_video_qualities:
97 video_quality = quality.split(':')[0]
98 if video_quality in VIDEO_QUALITY_SIZE:
99 video_size = VIDEO_QUALITY_SIZE[video_quality]
100 supported_video_size_to_quality[video_size] = video_quality
101 logging.debug(
Carlos Chanfb27a5a2024-07-18 18:04:56 +0000102 'Supported video size to quality: %s', supported_video_size_to_quality
103 )
104 # Find the intersection of supported preview sizes and video sizes
105 common_sizes = supported_preview_sizes.intersection(
106 supported_video_size_to_quality.keys()
107 )
108 if not common_sizes:
109 raise AssertionError('No common size between Preview and Video!')
110 # Filter common sizes based on min_area
leslieshaw541e2972023-02-28 11:06:07 -0800111 size_to_area = lambda s: int(s.split('x')[0])*int(s.split('x')[1])
Carlos Chanfb27a5a2024-07-18 18:04:56 +0000112 common_sizes = (
113 [size for size in common_sizes if size_to_area(size) >= min_area]
114 )
115 if not common_sizes:
116 raise AssertionError(
117 'No common size above min_area between Preview and Video!'
118 )
119 # Use areas of video sizes to find the smallest and largest common size
120 smallest_common_size = min(common_sizes, key=size_to_area)
121 largest_common_size = max(common_sizes, key=size_to_area)
122 logging.debug('Smallest common size: %s', smallest_common_size)
123 logging.debug('Largest common size: %s', largest_common_size)
leslieshaw541e2972023-02-28 11:06:07 -0800124 # Find video quality of resolution with resolution as key
Carlos Chanfb27a5a2024-07-18 18:04:56 +0000125 smallest_common_quality = (
126 supported_video_size_to_quality[smallest_common_size]
127 )
128 logging.debug('Smallest common quality: %s', smallest_common_quality)
129 largest_common_quality = supported_video_size_to_quality[largest_common_size]
130 logging.debug('Largest common quality: %s', largest_common_quality)
131 common_size_quality = CommonPreviewSizeData(
132 smallest_size=smallest_common_size,
133 smallest_quality=smallest_common_quality,
134 largest_size=largest_common_size,
135 largest_quality=largest_common_quality
136 )
137 return common_size_quality
leslieshaw541e2972023-02-28 11:06:07 -0800138
139
Leslie Shaw5755b4d2024-08-30 16:21:01 -0700140def clamp_preview_sizes(preview_sizes, min_area=0, max_area=math.inf):
141 """Returns a list of preview_sizes with areas between min/max_area.
142
143 Args:
144 preview_sizes: list; sizes to be filtered (ex. "1280x720")
145 min_area: int; optional filter to eliminate sizes <= to the specified
146 area (ex. 640*480).
147 max_area: int; optional filter to eliminate sizes >= to the specified
148 area (ex. 3840*2160).
149 Returns:
150 preview_sizes: list; filtered preview sizes clamped by min/max_area.
151 """
152 size_to_area = lambda size: int(size.split('x')[0])*int(size.split('x')[1])
153 filtered_preview_sizes = [
154 size for size in preview_sizes
155 if max_area >= size_to_area(size) >= min_area]
156 logging.debug('Filtered preview sizes: %s', filtered_preview_sizes)
157 if not filtered_preview_sizes:
158 raise AssertionError(f'No preview sizes between {min_area} and {max_area}')
159 return filtered_preview_sizes
160
161
Clemenz Portmann6e00e052023-02-14 16:39:57 -0800162def log_ffmpeg_version():
163 """Logs the ffmpeg version being used."""
Rucha Katakwar31a54502022-08-10 16:32:30 -0700164
165 ffmpeg_version_cmd = ('ffmpeg -version')
166 p = subprocess.Popen(ffmpeg_version_cmd, shell=True, stdout=subprocess.PIPE)
167 output, _ = p.communicate()
168 if p.poll() != 0:
169 raise error_util.CameraItsError('Error running ffmpeg version cmd.')
170 decoded_output = output.decode('utf-8')
Clemenz Portmann6e00e052023-02-14 16:39:57 -0800171 logging.debug('ffmpeg version: %s', decoded_output.split(' ')[2])
Rucha Katakwar31a54502022-08-10 16:32:30 -0700172
173
Rucha Katakwar932bded2022-04-04 14:29:52 -0700174def extract_key_frames_from_video(log_path, video_file_name):
Clemenz Portmann8d40e422022-04-28 10:29:01 -0700175 """Returns a list of extracted key frames.
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700176
177 Ffmpeg tool is used to extract key frames from the video at path
Rucha Katakwar932bded2022-04-04 14:29:52 -0700178 os.path.join(log_path, video_file_name).
179 The extracted key frames will have the name video_file_name with "_key_frame"
leslieshaw3e0fb3c2024-03-31 23:56:04 -0700180 suffix to identify the frames for video of each quality. Since there can be
Shuzhen Wang838577f2024-08-13 09:27:41 -0700181 multiple key frames, each key frame image will be differentiated with its
leslieshaw3e0fb3c2024-03-31 23:56:04 -0700182 frame index. All the extracted key frames will be available in jpeg format
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700183 at the same path as the video file.
184
Rucha Katakwar05167b72022-05-25 13:42:32 -0700185 The run time flag '-loglevel quiet' hides the information from terminal.
186 In order to see the detailed output of ffmpeg command change the loglevel
187 option to 'info'.
188
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700189 Args:
leslieshaw3e0fb3c2024-03-31 23:56:04 -0700190 log_path: path for video file directory.
Rucha Katakwar932bded2022-04-04 14:29:52 -0700191 video_file_name: name of the video file.
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700192 Returns:
leslieshaw3e0fb3c2024-03-31 23:56:04 -0700193 key_frame_files: a sorted list of files which contains a name per key
194 frame. Ex: VID_20220325_050918_0_preview_1920x1440_key_frame_0001.png
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700195 """
Clemenz Portmann787a70c2024-05-06 13:44:32 -0700196 ffmpeg_image_name = f'{os.path.splitext(video_file_name)[0]}_key_frame'
Clemenz Portmann8d40e422022-04-28 10:29:01 -0700197 ffmpeg_image_file_path = os.path.join(
leslieshaw3e0fb3c2024-03-31 23:56:04 -0700198 log_path, ffmpeg_image_name + '_%04d.png')
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700199 cmd = ['ffmpeg',
Clemenz Portmann8d40e422022-04-28 10:29:01 -0700200 '-skip_frame',
201 'nokey',
202 '-i',
203 os.path.join(log_path, video_file_name),
204 '-vsync',
205 'vfr',
206 '-frame_pts',
207 'true',
208 ffmpeg_image_file_path,
Rucha Katakwar05167b72022-05-25 13:42:32 -0700209 '-loglevel',
210 'quiet',
Clemenz Portmann8d40e422022-04-28 10:29:01 -0700211 ]
212 logging.debug('Extracting key frames from: %s', video_file_name)
Jonathan Liu98b99812023-07-14 13:20:38 -0700213 _ = subprocess.call(cmd,
214 stdin=subprocess.DEVNULL,
215 stdout=subprocess.DEVNULL,
216 stderr=subprocess.DEVNULL)
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700217 arr = os.listdir(os.path.join(log_path))
218 key_frame_files = []
219 for file in arr:
220 if '.png' in file and not os.path.isdir(file) and ffmpeg_image_name in file:
221 key_frame_files.append(file)
leslieshaw3e0fb3c2024-03-31 23:56:04 -0700222 key_frame_files.sort()
Rucha Katakwar2e8ab092022-05-26 10:31:40 -0700223 logging.debug('Extracted key frames: %s', key_frame_files)
224 logging.debug('Length of key_frame_files: %d', len(key_frame_files))
Clemenz Portmann00c6ef32022-06-02 16:21:19 -0700225 if not key_frame_files:
Rucha Katakwar2c49c452022-05-19 14:29:03 -0700226 raise AssertionError('No key frames extracted. Check source video.')
227
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700228 return key_frame_files
229
230
231def get_key_frame_to_process(key_frame_files):
Clemenz Portmann8d40e422022-04-28 10:29:01 -0700232 """Returns the key frame file from the list of key_frame_files.
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700233
234 If the size of the list is 1 then the file in the list will be returned else
235 the file with highest frame_index will be returned for further processing.
236
237 Args:
238 key_frame_files: A list of key frame files.
239 Returns:
240 key_frame_file to be used for further processing.
241 """
Rucha Katakwar312d64e2022-05-25 10:24:25 -0700242 if not key_frame_files:
243 raise AssertionError('key_frame_files list is empty.')
Rucha Katakwarb6847db2022-03-24 16:49:13 -0700244 key_frame_files.sort()
245 return key_frame_files[-1]
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700246
247
Leslie Shawca6c7632024-07-10 18:41:10 -0700248def extract_all_frames_from_video(
249 log_path, video_file_name, img_format, video_fps=None):
250 """Extracts and returns a list of frames from a video using FFmpeg.
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700251
Leslie Shawca6c7632024-07-10 18:41:10 -0700252 Extract all frames from the video at path <log_path>/<video_file_name>.
253 The extracted frames will have the name video_file_name with "_frame"
254 suffix to identify the frames for video of each size. Each frame image
255 will be differentiated with its frame index. All extracted rames will be
256 available in the provided img_format format at the same path as the video.
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700257
Rucha Katakwar05167b72022-05-25 13:42:32 -0700258 The run time flag '-loglevel quiet' hides the information from terminal.
259 In order to see the detailed output of ffmpeg command change the loglevel
260 option to 'info'.
261
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700262 Args:
Leslie Shawca6c7632024-07-10 18:41:10 -0700263 log_path: str; directory containing video file.
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700264 video_file_name: str; name of the video file.
Leslie Shawca6c7632024-07-10 18:41:10 -0700265 img_format: str; desired image format for export frames. ex. 'png'
266 video_fps: str; fps of imported video.
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700267 Returns:
Leslie Shawca6c7632024-07-10 18:41:10 -0700268 an ordered list of paths to the extracted frame images.
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700269 """
270 logging.debug('Extracting all frames')
271 ffmpeg_image_name = f"{video_file_name.split('.')[0]}_frame"
272 logging.debug('ffmpeg_image_name: %s', ffmpeg_image_name)
273 ffmpeg_image_file_names = (
Dipen Patelc4c10e92024-05-07 16:35:35 -0700274 f'{os.path.join(log_path, ffmpeg_image_name)}_%04d.{img_format}')
Leslie Shawca6c7632024-07-10 18:41:10 -0700275 if video_fps:
276 cmd = [
277 'ffmpeg', '-i', os.path.join(log_path, video_file_name),
278 '-r', video_fps, # force a constant frame rate for reliability
279 ffmpeg_image_file_names, '-loglevel', 'quiet'
280 ]
281 else:
282 cmd = [
283 'ffmpeg', '-i', os.path.join(log_path, video_file_name),
284 '-vsync', 'passthrough', # prevents frame drops during decoding
285 ffmpeg_image_file_names, '-loglevel', 'quiet'
286 ]
287 subprocess.call(cmd,
288 stdin=subprocess.DEVNULL,
289 stdout=subprocess.DEVNULL,
290 stderr=subprocess.DEVNULL)
Avichal Rakeshf82d5262022-04-22 16:24:18 -0700291
Leslie Shawca6c7632024-07-10 18:41:10 -0700292 files = sorted(
293 [file for file in os.listdir(log_path) if
294 (file.endswith(img_format) and ffmpeg_image_name in file)])
295 if not files:
Rucha Katakwar2c49c452022-05-19 14:29:03 -0700296 raise AssertionError('No frames extracted. Check source video.')
297
Leslie Shawca6c7632024-07-10 18:41:10 -0700298 return files
Jonathan Liu76f51682022-11-12 00:27:47 +0000299
300
leslieshaw21c49e512023-08-25 13:47:28 -0700301def extract_last_key_frame_from_recording(log_path, file_name):
leslieshaw5b54ee52023-08-29 13:22:30 -0700302 """Extract last key frame from recordings.
leslieshaw21c49e512023-08-25 13:47:28 -0700303
304 Args:
305 log_path: str; file location
306 file_name: str file name for saved video
307
308 Returns:
Clemenz Portmann25529eb2023-09-17 21:13:15 -0700309 numpy image of last key frame
leslieshaw21c49e512023-08-25 13:47:28 -0700310 """
311 key_frame_files = extract_key_frames_from_video(log_path, file_name)
312 logging.debug('key_frame_files: %s', key_frame_files)
313
314 # Get the last_key_frame file to process.
315 last_key_frame_file = get_key_frame_to_process(key_frame_files)
316 logging.debug('last_key_frame: %s', last_key_frame_file)
317
Clemenz Portmann25529eb2023-09-17 21:13:15 -0700318 # Convert last_key_frame to numpy array
leslieshaw21c49e512023-08-25 13:47:28 -0700319 np_image = image_processing_utils.convert_image_to_numpy_array(
320 os.path.join(log_path, last_key_frame_file))
321 logging.debug('last key frame image shape: %s', np_image.shape)
322
323 return np_image
324
325
Shuzhen Wang9140e452024-08-01 13:41:52 -0700326def get_avg_frame_rate(video_file_name_with_path):
Jonathan Liu76f51682022-11-12 00:27:47 +0000327 """Get average frame rate assuming variable frame rate video.
328
329 Args:
330 video_file_name_with_path: path to the video to be analyzed
331 Returns:
332 Float. average frames per second.
333 """
334
Jonathan Liufb3f6032023-08-21 11:02:40 -0700335 cmd = ['ffprobe',
336 '-v',
337 'quiet',
338 '-show_streams',
339 '-select_streams',
340 'v:0', # first video stream
341 video_file_name_with_path
Jonathan Liu76f51682022-11-12 00:27:47 +0000342 ]
343 logging.debug('Getting frame rate')
344 raw_output = ''
345 try:
Jonathan Liu98b99812023-07-14 13:20:38 -0700346 raw_output = subprocess.check_output(cmd,
347 stdin=subprocess.DEVNULL,
348 stderr=subprocess.STDOUT)
Jonathan Liu76f51682022-11-12 00:27:47 +0000349 except subprocess.CalledProcessError as e:
350 raise AssertionError(str(e.output)) from e
351 if raw_output:
352 output = str(raw_output.decode('utf-8')).strip()
Jonathan Liufb3f6032023-08-21 11:02:40 -0700353 logging.debug('ffprobe command %s output: %s', ' '.join(cmd), output)
354 average_frame_rate_data = (
Kailiang Chen6526b3a2024-01-25 11:28:58 -0800355 re.search(r'avg_frame_rate=*([0-9]+/[0-9]+)', output)
356 .group(INDEX_FIRST_SUBGROUP)
Jonathan Liufb3f6032023-08-21 11:02:40 -0700357 )
358 average_frame_rate = (int(average_frame_rate_data.split('/')[0]) /
359 int(average_frame_rate_data.split('/')[1]))
360 logging.debug('Average FPS: %.4f', average_frame_rate)
361 return average_frame_rate
Jonathan Liu76f51682022-11-12 00:27:47 +0000362 else:
Jonathan Liufb3f6032023-08-21 11:02:40 -0700363 raise AssertionError('ffprobe failed to provide frame rate data')
Jonathan Liu76f51682022-11-12 00:27:47 +0000364
365
366def get_frame_deltas(video_file_name_with_path, timestamp_type='pts'):
367 """Get list of time diffs between frames.
368
369 Args:
370 video_file_name_with_path: path to the video to be analyzed
Jonathan Liu1c465e82025-02-18 16:24:21 -0800371 timestamp_type: 'pts' or 'pkt_dts'
Jonathan Liu76f51682022-11-12 00:27:47 +0000372 Returns:
373 List of floats. Time diffs between frames in seconds.
374 """
375
Jonathan Liu1c465e82025-02-18 16:24:21 -0800376 cmd = [
377 'ffprobe', '-show_entries', f'frame={timestamp_type}_time',
378 '-select_streams', 'v', video_file_name_with_path
379 ]
Jonathan Liu76f51682022-11-12 00:27:47 +0000380 logging.debug('Getting frame deltas')
381 raw_output = ''
382 try:
Jonathan Liu98b99812023-07-14 13:20:38 -0700383 raw_output = subprocess.check_output(cmd,
384 stdin=subprocess.DEVNULL,
385 stderr=subprocess.STDOUT)
Jonathan Liu76f51682022-11-12 00:27:47 +0000386 except subprocess.CalledProcessError as e:
387 raise AssertionError(str(e.output)) from e
388 if raw_output:
389 output = str(raw_output.decode('utf-8')).strip().split('\n')
390 deltas = []
Jonathan Liu61b36cf2023-06-14 10:30:06 -0700391 prev_time = None
Jonathan Liu76f51682022-11-12 00:27:47 +0000392 for line in output:
393 if timestamp_type not in line:
394 continue
Kailiang Chen6526b3a2024-01-25 11:28:58 -0800395 curr_time = float(re.search(r'time= *([0-9][0-9\.]*)', line)
396 .group(INDEX_FIRST_SUBGROUP))
Jonathan Liu61b36cf2023-06-14 10:30:06 -0700397 if prev_time is not None:
398 deltas.append(curr_time - prev_time)
Jonathan Liu76f51682022-11-12 00:27:47 +0000399 prev_time = curr_time
400 logging.debug('Frame deltas: %s', deltas)
401 return deltas
402 else:
403 raise AssertionError('ffprobe failed to provide frame delta data')
Kailiang Chen6526b3a2024-01-25 11:28:58 -0800404
405
406def get_video_colorspace(log_path, video_file_name):
407 """Get the video colorspace.
408
409 Args:
410 log_path: path for video file directory
411 video_file_name: name of the video file
412 Returns:
413 video colorspace, e.g. BT.2020 or BT.709
414 """
415
416 cmd = ['ffprobe',
417 '-show_streams',
418 '-select_streams',
419 'v:0',
420 '-of',
421 'json',
422 '-i',
423 os.path.join(log_path, video_file_name)
424 ]
425 logging.debug('Get the video colorspace')
426 raw_output = ''
427 try:
428 raw_output = subprocess.check_output(cmd,
429 stdin=subprocess.DEVNULL,
430 stderr=subprocess.STDOUT)
431 except subprocess.CalledProcessError as e:
432 raise AssertionError(str(e.output)) from e
433
434 logging.debug('raw_output: %s', raw_output)
435 if raw_output:
436 colorspace = ''
437 output = str(raw_output.decode('utf-8')).strip().split('\n')
438 logging.debug('output: %s', output)
439 for line in output:
440 logging.debug('line: %s', line)
441 metadata = re.search(r'"color_space": ("[a-z0-9]*")', line)
442 if metadata:
443 colorspace = metadata.group(INDEX_FIRST_SUBGROUP)
444 logging.debug('Colorspace: %s', colorspace)
445 return colorspace
446 else:
447 raise AssertionError('ffprobe failed to provide color space')