e6981bc22831ed1d92c98584997178eeba8811cb
[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           result = Parser.ReadFrame(FrameBuffer);
411           
412           if ( ASDCP_SUCCESS(result) )
413             {
414               if ( Options.verbose_flag )
415                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
416               
417               if ( Options.encrypt_header_flag )
418                 FrameBuffer.PlaintextOffset(0);
419             }
420
421           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
422             {
423               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
424
425               // The Writer class will forward the last block of ciphertext
426               // to the encryption context for use as the IV for the next
427               // frame. If you want to use non-sequitur IV values, un-comment
428               // the following  line of code.
429               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
430               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
431             }
432         }
433
434       if ( result == RESULT_ENDOFFILE )
435         result = RESULT_OK;
436     }
437
438   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
439     result = Writer.Finalize();
440
441   return result;
442 }
443
444 //------------------------------------------------------------------------------------------
445 // PCM essence
446
447
448 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
449 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
450 //
451 Result_t
452 write_PCM_file(CommandOptions& Options)
453 {
454   AESEncContext*    Context = 0;
455   HMACContext*      HMAC = 0;
456   PCMParserList     Parser;
457   PCM::MXFWriter    Writer;
458   PCM::FrameBuffer  FrameBuffer;
459   PCM::AudioDescriptor ADesc;
460   byte_t            IV_buf[CBC_BLOCK_SIZE];
461   Kumu::FortunaRNG  RNG;
462
463   // set up essence parser
464   Result_t result = Parser.OpenRead(Options.filenames, Rational(1, 1));
465
466   // set up MXF writer
467   if ( ASDCP_SUCCESS(result) )
468     {
469       Parser.FillAudioDescriptor(ADesc);
470
471       ADesc.EditRate = Options.edit_rate;
472       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
473
474       if ( Options.verbose_flag )
475         {
476           char buf[64];
477           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
478                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
479                   RationalToString(Options.edit_rate, buf, 64),
480                   PCM::CalcSamplesPerFrame(ADesc));
481           fputs("AudioDescriptor:\n", stderr);
482           PCM::AudioDescriptorDump(ADesc);
483         }
484     }
485
486   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
487     {
488       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
489       Info.LabelSetType = LS_MXF_SMPTE;
490
491       if ( Options.asset_id_flag )
492         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
493       else
494         Kumu::GenRandomUUID(Info.AssetUUID);
495
496       // configure encryption
497       if( Options.key_flag )
498         {
499           Kumu::GenRandomUUID(Info.ContextID);
500           Info.EncryptedEssence = true;
501
502           if ( Options.key_id_flag )
503             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
504           else
505             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
506
507           Context = new AESEncContext;
508           result = Context->InitKey(Options.key_value);
509
510           if ( ASDCP_SUCCESS(result) )
511             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
512
513           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
514             {
515               Info.UsesHMAC = true;
516               HMAC = new HMACContext;
517               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
518             }
519         }
520
521       if ( ASDCP_SUCCESS(result) )
522         result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
523
524       if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
525         {
526           MXF::WaveAudioDescriptor *descriptor = 0;
527           Writer.OP1aHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
528                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
529           descriptor->ChannelAssignment = Options.channel_assignment;
530         }
531     }
532
533   if ( ASDCP_SUCCESS(result) )
534     {
535       result = Parser.Reset();
536       ui32_t duration = 0;
537
538       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
539         {
540           result = Parser.ReadFrame(FrameBuffer);
541
542           if ( ASDCP_SUCCESS(result) )
543             {
544               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
545                 {
546                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
547                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
548                   result = RESULT_ENDOFFILE;
549                   continue;
550                 }
551
552               if ( Options.verbose_flag )
553                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
554
555               if ( ! Options.no_write_flag )
556                 {
557                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
558
559                   // The Writer class will forward the last block of ciphertext
560                   // to the encryption context for use as the IV for the next
561                   // frame. If you want to use non-sequitur IV values, un-comment
562                   // the following  line of code.
563                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
564                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
565                 }
566             }
567         }
568
569       if ( result == RESULT_ENDOFFILE )
570         result = RESULT_OK;
571     }
572
573   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
574     result = Writer.Finalize();
575
576   return result;
577 }
578
579
580 //
581 int
582 main(int argc, const char** argv)
583 {
584   Result_t result = RESULT_OK;
585   char     str_buf[64];
586   CommandOptions Options(argc, argv);
587
588   if ( Options.version_flag )
589     banner();
590
591   if ( Options.help_flag )
592     usage();
593
594   if ( Options.version_flag || Options.help_flag )
595     return 0;
596
597   if ( Options.error_flag )
598     {
599       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
600       return 3;
601     }
602
603   if ( Options.show_ul_values )
604     {
605       DefaultSMPTEDict().Dump(stdout);
606     }
607
608   EssenceType_t EssenceType;
609   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
610
611   if ( ASDCP_SUCCESS(result) )
612     {
613       switch ( EssenceType )
614         {
615         case ESS_JPEG_2000:
616           result = write_JP2K_file(Options);
617           break;
618
619         case ESS_PCM_24b_48k:
620         case ESS_PCM_24b_96k:
621           result = write_PCM_file(Options);
622           break;
623
624         default:
625           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
626                   Options.filenames.front().c_str());
627           return 5;
628         }
629     }
630
631   if ( ASDCP_FAILURE(result) )
632     {
633       fputs("Program stopped on error.\n", stderr);
634
635       if ( result != RESULT_FAIL )
636         {
637           fputs(result, stderr);
638           fputc('\n', stderr);
639         }
640
641       return 1;
642     }
643
644   return 0;
645 }
646
647
648 //
649 // end as-02-wrap.cpp
650 //