o Added preliminary support for timed-text wrapping for AS-02. This
[asdcplib.git] / src / as-02-wrap.cpp
1 /*
2 Copyright (c) 2011-2013, 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-wrap.cpp
30     \version $Id$       
31     \brief   AS-02 file manipulation utility
32
33   This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
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 <KM_prng.h>
41 #include <AS_02.h>
42 #include <PCMParserList.h>
43 #include <Metadata.h>
44
45 using namespace ASDCP;
46
47 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
48 const ASDCP::Dictionary *g_dict = 0;
49
50
51 const char*
52 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
53 {
54   snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
55   return buf;
56 }
57
58
59
60 //------------------------------------------------------------------------------------------
61 //
62 // command line option parser class
63
64 static const char* PROGRAM_NAME = "as-02-wrap";  // program name for messages
65
66 // local program identification info written to file headers
67 class MyInfo : public WriterInfo
68 {
69 public:
70   MyInfo()
71   {
72       static byte_t default_ProductUUID_Data[UUIDlen] =
73       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
74         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
75       
76       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
77       CompanyName = "WidgetCo";
78       ProductName = "as-02-wrap";
79       ProductVersion = ASDCP::Version();
80   }
81 } s_MyInfo;
82
83
84
85 // Increment the iterator, test for an additional non-option command line argument.
86 // Causes the caller to return if there are no remaining arguments or if the next
87 // argument begins with '-'.
88 #define TEST_EXTRA_ARG(i,c)                                             \
89   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
90     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
91     return;                                                             \
92   }
93
94 //
95 void
96 banner(FILE* stream = stdout)
97 {
98   fprintf(stream, "\n\
99 %s (asdcplib %s)\n\n\
100 Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
101 asdcplib may be copied only under the terms of the license found at\n\
102 the top of every file in the asdcplib distribution kit.\n\n\
103 Specify the -h (help) option for further information about %s\n\n",
104           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
105 }
106
107 //
108 void
109 usage(FILE* stream = stdout)
110 {
111   fprintf(stream, "\
112 USAGE: %s [-h|-help] [-V]\n\
113 \n\
114        %s [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
115           [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
116             [-M] [-m <expr>] [-r <n>/<d>] [-s <seconds>] [-v] [-W]\n\
117           [-z|-Z] <input-file>+ <output-file>\n\n",
118           PROGRAM_NAME, PROGRAM_NAME);
119
120   fprintf(stream, "\
121 Options:\n\
122   -C <UL>           - Set ChannelAssignment UL value\n\
123   -h | -help        - Show help\n\
124   -V                - Show version information\n\
125   -e                - Encrypt JP2K headers (default)\n\
126   -E                - Do not encrypt JP2K headers\n\
127   -j <key-id-str>   - Write key ID instead of creating a random value\n\
128   -k <key-string>   - Use key for ciphertext operations\n\
129   -M                - Do not create HMAC values when writing\n\
130   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
131                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
132   -a <UUID>         - Specify the Asset ID of the file\n\
133   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
134                       Defaults to 4,194,304 (4MB)\n\
135   -d <duration>     - Number of frames to process, default all\n\
136   -f <start-frame>  - Starting frame number, default 0\n\
137   -r <n>/<d>        - Edit Rate of the output file.  24/1 is the default\n\
138   -s <seconds>      - Duration of a frame-wrapped partition (default 60)\n\
139   -v                - Verbose, prints informative messages to stderr\n\
140   -W                - Read input file only, do not write source file\n\
141   -z                - Fail if j2c inputs have unequal parameters (default)\n\
142   -Z                - Ignore unequal parameters in j2c inputs\n\
143 \n\
144   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
145          o All option arguments must be separated from the option by whitespace.\n\n");
146 }
147
148 //
149 static ASDCP::Rational
150 decode_rational(const char* str_rat)
151 {
152   assert(str_rat);
153   ui32_t Num = atoi(str_rat);
154   ui32_t Den = 0;
155
156   const char* den_str = strrchr(str_rat, ' ');
157   if ( den_str != 0 )
158     Den = atoi(den_str+1);
159
160   return ASDCP::Rational(Num, Den);
161 }
162 //
163 //
164 class CommandOptions
165 {
166   CommandOptions();
167
168 public:
169   bool   error_flag;     // true if the given options are in error or not complete
170   bool   key_flag;       // true if an encryption key was given
171   bool   asset_id_flag;  // true if an asset ID was given
172   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
173   bool   write_hmac;     // true if HMAC values are to be generated and written
174   bool   verbose_flag;   // true if the verbose option was selected
175   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
176   bool   no_write_flag;  // true if no output files are to be written
177   bool   version_flag;   // true if the version display option was selected
178   bool   help_flag;      // true if the help display option was selected
179   ui32_t start_frame;    // frame number to begin processing
180   ui32_t duration;       // number of frames to be processed
181   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
182   bool use_cdci_descriptor; // 
183   Rational edit_rate;    // edit rate of JP2K sequence
184   ui32_t fb_size;        // size of picture frame buffer
185   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
186   bool   key_id_flag;    // true if a key ID was given
187   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
188   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
189   std::string out_file; //
190   bool show_ul_values_flag;    /// if true, dump the UL table before going tp work.
191   Kumu::PathList_t filenames;  // list of filenames to be processed
192   UL channel_assignment;
193   ASDCP::MXF::AS02_MCAConfigParser mca_config;
194   ui32_t cdci_depth;
195   ui32_t rgba_MaxRef;
196   ui32_t rgba_MinRef;
197   ui32_t mxf_header_size;
198
199   //new attributes for AS-02 support 
200   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
201   ui32_t partition_space; //Shim parameter partition_spacing
202
203   //
204   CommandOptions(int argc, const char** argv) :
205     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
206     encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
207     no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
208     duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
209     show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
210     mca_config(g_dict), cdci_depth(0), rgba_MaxRef(1024), rgba_MinRef(0), mxf_header_size(16384)
211
212   {
213     memset(key_value, 0, KeyLen);
214     memset(key_id_value, 0, UUIDlen);
215
216     for ( int i = 1; i < argc; i++ )
217       {
218
219         if ( (strcmp( argv[i], "-help") == 0) )
220           {
221             help_flag = true;
222             continue;
223           }
224          
225         if ( argv[i][0] == '-'
226              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
227              && argv[i][2] == 0 )
228           {
229             switch ( argv[i][1] )
230               {
231               case 'a':
232                 asset_id_flag = true;
233                 TEST_EXTRA_ARG(i, 'a');
234                 {
235                   ui32_t length;
236                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
237
238                   if ( length != UUIDlen )
239                     {
240                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
241                       return;
242                     }
243                 }
244                 break;
245
246               case 'b':
247                 TEST_EXTRA_ARG(i, 'b');
248                 fb_size = abs(atoi(argv[i]));
249
250                 if ( verbose_flag )
251                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
252
253                 break;
254
255               case 'C':
256                 TEST_EXTRA_ARG(i, 'U');
257                 if ( ! channel_assignment.DecodeHex(argv[i]) )
258                   {
259                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
260                     return;
261                   }
262                 break;
263
264               case 'd':
265                 TEST_EXTRA_ARG(i, 'd');
266                 duration = abs(atoi(argv[i]));
267                 break;
268
269               case 'E': encrypt_header_flag = false; break;
270               case 'e': encrypt_header_flag = true; break;
271
272               case 'f':
273                 TEST_EXTRA_ARG(i, 'f');
274                 start_frame = abs(atoi(argv[i]));
275                 break;
276
277               case 'h': help_flag = true; break;
278
279               case 'j': key_id_flag = true;
280                 TEST_EXTRA_ARG(i, 'j');
281                 {
282                   ui32_t length;
283                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
284
285                   if ( length != UUIDlen )
286                     {
287                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
288                       return;
289                     }
290                 }
291                 break;
292
293               case 'k': key_flag = true;
294                 TEST_EXTRA_ARG(i, 'k');
295                 {
296                   ui32_t length;
297                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
298
299                   if ( length != KeyLen )
300                     {
301                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
302                       return;
303                     }
304                 }
305                 break;
306
307               case 'M': write_hmac = false; break;
308
309               case 'm':
310                 TEST_EXTRA_ARG(i, 'm');
311                 if ( ! mca_config.DecodeString(argv[i]) )
312                   {
313                     return;
314                   }
315                 break;
316
317               case 'r':
318                 TEST_EXTRA_ARG(i, 'r');
319                 edit_rate = decode_rational(argv[i]);
320                 break;
321
322               case 's':
323                 TEST_EXTRA_ARG(i, 's');
324                 partition_space = abs(atoi(argv[i]));
325                 break;
326
327               case 'u': show_ul_values_flag = true; break;
328               case 'V': version_flag = true; break;
329               case 'v': verbose_flag = true; break;
330               case 'W': no_write_flag = true; break;
331               case 'Z': j2c_pedantic = false; break;
332               case 'z': j2c_pedantic = true; break;
333
334               default:
335                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
336                 return;
337               }
338           }
339         else
340           {
341
342             if ( argv[i][0] != '-' )
343               {
344                 filenames.push_back(argv[i]);
345               }
346             else
347               {
348                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
349                 return;
350               }
351           }
352       }
353
354     if ( help_flag || version_flag )
355       return;
356     
357     if ( filenames.size() < 2 )
358       {
359         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
360         return;
361       }
362
363     out_file = filenames.back();
364     filenames.pop_back();
365     error_flag = false;
366   }
367 };
368
369
370 //------------------------------------------------------------------------------------------
371 // JPEG 2000 essence
372
373 namespace ASDCP {
374   Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
375                             const ASDCP::Dictionary& dict,
376                             ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
377                             ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
378
379   Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
380 }
381
382 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
383 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
384 //
385 Result_t
386 write_JP2K_file(CommandOptions& Options)
387 {
388   AESEncContext*          Context = 0;
389   HMACContext*            HMAC = 0;
390   AS_02::JP2K::MXFWriter  Writer;
391   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
392   JP2K::SequenceParser    Parser;
393   byte_t                  IV_buf[CBC_BLOCK_SIZE];
394   Kumu::FortunaRNG        RNG;
395   ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
396   ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
397
398   // set up essence parser
399   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
400
401   // set up MXF writer
402   if ( ASDCP_SUCCESS(result) )
403     {
404       ASDCP::JP2K::PictureDescriptor PDesc;
405       Parser.FillPictureDescriptor(PDesc);
406       PDesc.EditRate = Options.edit_rate;
407
408       if ( Options.verbose_flag )
409         {
410           fprintf(stderr, "JPEG 2000 pictures\n");
411           fputs("PictureDescriptor:\n", stderr);
412           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
413           JP2K::PictureDescriptorDump(PDesc);
414         }
415
416       if ( Options.use_cdci_descriptor )
417         {
418           ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
419           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
420           
421           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
422                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
423                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
424
425           if ( ASDCP_SUCCESS(result) )
426             {
427               // TODO, select profile
428               tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
429               tmp_dscr->ComponentDepth = Options.cdci_depth;
430
431               // more options here
432
433               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
434             }
435         }
436       else
437         { // use RGB
438           ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
439           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
440           
441           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
442                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
443                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
444
445           if ( ASDCP_SUCCESS(result) )
446             {
447               // TODO, select profile
448               tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
449               tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
450               tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
451
452               // more options here
453
454               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
455             }
456         }
457     }
458
459   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
460     {
461       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
462       Info.LabelSetType = LS_MXF_SMPTE;
463
464       if ( Options.asset_id_flag )
465         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
466       else
467         Kumu::GenRandomUUID(Info.AssetUUID);
468
469       // configure encryption
470       if( Options.key_flag )
471         {
472           Kumu::GenRandomUUID(Info.ContextID);
473           Info.EncryptedEssence = true;
474
475           if ( Options.key_id_flag )
476             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
477           else
478             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
479
480           Context = new AESEncContext;
481           result = Context->InitKey(Options.key_value);
482
483           if ( ASDCP_SUCCESS(result) )
484             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
485
486           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
487             {
488               Info.UsesHMAC = true;
489               HMAC = new HMACContext;
490               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
491             }
492         }
493
494       if ( ASDCP_SUCCESS(result) )
495         {
496           result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
497                                     Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
498         }
499     }
500
501   if ( ASDCP_SUCCESS(result) )
502     {
503       ui32_t duration = 0;
504       result = Parser.Reset();
505
506       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
507         {
508           result = Parser.ReadFrame(FrameBuffer);
509           
510           if ( ASDCP_SUCCESS(result) )
511             {
512               if ( Options.verbose_flag )
513                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
514               
515               if ( Options.encrypt_header_flag )
516                 FrameBuffer.PlaintextOffset(0);
517             }
518
519           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
520             {
521               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
522
523               // The Writer class will forward the last block of ciphertext
524               // to the encryption context for use as the IV for the next
525               // frame. If you want to use non-sequitur IV values, un-comment
526               // the following  line of code.
527               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
528               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
529             }
530         }
531
532       if ( result == RESULT_ENDOFFILE )
533         result = RESULT_OK;
534     }
535
536   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
537     result = Writer.Finalize();
538
539   return result;
540 }
541
542 //------------------------------------------------------------------------------------------
543 // PCM essence
544
545
546 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
547 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
548 //
549 Result_t
550 write_PCM_file(CommandOptions& Options)
551 {
552   AESEncContext*    Context = 0;
553   HMACContext*      HMAC = 0;
554   PCMParserList     Parser;
555   AS_02::PCM::MXFWriter    Writer;
556   PCM::FrameBuffer  FrameBuffer;
557   byte_t            IV_buf[CBC_BLOCK_SIZE];
558   Kumu::FortunaRNG  RNG;
559   ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
560
561   // set up essence parser
562   Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
563
564   // set up MXF writer
565   if ( ASDCP_SUCCESS(result) )
566     {
567       ASDCP::PCM::AudioDescriptor ADesc;
568       Parser.FillAudioDescriptor(ADesc);
569
570       ADesc.EditRate = Options.edit_rate;
571       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
572
573       if ( Options.verbose_flag )
574         {
575           char buf[64];
576           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
577                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
578                   RationalToString(Options.edit_rate, buf, 64),
579                   PCM::CalcSamplesPerFrame(ADesc));
580           fputs("AudioDescriptor:\n", stderr);
581           PCM::AudioDescriptorDump(ADesc);
582         }
583
584       essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
585
586       result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
587
588       if ( Options.mca_config.empty() )
589         {
590           essence_descriptor->ChannelAssignment = Options.channel_assignment;
591         }
592       else
593         {
594           if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
595             {
596               fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
597                       Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
598               return RESULT_FAIL;
599             }
600
601           // this is the d-cinema MCA label, what is the one for IMF?
602           essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA);
603         }
604     }
605
606   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
607     {
608       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
609       Info.LabelSetType = LS_MXF_SMPTE;
610
611       if ( Options.asset_id_flag )
612         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
613       else
614         Kumu::GenRandomUUID(Info.AssetUUID);
615
616       // configure encryption
617       if( Options.key_flag )
618         {
619           Kumu::GenRandomUUID(Info.ContextID);
620           Info.EncryptedEssence = true;
621
622           if ( Options.key_id_flag )
623             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
624           else
625             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
626
627           Context = new AESEncContext;
628           result = Context->InitKey(Options.key_value);
629
630           if ( ASDCP_SUCCESS(result) )
631             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
632
633           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
634             {
635               Info.UsesHMAC = true;
636               HMAC = new HMACContext;
637               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
638             }
639         }
640
641       if ( ASDCP_SUCCESS(result) )
642         {
643           result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
644                                     Options.mca_config, Options.edit_rate);
645         }
646     }
647
648   if ( ASDCP_SUCCESS(result) )
649     {
650       result = Parser.Reset();
651       ui32_t duration = 0;
652
653       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
654         {
655           result = Parser.ReadFrame(FrameBuffer);
656
657           if ( ASDCP_SUCCESS(result) )
658             {
659               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
660                 {
661                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
662                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
663                   result = RESULT_ENDOFFILE;
664                   continue;
665                 }
666
667               if ( Options.verbose_flag )
668                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
669
670               if ( ! Options.no_write_flag )
671                 {
672                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
673
674                   // The Writer class will forward the last block of ciphertext
675                   // to the encryption context for use as the IV for the next
676                   // frame. If you want to use non-sequitur IV values, un-comment
677                   // the following  line of code.
678                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
679                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
680                 }
681             }
682         }
683
684       if ( result == RESULT_ENDOFFILE )
685         result = RESULT_OK;
686     }
687
688   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
689     result = Writer.Finalize();
690
691   return result;
692 }
693
694
695 //------------------------------------------------------------------------------------------
696 // TimedText essence
697
698
699 // Write one or more plaintext timed text streams to a plaintext AS-02 file
700 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
701 //
702 Result_t
703 write_timed_text_file(CommandOptions& Options)
704 {
705   AESEncContext*    Context = 0;
706   HMACContext*      HMAC = 0;
707   AS_02::TimedText::ST2052_TextParser  Parser;
708   AS_02::TimedText::MXFWriter    Writer;
709   TimedText::FrameBuffer  FrameBuffer;
710   TimedText::TimedTextDescriptor TDesc;
711   byte_t            IV_buf[CBC_BLOCK_SIZE];
712   Kumu::FortunaRNG  RNG;
713
714   // set up essence parser
715   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
716
717   // set up MXF writer
718   if ( ASDCP_SUCCESS(result) )
719     {
720       Parser.FillTimedTextDescriptor(TDesc);
721       FrameBuffer.Capacity(Options.fb_size);
722
723       if ( Options.verbose_flag )
724         {
725           fputs("IMF Timed-Text Descriptor:\n", stderr);
726           TimedText::DescriptorDump(TDesc);
727         }
728     }
729
730   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
731     {
732       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
733       if ( Options.asset_id_flag )
734         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
735       else
736         Kumu::GenRandomUUID(Info.AssetUUID);
737
738       // configure encryption
739       if( Options.key_flag )
740         {
741           Kumu::GenRandomUUID(Info.ContextID);
742           Info.EncryptedEssence = true;
743
744           if ( Options.key_id_flag )
745             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
746           else
747             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
748
749           Context = new AESEncContext;
750           result = Context->InitKey(Options.key_value);
751
752           if ( ASDCP_SUCCESS(result) )
753             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
754
755           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
756             {
757               Info.UsesHMAC = true;
758               HMAC = new HMACContext;
759               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
760             }
761         }
762
763       if ( ASDCP_SUCCESS(result) )
764         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
765     }
766
767   if ( ASDCP_FAILURE(result) )
768     return result;
769
770   std::string XMLDoc;
771   TimedText::ResourceList_t::const_iterator ri;
772
773   result = Parser.ReadTimedTextResource(XMLDoc);
774
775   if ( ASDCP_SUCCESS(result) )
776     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
777
778   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
779     {
780       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
781
782       if ( ASDCP_SUCCESS(result) )
783         {
784           if ( Options.verbose_flag )
785             FrameBuffer.Dump(stderr, Options.fb_dump_size);
786
787           if ( ! Options.no_write_flag )
788             {
789               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
790
791               // The Writer class will forward the last block of ciphertext
792               // to the encryption context for use as the IV for the next
793               // frame. If you want to use non-sequitur IV values, un-comment
794               // the following  line of code.
795               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
796               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
797             }
798         }
799
800       if ( result == RESULT_ENDOFFILE )
801         result = RESULT_OK;
802     }
803
804   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
805     result = Writer.Finalize();
806
807   return result;
808 }
809
810 //
811 int
812 main(int argc, const char** argv)
813 {
814   Result_t result = RESULT_OK;
815   char     str_buf[64];
816   g_dict = &ASDCP::DefaultSMPTEDict();
817   assert(g_dict);
818
819   CommandOptions Options(argc, argv);
820
821   if ( Options.version_flag )
822     banner();
823
824   if ( Options.help_flag )
825     usage();
826
827   if ( Options.show_ul_values_flag )
828     {
829       g_dict->Dump(stdout);
830     }
831
832   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
833     return 0;
834
835   if ( Options.error_flag )
836     {
837       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
838       return 3;
839     }
840
841   EssenceType_t EssenceType;
842   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
843
844   if ( ASDCP_SUCCESS(result) )
845     {
846       switch ( EssenceType )
847         {
848         case ESS_JPEG_2000:
849           result = write_JP2K_file(Options);
850           break;
851
852         case ESS_PCM_24b_48k:
853         case ESS_PCM_24b_96k:
854           result = write_PCM_file(Options);
855           break;
856
857         default:
858           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
859                   Options.filenames.front().c_str());
860           return 5;
861         }
862     }
863
864   if ( ASDCP_FAILURE(result) )
865     {
866       fputs("Program stopped on error.\n", stderr);
867
868       if ( result != RESULT_FAIL )
869         {
870           fputs(result, stderr);
871           fputc('\n', stderr);
872         }
873
874       return 1;
875     }
876
877   return 0;
878 }
879
880
881 //
882 // end as-02-wrap.cpp
883 //