Merge pull request #9 from dcbullock/master
[asdcplib.git] / src / phdr-unwrap.cpp
1 /*
2 Copyright (c) 2011-2018, John Hurst
3
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 1. Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12    notice, this list of conditions and the following disclaimer in the
13    documentation and/or other materials provided with the distribution.
14 3. The name of the author may not be used to endorse or promote products
15    derived from this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 /*! \file    phdr-unwrap.cpp
29     \version $Id$       
30     \brief   prototype unwrapping for HDR images in AS-02
31
32   This program extracts picture (P-HDR picture) from an AS-02 MXF file.
33 */
34
35 #include <KM_fileio.h>
36 #include <AS_02_PHDR.h>
37
38 using namespace ASDCP;
39
40 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
41
42 //------------------------------------------------------------------------------------------
43 //
44 // command line option parser class
45
46 static const char* PROGRAM_NAME = "as-02-unwrap";  // program name for messages
47
48 // Increment the iterator, test for an additional non-option command line argument.
49 // Causes the caller to return if there are no remaining arguments or if the next
50 // argument begins with '-'.
51 #define TEST_EXTRA_ARG(i,c)                                             \
52   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
53     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
54     return;                                                             \
55   }
56
57 //
58 void
59 banner(FILE* stream = stdout)
60 {
61   fprintf(stream, "\n\
62 %s (asdcplib %s)\n\n\
63 Copyright (c) 2011-2018, John Hurst\n\n\
64 asdcplib may be copied only under the terms of the license found at\n\
65 the top of every file in the asdcplib distribution kit.\n\n\
66 Specify the -h (help) option for further information about %s\n\n",
67           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
68 }
69
70 //
71 void
72 usage(FILE* stream = stdout)
73 {
74   fprintf(stream, "\
75 USAGE: %s [-h|-help] [-V]\n\
76 \n\
77        %s [-b <buffer-size>] [-d <duration>]\n\
78        [-f <starting-frame>] [-m] [-R] [-s <size>] [-v] [-W]\n\
79        [-w] <input-file> [<file-prefix>]\n\n",
80           PROGRAM_NAME, PROGRAM_NAME);
81
82   fprintf(stream, "\
83 Options:\n\
84   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
85                       Defaults to 4,194,304 (4MB)\n\
86   -d <duration>     - Number of frames to process, default all\n\
87   -e <extension>    - Extension to use for aux data files. default \"bin\"\n\
88   -f <start-frame>  - Starting frame number, default 0\n\
89   -g <filename>     - Extract global metadata to the named file.\n\
90   -h | -help        - Show help\n\
91   -k <key-string>   - Use key for ciphertext operations\n\
92   -m                - verify HMAC values when reading\n\
93   -s <size>         - Number of bytes to dump to output when -v is given\n\
94   -V                - Show version information\n\
95   -v                - Verbose, prints informative messages to stderr\n\
96   -W                - Read input file only, do not write destination file\n\
97   -w <width>        - Width of numeric element in a series of frame file names\n\
98                       (default 6)\n\
99 \n\
100   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
101          o All option arguments must be separated from the option by whitespace.\n\n");
102 }
103
104 //
105 class CommandOptions
106 {
107   CommandOptions();
108
109 public:
110   bool   error_flag;     // true if the given options are in error or not complete
111   bool   key_flag;       // true if an encryption key was given
112   bool   read_hmac;      // true if HMAC values are to be validated
113   bool   verbose_flag;   // true if the verbose option was selected
114   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
115   bool   no_write_flag;  // true if no output files are to be written
116   bool   version_flag;   // true if the version display option was selected
117   bool   help_flag;      // true if the help display option was selected
118   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
119   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
120   ui32_t start_frame;    // frame number to begin processing
121   ui32_t duration;       // number of frames to be processed
122   bool   duration_flag;  // true if duration argument given
123   ui32_t fb_size;        // size of picture frame buffer
124   const char* file_prefix; // filename pre for files written by the extract mode
125   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
126   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
127   const char* input_filename;
128   const char* extension;
129   std::string global_metadata_filename, prefix_buffer;
130
131   //
132   CommandOptions(int argc, const char** argv) :
133     error_flag(true), key_flag(false), read_hmac(false), verbose_flag(false),
134     fb_dump_size(0), no_write_flag(false),
135     version_flag(false), help_flag(false), number_width(6),
136     start_frame(0), duration(0xffffffff), duration_flag(false),
137     fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
138     input_filename(0), extension("bin")
139   {
140     memset(key_value, 0, KeyLen);
141     memset(key_id_value, 0, UUIDlen);
142
143     for ( int i = 1; i < argc; ++i )
144       {
145
146         if ( (strcmp( argv[i], "-help") == 0) )
147           {
148             help_flag = true;
149             continue;
150           }
151          
152         if ( argv[i][0] == '-'
153              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
154              && argv[i][2] == 0 )
155           {
156             switch ( argv[i][1] )
157               {
158               case 'b':
159                 TEST_EXTRA_ARG(i, 'b');
160                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
161
162                 if ( verbose_flag )
163                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
164
165                 break;
166
167               case 'd':
168                 TEST_EXTRA_ARG(i, 'd');
169                 duration_flag = true;
170                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
171                 break;
172
173               case 'f':
174                 TEST_EXTRA_ARG(i, 'f');
175                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
176                 break;
177
178               case 'g':
179                 TEST_EXTRA_ARG(i, 'g');
180                 global_metadata_filename = argv[i];
181                 break;
182
183               case 'h': help_flag = true; break;
184               case 'm': read_hmac = true; break;
185
186               case 's':
187                 TEST_EXTRA_ARG(i, 's');
188                 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
189                 break;
190
191               case 'V': version_flag = true; break;
192               case 'v': verbose_flag = true; break;
193               case 'W': no_write_flag = true; break;
194
195               case 'w':
196                 TEST_EXTRA_ARG(i, 'w');
197                 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
198                 break;
199
200               default:
201                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
202                 return;
203               }
204           }
205         else
206           {
207             if ( argv[i][0] != '-' )
208               {
209                 if ( input_filename == 0 )
210                   {
211                     input_filename = argv[i];
212                   }
213                 else if ( file_prefix == 0 )
214                   {
215                     file_prefix = argv[i];
216                   }
217               }
218             else
219               {
220                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
221                 return;
222               }
223           }
224       }
225
226     if ( help_flag || version_flag )
227       return;
228     
229     if ( input_filename == 0 )
230       {
231         fputs("At least one filename argument is required.\n", stderr);
232         return;
233       }
234
235     if ( file_prefix == 0 )
236       {
237         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
238         file_prefix = prefix_buffer.c_str();
239       }
240
241     error_flag = false;
242   }
243 };
244
245
246 //------------------------------------------------------------------------------------------
247 // JPEG 2000 essence
248
249
250 // Read one or more plaintext JPEG 2000 codestreams from a plaintext P-HDR file
251 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext P-HDR file
252 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext P-HDR file
253 //
254 Result_t
255 read_JP2K_file(CommandOptions& Options)
256 {
257   AESDecContext*     Context = 0;
258   HMACContext*       HMAC = 0;
259   AS_02::PHDR::MXFReader    Reader;
260   AS_02::PHDR::FrameBuffer  FrameBuffer(Options.fb_size);
261   ui32_t             frame_count = 0;
262
263   std::string PHDR_master_metadata; // todo: write to a file?
264
265   Result_t result = Reader.OpenRead(Options.input_filename, PHDR_master_metadata);
266   fprintf(stderr, "PHDR_master_metadata size=%zd\n", PHDR_master_metadata.size());
267
268   if ( ASDCP_SUCCESS(result) )
269     {
270       if ( Options.verbose_flag )
271         {
272           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
273         }
274
275       ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
276       ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
277
278       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
279                                                      reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
280
281       if ( KM_SUCCESS(result) )
282         {
283           assert(rgba_descriptor);
284           frame_count = rgba_descriptor->ContainerDuration;
285
286           if ( Options.verbose_flag )
287             {
288               rgba_descriptor->Dump();
289             }
290         }
291       else
292         {
293           result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
294                                                          reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
295
296           if ( KM_SUCCESS(result) )
297             {
298               assert(cdci_descriptor);
299               frame_count = cdci_descriptor->ContainerDuration;
300
301               if ( Options.verbose_flag )
302                 {
303                   cdci_descriptor->Dump();
304                 }
305             }
306           else
307             {
308               fprintf(stderr, "File does not contain an essence descriptor.\n");
309               frame_count = Reader.AS02IndexReader().GetDuration();
310             }
311         }
312
313       if ( frame_count == 0 )
314         {
315           frame_count = Reader.AS02IndexReader().GetDuration();
316         }
317
318       if ( frame_count == 0 )
319         {
320           fprintf(stderr, "Unable to determine file duration.\n");
321           return RESULT_FAIL;
322         }
323     }
324
325   if ( ASDCP_SUCCESS(result) && Options.key_flag )
326     {
327       Context = new AESDecContext;
328       result = Context->InitKey(Options.key_value);
329
330       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
331         {
332           WriterInfo Info;
333           Reader.FillWriterInfo(Info);
334
335           if ( Info.UsesHMAC )
336             {
337               HMAC = new HMACContext;
338               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
339             }
340           else
341             {
342               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
343             }
344         }
345     }
346
347   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
348   if ( last_frame > frame_count )
349     last_frame = frame_count;
350
351   char name_format[64];
352   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
353
354   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
355     {
356       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
357
358       char filename[1024];
359       snprintf(filename, 1024, name_format, Options.file_prefix, i);
360
361       if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
362         {
363           printf("Frame %d, %d bytes", i, FrameBuffer.Size());
364
365           if ( ! Options.no_write_flag )
366             {
367               printf(" -> %s", filename);
368             }
369
370           printf("\n");
371         }
372
373       if ( ASDCP_SUCCESS(result)  && ( ! Options.no_write_flag ) )
374         {
375           Kumu::FileWriter OutFile;
376           ui32_t write_count;
377           result = OutFile.OpenWrite(filename);
378
379           if ( ASDCP_SUCCESS(result) )
380             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
381
382           if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
383             {
384               FrameBuffer.Dump(stderr, Options.fb_dump_size);
385             }
386         }
387     }
388
389   return result;
390 }
391
392 //
393 int
394 main(int argc, const char** argv)
395 {
396   char     str_buf[64];
397   CommandOptions Options(argc, argv);
398
399   if ( Options.version_flag )
400     banner();
401
402   if ( Options.help_flag )
403     usage();
404
405   if ( Options.version_flag || Options.help_flag )
406     return 0;
407
408   if ( Options.error_flag )
409     {
410       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
411       return 3;
412     }
413
414   EssenceType_t EssenceType;
415   Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
416
417   if ( ASDCP_SUCCESS(result) )
418     {
419       switch ( EssenceType )
420         {
421         case ESS_AS02_JPEG_2000:
422           result = read_JP2K_file(Options);
423           break;
424
425         default:
426           fprintf(stderr, "%s: Unknown file type, not P-HDR essence.\n", Options.input_filename);
427           return 5;
428         }
429     }
430
431   if ( ASDCP_FAILURE(result) )
432     {
433       fputs("Program stopped on error.\n", stderr);
434
435       if ( result != RESULT_FAIL )
436         {
437           fputs(result, stderr);
438           fputc('\n', stderr);
439         }
440
441       return 1;
442     }
443
444   return 0;
445 }
446
447
448 //
449 // end phdr-unwrap.cpp
450 //