be621106565bd1b25d0ac309544afb892d05757e
[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           if ( ! rgba_descriptor->ContainerDuration.empty() )
285             {
286               frame_count = rgba_descriptor->ContainerDuration;
287             }
288           if ( Options.verbose_flag )
289             {
290               rgba_descriptor->Dump();
291             }
292         }
293       else
294         {
295           result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
296                                                          reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
297
298           if ( KM_SUCCESS(result) )
299             {
300               assert(cdci_descriptor);
301               if ( ! cdci_descriptor->ContainerDuration.empty() )
302                 {
303                   frame_count = cdci_descriptor->ContainerDuration;
304                 }
305               if ( Options.verbose_flag )
306                 {
307                   cdci_descriptor->Dump();
308                 }
309             }
310           else
311             {
312               fprintf(stderr, "File does not contain an essence descriptor.\n");
313               frame_count = Reader.AS02IndexReader().GetDuration();
314             }
315         }
316
317       if ( frame_count == 0 )
318         {
319           frame_count = Reader.AS02IndexReader().GetDuration();
320         }
321
322       if ( frame_count == 0 )
323         {
324           fprintf(stderr, "Unable to determine file duration.\n");
325           return RESULT_FAIL;
326         }
327     }
328
329 #ifdef HAVE_OPENSSL
330   if ( ASDCP_SUCCESS(result) && Options.key_flag )
331     {
332       Context = new AESDecContext;
333       result = Context->InitKey(Options.key_value);
334
335       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
336         {
337           WriterInfo Info;
338           Reader.FillWriterInfo(Info);
339
340           if ( Info.UsesHMAC )
341             {
342               HMAC = new HMACContext;
343               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
344             }
345           else
346             {
347               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
348             }
349         }
350     }
351 #endif // HAVE_OPENSSL
352
353   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
354   if ( last_frame > frame_count )
355     last_frame = frame_count;
356
357   char name_format[64];
358   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
359
360   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
361     {
362       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
363
364       char filename[1024];
365       snprintf(filename, 1024, name_format, Options.file_prefix, i);
366
367       if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
368         {
369           printf("Frame %d, %d bytes", i, FrameBuffer.Size());
370
371           if ( ! Options.no_write_flag )
372             {
373               printf(" -> %s", filename);
374             }
375
376           printf("\n");
377         }
378
379       if ( ASDCP_SUCCESS(result)  && ( ! Options.no_write_flag ) )
380         {
381           Kumu::FileWriter OutFile;
382           ui32_t write_count;
383           result = OutFile.OpenWrite(filename);
384
385           if ( ASDCP_SUCCESS(result) )
386             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
387
388           if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
389             {
390               FrameBuffer.Dump(stderr, Options.fb_dump_size);
391             }
392         }
393     }
394
395   return result;
396 }
397
398 //
399 int
400 main(int argc, const char** argv)
401 {
402   char     str_buf[64];
403   CommandOptions Options(argc, argv);
404
405   if ( Options.version_flag )
406     banner();
407
408   if ( Options.help_flag )
409     usage();
410
411   if ( Options.version_flag || Options.help_flag )
412     return 0;
413
414   if ( Options.error_flag )
415     {
416       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
417       return 3;
418     }
419
420   EssenceType_t EssenceType;
421   Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
422
423   if ( ASDCP_SUCCESS(result) )
424     {
425       switch ( EssenceType )
426         {
427         case ESS_AS02_JPEG_2000:
428           result = read_JP2K_file(Options);
429           break;
430
431         default:
432           fprintf(stderr, "%s: Unknown file type, not P-HDR essence.\n", Options.input_filename);
433           return 5;
434         }
435     }
436
437   if ( ASDCP_FAILURE(result) )
438     {
439       fputs("Program stopped on error.\n", stderr);
440
441       if ( result != RESULT_FAIL )
442         {
443           fputs(result, stderr);
444           fputc('\n', stderr);
445         }
446
447       return 1;
448     }
449
450   return 0;
451 }
452
453
454 //
455 // end phdr-unwrap.cpp
456 //