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
10import optparse
11import os
12import sys
13
14import helper_functions
15
16_DEFAULT_BARCODE_WIDTH = 352
17_DEFAULT_BARCODES_FILE = 'barcodes.yuv'
18
19
20def 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
67def 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
86def _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
119def 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
140def _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
162def _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
204def 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
243def 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
261def _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
276def _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
323def _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
371if __name__ == '__main__':
372  sys.exit(_main())
373