Subversion Repositories ipe_artfile_utils

Rev

Rev 5 | Go to most recent revision | 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
 
276
        if (!CreateDirectoryA(outdir, NULL)) {
277
                // Also happens if the directory already exists...
278
                //printf("ERROR: Could not create directory %s\n", outdir);
279
                //return false;
280
        }
281
 
282
        if (!dirExists(outdir)) {
283
                fprintf(stderr, "ERROR: Directory couldn't be created! %s\n", outdir);
284
                return 1;
285
        }
286
 
287
        res = AVIFileOpenA(&pFile, filename, OF_SHARE_DENY_WRITE, 0L);
288
        if (res == AVIERR_FILEOPEN) {
289
                printf("ERROR: AVIFileOpenA(%s) returns AVIERR_FILEOPEN. Does the file exist?\n", filename);
290
                return false;
291
        }
292
        if (res != 0) {
293
                printf("ERROR: AVIFileOpenA(%s) returns %d\n", filename, res);
294
                return false;
295
        }
296
 
297
        res = AVIFileGetStream(pFile, &pStream1, streamtypeVIDEO, 0);
298
        if (res == AVIERR_NODATA) {
299
                printf("ERROR: AVIFileGetStream returns AVIERR_NODATA\n");
300
                AVIFileRelease(pFile);
301
                return false;
302
        }
303
        if (res == AVIERR_MEMORY) {
304
                printf("ERROR: AVIFileGetStream returns AVIERR_MEMORY\n");
305
                AVIFileRelease(pFile);
306
                return false;
307
        }
308
        if (res != 0) {
309
                printf("ERROR: AVIFileGetStream returns %d\n", res);
310
                AVIFileRelease(pFile);
311
                return false;
312
        }
313
 
314
 
315
        res = AVIStreamInfoA(pStream1, &asi1, sizeof(asi1));
316
        if (res != 0) {
317
                printf("ERROR: AVIStreamInfoA returns %d\n", res);
318
                AVIStreamRelease(pStream1);
319
                AVIFileRelease(pFile);
320
                return false;
321
        }
322
 
323
 
324
        // The official handler name is "ipma", but some AVI files also use "IPMA"
325
        int ipmaVersion = 0;
326
        //if (asi1.fccHandler == mmioFOURCC('i', 'p', 'm', 'a')) ipmaVersion = 1;
327
        if ((tolower(((asi1.fccHandler >> 0) & 0xFF)) == 'i') &&
328
                (tolower(((asi1.fccHandler >> 8) & 0xFF)) == 'p') &&
329
                (tolower(((asi1.fccHandler >> 16) & 0xFF)) == 'm') &&
330
                (tolower(((asi1.fccHandler >> 24) & 0xFF)) == 'a'))
331
        {
332
                ipmaVersion = 1;
333
        }
334
 
335
        // The official handler name is "IP20", but all AVI files use "ip20"
336
        //if (asi1.fccHandler == mmioFOURCC('I', 'P', '2', '0')) ipmaVersion = 2;
337
        if ((tolower(((asi1.fccHandler >> 0) & 0xFF)) == 'i') &&
338
                (tolower(((asi1.fccHandler >> 8) & 0xFF)) == 'p') &&
339
                (tolower(((asi1.fccHandler >> 16) & 0xFF)) == '2') &&
340
                (tolower(((asi1.fccHandler >> 24) & 0xFF)) == '0'))
341
        {
342
                ipmaVersion = 2;
343
        }
344
 
345
        if (ipmaVersion == 0) {
346
                printf("ERROR: Not an IPMA or IP20 AVI file!\n");
347
                AVIStreamRelease(pStream1);
348
                AVIFileRelease(pFile);
349
                return false;
350
        }
351
 
352
 
353
        int framesWritten = 0;
