blob: 0c65f91f58421ca2f47bee1831aef785320916a9 [file] [log] [blame]
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +00001#!/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 subprocess
13import sys
14
15import helper_functions
16
17_DEFAULT_BARCODE_WIDTH = 352
18
19
20def convert_yuv_to_png_files(yuv_file_name, yuv_frame_width, yuv_frame_height,
21 output_directory = '.'):
22 """Converts a YUV video file into PNG frames.
23
24 The function uses ffmpeg to convert the YUV file. The output of ffmpeg is in
25 the form frame_xxxx.png, where xxxx is the frame number, starting from 0001.
26
27 Args:
28 yuv_file_name(string): The name of the YUV file.
29 yuv_frame_width(int): The width of one YUV frame.
30 yuv_frame_height(int): The height of one YUV frame.
31 output_directory(string): The output directory where the PNG frames will be
32 stored.
33
34 Return:
35 (bool): True if the conversion was OK.
36 """
37 size_string = str(yuv_frame_width) + 'x' + str(yuv_frame_height)
38 output_files_pattern = os.path.join(output_directory, 'frame_%04d.png')
39 command = ['ffmpeg', '-s', '%s' % size_string, '-i', '%s'
40 % yuv_file_name, '-f', 'image2', '-vcodec', 'png',
41 '%s' % output_files_pattern]
42 try:
43 helper_functions.run_shell_command(
44 command, msg='Error during YUV to PNG conversion')
45 except helper_functions.HelperError, err:
46 print err
47 return False
48 return True
49
50
51def decode_frames(barcode_width, barcode_height, input_directory='.',
52 path_to_zxing='zxing-read-only'):
53 """Decodes the barcodes overlaid in each frame.
54
55 The function uses the example Java command-line tool from the Zxing
56 distribution to decode the barcode in every PNG frame from the input
57 directory. The frames should be named frame_xxxx.png, where xxxx is the frame
58 number. The frame numbers should be consecutive and should start from 0001.
59 The decoding results in a frame_xxxx.txt file for every successfully decoded
60 barcode. This file contains the decoded barcode as 12-digit string (UPC-A
61 format: 11 digits content + one check digit).
62
63 Args:
64 barcode_width(int): Width of the barcode.
65 barcode_height(int): Height of the barcode.
66 input_directory(string): The input directory from where the PNG frames are
67 read.
68 path_to_zxing(string): The path to Zxing.
69 Return:
70 (bool): True if the decoding went without errors.
71 """
72 jars = helper_functions.form_jars_string(path_to_zxing)
73 command_line_decoder ='com.google.zxing.client.j2se.CommandLineRunner'
74 return helper_functions.perform_action_on_all_files(
75 directory=input_directory, file_pattern='frame_',
76 file_extension='png', start_number=1, action=_decode_barcode_in_file,
77 barcode_width=barcode_width, barcode_height=barcode_height, jars=jars,
78 command_line_decoder=command_line_decoder)
79
80
81def _decode_barcode_in_file(file_name, barcode_width, barcode_height, jars,
82 command_line_decoder):
83 """Decodes the barcode in the upper left corner of a PNG file.
84
85 Args:
86 file_name(string): File name of the PNG file.
87 barcode_width(int): Width of the barcode (in pixels).
88 barcode_height(int): Height of the barcode (in pixels)
89 jars(string): The Zxing core and javase string.
90 command_line_decoder(string): The ZXing command-line decoding tool.
91
92 Return:
93 (bool): True upon success, False otherwise.
94 """
95 command = ['java', '-cp', '%s' % jars,
96 '%s' % command_line_decoder, '--products_only',
97 '--dump_results', '--brief', '--crop=%d,%d,%d,%d' %
98 (0, 0, barcode_width, barcode_height),
99 '%s' % file_name]
100 try:
101 out = helper_functions.run_shell_command(
102 command, msg='Error during decoding of %s' % file_name)
103 if not 'Success' in out:
104 sys.stderr.write('Barcode in %s cannot be decoded\n' % file_name)
105 return False
106 except helper_functions.HelperError, err:
107 print err
108 return False
109 return True
110
111
112def _generate_stats_file(stats_file_name, input_directory='.'):
113 """Generate statistics file.
114
115 The function generates a statistics file. The contents of the file are in the
116 format <frame_name> <barcode>, where frame name is the name of every frame
117 (effectively the frame number) and barcode is the decoded barcode. The frames
118 and the helper .txt files are removed after they have been used.
119 """
120 file_prefix = os.path.join(input_directory, 'frame_')
121 stats_file = open(stats_file_name, 'w')
122
123 for i in range(1, _count_frames_in(input_directory=input_directory) + 1):
124 frame_number = helper_functions.zero_pad(i)
125 barcode_file_name = file_prefix + frame_number + '.txt'
126 png_frame = file_prefix + frame_number + '.png'
127 entry_frame_number = helper_functions.zero_pad(i-1)
128 entry = 'frame_' + entry_frame_number + ' '
129
130 if os.path.isfile(barcode_file_name):
131 barcode = _read_barcode_from_text_file(barcode_file_name)
132 helper_functions.delete_file(barcode_file_name)
133
134 if _check_barcode(barcode):
135 entry += (helper_functions.zero_pad(int(barcode[0:11])) + '\n')
136 else:
137 entry += 'Barcode error\n' # Barcode is wrongly detected.
138 else: # Barcode file doesn't exist.
139 entry += 'Barcode error\n'
140
141 stats_file.write(entry)
142 helper_functions.delete_file(png_frame)
143
144 stats_file.close()
145
146
147def _read_barcode_from_text_file(barcode_file_name):
148 """Reads the decoded barcode for a .txt file.
149
150 Args:
151 barcode_file_name(string): The name of the .txt file.
152 Return:
153 (string): The decoded barcode.
154 """
155 barcode_file = open(barcode_file_name, 'r')
156 barcode = barcode_file.read()
157 barcode_file.close()
158
159 return barcode
160
161
162def _check_barcode(barcode):
163 """Check weather the UPC-A barcode was decoded correctly.
164
165 This function calculates the check digit of the provided barcode and compares
166 it to the check digit that was decoded.
167
168 Args:
169 barcode(string): The barcode (12-digit).
170 Return:
171 (bool): True if the barcode was decoded correctly.
172 """
173 if len(barcode) != 12:
174 return False
175
176 r1 = range(0, 11, 2) # Odd digits
177 r2 = range(1, 10, 2) # Even digits except last
178 dsum = 0
179 # Sum all the even digits
180 for i in r1:
181 dsum += int(barcode[i])
182 # Multiply the sum by 3
183 dsum *= 3
184 # Add all the even digits except the check digit (12th digit)
185 for i in r2:
186 dsum += int(barcode[i])
187 # Get the modulo 10
188 dsum = dsum % 10
189 # If not 0 substract from 10
190 if dsum != 0:
191 dsum = 10 - dsum
192 # Compare result and check digit
193 return dsum == int(barcode[11])
194
195
196def _count_frames_in(input_directory = '.'):
197 """Calculates the number of frames in the input directory.
198
199 The function calculates the number of frames in the input directory. The
200 frames should be named frame_xxxx.png, where xxxx is the number of the frame.
201 The numbers should start from 1 and should be consecutive.
202
203 Args:
204 input_directory(string): The input directory.
205 Return:
206 (int): The number of frames.
207 """
208 file_prefix = os.path.join(input_directory, 'frame_')
209 file_exists = True
210 num = 1
211
212 while file_exists:
213 file_name = (file_prefix + helper_functions.zero_pad(num) + '.png')
214 if os.path.isfile(file_name):
215 num += 1
216 else:
217 file_exists = False
218 return num - 1
219
220
221def _parse_args():
222 """Registers the command-line options."""
223 usage = "usage: %prog [options]"
224 parser = optparse.OptionParser(usage=usage)
225
226 parser.add_option('--yuv_frame_width', type='int', default=352,
227 help=('Width of the YUV file\'s frames. '
228 'Default: %default'))
229 parser.add_option('--yuv_frame_height', type='int', default=288,
230 help=('Height of the YUV file\'s frames. '
231 'Default: %default'))
232 parser.add_option('--barcode_width', type='int',
233 default=_DEFAULT_BARCODE_WIDTH,
234 help=('Width of the barcodes. Default: %default'))
235 parser.add_option('--barcode_height', type='int', default=32,
236 help=('Height of the barcodes. Default: %default'))
237 parser.add_option('--yuv_file', type='string', default='output.yuv',
238 help=('The YUV file to be decoded. Default: %default'))
239 parser.add_option('--stats_file', type='string', default='stats.txt',
240 help=('The output stats file. Default: %default'))
241 parser.add_option('--png_output_dir', type='string', default='.',
242 help=('The output directory for the generated PNG files. '
243 'Default: %default'))
244 parser.add_option('--png_input_dir', type='string', default='.',
245 help=('The input directory for the generated PNG files. '
246 'Default: %default'))
247 parser.add_option('--path_to_zxing', type='string', default='zxing',
248 help=('The path to Zxing. Default: %default'))
249 options = parser.parse_args()[0]
250 return options
251
252
253def _main():
254 """The main function.
255
256 A simple invocation is:
257 ./tools/barcode_tolls/barcode_decoder.py
258 --yuv_file=<path_and_name_of_overlaid_yuv_video>
259 --yuv_frame_width=352 --yuv_frame_height=288 --barcode_height=32
260 --stats_file=<path_and_name_to_stats_file>
261 """
262 options = _parse_args()
263
264 # The barcodes with will be different than the base frame width only if
265 # explicitly specified at the command line.
266 if options.barcode_width == _DEFAULT_BARCODE_WIDTH:
267 options.barcode_width = options.yuv_frame_width
268
269 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
270 zxing_dir = os.path.join(script_dir, '..', 'third_party', 'zxing')
271
272 # Convert the overlaid YUV video into a set of PNG frames.
273 convert_yuv_to_png_files(options.yuv_file, options.yuv_frame_width,
274 options.yuv_frame_height,
275 output_directory=options.png_output_dir)
276 # Decode the barcodes from the PNG frames.
277 decode_frames(options.barcode_width, options.barcode_height,
278 input_directory=options.png_input_dir, path_to_zxing=zxing_dir)
279 # Generate statistics file.
280 _generate_stats_file(options.stats_file,
281 input_directory=options.png_input_dir)
282
283
284if __name__ == '__main__':
285 sys.exit(_main())