Rev 7 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
5 | daniel-mar | 1 | /** |
2 | * IPMA video frame extractor by Daniel Marschall, ViaThinkSoft (C) 2022 |
||
3 | * Supports codecs IPMA and IP20 |
||
7 | daniel-mar | 4 | * Revision: 2022-01-16 |
5 | daniel-mar | 5 | * License: Apache 2.0 |
6 | **/ |
||
7 | |||
7 | daniel-mar | 8 | #define VERSION "2022-01-16" |
5 | daniel-mar | 9 | |
10 | #define _CRT_SECURE_NO_WARNINGS |
||
11 | // #define VISUAL_STUDIO_TEST |
||
12 | |||
13 | #include <windows.h> |
||
14 | #include <vfw.h> |
||
15 | #include <stdio.h> |
||
16 | #include <stdbool.h> |
||
17 | |||
18 | #ifndef VISUAL_STUDIO_TEST |
||
19 | #include <getopt.h> |
||
20 | #endif |
||
21 | |||
22 | #define LZ_MIN_BITS 9 |
||
23 | #define LZ_MAX_BITS 12 |
||
24 | |||
25 | #define LZ_MAX_CODE 4095 /* Largest 12 bit code */ |
||
26 | #define NO_SUCH_CODE 4098 /* Impossible code = empty */ |
||
27 | |||
28 | #define CLEAR_CODE 256 |
||
29 | #define END_CODE 257 |
||
30 | #define FIRST_CODE 258 |
||
31 | |||
32 | bool dirExists(const char* dirName_in) { |
||
33 | DWORD ftyp = GetFileAttributesA(dirName_in); |
||
34 | if (ftyp == INVALID_FILE_ATTRIBUTES) |
||
35 | return false; //something is wrong with your path! |
||
36 | |||
37 | if (ftyp & FILE_ATTRIBUTE_DIRECTORY) |
||
38 | return true; // this is a directory! |
||
39 | |||
40 | return false; // this is not a directory! |
||
41 | } |
||
42 | |||
43 | typedef struct tagIpe16LZWDecoder { |
||
44 | int running_code; |
||
45 | int running_bits; |
||
46 | int max_code_plus_one; |
||
47 | int shift_state; |
||
48 | unsigned long shift_data; |
||
49 | unsigned char stack[LZ_MAX_CODE + 1]; |
||
50 | unsigned int suffix[LZ_MAX_CODE + 1]; |
||
51 | unsigned int prefix[LZ_MAX_CODE + 1]; |
||
52 | } Ipe16LZWDecoder; |
||
53 | |||
54 | void ipe16lzw_init_decoder(Ipe16LZWDecoder* decoder) { |
||
55 | decoder->running_code = FIRST_CODE; |
||
56 | decoder->running_bits = LZ_MIN_BITS; |
||
57 | decoder->max_code_plus_one = 1 << decoder->running_bits; |
||
58 | decoder->shift_state = 0; |
||
59 | decoder->shift_data = 0; |
||
60 | |||
61 | int i; |
||
62 | for (i = 0; i <= LZ_MAX_CODE; i++) { |
||
63 | decoder->prefix[i] = NO_SUCH_CODE; |
||
64 | } |
||
65 | } |
||
66 | |||
67 | int ipe16lzw_read_code(unsigned char** inFile, Ipe16LZWDecoder* decoder) { |
||
68 | int code; |
||
69 | unsigned char next_byte; |
||
70 | static int code_masks[] = { |
||
71 | 0x0000, 0x0001, 0x0003, 0x0007, |
||
72 | 0x000f, 0x001f, 0x003f, 0x007f, |
||
73 | 0x00ff, 0x01ff, 0x03ff, 0x07ff, |
||
74 | 0x0fff |
||
75 | }; |
||
76 | |||
77 | while (decoder->shift_state < decoder->running_bits) { |
||
78 | next_byte = **inFile; |
||
79 | *inFile = *inFile + 1; |
||
80 | decoder->shift_data |= |
||
81 | ((unsigned long)next_byte) << decoder->shift_state; |
||
82 | decoder->shift_state += 8; |
||
83 | } |
||
84 | |||
85 | code = decoder->shift_data & code_masks[decoder->running_bits]; |
||
86 | |||
87 | decoder->shift_data >>= decoder->running_bits; |
||
88 | decoder->shift_state -= decoder->running_bits; |
||
89 | |||
90 | if (++decoder->running_code > decoder->max_code_plus_one |
||
91 | && decoder->running_bits < LZ_MAX_BITS) { |
||
92 | decoder->max_code_plus_one <<= 1; |
||
93 | decoder->running_bits++; |
||
94 | } |
||
95 | |||
96 | return code; |
||
97 | } |
||
98 | |||
99 | static int ipe16lzw_trace_prefix(unsigned int* prefix, int code, int clear_code) { |
||
100 | int i = 0; |
||
101 | |||
102 | while (code > clear_code && i++ <= LZ_MAX_CODE) { |
||
103 | code = prefix[code]; |
||
104 | } |
||
105 | return code; |
||
106 | } |
||
107 | |||
108 | // Difference between ipma_lzw_decode and ipe16lzw_decode: At ipma_lzw_decode, inFile is "unsigned char**" and not "FILE*" |
||
109 | // We don't do unsigned, because we want to have <0 as error result |
||
110 | /*unsigned*/ int ipma_lzw_decode(unsigned char** inFile, Ipe16LZWDecoder* decoder, unsigned char* output, int outputLength) { |
||
111 | int i = 0, j; |
||
112 | int current_code; |
||
113 | int current_prefix; |
||
114 | int stack_ptr = 0; |
||
115 | int prev_code = NO_SUCH_CODE; |
||
116 | unsigned char* stack; |
||
117 | unsigned int* prefix; |
||
118 | unsigned int* suffix; |
||
119 | unsigned int bytes_written = 0; |
||
120 | |||
121 | ipe16lzw_init_decoder(decoder); |
||
122 | |||
123 | prefix = decoder->prefix; |
||
124 | suffix = decoder->suffix; |
||
125 | stack = decoder->stack; |
||
126 | |||
127 | /* Pop the stack */ |
||
128 | while (stack_ptr != 0 && i < outputLength) { |
||
129 | output[i++] = stack[--stack_ptr]; |
||
130 | //if (i > bytes_written) bytes_written = i; |
||
131 | ++bytes_written; |
||
132 | } |
||
133 | |||
134 | while (i < outputLength) { |
||
135 | current_code = ipe16lzw_read_code(inFile, decoder); |
||
136 | |||
137 | if (current_code == END_CODE) { |
||
138 | if (i != outputLength - 1) // || decoder->pixel_count != 0 |
||
139 | return -1; /* unexpected eof */ |
||
140 | i++; |
||
141 | } |
||
142 | else if (current_code == CLEAR_CODE) { |
||
143 | for (j = 0; j <= LZ_MAX_CODE; j++) { |
||
144 | prefix[j] = NO_SUCH_CODE; |
||
145 | } |
||
146 | decoder->running_code = FIRST_CODE; |
||
147 | decoder->running_bits = LZ_MIN_BITS; |
||
148 | decoder->max_code_plus_one = 1 << decoder->running_bits; |
||
149 | prev_code = NO_SUCH_CODE; |
||
150 | } |
||
151 | else { |
||
152 | if (current_code < CLEAR_CODE) { |
||
153 | output[i++] = current_code; |
||
154 | //if (i > bytes_written) bytes_written = i; |
||
155 | ++bytes_written; |
||
156 | } |
||
157 | else { |
||
158 | if ((current_code < 0) || (current_code > LZ_MAX_CODE)) |
||
159 | return -2; /* image defect */ |
||
160 | if (prefix[current_code] == NO_SUCH_CODE) { |
||
161 | if (current_code == decoder->running_code - 2) { |
||
162 | current_prefix = prev_code; |
||
163 | suffix[decoder->running_code - 2] |
||
164 | = stack[stack_ptr++] |
||
165 | = ipe16lzw_trace_prefix(prefix, prev_code, CLEAR_CODE); |
||
166 | } |
||
167 | else { |
||
168 | return -3; /* image defect */ |
||
169 | } |
||
170 | } |
||
171 | else { |
||
172 | current_prefix = current_code; |
||
173 | } |
||
174 | j = 0; |
||
175 | while (j++ <= LZ_MAX_CODE && current_prefix > CLEAR_CODE && current_prefix <= LZ_MAX_CODE) { |
||
176 | stack[stack_ptr++] = suffix[current_prefix]; |
||
177 | current_prefix = prefix[current_prefix]; |
||
178 | } |
||
179 | if (j >= LZ_MAX_CODE || current_prefix > LZ_MAX_CODE) |
||
180 | return -4; /* image defect */ |
||
181 | |||
182 | stack[stack_ptr++] = current_prefix; |
||
183 | |||
184 | while (stack_ptr != 0 && i < outputLength) { |
||
185 | output[i++] = stack[--stack_ptr]; |
||
186 | //if (i > bytes_written) bytes_written = i; |
||
187 | ++bytes_written; |
||
188 | } |
||
189 | } |
||
190 | if (prev_code != NO_SUCH_CODE) { |
||
191 | if ((decoder->running_code < 2) || |
||
192 | (decoder->running_code > LZ_MAX_CODE + 2)) |
||
193 | return -5; /* image defect */ |
||
194 | prefix[decoder->running_code - 2] = prev_code; |
||
195 | |||
196 | if (current_code == decoder->running_code - 2) { |
||
197 | suffix[decoder->running_code - 2] |
||
198 | = ipe16lzw_trace_prefix(prefix, prev_code, CLEAR_CODE); |
||
199 | } |
||
200 | else { |
||
201 | suffix[decoder->running_code - 2] |
||
202 | = ipe16lzw_trace_prefix(prefix, current_code, CLEAR_CODE); |
||
203 | } |
||
204 | } |
||
205 | prev_code = current_code; |
||
206 | } |
||
207 | } |
||
208 | |||
209 | return bytes_written; |
||
210 | } |
||
211 | |||
212 | #define BMP_LINE_PADDING 4 |
||
213 | #define BI_SIGNATURE 0x4D42 |
||
214 | |||
215 | // Difference between ipma_write_bmp and ipe16_write_bmp: At ipma_write_bmp, the imagedata is bottom-down, and the palette is a RGBA-structure and not a RGB-structure |
||
216 | void ipma_write_bmp(FILE* output, unsigned int width, unsigned int height, unsigned char* imagedata, size_t imagedata_len, RGBQUAD *pal, int numColors) { |
||
217 | |||
218 | // Each line must be padded to a multiple of 4 |
||
219 | int pad = (BMP_LINE_PADDING - (width % BMP_LINE_PADDING)) % BMP_LINE_PADDING; |
||
220 | int newwidth = width + pad; |
||
221 | int newsize = newwidth * height; |
||
222 | unsigned char* padded_imagedata = (unsigned char*)malloc(newsize); |
||
223 | if (padded_imagedata == NULL) return; |
||
224 | unsigned int i; |
||
225 | for (i = 0; i < height; ++i) { |
||
226 | int offset = newwidth * i; |
||
227 | memcpy(&padded_imagedata[offset], imagedata, width); |
||
228 | memset(&padded_imagedata[offset + width], 0, pad); |
||
229 | imagedata += width; |
||
230 | } |
||
231 | |||
232 | BITMAPFILEHEADER bfh; |
||
233 | bfh.bfType = BI_SIGNATURE; |
||
234 | bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * numColors + newsize; |
||
235 | bfh.bfReserved1 = 0; |
||
236 | bfh.bfReserved2 = 0; |
||
237 | bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * numColors; |
||
238 | fwrite(&bfh, sizeof(bfh), 1, output); |
||
239 | |||
240 | BITMAPINFOHEADER bih; |
||
241 | bih.biSize = sizeof(BITMAPINFOHEADER); |
||
242 | bih.biWidth = width; |
||
243 | bih.biHeight = height; // (positive = "bottom-up"-Bitmap) |
||
244 | bih.biPlanes = 1; |
||
245 | bih.biBitCount = 8; |
||
246 | bih.biCompression = BI_RGB; |
||
247 | bih.biSizeImage = 0; |
||
248 | bih.biXPelsPerMeter = 0; |
||
249 | bih.biYPelsPerMeter = 0; |
||
250 | bih.biClrUsed = 0; |
||
251 | bih.biClrImportant = 0; |
||
252 | fwrite(&bih, sizeof(bih), 1, output); |
||
253 | |||
254 | // Color table |
||
255 | fwrite(pal, sizeof(RGBQUAD) * numColors, 1, output); |
||
256 | |||
257 | // Image data |
||
258 | fwrite(padded_imagedata, newsize, 1, output); |
||
259 | |||
260 | free(padded_imagedata); |
||
261 | } |
||
262 | |||
263 | |||
264 | typedef struct tagBitmapInfoAndPalette { |
||
265 | BITMAPINFOHEADER bi; |
||
266 | RGBQUAD pal[256]; |
||
267 | } BitmapInfoAndPalette; |
||
268 | |||
269 | bool ipma_export_frames_bmp(char* filename, char* outdir) |
||
270 | { |
||
271 | PAVIFILE pFile; |
||
272 | int res; |
||
273 | PAVISTREAM pStream1; |
||
274 | AVISTREAMINFOA asi1; |
||
275 | |||
8 | daniel-mar | 276 | // Try creating the directory |
277 | CreateDirectoryA(outdir, NULL); |
||
5 | daniel-mar | 278 | |
279 | if (!dirExists(outdir)) { |
||
280 | fprintf(stderr, "ERROR: Directory couldn't be created! %s\n", outdir); |
||
281 | return 1; |
||
282 | } |
||
283 | |||
284 | res = AVIFileOpenA(&pFile, filename, OF_SHARE_DENY_WRITE, 0L); |
||
285 | if (res == AVIERR_FILEOPEN) { |
||
8 | daniel-mar | 286 | fprintf(stderr, "ERROR: AVIFileOpenA(%s) returns AVIERR_FILEOPEN. Does the file exist?\n", filename); |
5 | daniel-mar | 287 | return false; |
288 | } |
||
289 | if (res != 0) { |
||
8 | daniel-mar | 290 | fprintf(stderr, "ERROR: AVIFileOpenA(%s) returns %d\n", filename, res); |
5 | daniel-mar | 291 | return false; |
292 | } |
||
293 | |||
294 | res = AVIFileGetStream(pFile, &pStream1, streamtypeVIDEO, 0); |
||
295 | if (res == AVIERR_NODATA) { |
||
8 | daniel-mar | 296 | fprintf(stderr, "ERROR: AVIFileGetStream returns AVIERR_NODATA\n"); |
5 | daniel-mar | 297 | AVIFileRelease(pFile); |
298 | return false; |
||
299 | } |
||
300 | if (res == AVIERR_MEMORY) { |
||
8 | daniel-mar | 301 | fprintf(stderr, "ERROR: AVIFileGetStream returns AVIERR_MEMORY\n"); |
5 | daniel-mar | 302 | AVIFileRelease(pFile); |
303 | return false; |
||
304 | } |
||
305 | if (res != 0) { |
||
8 | daniel-mar | 306 | fprintf(stderr, "ERROR: AVIFileGetStream returns %d\n", res); |
5 | daniel-mar | 307 | AVIFileRelease(pFile); |
308 | return false; |
||
309 | } |
||
310 | |||
311 | |||
312 | res = AVIStreamInfoA(pStream1, &asi1, sizeof(asi1)); |
||
313 | if (res != 0) { |
||
8 | daniel-mar | 314 | fprintf(stderr, "ERROR: AVIStreamInfoA returns %d\n", res); |
5 | daniel-mar | 315 | AVIStreamRelease(pStream1); |
316 | AVIFileRelease(pFile); |
||
317 | return false; |
||
318 | } |
||
319 | |||
320 | |||
321 | // The official handler name is "ipma", but some AVI files also use "IPMA" |
||
322 | int ipmaVersion = 0; |
||
323 | //if (asi1.fccHandler == mmioFOURCC('i', 'p', 'm', 'a')) ipmaVersion = 1; |
||
324 | if ((tolower(((asi1.fccHandler >> 0) & 0xFF)) == 'i') && |
||
325 | (tolower(((asi1.fccHandler >> 8) & 0xFF)) == 'p') && |
||
326 | (tolower(((asi1.fccHandler >> 16) & 0xFF)) == 'm') && |
||
327 | (tolower(((asi1.fccHandler >> 24) & 0xFF)) == 'a')) |
||
328 | { |
||
329 | ipmaVersion = 1; |
||
330 | } |
||
331 | |||
332 | // The official handler name is "IP20", but all AVI files use "ip20" |
||
333 | //if (asi1.fccHandler == mmioFOURCC('I', 'P', '2', '0')) ipmaVersion = 2; |
||
334 | if ((tolower(((asi1.fccHandler >> 0) & 0xFF)) == 'i') && |
||
335 | (tolower(((asi1.fccHandler >> 8) & 0xFF)) == 'p') && |
||
336 | (tolower(((asi1.fccHandler >> 16) & 0xFF)) == '2') && |
||
337 | (tolower(((asi1.fccHandler >> 24) & 0xFF)) == '0')) |
||
338 | { |
||
339 | ipmaVersion = 2; |
||
340 | } |
||
341 | |||
342 | if (ipmaVersion == 0) { |
||
8 | daniel-mar | 343 | fprintf(stderr, "ERROR: Not an IPMA or IP20 AVI file!\n"); |
5 | daniel-mar | 344 | AVIStreamRelease(pStream1); |
345 | AVIFileRelease(pFile); |
||
346 | return false; |
||
347 | } |
||
348 | |||
349 | |||
350 | int framesWritten = 0; |
||
351 | for (int i = 0; 1; i++) { |
||
352 | BitmapInfoAndPalette* pstrf = (BitmapInfoAndPalette*)malloc(sizeof(BitmapInfoAndPalette)); |
||
353 | if (pstrf == NULL) return false; |
||
354 | LONG strf_siz = sizeof(BitmapInfoAndPalette); |
||
355 | ZeroMemory(pstrf, strf_siz); |
||
356 | |||
357 | //res = pStream1->ReadFormat(i, (LPVOID)pstrf, &strf_siz); |
||
358 | res = AVIStreamReadFormat(pStream1, i, (LPVOID)pstrf, &strf_siz); |
||
359 | if (res != 0) { |
||
8 | daniel-mar | 360 | fprintf(stderr, "ERROR: Read format info failed\n"); |
5 | daniel-mar | 361 | AVIStreamRelease(pStream1); |
362 | AVIFileRelease(pFile); |
||
363 | return false; |
||
364 | } |
||
365 | |||
366 | |||
367 | if (((ipmaVersion == 1) && (asi1.fccHandler != mmioFOURCC('I', 'p', 'm', 'a'))) && |
||
368 | ((ipmaVersion == 2) && (asi1.fccHandler != mmioFOURCC('I', 'p', '2', '0')))) |
||
369 | { |
||
370 | // biCompression is case-sensitive and must be "Ipma" or "Ip20" |
||
8 | daniel-mar | 371 | if (ipmaVersion == 1) fprintf(stderr, "ERROR: biCompression is not Ipma!\n"); |
372 | if (ipmaVersion == 2) fprintf(stderr, "ERROR: biCompression is not Ip20!\n"); |
||
5 | daniel-mar | 373 | AVIStreamRelease(pStream1); |
374 | AVIFileRelease(pFile); |
||
375 | return false; |
||
376 | } |
||
377 | |||
7 | daniel-mar | 378 | // Note that for 2 files, bi.biSizeImage is wrong (much too small!) |
379 | // LB05M08.AVI: biSizeImage (10598) != rectWidth * rectHeight (27492) |
||
380 | // TY06M12.AVI: biSizeImage (1274) != rectWidth * rectHeight (8058) |
||
381 | //int bufsiz_uncompressed = pstrf->bi.biSizeImage; |
||
382 | int bufsiz_uncompressed = (asi1.rcFrame.right - asi1.rcFrame.left) * (asi1.rcFrame.bottom - asi1.rcFrame.top); |
||
383 | |||
384 | // theoretically, compressed can sometimes be larger than uncompressed, so we multiply by 10 |
||
385 | int bufsiz_compressed = bufsiz_uncompressed * 10; |
||
386 | |||
5 | daniel-mar | 387 | unsigned char* buffer_uncompressed = (unsigned char*)malloc(bufsiz_uncompressed); |
388 | if (buffer_uncompressed == NULL) return false; |
||
389 | unsigned char* buffer_compressed = (unsigned char*)malloc(bufsiz_compressed); |
||
390 | if (buffer_compressed == NULL) return false; |
||
391 | |||
392 | |||
393 | LONG plBytes = 0; |
||
394 | LONG plSamples = 0; |
||
395 | //res = pStream1->Read(i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples); |
||
396 | res = AVIStreamRead(pStream1, i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples); |
||
397 | if ((res != 0) || (plSamples == 0)) break; |
||
398 | |||
8 | daniel-mar | 399 | int plBytesUncompressed; |
400 | if ((plBytes == 0) && (plSamples > 0)) { |
||
401 | // In "Panic in the Park", frames at the end of some videos have 0 bytes data. |
||
402 | // This should be interpreted as "repeat frame" (still image). So we fill it with palette #0 (transparent) |
||
403 | plBytesUncompressed = bufsiz_uncompressed; |
||
404 | ZeroMemory(buffer_uncompressed, bufsiz_uncompressed); |
||
405 | } else { |
||
406 | Ipe16LZWDecoder* pdecoder = (Ipe16LZWDecoder*)malloc(sizeof(Ipe16LZWDecoder)); |
||
407 | if (pdecoder == NULL) return false; |
||
408 | ZeroMemory(pdecoder, sizeof(Ipe16LZWDecoder)); |
||
409 | unsigned char* work_buffer_compressed = buffer_compressed; |
||
410 | plBytesUncompressed = ipma_lzw_decode(&work_buffer_compressed, pdecoder, buffer_uncompressed, bufsiz_uncompressed); |
||
411 | free(pdecoder); |
||
412 | } |
||
413 | if (plBytesUncompressed < 0) fprintf(stderr, "WARNING: LZW Error %d at frame %d\n", plBytesUncompressed, i); |
||
414 | if (plBytesUncompressed != bufsiz_uncompressed) fprintf(stderr, "WARNING: piBytesUncompressed != bufsiz_uncompressed\n"); |
||
5 | daniel-mar | 415 | if (plBytesUncompressed > 0) { |
416 | char filnam[MAX_PATH]; |
||
417 | if (AVIStreamIsKeyFrame(pStream1, i)) { |
||
418 | sprintf(filnam, "%s\\frame_%05d_key.bmp", outdir, i); |
||
419 | } else { |
||
420 | sprintf(filnam, "%s\\frame_%05d.bmp", outdir, i); |
||
421 | } |
||
422 | FILE* fh2 = fopen(filnam, "wb+"); |
||
423 | ipma_write_bmp(fh2, pstrf->bi.biWidth, pstrf->bi.biHeight, buffer_uncompressed, plBytesUncompressed, &pstrf->pal[0], 256); |
||
424 | fclose(fh2); |
||
425 | framesWritten++; |
||
426 | } |
||
427 | |||
428 | free(pstrf); |
||
429 | free(buffer_compressed); |
||
430 | free(buffer_uncompressed); |
||
431 | } |
||
432 | |||
8 | daniel-mar | 433 | fprintf(stdout, "%s: %d frames written to %s\n", filename, framesWritten, outdir); |
5 | daniel-mar | 434 | |
435 | AVIStreamRelease(pStream1); |
||
436 | AVIFileRelease(pFile); |
||
437 | return true; |
||
438 | } |
||
439 | |||
440 | void print_syntax() { |
||
441 | fprintf(stderr, "Syntax: -o <outputdir> -i <avifile>\n"); |
||
442 | } |
||
443 | |||
444 | int main(int argc, char* argv[]) { |
||
445 | char filename[MAX_PATH]; |
||
446 | char outdir[MAX_PATH]; |
||
447 | |||
448 | #ifndef VISUAL_STUDIO_TEST |
||
449 | int c; |
||
450 | |||
451 | #define PRINT_SYNTAX { print_syntax(); return 0; } |
||
452 | |||
453 | while ((c = getopt(argc, argv, "Vi:o:")) != -1) { |
||
454 | switch (c) { |
||
455 | case 'V': |
||
456 | fprintf(stdout, "IPMA video frame extractor, revision %s\n", VERSION); |
||
457 | return 0; |
||
458 | case 'i': |
||
459 | strcpy(filename, optarg); |
||
460 | break; |
||
461 | case 'o': |
||
462 | strcpy(outdir, optarg); |
||
463 | break; |
||
464 | case '?': |
||
465 | PRINT_SYNTAX; |
||
466 | break; |
||
467 | } |
||
468 | } |
||
469 | if (optind < argc) PRINT_SYNTAX; |
||
470 | |||
471 | if (strlen(filename) == 0) PRINT_SYNTAX; |
||
472 | if (strlen(outdir) == 0) PRINT_SYNTAX; |
||
473 | |||
474 | FILE* fhTest = fopen(filename, "rb"); |
||
475 | if (!fhTest) { |
||
476 | fprintf(stderr, "FATAL: Cannot open %s\n", filename); |
||
477 | return 1; |
||
478 | } |
||
479 | fclose(fhTest); |
||
480 | |||
481 | #else |
||
482 | strcpy(filename, "D:\\test\\AVI_TEST.avi"); |
||
483 | strcpy(outdir, "D:\\test\\AVI_TEST"); |
||
484 | #endif |
||
485 | |||
486 | if (CoInitialize(NULL) != 0) return 1; |
||
487 | |||
488 | bool res = ipma_export_frames_bmp(filename, outdir); |
||
489 | |||
490 | CoUninitialize(); |
||
491 | |||
492 | return res ? 0 : 1; |
||
493 | } |