Subversion Repositories ipe_artfile_utils

Rev

Go to most recent revision | 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-15
  5.  * License: Apache 2.0
  6.  **/
  7.  
  8. #define VERSION "2022-01-15"
  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.  
  381.                 int bufsiz_uncompressed = pstrf->bi.biSizeImage;
  382.                 int bufsiz_compressed = pstrf->bi.biSizeImage * 1000; // for some reason, compressed can sometimes be larger than uncompressed, so we multiply by 1000
  383.                 if (bufsiz_uncompressed != (asi1.rcFrame.right - asi1.rcFrame.left) * (asi1.rcFrame.bottom - asi1.rcFrame.top)) {
  384.                         printf("WARNING: biSizeImage != rectWidth * rectHeight\n");
  385.                 }
  386.                 unsigned char* buffer_uncompressed = (unsigned char*)malloc(bufsiz_uncompressed);
  387.                 if (buffer_uncompressed == NULL) return false;
  388.                 unsigned char* buffer_compressed = (unsigned char*)malloc(bufsiz_compressed);
  389.                 if (buffer_compressed == NULL) return false;
  390.  
  391.  
  392.                 LONG plBytes = 0;
  393.                 LONG plSamples = 0;
  394.                 //res = pStream1->Read(i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples);
  395.                 res = AVIStreamRead(pStream1, i, 1, buffer_compressed, bufsiz_compressed, &plBytes, &plSamples);
  396.                 if ((res != 0) || (plSamples == 0)) break;
  397.  
  398.                 Ipe16LZWDecoder* pdecoder = (Ipe16LZWDecoder*)malloc(sizeof(Ipe16LZWDecoder));
  399.                 if (pdecoder == NULL) return false;
  400.                 ZeroMemory(pdecoder, sizeof(Ipe16LZWDecoder));
  401.                 unsigned char* work_buffer_compressed = buffer_compressed;
  402.                 int plBytesUncompressed = ipma_lzw_decode(&work_buffer_compressed, pdecoder, buffer_uncompressed, bufsiz_uncompressed);
  403.                 free(pdecoder);
  404.                 if (plBytesUncompressed < 0) printf("WARNING: LZW Error %d at frame %d\n", plBytesUncompressed, i);
  405.                 if (plBytesUncompressed != bufsiz_uncompressed) printf("WARNING: piBytesUncompressed != bufsiz_uncompressed\n");
  406.                 if (plBytesUncompressed > 0) {
  407.                         char filnam[MAX_PATH];
  408.                         if (AVIStreamIsKeyFrame(pStream1, i)) {
  409.                                 sprintf(filnam, "%s\\frame_%05d_key.bmp", outdir, i);
  410.                         } else {
  411.                                 sprintf(filnam, "%s\\frame_%05d.bmp", outdir, i);
  412.                         }
  413.                         FILE* fh2 = fopen(filnam, "wb+");
  414.                         ipma_write_bmp(fh2, pstrf->bi.biWidth, pstrf->bi.biHeight, buffer_uncompressed, plBytesUncompressed, &pstrf->pal[0], 256);
  415.                         fclose(fh2);
  416.                         framesWritten++;
  417.                 }
  418.  
  419.                 free(pstrf);
  420.                 free(buffer_compressed);
  421.                 free(buffer_uncompressed);
  422.         }
  423.  
  424.         printf("%s: %d frames written to %s\n", filename, framesWritten, outdir);
  425.  
  426.         AVIStreamRelease(pStream1);
  427.         AVIFileRelease(pFile);
  428.         return true;
  429. }
  430.  
  431. void print_syntax() {
  432.         fprintf(stderr, "Syntax: -o <outputdir> -i <avifile>\n");
  433. }
  434.  
  435. int main(int argc, char* argv[]) {
  436.         char filename[MAX_PATH];
  437.         char outdir[MAX_PATH];
  438.  
  439. #ifndef VISUAL_STUDIO_TEST
  440.         int c;
  441.  
  442.         #define PRINT_SYNTAX { print_syntax(); return 0; }
  443.  
  444.         while ((c = getopt(argc, argv, "Vi:o:")) != -1) {
  445.                 switch (c) {
  446.                 case 'V':
  447.                         fprintf(stdout, "IPMA video frame extractor, revision %s\n", VERSION);
  448.                         return 0;
  449.                 case 'i':
  450.                         strcpy(filename, optarg);
  451.                         break;
  452.                 case 'o':
  453.                         strcpy(outdir, optarg);
  454.                         break;
  455.                 case '?':
  456.                         PRINT_SYNTAX;
  457.                         break;
  458.                 }
  459.         }
  460.         if (optind < argc) PRINT_SYNTAX;
  461.  
  462.         if (strlen(filename) == 0) PRINT_SYNTAX;
  463.         if (strlen(outdir) == 0) PRINT_SYNTAX;
  464.  
  465.         FILE* fhTest = fopen(filename, "rb");
  466.         if (!fhTest) {
  467.                 fprintf(stderr, "FATAL: Cannot open %s\n", filename);
  468.                 return 1;
  469.         }
  470.         fclose(fhTest);
  471.  
  472. #else
  473.         strcpy(filename, "D:\\test\\AVI_TEST.avi");
  474.         strcpy(outdir, "D:\\test\\AVI_TEST");
  475. #endif
  476.  
  477.         if (CoInitialize(NULL) != 0) return 1;
  478.  
  479.         bool res = ipma_export_frames_bmp(filename, outdir);
  480.  
  481.         CoUninitialize();
  482.  
  483.         return res ? 0 : 1;
  484. }
  485.