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 convert_yuv_to_png_files(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.run_shell_command( 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 decode_frames(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.perform_action_on_all_files( 87 directory=input_directory, file_pattern='frame_', 88 file_extension='png', start_number=1, action=_decode_barcode_in_file, 89 command_line_decoder=zxing_path) 90 91 92 def _decode_barcode_in_file(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.run_shell_command( 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 _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 130 print 'Generating stats file: %s' % stats_file_name 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) 140 os.remove(barcode_file_name) 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) 150 os.remove(png_frame) 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() 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 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, _args = 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 = _parse_args() 269 270 # Convert the overlaid YUV video into a set of PNG frames. 271 if not convert_yuv_to_png_files(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 decode_frames(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 _generate_stats_file(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()) 292