Home | History | Annotate | Download | only in barcode_tools
      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