Bump patch version post tag.
[asdcplib.git] / src / asdcp-unwrap.cpp
1 /*
2 Copyright (c) 2003-2016, 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$
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>         - Alternative picture rate when unwrapping PCM:\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     if ( picture_rate == 192 ) return EditRate_192;
177     if ( picture_rate == 200 ) return EditRate_200;
178     if ( picture_rate == 240 ) return EditRate_240;
179     return EditRate_24;
180   }
181
182   //
183   CommandOptions(int argc, const char** argv) :
184     mode(MMT_EXTRACT), error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
185     mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
186     version_flag(false), help_flag(false), stereo_image_flag(false), number_width(6),
187     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
188     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
189     channel_fmt(PCM::CF_NONE), input_filename(0), extension("dcdata")
190   {
191     memset(key_value, 0, KeyLen);
192     memset(key_id_value, 0, UUIDlen);
193
194     for ( int i = 1; i < argc; ++i )
195       {
196
197         if ( (strcmp( argv[i], "-help") == 0) )
198           {
199             help_flag = true;
200             continue;
201           }
202
203         if ( argv[i][0] == '-'
204              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
205              && argv[i][2] == 0 )
206           {
207             switch ( argv[i][1] )
208               {
209               case '1': mono_wav = true; break;
210               case '2': split_wav = true; break;
211               case '3': stereo_image_flag = true; break;
212
213               case 'b':
214                 TEST_EXTRA_ARG(i, 'b');
215                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
216                 break;
217
218               case 'd':
219                 TEST_EXTRA_ARG(i, 'd');
220                 duration_flag = true;
221                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
222                 break;
223
224               case 'e':
225                 TEST_EXTRA_ARG(i, 'e');
226                 extension = argv[i];
227                 break;
228
229               case 'f':
230                 TEST_EXTRA_ARG(i, 'f');
231                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
232                 break;
233
234               case 'G': mode = MMT_GOP_START; break;
235               case 'h': help_flag = true; break;
236
237               case 'k': key_flag = true;
238                 TEST_EXTRA_ARG(i, 'k');
239                 {
240                   ui32_t length;
241                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
242
243                   if ( length != KeyLen )
244                     {
245                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
246                       return;
247                     }
248                 }
249                 break;
250
251               case 'm': read_hmac = true; break;
252
253               case 'p':
254                 TEST_EXTRA_ARG(i, 'p');
255                 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
256                 break;
257
258               case 's':
259                 TEST_EXTRA_ARG(i, 's');
260                 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
261                 break;
262
263               case 'V': version_flag = true; break;
264               case 'v': verbose_flag = true; break;
265               case 'W': no_write_flag = true; break;
266
267               case 'w':
268                 TEST_EXTRA_ARG(i, 'w');
269                 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
270                 break;
271
272               case 'Z': j2c_pedantic = false; break;
273               case 'z': j2c_pedantic = true; break;
274
275               default:
276                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
277                 return;
278               }
279           }
280         else
281           {
282             if ( argv[i][0] != '-' )
283               {
284                 if ( input_filename == 0 )
285                   {
286                     input_filename = argv[i];
287                   }
288                 else if ( file_prefix == 0 )
289                   {
290                     file_prefix = argv[i];
291                   }
292               }
293             else
294               {
295                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
296                 return;
297               }
298           }
299       }
300
301     if ( help_flag || version_flag )
302       return;
303
304     if ( (  mode == MMT_EXTRACT || mode == MMT_GOP_START ) && input_filename == 0 )
305       {
306         fputs("Option requires at least one filename argument.\n", stderr);
307         return;
308       }
309
310     if ( mode == MMT_EXTRACT  && file_prefix == 0 )
311       {
312         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
313         file_prefix = prefix_buffer.c_str();
314       }
315
316     error_flag = false;
317   }
318 };
319
320 //------------------------------------------------------------------------------------------
321 // MPEG2 essence
322
323 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
324 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
325 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
326 //
327 Result_t
328 read_MPEG2_file(CommandOptions& Options)
329 {
330   AESDecContext*     Context = 0;
331   HMACContext*       HMAC = 0;
332   MPEG2::MXFReader   Reader;
333   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
334   Kumu::FileWriter   OutFile;
335   ui32_t             frame_count = 0;
336
337   Result_t result = Reader.OpenRead(Options.input_filename);
338
339   if ( ASDCP_SUCCESS(result) )
340     {
341       MPEG2::VideoDescriptor VDesc;
342       Reader.FillVideoDescriptor(VDesc);
343       frame_count = VDesc.ContainerDuration;
344
345       if ( Options.verbose_flag )
346         {
347           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
348           MPEG2::VideoDescriptorDump(VDesc);
349         }
350     }
351
352   if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
353     {
354       char filename[256];
355       snprintf(filename, 256, "%s.ves", Options.file_prefix);
356       result = OutFile.OpenWrite(filename);
357     }
358
359   if ( ASDCP_SUCCESS(result) && Options.key_flag )
360     {
361       Context = new AESDecContext;
362       result = Context->InitKey(Options.key_value);
363
364       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
365         {
366           WriterInfo Info;
367           Reader.FillWriterInfo(Info);
368
369           if ( Info.UsesHMAC )
370             {
371               HMAC = new HMACContext;
372               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
373             }
374           else
375             {
376               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
377             }
378         }
379     }
380
381   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
382   if ( last_frame > frame_count )
383     last_frame = frame_count;
384
385   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
386     {
387       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
388
389       if ( ASDCP_SUCCESS(result) )
390         {
391           if ( Options.verbose_flag )
392             FrameBuffer.Dump(stderr, Options.fb_dump_size);
393
394           if ( ! Options.no_write_flag )
395             {
396           ui32_t write_count = 0;
397           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
398         }
399     }
400     }
401
402   return result;
403 }
404
405
406 //
407 Result_t
408 gop_start_test(CommandOptions& Options)
409 {
410   using namespace ASDCP::MPEG2;
411
412   MXFReader   Reader;
413   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
414   ui32_t      frame_count = 0;
415
416   Result_t result = Reader.OpenRead(Options.input_filename);
417
418   if ( ASDCP_SUCCESS(result) )
419     {
420       MPEG2::VideoDescriptor VDesc;
421       Reader.FillVideoDescriptor(VDesc);
422       frame_count = VDesc.ContainerDuration;
423
424       if ( Options.verbose_flag )
425         {
426           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
427           MPEG2::VideoDescriptorDump(VDesc);
428         }
429     }
430
431   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
432   if ( last_frame > frame_count )
433     last_frame = frame_count;
434
435   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
436     {
437       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
438
439       if ( ASDCP_SUCCESS(result) )
440         {
441           if ( Options.verbose_flag )
442             FrameBuffer.Dump(stderr, Options.fb_dump_size);
443
444           if ( FrameBuffer.FrameType() != FRAME_I )
445             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
446
447           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
448         }
449     }
450
451   return result;
452 }
453
454 //------------------------------------------------------------------------------------------
455 // JPEG 2000 essence
456
457
458 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
459 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
460 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
461 Result_t
462 read_JP2K_S_file(CommandOptions& Options)
463 {
464   AESDecContext*     Context = 0;
465   HMACContext*       HMAC = 0;
466   JP2K::MXFSReader    Reader;
467   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
468   ui32_t             frame_count = 0;
469
470   Result_t result = Reader.OpenRead(Options.input_filename);
471
472   if ( ASDCP_SUCCESS(result) )
473     {
474       JP2K::PictureDescriptor PDesc;
475       Reader.FillPictureDescriptor(PDesc);
476
477       frame_count = PDesc.ContainerDuration;
478
479       if ( Options.verbose_flag )
480         {
481           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
482           JP2K::PictureDescriptorDump(PDesc);
483         }
484     }
485
486   if ( ASDCP_SUCCESS(result) && Options.key_flag )
487     {
488       Context = new AESDecContext;
489       result = Context->InitKey(Options.key_value);
490
491       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
492         {
493           WriterInfo Info;
494           Reader.FillWriterInfo(Info);
495
496           if ( Info.UsesHMAC )
497             {
498               HMAC = new HMACContext;
499               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
500             }
501           else
502             {
503               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
504             }
505         }
506     }
507
508   const int filename_max = 1024;
509   char filename[filename_max];
510   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
511   if ( last_frame > frame_count )
512     last_frame = frame_count;
513
514   char left_format[64];  char right_format[64];
515   snprintf(left_format,  64, "%%s%%0%duL.j2c", Options.number_width);
516   snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
517
518   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
519     {
520       result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
521
522       if ( ASDCP_SUCCESS(result) )
523         {
524           if ( ! Options.no_write_flag )
525             {
526           Kumu::FileWriter OutFile;
527           ui32_t write_count;
528           snprintf(filename, filename_max, left_format, Options.file_prefix, i);
529           result = OutFile.OpenWrite(filename);
530
531           if ( ASDCP_SUCCESS(result) )
532             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
533             }
534
535           if ( Options.verbose_flag )
536             FrameBuffer.Dump(stderr, Options.fb_dump_size);
537         }
538
539       if ( ASDCP_SUCCESS(result) )
540         result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
541
542       if ( ASDCP_SUCCESS(result) )
543         {
544           if ( ! Options.no_write_flag )
545             {
546           Kumu::FileWriter OutFile;
547           ui32_t write_count;
548           snprintf(filename, filename_max, right_format, Options.file_prefix, i);
549           result = OutFile.OpenWrite(filename);
550
551           if ( ASDCP_SUCCESS(result) )
552             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
553         }
554
555           if ( Options.verbose_flag )
556             FrameBuffer.Dump(stderr, Options.fb_dump_size);
557     }
558     }
559
560   return result;
561 }
562
563 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
564 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
565 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
566 //
567 Result_t
568 read_JP2K_file(CommandOptions& Options)
569 {
570   AESDecContext*     Context = 0;
571   HMACContext*       HMAC = 0;
572   JP2K::MXFReader    Reader;
573   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
574   ui32_t             frame_count = 0;
575
576   Result_t result = Reader.OpenRead(Options.input_filename);
577
578   if ( ASDCP_SUCCESS(result) )
579     {
580       JP2K::PictureDescriptor PDesc;
581       Reader.FillPictureDescriptor(PDesc);
582
583       frame_count = PDesc.ContainerDuration;
584
585       if ( Options.verbose_flag )
586         {
587           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
588           JP2K::PictureDescriptorDump(PDesc);
589         }
590     }
591
592   if ( ASDCP_SUCCESS(result) && Options.key_flag )
593     {
594       Context = new AESDecContext;
595       result = Context->InitKey(Options.key_value);
596
597       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
598         {
599           WriterInfo Info;
600           Reader.FillWriterInfo(Info);
601
602           if ( Info.UsesHMAC )
603             {
604               HMAC = new HMACContext;
605               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
606             }
607           else
608             {
609               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
610             }
611         }
612     }
613
614   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
615   if ( last_frame > frame_count )
616     last_frame = frame_count;
617
618   char name_format[64];
619   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
620
621   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
622     {
623       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
624
625       if ( ASDCP_SUCCESS(result) )
626         {
627           if ( ! Options.no_write_flag )
628             {
629           Kumu::FileWriter OutFile;
630           char filename[256];
631           ui32_t write_count;
632           snprintf(filename, 256, name_format, Options.file_prefix, i);
633           result = OutFile.OpenWrite(filename);
634
635           if ( ASDCP_SUCCESS(result) )
636             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
637             }
638
639           if ( Options.verbose_flag )
640             FrameBuffer.Dump(stderr, Options.fb_dump_size);
641         }
642     }
643
644   return result;
645 }
646
647 //------------------------------------------------------------------------------------------
648 // PCM essence
649
650 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
651 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
652 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
653 //
654 Result_t
655 read_PCM_file(CommandOptions& Options)
656 {
657   AESDecContext*     Context = 0;
658   HMACContext*       HMAC = 0;
659   PCM::MXFReader     Reader;
660   PCM::FrameBuffer   FrameBuffer;
661   WavFileWriter      OutWave;
662   PCM::AudioDescriptor ADesc;
663   ui32_t last_frame = 0;
664
665   Result_t result = Reader.OpenRead(Options.input_filename);
666
667   if ( ASDCP_SUCCESS(result) )
668     {
669       Reader.FillAudioDescriptor(ADesc);
670
671       if ( ADesc.EditRate != EditRate_23_98
672            && ADesc.EditRate != EditRate_24
673            && ADesc.EditRate != EditRate_25
674            && ADesc.EditRate != EditRate_30
675            && ADesc.EditRate != EditRate_48
676            && ADesc.EditRate != EditRate_50
677            && ADesc.EditRate != EditRate_60 )
678         ADesc.EditRate = Options.PictureRate();
679
680       if ( Options.fb_size != FRAME_BUFFER_SIZE )
681         {
682           FrameBuffer.Capacity(Options.fb_size);
683         }
684       else
685         {
686       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
687         }
688
689       if ( Options.verbose_flag )
690         {
691           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
692         PCM::AudioDescriptorDump(ADesc);
693     }
694     }
695
696   if ( ASDCP_SUCCESS(result) )
697     {
698       last_frame = ADesc.ContainerDuration;
699
700       if ( Options.duration > 0 && Options.duration < last_frame )
701         last_frame = Options.duration;
702
703       if ( Options.start_frame > 0 )
704         {
705           if ( Options.start_frame > ADesc.ContainerDuration )
706             {
707               fprintf(stderr, "Start value greater than file duration.\n");
708               return RESULT_FAIL;
709             }
710
711           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
712         }
713
714       ADesc.ContainerDuration = last_frame - Options.start_frame;
715
716       if ( ! Options.no_write_flag )
717         {
718       OutWave.OpenWrite(ADesc, Options.file_prefix,
719                         ( Options.split_wav ? WavFileWriter::ST_STEREO :
720                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
721     }
722     }
723
724   if ( ASDCP_SUCCESS(result) && Options.key_flag )
725     {
726       Context = new AESDecContext;
727       result = Context->InitKey(Options.key_value);
728
729       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
730         {
731           WriterInfo Info;
732           Reader.FillWriterInfo(Info);
733
734           if ( Info.UsesHMAC )
735             {
736               HMAC = new HMACContext;
737               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
738             }
739           else
740             {
741               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
742             }
743         }
744     }
745
746   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
747     {
748       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
749
750       if ( ASDCP_SUCCESS(result) )
751         {
752           if ( Options.verbose_flag )
753             FrameBuffer.Dump(stderr, Options.fb_dump_size);
754
755           if ( ! Options.no_write_flag )
756             {
757           result = OutWave.WriteFrame(FrameBuffer);
758         }
759     }
760     }
761
762   return result;
763 }
764
765
766 //------------------------------------------------------------------------------------------
767 // TimedText essence
768
769 // Read one or more timed text streams from a plaintext ASDCP file
770 // Read one or more timed text streams from a ciphertext ASDCP file
771 // Read one or more timed text streams from a ciphertext ASDCP file
772 //
773 Result_t
774 read_timed_text_file(CommandOptions& Options)
775 {
776   AESDecContext*     Context = 0;
777   HMACContext*       HMAC = 0;
778   TimedText::MXFReader     Reader;
779   TimedText::FrameBuffer   FrameBuffer;
780   TimedText::TimedTextDescriptor TDesc;
781
782   Result_t result = Reader.OpenRead(Options.input_filename);
783
784   if ( ASDCP_SUCCESS(result) )
785     {
786       Reader.FillTimedTextDescriptor(TDesc);
787       FrameBuffer.Capacity(Options.fb_size);
788
789       if ( Options.verbose_flag )
790         TimedText::DescriptorDump(TDesc);
791     }
792
793   if ( ASDCP_SUCCESS(result) && Options.key_flag )
794     {
795       Context = new AESDecContext;
796       result = Context->InitKey(Options.key_value);
797
798       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
799         {
800           WriterInfo Info;
801           Reader.FillWriterInfo(Info);
802
803           if ( Info.UsesHMAC )
804             {
805               HMAC = new HMACContext;
806               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
807             }
808           else
809             {
810               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
811             }
812         }
813     }
814
815   if ( ASDCP_FAILURE(result) )
816     return result;
817
818   std::string XMLDoc;
819   std::string out_path = Kumu::PathDirname(Options.file_prefix);
820   ui32_t write_count;
821   char buf[64];
822   TimedText::ResourceList_t::const_iterator ri;
823
824   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
825
826   if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
827     {
828       Kumu::FileWriter Writer;
829       result = Writer.OpenWrite(Options.file_prefix);
830
831       if ( ASDCP_SUCCESS(result) )
832         result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
833     }
834
835   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
836     {
837       result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
838
839       if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
840         {
841           Kumu::FileWriter Writer;
842           result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
843
844           if ( ASDCP_SUCCESS(result) )
845             result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
846
847               if ( Options.verbose_flag )
848                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
849         }
850     }
851
852   return result;
853 }
854
855 // Read one or more plaintext DCData bytestreams from a plaintext ASDCP file
856 // Read one or more plaintext DCData bytestreams from a ciphertext ASDCP file
857 // Read one or more ciphertext DCData byestreams from a ciphertext ASDCP file
858 //
859 Result_t
860 read_DCData_file(CommandOptions& Options)
861 {
862   AESDecContext*     Context = 0;
863   HMACContext*       HMAC = 0;
864   DCData::MXFReader    Reader;
865   DCData::FrameBuffer  FrameBuffer(Options.fb_size);
866   ui32_t             frame_count = 0;
867
868   Result_t result = Reader.OpenRead(Options.input_filename);
869
870   if ( ASDCP_SUCCESS(result) )
871     {
872       DCData::DCDataDescriptor DDesc;
873       Reader.FillDCDataDescriptor(DDesc);
874
875       frame_count = DDesc.ContainerDuration;
876
877       if ( Options.verbose_flag )
878         {
879           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
880           DCData::DCDataDescriptorDump(DDesc);
881         }
882     }
883
884   if ( ASDCP_SUCCESS(result) && Options.key_flag )
885     {
886       Context = new AESDecContext;
887       result = Context->InitKey(Options.key_value);
888
889       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
890         {
891           WriterInfo Info;
892           Reader.FillWriterInfo(Info);
893
894           if ( Info.UsesHMAC )
895             {
896               HMAC = new HMACContext;
897               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
898             }
899           else
900             {
901               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
902             }
903         }
904     }
905
906   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
907   if ( last_frame > frame_count )
908     last_frame = frame_count;
909
910   char name_format[64];
911   snprintf(name_format,  64, "%%s%%0%du.%s", Options.number_width, Options.extension);
912
913   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
914     {
915       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
916
917       if ( ASDCP_SUCCESS(result) )
918         {
919           if ( ! Options.no_write_flag )
920             {
921           Kumu::FileWriter OutFile;
922           char filename[256];
923           ui32_t write_count;
924           snprintf(filename, 256, name_format, Options.file_prefix, i);
925           result = OutFile.OpenWrite(filename);
926
927           if ( ASDCP_SUCCESS(result) )
928             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
929         }
930
931           if ( Options.verbose_flag )
932             FrameBuffer.Dump(stderr, Options.fb_dump_size);
933         }
934     }
935
936   return result;
937 }
938
939 //
940 int
941 main(int argc, const char** argv)
942 {
943   Result_t result = RESULT_OK;
944   char     str_buf[64];
945   CommandOptions Options(argc, argv);
946
947   if ( Options.version_flag )
948     banner();
949
950   if ( Options.help_flag )
951     usage();
952
953   if ( Options.version_flag || Options.help_flag )
954     return 0;
955
956   if ( Options.error_flag )
957     {
958       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
959       return 3;
960     }
961
962   if ( Options.mode == MMT_GOP_START )
963     {
964       result = gop_start_test(Options);
965     }
966   else if ( Options.mode == MMT_EXTRACT )
967     {
968       EssenceType_t EssenceType;
969       result = ASDCP::EssenceType(Options.input_filename, EssenceType);
970
971       if ( ASDCP_SUCCESS(result) )
972         {
973           switch ( EssenceType )
974             {
975             case ESS_MPEG2_VES:
976               result = read_MPEG2_file(Options);
977               break;
978
979             case ESS_JPEG_2000:
980               if ( Options.stereo_image_flag )
981                 result = read_JP2K_S_file(Options);
982               else
983                 result = read_JP2K_file(Options);
984               break;
985
986             case ESS_JPEG_2000_S:
987               result = read_JP2K_S_file(Options);
988               break;
989
990             case ESS_PCM_24b_48k:
991             case ESS_PCM_24b_96k:
992               result = read_PCM_file(Options);
993               break;
994
995             case ESS_TIMED_TEXT:
996               result = read_timed_text_file(Options);
997               break;
998
999         case ESS_DCDATA_UNKNOWN:
1000           result = read_DCData_file(Options);
1001           break;
1002
1003         case ESS_DCDATA_DOLBY_ATMOS:
1004           Options.extension = "atmos";
1005           result = read_DCData_file(Options);
1006           break;
1007
1008             default:
1009               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.input_filename);
1010               return 5;
1011             }
1012         }
1013     }
1014   else
1015     {
1016       fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
1017       return 6;
1018     }
1019
1020   if ( ASDCP_FAILURE(result) )
1021     {
1022       fputs("Program stopped on error.\n", stderr);
1023
1024       if ( result == RESULT_SFORMAT )
1025         {
1026           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
1027         }
1028       else if ( result != RESULT_FAIL )
1029         {
1030           fputs(result, stderr);
1031           fputc('\n', stderr);
1032         }
1033
1034       return 1;
1035     }
1036
1037   return 0;
1038 }
1039
1040
1041 //
1042 // end asdcp-unwrap.cpp
1043 //