oops.
[asdcplib.git] / src / as-02-unwrap.cpp
1 /*
2 Copyright (c) 2011-2012, 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-2013, 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) )
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 ( Options.verbose_flag )
391             FrameBuffer.Dump(stderr, Options.fb_dump_size);
392         }
393     }
394
395   return result;
396 }
397
398 //------------------------------------------------------------------------------------------
399 // PCM essence
400
401 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
402 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
403 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
404 //
405 Result_t
406 read_PCM_file(CommandOptions& Options)
407 {
408   AESDecContext*     Context = 0;
409   HMACContext*       HMAC = 0;
410   AS_02::PCM::MXFReader     Reader;
411   PCM::FrameBuffer   FrameBuffer;
412   WavFileWriter      OutWave;
413   ui32_t last_frame = 0;
414   ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0;
415
416   if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI
417     {
418       Options.edit_rate = EditRate_24;
419     }
420
421   Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
422
423   if ( ASDCP_SUCCESS(result) )
424     {
425       if ( Options.verbose_flag )
426         {
427           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
428         }
429
430       result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor),
431                                                      reinterpret_cast<MXF::InterchangeObject**>(&wave_descriptor));
432
433       if ( KM_SUCCESS(result) )
434         {
435           assert(wave_descriptor);
436           last_frame = wave_descriptor->ContainerDuration;
437
438           if ( Options.verbose_flag )
439             {
440               wave_descriptor->Dump();
441             }
442         }
443       else
444         {
445           fprintf(stderr, "File does not contain an essence descriptor.\n");
446           last_frame = Reader.AS02IndexReader().GetDuration();
447         }
448
449       if ( last_frame == 0 )
450         {
451           fprintf(stderr, "Unable to determine file duration.\n");
452           return RESULT_FAIL;
453         }
454
455       FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
456
457       if ( Options.verbose_flag )
458         {
459           wave_descriptor->Dump();
460         }
461     }
462
463   if ( ASDCP_SUCCESS(result) )
464     {
465       if ( Options.duration > 0 && Options.duration < last_frame )
466         last_frame = Options.duration;
467
468       if ( Options.start_frame > 0 )
469         {
470           if ( Options.start_frame > last_frame )
471             {
472               fprintf(stderr, "Start value greater than file duration.\n");
473               return RESULT_FAIL;
474             }
475
476           last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame);
477         }
478
479       last_frame = last_frame - Options.start_frame;
480
481       PCM::AudioDescriptor ADesc;
482
483       result = MD_to_PCM_ADesc(wave_descriptor, ADesc);
484
485       if ( ASDCP_SUCCESS(result) )
486         {
487           ADesc.ContainerDuration = last_frame;
488           ADesc.EditRate = Options.edit_rate;
489
490           result = OutWave.OpenWrite(ADesc, Options.file_prefix,
491                                      ( Options.split_wav ? WavFileWriter::ST_STEREO : 
492                                        ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
493         }
494     }
495
496   if ( ASDCP_SUCCESS(result) && Options.key_flag )
497     {
498       Context = new AESDecContext;
499       result = Context->InitKey(Options.key_value);
500
501       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
502         {
503           WriterInfo Info;
504           Reader.FillWriterInfo(Info);
505
506           if ( Info.UsesHMAC )
507             {
508               HMAC = new HMACContext;
509               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
510             }
511           else
512             {
513               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
514             }
515         }
516     }
517
518   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
519     {
520       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
521
522       if ( ASDCP_SUCCESS(result) )
523         {
524           if ( Options.verbose_flag )
525             FrameBuffer.Dump(stderr, Options.fb_dump_size);
526
527           result = OutWave.WriteFrame(FrameBuffer);
528         }
529     }
530
531   return result;
532 }
533
534
535 //
536 int
537 main(int argc, const char** argv)
538 {
539   char     str_buf[64];
540   CommandOptions Options(argc, argv);
541
542   if ( Options.version_flag )
543     banner();
544
545   if ( Options.help_flag )
546     usage();
547
548   if ( Options.version_flag || Options.help_flag )
549     return 0;
550
551   if ( Options.error_flag )
552     {
553       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
554       return 3;
555     }
556
557   EssenceType_t EssenceType;
558   Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
559
560   if ( ASDCP_SUCCESS(result) )
561     {
562       switch ( EssenceType )
563         {
564         case ESS_JPEG_2000:
565           result = read_JP2K_file(Options);
566           break;
567
568         case ESS_PCM_24b_48k:
569         case ESS_PCM_24b_96k:
570           result = read_PCM_file(Options);
571           break;
572
573         default:
574           fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.input_filename);
575           return 5;
576         }
577     }
578
579   if ( ASDCP_FAILURE(result) )
580     {
581       fputs("Program stopped on error.\n", stderr);
582
583       if ( result == RESULT_SFORMAT )
584         {
585           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
586         }
587       else if ( result != RESULT_FAIL )
588         {
589           fputs(result, stderr);
590           fputc('\n', stderr);
591         }
592
593       return 1;
594     }
595
596   return 0;
597 }
598
599
600 //
601 // end as-02-unwrap.cpp
602 //