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