| #!/usr/bin/env python | |
| # Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | |
| # | |
| # Use of this source code is governed by a BSD-style license | |
| # that can be found in the LICENSE file in the root of the source | |
| # tree. An additional intellectual property rights grant can be found | |
| # in the file PATENTS. All contributing project authors may | |
| # be found in the AUTHORS file in the root of the source tree. | |
| import optparse | |
| import os | |
| import sys | |
| if __name__ == '__main__': | |
| # Make sure we always can import helper_functions. | |
| sys.path.append(os.path.dirname(__file__)) | |
| import helper_functions | |
| # Chrome browsertests will throw away stderr; avoid that output gets lost. | |
| sys.stderr = sys.stdout | |
| def convert_yuv_to_png_files(yuv_file_name, yuv_frame_width, yuv_frame_height, | |
| output_directory, ffmpeg_path): | |
| """Converts a YUV video file into PNG frames. | |
| The function uses ffmpeg to convert the YUV file. The output of ffmpeg is in | |
| the form frame_xxxx.png, where xxxx is the frame number, starting from 0001. | |
| Args: | |
| yuv_file_name(string): The name of the YUV file. | |
| yuv_frame_width(int): The width of one YUV frame. | |
| yuv_frame_height(int): The height of one YUV frame. | |
| output_directory(string): The output directory where the PNG frames will be | |
| stored. | |
| ffmpeg_path(string): The path to the ffmpeg executable. If None, the PATH | |
| will be searched for it. | |
| Return: | |
| (bool): True if the conversion was OK. | |
| """ | |
| size_string = str(yuv_frame_width) + 'x' + str(yuv_frame_height) | |
| output_files_pattern = os.path.join(output_directory, 'frame_%04d.png') | |
| if not ffmpeg_path: | |
| ffmpeg_path = 'ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg' | |
| command = [ffmpeg_path, '-s', '%s' % size_string, '-i', '%s' | |
| % yuv_file_name, '-f', 'image2', '-vcodec', 'png', | |
| '%s' % output_files_pattern] | |
| try: | |
| print 'Converting YUV file to PNG images (may take a while)...' | |
| print ' '.join(command) | |
| helper_functions.run_shell_command( | |
| command, fail_msg='Error during YUV to PNG conversion') | |
| except helper_functions.HelperError, err: | |
| print 'Error executing command: %s. Error: %s' % (command, err) | |
| return False | |
| except OSError: | |
| print 'Did not find %s. Have you installed it?' % ffmpeg_path | |
| return False | |
| return True | |
| def decode_frames(input_directory, zxing_path): | |
| """Decodes the barcodes overlaid in each frame. | |
| The function uses the Zxing command-line tool from the Zxing C++ distribution | |
| to decode the barcode in every PNG frame from the input directory. The frames | |
| should be named frame_xxxx.png, where xxxx is the frame number. The frame | |
| numbers should be consecutive and should start from 0001. | |
| The decoding results in a frame_xxxx.txt file for every successfully decoded | |
| barcode. This file contains the decoded barcode as 12-digit string (UPC-A | |
| format: 11 digits content + one check digit). | |
| Args: | |
| input_directory(string): The input directory from where the PNG frames are | |
| read. | |
| zxing_path(string): The path to the zxing binary. If specified as None, | |
| the PATH will be searched for it. | |
| Return: | |
| (bool): True if the decoding succeeded. | |
| """ | |
| if not zxing_path: | |
| zxing_path = 'zxing.exe' if sys.platform == 'win32' else 'zxing' | |
| print 'Decoding barcodes from PNG files with %s...' % zxing_path | |
| return helper_functions.perform_action_on_all_files( | |
| directory=input_directory, file_pattern='frame_', | |
| file_extension='png', start_number=1, action=_decode_barcode_in_file, | |
| command_line_decoder=zxing_path) | |
| def _decode_barcode_in_file(file_name, command_line_decoder): | |
| """Decodes the barcode in the upper left corner of a PNG file. | |
| Args: | |
| file_name(string): File name of the PNG file. | |
| command_line_decoder(string): The ZXing command-line decoding tool. | |
| Return: | |
| (bool): True upon success, False otherwise. | |
| """ | |
| command = [command_line_decoder, '--try-harder', '--dump-raw', file_name] | |
| try: | |
| out = helper_functions.run_shell_command( | |
| command, fail_msg='Error during decoding of %s' % file_name) | |
| text_file = open('%s.txt' % file_name[:-4], 'w') | |
| text_file.write(out) | |
| text_file.close() | |
| except helper_functions.HelperError, err: | |
| print 'Barcode in %s cannot be decoded.' % file_name | |
| print err | |
| return False | |
| except OSError: | |
| print 'Did not find %s. Have you installed it?' % command_line_decoder | |
| return False | |
| return True | |
| def _generate_stats_file(stats_file_name, input_directory='.'): | |
| """Generate statistics file. | |
| The function generates a statistics file. The contents of the file are in the | |
| format <frame_name> <barcode>, where frame name is the name of every frame | |
| (effectively the frame number) and barcode is the decoded barcode. The frames | |
| and the helper .txt files are removed after they have been used. | |
| """ | |
| file_prefix = os.path.join(input_directory, 'frame_') | |
| stats_file = open(stats_file_name, 'w') | |
| print 'Generating stats file: %s' % stats_file_name | |
| for i in range(1, _count_frames_in(input_directory=input_directory) + 1): | |
| frame_number = helper_functions.zero_pad(i) | |
| barcode_file_name = file_prefix + frame_number + '.txt' | |
| png_frame = file_prefix + frame_number + '.png' | |
| entry_frame_number = helper_functions.zero_pad(i-1) | |
| entry = 'frame_' + entry_frame_number + ' ' | |
| if os.path.isfile(barcode_file_name): | |
| barcode = _read_barcode_from_text_file(barcode_file_name) | |
| os.remove(barcode_file_name) | |
| if _check_barcode(barcode): | |
| entry += (helper_functions.zero_pad(int(barcode[0:11])) + '\n') | |
| else: | |
| entry += 'Barcode error\n' # Barcode is wrongly detected. | |
| else: # Barcode file doesn't exist. | |
| entry += 'Barcode error\n' | |
| stats_file.write(entry) | |
| os.remove(png_frame) | |
| stats_file.close() | |
| def _read_barcode_from_text_file(barcode_file_name): | |
| """Reads the decoded barcode for a .txt file. | |
| Args: | |
| barcode_file_name(string): The name of the .txt file. | |
| Return: | |
| (string): The decoded barcode. | |
| """ | |
| barcode_file = open(barcode_file_name, 'r') | |
| barcode = barcode_file.read() | |
| barcode_file.close() | |
| return barcode | |
| def _check_barcode(barcode): | |
| """Check weather the UPC-A barcode was decoded correctly. | |
| This function calculates the check digit of the provided barcode and compares | |
| it to the check digit that was decoded. | |
| Args: | |
| barcode(string): The barcode (12-digit). | |
| Return: | |
| (bool): True if the barcode was decoded correctly. | |
| """ | |
| if len(barcode) != 12: | |
| return False | |
| r1 = range(0, 11, 2) # Odd digits | |
| r2 = range(1, 10, 2) # Even digits except last | |
| dsum = 0 | |
| # Sum all the even digits | |
| for i in r1: | |
| dsum += int(barcode[i]) | |
| # Multiply the sum by 3 | |
| dsum *= 3 | |
| # Add all the even digits except the check digit (12th digit) | |
| for i in r2: | |
| dsum += int(barcode[i]) | |
| # Get the modulo 10 | |
| dsum = dsum % 10 | |
| # If not 0 substract from 10 | |
| if dsum != 0: | |
| dsum = 10 - dsum | |
| # Compare result and check digit | |
| return dsum == int(barcode[11]) | |
| def _count_frames_in(input_directory='.'): | |
| """Calculates the number of frames in the input directory. | |
| The function calculates the number of frames in the input directory. The | |
| frames should be named frame_xxxx.png, where xxxx is the number of the frame. | |
| The numbers should start from 1 and should be consecutive. | |
| Args: | |
| input_directory(string): The input directory. | |
| Return: | |
| (int): The number of frames. | |
| """ | |
| file_prefix = os.path.join(input_directory, 'frame_') | |
| file_exists = True | |
| num = 1 | |
| while file_exists: | |
| file_name = (file_prefix + helper_functions.zero_pad(num) + '.png') | |
| if os.path.isfile(file_name): | |
| num += 1 | |
| else: | |
| file_exists = False | |
| return num - 1 | |
| def _parse_args(): | |
| """Registers the command-line options.""" | |
| usage = "usage: %prog [options]" | |
| parser = optparse.OptionParser(usage=usage) | |
| parser.add_option('--zxing_path', type='string', | |
| help=('The path to where the zxing executable is located. ' | |
| 'If omitted, it will be assumed to be present in the ' | |
| 'PATH with the name zxing[.exe].')) | |
| parser.add_option('--ffmpeg_path', type='string', | |
| help=('The path to where the ffmpeg executable is located. ' | |
| 'If omitted, it will be assumed to be present in the ' | |
| 'PATH with the name ffmpeg[.exe].')) | |
| parser.add_option('--yuv_frame_width', type='int', default=640, | |
| help='Width of the YUV file\'s frames. Default: %default') | |
| parser.add_option('--yuv_frame_height', type='int', default=480, | |
| help='Height of the YUV file\'s frames. Default: %default') | |
| parser.add_option('--yuv_file', type='string', default='output.yuv', | |
| help='The YUV file to be decoded. Default: %default') | |
| parser.add_option('--stats_file', type='string', default='stats.txt', | |
| help='The output stats file. Default: %default') | |
| parser.add_option('--png_working_dir', type='string', default='.', | |
| help=('The directory for temporary PNG images to be stored ' | |
| 'in when decoding from YUV before they\'re barcode ' | |
| 'decoded. If using Windows and a Cygwin-compiled ' | |
| 'zxing.exe, you should keep the default value to ' | |
| 'avoid problems. Default: %default')) | |
| options, _ = parser.parse_args() | |
| return options | |
| def _main(): | |
| """The main function. | |
| A simple invocation is: | |
| ./webrtc/tools/barcode_tools/barcode_decoder.py | |
| --yuv_file=<path_and_name_of_overlaid_yuv_video> | |
| --yuv_frame_width=640 --yuv_frame_height=480 | |
| --stats_file=<path_and_name_to_stats_file> | |
| """ | |
| options = _parse_args() | |
| # Convert the overlaid YUV video into a set of PNG frames. | |
| if not convert_yuv_to_png_files(options.yuv_file, options.yuv_frame_width, | |
| options.yuv_frame_height, | |
| output_directory=options.png_working_dir, | |
| ffmpeg_path=options.ffmpeg_path): | |
| print 'An error occurred converting from YUV to PNG frames.' | |
| return -1 | |
| # Decode the barcodes from the PNG frames. | |
| if not decode_frames(input_directory=options.png_working_dir, | |
| zxing_path=options.zxing_path): | |
| print 'An error occurred decoding barcodes from PNG frames.' | |
| return -2 | |
| # Generate statistics file. | |
| _generate_stats_file(options.stats_file, | |
| input_directory=options.png_working_dir) | |
| print 'Completed barcode decoding.' | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(_main()) |