Rucha Katakwar | 222c531 | 2022-03-21 14:17:01 -0700 | [diff] [blame] | 1 | # 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. |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 19 | import logging |
| 20 | import os.path |
| 21 | import subprocess |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 22 | |
| 23 | |
Rucha Katakwar | 089e609 | 2022-03-30 11:19:26 -0700 | [diff] [blame] | 24 | ITS_SUPPORTED_QUALITIES = ( |
Rucha Katakwar | 41133f9 | 2022-03-22 13:44:09 -0700 | [diff] [blame] | 25 | 'HIGH', |
| 26 | '2160P', |
| 27 | '1080P', |
| 28 | '720P', |
| 29 | '480P', |
| 30 | 'CIF', |
| 31 | 'QCIF', |
| 32 | 'QVGA', |
| 33 | 'LOW', |
| 34 | 'VGA' |
Rucha Katakwar | 222c531 | 2022-03-21 14:17:01 -0700 | [diff] [blame] | 35 | ) |
Rucha Katakwar | 0ced2c3 | 2022-08-01 14:54:42 -0700 | [diff] [blame] | 36 | |
| 37 | LOW_RESOLUTION_SIZES = ( |
| 38 | '176x144', |
| 39 | '192x144', |
| 40 | ) |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 41 | |
| 42 | |
Rucha Katakwar | 932bded | 2022-04-04 14:29:52 -0700 | [diff] [blame] | 43 | def extract_key_frames_from_video(log_path, video_file_name): |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 44 | """Returns a list of extracted key frames. |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 45 | |
| 46 | Ffmpeg tool is used to extract key frames from the video at path |
Rucha Katakwar | 932bded | 2022-04-04 14:29:52 -0700 | [diff] [blame] | 47 | os.path.join(log_path, video_file_name). |
| 48 | The extracted key frames will have the name video_file_name with "_key_frame" |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 49 | suffix to identify the frames for video of each quality.Since there can be |
| 50 | multiple key frames, each key frame image will be differentiated with it's |
| 51 | frame index.All the extracted key frames will be available in jpeg format |
| 52 | at the same path as the video file. |
| 53 | |
Rucha Katakwar | 05167b7 | 2022-05-25 13:42:32 -0700 | [diff] [blame] | 54 | The run time flag '-loglevel quiet' hides the information from terminal. |
| 55 | In order to see the detailed output of ffmpeg command change the loglevel |
| 56 | option to 'info'. |
| 57 | |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 58 | Args: |
| 59 | log_path: path for video file directory |
Rucha Katakwar | 932bded | 2022-04-04 14:29:52 -0700 | [diff] [blame] | 60 | video_file_name: name of the video file. |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 61 | Returns: |
| 62 | key_frame_files: A list of paths for each key frame extracted from the |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 63 | video. Ex: VID_20220325_050918_0_CIF_352x288.mp4 |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 64 | """ |
Rucha Katakwar | 932bded | 2022-04-04 14:29:52 -0700 | [diff] [blame] | 65 | ffmpeg_image_name = f"{video_file_name.split('.')[0]}_key_frame" |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 66 | ffmpeg_image_file_path = os.path.join( |
| 67 | log_path, ffmpeg_image_name + '_%02d.png') |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 68 | cmd = ['ffmpeg', |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 69 | '-skip_frame', |
| 70 | 'nokey', |
| 71 | '-i', |
| 72 | os.path.join(log_path, video_file_name), |
| 73 | '-vsync', |
| 74 | 'vfr', |
| 75 | '-frame_pts', |
| 76 | 'true', |
| 77 | ffmpeg_image_file_path, |
Rucha Katakwar | 05167b7 | 2022-05-25 13:42:32 -0700 | [diff] [blame] | 78 | '-loglevel', |
| 79 | 'quiet', |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 80 | ] |
| 81 | logging.debug('Extracting key frames from: %s', video_file_name) |
| 82 | _ = subprocess.call(cmd) |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 83 | arr = os.listdir(os.path.join(log_path)) |
| 84 | key_frame_files = [] |
| 85 | for file in arr: |
| 86 | if '.png' in file and not os.path.isdir(file) and ffmpeg_image_name in file: |
| 87 | key_frame_files.append(file) |
Rucha Katakwar | 2c49c45 | 2022-05-19 14:29:03 -0700 | [diff] [blame] | 88 | |
Rucha Katakwar | 2e8ab09 | 2022-05-26 10:31:40 -0700 | [diff] [blame] | 89 | logging.debug('Extracted key frames: %s', key_frame_files) |
| 90 | logging.debug('Length of key_frame_files: %d', len(key_frame_files)) |
Clemenz Portmann | 00c6ef3 | 2022-06-02 16:21:19 -0700 | [diff] [blame] | 91 | if not key_frame_files: |
Rucha Katakwar | 2c49c45 | 2022-05-19 14:29:03 -0700 | [diff] [blame] | 92 | raise AssertionError('No key frames extracted. Check source video.') |
| 93 | |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 94 | return key_frame_files |
| 95 | |
| 96 | |
| 97 | def get_key_frame_to_process(key_frame_files): |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 98 | """Returns the key frame file from the list of key_frame_files. |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 99 | |
| 100 | If the size of the list is 1 then the file in the list will be returned else |
| 101 | the file with highest frame_index will be returned for further processing. |
| 102 | |
| 103 | Args: |
| 104 | key_frame_files: A list of key frame files. |
| 105 | Returns: |
| 106 | key_frame_file to be used for further processing. |
| 107 | """ |
Rucha Katakwar | 312d64e | 2022-05-25 10:24:25 -0700 | [diff] [blame] | 108 | if not key_frame_files: |
| 109 | raise AssertionError('key_frame_files list is empty.') |
Rucha Katakwar | b6847db | 2022-03-24 16:49:13 -0700 | [diff] [blame] | 110 | key_frame_files.sort() |
| 111 | return key_frame_files[-1] |
Avichal Rakesh | f82d526 | 2022-04-22 16:24:18 -0700 | [diff] [blame] | 112 | |
| 113 | |
| 114 | def extract_all_frames_from_video(log_path, video_file_name, img_format): |
Clemenz Portmann | 8d40e42 | 2022-04-28 10:29:01 -0700 | [diff] [blame] | 115 | """Extracts and returns a list of all extracted frames. |
Avichal Rakesh | f82d526 | 2022-04-22 16:24:18 -0700 | [diff] [blame] | 116 | |
| 117 | Ffmpeg tool is used to extract all frames from the video at path |
| 118 | <log_path>/<video_file_name>. The extracted key frames will have the name |
| 119 | video_file_name with "_frame" suffix to identify the frames for video of each |
| 120 | size. Each frame image will be differentiated with its frame index. All |
| 121 | extracted key frames will be available in the provided img_format format at |
| 122 | the same path as the video file. |
| 123 | |
Rucha Katakwar | 05167b7 | 2022-05-25 13:42:32 -0700 | [diff] [blame] | 124 | The run time flag '-loglevel quiet' hides the information from terminal. |
| 125 | In order to see the detailed output of ffmpeg command change the loglevel |
| 126 | option to 'info'. |
| 127 | |
Avichal Rakesh | f82d526 | 2022-04-22 16:24:18 -0700 | [diff] [blame] | 128 | Args: |
| 129 | log_path: str; path for video file directory |
| 130 | video_file_name: str; name of the video file. |
| 131 | img_format: str; type of image to export frames into. ex. 'png' |
| 132 | Returns: |
| 133 | key_frame_files: An ordered list of paths for each frame extracted from the |
| 134 | video |
| 135 | """ |
| 136 | logging.debug('Extracting all frames') |
| 137 | ffmpeg_image_name = f"{video_file_name.split('.')[0]}_frame" |
| 138 | logging.debug('ffmpeg_image_name: %s', ffmpeg_image_name) |
| 139 | ffmpeg_image_file_names = ( |
| 140 | f'{os.path.join(log_path, ffmpeg_image_name)}_%03d.{img_format}') |
| 141 | cmd = [ |
| 142 | 'ffmpeg', '-i', os.path.join(log_path, video_file_name), |
Rucha Katakwar | 05167b7 | 2022-05-25 13:42:32 -0700 | [diff] [blame] | 143 | ffmpeg_image_file_names, '-loglevel', 'quiet' |
Avichal Rakesh | f82d526 | 2022-04-22 16:24:18 -0700 | [diff] [blame] | 144 | ] |
| 145 | _ = subprocess.call(cmd) |
| 146 | |
| 147 | file_list = sorted( |
| 148 | [_ for _ in os.listdir(log_path) if (_.endswith(img_format) |
| 149 | and ffmpeg_image_name in _)]) |
Clemenz Portmann | 00c6ef3 | 2022-06-02 16:21:19 -0700 | [diff] [blame] | 150 | if not file_list: |
Rucha Katakwar | 2c49c45 | 2022-05-19 14:29:03 -0700 | [diff] [blame] | 151 | raise AssertionError('No frames extracted. Check source video.') |
| 152 | |
Avichal Rakesh | f82d526 | 2022-04-22 16:24:18 -0700 | [diff] [blame] | 153 | return file_list |