megachanges
[asdcplib.git] / src / asdcp-unwrap.cpp
1 /*
2 Copyright (c) 2003-2012, 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-2012 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   -f <start-frame>  - Starting frame number, default 0\n\
96   -G                - Perform GOP start lookup test on MXF+Interop MPEG file\n\
97   -h | -help        - Show help\n\
98   -k <key-string>   - Use key for ciphertext operations\n\
99   -m                - verify HMAC values when reading\n\
100   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
101                       Use one of [23|24|25|30|48|50|60], 24 is default\n\
102   -s <size>         - Number of bytes to dump to output when -v is given\n\
103   -V                - Show version information\n\
104   -v                - Verbose, prints informative messages to stderr\n\
105   -W                - Read input file only, do not write destination file\n\
106   -w <width>        - Width of numeric element in a series of frame file names\n\
107                       (default 6).\n\
108   -z                - Fail if j2c inputs have unequal parameters (default)\n\
109   -Z                - Ignore unequal parameters in j2c inputs\n\
110 \n\
111   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
112          o All option arguments must be separated from the option by whitespace.\n\
113          o An argument of \"23\" to the -p option will be interpreted\n\
114            as 24000/1001 fps.\n\n");
115 }
116
117 //
118 enum MajorMode_t
119 {
120   MMT_NONE,
121   MMT_EXTRACT,
122   MMT_GOP_START,
123 };
124
125 //
126 class CommandOptions
127 {
128   CommandOptions();
129
130 public:
131   MajorMode_t mode;
132   bool   error_flag;     // true if the given options are in error or not complete
133   bool   key_flag;       // true if an encryption key was given
134   bool   read_hmac;      // true if HMAC values are to be validated
135   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
136   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
137   bool   verbose_flag;   // true if the verbose option was selected
138   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
139   bool   no_write_flag;  // true if no output files are to be written
140   bool   version_flag;   // true if the version display option was selected
141   bool   help_flag;      // true if the help display option was selected
142   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
143   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
144   ui32_t start_frame;    // frame number to begin processing
145   ui32_t duration;       // number of frames to be processed
146   bool   duration_flag;  // true if duration argument given
147   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
148   ui32_t picture_rate;   // fps of picture when wrapping PCM
149   ui32_t fb_size;        // size of picture frame buffer
150   const char* file_prefix; // filename pre for files written by the extract mode
151   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
152   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
153   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
154   const char* input_filename;
155   std::string prefix_buffer;
156
157   //
158   Rational PictureRate()
159   {
160     if ( picture_rate == 23 ) return EditRate_23_98;
161     if ( picture_rate == 24 ) return EditRate_24;
162     if ( picture_rate == 25 ) return EditRate_25;
163     if ( picture_rate == 30 ) return EditRate_30;
164     if ( picture_rate == 48 ) return EditRate_48;
165     if ( picture_rate == 50 ) return EditRate_50;
166     if ( picture_rate == 60 ) return EditRate_60;
167     if ( picture_rate == 96 ) return EditRate_96;
168     if ( picture_rate == 100 ) return EditRate_100;
169     if ( picture_rate == 120 ) return EditRate_120;
170     return EditRate_24;
171   }
172
173   //
174   CommandOptions(int argc, const char** argv) :
175     mode(MMT_EXTRACT), error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
176     mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
177     version_flag(false), help_flag(false), stereo_image_flag(false), number_width(6),
178     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
179     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
180     channel_fmt(PCM::CF_NONE), input_filename(0)
181   {
182     memset(key_value, 0, KeyLen);
183     memset(key_id_value, 0, UUIDlen);
184
185     for ( int i = 1; i < argc; ++i )
186       {
187
188         if ( (strcmp( argv[i], "-help") == 0) )
189           {
190             help_flag = true;
191             continue;
192           }
193          
194         if ( argv[i][0] == '-'
195              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
196              && argv[i][2] == 0 )
197           {
198             switch ( argv[i][1] )
199               {
200               case '1': mono_wav = true; break;
201               case '2': split_wav = true; break;
202               case '3': stereo_image_flag = true; break;
203
204               case 'b':
205                 TEST_EXTRA_ARG(i, 'b');
206                 fb_size = abs(atoi(argv[i]));
207
208                 if ( verbose_flag )
209                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
210
211                 break;
212
213               case 'd':
214                 TEST_EXTRA_ARG(i, 'd');
215                 duration_flag = true;
216                 duration = abs(atoi(argv[i]));
217                 break;
218
219               case 'f':
220                 TEST_EXTRA_ARG(i, 'f');
221                 start_frame = abs(atoi(argv[i]));
222                 break;
223
224               case 'G': mode = MMT_GOP_START; break;
225               case 'h': help_flag = true; break;
226
227               case 'm': read_hmac = true; break;
228
229               case 'p':
230                 TEST_EXTRA_ARG(i, 'p');
231                 picture_rate = abs(atoi(argv[i]));
232                 break;
233
234               case 's':
235                 TEST_EXTRA_ARG(i, 's');
236                 fb_dump_size = abs(atoi(argv[i]));
237                 break;
238
239               case 'V': version_flag = true; break;
240               case 'v': verbose_flag = true; break;
241               case 'W': no_write_flag = true; break;
242
243               case 'w':
244                 TEST_EXTRA_ARG(i, 'w');
245                 number_width = abs(atoi(argv[i]));
246                 break;
247
248               case 'Z': j2c_pedantic = false; break;
249               case 'z': j2c_pedantic = true; break;
250
251               default:
252                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
253                 return;
254               }
255           }
256         else
257           {
258             if ( argv[i][0] != '-' )
259               {
260                 if ( input_filename == 0 )
261                   {
262                     input_filename = argv[i];
263                   }
264                 else if ( file_prefix == 0 )
265                   {
266                     file_prefix = argv[i];
267                   }
268               }
269             else
270               {
271                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
272                 return;
273               }
274           }
275       }
276
277     if ( help_flag || version_flag )
278       return;
279     
280     if ( (  mode == MMT_EXTRACT || mode == MMT_GOP_START ) && input_filename == 0 )
281       {
282         fputs("Option requires at least one filename argument.\n", stderr);
283         return;
284       }
285
286     if ( mode == MMT_EXTRACT  && file_prefix == 0 )
287       {
288         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
289         file_prefix = prefix_buffer.c_str();
290       }
291
292     error_flag = false;
293   }
294 };
295
296 //------------------------------------------------------------------------------------------
297 // MPEG2 essence
298
299 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
300 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
301 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
302 //
303 Result_t
304 read_MPEG2_file(CommandOptions& Options)
305 {
306   AESDecContext*     Context = 0;
307   HMACContext*       HMAC = 0;
308   MPEG2::MXFReader   Reader;
309   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
310   Kumu::FileWriter   OutFile;
311   ui32_t             frame_count = 0;
312
313   Result_t result = Reader.OpenRead(Options.input_filename);
314
315   if ( ASDCP_SUCCESS(result) )
316     {
317       MPEG2::VideoDescriptor VDesc;
318       Reader.FillVideoDescriptor(VDesc);
319       frame_count = VDesc.ContainerDuration;
320
321       if ( Options.verbose_flag )
322         {
323           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
324           MPEG2::VideoDescriptorDump(VDesc);
325         }
326     }
327
328   if ( ASDCP_SUCCESS(result) )
329     {
330       char filename[256];
331       snprintf(filename, 256, "%s.ves", Options.file_prefix);
332       result = OutFile.OpenWrite(filename);
333     }
334
335   if ( ASDCP_SUCCESS(result) && Options.key_flag )
336     {
337       Context = new AESDecContext;
338       result = Context->InitKey(Options.key_value);
339
340       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
341         {
342           WriterInfo Info;
343           Reader.FillWriterInfo(Info);
344
345           if ( Info.UsesHMAC )
346             {
347               HMAC = new HMACContext;
348               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
349             }
350           else
351             {
352               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
353             }
354         }
355     }
356
357   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
358   if ( last_frame > frame_count )
359     last_frame = frame_count;
360
361   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
362     {
363       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
364
365       if ( ASDCP_SUCCESS(result) )
366         {
367           if ( Options.verbose_flag )
368             FrameBuffer.Dump(stderr, Options.fb_dump_size);
369
370           ui32_t write_count = 0;
371           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
372         }
373     }
374
375   return result;
376 }
377
378
379 //
380 Result_t
381 gop_start_test(CommandOptions& Options)
382 {
383   using namespace ASDCP::MPEG2;
384
385   MXFReader   Reader;
386   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
387   ui32_t      frame_count = 0;
388
389   Result_t result = Reader.OpenRead(Options.input_filename);
390
391   if ( ASDCP_SUCCESS(result) )
392     {
393       MPEG2::VideoDescriptor VDesc;
394       Reader.FillVideoDescriptor(VDesc);
395       frame_count = VDesc.ContainerDuration;
396
397       if ( Options.verbose_flag )
398         {
399           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
400           MPEG2::VideoDescriptorDump(VDesc);
401         }
402     }
403
404   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
405   if ( last_frame > frame_count )
406     last_frame = frame_count;
407
408   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
409     {
410       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
411
412       if ( ASDCP_SUCCESS(result) )
413         {
414           if ( Options.verbose_flag )
415             FrameBuffer.Dump(stderr, Options.fb_dump_size);
416
417           if ( FrameBuffer.FrameType() != FRAME_I )
418             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
419
420           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
421         }
422     }
423
424   return result;
425 }
426
427 //------------------------------------------------------------------------------------------
428 // JPEG 2000 essence
429
430
431 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
432 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
433 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
434 Result_t
435 read_JP2K_S_file(CommandOptions& Options)
436 {
437   AESDecContext*     Context = 0;
438   HMACContext*       HMAC = 0;
439   JP2K::MXFSReader    Reader;
440   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
441   ui32_t             frame_count = 0;
442
443   Result_t result = Reader.OpenRead(Options.input_filename);
444
445   if ( ASDCP_SUCCESS(result) )
446     {
447       JP2K::PictureDescriptor PDesc;
448       Reader.FillPictureDescriptor(PDesc);
449
450       frame_count = PDesc.ContainerDuration;
451
452       if ( Options.verbose_flag )
453         {
454           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
455           JP2K::PictureDescriptorDump(PDesc);
456         }
457     }
458
459   if ( ASDCP_SUCCESS(result) && Options.key_flag )
460     {
461       Context = new AESDecContext;
462       result = Context->InitKey(Options.key_value);
463
464       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
465         {
466           WriterInfo Info;
467           Reader.FillWriterInfo(Info);
468
469           if ( Info.UsesHMAC )
470             {
471               HMAC = new HMACContext;
472               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
473             }
474           else
475             {
476               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
477             }
478         }
479     }
480
481   const int filename_max = 1024;
482   char filename[filename_max];
483   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
484   if ( last_frame > frame_count )
485     last_frame = frame_count;
486
487   char left_format[64];  char right_format[64];
488   snprintf(left_format,  64, "%%s%%0%duL.j2c", Options.number_width);
489   snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
490
491   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
492     {
493       result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
494
495       if ( ASDCP_SUCCESS(result) )
496         {
497           Kumu::FileWriter OutFile;
498           ui32_t write_count;
499           snprintf(filename, filename_max, left_format, Options.file_prefix, i);
500           result = OutFile.OpenWrite(filename);
501
502           if ( ASDCP_SUCCESS(result) )
503             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
504
505           if ( Options.verbose_flag )
506             FrameBuffer.Dump(stderr, Options.fb_dump_size);
507         }
508
509       if ( ASDCP_SUCCESS(result) )
510         result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
511
512       if ( ASDCP_SUCCESS(result) )
513         {
514           Kumu::FileWriter OutFile;
515           ui32_t write_count;
516           snprintf(filename, filename_max, right_format, Options.file_prefix, i);
517           result = OutFile.OpenWrite(filename);
518
519           if ( ASDCP_SUCCESS(result) )
520             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
521         }
522     }
523
524   return result;
525 }
526
527 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
528 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
529 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
530 //
531 Result_t
532 read_JP2K_file(CommandOptions& Options)
533 {
534   AESDecContext*     Context = 0;
535   HMACContext*       HMAC = 0;
536   JP2K::MXFReader    Reader;
537   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
538   ui32_t             frame_count = 0;
539
540   Result_t result = Reader.OpenRead(Options.input_filename);
541
542   if ( ASDCP_SUCCESS(result) )
543     {
544       JP2K::PictureDescriptor PDesc;
545       Reader.FillPictureDescriptor(PDesc);
546
547       frame_count = PDesc.ContainerDuration;
548
549       if ( Options.verbose_flag )
550         {
551           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
552           JP2K::PictureDescriptorDump(PDesc);
553         }
554     }
555
556   if ( ASDCP_SUCCESS(result) && Options.key_flag )
557     {
558       Context = new AESDecContext;
559       result = Context->InitKey(Options.key_value);
560
561       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
562         {
563           WriterInfo Info;
564           Reader.FillWriterInfo(Info);
565
566           if ( Info.UsesHMAC )
567             {
568               HMAC = new HMACContext;
569               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
570             }
571           else
572             {
573               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
574             }
575         }
576     }
577
578   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
579   if ( last_frame > frame_count )
580     last_frame = frame_count;
581
582   char name_format[64];
583   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
584
585   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
586     {
587       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
588
589       if ( ASDCP_SUCCESS(result) )
590         {
591           Kumu::FileWriter OutFile;
592           char filename[256];
593           ui32_t write_count;
594           snprintf(filename, 256, name_format, Options.file_prefix, i);
595           result = OutFile.OpenWrite(filename);
596
597           if ( ASDCP_SUCCESS(result) )
598             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
599
600           if ( Options.verbose_flag )
601             FrameBuffer.Dump(stderr, Options.fb_dump_size);
602         }
603     }
604
605   return result;
606 }
607
608 //------------------------------------------------------------------------------------------
609 // PCM essence
610
611 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
612 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
613 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
614 //
615 Result_t
616 read_PCM_file(CommandOptions& Options)
617 {
618   AESDecContext*     Context = 0;
619   HMACContext*       HMAC = 0;
620   PCM::MXFReader     Reader;
621   PCM::FrameBuffer   FrameBuffer;
622   WavFileWriter      OutWave;
623   PCM::AudioDescriptor ADesc;
624   ui32_t last_frame = 0;
625
626   Result_t result = Reader.OpenRead(Options.input_filename);
627
628   if ( ASDCP_SUCCESS(result) )
629     {
630       Reader.FillAudioDescriptor(ADesc);
631
632       if ( ADesc.EditRate != EditRate_23_98
633            && ADesc.EditRate != EditRate_24
634            && ADesc.EditRate != EditRate_25
635            && ADesc.EditRate != EditRate_30
636            && ADesc.EditRate != EditRate_48
637            && ADesc.EditRate != EditRate_50
638            && ADesc.EditRate != EditRate_60 )
639         ADesc.EditRate = Options.PictureRate();
640
641       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
642
643       if ( Options.verbose_flag )
644         PCM::AudioDescriptorDump(ADesc);
645     }
646
647   if ( ASDCP_SUCCESS(result) )
648     {
649       last_frame = ADesc.ContainerDuration;
650
651       if ( Options.duration > 0 && Options.duration < last_frame )
652         last_frame = Options.duration;
653
654       if ( Options.start_frame > 0 )
655         {
656           if ( Options.start_frame > ADesc.ContainerDuration )
657             {
658               fprintf(stderr, "Start value greater than file duration.\n");
659               return RESULT_FAIL;
660             }
661
662           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
663         }
664
665       ADesc.ContainerDuration = last_frame - Options.start_frame;
666       OutWave.OpenWrite(ADesc, Options.file_prefix,
667                         ( Options.split_wav ? WavFileWriter::ST_STEREO : 
668                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
669     }
670
671   if ( ASDCP_SUCCESS(result) && Options.key_flag )
672     {
673       Context = new AESDecContext;
674       result = Context->InitKey(Options.key_value);
675
676       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
677         {
678           WriterInfo Info;
679           Reader.FillWriterInfo(Info);
680
681           if ( Info.UsesHMAC )
682             {
683               HMAC = new HMACContext;
684               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
685             }
686           else
687             {
688               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
689             }
690         }
691     }
692
693   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
694     {
695       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
696
697       if ( ASDCP_SUCCESS(result) )
698         {
699           if ( Options.verbose_flag )
700             FrameBuffer.Dump(stderr, Options.fb_dump_size);
701
702           result = OutWave.WriteFrame(FrameBuffer);
703         }
704     }
705
706   return result;
707 }
708
709
710 //------------------------------------------------------------------------------------------
711 // TimedText essence
712
713 // Read one or more timed text streams from a plaintext ASDCP file
714 // Read one or more timed text streams from a ciphertext ASDCP file
715 // Read one or more timed text streams from a ciphertext ASDCP file
716 //
717 Result_t
718 read_timed_text_file(CommandOptions& Options)
719 {
720   AESDecContext*     Context = 0;
721   HMACContext*       HMAC = 0;
722   TimedText::MXFReader     Reader;
723   TimedText::FrameBuffer   FrameBuffer;
724   TimedText::TimedTextDescriptor TDesc;
725
726   Result_t result = Reader.OpenRead(Options.input_filename);
727
728   if ( ASDCP_SUCCESS(result) )
729     {
730       Reader.FillTimedTextDescriptor(TDesc);
731       FrameBuffer.Capacity(Options.fb_size);
732
733       if ( Options.verbose_flag )
734         TimedText::DescriptorDump(TDesc);
735     }
736
737   if ( ASDCP_SUCCESS(result) && Options.key_flag )
738     {
739       Context = new AESDecContext;
740       result = Context->InitKey(Options.key_value);
741
742       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
743         {
744           WriterInfo Info;
745           Reader.FillWriterInfo(Info);
746
747           if ( Info.UsesHMAC )
748             {
749               HMAC = new HMACContext;
750               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
751             }
752           else
753             {
754               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
755             }
756         }
757     }
758
759   if ( ASDCP_FAILURE(result) )
760     return result;
761
762   std::string XMLDoc;
763   std::string out_path = Kumu::PathDirname(Options.file_prefix);
764   ui32_t write_count;
765   char buf[64];
766   TimedText::ResourceList_t::const_iterator ri;
767
768   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
769
770   if ( ASDCP_SUCCESS(result) )
771     {
772       Kumu::FileWriter Writer;
773       result = Writer.OpenWrite(Options.file_prefix);
774
775       if ( ASDCP_SUCCESS(result) )
776         result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
777     }
778
779   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
780     {
781       result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
782
783       if ( ASDCP_SUCCESS(result) )
784         {
785           Kumu::FileWriter Writer;
786           result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
787
788           if ( ASDCP_SUCCESS(result) )
789             {
790               if ( Options.verbose_flag )
791                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
792
793               result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
794             }
795         }
796     }
797
798   return result;
799 }
800
801 //
802 int
803 main(int argc, const char** argv)
804 {
805   Result_t result = RESULT_OK;
806   char     str_buf[64];
807   CommandOptions Options(argc, argv);
808
809   if ( Options.version_flag )
810     banner();
811
812   if ( Options.help_flag )
813     usage();
814
815   if ( Options.version_flag || Options.help_flag )
816     return 0;
817
818   if ( Options.error_flag )
819     {
820       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
821       return 3;
822     }
823
824   if ( Options.mode == MMT_GOP_START )
825     {
826       result = gop_start_test(Options);
827     }
828   else if ( Options.mode == MMT_EXTRACT )
829     {
830       EssenceType_t EssenceType;
831       result = ASDCP::EssenceType(Options.input_filename, EssenceType);
832
833       if ( ASDCP_SUCCESS(result) )
834         {
835           switch ( EssenceType )
836             {
837             case ESS_MPEG2_VES:
838               result = read_MPEG2_file(Options);
839               break;
840
841             case ESS_JPEG_2000:
842               if ( Options.stereo_image_flag )
843                 result = read_JP2K_S_file(Options);
844               else
845                 result = read_JP2K_file(Options);
846               break;
847
848             case ESS_JPEG_2000_S:
849               result = read_JP2K_S_file(Options);
850               break;
851
852             case ESS_PCM_24b_48k:
853             case ESS_PCM_24b_96k:
854               result = read_PCM_file(Options);
855               break;
856
857             case ESS_TIMED_TEXT:
858               result = read_timed_text_file(Options);
859               break;
860
861             default:
862               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.input_filename);
863               return 5;
864             }
865         }
866     }
867   else
868     {
869       fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
870       return 6;
871     }
872
873   if ( ASDCP_FAILURE(result) )
874     {
875       fputs("Program stopped on error.\n", stderr);
876
877       if ( result == RESULT_SFORMAT )
878         {
879           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
880         }
881       else if ( result != RESULT_FAIL )
882         {
883           fputs(result, stderr);
884           fputc('\n', stderr);
885         }
886
887       return 1;
888     }
889
890   return 0;
891 }
892
893
894 //
895 // end asdcp-unwrap.cpp
896 //