Subversion Repositories ipe_artfile_utils

Rev

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
}