newlines are valid inside elements!
[asdcplib.git] / src / as-02-unwrap.cpp
1 /*
2 Copyright (c) 2011-2014, 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-2014, 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, 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   -h | -help        - Show help\n\
102   -k <key-string>   - Use key for ciphertext operations\n\
103   -m                - verify HMAC values when reading\n\
104   -s <size>         - Number of bytes to dump to output when -v is given\n\
105   -V                - Show version information\n\
106   -v                - Verbose, prints informative messages to stderr\n\
107   -W                - Read input file only, do not write destination file\n\
108   -w <width>        - Width of numeric element in a series of frame file names\n\
109                       (default 6)\n\
110   -z                - Fail if j2c inputs have unequal parameters (default)\n\
111   -Z                - Ignore unequal parameters in j2c inputs\n\
112 \n\
113   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
114          o All option arguments must be separated from the option by whitespace.\n\n");
115 }
116
117 //
118 class CommandOptions
119 {
120   CommandOptions();
121
122 public:
123   bool   error_flag;     // true if the given options are in error or not complete
124   bool   key_flag;       // true if an encryption key was given
125   bool   read_hmac;      // true if HMAC values are to be validated
126   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
127   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
128   bool   verbose_flag;   // true if the verbose option was selected
129   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
130   bool   no_write_flag;  // true if no output files are to be written
131   bool   version_flag;   // true if the version display option was selected
132   bool   help_flag;      // true if the help display option was selected
133   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
134   ui32_t number_width;   // number of digits in a serialized filename (for JPEG extract)
135   ui32_t start_frame;    // frame number to begin processing
136   ui32_t duration;       // number of frames to be processed
137   bool   duration_flag;  // true if duration argument given
138   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
139   ui32_t picture_rate;   // fps of picture when wrapping PCM
140   ui32_t fb_size;        // size of picture frame buffer
141   Rational edit_rate;    // frame buffer size for reading clip-wrapped PCM
142   const char* file_prefix; // filename pre for files written by the extract mode
143   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
144   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
145   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
146   const char* input_filename;
147   std::string prefix_buffer;
148
149   //
150   CommandOptions(int argc, const char** argv) :
151     error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
152     mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
153     version_flag(false), help_flag(false), number_width(6),
154     start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
155     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
156     input_filename(0)
157   {
158     memset(key_value, 0, KeyLen);
159     memset(key_id_value, 0, UUIDlen);
160
161     for ( int i = 1; i < argc; ++i )
162       {
163
164         if ( (strcmp( argv[i], "-help") == 0) )
165           {
166             help_flag = true;
167             continue;
168           }
169          
170         if ( argv[i][0] == '-'
171              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
172              && argv[i][2] == 0 )
173           {
174             switch ( argv[i][1] )
175               {
176               case '1': mono_wav = true; break;
177               case '2': split_wav = true; break;
178
179               case 'b':
180                 TEST_EXTRA_ARG(i, 'b');
181                 fb_size = abs(atoi(argv[i]));
182
183                 if ( verbose_flag )
184                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
185
186                 break;
187
188               case 'd':
189                 TEST_EXTRA_ARG(i, 'd');
190                 duration_flag = true;
191                 duration = abs(atoi(argv[i]));
192                 break;
193
194               case 'f':
195                 TEST_EXTRA_ARG(i, 'f');
196                 start_frame = abs(atoi(argv[i]));
197                 break;
198
199               case 'h': help_flag = true; break;
200               case 'm': read_hmac = true; break;
201
202               case 'p':
203                 TEST_EXTRA_ARG(i, 'p');
204                 picture_rate = abs(atoi(argv[i]));
205                 break;
206
207               case 's':
208                 TEST_EXTRA_ARG(i, 's');
209                 fb_dump_size = abs(atoi(argv[i]));
210                 break;
211
212               case 'V': version_flag = true; break;
213               case 'v': verbose_flag = true; break;
214               case 'W': no_write_flag = true; break;
215
216               case 'w':
217                 TEST_EXTRA_ARG(i, 'w');
218                 number_width = abs(atoi(argv[i]));
219                 break;
220
221               case 'Z': j2c_pedantic = false; break;
222               case 'z': j2c_pedantic = true; break;
223
224               default:
225                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
226                 return;
227               }
228           }
229         else
230           {
231             if ( argv[i][0] != '-' )
232               {
233                 if ( input_filename == 0 )
234                   {
235                     input_filename = argv[i];
236                   }
237                 else if ( file_prefix == 0 )
238                   {
239                     file_prefix = argv[i];
240                   }
241               }
242             else
243               {
244                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
245                 return;
246               }
247           }
248       }
249
250     if ( help_flag || version_flag )
251       return;
252     
253     if ( input_filename == 0 )
254       {
255         fputs("At least one filename argument is required.\n", stderr);
256         return;
257       }
258
259     if ( file_prefix == 0 )
260       {
261         prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
262         file_prefix = prefix_buffer.c_str();
263       }
264
265     error_flag = false;
266   }
267 };
268
269
270 //------------------------------------------------------------------------------------------
271 // JPEG 2000 essence
272
273
274 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
275 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
276 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
277 //
278 Result_t
279 read_JP2K_file(CommandOptions& Options)
280 {
281   AESDecContext*     Context = 0;
282   HMACContext*       HMAC = 0;
283   AS_02::JP2K::MXFReader    Reader;
284   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
285   ui32_t             frame_count = 0;
286
287   Result_t result = Reader.OpenRead(Options.input_filename);
288
289   if ( ASDCP_SUCCESS(result) )
290     {
291       if ( Options.verbose_flag )
292         {
293           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
294         }
295
296       ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
297       ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
298
299       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
300                                                      reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
301
302       if ( KM_SUCCESS(result) )
303         {
304           assert(rgba_descriptor);
305           frame_count = rgba_descriptor->ContainerDuration;
306
307           if ( Options.verbose_flag )
308             {
309               rgba_descriptor->Dump();
310             }
311         }
312       else
313         {
314           result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
315                                                          reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
316
317           if ( KM_SUCCESS(result) )
318             {
319               assert(cdci_descriptor);
320               frame_count = cdci_descriptor->ContainerDuration;
321
322               if ( Options.verbose_flag )
323                 {
324                   cdci_descriptor->Dump();
325                 }
326             }
327           else
328             {
329               fprintf(stderr, "File does not contain an essence descriptor.\n");
330               frame_count = Reader.AS02IndexReader().GetDuration();
331             }
332         }
333
334       if ( frame_count == 0 )
335         {
336           frame_count = Reader.AS02IndexReader().GetDuration();
337         }
338
339       if ( frame_count == 0 )
340         {
341           fprintf(stderr, "Unable to determine file duration.\n");
342           return RESULT_FAIL;
343         }
344     }
345
346   if ( ASDCP_SUCCESS(result) && Options.key_flag )
347     {
348       Context = new AESDecContext;
349       result = Context->InitKey(Options.key_value);
350
351       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
352         {
353           WriterInfo Info;
354           Reader.FillWriterInfo(Info);
355
356           if ( Info.UsesHMAC )
357             {
358               HMAC = new HMACContext;
359               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
360             }
361           else
362             {
363               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
364             }
365         }
366     }
367
368   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
369   if ( last_frame > frame_count )
370     last_frame = frame_count;
371
372   char name_format[64];
373   snprintf(name_format,  64, "%%s%%0%du.j2c", Options.number_width);
374
375   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
376     {
377       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
378
379       if ( ASDCP_SUCCESS(result)  && ( ! Options.no_write_flag ) )
380         {
381           Kumu::FileWriter OutFile;
382           char filename[256];
383           ui32_t write_count;
384           snprintf(filename, 256, name_format, Options.file_prefix, i);
385           result = OutFile.OpenWrite(filename);
386
387           if ( ASDCP_SUCCESS(result) )
388             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
389
390           if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
391             {
392               FrameBuffer.Dump(stderr, Options.fb_dump_size);
393             }
394         }
395     }
396
397   return result;
398 }
399
400 //------------------------------------------------------------------------------------------
401 // PCM essence
402
403 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
404 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
405 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
406 //
407 Result_t
408 read_PCM_file(CommandOptions& Options)
409 {
410   AESDecContext*     Context = 0;
411   HMACContext*       HMAC = 0;
412   AS_02::PCM::MXFReader     Reader;
413   PCM::FrameBuffer   FrameBuffer;
414   WavFileWriter      OutWave;
415   ui32_t last_frame = 0;
416   ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0;
417
418   if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI
419     {
420       Options.edit_rate = EditRate_24;
421     }
422
423   Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
424
425   if ( KM_SUCCESS(result) )
426     {
427       if ( Options.verbose_flag )
428         {
429           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
430         }
431       
432       ASDCP::MXF::InterchangeObject* tmp_obj = 0;
433
434       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), &tmp_obj);
435
436       if ( KM_SUCCESS(result) )
437         {
438           wave_descriptor = dynamic_cast<ASDCP::MXF::WaveAudioDescriptor*>(tmp_obj);
439
440           if ( wave_descriptor == 0 )
441             {
442               fprintf(stderr, "File does not contain an essence descriptor.\n");
443               return RESULT_FAIL;
444             }
445       
446           if ( Options.verbose_flag )
447             {
448               wave_descriptor->Dump();
449             }
450
451           if ( wave_descriptor->ContainerDuration.get() == 0 )
452             {
453               fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
454               last_frame = Reader.AS02IndexReader().GetDuration();
455             }
456           else
457             {
458               last_frame = wave_descriptor->ContainerDuration;
459             }
460
461           if ( last_frame == 0 )
462             {
463               fprintf(stderr, "Unable to determine file duration.\n");
464               return RESULT_FAIL;
465             }
466
467           assert(wave_descriptor);
468           FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
469           last_frame = AS_02::MXF::CalcFramesFromDurationInSamples(last_frame, *wave_descriptor, Options.edit_rate);
470         }
471     }
472
473   if ( ASDCP_SUCCESS(result) )
474     {
475       if ( Options.duration > 0 && Options.duration < last_frame )
476         last_frame = Options.duration;
477
478       if ( Options.start_frame > 0 )
479         {
480           if ( Options.start_frame > last_frame )
481             {
482               fprintf(stderr, "Start value greater than file duration.\n");
483               return RESULT_FAIL;
484             }
485
486           last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame);
487         }
488
489       last_frame = last_frame - Options.start_frame;
490
491       PCM::AudioDescriptor ADesc;
492
493       result = MD_to_PCM_ADesc(wave_descriptor, ADesc);
494
495       if ( ASDCP_SUCCESS(result) )
496         {
497           ADesc.ContainerDuration = last_frame;
498           ADesc.EditRate = Options.edit_rate;
499
500           result = OutWave.OpenWrite(ADesc, Options.file_prefix,
501                                      ( Options.split_wav ? WavFileWriter::ST_STEREO : 
502                                        ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
503         }
504     }
505
506   if ( ASDCP_SUCCESS(result) && Options.key_flag )
507     {
508       Context = new AESDecContext;
509       result = Context->InitKey(Options.key_value);
510
511       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
512         {
513           WriterInfo Info;
514           Reader.FillWriterInfo(Info);
515
516           if ( Info.UsesHMAC )
517             {
518               HMAC = new HMACContext;
519               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
520             }
521           else
522             {
523               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
524             }
525         }
526     }
527
528   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
529     {
530       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
531
532       if ( ASDCP_SUCCESS(result) )
533         {
534           if ( Options.verbose_flag )
535             {
536               FrameBuffer.FrameNumber(i);
537               FrameBuffer.Dump(stderr, Options.fb_dump_size);
538             }
539
540           result = OutWave.WriteFrame(FrameBuffer);
541         }
542     }
543
544   return result;
545 }
546
547
548 //
549 int
550 main(int argc, const char** argv)
551 {
552   char     str_buf[64];
553   CommandOptions Options(argc, argv);
554
555   if ( Options.version_flag )
556     banner();
557
558   if ( Options.help_flag )
559     usage();
560
561   if ( Options.version_flag || Options.help_flag )
562     return 0;
563
564   if ( Options.error_flag )
565     {
566       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
567       return 3;
568     }
569
570   EssenceType_t EssenceType;
571   Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
572
573   if ( ASDCP_SUCCESS(result) )
574     {
575       switch ( EssenceType )
576         {
577         case ESS_AS02_JPEG_2000:
578           result = read_JP2K_file(Options);
579           break;
580
581         case ESS_AS02_PCM_24b_48k:
582         case ESS_AS02_PCM_24b_96k:
583           result = read_PCM_file(Options);
584           break;
585
586         default:
587           fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.input_filename);
588           return 5;
589         }
590     }
591
592   if ( ASDCP_FAILURE(result) )
593     {
594       fputs("Program stopped on error.\n", stderr);
595
596       if ( result != RESULT_FAIL )
597         {
598           fputs(result, stderr);
599           fputc('\n', stderr);
600         }
601
602       return 1;
603     }
604
605   return 0;
606 }
607
608
609 //
610 // end as-02-unwrap.cpp
611 //