as-02ooooooooooo!
[asdcplib.git] / src / as-02-wrap.cpp
1 /*
2 Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    as-02-wrap.cpp
28     \version $Id$       
29     \brief   AS-02 file manipulation utility
30
31   This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
32
33   For more information about AS-02, please refer to the header file AS_02.h
34   For more information about asdcplib, please refer to the header file AS_DCP.h
35 */
36
37 #include <KM_fileio.h>
38 #include <KM_prng.h>
39 #include <AS_02.h>
40 #include <PCMParserList.h>
41 #include <Metadata.h>
42
43 using namespace ASDCP;
44
45 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
46
47
48 const char*
49 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
50 {
51   snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
52   return buf;
53 }
54
55 //------------------------------------------------------------------------------------------
56 //
57 // command line option parser class
58
59 static const char* PROGRAM_NAME = "as-02-wrap";  // program name for messages
60
61 // local program identification info written to file headers
62 class MyInfo : public WriterInfo
63 {
64 public:
65   MyInfo()
66   {
67       static byte_t default_ProductUUID_Data[UUIDlen] =
68       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
69         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
70       
71       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
72       CompanyName = "WidgetCo";
73       ProductName = "as-02-wrap";
74       ProductVersion = ASDCP::Version();
75   }
76 } s_MyInfo;
77
78
79
80 // Increment the iterator, test for an additional non-option command line argument.
81 // Causes the caller to return if there are no remaining arguments or if the next
82 // argument begins with '-'.
83 #define TEST_EXTRA_ARG(i,c)                                             \
84   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
85     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
86     return;                                                             \
87   }
88
89 //
90 void
91 banner(FILE* stream = stdout)
92 {
93   fprintf(stream, "\n\
94 %s (asdcplib %s)\n\n\
95 Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
96 asdcplib may be copied only under the terms of the license found at\n\
97 the top of every file in the asdcplib distribution kit.\n\n\
98 Specify the -h (help) option for further information about %s\n\n",
99           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
100 }
101
102 //
103 void
104 usage(FILE* stream = stdout)
105 {
106   fprintf(stream, "\
107 USAGE: %s [-h|-help] [-V]\n\
108 \n\
109        %s [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
110           [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
111             [-M] [-p <n>/<d>]  [-v] [-W]\n\
112           [-z|-Z] <input-file>+ <output-file>\n\n",
113           PROGRAM_NAME, PROGRAM_NAME);
114
115   fprintf(stream, "\
116 Options:\n\
117   -C <UL>           - Set ChannelAssignment UL value\n\
118   -h | -help        - Show help\n\
119   -V                - Show version information\n\
120   -e                - Encrypt JP2K headers (default)\n\
121   -E                - Do not encrypt JP2K headers\n\
122   -j <key-id-str>   - Write key ID instead of creating a random value\n\
123   -k <key-string>   - Use key for ciphertext operations\n\
124   -M                - Do not create HMAC values when writing\n\
125   -a <UUID>         - Specify the Asset ID of the file\n\
126   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
127                       Defaults to 4,194,304 (4MB)\n\
128   -d <duration>     - Number of frames to process, default all\n\
129   -f <start-frame>  - Starting frame number, default 0\n\
130   -p <n>/<d>        - Edit Rate of the output file.  24/1 is the default\n\
131   -v                - Verbose, prints informative messages to stderr\n\
132   -W                - Read input file only, do not write source file\n\
133   -z                - Fail if j2c inputs have unequal parameters (default)\n\
134   -Z                - Ignore unequal parameters in j2c inputs\n\
135 \n\
136   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
137          o All option arguments must be separated from the option by whitespace.\n\n");
138 }
139
140 //
141 //
142 class CommandOptions
143 {
144   CommandOptions();
145
146 public:
147   bool   error_flag;     // true if the given options are in error or not complete
148   bool   key_flag;       // true if an encryption key was given
149   bool   asset_id_flag;  // true if an asset ID was given
150   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
151   bool   write_hmac;     // true if HMAC values are to be generated and written
152   bool   verbose_flag;   // true if the verbose option was selected
153   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
154   bool   no_write_flag;  // true if no output files are to be written
155   bool   version_flag;   // true if the version display option was selected
156   bool   help_flag;      // true if the help display option was selected
157   ui32_t start_frame;    // frame number to begin processing
158   ui32_t duration;       // number of frames to be processed
159   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
160   Rational edit_rate;    // edit rate of JP2K sequence
161   ui32_t fb_size;        // size of picture frame buffer
162   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
163   bool   key_id_flag;    // true if a key ID was given
164   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
165   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
166   std::string out_file; //
167   bool show_ul_values;    /// if true, dump the UL table before going tp work.
168   Kumu::PathList_t filenames;  // list of filenames to be processed
169   UL channel_assignment;
170
171   //new attributes for AS-02 support 
172   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
173   ui32_t partition_space; //Shim parameter partition_spacing
174
175   //
176   CommandOptions(int argc, const char** argv) :
177     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
178     encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
179     no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
180     duration(0xffffffff), j2c_pedantic(true), fb_size(FRAME_BUFFER_SIZE),
181     show_ul_values(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60)
182   {
183     memset(key_value, 0, KeyLen);
184     memset(key_id_value, 0, UUIDlen);
185
186     for ( int i = 1; i < argc; i++ )
187       {
188
189         if ( (strcmp( argv[i], "-help") == 0) )
190           {
191             help_flag = true;
192             continue;
193           }
194          
195         if ( argv[i][0] == '-'
196              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
197              && argv[i][2] == 0 )
198           {
199             switch ( argv[i][1] )
200               {
201               case 'a':
202                 asset_id_flag = true;
203                 TEST_EXTRA_ARG(i, 'a');
204                 {
205                   ui32_t length;
206                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
207
208                   if ( length != UUIDlen )
209                     {
210                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
211                       return;
212                     }
213                 }
214                 break;
215
216               case 'b':
217                 TEST_EXTRA_ARG(i, 'b');
218                 fb_size = abs(atoi(argv[i]));
219
220                 if ( verbose_flag )
221                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
222
223                 break;
224
225               case 'C':
226                 TEST_EXTRA_ARG(i, 'U');
227                 if ( ! channel_assignment.DecodeHex(argv[i]) )
228                   {
229                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
230                     return;
231                   }
232                 break;
233
234               case 'd':
235                 TEST_EXTRA_ARG(i, 'd');
236                 duration = abs(atoi(argv[i]));
237                 break;
238
239               case 'E': encrypt_header_flag = false; break;
240               case 'e': encrypt_header_flag = true; break;
241
242               case 'f':
243                 TEST_EXTRA_ARG(i, 'f');
244                 start_frame = abs(atoi(argv[i]));
245                 break;
246
247               case 'h': help_flag = true; break;
248
249               case 'j': key_id_flag = true;
250                 TEST_EXTRA_ARG(i, 'j');
251                 {
252                   ui32_t length;
253                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
254
255                   if ( length != UUIDlen )
256                     {
257                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
258                       return;
259                     }
260                 }
261                 break;
262
263               case 'k': key_flag = true;
264                 TEST_EXTRA_ARG(i, 'k');
265                 {
266                   ui32_t length;
267                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
268
269                   if ( length != KeyLen )
270                     {
271                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
272                       return;
273                     }
274                 }
275                 break;
276
277               case 'M': write_hmac = false; break;
278
279               case 'p':
280                 TEST_EXTRA_ARG(i, 'p');
281                 /// TODO: VERY BROKEN, WANT RATIONAL
282                 edit_rate.Numerator = abs(atoi(argv[i]));
283                 edit_rate.Denominator = 1;
284                 break;
285
286               case 'V': version_flag = true; break;
287               case 'v': verbose_flag = true; break;
288               case 'W': no_write_flag = true; break;
289               case 'Z': j2c_pedantic = false; break;
290               case 'z': j2c_pedantic = true; break;
291
292               default:
293                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
294                 return;
295               }
296           }
297         else
298           {
299
300             if ( argv[i][0] != '-' )
301               {
302                 filenames.push_back(argv[i]);
303               }
304             else
305               {
306                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
307                 return;
308               }
309           }
310       }
311
312     if ( help_flag || version_flag )
313       return;
314     
315     if ( filenames.size() < 2 )
316       {
317         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
318         return;
319       }
320
321     out_file = filenames.back();
322     filenames.pop_back();
323     error_flag = false;
324   }
325 };
326
327
328 //------------------------------------------------------------------------------------------
329 // JPEG 2000 essence
330
331 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
332 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
333 //
334 Result_t
335 write_JP2K_file(CommandOptions& Options)
336 {
337   AESEncContext*          Context = 0;
338   HMACContext*            HMAC = 0;
339   AS_02::JP2K::MXFWriter  Writer;
340   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
341   JP2K::PictureDescriptor PDesc;
342   JP2K::SequenceParser    Parser;
343   byte_t                  IV_buf[CBC_BLOCK_SIZE];
344   Kumu::FortunaRNG        RNG;
345
346   // set up essence parser
347   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
348
349   // set up MXF writer
350   if ( ASDCP_SUCCESS(result) )
351     {
352       Parser.FillPictureDescriptor(PDesc);
353       PDesc.EditRate = Options.edit_rate;
354
355       if ( Options.verbose_flag )
356         {
357           fprintf(stderr, "JPEG 2000 pictures\n");
358           fputs("PictureDescriptor:\n", stderr);
359           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
360           JP2K::PictureDescriptorDump(PDesc);
361         }
362     }
363
364   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
365     {
366       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
367       Info.LabelSetType = LS_MXF_SMPTE;
368
369       if ( Options.asset_id_flag )
370         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
371       else
372         Kumu::GenRandomUUID(Info.AssetUUID);
373
374       // configure encryption
375       if( Options.key_flag )
376         {
377           Kumu::GenRandomUUID(Info.ContextID);
378           Info.EncryptedEssence = true;
379
380           if ( Options.key_id_flag )
381             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
382           else
383             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
384
385           Context = new AESEncContext;
386           result = Context->InitKey(Options.key_value);
387
388           if ( ASDCP_SUCCESS(result) )
389             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
390
391           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
392             {
393               Info.UsesHMAC = true;
394               HMAC = new HMACContext;
395               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
396             }
397         }
398
399       if ( ASDCP_SUCCESS(result) )
400         result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc, Options.index_strategy, Options.partition_space);
401     }
402
403   if ( ASDCP_SUCCESS(result) )
404     {
405       ui32_t duration = 0;
406       result = Parser.Reset();
407
408       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
409         {
410           if ( duration == 1 )
411             {
412               result = Parser.ReadFrame(FrameBuffer);
413
414               if ( ASDCP_SUCCESS(result) )
415                 {
416                   if ( Options.verbose_flag )
417                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
418                   
419                   if ( Options.encrypt_header_flag )
420                     FrameBuffer.PlaintextOffset(0);
421                 }
422             }
423
424           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
425             {
426               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
427
428               // The Writer class will forward the last block of ciphertext
429               // to the encryption context for use as the IV for the next
430               // frame. If you want to use non-sequitur IV values, un-comment
431               // the following  line of code.
432               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
433               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
434             }
435         }
436
437       if ( result == RESULT_ENDOFFILE )
438         result = RESULT_OK;
439     }
440
441   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
442     result = Writer.Finalize();
443
444   return result;
445 }
446
447 //------------------------------------------------------------------------------------------
448 // PCM essence
449
450
451 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
452 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
453 //
454 Result_t
455 write_PCM_file(CommandOptions& Options)
456 {
457   AESEncContext*    Context = 0;
458   HMACContext*      HMAC = 0;
459   PCMParserList     Parser;
460   PCM::MXFWriter    Writer;
461   PCM::FrameBuffer  FrameBuffer;
462   PCM::AudioDescriptor ADesc;
463   byte_t            IV_buf[CBC_BLOCK_SIZE];
464   Kumu::FortunaRNG  RNG;
465
466   // set up essence parser
467   Result_t result = Parser.OpenRead(Options.filenames, Rational(1, 1));
468
469   // set up MXF writer
470   if ( ASDCP_SUCCESS(result) )
471     {
472       Parser.FillAudioDescriptor(ADesc);
473
474       ADesc.EditRate = Options.edit_rate;
475       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
476
477       if ( Options.verbose_flag )
478         {
479           char buf[64];
480           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
481                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
482                   RationalToString(Options.edit_rate, buf, 64),
483                   PCM::CalcSamplesPerFrame(ADesc));
484           fputs("AudioDescriptor:\n", stderr);
485           PCM::AudioDescriptorDump(ADesc);
486         }
487     }
488
489   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
490     {
491       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
492       Info.LabelSetType = LS_MXF_SMPTE;
493
494       if ( Options.asset_id_flag )
495         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
496       else
497         Kumu::GenRandomUUID(Info.AssetUUID);
498
499       // configure encryption
500       if( Options.key_flag )
501         {
502           Kumu::GenRandomUUID(Info.ContextID);
503           Info.EncryptedEssence = true;
504
505           if ( Options.key_id_flag )
506             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
507           else
508             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
509
510           Context = new AESEncContext;
511           result = Context->InitKey(Options.key_value);
512
513           if ( ASDCP_SUCCESS(result) )
514             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
515
516           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
517             {
518               Info.UsesHMAC = true;
519               HMAC = new HMACContext;
520               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
521             }
522         }
523
524       if ( ASDCP_SUCCESS(result) )
525         result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
526
527       if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
528         {
529           MXF::WaveAudioDescriptor *descriptor = 0;
530           Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
531                                                   reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
532           descriptor->ChannelAssignment = Options.channel_assignment;
533         }
534     }
535
536   if ( ASDCP_SUCCESS(result) )
537     {
538       result = Parser.Reset();
539       ui32_t duration = 0;
540
541       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
542         {
543           result = Parser.ReadFrame(FrameBuffer);
544
545           if ( ASDCP_SUCCESS(result) )
546             {
547               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
548                 {
549                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
550                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
551                   result = RESULT_ENDOFFILE;
552                   continue;
553                 }
554
555               if ( Options.verbose_flag )
556                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
557
558               if ( ! Options.no_write_flag )
559                 {
560                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
561
562                   // The Writer class will forward the last block of ciphertext
563                   // to the encryption context for use as the IV for the next
564                   // frame. If you want to use non-sequitur IV values, un-comment
565                   // the following  line of code.
566                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
567                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
568                 }
569             }
570         }
571
572       if ( result == RESULT_ENDOFFILE )
573         result = RESULT_OK;
574     }
575
576   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
577     result = Writer.Finalize();
578
579   return result;
580 }
581
582
583 //
584 int
585 main(int argc, const char** argv)
586 {
587   Result_t result = RESULT_OK;
588   char     str_buf[64];
589   CommandOptions Options(argc, argv);
590
591   if ( Options.version_flag )
592     banner();
593
594   if ( Options.help_flag )
595     usage();
596
597   if ( Options.version_flag || Options.help_flag )
598     return 0;
599
600   if ( Options.error_flag )
601     {
602       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
603       return 3;
604     }
605
606   if ( Options.show_ul_values )
607     {
608       DefaultSMPTEDict().Dump(stdout);
609     }
610
611   EssenceType_t EssenceType;
612   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
613
614   if ( ASDCP_SUCCESS(result) )
615     {
616       switch ( EssenceType )
617         {
618         case ESS_JPEG_2000:
619           result = write_JP2K_file(Options);
620           break;
621
622         case ESS_PCM_24b_48k:
623         case ESS_PCM_24b_96k:
624           result = write_PCM_file(Options);
625           break;
626
627         default:
628           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
629                   Options.filenames.front().c_str());
630           return 5;
631         }
632     }
633
634   if ( ASDCP_FAILURE(result) )
635     {
636       fputs("Program stopped on error.\n", stderr);
637
638       if ( result != RESULT_FAIL )
639         {
640           fputs(result, stderr);
641           fputc('\n', stderr);
642         }
643
644       return 1;
645     }
646
647   return 0;
648 }
649
650
651 //
652 // end as-02-wrap.cpp
653 //