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