version bump
[asdcplib.git] / src / as-02-wrap.cpp
1 /*
2 Copyright (c) 2011-2014, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
3 John Hurst
4
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10 1. Redistributions of source code must retain the above copyright
11    notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15 3. The name of the author may not be used to endorse or promote products
16    derived from this software without specific prior written permission.
17
18 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 /*! \file    as-02-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 //
96 static void
97 create_random_uuid(byte_t* uuidbuf)
98 {
99   Kumu::UUID tmp_id;
100   GenRandomValue(tmp_id);
101   memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size());
102 }
103
104 //
105 void
106 banner(FILE* stream = stdout)
107 {
108   fprintf(stream, "\n\
109 %s (asdcplib %s)\n\n\
110 Copyright (c) 2011-2014, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
111 asdcplib may be copied only under the terms of the license found at\n\
112 the top of every file in the asdcplib distribution kit.\n\n\
113 Specify the -h (help) option for further information about %s\n\n",
114           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
115 }
116
117 //
118 void
119 usage(FILE* stream = stdout)
120 {
121   fprintf(stream, "\
122 USAGE: %s [-h|-help] [-V]\n\
123 \n\
124        %s [-a <uuid>] [-A <w>/<h>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
125             [-D <depth>] [-e|-E] [-i] [-j <key-id-string>] [-k <key-string>]\n\
126             [-M] [-m <expr>] [-p <ul>] [-r <n>/<d>] [-R] [-s <seconds>]\n\
127             [-t <min>] [-T <max>] [-u] [-v] [-W] [-x <int>] [-X <int>] [-Y]\n\
128             [-z|-Z] <input-file>+ <output-file>\n\n",
129           PROGRAM_NAME, PROGRAM_NAME);
130
131   fprintf(stream, "\
132 Options:\n\
133   -h | -help        - Show help\n\
134   -V                - Show version information\n\
135   -a <uuid>         - Specify the Asset ID of the file\n\
136   -A <w>/<h>        - Set aspect ratio for image (default 4/3)\n\
137   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
138                       Defaults to 4,194,304 (4MB)\n\
139   -C <ul>           - Set ChannelAssignment UL value\n\
140   -d <duration>     - Number of frames to process, default all\n\
141   -D <depth>        - Component depth for YCbCr images (default: 10)\n\
142   -e                - Encrypt JP2K headers (default)\n\
143   -E                - Do not encrypt JP2K headers\n\
144   -F (0|1)          - Set field dominance for interlaced image (default: 0)\n\
145   -i                - Indicates input essence is interlaced fields (forces -Y)\n\
146   -j <key-id-str>   - Write key ID instead of creating a random value\n\
147   -k <key-string>   - Use key for ciphertext operations\n\
148   -M                - Do not create HMAC values when writing\n\
149   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
150                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
151   -p <ul>           - Set broadcast profile\n\
152   -r <n>/<d>        - Edit Rate of the output file.  24/1 is the default\n\
153   -R                - Indicates RGB image essence (default)\n\
154   -s <seconds>      - Duration of a frame-wrapped partition (default 60)\n\
155   -t <min>          - Set RGB component minimum code value (default: 0)\n\
156   -T <max>          - Set RGB component maximum code value (default: 1023)\n\
157   -u                - Print UL catalog to stderr\n\
158   -v                - Verbose, prints informative messages to stderr\n\
159   -W                - Read input file only, do not write source file\n\
160   -x <int>          - Horizontal subsampling degree (default: 2)\n\
161   -X <int>          - Vertical subsampling degree (default: 2)\n\
162   -Y                - Indicates YCbCr image essence (default: RGB)\n\
163   -z                - Fail if j2c inputs have unequal parameters (default)\n\
164   -Z                - Ignore unequal parameters in j2c inputs\n\
165 \n\
166   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
167          o All option arguments must be separated from the option by whitespace.\n\n");
168 }
169
170 //
171 static ASDCP::Rational
172 decode_rational(const char* str_rat)
173 {
174   assert(str_rat);
175   ui32_t Num = atoi(str_rat);
176   ui32_t Den = 0;
177
178   const char* den_str = strrchr(str_rat, '/');
179   if ( den_str != 0 )
180     Den = atoi(den_str+1);
181
182   return ASDCP::Rational(Num, Den);
183 }
184
185 //
186 //
187 class CommandOptions
188 {
189   CommandOptions();
190
191 public:
192   bool   error_flag;     // true if the given options are in error or not complete
193   bool   key_flag;       // true if an encryption key was given
194   bool   asset_id_flag;  // true if an asset ID was given
195   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
196   bool   write_hmac;     // true if HMAC values are to be generated and written
197   bool   verbose_flag;   // true if the verbose option was selected
198   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
199   bool   no_write_flag;  // true if no output files are to be written
200   bool   version_flag;   // true if the version display option was selected
201   bool   help_flag;      // true if the help display option was selected
202   ui32_t duration;       // number of frames to be processed
203   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
204   bool use_cdci_descriptor; // 
205   Rational edit_rate;    // edit rate of JP2K sequence
206   ui32_t fb_size;        // size of picture frame buffer
207   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
208   bool   key_id_flag;    // true if a key ID was given
209   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
210   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
211   std::string out_file; //
212   bool show_ul_values_flag;    /// if true, dump the UL table before going tp work.
213   Kumu::PathList_t filenames;  // list of filenames to be processed
214
215   UL channel_assignment;
216   ASDCP::MXF::AS02_MCAConfigParser mca_config;
217
218   UL picture_coding;
219   ui32_t rgba_MaxRef;
220   ui32_t rgba_MinRef;
221
222   ui32_t horizontal_subsampling;
223   ui32_t vertical_subsampling;
224   ui32_t component_depth;
225   ui8_t frame_layout;
226   ASDCP::Rational aspect_ratio;
227   ui8_t field_dominance;
228   ui32_t mxf_header_size;
229
230   //new attributes for AS-02 support 
231   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
232   ui32_t partition_space; //Shim parameter partition_spacing
233
234   //
235   CommandOptions(int argc, const char** argv) :
236     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
237     encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
238     no_write_flag(false), version_flag(false), help_flag(false),
239     duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
240     show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
241     mca_config(g_dict), rgba_MaxRef(1023), rgba_MinRef(0),
242     horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
243     frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
244     mxf_header_size(16384)
245   {
246     memset(key_value, 0, KeyLen);
247     memset(key_id_value, 0, UUIDlen);
248
249     for ( int i = 1; i < argc; i++ )
250       {
251
252         if ( (strcmp( argv[i], "-help") == 0) )
253           {
254             help_flag = true;
255             continue;
256           }
257          
258         if ( argv[i][0] == '-'
259              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
260              && argv[i][2] == 0 )
261           {
262             switch ( argv[i][1] )
263               {
264               case 'A':
265                 TEST_EXTRA_ARG(i, 'A');
266                 edit_rate = decode_rational(argv[i]);
267                 break;
268
269               case 'a':
270                 asset_id_flag = true;
271                 TEST_EXTRA_ARG(i, 'a');
272                 {
273                   ui32_t length;
274                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
275
276                   if ( length != UUIDlen )
277                     {
278                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
279                       return;
280                     }
281                 }
282                 break;
283
284               case 'b':
285                 TEST_EXTRA_ARG(i, 'b');
286                 fb_size = abs(atoi(argv[i]));
287
288                 if ( verbose_flag )
289                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
290
291                 break;
292
293               case 'C':
294                 TEST_EXTRA_ARG(i, 'C');
295                 if ( ! channel_assignment.DecodeHex(argv[i]) )
296                   {
297                     fprintf(stderr, "Error decoding ChannelAssignment UL value: %s\n", argv[i]);
298                     return;
299                   }
300                 break;
301
302               case 'D':
303                 TEST_EXTRA_ARG(i, 'D');
304                 component_depth = abs(atoi(argv[i]));
305                 break;
306
307               case 'd':
308                 TEST_EXTRA_ARG(i, 'd');
309                 duration = abs(atoi(argv[i]));
310                 break;
311
312               case 'E': encrypt_header_flag = false; break;
313               case 'e': encrypt_header_flag = true; break;
314
315               case 'F':
316                 TEST_EXTRA_ARG(i, 'F');
317                 field_dominance = abs(atoi(argv[i]));
318                 if ( field_dominance > 1 )
319                   {
320                     fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
321                     return;
322                   }
323                 break;
324
325               case 'h': help_flag = true; break;
326
327               case 'i':
328                 frame_layout = 1;
329                 use_cdci_descriptor = true;
330                 break;
331
332               case 'j':
333                 key_id_flag = true;
334                 TEST_EXTRA_ARG(i, 'j');
335                 {
336                   ui32_t length;
337                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
338
339                   if ( length != UUIDlen )
340                     {
341                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
342                       return;
343                     }
344                 }
345                 break;
346
347               case 'k': key_flag = true;
348                 TEST_EXTRA_ARG(i, 'k');
349                 {
350                   ui32_t length;
351                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
352
353                   if ( length != KeyLen )
354                     {
355                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
356                       return;
357                     }
358                 }
359                 break;
360
361               case 'M': write_hmac = false; break;
362
363               case 'm':
364                 TEST_EXTRA_ARG(i, 'm');
365                 if ( ! mca_config.DecodeString(argv[i]) )
366                   {
367                     return;
368                   }
369                 break;
370
371               case 'p':
372                 TEST_EXTRA_ARG(i, 'p');
373                 if ( ! picture_coding.DecodeHex(argv[i]) )
374                   {
375                     fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
376                     return;
377                   }
378                 break;
379
380               case 'r':
381                 TEST_EXTRA_ARG(i, 'r');
382                 edit_rate = decode_rational(argv[i]);
383                 break;
384
385               case 'R':
386                 use_cdci_descriptor = false;
387                 break;
388
389               case 's':
390                 TEST_EXTRA_ARG(i, 's');
391                 partition_space = abs(atoi(argv[i]));
392                 break;
393
394               case 't':
395                 TEST_EXTRA_ARG(i, 't');
396                 rgba_MinRef = abs(atoi(argv[i]));
397                 break;
398
399               case 'T':
400                 TEST_EXTRA_ARG(i, 'T');
401                 rgba_MaxRef = abs(atoi(argv[i]));
402                 break;
403
404               case 'u': show_ul_values_flag = true; break;
405               case 'V': version_flag = true; break;
406               case 'v': verbose_flag = true; break;
407               case 'W': no_write_flag = true; break;
408
409               case 'x':
410                 TEST_EXTRA_ARG(i, 'x');
411                 horizontal_subsampling = abs(atoi(argv[i]));
412                 break;
413
414               case 'X':
415                 TEST_EXTRA_ARG(i, 'X');
416                 vertical_subsampling = abs(atoi(argv[i]));
417                 break;
418
419               case 'Y':
420                 use_cdci_descriptor = true;
421                 break;
422
423               case 'Z': j2c_pedantic = false; break;
424               case 'z': j2c_pedantic = true; break;
425
426               default:
427                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
428                 return;
429               }
430           }
431         else
432           {
433
434             if ( argv[i][0] != '-' )
435               {
436                 filenames.push_back(argv[i]);
437               }
438             else
439               {
440                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
441                 return;
442               }
443           }
444       }
445
446     if ( help_flag || version_flag )
447       return;
448     
449     if ( filenames.size() < 2 )
450       {
451         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
452         return;
453       }
454
455     out_file = filenames.back();
456     filenames.pop_back();
457
458     if ( ! picture_coding.HasValue() )
459       {
460         picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
461       }
462
463     error_flag = false;
464   }
465 };
466
467
468 //------------------------------------------------------------------------------------------
469 // JPEG 2000 essence
470
471 namespace ASDCP {
472   Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
473                             const ASDCP::Dictionary& dict,
474                             ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
475                             ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
476
477   Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
478 }
479
480 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
481 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
482 //
483 Result_t
484 write_JP2K_file(CommandOptions& Options)
485 {
486   AESEncContext*          Context = 0;
487   HMACContext*            HMAC = 0;
488   AS_02::JP2K::MXFWriter  Writer;
489   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
490   JP2K::SequenceParser    Parser;
491   byte_t                  IV_buf[CBC_BLOCK_SIZE];
492   Kumu::FortunaRNG        RNG;
493   ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
494   ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
495
496   // set up essence parser
497   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
498
499   // set up MXF writer
500   if ( ASDCP_SUCCESS(result) )
501     {
502       ASDCP::JP2K::PictureDescriptor PDesc;
503       Parser.FillPictureDescriptor(PDesc);
504       PDesc.EditRate = Options.edit_rate;
505
506       if ( Options.verbose_flag )
507         {
508           fprintf(stderr, "JPEG 2000 pictures\n");
509           fputs("PictureDescriptor:\n", stderr);
510           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
511           JP2K::PictureDescriptorDump(PDesc);
512         }
513
514       if ( Options.use_cdci_descriptor )
515         {
516           ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
517           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
518           
519           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
520                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
521                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
522
523           if ( ASDCP_SUCCESS(result) )
524             {
525               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
526               tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
527               tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
528               tmp_dscr->ComponentDepth = Options.component_depth;
529               tmp_dscr->FrameLayout = Options.frame_layout;
530               tmp_dscr->AspectRatio = Options.aspect_ratio;
531               tmp_dscr->FieldDominance = Options.field_dominance;
532               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
533             }
534         }
535       else
536         { // use RGB
537           ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
538           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
539           
540           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
541                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
542                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
543
544           if ( ASDCP_SUCCESS(result) )
545             {
546               tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
547               tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
548               tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
549               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
550             }
551         }
552     }
553
554   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
555     {
556       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
557       Info.LabelSetType = LS_MXF_SMPTE;
558
559       if ( Options.asset_id_flag )
560         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
561       else
562         Kumu::GenRandomUUID(Info.AssetUUID);
563
564       // configure encryption
565       if( Options.key_flag )
566         {
567           Kumu::GenRandomUUID(Info.ContextID);
568           Info.EncryptedEssence = true;
569
570           if ( Options.key_id_flag )
571             {
572               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
573             }
574           else
575             {
576               create_random_uuid(Info.CryptographicKeyID);
577             }
578
579           Context = new AESEncContext;
580           result = Context->InitKey(Options.key_value);
581
582           if ( ASDCP_SUCCESS(result) )
583             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
584
585           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
586             {
587               Info.UsesHMAC = true;
588               HMAC = new HMACContext;
589               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
590             }
591         }
592
593       if ( ASDCP_SUCCESS(result) )
594         {
595           result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
596                                     Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
597         }
598     }
599
600   if ( ASDCP_SUCCESS(result) )
601     {
602       ui32_t duration = 0;
603       result = Parser.Reset();
604
605       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
606         {
607           result = Parser.ReadFrame(FrameBuffer);
608           
609           if ( ASDCP_SUCCESS(result) )
610             {
611               if ( Options.verbose_flag )
612                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
613               
614               if ( Options.encrypt_header_flag )
615                 FrameBuffer.PlaintextOffset(0);
616             }
617
618           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
619             {
620               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
621
622               // The Writer class will forward the last block of ciphertext
623               // to the encryption context for use as the IV for the next
624               // frame. If you want to use non-sequitur IV values, un-comment
625               // the following  line of code.
626               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
627               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
628             }
629         }
630
631       if ( result == RESULT_ENDOFFILE )
632         result = RESULT_OK;
633     }
634
635   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
636     result = Writer.Finalize();
637
638   return result;
639 }
640
641 //------------------------------------------------------------------------------------------
642 // PCM essence
643
644
645 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
646 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
647 //
648 Result_t
649 write_PCM_file(CommandOptions& Options)
650 {
651   AESEncContext*    Context = 0;
652   HMACContext*      HMAC = 0;
653   PCMParserList     Parser;
654   AS_02::PCM::MXFWriter    Writer;
655   PCM::FrameBuffer  FrameBuffer;
656   byte_t            IV_buf[CBC_BLOCK_SIZE];
657   Kumu::FortunaRNG  RNG;
658   ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
659
660   // set up essence parser
661   Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
662
663   // set up MXF writer
664   if ( ASDCP_SUCCESS(result) )
665     {
666       ASDCP::PCM::AudioDescriptor ADesc;
667       Parser.FillAudioDescriptor(ADesc);
668
669       ADesc.EditRate = Options.edit_rate;
670       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
671
672       if ( Options.verbose_flag )
673         {
674           char buf[64];
675           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
676                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
677                   RationalToString(Options.edit_rate, buf, 64),
678                   PCM::CalcSamplesPerFrame(ADesc));
679           fputs("AudioDescriptor:\n", stderr);
680           PCM::AudioDescriptorDump(ADesc);
681         }
682
683       essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
684
685       result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
686
687       if ( Options.mca_config.empty() )
688         {
689           essence_descriptor->ChannelAssignment = Options.channel_assignment;
690         }
691       else
692         {
693           if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
694             {
695               fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
696                       Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
697               return RESULT_FAIL;
698             }
699
700           // this is the d-cinema MCA label, what is the one for IMF?
701           essence_descriptor->ChannelAssignment = g_dict->ul(MDD_IMFAudioChannelCfg_MCA);
702         }
703     }
704
705   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
706     {
707       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
708       Info.LabelSetType = LS_MXF_SMPTE;
709
710       if ( Options.asset_id_flag )
711         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
712       else
713         Kumu::GenRandomUUID(Info.AssetUUID);
714
715       // configure encryption
716       if( Options.key_flag )
717         {
718           Kumu::GenRandomUUID(Info.ContextID);
719           Info.EncryptedEssence = true;
720
721           if ( Options.key_id_flag )
722             {
723               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
724             }
725           else
726             {
727               create_random_uuid(Info.CryptographicKeyID);
728             }
729
730           Context = new AESEncContext;
731           result = Context->InitKey(Options.key_value);
732
733           if ( ASDCP_SUCCESS(result) )
734             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
735
736           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
737             {
738               Info.UsesHMAC = true;
739               HMAC = new HMACContext;
740               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
741             }
742         }
743
744       if ( ASDCP_SUCCESS(result) )
745         {
746           result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
747                                     Options.mca_config, Options.edit_rate);
748         }
749     }
750
751   if ( ASDCP_SUCCESS(result) )
752     {
753       result = Parser.Reset();
754       ui32_t duration = 0;
755
756       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
757         {
758           result = Parser.ReadFrame(FrameBuffer);
759
760           if ( ASDCP_SUCCESS(result) )
761             {
762               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
763                 {
764                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
765                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
766                   result = RESULT_ENDOFFILE;
767                   continue;
768                 }
769
770               if ( Options.verbose_flag )
771                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
772
773               if ( ! Options.no_write_flag )
774                 {
775                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
776
777                   // The Writer class will forward the last block of ciphertext
778                   // to the encryption context for use as the IV for the next
779                   // frame. If you want to use non-sequitur IV values, un-comment
780                   // the following  line of code.
781                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
782                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
783                 }
784             }
785         }
786
787       if ( result == RESULT_ENDOFFILE )
788         result = RESULT_OK;
789     }
790
791   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
792     result = Writer.Finalize();
793
794   return result;
795 }
796
797
798
799
800 //------------------------------------------------------------------------------------------
801 // TimedText essence
802
803
804 // Write one or more plaintext timed text streams to a plaintext AS-02 file
805 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
806 //
807 Result_t
808 write_timed_text_file(CommandOptions& Options)
809 {
810   AESEncContext*    Context = 0;
811   HMACContext*      HMAC = 0;
812   AS_02::TimedText::ST2052_TextParser  Parser;
813   AS_02::TimedText::MXFWriter    Writer;
814   TimedText::FrameBuffer  FrameBuffer;
815   TimedText::TimedTextDescriptor TDesc;
816   byte_t            IV_buf[CBC_BLOCK_SIZE];
817   Kumu::FortunaRNG  RNG;
818
819   // set up essence parser
820   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
821
822   // set up MXF writer
823   if ( ASDCP_SUCCESS(result) )
824     {
825       Parser.FillTimedTextDescriptor(TDesc);
826       FrameBuffer.Capacity(Options.fb_size);
827
828       if ( Options.verbose_flag )
829         {
830           fputs("IMF Timed-Text Descriptor:\n", stderr);
831           TimedText::DescriptorDump(TDesc);
832         }
833     }
834
835   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
836     {
837       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
838       if ( Options.asset_id_flag )
839         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
840       else
841         Kumu::GenRandomUUID(Info.AssetUUID);
842
843       // configure encryption
844       if( Options.key_flag )
845         {
846           Kumu::GenRandomUUID(Info.ContextID);
847           Info.EncryptedEssence = true;
848
849           if ( Options.key_id_flag )
850             {
851               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
852             }
853           else
854             {
855               create_random_uuid(Info.CryptographicKeyID);
856             }
857
858           Context = new AESEncContext;
859           result = Context->InitKey(Options.key_value);
860
861           if ( ASDCP_SUCCESS(result) )
862             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
863
864           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
865             {
866               Info.UsesHMAC = true;
867               HMAC = new HMACContext;
868               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
869             }
870         }
871
872       if ( ASDCP_SUCCESS(result) )
873         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
874     }
875
876   if ( ASDCP_FAILURE(result) )
877     return result;
878
879   std::string XMLDoc;
880   TimedText::ResourceList_t::const_iterator ri;
881
882   result = Parser.ReadTimedTextResource(XMLDoc);
883
884   if ( ASDCP_SUCCESS(result) )
885     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
886
887   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
888     {
889       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
890
891       if ( ASDCP_SUCCESS(result) )
892         {
893           if ( Options.verbose_flag )
894             FrameBuffer.Dump(stderr, Options.fb_dump_size);
895
896           if ( ! Options.no_write_flag )
897             {
898               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
899
900               // The Writer class will forward the last block of ciphertext
901               // to the encryption context for use as the IV for the next
902               // frame. If you want to use non-sequitur IV values, un-comment
903               // the following  line of code.
904               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
905               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
906             }
907         }
908
909       if ( result == RESULT_ENDOFFILE )
910         result = RESULT_OK;
911     }
912
913   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
914     result = Writer.Finalize();
915
916   return result;
917 }
918
919 //
920 int
921 main(int argc, const char** argv)
922 {
923   Result_t result = RESULT_OK;
924   char     str_buf[64];
925   g_dict = &ASDCP::DefaultSMPTEDict();
926   assert(g_dict);
927
928   CommandOptions Options(argc, argv);
929
930   if ( Options.version_flag )
931     banner();
932
933   if ( Options.help_flag )
934     usage();
935
936   if ( Options.show_ul_values_flag )
937     {
938       g_dict->Dump(stdout);
939     }
940
941   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
942     return 0;
943
944   if ( Options.error_flag )
945     {
946       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
947       return 3;
948     }
949
950   EssenceType_t EssenceType;
951   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
952
953   if ( ASDCP_SUCCESS(result) )
954     {
955       switch ( EssenceType )
956         {
957         case ESS_JPEG_2000:
958           result = write_JP2K_file(Options);
959           break;
960
961         case ESS_PCM_24b_48k:
962         case ESS_PCM_24b_96k:
963           result = write_PCM_file(Options);
964           break;
965
966         default:
967           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
968                   Options.filenames.front().c_str());
969           return 5;
970         }
971     }
972
973   if ( ASDCP_FAILURE(result) )
974     {
975       fputs("Program stopped on error.\n", stderr);
976
977       if ( result != RESULT_FAIL )
978         {
979           fputs(result, stderr);
980           fputc('\n', stderr);
981         }
982
983       return 1;
984     }
985
986   return 0;
987 }
988
989
990 //
991 // end as-02-wrap.cpp
992 //