Created a barcode encoder and decoder Python tools.
The barcode encoder generates barcodes as PNG files, than converts them to YUV files, then stitches these into one YUV video file and finaly overlays this video over the base video to produce a YUV video of the base with encoded barcodes in the upper part of the video. The decoder gets a YUV video with overlaid barcodes and first splits it into PNG files, than decodes every file, and finally generates a stats file. The encoder and decoder use Zxing and its Java command-line tools for the encoding and decoding. They also use ffmpeg for the conversion between PNG and YUV and vice versa. BUG= TEST= The tools could be used from trunk as: ./tools/barcode_tools/barcode_encoder.py --barcode_height=32 --base_frame_width=352 --base_frame_height=288 --base_yuv=<path_nad_name_to_base_file> --output_yuv=<path_nad_name_to_output_file> ./tools/barcode_tools/barcode_decoder.py --yuv_file=<path_and_name_to_yuv_file> --yuv_frame_width=352 --yuv=frame_height=288 --barcode_height=32 --stats_file=<path_and_name_of_stats_file> By default the width of the barcodes is the same as the width of the base YUV frames. This could be changes with the command-line option --barcode_width Review URL: https://webrtc-codereview.appspot.com/679010 git-svn-id: http://webrtc.googlecode.com/svn/trunk@2612 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
8e221ee102
commit
400e7da51b
21
tools/barcode_tools/DEPS
Normal file
21
tools/barcode_tools/DEPS
Normal file
@ -0,0 +1,21 @@
|
||||
# This is trimmed down version of the main tools DEPS file which is to be used
|
||||
# in Chromiums PyAuto WebRTC video quality measurement test. We will only
|
||||
# need the Zxing dependencies as we only use the barcode tools in this test.
|
||||
|
||||
deps = {
|
||||
# Used by tools/barcode_tools
|
||||
"third_party/zxing/core":
|
||||
"http://zxing.googlecode.com/svn/trunk/core@2349",
|
||||
|
||||
# Used by tools/barcode_tools
|
||||
"third_party/zxing/javase":
|
||||
"http://zxing.googlecode.com/svn/trunk/javase@2349",
|
||||
}
|
||||
|
||||
hooks = [
|
||||
{
|
||||
# A change to a .gyp, .gypi, or to GYP itself should run the generator.
|
||||
"pattern": ".",
|
||||
"action": ["python", "src/build/gyp_chromium", "--depth=.",
|
||||
"barcode_tools.gyp"],
|
||||
},
|
285
tools/barcode_tools/barcode_decoder.py
Executable file
285
tools/barcode_tools/barcode_decoder.py
Executable file
@ -0,0 +1,285 @@
|
||||
#!/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 subprocess
|
||||
import sys
|
||||
|
||||
import helper_functions
|
||||
|
||||
_DEFAULT_BARCODE_WIDTH = 352
|
||||
|
||||
|
||||
def convert_yuv_to_png_files(yuv_file_name, yuv_frame_width, yuv_frame_height,
|
||||
output_directory = '.'):
|
||||
"""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.
|
||||
|
||||
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')
|
||||
command = ['ffmpeg', '-s', '%s' % size_string, '-i', '%s'
|
||||
% yuv_file_name, '-f', 'image2', '-vcodec', 'png',
|
||||
'%s' % output_files_pattern]
|
||||
try:
|
||||
helper_functions.run_shell_command(
|
||||
command, msg='Error during YUV to PNG conversion')
|
||||
except helper_functions.HelperError, err:
|
||||
print err
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def decode_frames(barcode_width, barcode_height, input_directory='.',
|
||||
path_to_zxing='zxing-read-only'):
|
||||
"""Decodes the barcodes overlaid in each frame.
|
||||
|
||||
The function uses the example Java command-line tool from the Zxing
|
||||
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:
|
||||
barcode_width(int): Width of the barcode.
|
||||
barcode_height(int): Height of the barcode.
|
||||
input_directory(string): The input directory from where the PNG frames are
|
||||
read.
|
||||
path_to_zxing(string): The path to Zxing.
|
||||
Return:
|
||||
(bool): True if the decoding went without errors.
|
||||
"""
|
||||
jars = helper_functions.form_jars_string(path_to_zxing)
|
||||
command_line_decoder ='com.google.zxing.client.j2se.CommandLineRunner'
|
||||
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,
|
||||
barcode_width=barcode_width, barcode_height=barcode_height, jars=jars,
|
||||
command_line_decoder=command_line_decoder)
|
||||
|
||||
|
||||
def _decode_barcode_in_file(file_name, barcode_width, barcode_height, jars,
|
||||
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.
|
||||
barcode_width(int): Width of the barcode (in pixels).
|
||||
barcode_height(int): Height of the barcode (in pixels)
|
||||
jars(string): The Zxing core and javase string.
|
||||
command_line_decoder(string): The ZXing command-line decoding tool.
|
||||
|
||||
Return:
|
||||
(bool): True upon success, False otherwise.
|
||||
"""
|
||||
command = ['java', '-cp', '%s' % jars,
|
||||
'%s' % command_line_decoder, '--products_only',
|
||||
'--dump_results', '--brief', '--crop=%d,%d,%d,%d' %
|
||||
(0, 0, barcode_width, barcode_height),
|
||||
'%s' % file_name]
|
||||
try:
|
||||
out = helper_functions.run_shell_command(
|
||||
command, msg='Error during decoding of %s' % file_name)
|
||||
if not 'Success' in out:
|
||||
sys.stderr.write('Barcode in %s cannot be decoded\n' % file_name)
|
||||
return False
|
||||
except helper_functions.HelperError, err:
|
||||
print err
|
||||
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')
|
||||
|
||||
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)
|
||||
helper_functions.delete_file(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)
|
||||
helper_functions.delete_file(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('--yuv_frame_width', type='int', default=352,
|
||||
help=('Width of the YUV file\'s frames. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--yuv_frame_height', type='int', default=288,
|
||||
help=('Height of the YUV file\'s frames. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--barcode_width', type='int',
|
||||
default=_DEFAULT_BARCODE_WIDTH,
|
||||
help=('Width of the barcodes. Default: %default'))
|
||||
parser.add_option('--barcode_height', type='int', default=32,
|
||||
help=('Height of the barcodes. 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_output_dir', type='string', default='.',
|
||||
help=('The output directory for the generated PNG files. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--png_input_dir', type='string', default='.',
|
||||
help=('The input directory for the generated PNG files. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--path_to_zxing', type='string', default='zxing',
|
||||
help=('The path to Zxing. Default: %default'))
|
||||
options = parser.parse_args()[0]
|
||||
return options
|
||||
|
||||
|
||||
def _main():
|
||||
"""The main function.
|
||||
|
||||
A simple invocation is:
|
||||
./tools/barcode_tolls/barcode_decoder.py
|
||||
--yuv_file=<path_and_name_of_overlaid_yuv_video>
|
||||
--yuv_frame_width=352 --yuv_frame_height=288 --barcode_height=32
|
||||
--stats_file=<path_and_name_to_stats_file>
|
||||
"""
|
||||
options = _parse_args()
|
||||
|
||||
# The barcodes with will be different than the base frame width only if
|
||||
# explicitly specified at the command line.
|
||||
if options.barcode_width == _DEFAULT_BARCODE_WIDTH:
|
||||
options.barcode_width = options.yuv_frame_width
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
zxing_dir = os.path.join(script_dir, '..', 'third_party', 'zxing')
|
||||
|
||||
# Convert the overlaid YUV video into a set of PNG frames.
|
||||
convert_yuv_to_png_files(options.yuv_file, options.yuv_frame_width,
|
||||
options.yuv_frame_height,
|
||||
output_directory=options.png_output_dir)
|
||||
# Decode the barcodes from the PNG frames.
|
||||
decode_frames(options.barcode_width, options.barcode_height,
|
||||
input_directory=options.png_input_dir, path_to_zxing=zxing_dir)
|
||||
# Generate statistics file.
|
||||
_generate_stats_file(options.stats_file,
|
||||
input_directory=options.png_input_dir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(_main())
|
353
tools/barcode_tools/barcode_encoder.py
Executable file
353
tools/barcode_tools/barcode_encoder.py
Executable file
@ -0,0 +1,353 @@
|
||||
#!/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 subprocess
|
||||
import sys
|
||||
|
||||
import helper_functions
|
||||
|
||||
_DEFAULT_BARCODE_WIDTH = 352
|
||||
_DEFAULT_BARCODES_FILE = 'barcodes.yuv'
|
||||
|
||||
|
||||
def generate_upca_barcodes(number_of_barcodes, barcode_width, barcode_height,
|
||||
output_directory='.',
|
||||
path_to_zxing='zxing-read-only'):
|
||||
"""Generates UPC-A barcodes.
|
||||
|
||||
This function generates a number_of_barcodes UPC-A barcodes. The function
|
||||
calls an example Java encoder from the Zxing library. The barcodes are
|
||||
generated as PNG images. The width of the barcodes shouldn't be less than 102
|
||||
pixels because otherwise Zxing can't properly generate the barcodes.
|
||||
|
||||
Args:
|
||||
number_of_barcodes(int): The number of barcodes to generate.
|
||||
barcode_width(int): Width of barcode in pixels.
|
||||
barcode_height(int): Height of barcode in pixels.
|
||||
output_directory(string): Output directory where to store generated
|
||||
barcodes.
|
||||
path_to_zxing(string): The path to Zxing.
|
||||
|
||||
Return:
|
||||
(bool): True if the conversion is successful.
|
||||
"""
|
||||
base_file_name = os.path.join(output_directory, "barcode_")
|
||||
jars = helper_functions.form_jars_string(path_to_zxing)
|
||||
command_line_encoder ='com.google.zxing.client.j2se.CommandLineEncoder'
|
||||
barcode_width = str(barcode_width)
|
||||
barcode_height = str(barcode_height)
|
||||
|
||||
errors = False
|
||||
for i in range(number_of_barcodes):
|
||||
suffix = helper_functions.zero_pad(i)
|
||||
# Barcodes starting from 0
|
||||
content = helper_functions.zero_pad(i, 11)
|
||||
output_file_name = base_file_name + suffix + ".png"
|
||||
|
||||
command = ["java", "-cp", jars, command_line_encoder,
|
||||
"--barcode_format=UPC_A", "--height=%s" % barcode_height,
|
||||
"--width=%s" % barcode_width,
|
||||
"--output=%s" % (output_file_name), "%s" % (content)]
|
||||
try:
|
||||
helper_functions.run_shell_command(
|
||||
command, msg=('Error during barcode %s generation' % content))
|
||||
except helper_functions.HelperError, err:
|
||||
print err
|
||||
errors = True
|
||||
return not errors
|
||||
|
||||
|
||||
def convert_png_to_yuv_barcodes(input_directory='.', output_directory='.'):
|
||||
"""Converts PNG barcodes to YUV barcode images.
|
||||
|
||||
This function reads all the PNG files from the input directory which are in
|
||||
the format frame_xxxx.png, where xxxx is the number of the frame, starting
|
||||
from 0000. The frames should be consecutive numbers. The output YUV file is
|
||||
named frame_xxxx.yuv. The function uses ffmpeg to do the conversion.
|
||||
|
||||
Args:
|
||||
input_directory(string): The input direcotry to read the PNG barcodes from.
|
||||
output_directory(string): The putput directory to write the YUV files to.
|
||||
Return:
|
||||
(bool): True if the conversion was without errors.
|
||||
"""
|
||||
return helper_functions.perform_action_on_all_files(
|
||||
input_directory, 'barcode_', 'png', 0, _convert_to_yuv_and_delete,
|
||||
output_directory=output_directory, pattern='barcode_')
|
||||
|
||||
|
||||
def _convert_to_yuv_and_delete(output_directory, file_name, pattern):
|
||||
"""Converts a PNG file to a YUV file and deletes the PNG file.
|
||||
|
||||
Args:
|
||||
output_directory(string): The output directory for the YUV file.
|
||||
file_name(string): The PNG file name.
|
||||
pattern(string): The file pattern of the PNG/YUV file. The PNG/YUV files are
|
||||
named patternxx..x.png/yuv, where xx..x are digits starting from 00..0.
|
||||
Return:
|
||||
(bool): True upon successful conversion, false otherwise.
|
||||
"""
|
||||
# Pattern should be in file name
|
||||
if not pattern in file_name:
|
||||
return False
|
||||
pattern_position = file_name.rfind(pattern)
|
||||
|
||||
# Strip the path to the PNG file and replace the png extension with yuv
|
||||
yuv_file_name = file_name[pattern_position:-3] + 'yuv'
|
||||
yuv_file_name = os.path.join(output_directory, yuv_file_name)
|
||||
|
||||
command = ['ffmpeg', '-i', '%s' % (file_name), '-pix_fmt', 'yuv420p',
|
||||
'%s' % (yuv_file_name)]
|
||||
try:
|
||||
helper_functions.run_shell_command(
|
||||
command, msg=('Error during PNG to YUV conversion of %s' %
|
||||
file_name));
|
||||
helper_functions.delete_file(file_name)
|
||||
except helper_functions.HelperError, err:
|
||||
print err
|
||||
return Flase
|
||||
return True
|
||||
|
||||
|
||||
def combine_yuv_frames_into_one_file(output_file_name, input_directory='.'):
|
||||
"""Combines several YUV frames into one YUV video file.
|
||||
|
||||
The function combines the YUV frames from input_directory into one YUV video
|
||||
file. The frames should be named in the format frame_xxxx.yuv where xxxx
|
||||
stands for the frame number. The numbers have to be consecutive and start from
|
||||
0000. The YUV frames are removed after they have been added to the video.
|
||||
|
||||
Args:
|
||||
output_file_name(string): The name of the file to produce.
|
||||
input_directory(string): The directory from which the YUV frames are read.
|
||||
Return:
|
||||
(bool): True if the frame stitching went OK.
|
||||
"""
|
||||
output_file = open(output_file_name, "wb")
|
||||
success = helper_functions.perform_action_on_all_files(
|
||||
input_directory, 'barcode_', 'yuv', 0, _add_to_file_and_delete,
|
||||
output_file=output_file)
|
||||
output_file.close()
|
||||
return success
|
||||
|
||||
def _add_to_file_and_delete(output_file, file_name):
|
||||
"""Adds the contents of a file to a previously opened file.
|
||||
|
||||
Args:
|
||||
output_file(file): The ouput file, previously opened.
|
||||
file_name(string): The file name of the file to add to the output file.
|
||||
|
||||
Return:
|
||||
(bool): True if successful, False otherwise.
|
||||
"""
|
||||
input_file = open(file_name, "rb")
|
||||
input_file_contents = input_file.read()
|
||||
output_file.write(input_file_contents)
|
||||
input_file.close()
|
||||
return helper_functions.delete_file(file_name)
|
||||
|
||||
|
||||
def _overlay_barcode_and_base_frames(barcodes_file, base_file, output_file,
|
||||
barcodes_component_sizes,
|
||||
base_component_sizes):
|
||||
"""Overlays the next YUV frame from a file with a barcode.
|
||||
|
||||
Args:
|
||||
barcodes_file(FileObject): The YUV file containing the barcodes (opened).
|
||||
base_file(FileObject): The base YUV file (opened).
|
||||
output_file(FileObject): The output overlaid file (opened).
|
||||
barcodes_component_sizes(list of tuples): The width and height of each Y, U
|
||||
and V plane of the barcodes YUV file.
|
||||
base_component_sizes(list of tuples): The width and height of each Y, U and
|
||||
V plane of the base YUV file.
|
||||
Return:
|
||||
(bool): True if there are more planes (i.e. frames) in the base file, false
|
||||
otherwise.
|
||||
"""
|
||||
# We will loop three times - once for the Y, U and V planes
|
||||
for ((barcode_comp_width, barcode_comp_height),
|
||||
(base_comp_width, base_comp_height)) in zip(barcodes_component_sizes,
|
||||
base_component_sizes):
|
||||
for base_row in range(base_comp_height):
|
||||
barcode_plane_traversed = False
|
||||
if (base_row < barcode_comp_height) and not barcode_plane_traversed:
|
||||
barcode_plane = barcodes_file.read(barcode_comp_width)
|
||||
if barcode_plane == "":
|
||||
barcode_plane_traversed = True
|
||||
else:
|
||||
barcode_plane_traversed = True
|
||||
base_plane = base_file.read(base_comp_width)
|
||||
|
||||
if base_plane == "":
|
||||
return False
|
||||
|
||||
if not barcode_plane_traversed:
|
||||
# Substitute part of the base component with the top component
|
||||
output_file.write(barcode_plane)
|
||||
base_plane = base_plane[barcode_comp_width:]
|
||||
output_file.write(base_plane)
|
||||
return True
|
||||
|
||||
|
||||
def overlay_yuv_files(barcode_width, barcode_height, base_width, base_height,
|
||||
barcodes_file_name, base_file_name, output_file_name):
|
||||
"""Overlays two YUV files starting from the upper left corner of both.
|
||||
|
||||
Args:
|
||||
barcode_width(int): The width of the barcode (to be overlaid).
|
||||
barcode_height(int): The height of the barcode (to be overlaid).
|
||||
base_width(int): The width of a frame of the base file.
|
||||
base_height(int): The height of a frame of the base file.
|
||||
barcodes_file_name(string): The name of the YUV file containing the YUV
|
||||
barcodes.
|
||||
base_file_name(string): The name of the base YUV file.
|
||||
output_file_name(string): The name of the output file where the overlaid
|
||||
video will be written.
|
||||
"""
|
||||
# Component sizes = [Y_sizes, U_sizes, V_sizes]
|
||||
barcodes_component_sizes = [(barcode_width, barcode_height),
|
||||
(barcode_width/2, barcode_height/2),
|
||||
(barcode_width/2, barcode_height/2)]
|
||||
base_component_sizes = [(base_width, base_height),
|
||||
(base_width/2, base_height/2),
|
||||
(base_width/2, base_height/2)]
|
||||
|
||||
barcodes_file = open(barcodes_file_name, 'rb')
|
||||
base_file = open(base_file_name, 'rb')
|
||||
output_file = open(output_file_name, 'wb')
|
||||
|
||||
data_left = True
|
||||
while data_left:
|
||||
data_left = _overlay_barcode_and_base_frames(barcodes_file, base_file,
|
||||
output_file,
|
||||
barcodes_component_sizes,
|
||||
base_component_sizes)
|
||||
|
||||
barcodes_file.close()
|
||||
base_file.close()
|
||||
output_file.close()
|
||||
|
||||
|
||||
def calculate_frames_number_from_yuv(yuv_width, yuv_height, file_name):
|
||||
"""Calculates the number of frames of a YUV video.
|
||||
|
||||
Args:
|
||||
yuv_width(int): Width of a frame of the yuv file.
|
||||
yuv_height(int): Height of a frame of the YUV file.
|
||||
file_name(string): The name of the YUV file.
|
||||
Return:
|
||||
(int): The number of frames in the YUV file.
|
||||
"""
|
||||
file_size = os.path.getsize(file_name)
|
||||
|
||||
y_plane_size = yuv_width * yuv_height
|
||||
u_plane_size = (yuv_width/2) * (yuv_height/2) # Equals to V plane size too
|
||||
frame_size = y_plane_size + (2 * u_plane_size)
|
||||
return int(file_size/frame_size) # Should be int anyway
|
||||
|
||||
|
||||
def _parse_args():
|
||||
"""Registers the command-line options."""
|
||||
usage = "usage: %prog [options]"
|
||||
parser = optparse.OptionParser(usage=usage)
|
||||
|
||||
parser.add_option('--barcode_width', type='int',
|
||||
default=_DEFAULT_BARCODE_WIDTH,
|
||||
help=('Width of the barcodes to be overlaid on top of the'
|
||||
' base file. Default: %default'))
|
||||
parser.add_option('--barcode_height', type='int', default=32,
|
||||
help=('Height of the barcodes to be overlaid on top of the'
|
||||
' base file. Default: %default'))
|
||||
parser.add_option('--base_frame_width', type='int', default=352,
|
||||
help=('Width of the base YUV file\'s frames. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--base_frame_height', type='int', default=288,
|
||||
help=('Height of the top YUV file\'s frames. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--barcodes_yuv', type='string',
|
||||
default=_DEFAULT_BARCODES_FILE,
|
||||
help=('The YUV file with the barcodes in YUV. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--base_yuv', type='string', default='base.yuv',
|
||||
help=('The base YUV file to be overlaid. '
|
||||
'Default: %default'))
|
||||
parser.add_option('--output_yuv', type='string', default='output.yuv',
|
||||
help=('The output YUV file containing the base overlaid'
|
||||
' with the barcodes. Default: %default'))
|
||||
parser.add_option('--png_barcodes_output_dir', type='string', default='.',
|
||||
help=('Output directory where the PNG barcodes will be '
|
||||
'generated. Default: %default'))
|
||||
parser.add_option('--png_barcodes_input_dir', type='string', default='.',
|
||||
help=('Input directory from where the PNG barcodes will be '
|
||||
'read. Default: %default'))
|
||||
parser.add_option('--yuv_barcodes_output_dir', type='string', default='.',
|
||||
help=('Output directory where the YUV barcodes will be '
|
||||
'generated. Default: %default'))
|
||||
parser.add_option('--yuv_frames_input_dir', type='string', default='.',
|
||||
help=('Input directory from where the YUV will be '
|
||||
'read before combination. Default: %default'))
|
||||
parser.add_option('--zxing_dir', type='string', default='zxing',
|
||||
help=('Path to the Zxing barcodes library. '
|
||||
'Default: %default'))
|
||||
options = parser.parse_args()[0]
|
||||
return options
|
||||
|
||||
|
||||
def _main():
|
||||
"""The main function.
|
||||
|
||||
A simple invocation will be:
|
||||
./tools/barcode_tools/barcode_encoder.py --barcode_height=32
|
||||
--base_frame_width=352 --base_frame_height=288
|
||||
--base_yuv=<path_and_name_of_base_file>
|
||||
--output_yuv=<path and name_of_output_file>
|
||||
"""
|
||||
options = _parse_args()
|
||||
# The barcodes with will be different than the base frame width only if
|
||||
# explicitly specified at the command line.
|
||||
if options.barcode_width == _DEFAULT_BARCODE_WIDTH:
|
||||
options.barcode_width = options.base_frame_width
|
||||
# If the user provides a value for the barcodes YUV video file, we will keep
|
||||
# it. Otherwise we create a temp file which is removed after it has been used.
|
||||
keep_barcodes_yuv_file = False
|
||||
if options.barcodes_yuv != _DEFAULT_BARCODES_FILE:
|
||||
keep_barcodes_yuv_file = True
|
||||
|
||||
# Calculate the number of barcodes - it is equal to the number of frames in
|
||||
# the base file.
|
||||
number_of_barcodes = calculate_frames_number_from_yuv(
|
||||
options.base_frame_width, options.base_frame_height, options.base_yuv)
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
zxing_dir = os.path.join(script_dir, '..', 'third_party', 'zxing')
|
||||
# Generate barcodes - will generate them in PNG.
|
||||
generate_upca_barcodes(number_of_barcodes, options.barcode_width,
|
||||
options.barcode_height,
|
||||
output_directory=options.png_barcodes_output_dir,
|
||||
path_to_zxing=zxing_dir)
|
||||
# Convert the PNG barcodes to to YUV format.
|
||||
convert_png_to_yuv_barcodes(options.png_barcodes_input_dir,
|
||||
options.yuv_barcodes_output_dir)
|
||||
# Combine the YUV barcodes into one YUV file.
|
||||
combine_yuv_frames_into_one_file(options.barcodes_yuv,
|
||||
input_directory=options.yuv_frames_input_dir)
|
||||
# Overlay the barcodes over the base file.
|
||||
overlay_yuv_files(options.barcode_width, options.barcode_height,
|
||||
options.base_frame_width, options.base_frame_height,
|
||||
options.barcodes_yuv, options.base_yuv, options.output_yuv)
|
||||
|
||||
if not keep_barcodes_yuv_file:
|
||||
# Remove the temporary barcodes YUV file
|
||||
helper_functions.delete_file(options.barcodes_yuv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(_main())
|
47
tools/barcode_tools/barcode_tools.gyp
Normal file
47
tools/barcode_tools/barcode_tools.gyp
Normal file
@ -0,0 +1,47 @@
|
||||
# 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.
|
||||
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'zxing',
|
||||
'message': 'build zxing barcode tool',
|
||||
'type': 'none',
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'build_zxing_core',
|
||||
'inputs': [
|
||||
'<(DEPTH)/third_party/zxing/core/build.xml',
|
||||
],
|
||||
'outputs': [
|
||||
'<(DEPTH)/third_party/zxing/core/core.jar',
|
||||
],
|
||||
'action': [
|
||||
'ant',
|
||||
'-buildfile',
|
||||
'<(DEPTH)/third_party/zxing/core/build.xml',
|
||||
]
|
||||
},
|
||||
{
|
||||
'action_name': 'build_zxing_javase',
|
||||
'inputs': [
|
||||
'<(DEPTH)/third_party/zxing/javase/build.xml',
|
||||
],
|
||||
'outputs': [
|
||||
'<(DEPTH)/third_party/zxing/javase/javase.jar',
|
||||
],
|
||||
'action': [
|
||||
'ant',
|
||||
'-buildfile',
|
||||
'<(DEPTH)/third_party/zxing/javase/build.xml',
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
130
tools/barcode_tools/helper_functions.py
Normal file
130
tools/barcode_tools/helper_functions.py
Normal file
@ -0,0 +1,130 @@
|
||||
#!/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 os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
_DEFAULT_PADDING = 4
|
||||
|
||||
|
||||
class HelperError(Exception):
|
||||
"""Exception raised for errors in the helper."""
|
||||
pass
|
||||
|
||||
|
||||
def zero_pad(number, padding=_DEFAULT_PADDING):
|
||||
"""Converts an int into a zero padded string.
|
||||
|
||||
Args:
|
||||
number(int): The number to convert.
|
||||
padding(int): The number of chars in the output. Note that if you pass for
|
||||
example number=23456 and padding=4, the output will still be '23456',
|
||||
i.e. it will not be cropped. If you pass number=2 and padding=4, the
|
||||
return value will be '0002'.
|
||||
Return:
|
||||
(string): The zero padded number converted to string.
|
||||
"""
|
||||
return str(number).zfill(padding)
|
||||
|
||||
|
||||
def delete_file(file_name):
|
||||
"""Deletes the file with file_name.
|
||||
|
||||
Args:
|
||||
file_name(string): The file to be deleted.
|
||||
Return:
|
||||
(bool): True on success, False otherwise.
|
||||
"""
|
||||
try:
|
||||
subprocess.check_call(['rm', '%s' % file_name])
|
||||
except subprocess.CalledProcessError, err:
|
||||
sys.stderr.write('Error in deleting file %s' % file_name)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def run_shell_command(command, msg=None):
|
||||
"""Executes a command.
|
||||
|
||||
Args:
|
||||
command(list): Command list to execute.
|
||||
msg(string): Message describing the error in case the command fails.
|
||||
|
||||
Return:
|
||||
(string): The standard output from running the command.
|
||||
|
||||
Raise:
|
||||
HelperError: If command fails.
|
||||
"""
|
||||
cmd_list = [str(x) for x in command]
|
||||
cmd = ' '.join(cmd_list)
|
||||
|
||||
process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
if msg:
|
||||
print msg
|
||||
raise HelperError('Failed to run %s: command returned %d and printed '
|
||||
'%s and %s' % (cmd, process.returncode, output, error))
|
||||
return output.strip()
|
||||
|
||||
|
||||
def form_jars_string(path_to_zxing):
|
||||
"""Forms the the Zxing core and javase jars argument.
|
||||
|
||||
Args:
|
||||
path_to_zxing(string): The path to the Zxing checkout folder.
|
||||
Return:
|
||||
(string): The newly formed jars argument.
|
||||
"""
|
||||
javase_jar = os.path.join(path_to_zxing, "javase", "javase.jar")
|
||||
core_jar = os.path.join(path_to_zxing, "core", "core.jar")
|
||||
delimiter = ':'
|
||||
if os.name != 'posix':
|
||||
delimiter = ';'
|
||||
return javase_jar + delimiter + core_jar
|
||||
|
||||
|
||||
def perform_action_on_all_files(directory, file_pattern, file_extension,
|
||||
start_number, action, **kwargs):
|
||||
"""Function that performs a given action on all files matching a pattern.
|
||||
|
||||
It is assumed that the files are named file_patternxxxx.file_extension, where
|
||||
xxxx are digits. The file names start from
|
||||
file_patern0..start_number>.file_extension.
|
||||
|
||||
Args:
|
||||
directory(string): The directory where the files live.
|
||||
file_pattern(string): The name pattern of the files.
|
||||
file_extension(string): The files' extension.
|
||||
start_number(int): From where to start to count frames.
|
||||
action(function): The action to be performed over the files.
|
||||
|
||||
Return:
|
||||
(bool): Whether performing the action over all files was successful or not.
|
||||
"""
|
||||
file_prefix = os.path.join(directory, file_pattern)
|
||||
file_exists = True
|
||||
file_number = start_number
|
||||
errors = False
|
||||
|
||||
while file_exists:
|
||||
zero_padded_file_number = zero_pad(file_number)
|
||||
file_name = file_prefix + zero_padded_file_number + '.' + file_extension
|
||||
if os.path.isfile(file_name):
|
||||
if not action(file_name=file_name, **kwargs):
|
||||
errors = True
|
||||
file_number += 1
|
||||
else:
|
||||
file_exists = False
|
||||
return not errors
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user