5a0ae685e5f3797573e15ac4f4dc13c3b0c54b24
[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 <WavFileWriter.h>
42
43 namespace ASDCP {
44   Result_t MD_to_PCM_ADesc(ASDCP::MXF::WaveAudioDescriptor* ADescObj, ASDCP::PCM::AudioDescriptor& ADesc);
45 }
46
47 using namespace ASDCP;
48
49 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
50
51 //------------------------------------------------------------------------------------------
52 //
53 // command line option parser class
54
55 static const char* PROGRAM_NAME = "as-02-unwrap";  // program name for messages
56
57 // Increment the iterator, test for an additional non-option command line argument.
58 // Causes the caller to return if there are no remaining arguments or if the next
59 // argument begins with '-'.
60 #define TEST_EXTRA_ARG(i,c)                                             \
61   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
62     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
63     return;                                                             \
64   }
65
66 //
67 void
68 banner(FILE* stream = stdout)
69 {
70   fprintf(stream, "\n\
71 %s (asdcplib %s)\n\n\
72 Copyright (c) 2011-2018, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
73 asdcplib may be copied only under the terms of the license found at\n\
74 the top of every file in the asdcplib distribution kit.\n\n\
75 Specify the -h (help) option for further information about %s\n\n",
76           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
77 }
78
79 //
80 void
81 usage(FILE* stream = stdout)
82 {
83   fprintf(stream, "\
84 USAGE: %s [-h|-help] [-V]\n\
85 \n\
86        %s [-1|-2] [-b <buffer-size>] [-d <duration>]\n\
87        [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <size>] [-v] [-W]\n\
88        [-w] <input-file> [<file-prefix>]\n\n",
89           PROGRAM_NAME, PROGRAM_NAME);
90
91   fprintf(stream, "\
92 Options:\n\
93   -1                - Split Wave essence to mono WAV files during extract.\n\
94                       Default is multichannel WAV\n\
95   -2                - Split Wave essence to stereo WAV files during extract.\n\
96                       Default is multichannel WAV\n\
97   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
98                       Defaults to 4,194,304 (4MB)\n\
99   -d <duration>     - Number of frames to process, default all\n\
100   -f <start-frame>  - Starting frame number, default 0\n\
101   -g <SID>          - Extract the Generic Stream Partition payload\n\
102   -h | -help        - Show help\n\
103   -k <key-string>   - Use key for ciphertext operations\n\
104   -m                - verify HMAC values when reading\n\
105   -s <size>         - Number of bytes to dump to output when -v is given\n\
106   -V                - Show version information\n\
107   -v                - Verbose, prints informative messages to stderr\n\
108   -W                - Read input file only, do not write destination file\n\
109   -w <width>        - Width of numeric element in a series of frame file names\n\
110                       (default 6)\n\
111   -z                - Fail if j2c inputs have unequal parameters (default)\n\
112   -Z                - Ignore unequal parameters in j2c inputs\n\
113 \n\
114   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
115          o All option arguments must be separated from the option by whitespace.\n\n");
116 }
117
118 //
119 class CommandOptions
120 {
121   CommandOptions();
122
123 public:
124   bool   error_flag;     // true if the given options are in error or not complete
125   bool   key_flag;       // true if an encryption key was given
126   bool   read_hmac;      // true if HMAC values are to be validated
127   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
128   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
129   bool   verbose_flag;   // true if the verbose option was selected
130   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
131   bool   no_write_flag;  // true if no output files are to be written
132   bool   version_flag;   // true if the version display option was selected
133   bool   help_flag;      // true if the help display option was selected
134   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
135   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
136   ui32_t start_frame;    // frame number to begin processing
137   ui32_t duration;       // number of frames to be processed
138   bool   duration_flag;  // true if duration argument given
139   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
140   ui32_t picture_rate;   // fps of picture when wrapping PCM
141   ui32_t fb_size;        // size of picture frame buffer
142   Rational edit_rate;    // frame buffer size for reading clip-wrapped PCM
143   const char* file_prefix; // filename pre for files written by the extract mode
144   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
145   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
146   const char* input_filename;
147   const char* extension;
148   i32_t g_stream_sid;     // Stream ID of a generic stream partition payload to be extracted
149   std::string prefix_buffer;
150
151   //
152   CommandOptions(int argc, const char** argv) :
153     error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
154     mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
155     version_flag(false), help_flag(false), number_width(6),
156     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
157     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
158     input_filename(0), extension(0), g_stream_sid(0)
159   {
160     memset(key_value, 0, KeyLen);
161     memset(key_id_value, 0, UUIDlen);
162
163     for ( int i = 1; i < argc; ++i )
164       {
165
166         if ( (strcmp( argv[i], "-help") == 0) )
167           {
168             help_flag = true;
169             continue;
170           }
171          
172         if ( argv[i][0] == '-'
173              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
174              && argv[i][2] == 0 )
175           {
176             switch ( argv[i][1] )
177               {
178               case '1': mono_wav = true; break;
179               case '2': split_wav = true; break;
180
181               case 'b':
182                 TEST_EXTRA_ARG(i, 'b');
183                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
184
185                 if ( verbose_flag )
186                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
187
188                 break;
189
190               case 'd':
191                 TEST_EXTRA_ARG(i, 'd');
192                 duration_flag = true;
193                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
194                 break;
195
196               case 'e':
197                 TEST_EXTRA_ARG(i, 'e');
198                 extension = argv[i];
199                 break;
200
201               case 'f':
202                 TEST_EXTRA_ARG(i, 'f');
203                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
204                 break;
205
206               case 'g':
207                 TEST_EXTRA_ARG(i, 'g');
208                 g_stream_sid = strtol(argv[i], 0, 10);
209                 break;
210                   
211               case 'h': help_flag = true; break;
212               case 'm': read_hmac = true; break;
213
214               case 'p':
215                 TEST_EXTRA_ARG(i, 'p');
216                 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
217                 break;
218
219               case 's':
220                 TEST_EXTRA_ARG(i, 's');
221                 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
222                 break;
223
224               case 'V': version_flag = true; break;
225               case 'v': verbose_flag = true; break;
226               case 'W': no_write_flag = true; break;
227
228               case 'w':
229                 TEST_EXTRA_ARG(i, 'w');
230                 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
231                 break;
232
233               case 'Z': j2c_pedantic = false; break;
234               case 'z': j2c_pedantic = true; break;
235
236               default:
237                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
238                 return;
239               }
240           }
241         else
242           {
243             if ( argv[i][0] != '-' )
244               {
245                 if ( input_filename == 0 )
246                   {
247                     input_filename = argv[i];
248                   }
249                 else if ( file_prefix == 0 )
250                   {
251                     file_prefix = argv[i];
252                   }
253               }
254             else
255               {
256                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
257                 return;
258               }
259           }
260       }
261
262     if ( help_flag || version_flag )
263       return;
264     
265     if ( input_filename == 0 )
266       {
267         fputs("At least one filename argument is required.\n", stderr);
268         return;
269       }
270
271     if ( file_prefix == 0 )
272       {
273         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
274         file_prefix = prefix_buffer.c_str();
275       }
276
277     error_flag = false;
278   }
279 };
280
281
282 //------------------------------------------------------------------------------------------
283 // JPEG 2000 essence
284
285
286 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
287 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
288 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
289 //
290 Result_t
291 read_JP2K_file(CommandOptions& Options)
292 {
293   AESDecContext*     Context = 0;
294   HMACContext*       HMAC = 0;
295   AS_02::JP2K::MXFReader    Reader;
296   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
297   ui32_t             frame_count = 0;
298
299   Result_t result = Reader.OpenRead(Options.input_filename);
300
301   if ( ASDCP_SUCCESS(result) )
302     {
303       if ( Options.verbose_flag )
304         {
305           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
306         }
307
308       ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
309       ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
310
311       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
312                                                      reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
313
314       if ( KM_SUCCESS(result) )
315         {
316           assert(rgba_descriptor);
317           frame_count = (ui32_t)rgba_descriptor->ContainerDuration;
318
319           if ( Options.verbose_flag )
320             {
321               rgba_descriptor->Dump();
322             }
323         }
324       else
325         {
326           result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
327                                                          reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
328
329           if ( KM_SUCCESS(result) )
330             {
331               assert(cdci_descriptor);
332               frame_count = (ui32_t)cdci_descriptor->ContainerDuration;
333
334               if ( Options.verbose_flag )
335                 {
336                   cdci_descriptor->Dump();
337                 }
338             }
339           else
340             {
341               fprintf(stderr, "File does not contain an essence descriptor.\n");
342               frame_count = Reader.AS02IndexReader().GetDuration();
343             }
344         }
345
346       if ( frame_count == 0 )
347         {
348           frame_count = Reader.AS02IndexReader().GetDuration();
349         }
350
351       if ( frame_count == 0 )
352         {
353           fprintf(stderr, "Unable to determine file duration.\n");
354           return RESULT_FAIL;
355         }
356     }
357
358   if ( ASDCP_SUCCESS(result) && Options.key_flag )
359     {
360       Context = new AESDecContext;
361       result = Context->InitKey(Options.key_value);
362
363       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
364         {
365           WriterInfo Info;
366           Reader.FillWriterInfo(Info);
367
368           if ( Info.UsesHMAC )
369             {
370               HMAC = new HMACContext;
371               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
372             }
373           else
374             {
375               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
376             }
377         }
378     }
379
380   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
381   if ( last_frame > frame_count )
382     last_frame = frame_count;
383
384   char name_format[64];
385   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
386
387   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
388     {
389       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
390
391       char filename[1024];
392       snprintf(filename, 1024, name_format, Options.file_prefix, i);
393
394       if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
395         {
396           printf("Frame %d, %d bytes", i, FrameBuffer.Size());
397
398           if ( ! Options.no_write_flag )
399             {
400               printf(" -> %s", filename);
401             }
402
403           printf("\n");
404         }
405
406       if ( ASDCP_SUCCESS(result)  && ( ! Options.no_write_flag ) )
407         {
408           Kumu::FileWriter OutFile;
409           ui32_t write_count;
410           result = OutFile.OpenWrite(filename);
411
412           if ( ASDCP_SUCCESS(result) )
413             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
414
415           if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
416             {
417               FrameBuffer.Dump(stderr, Options.fb_dump_size);
418             }
419         }
420     }
421
422   return result;
423 }
424
425 //------------------------------------------------------------------------------------------
426 // PCM essence
427
428 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
429 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
430 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
431 //
432 Result_t
433 read_PCM_file(CommandOptions& Options)
434 {
435   AESDecContext*     Context = 0;
436   HMACContext*       HMAC = 0;
437   AS_02::PCM::MXFReader     Reader;
438   PCM::FrameBuffer   FrameBuffer;
439   WavFileWriter      OutWave;
440   ui32_t last_frame = 0;
441   ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0;
442
443   if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI
444     {
445       Options.edit_rate = EditRate_24;
446     }
447
448   Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
449
450   if ( KM_SUCCESS(result) )
451     {
452       if ( Options.verbose_flag )
453         {
454           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
455         }
456       
457       ASDCP::MXF::InterchangeObject* tmp_obj = 0;
458
459       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), &tmp_obj);
460
461       if ( KM_SUCCESS(result) )
462         {
463           wave_descriptor = dynamic_cast<ASDCP::MXF::WaveAudioDescriptor*>(tmp_obj);
464
465           if ( wave_descriptor == 0 )
466             {
467               fprintf(stderr, "File does not contain an essence descriptor.\n");
468               return RESULT_FAIL;
469             }
470       
471           if ( Options.verbose_flag )
472             {
473               wave_descriptor->Dump();
474             }
475
476           if ( wave_descriptor->ContainerDuration.get() == 0 )
477             {
478               fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
479               last_frame = Reader.AS02IndexReader().GetDuration();
480             }
481           else
482             {
483               last_frame = (ui32_t)wave_descriptor->ContainerDuration;
484             }
485
486           if ( last_frame == 0 )
487             {
488               fprintf(stderr, "ContainerDuration not set in index, attempting to use Duration from SourceClip.\n");
489               result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_SourceClip), &tmp_obj);
490               if ( KM_SUCCESS(result))
491                 {
492                   ASDCP::MXF::SourceClip *sourceClip = dynamic_cast<ASDCP::MXF::SourceClip*>(tmp_obj);
493                   if ( ! sourceClip->Duration.empty() )
494                     {
495                       last_frame = (ui32_t)sourceClip->Duration;
496                     }
497                 }
498             }
499
500           if ( last_frame == 0 )
501             {
502               fprintf(stderr, "Unable to determine file duration.\n");
503               return RESULT_FAIL;
504             }
505
506           assert(wave_descriptor);
507           FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
508           last_frame = AS_02::MXF::CalcFramesFromDurationInSamples(last_frame, *wave_descriptor, Options.edit_rate);
509         }
510     }
511
512   if ( ASDCP_SUCCESS(result) )
513     {
514       if ( Options.duration > 0 && Options.duration < last_frame )
515         last_frame = Options.duration;
516
517       if ( Options.start_frame > 0 )
518         {
519           if ( Options.start_frame > last_frame )
520             {
521               fprintf(stderr, "Start value greater than file duration.\n");
522               return RESULT_FAIL;
523             }
524
525           last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame);
526         }
527
528       last_frame = last_frame - Options.start_frame;
529
530       PCM::AudioDescriptor ADesc;
531
532       result = MD_to_PCM_ADesc(wave_descriptor, ADesc);
533
534       if ( ASDCP_SUCCESS(result) )
535         {
536           ADesc.ContainerDuration = last_frame;
537           ADesc.EditRate = Options.edit_rate;
538
539           result = OutWave.OpenWrite(ADesc, Options.file_prefix,
540                                      ( Options.split_wav ? WavFileWriter::ST_STEREO : 
541                                        ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
542         }
543     }
544
545   if ( ASDCP_SUCCESS(result) && Options.key_flag )
546     {
547       Context = new AESDecContext;
548       result = Context->InitKey(Options.key_value);
549
550       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
551         {
552           WriterInfo Info;
553           Reader.FillWriterInfo(Info);
554
555           if ( Info.UsesHMAC )
556             {
557               HMAC = new HMACContext;
558               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
559             }
560           else
561             {
562               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
563             }
564         }
565     }
566
567   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
568     {
569       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
570
571       if ( ASDCP_SUCCESS(result) )
572         {
573           if ( Options.verbose_flag )
574             {
575               FrameBuffer.FrameNumber(i);
576               FrameBuffer.Dump(stderr, Options.fb_dump_size);
577             }
578
579           if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
580             {
581               fprintf(stderr, "Last frame is incomplete, padding with zeros.\n");
582               // actually, it has already been zeroed for us, we just need to recognize the appropriate size
583               FrameBuffer.Size(FrameBuffer.Capacity());
584             }
585
586           result = OutWave.WriteFrame(FrameBuffer);
587         }
588     }
589
590   return result;
591 }
592
593
594 //------------------------------------------------------------------------------------------
595 // TimedText essence
596
597 // Read one or more timed text streams from a plaintext AS-02 file
598 //
599 Result_t
600 read_timed_text_file(CommandOptions& Options)
601 {
602   AESDecContext*     Context = 0;
603   HMACContext*       HMAC = 0;
604   AS_02::TimedText::MXFReader     Reader;
605   TimedText::FrameBuffer   FrameBuffer(Options.fb_size);
606   //ASDCP::TimedText::FrameBuffer   FrameBuffer(Options.fb_size);
607   AS_02::TimedText::TimedTextDescriptor TDesc;
608   ASDCP::MXF::TimedTextDescriptor *tt_descriptor = 0;
609
610   Result_t result = Reader.OpenRead(Options.input_filename);
611
612   if ( ASDCP_SUCCESS(result) )
613     {
614       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_TimedTextDescriptor),
615                                                      reinterpret_cast<MXF::InterchangeObject**>(&tt_descriptor));
616     if ( Options.verbose_flag ) {
617         tt_descriptor->Dump();
618     }
619
620
621   if ( ASDCP_FAILURE(result) )
622     return result;
623
624   std::string XMLDoc;
625   std::string out_path = Kumu::PathDirname(Options.file_prefix);
626   ui32_t write_count;
627   char buf[64];
628   TimedText::ResourceList_t::const_iterator ri;
629
630   result = Reader.ReadTimedTextResource(XMLDoc);
631
632   if ( ASDCP_SUCCESS(result) )
633     {
634       Reader.FillTimedTextDescriptor(TDesc);
635       FrameBuffer.Capacity(Options.fb_size);
636
637       if ( Options.verbose_flag )
638         TimedText::DescriptorDump(TDesc);
639     }
640
641   if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
642     {
643       Kumu::FileWriter Writer;
644       result = Writer.OpenWrite(Options.file_prefix);
645
646       if ( ASDCP_SUCCESS(result) )
647         result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
648     }
649
650   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
651     {
652       result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
653
654       if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
655         {
656           Kumu::FileWriter Writer;
657           if (out_path != "") {
658                   result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
659           } else {
660                   // Workaround for a bug in Kumu::PathJoin
661                   result = Writer.OpenWrite(Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64));
662           }
663
664           if ( ASDCP_SUCCESS(result) )
665             result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
666
667               if ( Options.verbose_flag )
668                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
669         }
670     }
671     }
672   return result;
673 }
674
675 //
676 Result_t
677 read_isxd_file(CommandOptions& Options)
678 {
679   AESDecContext*     Context = 0;
680   HMACContext*       HMAC = 0;
681   AS_02::ISXD::MXFReader    Reader;
682   ASDCP::FrameBuffer  FrameBuffer;
683   ui32_t             frame_count = 0;
684
685   Result_t result = Reader.OpenRead(Options.input_filename);
686
687   if ( ASDCP_SUCCESS(result) )
688     {
689       result = FrameBuffer.Capacity(Options.fb_size);
690     }
691
692   if ( ASDCP_SUCCESS(result) )
693     {
694       std::list<MXF::InterchangeObject*> object_list;
695       Reader.OP1aHeader().GetMDObjectsByType(DefaultSMPTEDict().ul(MDD_GenericStreamTextBasedSet), object_list);
696
697       std::list<MXF::InterchangeObject*>::iterator i;
698       for ( i = object_list.begin(); i != object_list.end(); ++i )
699         {
700           MXF::GenericStreamTextBasedSet *text_object = dynamic_cast<MXF::GenericStreamTextBasedSet*>(*i);
701           assert(text_object);
702           text_object->Dump(stderr);
703         }
704     }
705
706   if ( ASDCP_SUCCESS(result) && Options.key_flag )
707     {
708       Context = new AESDecContext;
709       result = Context->InitKey(Options.key_value);
710
711       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
712         {
713           WriterInfo Info;
714           Reader.FillWriterInfo(Info);
715
716           if ( Info.UsesHMAC )
717             {
718               HMAC = new HMACContext;
719               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
720             }
721           else
722             {
723               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
724             }
725         }
726     }
727
728   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
729   if ( last_frame > frame_count )
730     last_frame = frame_count;
731
732   char name_format[64];
733   snprintf(name_format,  64, "%%s%%0%du.%s", Options.number_width, Options.extension);
734
735   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
736     {
737       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
738
739       if ( ASDCP_SUCCESS(result) )
740         {
741           if ( ! Options.no_write_flag )
742             {
743               Kumu::FileWriter OutFile;
744               char filename[256];
745               ui32_t write_count;
746               snprintf(filename, 256, name_format, Options.file_prefix, i);
747               result = OutFile.OpenWrite(filename);
748
749               if ( ASDCP_SUCCESS(result) )
750                 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
751             }
752         }
753     }
754
755   return result;
756 }
757
758 Result_t
759 extract_generic_stream_partition_payload(const std::string& in_filename, const ui32_t sid, const std::string& out_filename)
760 {
761   ASDCP::FrameBuffer payload;
762   AS_02::ISXD::MXFReader reader;
763
764   Result_t result = reader.OpenRead(in_filename);
765           
766   if ( KM_SUCCESS(result) )
767     {
768       result = reader.ReadGenericStreamPartitionPayload(sid, payload);
769     }
770   
771   if ( KM_SUCCESS(result) )
772     {
773       Kumu::FileWriter writer;
774       ui32_t write_count = 0;
775       result = writer.OpenWrite(out_filename);
776       
777       if ( KM_SUCCESS(result) )
778         {
779           result = writer.Write(payload.RoData(), payload.Size(), &write_count);
780         }
781     }
782
783   return result;
784 }
785
786
787 //
788 int
789 main(int argc, const char** argv)
790 {
791   CommandOptions Options(argc, argv);
792
793   if ( Options.version_flag )
794     banner();
795
796   if ( Options.help_flag )
797     usage();
798
799   if ( Options.version_flag || Options.help_flag )
800     return 0;
801
802   if ( Options.error_flag )
803     {
804       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
805       return 3;
806     }
807
808   EssenceType_t EssenceType;
809   Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
810
811   if ( ASDCP_SUCCESS(result) )
812     {
813       switch ( EssenceType )
814         {
815         case ESS_AS02_JPEG_2000:
816           result = read_JP2K_file(Options);
817           break;
818
819         case ESS_AS02_PCM_24b_48k:
820         case ESS_AS02_PCM_24b_96k:
821           result = read_PCM_file(Options);
822           break;
823
824         case ESS_AS02_TIMED_TEXT:
825           result = read_timed_text_file(Options);
826           break;
827
828         case ESS_AS02_ISXD:
829           if ( Options.g_stream_sid == 0 )
830             {
831               result = read_isxd_file(Options);
832             }
833           else
834             {
835               result = extract_generic_stream_partition_payload(Options.input_filename,
836                                                                 Options.g_stream_sid,
837                                                                 Options.file_prefix);
838             }
839           break;
840
841         default:
842           fprintf(stderr, "%s: Unknown file type (%d), not AS-02 essence.\n", Options.input_filename, EssenceType);
843           return 5;
844         }
845     }
846
847   if ( ASDCP_FAILURE(result) )
848     {
849       fputs("Program stopped on error.\n", stderr);
850
851       if ( result != RESULT_FAIL )
852         {
853           fputs(result, stderr);
854           fputc('\n', stderr);
855         }
856
857       return 1;
858     }
859
860   return 0;
861 }
862
863
864 //
865 // end as-02-unwrap.cpp
866 //