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 import helper_functions
     15 
     16 _DEFAULT_BARCODE_WIDTH = 352
     17 _DEFAULT_BARCODES_FILE = 'barcodes.yuv'
     18 
     19 
     20 def generate_upca_barcodes(number_of_barcodes, barcode_width, barcode_height,
     21                            output_directory='.',
     22                            path_to_zxing='zxing-read-only'):
     23   """Generates UPC-A barcodes.
     24 
     25   This function generates a number_of_barcodes UPC-A barcodes. The function
     26   calls an example Java encoder from the Zxing library. The barcodes are
     27   generated as PNG images. The width of the barcodes shouldn't be less than 102
     28   pixels because otherwise Zxing can't properly generate the barcodes.
     29 
     30   Args:
     31     number_of_barcodes(int): The number of barcodes to generate.
     32     barcode_width(int): Width of barcode in pixels.
     33     barcode_height(int): Height of barcode in pixels.
     34     output_directory(string): Output directory where to store generated
     35       barcodes.
     36     path_to_zxing(string): The path to Zxing.
     37 
     38   Return:
     39     (bool): True if the conversion is successful.
     40   """
     41   base_file_name = os.path.join(output_directory, "barcode_")
     42   jars = _form_jars_string(path_to_zxing)
     43   command_line_encoder = 'com.google.zxing.client.j2se.CommandLineEncoder'
     44   barcode_width = str(barcode_width)
     45   barcode_height = str(barcode_height)
     46 
     47   errors = False
     48   for i in range(number_of_barcodes):
     49     suffix = helper_functions.zero_pad(i)
     50     # Barcodes starting from 0
     51     content = helper_functions.zero_pad(i, 11)
     52     output_file_name = base_file_name + suffix + ".png"
     53 
     54     command = ["java", "-cp", jars, command_line_encoder,
     55                "--barcode_format=UPC_A", "--height=%s" % barcode_height,
     56                "--width=%s" % barcode_width,
     57                "--output=%s" % (output_file_name), "%s" % (content)]
     58     try:
     59       helper_functions.run_shell_command(
     60           command, fail_msg=('Error during barcode %s generation' % content))
     61     except helper_functions.HelperError as err:
     62       print >> sys.stderr, err
     63       errors = True
     64   return not errors
     65 
     66 
     67 def convert_png_to_yuv_barcodes(input_directory='.', output_directory='.'):
     68   """Converts PNG barcodes to YUV barcode images.
     69 
     70   This function reads all the PNG files from the input directory which are in
     71   the format frame_xxxx.png, where xxxx is the number of the frame, starting
     72   from 0000. The frames should be consecutive numbers. The output YUV file is
     73   named frame_xxxx.yuv. The function uses ffmpeg to do the conversion.
     74 
     75   Args:
     76     input_directory(string): The input direcotry to read the PNG barcodes from.
     77     output_directory(string): The putput directory to write the YUV files to.
     78   Return:
     79     (bool): True if the conversion was without errors.
     80   """
     81   return helper_functions.perform_action_on_all_files(
     82       input_directory, 'barcode_', 'png', 0, _convert_to_yuv_and_delete,
     83       output_directory=output_directory, pattern='barcode_')
     84 
     85 
     86 def _convert_to_yuv_and_delete(output_directory, file_name, pattern):
     87   """Converts a PNG file to a YUV file and deletes the PNG file.
     88 
     89   Args:
     90     output_directory(string): The output directory for the YUV file.
     91     file_name(string): The PNG file name.
     92     pattern(string): The file pattern of the PNG/YUV file. The PNG/YUV files are
     93       named patternxx..x.png/yuv, where xx..x are digits starting from 00..0.
     94   Return:
     95     (bool): True upon successful conversion, false otherwise.
     96   """
     97   # Pattern should be in file name
     98   if not pattern in file_name:
     99     return False
    100   pattern_position = file_name.rfind(pattern)
    101 
    102   # Strip the path to the PNG file and replace the png extension with yuv
    103   yuv_file_name = file_name[pattern_position:-3] + 'yuv'
    104   yuv_file_name = os.path.join(output_directory, yuv_file_name)
    105 
    106   command = ['ffmpeg', '-i', '%s' % (file_name), '-pix_fmt', 'yuv420p',
    107              '%s' % (yuv_file_name)]
    108   try:
    109     helper_functions.run_shell_command(
    110         command, fail_msg=('Error during PNG to YUV conversion of %s' %
    111                            file_name))
    112     os.remove(file_name)
    113   except helper_functions.HelperError as err:
    114     print >> sys.stderr, err
    115     return False
    116   return True
    117 
    118 
    119 def combine_yuv_frames_into_one_file(output_file_name, input_directory='.'):
    120   """Combines several YUV frames into one YUV video file.
    121 
    122   The function combines the YUV frames from input_directory into one YUV video
    123   file. The frames should be named in the format frame_xxxx.yuv where xxxx
    124   stands for the frame number. The numbers have to be consecutive and start from
    125   0000. The YUV frames are removed after they have been added to the video.
    126 
    127   Args:
    128     output_file_name(string): The name of the file to produce.
    129     input_directory(string): The directory from which the YUV frames are read.
    130   Return:
    131     (bool): True if the frame stitching went OK.
    132   """
    133   output_file = open(output_file_name, "wb")
    134   success = helper_functions.perform_action_on_all_files(
    135       input_directory, 'barcode_', 'yuv', 0, _add_to_file_and_delete,
    136       output_file=output_file)
    137   output_file.close()
    138   return success
    139 
    140 def _add_to_file_and_delete(output_file, file_name):
    141   """Adds the contents of a file to a previously opened file.
    142 
    143   Args:
    144     output_file(file): The ouput file, previously opened.
    145     file_name(string): The file name of the file to add to the output file.
    146 
    147   Return:
    148     (bool): True if successful, False otherwise.
    149   """
    150   input_file = open(file_name, "rb")
    151   input_file_contents = input_file.read()
    152   output_file.write(input_file_contents)
    153   input_file.close()
    154   try:
    155     os.remove(file_name)
    156   except OSError as e:
    157     print >> sys.stderr, 'Error deleting file %s.\nError: %s' % (file_name, e)
    158     return False
    159   return True
    160 
    161 
    162 def _overlay_barcode_and_base_frames(barcodes_file, base_file, output_file,
    163                                      barcodes_component_sizes,
    164                                      base_component_sizes):
    165   """Overlays the next YUV frame from a file with a barcode.
    166 
    167   Args:
    168     barcodes_file(FileObject): The YUV file containing the barcodes (opened).
    169     base_file(FileObject): The base YUV file (opened).
    170     output_file(FileObject): The output overlaid file (opened).
    171     barcodes_component_sizes(list of tuples): The width and height of each Y, U
    172       and V plane of the barcodes YUV file.
    173     base_component_sizes(list of tuples): The width and height of each Y, U and
    174       V plane of the base YUV file.
    175   Return:
    176     (bool): True if there are more planes (i.e. frames) in the base file, false
    177       otherwise.
    178   """
    179   # We will loop three times - once for the Y, U and V planes
    180   for ((barcode_comp_width, barcode_comp_height),
    181       (base_comp_width, base_comp_height)) in zip(barcodes_component_sizes,
    182                                                   base_component_sizes):
    183     for base_row in range(base_comp_height):
    184       barcode_plane_traversed = False
    185       if (base_row < barcode_comp_height) and not barcode_plane_traversed:
    186         barcode_plane = barcodes_file.read(barcode_comp_width)
    187         if barcode_plane == "":
    188           barcode_plane_traversed = True
    189       else:
    190         barcode_plane_traversed = True
    191       base_plane = base_file.read(base_comp_width)
    192 
    193       if base_plane == "":
    194         return False
    195 
    196       if not barcode_plane_traversed:
    197         # Substitute part of the base component with the top component
    198         output_file.write(barcode_plane)
    199         base_plane = base_plane[barcode_comp_width:]
    200       output_file.write(base_plane)
    201   return True
    202 
    203 
    204 def overlay_yuv_files(barcode_width, barcode_height, base_width, base_height,
    205                       barcodes_file_name, base_file_name, output_file_name):
    206   """Overlays two YUV files starting from the upper left corner of both.
    207 
    208   Args:
    209     barcode_width(int): The width of the barcode (to be overlaid).
    210     barcode_height(int): The height of the barcode (to be overlaid).
    211     base_width(int): The width of a frame of the base file.
    212     base_height(int): The height of a frame of the base file.
    213     barcodes_file_name(string): The name of the YUV file containing the YUV
    214       barcodes.
    215     base_file_name(string): The name of the base YUV file.
    216     output_file_name(string): The name of the output file where the overlaid
    217       video will be written.
    218   """
    219   # Component sizes = [Y_sizes, U_sizes, V_sizes]
    220   barcodes_component_sizes = [(barcode_width, barcode_height),
    221                               (barcode_width/2, barcode_height/2),
    222                               (barcode_width/2, barcode_height/2)]
    223   base_component_sizes = [(base_width, base_height),
    224                           (base_width/2, base_height/2),
    225                           (base_width/2, base_height/2)]
    226 
    227   barcodes_file = open(barcodes_file_name, 'rb')
    228   base_file = open(base_file_name, 'rb')
    229   output_file = open(output_file_name, 'wb')
    230 
    231   data_left = True
    232   while data_left:
    233     data_left = _overlay_barcode_and_base_frames(barcodes_file, base_file,
    234                                                  output_file,
    235                                                  barcodes_component_sizes,
    236                                                  base_component_sizes)
    237 
    238   barcodes_file.close()
    239   base_file.close()
    240   output_file.close()
    241 
    242 
    243 def calculate_frames_number_from_yuv(yuv_width, yuv_height, file_name):
    244   """Calculates the number of frames of a YUV video.
    245 
    246   Args:
    247     yuv_width(int): Width of a frame of the yuv file.
    248     yuv_height(int): Height of a frame of the YUV file.
    249     file_name(string): The name of the YUV file.
    250   Return:
    251     (int): The number of frames in the YUV file.
    252   """
    253   file_size = os.path.getsize(file_name)
    254 
    255   y_plane_size = yuv_width * yuv_height
    256   u_plane_size = (yuv_width/2) * (yuv_height/2)  # Equals to V plane size too
    257   frame_size = y_plane_size + (2 * u_plane_size)
    258   return int(file_size/frame_size)  # Should be int anyway
    259 
    260 
    261 def _form_jars_string(path_to_zxing):
    262   """Forms the the Zxing core and javase jars argument.
    263 
    264   Args:
    265     path_to_zxing(string): The path to the Zxing checkout folder.
    266   Return:
    267     (string): The newly formed jars argument.
    268   """
    269   javase_jar = os.path.join(path_to_zxing, "javase", "javase.jar")
    270   core_jar = os.path.join(path_to_zxing, "core", "core.jar")
    271   delimiter = ':'
    272   if os.name != 'posix':
    273     delimiter = ';'
    274   return javase_jar + delimiter + core_jar
    275 
    276 def _parse_args():
    277   """Registers the command-line options."""
    278   usage = "usage: %prog [options]"
    279   parser = optparse.OptionParser(usage=usage)
    280 
    281   parser.add_option('--barcode_width', type='int',
    282                     default=_DEFAULT_BARCODE_WIDTH,
    283                     help=('Width of the barcodes to be overlaid on top of the'
    284                           ' base file. Default: %default'))
    285   parser.add_option('--barcode_height', type='int', default=32,
    286                     help=('Height of the barcodes to be overlaid on top of the'
    287                           ' base file. Default: %default'))
    288   parser.add_option('--base_frame_width', type='int', default=352,
    289                     help=('Width of the base YUV file\'s frames. '
    290                           'Default: %default'))
    291   parser.add_option('--base_frame_height', type='int', default=288,
    292                     help=('Height of the top YUV file\'s frames. '
    293                           'Default: %default'))
    294   parser.add_option('--barcodes_yuv', type='string',
    295                     default=_DEFAULT_BARCODES_FILE,
    296                     help=('The YUV file with the barcodes in YUV. '
    297                           'Default: %default'))
    298   parser.add_option('--base_yuv', type='string', default='base.yuv',
    299                     help=('The base YUV file to be overlaid. '
    300                           'Default: %default'))
    301   parser.add_option('--output_yuv', type='string', default='output.yuv',
    302                     help=('The output YUV file containing the base overlaid'
    303                           ' with the barcodes. Default: %default'))
    304   parser.add_option('--png_barcodes_output_dir', type='string', default='.',
    305                     help=('Output directory where the PNG barcodes will be '
    306                           'generated. Default: %default'))
    307   parser.add_option('--png_barcodes_input_dir', type='string', default='.',
    308                     help=('Input directory from where the PNG barcodes will be '
    309                           'read. Default: %default'))
    310   parser.add_option('--yuv_barcodes_output_dir', type='string', default='.',
    311                     help=('Output directory where the YUV barcodes will be '
    312                           'generated. Default: %default'))
    313   parser.add_option('--yuv_frames_input_dir', type='string', default='.',
    314                     help=('Input directory from where the YUV will be '
    315                           'read before combination. Default: %default'))
    316   parser.add_option('--zxing_dir', type='string', default='zxing',
    317                     help=('Path to the Zxing barcodes library. '
    318                           'Default: %default'))
    319   options = parser.parse_args()[0]
    320   return options
    321 
    322 
    323 def _main():
    324   """The main function.
    325 
    326   A simple invocation will be:
    327   ./webrtc/tools/barcode_tools/barcode_encoder.py --barcode_height=32
    328   --base_frame_width=352 --base_frame_height=288
    329   --base_yuv=<path_and_name_of_base_file>
    330   --output_yuv=<path and name_of_output_file>
    331   """
    332   options = _parse_args()
    333   # The barcodes with will be different than the base frame width only if
    334   # explicitly specified at the command line.
    335   if options.barcode_width == _DEFAULT_BARCODE_WIDTH:
    336     options.barcode_width = options.base_frame_width
    337   # If the user provides a value for the barcodes YUV video file, we will keep
    338   # it. Otherwise we create a temp file which is removed after it has been used.
    339   keep_barcodes_yuv_file = False
    340   if options.barcodes_yuv != _DEFAULT_BARCODES_FILE:
    341     keep_barcodes_yuv_file = True
    342 
    343   # Calculate the number of barcodes - it is equal to the number of frames in
    344   # the base file.
    345   number_of_barcodes = calculate_frames_number_from_yuv(
    346       options.base_frame_width, options.base_frame_height, options.base_yuv)
    347 
    348   script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
    349   zxing_dir = os.path.join(script_dir, 'third_party', 'zxing')
    350   # Generate barcodes - will generate them in PNG.
    351   generate_upca_barcodes(number_of_barcodes, options.barcode_width,
    352                          options.barcode_height,
    353                          output_directory=options.png_barcodes_output_dir,
    354                          path_to_zxing=zxing_dir)
    355   # Convert the PNG barcodes to to YUV format.
    356   convert_png_to_yuv_barcodes(options.png_barcodes_input_dir,
    357                               options.yuv_barcodes_output_dir)
    358   # Combine the YUV barcodes into one YUV file.
    359   combine_yuv_frames_into_one_file(options.barcodes_yuv,
    360                                    input_directory=options.yuv_frames_input_dir)
    361   # Overlay the barcodes over the base file.
    362   overlay_yuv_files(options.barcode_width, options.barcode_height,
    363                     options.base_frame_width, options.base_frame_height,
    364                     options.barcodes_yuv, options.base_yuv, options.output_yuv)
    365 
    366   if not keep_barcodes_yuv_file:
    367     # Remove the temporary barcodes YUV file
    368     os.remove(options.barcodes_yuv)
    369 
    370 
    371 if __name__ == '__main__':
    372   sys.exit(_main())
    373