blob: 87b3ec2993eb07903292613e3d92e29999a88736 [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
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +000012import sys
13
14import helper_functions
15
16_DEFAULT_BARCODE_WIDTH = 352
17
18
19def convert_yuv_to_png_files(yuv_file_name, yuv_frame_width, yuv_frame_height,
20 output_directory = '.'):
21 """Converts a YUV video file into PNG frames.
22
23 The function uses ffmpeg to convert the YUV file. The output of ffmpeg is in
24 the form frame_xxxx.png, where xxxx is the frame number, starting from 0001.
25
26 Args:
27 yuv_file_name(string): The name of the YUV file.
28 yuv_frame_width(int): The width of one YUV frame.
29 yuv_frame_height(int): The height of one YUV frame.
30 output_directory(string): The output directory where the PNG frames will be
31 stored.
32
33 Return:
34 (bool): True if the conversion was OK.
35 """
36 size_string = str(yuv_frame_width) + 'x' + str(yuv_frame_height)
37 output_files_pattern = os.path.join(output_directory, 'frame_%04d.png')
38 command = ['ffmpeg', '-s', '%s' % size_string, '-i', '%s'
39 % yuv_file_name, '-f', 'image2', '-vcodec', 'png',
40 '%s' % output_files_pattern]
41 try:
42 helper_functions.run_shell_command(
43 command, msg='Error during YUV to PNG conversion')
44 except helper_functions.HelperError, err:
kjellander@webrtc.orgccb52c22012-10-10 16:11:28 +000045 print >> sys.stderr, 'Error executing command: %s. Error: %s' % (command,
46 err)
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +000047 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 """
kjellander@webrtc.orgead30052012-09-06 09:33:08 +000095 command = ['java', '-Djava.awt.headless=true', '-cp', '%s' % jars,
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +000096 '%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:
kjellander@webrtc.orgccb52c22012-10-10 16:11:28 +0000104 print >> sys.stderr, 'Barcode in %s cannot be decoded\n' % file_name
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000105 return False
106 except helper_functions.HelperError, err:
kjellander@webrtc.orgccb52c22012-10-10 16:11:28 +0000107 print >> sys.stderr, err
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000108 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)
vspasova@webrtc.org1b0a02e2012-08-30 12:56:38 +0000132 os.remove(barcode_file_name)
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000133
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)
vspasova@webrtc.org1b0a02e2012-08-30 12:56:38 +0000142 os.remove(png_frame)
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000143
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()
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000158 return barcode
159
160
161def _check_barcode(barcode):
162 """Check weather the UPC-A barcode was decoded correctly.
163
164 This function calculates the check digit of the provided barcode and compares
165 it to the check digit that was decoded.
166
167 Args:
168 barcode(string): The barcode (12-digit).
169 Return:
170 (bool): True if the barcode was decoded correctly.
171 """
172 if len(barcode) != 12:
173 return False
174
175 r1 = range(0, 11, 2) # Odd digits
176 r2 = range(1, 10, 2) # Even digits except last
177 dsum = 0
178 # Sum all the even digits
179 for i in r1:
180 dsum += int(barcode[i])
181 # Multiply the sum by 3
182 dsum *= 3
183 # Add all the even digits except the check digit (12th digit)
184 for i in r2:
185 dsum += int(barcode[i])
186 # Get the modulo 10
187 dsum = dsum % 10
188 # If not 0 substract from 10
189 if dsum != 0:
190 dsum = 10 - dsum
191 # Compare result and check digit
192 return dsum == int(barcode[11])
193
194
195def _count_frames_in(input_directory = '.'):
196 """Calculates the number of frames in the input directory.
197
198 The function calculates the number of frames in the input directory. The
199 frames should be named frame_xxxx.png, where xxxx is the number of the frame.
200 The numbers should start from 1 and should be consecutive.
201
202 Args:
203 input_directory(string): The input directory.
204 Return:
205 (int): The number of frames.
206 """
207 file_prefix = os.path.join(input_directory, 'frame_')
208 file_exists = True
209 num = 1
210
211 while file_exists:
212 file_name = (file_prefix + helper_functions.zero_pad(num) + '.png')
213 if os.path.isfile(file_name):
214 num += 1
215 else:
216 file_exists = False
217 return num - 1
218
219
220def _parse_args():
221 """Registers the command-line options."""
222 usage = "usage: %prog [options]"
223 parser = optparse.OptionParser(usage=usage)
224
225 parser.add_option('--yuv_frame_width', type='int', default=352,
226 help=('Width of the YUV file\'s frames. '
227 'Default: %default'))
228 parser.add_option('--yuv_frame_height', type='int', default=288,
229 help=('Height of the YUV file\'s frames. '
230 'Default: %default'))
231 parser.add_option('--barcode_width', type='int',
232 default=_DEFAULT_BARCODE_WIDTH,
233 help=('Width of the barcodes. Default: %default'))
234 parser.add_option('--barcode_height', type='int', default=32,
235 help=('Height of the barcodes. Default: %default'))
236 parser.add_option('--yuv_file', type='string', default='output.yuv',
237 help=('The YUV file to be decoded. Default: %default'))
238 parser.add_option('--stats_file', type='string', default='stats.txt',
239 help=('The output stats file. Default: %default'))
240 parser.add_option('--png_output_dir', type='string', default='.',
241 help=('The output directory for the generated PNG files. '
242 'Default: %default'))
243 parser.add_option('--png_input_dir', type='string', default='.',
244 help=('The input directory for the generated PNG files. '
245 'Default: %default'))
246 parser.add_option('--path_to_zxing', type='string', default='zxing',
247 help=('The path to Zxing. Default: %default'))
248 options = parser.parse_args()[0]
249 return options
250
251
252def _main():
253 """The main function.
254
255 A simple invocation is:
256 ./tools/barcode_tolls/barcode_decoder.py
257 --yuv_file=<path_and_name_of_overlaid_yuv_video>
258 --yuv_frame_width=352 --yuv_frame_height=288 --barcode_height=32
259 --stats_file=<path_and_name_to_stats_file>
260 """
261 options = _parse_args()
262
263 # The barcodes with will be different than the base frame width only if
264 # explicitly specified at the command line.
265 if options.barcode_width == _DEFAULT_BARCODE_WIDTH:
266 options.barcode_width = options.yuv_frame_width
267
268 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
vspasova@webrtc.org28655422012-08-15 14:35:40 +0000269 zxing_dir = os.path.join(script_dir, 'third_party', 'zxing')
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000270
271 # Convert the overlaid YUV video into a set of PNG frames.
kjellander@webrtc.orgccb52c22012-10-10 16:11:28 +0000272 if not convert_yuv_to_png_files(options.yuv_file, options.yuv_frame_width,
273 options.yuv_frame_height,
274 output_directory=options.png_output_dir):
275 print >> sys.stderr, 'An error occurred converting from YUV to PNG frames.'
276 return -1
277
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000278 # Decode the barcodes from the PNG frames.
kjellander@webrtc.orgccb52c22012-10-10 16:11:28 +0000279 if not decode_frames(options.barcode_width, options.barcode_height,
280 input_directory=options.png_input_dir,
281 path_to_zxing=zxing_dir):
282 print >> sys.stderr, ('An error occurred decoding barcodes from PNG frames.'
283 'Have you built the zxing library JAR files?')
284 return -2
285
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000286 # Generate statistics file.
287 _generate_stats_file(options.stats_file,
288 input_directory=options.png_input_dir)
kjellander@webrtc.orgccb52c22012-10-10 16:11:28 +0000289 return 0
vspasova@webrtc.org400e7da2012-08-15 10:25:12 +0000290
291if __name__ == '__main__':
292 sys.exit(_main())