o Moved personal dev environment from older gcc to newer clang. Many small changes...
[asdcplib.git] / src / as-02-unwrap.cpp
1 /*
2 Copyright (c) 2011-2015, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
3 John Hurst
4
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10 1. Redistributions of source code must retain the above copyright
11    notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15 3. The name of the author may not be used to endorse or promote products
16    derived from this software without specific prior written permission.
17
18 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 /*! \file    as-02-unwrap.cpp
30     \version $Id$       
31     \brief   AS-02 file manipulation utility
32
33   This program extracts picture and sound from AS-02 files.
34
35   For more information about AS-02, please refer to the header file AS_02.h
36   For more information about asdcplib, please refer to the header file AS_DCP.h
37 */
38
39 #include <KM_fileio.h>
40 #include <AS_02.h>
41 #include <WavFileWriter.h>
42
43 namespace ASDCP {
44   Result_t MD_to_PCM_ADesc(ASDCP::MXF::WaveAudioDescriptor* ADescObj, ASDCP::PCM::AudioDescriptor& ADesc);
45 }
46
47 using namespace ASDCP;
48
49 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
50
51 //------------------------------------------------------------------------------------------
52 //
53 // command line option parser class
54
55 static const char* PROGRAM_NAME = "as-02-unwrap";  // program name for messages
56
57 // Increment the iterator, test for an additional non-option command line argument.
58 // Causes the caller to return if there are no remaining arguments or if the next
59 // argument begins with '-'.
60 #define TEST_EXTRA_ARG(i,c)                                             \
61   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
62     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
63     return;                                                             \
64   }
65
66 //
67 void
68 banner(FILE* stream = stdout)
69 {
70   fprintf(stream, "\n\
71 %s (asdcplib %s)\n\n\
72 Copyright (c) 2011-2015, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
73 asdcplib may be copied only under the terms of the license found at\n\
74 the top of every file in the asdcplib distribution kit.\n\n\
75 Specify the -h (help) option for further information about %s\n\n",
76           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
77 }
78
79 //
80 void
81 usage(FILE* stream = stdout)
82 {
83   fprintf(stream, "\
84 USAGE: %s [-h|-help] [-V]\n\
85 \n\
86        %s [-1|-2] [-b <buffer-size>] [-d <duration>]\n\
87        [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <size>] [-v] [-W]\n\
88        [-w] <input-file> [<file-prefix>]\n\n",
89           PROGRAM_NAME, PROGRAM_NAME);
90
91   fprintf(stream, "\
92 Options:\n\
93   -1                - Split Wave essence to mono WAV files during extract.\n\
94                       Default is multichannel WAV\n\
95   -2                - Split Wave essence to stereo WAV files during extract.\n\
96                       Default is multichannel WAV\n\
97   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
98                       Defaults to 4,194,304 (4MB)\n\
99   -d <duration>     - Number of frames to process, default all\n\
100   -f <start-frame>  - Starting frame number, default 0\n\
101   -h | -help        - Show help\n\
102   -k <key-string>   - Use key for ciphertext operations\n\
103   -m                - verify HMAC values when reading\n\
104   -s <size>         - Number of bytes to dump to output when -v is given\n\
105   -V                - Show version information\n\
106   -v                - Verbose, prints informative messages to stderr\n\
107   -W                - Read input file only, do not write destination file\n\
108   -w <width>        - Width of numeric element in a series of frame file names\n\
109                       (default 6)\n\
110   -z                - Fail if j2c inputs have unequal parameters (default)\n\
111   -Z                - Ignore unequal parameters in j2c inputs\n\
112 \n\
113   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
114          o All option arguments must be separated from the option by whitespace.\n\n");
115 }
116
117 //
118 class CommandOptions
119 {
120   CommandOptions();
121
122 public:
123   bool   error_flag;     // true if the given options are in error or not complete
124   bool   key_flag;       // true if an encryption key was given
125   bool   read_hmac;      // true if HMAC values are to be validated
126   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
127   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
128   bool   verbose_flag;   // true if the verbose option was selected
129   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
130   bool   no_write_flag;  // true if no output files are to be written
131   bool   version_flag;   // true if the version display option was selected
132   bool   help_flag;      // true if the help display option was selected
133   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
134   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
135   ui32_t start_frame;    // frame number to begin processing
136   ui32_t duration;       // number of frames to be processed
137   bool   duration_flag;  // true if duration argument given
138   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
139   ui32_t picture_rate;   // fps of picture when wrapping PCM
140   ui32_t fb_size;        // size of picture frame buffer
141   Rational edit_rate;    // frame buffer size for reading clip-wrapped PCM
142   const char* file_prefix; // filename pre for files written by the extract mode
143   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
144   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
145   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
146   const char* input_filename;
147   std::string prefix_buffer;
148
149   //
150   CommandOptions(int argc, const char** argv) :
151     error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
152     mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
153     version_flag(false), help_flag(false), number_width(6),
154     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
155     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
156     input_filename(0)
157   {
158     memset(key_value, 0, KeyLen);
159     memset(key_id_value, 0, UUIDlen);
160
161     for ( int i = 1; i < argc; ++i )
162       {
163
164         if ( (strcmp( argv[i], "-help") == 0) )
165           {
166             help_flag = true;
167             continue;
168           }
169          
170         if ( argv[i][0] == '-'
171              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
172              && argv[i][2] == 0 )
173           {
174             switch ( argv[i][1] )
175               {
176               case '1': mono_wav = true; break;
177               case '2': split_wav = true; break;
178
179               case 'b':
180                 TEST_EXTRA_ARG(i, 'b');
181                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
182
183                 if ( verbose_flag )
184                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
185
186                 break;
187
188               case 'd':
189                 TEST_EXTRA_ARG(i, 'd');
190                 duration_flag = true;
191                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
192                 break;
193
194               case 'f':
195                 TEST_EXTRA_ARG(i, 'f');
196                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
197                 break;
198
199               case 'h': help_flag = true; break;
200               case 'm': read_hmac = true; break;
201
202               case 'p':
203                 TEST_EXTRA_ARG(i, 'p');
204                 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
205                 break;
206
207               case 's':
208                 TEST_EXTRA_ARG(i, 's');
209                 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
210                 break;
211
212               case 'V': version_flag = true; break;
213               case 'v': verbose_flag = true; break;
214               case 'W': no_write_flag = true; break;
215
216               case 'w':
217                 TEST_EXTRA_ARG(i, 'w');
218                 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
219                 break;
220
221               case 'Z': j2c_pedantic = false; break;
222               case 'z': j2c_pedantic = true; break;
223
224               default:
225                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
226                 return;
227               }
228           }
229         else
230           {
231             if ( argv[i][0] != '-' )
232               {
233                 if ( input_filename == 0 )
234                   {
235                     input_filename = argv[i];
236                   }
237                 else if ( file_prefix == 0 )
238                   {
239                     file_prefix = argv[i];
240                   }
241               }
242             else
243               {
244                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
245                 return;
246               }
247           }
248       }
249
250     if ( help_flag || version_flag )
251       return;
252     
253     if ( input_filename == 0 )
254       {
255         fputs("At least one filename argument is required.\n", stderr);
256         return;
257       }
258
259     if ( file_prefix == 0 )
260       {
261         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
262         file_prefix = prefix_buffer.c_str();
263       }
264
265     error_flag = false;
266   }
267 };
268
269
270 //------------------------------------------------------------------------------------------
271 // JPEG 2000 essence
272
273
274 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
275 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
276 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
277 //
278 Result_t
279 read_JP2K_file(CommandOptions& Options)
280 {
281   AESDecContext*     Context = 0;
282   HMACContext*       HMAC = 0;
283   AS_02::JP2K::MXFReader    Reader;
284   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
285   ui32_t             frame_count = 0;
286
287   Result_t result = Reader.OpenRead(Options.input_filename);
288
289   if ( ASDCP_SUCCESS(result) )
290     {
291       if ( Options.verbose_flag )
292         {
293           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
294         }
295
296       ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
297       ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
298
299       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
300                                                      reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
301
302       if ( KM_SUCCESS(result) )
303         {
304           assert(rgba_descriptor);
305           frame_count = rgba_descriptor->ContainerDuration;
306
307           if ( Options.verbose_flag )
308             {
309               rgba_descriptor->Dump();
310             }
311         }
312       else
313         {
314           result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
315                                                          reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
316
317           if ( KM_SUCCESS(result) )
318             {
319               assert(cdci_descriptor);
320               frame_count = cdci_descriptor->ContainerDuration;
321
322               if ( Options.verbose_flag )
323                 {
324                   cdci_descriptor->Dump();
325                 }
326             }
327           else
328             {
329               fprintf(stderr, "File does not contain an essence descriptor.\n");
330               frame_count = Reader.AS02IndexReader().GetDuration();
331             }
332         }
333
334       if ( frame_count == 0 )
335         {
336           frame_count = Reader.AS02IndexReader().GetDuration();
337         }
338
339       if ( frame_count == 0 )
340         {
341           fprintf(stderr, "Unable to determine file duration.\n");
342           return RESULT_FAIL;
343         }
344     }
345
346   if ( ASDCP_SUCCESS(result) && Options.key_flag )
347     {
348       Context = new AESDecContext;
349       result = Context->InitKey(Options.key_value);
350
351       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
352         {
353           WriterInfo Info;
354           Reader.FillWriterInfo(Info);
355
356           if ( Info.UsesHMAC )
357             {
358               HMAC = new HMACContext;
359               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
360             }
361           else
362             {
363               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
364             }
365         }
366     }
367
368   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
369   if ( last_frame > frame_count )
370     last_frame = frame_count;
371
372   char name_format[64];
373   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
374
375   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
376     {
377       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
378
379       char filename[1024];
380       snprintf(filename, 1024, name_format, Options.file_prefix, i);
381
382       if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
383         {
384           printf("Frame %d, %d bytes", i, FrameBuffer.Size());
385
386           if ( ! Options.no_write_flag )
387             {
388               printf(" -> %s", filename);
389             }
390
391           printf("\n");
392         }
393
394       if ( ASDCP_SUCCESS(result)  && ( ! Options.no_write_flag ) )
395         {
396           Kumu::FileWriter OutFile;
397           ui32_t write_count;
398           result = OutFile.OpenWrite(filename);
399
400           if ( ASDCP_SUCCESS(result) )
401             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
402
403           if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
404             {
405               FrameBuffer.Dump(stderr, Options.fb_dump_size);
406             }
407         }
408     }
409
410   return result;
411 }
412
413 //------------------------------------------------------------------------------------------
414 // PCM essence
415
416 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
417 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
418 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
419 //
420 Result_t
421 read_PCM_file(CommandOptions& Options)
422 {
423   AESDecContext*     Context = 0;
424   HMACContext*       HMAC = 0;
425   AS_02::PCM::MXFReader     Reader;
426   PCM::FrameBuffer   FrameBuffer;
427   WavFileWriter      OutWave;
428   ui32_t last_frame = 0;
429   ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0;
430
431   if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI
432     {
433       Options.edit_rate = EditRate_24;
434     }
435
436   Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
437
438   if ( KM_SUCCESS(result) )
439     {
440       if ( Options.verbose_flag )
441         {
442           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
443         }
444       
445       ASDCP::MXF::InterchangeObject* tmp_obj = 0;
446
447       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), &tmp_obj);
448
449       if ( KM_SUCCESS(result) )
450         {
451           wave_descriptor = dynamic_cast<ASDCP::MXF::WaveAudioDescriptor*>(tmp_obj);
452
453           if ( wave_descriptor == 0 )
454             {
455               fprintf(stderr, "File does not contain an essence descriptor.\n");
456               return RESULT_FAIL;
457             }
458       
459           if ( Options.verbose_flag )
460             {
461               wave_descriptor->Dump();
462             }
463
464           if ( wave_descriptor->ContainerDuration.get() == 0 )
465             {
466               fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
467               last_frame = Reader.AS02IndexReader().GetDuration();
468             }
469           else
470             {
471               last_frame = wave_descriptor->ContainerDuration;
472             }
473
474           if ( last_frame == 0 )
475             {
476               fprintf(stderr, "Unable to determine file duration.\n");
477               return RESULT_FAIL;
478             }
479
480           assert(wave_descriptor);
481           FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
482           last_frame = AS_02::MXF::CalcFramesFromDurationInSamples(last_frame, *wave_descriptor, Options.edit_rate);
483         }
484     }
485
486   if ( ASDCP_SUCCESS(result) )
487     {
488       if ( Options.duration > 0 && Options.duration < last_frame )
489         last_frame = Options.duration;
490
491       if ( Options.start_frame > 0 )
492         {
493           if ( Options.start_frame > last_frame )
494             {
495               fprintf(stderr, "Start value greater than file duration.\n");
496               return RESULT_FAIL;
497             }
498
499           last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame);
500         }
501
502       last_frame = last_frame - Options.start_frame;
503
504       PCM::AudioDescriptor ADesc;
505
506       result = MD_to_PCM_ADesc(wave_descriptor, ADesc);
507
508       if ( ASDCP_SUCCESS(result) )
509         {
510           ADesc.ContainerDuration = last_frame;
511           ADesc.EditRate = Options.edit_rate;
512
513           result = OutWave.OpenWrite(ADesc, Options.file_prefix,
514                                      ( Options.split_wav ? WavFileWriter::ST_STEREO : 
515                                        ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
516         }
517     }
518
519   if ( ASDCP_SUCCESS(result) && Options.key_flag )
520     {
521       Context = new AESDecContext;
522       result = Context->InitKey(Options.key_value);
523
524       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
525         {
526           WriterInfo Info;
527           Reader.FillWriterInfo(Info);
528
529           if ( Info.UsesHMAC )
530             {
531               HMAC = new HMACContext;
532               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
533             }
534           else
535             {
536               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
537             }
538         }
539     }
540
541   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
542     {
543       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
544
545       if ( ASDCP_SUCCESS(result) )
546         {
547           if ( Options.verbose_flag )
548             {
549               FrameBuffer.FrameNumber(i);
550               FrameBuffer.Dump(stderr, Options.fb_dump_size);
551             }
552
553           if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
554             {
555               fprintf(stderr, "Last frame is incomplete, padding with zeros.\n");
556               // actually, it has already been zeroed for us, we just need to recognize the appropriate size
557               FrameBuffer.Size(FrameBuffer.Capacity());
558             }
559
560           result = OutWave.WriteFrame(FrameBuffer);
561         }
562     }
563
564   return result;
565 }
566
567
568 //
569 int
570 main(int argc, const char** argv)
571 {
572   char     str_buf[64];
573   CommandOptions Options(argc, argv);
574
575   if ( Options.version_flag )
576     banner();
577
578   if ( Options.help_flag )
579     usage();
580
581   if ( Options.version_flag || Options.help_flag )
582     return 0;
583
584   if ( Options.error_flag )
585     {
586       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
587       return 3;
588     }
589
590   EssenceType_t EssenceType;
591   Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
592
593   if ( ASDCP_SUCCESS(result) )
594     {
595       switch ( EssenceType )
596         {
597         case ESS_AS02_JPEG_2000:
598           result = read_JP2K_file(Options);
599           break;
600
601         case ESS_AS02_PCM_24b_48k:
602         case ESS_AS02_PCM_24b_96k:
603           result = read_PCM_file(Options);
604           break;
605
606         default:
607           fprintf(stderr, "%s: Unknown file type, not AS-02 essence.\n", Options.input_filename);
608           return 5;
609         }
610     }
611
612   if ( ASDCP_FAILURE(result) )
613     {
614       fputs("Program stopped on error.\n", stderr);
615
616       if ( result != RESULT_FAIL )
617         {
618           fputs(result, stderr);
619           fputc('\n', stderr);
620         }
621
622       return 1;
623     }
624
625   return 0;
626 }
627
628
629 //
630 // end as-02-unwrap.cpp
631 //