OLD | NEW |
| (Empty) |
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 if __name__ == '__main__': | |
15 # Make sure we always can import helper_functions. | |
16 sys.path.append(os.path.dirname(__file__)) | |
17 | |
18 import helper_functions | |
19 | |
20 # Chrome browsertests will throw away stderr; avoid that output gets lost. | |
21 sys.stderr = sys.stdout | |
22 | |
23 | |
24 def ConvertYuvToPngFiles(yuv_file_name, yuv_frame_width, yuv_frame_height, | |
25 output_directory, ffmpeg_path): | |
26 """Converts a YUV video file into PNG frames. | |
27 | |
28 The function uses ffmpeg to convert the YUV file. The output of ffmpeg is in | |
29 the form frame_xxxx.png, where xxxx is the frame number, starting from 0001. | |
30 | |
31 Args: | |
32 yuv_file_name(string): The name of the YUV file. | |
33 yuv_frame_width(int): The width of one YUV frame. | |
34 yuv_frame_height(int): The height of one YUV frame. | |
35 output_directory(string): The output directory where the PNG frames will be | |
36 stored. | |
37 ffmpeg_path(string): The path to the ffmpeg executable. If None, the PATH | |
38 will be searched for it. | |
39 | |
40 Return: | |
41 (bool): True if the conversion was OK. | |
42 """ | |
43 size_string = str(yuv_frame_width) + 'x' + str(yuv_frame_height) | |
44 output_files_pattern = os.path.join(output_directory, 'frame_%04d.png') | |
45 if not ffmpeg_path: | |
46 ffmpeg_path = 'ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg' | |
47 command = [ffmpeg_path, '-s', '%s' % size_string, '-i', '%s' | |
48 % yuv_file_name, '-f', 'image2', '-vcodec', 'png', | |
49 '%s' % output_files_pattern] | |
50 try: | |
51 print 'Converting YUV file to PNG images (may take a while)...' | |
52 print ' '.join(command) | |
53 helper_functions.RunShellCommand( | |
54 command, fail_msg='Error during YUV to PNG conversion') | |
55 except helper_functions.HelperError, err: | |
56 print 'Error executing command: %s. Error: %s' % (command, err) | |
57 return False | |
58 except OSError: | |
59 print 'Did not find %s. Have you installed it?' % ffmpeg_path | |
60 return False | |
61 return True | |
62 | |
63 | |
64 def DecodeFrames(input_directory, zxing_path): | |
65 """Decodes the barcodes overlaid in each frame. | |
66 | |
67 The function uses the Zxing command-line tool from the Zxing C++ distribution | |
68 to decode the barcode in every PNG frame from the input directory. The frames | |
69 should be named frame_xxxx.png, where xxxx is the frame number. The frame | |
70 numbers should be consecutive and should start from 0001. | |
71 The decoding results in a frame_xxxx.txt file for every successfully decoded | |
72 barcode. This file contains the decoded barcode as 12-digit string (UPC-A | |
73 format: 11 digits content + one check digit). | |
74 | |
75 Args: | |
76 input_directory(string): The input directory from where the PNG frames are | |
77 read. | |
78 zxing_path(string): The path to the zxing binary. If specified as None, | |
79 the PATH will be searched for it. | |
80 Return: | |
81 (bool): True if the decoding succeeded. | |
82 """ | |
83 if not zxing_path: | |
84 zxing_path = 'zxing.exe' if sys.platform == 'win32' else 'zxing' | |
85 print 'Decoding barcodes from PNG files with %s...' % zxing_path | |
86 return helper_functions.PerformActionOnAllFiles( | |
87 directory=input_directory, file_pattern='frame_', | |
88 file_extension='png', start_number=1, action=_DecodeBarcodeInFile, | |
89 command_line_decoder=zxing_path) | |
90 | |
91 | |
92 def _DecodeBarcodeInFile(file_name, command_line_decoder): | |
93 """Decodes the barcode in the upper left corner of a PNG file. | |
94 | |
95 Args: | |
96 file_name(string): File name of the PNG file. | |
97 command_line_decoder(string): The ZXing command-line decoding tool. | |
98 | |
99 Return: | |
100 (bool): True upon success, False otherwise. | |
101 """ | |
102 command = [command_line_decoder, '--try-harder', '--dump-raw', file_name] | |
103 try: | |
104 out = helper_functions.RunShellCommand( | |
105 command, fail_msg='Error during decoding of %s' % file_name) | |
106 text_file = open('%s.txt' % file_name[:-4], 'w') | |
107 text_file.write(out) | |
108 text_file.close() | |
109 except helper_functions.HelperError, err: | |
110 print 'Barcode in %s cannot be decoded.' % file_name | |
111 print err | |
112 return False | |
113 except OSError: | |
114 print 'Did not find %s. Have you installed it?' % command_line_decoder | |
115 return False | |
116 return True | |
117 | |
118 | |
119 def _GenerateStatsFile(stats_file_name, input_directory='.'): | |
120 """Generate statistics file. | |
121 | |
122 The function generates a statistics file. The contents of the file are in the | |
123 format <frame_name> <barcode>, where frame name is the name of every frame | |
124 (effectively the frame number) and barcode is the decoded barcode. The frames | |
125 and the helper .txt files are removed after they have been used. | |
126 """ | |
127 file_prefix = os.path.join(input_directory, 'frame_') | |
128 stats_file = open(stats_file_name, 'w') | |
129 | |
130 print 'Generating stats file: %s' % stats_file_name | |
131 for i in range(1, _CountFramesIn(input_directory=input_directory) + 1): | |
132 frame_number = helper_functions.ZeroPad(i) | |
133 barcode_file_name = file_prefix + frame_number + '.txt' | |
134 png_frame = file_prefix + frame_number + '.png' | |
135 entry_frame_number = helper_functions.ZeroPad(i-1) | |
136 entry = 'frame_' + entry_frame_number + ' ' | |
137 | |
138 if os.path.isfile(barcode_file_name): | |
139 barcode = _ReadBarcodeFromTextFile(barcode_file_name) | |
140 os.remove(barcode_file_name) | |
141 | |
142 if _CheckBarcode(barcode): | |
143 entry += (helper_functions.ZeroPad(int(barcode[0:11])) + '\n') | |
144 else: | |
145 entry += 'Barcode error\n' # Barcode is wrongly detected. | |
146 else: # Barcode file doesn't exist. | |
147 entry += 'Barcode error\n' | |
148 | |
149 stats_file.write(entry) | |
150 os.remove(png_frame) | |
151 | |
152 stats_file.close() | |
153 | |
154 | |
155 def _ReadBarcodeFromTextFile(barcode_file_name): | |
156 """Reads the decoded barcode for a .txt file. | |
157 | |
158 Args: | |
159 barcode_file_name(string): The name of the .txt file. | |
160 Return: | |
161 (string): The decoded barcode. | |
162 """ | |
163 barcode_file = open(barcode_file_name, 'r') | |
164 barcode = barcode_file.read() | |
165 barcode_file.close() | |
166 return barcode | |
167 | |
168 | |
169 def _CheckBarcode(barcode): | |
170 """Check weather the UPC-A barcode was decoded correctly. | |
171 | |
172 This function calculates the check digit of the provided barcode and compares | |
173 it to the check digit that was decoded. | |
174 | |
175 Args: | |
176 barcode(string): The barcode (12-digit). | |
177 Return: | |
178 (bool): True if the barcode was decoded correctly. | |
179 """ | |
180 if len(barcode) != 12: | |
181 return False | |
182 | |
183 r1 = range(0, 11, 2) # Odd digits | |
184 r2 = range(1, 10, 2) # Even digits except last | |
185 dsum = 0 | |
186 # Sum all the even digits | |
187 for i in r1: | |
188 dsum += int(barcode[i]) | |
189 # Multiply the sum by 3 | |
190 dsum *= 3 | |
191 # Add all the even digits except the check digit (12th digit) | |
192 for i in r2: | |
193 dsum += int(barcode[i]) | |
194 # Get the modulo 10 | |
195 dsum = dsum % 10 | |
196 # If not 0 substract from 10 | |
197 if dsum != 0: | |
198 dsum = 10 - dsum | |
199 # Compare result and check digit | |
200 return dsum == int(barcode[11]) | |
201 | |
202 | |
203 def _CountFramesIn(input_directory='.'): | |
204 """Calculates the number of frames in the input directory. | |
205 | |
206 The function calculates the number of frames in the input directory. The | |
207 frames should be named frame_xxxx.png, where xxxx is the number of the frame. | |
208 The numbers should start from 1 and should be consecutive. | |
209 | |
210 Args: | |
211 input_directory(string): The input directory. | |
212 Return: | |
213 (int): The number of frames. | |
214 """ | |
215 file_prefix = os.path.join(input_directory, 'frame_') | |
216 file_exists = True | |
217 num = 1 | |
218 | |
219 while file_exists: | |
220 file_name = (file_prefix + helper_functions.ZeroPad(num) + '.png') | |
221 if os.path.isfile(file_name): | |
222 num += 1 | |
223 else: | |
224 file_exists = False | |
225 return num - 1 | |
226 | |
227 | |
228 def _ParseArgs(): | |
229 """Registers the command-line options.""" | |
230 usage = "usage: %prog [options]" | |
231 parser = optparse.OptionParser(usage=usage) | |
232 | |
233 parser.add_option('--zxing_path', type='string', | |
234 help=('The path to where the zxing executable is located. ' | |
235 'If omitted, it will be assumed to be present in the ' | |
236 'PATH with the name zxing[.exe].')) | |
237 parser.add_option('--ffmpeg_path', type='string', | |
238 help=('The path to where the ffmpeg executable is located. ' | |
239 'If omitted, it will be assumed to be present in the ' | |
240 'PATH with the name ffmpeg[.exe].')) | |
241 parser.add_option('--yuv_frame_width', type='int', default=640, | |
242 help='Width of the YUV file\'s frames. Default: %default') | |
243 parser.add_option('--yuv_frame_height', type='int', default=480, | |
244 help='Height of the YUV file\'s frames. Default: %default') | |
245 parser.add_option('--yuv_file', type='string', default='output.yuv', | |
246 help='The YUV file to be decoded. Default: %default') | |
247 parser.add_option('--stats_file', type='string', default='stats.txt', | |
248 help='The output stats file. Default: %default') | |
249 parser.add_option('--png_working_dir', type='string', default='.', | |
250 help=('The directory for temporary PNG images to be stored ' | |
251 'in when decoding from YUV before they\'re barcode ' | |
252 'decoded. If using Windows and a Cygwin-compiled ' | |
253 'zxing.exe, you should keep the default value to ' | |
254 'avoid problems. Default: %default')) | |
255 options, _ = parser.parse_args() | |
256 return options | |
257 | |
258 | |
259 def main(): | |
260 """The main function. | |
261 | |
262 A simple invocation is: | |
263 ./webrtc/tools/barcode_tools/barcode_decoder.py | |
264 --yuv_file=<path_and_name_of_overlaid_yuv_video> | |
265 --yuv_frame_width=640 --yuv_frame_height=480 | |
266 --stats_file=<path_and_name_to_stats_file> | |
267 """ | |
268 options = _ParseArgs() | |
269 | |
270 # Convert the overlaid YUV video into a set of PNG frames. | |
271 if not ConvertYuvToPngFiles(options.yuv_file, options.yuv_frame_width, | |
272 options.yuv_frame_height, | |
273 output_directory=options.png_working_dir, | |
274 ffmpeg_path=options.ffmpeg_path): | |
275 print 'An error occurred converting from YUV to PNG frames.' | |
276 return -1 | |
277 | |
278 # Decode the barcodes from the PNG frames. | |
279 if not DecodeFrames(input_directory=options.png_working_dir, | |
280 zxing_path=options.zxing_path): | |
281 print 'An error occurred decoding barcodes from PNG frames.' | |
282 return -2 | |
283 | |
284 # Generate statistics file. | |
285 _GenerateStatsFile(options.stats_file, | |
286 input_directory=options.png_working_dir) | |
287 print 'Completed barcode decoding.' | |
288 return 0 | |
289 | |
290 if __name__ == '__main__': | |
291 sys.exit(main()) | |
OLD | NEW |