2.5.11.
[asdcplib-cth.git] / src / asdcp-unwrap.cpp
1 /*
2 Copyright (c) 2003-2014, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    asdcp-unwrap.cpp
28     \version $Id: asdcp-unwrap.cpp,v 1.7 2015/10/07 16:41:23 jhurst Exp $
29     \brief   AS-DCP file manipulation utility
30
31   This program extracts picture, sound and text essence from AS-DCP files.
32
33   For more information about asdcplib, please refer to the header file AS_DCP.h
34 */
35
36 #include <KM_fileio.h>
37 #include <WavFileWriter.h>
38
39 using namespace ASDCP;
40
41 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
42
43 //------------------------------------------------------------------------------------------
44 //
45 // command line option parser class
46
47 static const char* PROGRAM_NAME = "asdcp-unwrap";  // program name for messages
48
49 // Increment the iterator, test for an additional non-option command line argument.
50 // Causes the caller to return if there are no remaining arguments or if the next
51 // argument begins with '-'.
52 #define TEST_EXTRA_ARG(i,c)                                             \
53   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
54     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
55     return;                                                             \
56   }
57
58 //
59 void
60 banner(FILE* stream = stdout)
61 {
62   fprintf(stream, "\n\
63 %s (asdcplib %s)\n\n\
64 Copyright (c) 2003-2015 John Hurst\n\n\
65 asdcplib may be copied only under the terms of the license found at\n\
66 the top of every file in the asdcplib distribution kit.\n\n\
67 Specify the -h (help) option for further information about %s\n\n",
68           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
69 }
70
71 //
72 void
73 usage(FILE* stream = stdout)
74 {
75   fprintf(stream, "\
76 USAGE: %s [-h|-help] [-V]\n\
77 \n\
78        %s -G [-v] <input-file>\n\
79 \n\
80        %s [-1|-2] [-3] [-b <buffer-size>] [-d <duration>]\n\
81        [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <size>] [-v] [-W]\n\
82        [-w] <input-file> [<file-prefix>]\n\n",
83           PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
84
85   fprintf(stream, "\
86 Options:\n\
87   -1                - Split Wave essence to mono WAV files during extract.\n\
88                       Default is multichannel WAV\n\
89   -2                - Split Wave essence to stereo WAV files during extract.\n\
90                       Default is multichannel WAV\n\
91   -3                - Force stereoscopic interpretation of a JP2K file.\n\
92   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
93                       Defaults to 4,194,304 (4MB)\n\
94   -d <duration>     - Number of frames to process, default all\n\
95   -e <extension>    - Extension to use for Unknown D-Cinema Data files. default dcdata\n\
96   -f <start-frame>  - Starting frame number, default 0\n                \
97   -G                - Perform GOP start lookup test on MXF+Interop MPEG file\n\
98   -h | -help        - Show help\n\
99   -k <key-string>   - Use key for ciphertext operations\n\
100   -m                - verify HMAC values when reading\n\
101   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
102                       Use one of [23|24|25|30|48|50|60], 24 is default\n\
103   -s <size>         - Number of bytes to dump to output when -v is given\n\
104   -V                - Show version information\n\
105   -v                - Verbose, prints informative messages to stderr\n\
106   -W                - Read input file only, do not write destination file\n\
107   -w <width>        - Width of numeric element in a series of frame file names\n\
108                       (default 6)\n\
109   -z                - Fail if j2c inputs have unequal parameters (default)\n\
110   -Z                - Ignore unequal parameters in j2c inputs\n\
111 \n\
112   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
113          o All option arguments must be separated from the option by whitespace.\n\
114          o An argument of \"23\" to the -p option will be interpreted\n\
115            as 24000/1001 fps.\n\n");
116 }
117
118 //
119 enum MajorMode_t
120 {
121   MMT_NONE,
122   MMT_EXTRACT,
123   MMT_GOP_START,
124 };
125
126 //
127 class CommandOptions
128 {
129   CommandOptions();
130
131 public:
132   MajorMode_t mode;
133   bool   error_flag;     // true if the given options are in error or not complete
134   bool   key_flag;       // true if an encryption key was given
135   bool   read_hmac;      // true if HMAC values are to be validated
136   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
137   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
138   bool   verbose_flag;   // true if the verbose option was selected
139   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
140   bool   no_write_flag;  // true if no output files are to be written
141   bool   version_flag;   // true if the version display option was selected
142   bool   help_flag;      // true if the help display option was selected
143   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
144   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
145   ui32_t start_frame;    // frame number to begin processing
146   ui32_t duration;       // number of frames to be processed
147   bool   duration_flag;  // true if duration argument given
148   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
149   ui32_t picture_rate;   // fps of picture when wrapping PCM
150   ui32_t fb_size;        // size of picture frame buffer
151   const char* file_prefix; // filename pre for files written by the extract mode
152   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
153   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
154   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
155   const char* input_filename;
156   std::string prefix_buffer;
157   const char* extension; // file extension to use for unknown D-Cinema Data track files.
158
159   //
160   Rational PictureRate()
161   {
162     if ( picture_rate == 16 ) return EditRate_16;
163     if ( picture_rate == 18 ) return EditRate_18;
164     if ( picture_rate == 20 ) return EditRate_20;
165     if ( picture_rate == 22 ) return EditRate_22;
166     if ( picture_rate == 23 ) return EditRate_23_98;
167     if ( picture_rate == 24 ) return EditRate_24;
168     if ( picture_rate == 25 ) return EditRate_25;
169     if ( picture_rate == 30 ) return EditRate_30;
170     if ( picture_rate == 48 ) return EditRate_48;
171     if ( picture_rate == 50 ) return EditRate_50;
172     if ( picture_rate == 60 ) return EditRate_60;
173     if ( picture_rate == 96 ) return EditRate_96;
174     if ( picture_rate == 100 ) return EditRate_100;
175     if ( picture_rate == 120 ) return EditRate_120;
176     return EditRate_24;
177   }
178
179   //
180   CommandOptions(int argc, const char** argv) :
181     mode(MMT_EXTRACT), error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
182     mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
183     version_flag(false), help_flag(false), stereo_image_flag(false), number_width(6),
184     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
185     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
186     channel_fmt(PCM::CF_NONE), input_filename(0), extension("dcdata")
187   {
188     memset(key_value, 0, KeyLen);
189     memset(key_id_value, 0, UUIDlen);
190
191     for ( int i = 1; i < argc; ++i )
192       {
193
194         if ( (strcmp( argv[i], "-help") == 0) )
195           {
196             help_flag = true;
197             continue;
198           }
199
200         if ( argv[i][0] == '-'
201              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
202              && argv[i][2] == 0 )
203           {
204             switch ( argv[i][1] )
205               {
206               case '1': mono_wav = true; break;
207               case '2': split_wav = true; break;
208               case '3': stereo_image_flag = true; break;
209
210               case 'b':
211                 TEST_EXTRA_ARG(i, 'b');
212                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
213                 break;
214
215               case 'd':
216                 TEST_EXTRA_ARG(i, 'd');
217                 duration_flag = true;
218                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
219                 break;
220
221           case 'e':
222             TEST_EXTRA_ARG(i, 'e');
223             extension = argv[i];
224             break;
225
226               case 'f':
227                 TEST_EXTRA_ARG(i, 'f');
228                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
229                 break;
230
231               case 'G': mode = MMT_GOP_START; break;
232               case 'h': help_flag = true; break;
233
234               case 'k': key_flag = true;
235                 TEST_EXTRA_ARG(i, 'k');
236                 {
237                   ui32_t length;
238                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
239
240                   if ( length != KeyLen )
241                     {
242                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
243                       return;
244                     }
245                 }
246                 break;
247
248               case 'm': read_hmac = true; break;
249
250               case 'p':
251                 TEST_EXTRA_ARG(i, 'p');
252                 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
253                 break;
254
255               case 's':
256                 TEST_EXTRA_ARG(i, 's');
257                 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
258                 break;
259
260               case 'V': version_flag = true; break;
261               case 'v': verbose_flag = true; break;
262               case 'W': no_write_flag = true; break;
263
264               case 'w':
265                 TEST_EXTRA_ARG(i, 'w');
266                 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
267                 break;
268
269               case 'Z': j2c_pedantic = false; break;
270               case 'z': j2c_pedantic = true; break;
271
272               default:
273                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
274                 return;
275               }
276           }
277         else
278           {
279             if ( argv[i][0] != '-' )
280               {
281                 if ( input_filename == 0 )
282                   {
283                     input_filename = argv[i];
284                   }
285                 else if ( file_prefix == 0 )
286                   {
287                     file_prefix = argv[i];
288                   }
289               }
290             else
291               {
292                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
293                 return;
294               }
295           }
296       }
297
298     if ( help_flag || version_flag )
299       return;
300
301     if ( (  mode == MMT_EXTRACT || mode == MMT_GOP_START ) && input_filename == 0 )
302       {
303         fputs("Option requires at least one filename argument.\n", stderr);
304         return;
305       }
306
307     if ( mode == MMT_EXTRACT  && file_prefix == 0 )
308       {
309         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
310         file_prefix = prefix_buffer.c_str();
311       }
312
313     error_flag = false;
314   }
315 };
316
317 //------------------------------------------------------------------------------------------
318 // MPEG2 essence
319
320 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
321 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
322 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
323 //
324 Result_t
325 read_MPEG2_file(CommandOptions& Options)
326 {
327   AESDecContext*     Context = 0;
328   HMACContext*       HMAC = 0;
329   MPEG2::MXFReader   Reader;
330   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
331   Kumu::FileWriter   OutFile;
332   ui32_t             frame_count = 0;
333
334   Result_t result = Reader.OpenRead(Options.input_filename);
335
336   if ( ASDCP_SUCCESS(result) )
337     {
338       MPEG2::VideoDescriptor VDesc;
339       Reader.FillVideoDescriptor(VDesc);
340       frame_count = VDesc.ContainerDuration;
341
342       if ( Options.verbose_flag )
343         {
344           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
345           MPEG2::VideoDescriptorDump(VDesc);
346         }
347     }
348
349   if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
350     {
351       char filename[256];
352       snprintf(filename, 256, "%s.ves", Options.file_prefix);
353       result = OutFile.OpenWrite(filename);
354     }
355
356   if ( ASDCP_SUCCESS(result) && Options.key_flag )
357     {
358       Context = new AESDecContext;
359       result = Context->InitKey(Options.key_value);
360
361       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
362         {
363           WriterInfo Info;
364           Reader.FillWriterInfo(Info);
365
366           if ( Info.UsesHMAC )
367             {
368               HMAC = new HMACContext;
369               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
370             }
371           else
372             {
373               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
374             }
375         }
376     }
377
378   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
379   if ( last_frame > frame_count )
380     last_frame = frame_count;
381
382   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
383     {
384       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
385
386       if ( ASDCP_SUCCESS(result) )
387         {
388           if ( Options.verbose_flag )
389             FrameBuffer.Dump(stderr, Options.fb_dump_size);
390
391           if ( ! Options.no_write_flag )
392             {
393           ui32_t write_count = 0;
394           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
395         }
396     }
397     }
398
399   return result;
400 }
401
402
403 //
404 Result_t
405 gop_start_test(CommandOptions& Options)
406 {
407   using namespace ASDCP::MPEG2;
408
409   MXFReader   Reader;
410   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
411   ui32_t      frame_count = 0;
412
413   Result_t result = Reader.OpenRead(Options.input_filename);
414
415   if ( ASDCP_SUCCESS(result) )
416     {
417       MPEG2::VideoDescriptor VDesc;
418       Reader.FillVideoDescriptor(VDesc);
419       frame_count = VDesc.ContainerDuration;
420
421       if ( Options.verbose_flag )
422         {
423           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
424           MPEG2::VideoDescriptorDump(VDesc);
425         }
426     }
427
428   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
429   if ( last_frame > frame_count )
430     last_frame = frame_count;
431
432   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
433     {
434       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
435
436       if ( ASDCP_SUCCESS(result) )
437         {
438           if ( Options.verbose_flag )
439             FrameBuffer.Dump(stderr, Options.fb_dump_size);
440
441           if ( FrameBuffer.FrameType() != FRAME_I )
442             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
443
444           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
445         }
446     }
447
448   return result;
449 }
450
451 //------------------------------------------------------------------------------------------
452 // JPEG 2000 essence
453
454
455 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
456 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
457 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
458 Result_t
459 read_JP2K_S_file(CommandOptions& Options)
460 {
461   AESDecContext*     Context = 0;
462   HMACContext*       HMAC = 0;
463   JP2K::MXFSReader    Reader;
464   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
465   ui32_t             frame_count = 0;
466
467   Result_t result = Reader.OpenRead(Options.input_filename);
468
469   if ( ASDCP_SUCCESS(result) )
470     {
471       JP2K::PictureDescriptor PDesc;
472       Reader.FillPictureDescriptor(PDesc);
473
474       frame_count = PDesc.ContainerDuration;
475
476       if ( Options.verbose_flag )
477         {
478           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
479           JP2K::PictureDescriptorDump(PDesc);
480         }
481     }
482
483   if ( ASDCP_SUCCESS(result) && Options.key_flag )
484     {
485       Context = new AESDecContext;
486       result = Context->InitKey(Options.key_value);
487
488       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
489         {
490           WriterInfo Info;
491           Reader.FillWriterInfo(Info);
492
493           if ( Info.UsesHMAC )
494             {
495               HMAC = new HMACContext;
496               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
497             }
498           else
499             {
500               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
501             }
502         }
503     }
504
505   const int filename_max = 1024;
506   char filename[filename_max];
507   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
508   if ( last_frame > frame_count )
509     last_frame = frame_count;
510
511   char left_format[64];  char right_format[64];
512   snprintf(left_format,  64, "%%s%%0%duL.j2c", Options.number_width);
513   snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
514
515   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
516     {
517       result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
518
519       if ( ASDCP_SUCCESS(result) )
520         {
521           if ( ! Options.no_write_flag )
522             {
523           Kumu::FileWriter OutFile;
524           ui32_t write_count;
525           snprintf(filename, filename_max, left_format, Options.file_prefix, i);
526           result = OutFile.OpenWrite(filename);
527
528           if ( ASDCP_SUCCESS(result) )
529             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
530             }
531
532           if ( Options.verbose_flag )
533             FrameBuffer.Dump(stderr, Options.fb_dump_size);
534         }
535
536       if ( ASDCP_SUCCESS(result) )
537         result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
538
539       if ( ASDCP_SUCCESS(result) )
540         {
541           if ( ! Options.no_write_flag )
542             {
543           Kumu::FileWriter OutFile;
544           ui32_t write_count;
545           snprintf(filename, filename_max, right_format, Options.file_prefix, i);
546           result = OutFile.OpenWrite(filename);
547
548           if ( ASDCP_SUCCESS(result) )
549             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
550         }
551
552           if ( Options.verbose_flag )
553             FrameBuffer.Dump(stderr, Options.fb_dump_size);
554     }
555     }
556
557   return result;
558 }
559
560 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
561 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
562 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
563 //
564 Result_t
565 read_JP2K_file(CommandOptions& Options)
566 {
567   AESDecContext*     Context = 0;
568   HMACContext*       HMAC = 0;
569   JP2K::MXFReader    Reader;
570   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
571   ui32_t             frame_count = 0;
572
573   Result_t result = Reader.OpenRead(Options.input_filename);
574
575   if ( ASDCP_SUCCESS(result) )
576     {
577       JP2K::PictureDescriptor PDesc;
578       Reader.FillPictureDescriptor(PDesc);
579
580       frame_count = PDesc.ContainerDuration;
581
582       if ( Options.verbose_flag )
583         {
584           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
585           JP2K::PictureDescriptorDump(PDesc);
586         }
587     }
588
589   if ( ASDCP_SUCCESS(result) && Options.key_flag )
590     {
591       Context = new AESDecContext;
592       result = Context->InitKey(Options.key_value);
593
594       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
595         {
596           WriterInfo Info;
597           Reader.FillWriterInfo(Info);
598
599           if ( Info.UsesHMAC )
600             {
601               HMAC = new HMACContext;
602               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
603             }
604           else
605             {
606               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
607             }
608         }
609     }
610
611   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
612   if ( last_frame > frame_count )
613     last_frame = frame_count;
614
615   char name_format[64];
616   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
617
618   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
619     {
620       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
621
622       if ( ASDCP_SUCCESS(result) )
623         {
624           if ( ! Options.no_write_flag )
625             {
626           Kumu::FileWriter OutFile;
627           char filename[256];
628           ui32_t write_count;
629           snprintf(filename, 256, name_format, Options.file_prefix, i);
630           result = OutFile.OpenWrite(filename);
631
632           if ( ASDCP_SUCCESS(result) )
633             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
634             }
635
636           if ( Options.verbose_flag )
637             FrameBuffer.Dump(stderr, Options.fb_dump_size);
638         }
639     }
640
641   return result;
642 }
643
644 //------------------------------------------------------------------------------------------
645 // PCM essence
646
647 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
648 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
649 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
650 //
651 Result_t
652 read_PCM_file(CommandOptions& Options)
653 {
654   AESDecContext*     Context = 0;
655   HMACContext*       HMAC = 0;
656   PCM::MXFReader     Reader;
657   PCM::FrameBuffer   FrameBuffer;
658   WavFileWriter      OutWave;
659   PCM::AudioDescriptor ADesc;
660   ui32_t last_frame = 0;
661
662   Result_t result = Reader.OpenRead(Options.input_filename);
663
664   if ( ASDCP_SUCCESS(result) )
665     {
666       Reader.FillAudioDescriptor(ADesc);
667
668       if ( ADesc.EditRate != EditRate_23_98
669            && ADesc.EditRate != EditRate_24
670            && ADesc.EditRate != EditRate_25
671            && ADesc.EditRate != EditRate_30
672            && ADesc.EditRate != EditRate_48
673            && ADesc.EditRate != EditRate_50
674            && ADesc.EditRate != EditRate_60 )
675         ADesc.EditRate = Options.PictureRate();
676
677       if ( Options.fb_size != FRAME_BUFFER_SIZE )
678         {
679           FrameBuffer.Capacity(Options.fb_size);
680         }
681       else
682         {
683       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
684         }
685
686       if ( Options.verbose_flag )
687         {
688           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
689         PCM::AudioDescriptorDump(ADesc);
690     }
691     }
692
693   if ( ASDCP_SUCCESS(result) )
694     {
695       last_frame = ADesc.ContainerDuration;
696
697       if ( Options.duration > 0 && Options.duration < last_frame )
698         last_frame = Options.duration;
699
700       if ( Options.start_frame > 0 )
701         {
702           if ( Options.start_frame > ADesc.ContainerDuration )
703             {
704               fprintf(stderr, "Start value greater than file duration.\n");
705               return RESULT_FAIL;
706             }
707
708           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
709         }
710
711       ADesc.ContainerDuration = last_frame - Options.start_frame;
712
713       if ( ! Options.no_write_flag )
714         {
715       OutWave.OpenWrite(ADesc, Options.file_prefix,
716                         ( Options.split_wav ? WavFileWriter::ST_STEREO :
717                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
718     }
719     }
720
721   if ( ASDCP_SUCCESS(result) && Options.key_flag )
722     {
723       Context = new AESDecContext;
724       result = Context->InitKey(Options.key_value);
725
726       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
727         {
728           WriterInfo Info;
729           Reader.FillWriterInfo(Info);
730
731           if ( Info.UsesHMAC )
732             {
733               HMAC = new HMACContext;
734               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
735             }
736           else
737             {
738               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
739             }
740         }
741     }
742
743   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
744     {
745       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
746
747       if ( ASDCP_SUCCESS(result) )
748         {
749           if ( Options.verbose_flag )
750             FrameBuffer.Dump(stderr, Options.fb_dump_size);
751
752           if ( ! Options.no_write_flag )
753             {
754           result = OutWave.WriteFrame(FrameBuffer);
755         }
756     }
757     }
758
759   return result;
760 }
761
762
763 //------------------------------------------------------------------------------------------
764 // TimedText essence
765
766 // Read one or more timed text streams from a plaintext ASDCP file
767 // Read one or more timed text streams from a ciphertext ASDCP file
768 // Read one or more timed text streams from a ciphertext ASDCP file
769 //
770 Result_t
771 read_timed_text_file(CommandOptions& Options)
772 {
773   AESDecContext*     Context = 0;
774   HMACContext*       HMAC = 0;
775   TimedText::MXFReader     Reader;
776   TimedText::FrameBuffer   FrameBuffer;
777   TimedText::TimedTextDescriptor TDesc;
778
779   Result_t result = Reader.OpenRead(Options.input_filename);
780
781   if ( ASDCP_SUCCESS(result) )
782     {
783       Reader.FillTimedTextDescriptor(TDesc);
784       FrameBuffer.Capacity(Options.fb_size);
785
786       if ( Options.verbose_flag )
787         TimedText::DescriptorDump(TDesc);
788     }
789
790   if ( ASDCP_SUCCESS(result) && Options.key_flag )
791     {
792       Context = new AESDecContext;
793       result = Context->InitKey(Options.key_value);
794
795       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
796         {
797           WriterInfo Info;
798           Reader.FillWriterInfo(Info);
799
800           if ( Info.UsesHMAC )
801             {
802               HMAC = new HMACContext;
803               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
804             }
805           else
806             {
807               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
808             }
809         }
810     }
811
812   if ( ASDCP_FAILURE(result) )
813     return result;
814
815   std::string XMLDoc;
816   std::string out_path = Kumu::PathDirname(Options.file_prefix);
817   ui32_t write_count;
818   char buf[64];
819   TimedText::ResourceList_t::const_iterator ri;
820
821   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
822
823   if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
824     {
825       Kumu::FileWriter Writer;
826       result = Writer.OpenWrite(Options.file_prefix);
827
828       if ( ASDCP_SUCCESS(result) )
829         result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
830     }
831
832   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
833     {
834       result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
835
836       if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
837         {
838           Kumu::FileWriter Writer;
839           result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
840
841           if ( ASDCP_SUCCESS(result) )
842             result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
843
844               if ( Options.verbose_flag )
845                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
846         }
847     }
848
849   return result;
850 }
851
852 // Read one or more plaintext DCData bytestreams from a plaintext ASDCP file
853 // Read one or more plaintext DCData bytestreams from a ciphertext ASDCP file
854 // Read one or more ciphertext DCData byestreams from a ciphertext ASDCP file
855 //
856 Result_t
857 read_DCData_file(CommandOptions& Options)
858 {
859   AESDecContext*     Context = 0;
860   HMACContext*       HMAC = 0;
861   DCData::MXFReader    Reader;
862   DCData::FrameBuffer  FrameBuffer(Options.fb_size);
863   ui32_t             frame_count = 0;
864
865   Result_t result = Reader.OpenRead(Options.input_filename);
866
867   if ( ASDCP_SUCCESS(result) )
868     {
869       DCData::DCDataDescriptor DDesc;
870       Reader.FillDCDataDescriptor(DDesc);
871
872       frame_count = DDesc.ContainerDuration;
873
874       if ( Options.verbose_flag )
875         {
876           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
877           DCData::DCDataDescriptorDump(DDesc);
878         }
879     }
880
881   if ( ASDCP_SUCCESS(result) && Options.key_flag )
882     {
883       Context = new AESDecContext;
884       result = Context->InitKey(Options.key_value);
885
886       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
887         {
888           WriterInfo Info;
889           Reader.FillWriterInfo(Info);
890
891           if ( Info.UsesHMAC )
892             {
893               HMAC = new HMACContext;
894               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
895             }
896           else
897             {
898               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
899             }
900         }
901     }
902
903   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
904   if ( last_frame > frame_count )
905     last_frame = frame_count;
906
907   char name_format[64];
908   snprintf(name_format,  64, "%%s%%0%du.%s", Options.number_width, Options.extension);
909
910   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
911     {
912       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
913
914       if ( ASDCP_SUCCESS(result) )
915         {
916           if ( ! Options.no_write_flag )
917             {
918           Kumu::FileWriter OutFile;
919           char filename[256];
920           ui32_t write_count;
921           snprintf(filename, 256, name_format, Options.file_prefix, i);
922           result = OutFile.OpenWrite(filename);
923
924           if ( ASDCP_SUCCESS(result) )
925             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
926         }
927
928           if ( Options.verbose_flag )
929             FrameBuffer.Dump(stderr, Options.fb_dump_size);
930         }
931     }
932
933   return result;
934 }
935
936 //
937 int
938 main(int argc, const char** argv)
939 {
940   Result_t result = RESULT_OK;
941   char     str_buf[64];
942   CommandOptions Options(argc, argv);
943
944   if ( Options.version_flag )
945     banner();
946
947   if ( Options.help_flag )
948     usage();
949
950   if ( Options.version_flag || Options.help_flag )
951     return 0;
952
953   if ( Options.error_flag )
954     {
955       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
956       return 3;
957     }
958
959   if ( Options.mode == MMT_GOP_START )
960     {
961       result = gop_start_test(Options);
962     }
963   else if ( Options.mode == MMT_EXTRACT )
964     {
965       EssenceType_t EssenceType;
966       result = ASDCP::EssenceType(Options.input_filename, EssenceType);
967
968       if ( ASDCP_SUCCESS(result) )
969         {
970           switch ( EssenceType )
971             {
972             case ESS_MPEG2_VES:
973               result = read_MPEG2_file(Options);
974               break;
975
976             case ESS_JPEG_2000:
977               if ( Options.stereo_image_flag )
978                 result = read_JP2K_S_file(Options);
979               else
980                 result = read_JP2K_file(Options);
981               break;
982
983             case ESS_JPEG_2000_S:
984               result = read_JP2K_S_file(Options);
985               break;
986
987             case ESS_PCM_24b_48k:
988             case ESS_PCM_24b_96k:
989               result = read_PCM_file(Options);
990               break;
991
992             case ESS_TIMED_TEXT:
993               result = read_timed_text_file(Options);
994               break;
995
996         case ESS_DCDATA_UNKNOWN:
997           result = read_DCData_file(Options);
998           break;
999
1000         case ESS_DCDATA_DOLBY_ATMOS:
1001           Options.extension = "atmos";
1002           result = read_DCData_file(Options);
1003           break;
1004
1005             default:
1006               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.input_filename);
1007               return 5;
1008             }
1009         }
1010     }
1011   else
1012     {
1013       fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
1014       return 6;
1015     }
1016
1017   if ( ASDCP_FAILURE(result) )
1018     {
1019       fputs("Program stopped on error.\n", stderr);
1020
1021       if ( result == RESULT_SFORMAT )
1022         {
1023           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
1024         }
1025       else if ( result != RESULT_FAIL )
1026         {
1027           fputs(result, stderr);
1028           fputc('\n', stderr);
1029         }
1030
1031       return 1;
1032     }
1033
1034   return 0;
1035 }
1036
1037
1038 //
1039 // end asdcp-unwrap.cpp
1040 //