| OLD | NEW |
| (Empty) |
| 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 | |
| 12 import sys | |
| 13 | |
| 14 if __name__ == '__main__': | |
| 15 # Make sure we always can import helper_functions. | |
| 16 sys.path.append(os.path.dirname(__file__)) | |
| 17 | |
| 18 import helper_functions | |
| 19 | |
| 20 # Chrome browsertests will throw away stderr; avoid that output gets lost. | |
| 21 sys.stderr = sys.stdout | |
| 22 | |
| 23 | |
| 24 def ConvertYuvToPngFiles(yuv_file_name, yuv_frame_width, yuv_frame_height, | |
| 25 output_directory, ffmpeg_path): | |
| 26 """Converts a YUV video file into PNG frames. | |
| 27 | |
| 28 The function uses ffmpeg to convert the YUV file. The output of ffmpeg is in | |
| 29 the form frame_xxxx.png, where xxxx is the frame number, starting from 0001. | |
| 30 | |
| 31 Args: | |
| 32 yuv_file_name(string): The name of the YUV file. | |
| 33 yuv_frame_width(int): The width of one YUV frame. | |
| 34 yuv_frame_height(int): The height of one YUV frame. | |
| 35 output_directory(string): The output directory where the PNG frames will be | |
| 36 stored. | |
| 37 ffmpeg_path(string): The path to the ffmpeg executable. If None, the PATH | |
| 38 will be searched for it. | |
| 39 | |
| 40 Return: | |
| 41 (bool): True if the conversion was OK. | |
| 42 """ | |
| 43 size_string = str(yuv_frame_width) + 'x' + str(yuv_frame_height) | |
| 44 output_files_pattern = os.path.join(output_directory, 'frame_%04d.png') | |
| 45 if not ffmpeg_path: | |
| 46 ffmpeg_path = 'ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg' | |
| 47 command = [ffmpeg_path, '-s', '%s' % size_string, '-i', '%s' | |
| 48 % yuv_file_name, '-f', 'image2', '-vcodec', 'png', | |
| 49 '%s' % output_files_pattern] | |
| 50 try: | |
| 51 print 'Converting YUV file to PNG images (may take a while)...' | |
| 52 print ' '.join(command) | |
| 53 helper_functions.RunShellCommand( | |
| 54 command, fail_msg='Error during YUV to PNG conversion') | |
| 55 except helper_functions.HelperError, err: | |
| 56 print 'Error executing command: %s. Error: %s' % (command, err) | |
| 57 return False | |
| 58 except OSError: | |
| 59 print 'Did not find %s. Have you installed it?' % ffmpeg_path | |
| 60 return False | |
| 61 return True | |
| 62 | |
| 63 | |
| 64 def DecodeFrames(input_directory, zxing_path): | |
| 65 """Decodes the barcodes overlaid in each frame. | |
| 66 | |
| 67 The function uses the Zxing command-line tool from the Zxing C++ distribution | |
| 68 to decode the barcode in every PNG frame from the input directory. The frames | |
| 69 should be named frame_xxxx.png, where xxxx is the frame number. The frame | |
| 70 numbers should be consecutive and should start from 0001. | |
| 71 The decoding results in a frame_xxxx.txt file for every successfully decoded | |
| 72 barcode. This file contains the decoded barcode as 12-digit string (UPC-A | |
| 73 format: 11 digits content + one check digit). | |
| 74 | |
| 75 Args: | |
| 76 input_directory(string): The input directory from where the PNG frames are | |
| 77 read. | |
| 78 zxing_path(string): The path to the zxing binary. If specified as None, | |
| 79 the PATH will be searched for it. | |
| 80 Return: | |
| 81 (bool): True if the decoding succeeded. | |
| 82 """ | |
| 83 if not zxing_path: | |
| 84 zxing_path = 'zxing.exe' if sys.platform == 'win32' else 'zxing' | |
| 85 print 'Decoding barcodes from PNG files with %s...' % zxing_path | |
| 86 return helper_functions.PerformActionOnAllFiles( | |
| 87 directory=input_directory, file_pattern='frame_', | |
| 88 file_extension='png', start_number=1, action=_DecodeBarcodeInFile, | |
| 89 command_line_decoder=zxing_path) | |
| 90 | |
| 91 | |
| 92 def _DecodeBarcodeInFile(file_name, command_line_decoder): | |
| 93 """Decodes the barcode in the upper left corner of a PNG file. | |
| 94 | |
| 95 Args: | |
| 96 file_name(string): File name of the PNG file. | |
| 97 command_line_decoder(string): The ZXing command-line decoding tool. | |
| 98 | |
| 99 Return: | |
| 100 (bool): True upon success, False otherwise. | |
| 101 """ | |
| 102 command = [command_line_decoder, '--try-harder', '--dump-raw', file_name] | |
| 103 try: | |
| 104 out = helper_functions.RunShellCommand( | |
| 105 command, fail_msg='Error during decoding of %s' % file_name) | |
| 106 text_file = open('%s.txt' % file_name[:-4], 'w') | |
| 107 text_file.write(out) | |
| 108 text_file.close() | |
| 109 except helper_functions.HelperError, err: | |
| 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 | |
| 115 return False | |
| 116 return True | |
| 117 | |
| 118 | |
| 119 def _GenerateStatsFile(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 | |
| 130 print 'Generating stats file: %s' % stats_file_name | |
| 131 for i in range(1, _CountFramesIn(input_directory=input_directory) + 1): | |
| 132 frame_number = helper_functions.ZeroPad(i) | |
| 133 barcode_file_name = file_prefix + frame_number + '.txt' | |
| 134 png_frame = file_prefix + frame_number + '.png' | |
| 135 entry_frame_number = helper_functions.ZeroPad(i-1) | |
| 136 entry = 'frame_' + entry_frame_number + ' ' | |
| 137 | |
| 138 if os.path.isfile(barcode_file_name): | |
| 139 barcode = _ReadBarcodeFromTextFile(barcode_file_name) | |
| 140 os.remove(barcode_file_name) | |
| 141 | |
| 142 if _CheckBarcode(barcode): | |
| 143 entry += (helper_functions.ZeroPad(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) | |
| 150 os.remove(png_frame) | |
| 151 | |
| 152 stats_file.close() | |
| 153 | |
| 154 | |
| 155 def _ReadBarcodeFromTextFile(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() | |
| 166 return barcode | |
| 167 | |
| 168 | |
| 169 def _CheckBarcode(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 _CountFramesIn(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.ZeroPad(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 _ParseArgs(): | |
| 229 """Registers the command-line options.""" | |
| 230 usage = "usage: %prog [options]" | |
| 231 parser = optparse.OptionParser(usage=usage) | |
| 232 | |
| 233 parser.add_option('--zxing_path', type='string', | |
| 234 help=('The path to where the zxing executable is located. ' | |
| 235 'If omitted, it will be assumed to be present in the ' | |
| 236 'PATH with the name zxing[.exe].')) | |
| 237 parser.add_option('--ffmpeg_path', type='string', | |
| 238 help=('The path to where the ffmpeg executable is located. ' | |
| 239 'If omitted, it will be assumed to be present in the ' | |
| 240 'PATH with the name ffmpeg[.exe].')) | |
| 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') | |
| 245 parser.add_option('--yuv_file', type='string', default='output.yuv', | |
| 246 help='The YUV file to be decoded. Default: %default') | |
| 247 parser.add_option('--stats_file', type='string', default='stats.txt', | |
| 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, _ = parser.parse_args() | |
| 256 return options | |
| 257 | |
| 258 | |
| 259 def main(): | |
| 260 """The main function. | |
| 261 | |
| 262 A simple invocation is: | |
| 263 ./webrtc/tools/barcode_tools/barcode_decoder.py | |
| 264 --yuv_file=<path_and_name_of_overlaid_yuv_video> | |
| 265 --yuv_frame_width=640 --yuv_frame_height=480 | |
| 266 --stats_file=<path_and_name_to_stats_file> | |
| 267 """ | |
| 268 options = _ParseArgs() | |
| 269 | |
| 270 # Convert the overlaid YUV video into a set of PNG frames. | |
| 271 if not ConvertYuvToPngFiles(options.yuv_file, options.yuv_frame_width, | |
| 272 options.yuv_frame_height, | |
| 273 output_directory=options.png_working_dir, | |
| 274 ffmpeg_path=options.ffmpeg_path): | |
| 275 print 'An error occurred converting from YUV to PNG frames.' | |
| 276 return -1 | |
| 277 | |
| 278 # Decode the barcodes from the PNG frames. | |
| 279 if not DecodeFrames(input_directory=options.png_working_dir, | |
| 280 zxing_path=options.zxing_path): | |
| 281 print 'An error occurred decoding barcodes from PNG frames.' | |
| 282 return -2 | |
| 283 | |
| 284 # Generate statistics file. | |
| 285 _GenerateStatsFile(options.stats_file, | |
| 286 input_directory=options.png_working_dir) | |
| 287 print 'Completed barcode decoding.' | |
| 288 return 0 | |
| 289 | |
| 290 if __name__ == '__main__': | |
| 291 sys.exit(main()) | |
| OLD | NEW |