Subversion Repositories ipe_artfile_utils

Rev

Rev 7 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /**
  2.  * IPMA video frame extractor by Daniel Marschall, ViaThinkSoft (C) 2022
  3.  * Supports codecs IPMA and IP20
  4.  * Revision: 2022-01-16
  5.  * License: Apache 2.0
  6.  **/
  7.  
  8. #define VERSION "2022-01-16"
  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.         // Try creating the directory
  277.         CreateDirectoryA(outdir, NULL);
  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) {
  286.                 fprintf(stderr, "ERROR: AVIFileOpenA(%s) returns AVIERR_FILEOPEN. Does the file exist?\n", filename);
  287.                 return false;
  288.         }
  289.         if (res != 0) {
  290.                 fprintf(stderr, "ERROR: AVIFileOpenA(%s) returns %d\n", filename, res);
  291.                 return false;
  292.         }
  293.  
  294.         res = AVIFileGetStream(pFile, &pStream1, streamtypeVIDEO, 0);
  295.         if (res == AVIERR_NODATA) {
  296.                 fprintf(stderr, "ERROR: AVIFileGetStream returns AVIERR_NODATA\n");
  297.                 AVIFileRelease(pFile);
  298.                 return false;
  299.         }
  300.         if (res == AVIERR_MEMORY) {
  301.                 fprintf(stderr, "ERROR: AVIFileGetStream returns AVIERR_MEMORY\n");
  302.                 AVIFileRelease(pFile);
  303.                 return false;
  304.         }
  305.         if (res != 0) {
  306.                 fprintf(stderr, "ERROR: AVIFileGetStream returns %d\n", res);
  307.                 AVIFileRelease(pFile);
  308.                 return false;
  309.         }
  310.  
  311.  
  312.         res = AVIStreamInfoA(pStream1, &asi1, sizeof(asi1));
  313.         if (res != 0) {
  314.                 fprintf(stderr, "ERROR: AVIStreamInfoA returns %d\n", res);
  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) {
  343.                 fprintf(stderr, "ERROR: Not an IPMA or IP20 AVI file!\n");
  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) {
  360.                         fprintf(stderr, "ERROR: Read format info failed\n");
  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"
  371.                         if (ipmaVersion == 1) fprintf(stderr, "ERROR: biCompression is not Ipma!\n");
  372.                         if (ipmaVersion == 2) fprintf(stderr, "ERROR: biCompression is not Ip20!\n");
  373.                         AVIStreamRelease(pStream1);
  374.                         AVIFileRelease(pFile);
  375.                         return false;
  376.                 }
  377.  
  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.  
  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.  
  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");
  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.  
  433.         fprintf(stdout, "%s: %d frames written to %s\n", filename, framesWritten, outdir);
  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. }
  494.