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