354
        for (int i = 0; 1; i++) {
355
                BitmapInfoAndPalette* pstrf = (BitmapInfoAndPalette*)malloc(sizeof(BitmapInfoAndPalette));
356
                if (pstrf == NULL) return false;
357
                LONG strf_siz = sizeof(BitmapInfoAndPalette);
358
                ZeroMemory(pstrf, strf_siz);
359
 
360
                //res = pStream1->ReadFormat(i, (LPVOID)pstrf, &strf_siz);
361
                res = AVIStreamReadFormat(pStream1, i, (LPVOID)pstrf, &strf_siz);
362
                if (res != 0) {
363
                        printf("ERROR: Read format info failed\n");
364
                        AVIStreamRelease(pStream1);
365
                        AVIFileRelease(pFile);
366
                        return false;
367
                }
368
 
369
 
370
                if (((ipmaVersion == 1) && (asi1.fccHandler != mmioFOURCC('I', 'p', 'm', 'a'))) &&
371
                        ((ipmaVersion == 2) && (asi1.fccHandler != mmioFOURCC('I', 'p', '2', '0'))))
372
                {
373
                        // biCompression is case-sensitive and must be "Ipma" or "Ip20"
374
                        if (ipmaVersion == 1) printf("ERROR: biCompression is not Ipma!\n");
375
                        if (ipmaVersion == 2) printf("ERROR: biCompression is not Ip20!\n");
376
                        AVIStreamRelease(pStream1);
377
                        AVIFileRelease(pFile);
378
                        return false;
379
                }
380
 
7 daniel-mar 381
                // Note that for 2 files, bi.biSizeImage is wrong (much too small!)
382
                // LB05M08.AVI: biSizeImage (10598) != rectWidth * rectHeight (27492)
383
                // TY06M12.AVI: biSizeImage  (1274) != rectWidth * rectHeight  (8058)
384
                //int bufsiz_uncompressed = pstrf->bi.biSizeImage;
385
                int bufsiz_uncompressed = (asi1.rcFrame.right - asi1.rcFrame.left) * (asi1.rcFrame.bottom - asi1.rcFrame.top);
386
 
387
                // theoretically, compressed can sometimes be larger than uncompressed, so we multiply by 10
388
                int bufsiz_compressed = bufsiz_uncompressed * 10;
389
 
5 daniel-mar 390
                unsigned char* buffer_uncompressed = (unsigned char*)malloc(bufsiz_uncompressed);
391
                if (buffer_uncompressed == NULL) return false;
392
                unsigned char* buffer_compressed = (unsigned char*)malloc(bufsiz_compressed);
393
                if (buffer_compressed == NULL) return false;
394
 
395
 
396
                LONG plBytes = 0;
397
                LONG plSamples = 0;
398
                //res = pStream1->Read(i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples);
399
                res = AVIStreamRead(pStream1, i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples);
400
                if ((res != 0) || (plSamples == 0)) break;
401
 
402
                Ipe16LZWDecoder* pdecoder = (Ipe16LZWDecoder*)malloc(sizeof(Ipe16LZWDecoder));
403
                if (pdecoder == NULL) return false;
404
                ZeroMemory(pdecoder, sizeof(Ipe16LZWDecoder));
405
                unsigned char* work_buffer_compressed = buffer_compressed;
406
                int plBytesUncompressed = ipma_lzw_decode(&work_buffer_compressed, pdecoder, buffer_uncompressed, bufsiz_uncompressed);
407
                free(pdecoder);
408
                if (plBytesUncompressed < 0) printf("WARNING: LZW Error %d at frame %d\n", plBytesUncompressed, i);
409
                if (plBytesUncompressed != bufsiz_uncompressed) printf("WARNING: piBytesUncompressed != bufsiz_uncompressed\n");
410
                if (plBytesUncompressed > 0) {
411
                        char filnam[MAX_PATH];
412
                        if (AVIStreamIsKeyFrame(pStream1, i)) {
413
                                sprintf(filnam, "%s\\frame_%05d_key.bmp", outdir, i);
414
                        } else {
415
                                sprintf(filnam, "%s\\frame_%05d.bmp", outdir, i);
416
                        }
417
                        FILE* fh2 = fopen(filnam, "wb+");
418
                        ipma_write_bmp(fh2, pstrf->bi.biWidth, pstrf->bi.biHeight, buffer_uncompressed, plBytesUncompressed, &pstrf->pal[0], 256);
419
                        fclose(fh2);
420
                        framesWritten++;
421
                }
422
 
423
                free(pstrf);
424
                free(buffer_compressed);
425
                free(buffer_uncompressed);
426
        }
427
 
428
        printf("%s: %d frames written to %s\n", filename, framesWritten, outdir);
429
 
430
        AVIStreamRelease(pStream1);
431
        AVIFileRelease(pFile);
432
        return true;
433
}
434
 
435
void print_syntax() {
436
        fprintf(stderr, "Syntax: -o <outputdir> -i <avifile>\n");
437
}
438
 
439
int main(int argc, char* argv[]) {
440
        char filename[MAX_PATH];
441
        char outdir[MAX_PATH];
442
 
443
#ifndef VISUAL_STUDIO_TEST
444
        int c;
445
 
446
        #define PRINT_SYNTAX { print_syntax(); return 0; }
447
 
448
        while ((c = getopt(argc, argv, "Vi:o:")) != -1) {
449
                switch (c) {
450
                case 'V':
451
                        fprintf(stdout, "IPMA video frame extractor, revision %s\n", VERSION);
452
                        return 0;
453
                case 'i':
454
                        strcpy(filename, optarg);
455
                        break;
456
                case 'o':
457
                        strcpy(outdir, optarg);
458
                        break;
459
                case '?':
460
                        PRINT_SYNTAX;
461
                        break;
462
                }
463
        }
464
        if (optind < argc) PRINT_SYNTAX;
465
 
466
        if (strlen(filename) == 0) PRINT_SYNTAX;
467
        if (strlen(outdir) == 0) PRINT_SYNTAX;
468
 
469
        FILE* fhTest = fopen(filename, "rb");
470
        if (!fhTest) {
471
                fprintf(stderr, "FATAL: Cannot open %s\n", filename);
472
                return 1;
473
        }
474
        fclose(fhTest);
475
 
476
#else
477
        strcpy(filename, "D:\\test\\AVI_TEST.avi");
478
        strcpy(outdir, "D:\\test\\AVI_TEST");
479
#endif
480
 
481
        if (CoInitialize(NULL) != 0) return 1;
482
 
483
        bool res = ipma_export_frames_bmp(filename, outdir);
484
 
485
        CoUninitialize();
486
 
487
        return res ? 0 : 1;
488
}