vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2012 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 | |
| 10 | import optparse |
| 11 | import os |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 12 | import sys |
| 13 | |
| 14 | import helper_functions |
| 15 | |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 16 | |
phoglund@webrtc.org | 8400246 | 2013-07-29 11:01:03 +0000 | [diff] [blame] | 17 | # Chrome browsertests will throw away stderr; avoid that output gets lost. |
| 18 | sys.stderr = sys.stdout |
| 19 | |
| 20 | |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 21 | def convert_yuv_to_png_files(yuv_file_name, yuv_frame_width, yuv_frame_height, |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 22 | output_directory, ffmpeg_dir=None): |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 23 | """Converts a YUV video file into PNG frames. |
| 24 | |
| 25 | The function uses ffmpeg to convert the YUV file. The output of ffmpeg is in |
| 26 | the form frame_xxxx.png, where xxxx is the frame number, starting from 0001. |
| 27 | |
| 28 | Args: |
| 29 | yuv_file_name(string): The name of the YUV file. |
| 30 | yuv_frame_width(int): The width of one YUV frame. |
| 31 | yuv_frame_height(int): The height of one YUV frame. |
| 32 | output_directory(string): The output directory where the PNG frames will be |
| 33 | stored. |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 34 | ffmpeg_dir(string): The directory containing the ffmpeg executable. If |
| 35 | omitted, the PATH will be searched for it. |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 36 | |
| 37 | Return: |
| 38 | (bool): True if the conversion was OK. |
| 39 | """ |
| 40 | size_string = str(yuv_frame_width) + 'x' + str(yuv_frame_height) |
| 41 | output_files_pattern = os.path.join(output_directory, 'frame_%04d.png') |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 42 | ffmpeg_executable = 'ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg' |
| 43 | if ffmpeg_dir: |
| 44 | ffmpeg_executable = os.path.join(ffmpeg_dir, ffmpeg_executable) |
kjellander@webrtc.org | b13dfbf | 2012-12-18 19:53:00 +0000 | [diff] [blame] | 45 | command = [ffmpeg_executable, '-s', '%s' % size_string, '-i', '%s' |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 46 | % yuv_file_name, '-f', 'image2', '-vcodec', 'png', |
| 47 | '%s' % output_files_pattern] |
| 48 | try: |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 49 | print 'Converting YUV file to PNG images (may take a while)...' |
| 50 | print ' '.join(command) |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 51 | helper_functions.run_shell_command( |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 52 | command, fail_msg='Error during YUV to PNG conversion') |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 53 | except helper_functions.HelperError, err: |
phoglund@webrtc.org | 8400246 | 2013-07-29 11:01:03 +0000 | [diff] [blame] | 54 | print 'Error executing command: %s. Error: %s' % (command, err) |
| 55 | return False |
| 56 | except OSError: |
| 57 | print ('Did not find %s. Have you installed it?' % ffmpeg_executable) |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 58 | return False |
| 59 | return True |
| 60 | |
| 61 | |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 62 | def decode_frames(input_directory, zxing_dir=None): |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 63 | """Decodes the barcodes overlaid in each frame. |
| 64 | |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 65 | The function uses the Zxing command-line tool from the Zxing C++ distribution |
| 66 | to decode the barcode in every PNG frame from the input directory. The frames |
| 67 | should be named frame_xxxx.png, where xxxx is the frame number. The frame |
| 68 | numbers should be consecutive and should start from 0001. |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 69 | The decoding results in a frame_xxxx.txt file for every successfully decoded |
| 70 | barcode. This file contains the decoded barcode as 12-digit string (UPC-A |
| 71 | format: 11 digits content + one check digit). |
| 72 | |
| 73 | Args: |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 74 | input_directory(string): The input directory from where the PNG frames are |
| 75 | read. |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 76 | zxing_dir(string): The directory containing the zxing executable. If |
| 77 | omitted, the PATH will be searched for it. |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 78 | Return: |
| 79 | (bool): True if the decoding went without errors. |
| 80 | """ |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 81 | zxing_executable = 'zxing.exe' if sys.platform == 'win32' else 'zxing' |
| 82 | if zxing_dir: |
| 83 | zxing_executable = os.path.join(zxing_dir, zxing_executable) |
| 84 | print 'Decoding barcodes from PNG files with %s...' % zxing_executable |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 85 | return helper_functions.perform_action_on_all_files( |
| 86 | directory=input_directory, file_pattern='frame_', |
| 87 | file_extension='png', start_number=1, action=_decode_barcode_in_file, |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 88 | command_line_decoder=zxing_executable) |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 89 | |
| 90 | |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 91 | def _decode_barcode_in_file(file_name, command_line_decoder): |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 92 | """Decodes the barcode in the upper left corner of a PNG file. |
| 93 | |
| 94 | Args: |
| 95 | file_name(string): File name of the PNG file. |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 96 | command_line_decoder(string): The ZXing command-line decoding tool. |
| 97 | |
| 98 | Return: |
| 99 | (bool): True upon success, False otherwise. |
| 100 | """ |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 101 | command = [command_line_decoder, '--try-harder', '--dump-raw', file_name] |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 102 | try: |
| 103 | out = helper_functions.run_shell_command( |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 104 | command, fail_msg='Error during decoding of %s' % file_name) |
| 105 | print 'Image %s : decoded barcode: %s' % (file_name, out) |
| 106 | text_file = open('%s.txt' % file_name[:-4], 'w') |
| 107 | text_file.write(out) |
| 108 | text_file.close() |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 109 | except helper_functions.HelperError, err: |
phoglund@webrtc.org | 8400246 | 2013-07-29 11:01:03 +0000 | [diff] [blame] | 110 | print 'Barcode in %s cannot be decoded.' % file_name |
| 111 | print err |
| 112 | return False |
| 113 | except OSError: |
| 114 | print ('Did not find %s. Have you installed it?' % command_line_decoder) |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 115 | return False |
| 116 | return True |
| 117 | |
| 118 | |
| 119 | def _generate_stats_file(stats_file_name, input_directory='.'): |
| 120 | """Generate statistics file. |
| 121 | |
| 122 | The function generates a statistics file. The contents of the file are in the |
| 123 | format <frame_name> <barcode>, where frame name is the name of every frame |
| 124 | (effectively the frame number) and barcode is the decoded barcode. The frames |
| 125 | and the helper .txt files are removed after they have been used. |
| 126 | """ |
| 127 | file_prefix = os.path.join(input_directory, 'frame_') |
| 128 | stats_file = open(stats_file_name, 'w') |
| 129 | |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 130 | print 'Generating stats file: %s' % stats_file_name |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 131 | for i in range(1, _count_frames_in(input_directory=input_directory) + 1): |
| 132 | frame_number = helper_functions.zero_pad(i) |
| 133 | barcode_file_name = file_prefix + frame_number + '.txt' |
| 134 | png_frame = file_prefix + frame_number + '.png' |
| 135 | entry_frame_number = helper_functions.zero_pad(i-1) |
| 136 | entry = 'frame_' + entry_frame_number + ' ' |
| 137 | |
| 138 | if os.path.isfile(barcode_file_name): |
| 139 | barcode = _read_barcode_from_text_file(barcode_file_name) |
vspasova@webrtc.org | 1b0a02e | 2012-08-30 12:56:38 +0000 | [diff] [blame] | 140 | os.remove(barcode_file_name) |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 141 | |
| 142 | if _check_barcode(barcode): |
| 143 | entry += (helper_functions.zero_pad(int(barcode[0:11])) + '\n') |
| 144 | else: |
| 145 | entry += 'Barcode error\n' # Barcode is wrongly detected. |
| 146 | else: # Barcode file doesn't exist. |
| 147 | entry += 'Barcode error\n' |
| 148 | |
| 149 | stats_file.write(entry) |
vspasova@webrtc.org | 1b0a02e | 2012-08-30 12:56:38 +0000 | [diff] [blame] | 150 | os.remove(png_frame) |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 151 | |
| 152 | stats_file.close() |
| 153 | |
| 154 | |
| 155 | def _read_barcode_from_text_file(barcode_file_name): |
| 156 | """Reads the decoded barcode for a .txt file. |
| 157 | |
| 158 | Args: |
| 159 | barcode_file_name(string): The name of the .txt file. |
| 160 | Return: |
| 161 | (string): The decoded barcode. |
| 162 | """ |
| 163 | barcode_file = open(barcode_file_name, 'r') |
| 164 | barcode = barcode_file.read() |
| 165 | barcode_file.close() |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 166 | return barcode |
| 167 | |
| 168 | |
| 169 | def _check_barcode(barcode): |
| 170 | """Check weather the UPC-A barcode was decoded correctly. |
| 171 | |
| 172 | This function calculates the check digit of the provided barcode and compares |
| 173 | it to the check digit that was decoded. |
| 174 | |
| 175 | Args: |
| 176 | barcode(string): The barcode (12-digit). |
| 177 | Return: |
| 178 | (bool): True if the barcode was decoded correctly. |
| 179 | """ |
| 180 | if len(barcode) != 12: |
| 181 | return False |
| 182 | |
| 183 | r1 = range(0, 11, 2) # Odd digits |
| 184 | r2 = range(1, 10, 2) # Even digits except last |
| 185 | dsum = 0 |
| 186 | # Sum all the even digits |
| 187 | for i in r1: |
| 188 | dsum += int(barcode[i]) |
| 189 | # Multiply the sum by 3 |
| 190 | dsum *= 3 |
| 191 | # Add all the even digits except the check digit (12th digit) |
| 192 | for i in r2: |
| 193 | dsum += int(barcode[i]) |
| 194 | # Get the modulo 10 |
| 195 | dsum = dsum % 10 |
| 196 | # If not 0 substract from 10 |
| 197 | if dsum != 0: |
| 198 | dsum = 10 - dsum |
| 199 | # Compare result and check digit |
| 200 | return dsum == int(barcode[11]) |
| 201 | |
| 202 | |
| 203 | def _count_frames_in(input_directory = '.'): |
| 204 | """Calculates the number of frames in the input directory. |
| 205 | |
| 206 | The function calculates the number of frames in the input directory. The |
| 207 | frames should be named frame_xxxx.png, where xxxx is the number of the frame. |
| 208 | The numbers should start from 1 and should be consecutive. |
| 209 | |
| 210 | Args: |
| 211 | input_directory(string): The input directory. |
| 212 | Return: |
| 213 | (int): The number of frames. |
| 214 | """ |
| 215 | file_prefix = os.path.join(input_directory, 'frame_') |
| 216 | file_exists = True |
| 217 | num = 1 |
| 218 | |
| 219 | while file_exists: |
| 220 | file_name = (file_prefix + helper_functions.zero_pad(num) + '.png') |
| 221 | if os.path.isfile(file_name): |
| 222 | num += 1 |
| 223 | else: |
| 224 | file_exists = False |
| 225 | return num - 1 |
| 226 | |
| 227 | |
| 228 | def _parse_args(): |
| 229 | """Registers the command-line options.""" |
| 230 | usage = "usage: %prog [options]" |
| 231 | parser = optparse.OptionParser(usage=usage) |
| 232 | |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 233 | parser.add_option('--zxing_dir', type='string', |
| 234 | help=('The path to the directory where the zxing executable' |
| 235 | 'is located. If omitted, it will be assumed to be ' |
| 236 | 'present in the PATH.')) |
| 237 | parser.add_option('--ffmpeg_dir', type='string', default=None, |
| 238 | help=('The path to the directory where the ffmpeg ' |
| 239 | 'executable is located. If omitted, it will be ' |
| 240 | 'assumed to be present in the PATH.')) |
| 241 | parser.add_option('--yuv_frame_width', type='int', default=640, |
| 242 | help='Width of the YUV file\'s frames. Default: %default') |
| 243 | parser.add_option('--yuv_frame_height', type='int', default=480, |
| 244 | help='Height of the YUV file\'s frames. Default: %default') |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 245 | parser.add_option('--yuv_file', type='string', default='output.yuv', |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 246 | help='The YUV file to be decoded. Default: %default') |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 247 | parser.add_option('--stats_file', type='string', default='stats.txt', |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 248 | help='The output stats file. Default: %default') |
| 249 | parser.add_option('--png_working_dir', type='string', default='.', |
| 250 | help=('The directory for temporary PNG images to be stored ' |
| 251 | 'in when decoding from YUV before they\'re barcode ' |
| 252 | 'decoded. If using Windows and a Cygwin-compiled ' |
| 253 | 'zxing.exe, you should keep the default value to ' |
| 254 | 'avoid problems. Default: %default')) |
| 255 | options, _args = parser.parse_args() |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 256 | return options |
| 257 | |
| 258 | |
| 259 | def _main(): |
| 260 | """The main function. |
| 261 | |
| 262 | A simple invocation is: |
kjellander@webrtc.org | a6ff845 | 2013-05-14 09:43:04 +0000 | [diff] [blame] | 263 | ./webrtc/tools/barcode_tools/barcode_decoder.py |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 264 | --yuv_file=<path_and_name_of_overlaid_yuv_video> |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 265 | --yuv_frame_width=640 --yuv_frame_height=480 |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 266 | --stats_file=<path_and_name_to_stats_file> |
| 267 | """ |
| 268 | options = _parse_args() |
| 269 | |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 270 | # Convert the overlaid YUV video into a set of PNG frames. |
kjellander@webrtc.org | ccb52c2 | 2012-10-10 16:11:28 +0000 | [diff] [blame] | 271 | if not convert_yuv_to_png_files(options.yuv_file, options.yuv_frame_width, |
| 272 | options.yuv_frame_height, |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 273 | output_directory=options.png_working_dir, |
| 274 | ffmpeg_dir=options.ffmpeg_dir): |
phoglund@webrtc.org | 8400246 | 2013-07-29 11:01:03 +0000 | [diff] [blame] | 275 | print 'An error occurred converting from YUV to PNG frames.' |
kjellander@webrtc.org | ccb52c2 | 2012-10-10 16:11:28 +0000 | [diff] [blame] | 276 | return -1 |
| 277 | |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 278 | # Decode the barcodes from the PNG frames. |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 279 | if not decode_frames(input_directory=options.png_working_dir, |
| 280 | zxing_dir=options.zxing_dir): |
phoglund@webrtc.org | 8400246 | 2013-07-29 11:01:03 +0000 | [diff] [blame] | 281 | print 'An error occurred decoding barcodes from PNG frames.' |
kjellander@webrtc.org | ccb52c2 | 2012-10-10 16:11:28 +0000 | [diff] [blame] | 282 | return -2 |
| 283 | |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 284 | # Generate statistics file. |
| 285 | _generate_stats_file(options.stats_file, |
kjellander@webrtc.org | 38ebf98 | 2013-03-08 10:58:21 +0000 | [diff] [blame] | 286 | input_directory=options.png_working_dir) |
| 287 | print 'Completed barcode decoding.' |
kjellander@webrtc.org | ccb52c2 | 2012-10-10 16:11:28 +0000 | [diff] [blame] | 288 | return 0 |
vspasova@webrtc.org | 400e7da | 2012-08-15 10:25:12 +0000 | [diff] [blame] | 289 | |
| 290 | if __name__ == '__main__': |
| 291 | sys.exit(_main()) |