2.5.11.
[asdcplib-cth.git] / src / as-02-wrap.cpp
1 /*
2 Copyright (c) 2011-2015, 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: as-02-wrap.cpp,v 1.22 2015/10/16 16:55:33 jhurst Exp $       
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-2015, 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 //
172 //
173 class CommandOptions
174 {
175   CommandOptions();
176
177 public:
178   bool   error_flag;     // true if the given options are in error or not complete
179   bool   key_flag;       // true if an encryption key was given
180   bool   asset_id_flag;  // true if an asset ID was given
181   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
182   bool   write_hmac;     // true if HMAC values are to be generated and written
183   bool   verbose_flag;   // true if the verbose option was selected
184   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
185   bool   no_write_flag;  // true if no output files are to be written
186   bool   version_flag;   // true if the version display option was selected
187   bool   help_flag;      // true if the help display option was selected
188   ui32_t duration;       // number of frames to be processed
189   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
190   bool use_cdci_descriptor; // 
191   Rational edit_rate;    // edit rate of JP2K sequence
192   ui32_t fb_size;        // size of picture frame buffer
193   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
194   bool   key_id_flag;    // true if a key ID was given
195   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
196   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
197   std::string out_file; //
198   bool show_ul_values_flag;    /// if true, dump the UL table before going tp work.
199   Kumu::PathList_t filenames;  // list of filenames to be processed
200
201   UL channel_assignment;
202   ASDCP::MXF::AS02_MCAConfigParser mca_config;
203
204   UL picture_coding;
205   ui32_t rgba_MaxRef;
206   ui32_t rgba_MinRef;
207
208   ui32_t horizontal_subsampling;
209   ui32_t vertical_subsampling;
210   ui32_t component_depth;
211   ui8_t frame_layout;
212   ASDCP::Rational aspect_ratio;
213   ui8_t field_dominance;
214   ui32_t mxf_header_size;
215
216   //new attributes for AS-02 support 
217   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
218   ui32_t partition_space; //Shim parameter partition_spacing
219
220   //
221   CommandOptions(int argc, const char** argv) :
222     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
223     encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
224     no_write_flag(false), version_flag(false), help_flag(false),
225     duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
226     show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
227     mca_config(g_dict), rgba_MaxRef(1023), rgba_MinRef(0),
228     horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
229     frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
230     mxf_header_size(16384)
231   {
232     memset(key_value, 0, KeyLen);
233     memset(key_id_value, 0, UUIDlen);
234
235     for ( int i = 1; i < argc; i++ )
236       {
237
238         if ( (strcmp( argv[i], "-help") == 0) )
239           {
240             help_flag = true;
241             continue;
242           }
243          
244         if ( argv[i][0] == '-'
245              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
246              && argv[i][2] == 0 )
247           {
248             switch ( argv[i][1] )
249               {
250               case 'A':
251                 TEST_EXTRA_ARG(i, 'A');
252                 if ( ! DecodeRational(argv[i], aspect_ratio) )
253                   {
254                     fprintf(stderr, "Error decoding aspect ratio value: %s\n", argv[i]);
255                     return;
256                   }
257                 break;
258
259               case 'a':
260                 asset_id_flag = true;
261                 TEST_EXTRA_ARG(i, 'a');
262                 {
263                   ui32_t length;
264                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
265
266                   if ( length != UUIDlen )
267                     {
268                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
269                       return;
270                     }
271                 }
272                 break;
273
274               case 'b':
275                 TEST_EXTRA_ARG(i, 'b');
276                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
277
278                 if ( verbose_flag )
279                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
280
281                 break;
282
283               case 'C':
284                 TEST_EXTRA_ARG(i, 'C');
285                 if ( ! channel_assignment.DecodeHex(argv[i]) )
286                   {
287                     fprintf(stderr, "Error decoding ChannelAssignment UL value: %s\n", argv[i]);
288                     return;
289                   }
290                 break;
291
292               case 'D':
293                 TEST_EXTRA_ARG(i, 'D');
294                 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
295                 break;
296
297               case 'd':
298                 TEST_EXTRA_ARG(i, 'd');
299                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
300                 break;
301
302               case 'E': encrypt_header_flag = false; break;
303               case 'e': encrypt_header_flag = true; break;
304
305               case 'F':
306                 TEST_EXTRA_ARG(i, 'F');
307                 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
308                 if ( field_dominance > 1 )
309                   {
310                     fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
311                     return;
312                   }
313                 break;
314
315               case 'h': help_flag = true; break;
316
317               case 'i':
318                 frame_layout = 1;
319                 use_cdci_descriptor = true;
320                 break;
321
322               case 'j':
323                 key_id_flag = true;
324                 TEST_EXTRA_ARG(i, 'j');
325                 {
326                   ui32_t length;
327                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
328
329                   if ( length != UUIDlen )
330                     {
331                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
332                       return;
333                     }
334                 }
335                 break;
336
337               case 'k': key_flag = true;
338                 TEST_EXTRA_ARG(i, 'k');
339                 {
340                   ui32_t length;
341                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
342
343                   if ( length != KeyLen )
344                     {
345                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
346                       return;
347                     }
348                 }
349                 break;
350
351               case 'M': write_hmac = false; break;
352
353               case 'm':
354                 TEST_EXTRA_ARG(i, 'm');
355                 if ( ! mca_config.DecodeString(argv[i]) )
356                   {
357                     return;
358                   }
359                 break;
360
361               case 'p':
362                 TEST_EXTRA_ARG(i, 'p');
363                 if ( ! picture_coding.DecodeHex(argv[i]) )
364                   {
365                     fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
366                     return;
367                   }
368                 break;
369
370               case 'r':
371                 TEST_EXTRA_ARG(i, 'r');
372                 if ( ! DecodeRational(argv[i], edit_rate) )
373                   {
374                     fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
375                     return;
376                   }
377                 
378                 break;
379
380               case 'R':
381                 use_cdci_descriptor = false;
382                 break;
383
384               case 's':
385                 TEST_EXTRA_ARG(i, 's');
386                 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
387                 break;
388
389               case 't':
390                 TEST_EXTRA_ARG(i, 't');
391                 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
392                 break;
393
394               case 'T':
395                 TEST_EXTRA_ARG(i, 'T');
396                 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
397                 break;
398
399               case 'u': show_ul_values_flag = true; break;
400               case 'V': version_flag = true; break;
401               case 'v': verbose_flag = true; break;
402               case 'W': no_write_flag = true; break;
403
404               case 'x':
405                 TEST_EXTRA_ARG(i, 'x');
406                 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
407                 break;
408
409               case 'X':
410                 TEST_EXTRA_ARG(i, 'X');
411                 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
412                 break;
413
414               case 'Y':
415                 use_cdci_descriptor = true;
416                 break;
417
418               case 'Z': j2c_pedantic = false; break;
419               case 'z': j2c_pedantic = true; break;
420
421               default:
422                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
423                 return;
424               }
425           }
426         else
427           {
428
429             if ( argv[i][0] != '-' )
430               {
431                 filenames.push_back(argv[i]);
432               }
433             else
434               {
435                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
436                 return;
437               }
438           }
439       }
440
441     if ( help_flag || version_flag )
442       return;
443     
444     if ( filenames.size() < 2 )
445       {
446         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
447         return;
448       }
449
450     out_file = filenames.back();
451     filenames.pop_back();
452
453     if ( ! picture_coding.HasValue() )
454       {
455         picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
456       }
457
458     error_flag = false;
459   }
460 };
461
462
463 //------------------------------------------------------------------------------------------
464 // JPEG 2000 essence
465
466 namespace ASDCP {
467   Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
468                             const ASDCP::Dictionary& dict,
469                             ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
470                             ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
471
472   Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
473 }
474
475 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
476 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
477 //
478 Result_t
479 write_JP2K_file(CommandOptions& Options)
480 {
481   AESEncContext*          Context = 0;
482   HMACContext*            HMAC = 0;
483   AS_02::JP2K::MXFWriter  Writer;
484   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
485   JP2K::SequenceParser    Parser;
486   byte_t                  IV_buf[CBC_BLOCK_SIZE];
487   Kumu::FortunaRNG        RNG;
488   ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
489   ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
490
491   // set up essence parser
492   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
493
494   // set up MXF writer
495   if ( ASDCP_SUCCESS(result) )
496     {
497       ASDCP::JP2K::PictureDescriptor PDesc;
498       Parser.FillPictureDescriptor(PDesc);
499       PDesc.EditRate = Options.edit_rate;
500
501       if ( Options.verbose_flag )
502         {
503           fprintf(stderr, "JPEG 2000 pictures\n");
504           fputs("PictureDescriptor:\n", stderr);
505           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
506           JP2K::PictureDescriptorDump(PDesc);
507         }
508
509       if ( Options.use_cdci_descriptor )
510         {
511           ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
512           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
513           
514           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
515                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
516                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
517
518           if ( ASDCP_SUCCESS(result) )
519             {
520               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
521               tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
522               tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
523               tmp_dscr->ComponentDepth = Options.component_depth;
524               tmp_dscr->FrameLayout = Options.frame_layout;
525               tmp_dscr->AspectRatio = Options.aspect_ratio;
526               tmp_dscr->FieldDominance = Options.field_dominance;
527               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
528             }
529         }
530       else
531         { // use RGB
532           ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
533           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
534           
535           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
536                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
537                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
538
539           if ( ASDCP_SUCCESS(result) )
540             {
541               tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
542               tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
543               tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
544               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
545             }
546         }
547     }
548
549   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
550     {
551       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
552       Info.LabelSetType = LS_MXF_SMPTE;
553
554       if ( Options.asset_id_flag )
555         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
556       else
557         Kumu::GenRandomUUID(Info.AssetUUID);
558
559       // configure encryption
560       if( Options.key_flag )
561         {
562           Kumu::GenRandomUUID(Info.ContextID);
563           Info.EncryptedEssence = true;
564
565           if ( Options.key_id_flag )
566             {
567               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
568             }
569           else
570             {
571               create_random_uuid(Info.CryptographicKeyID);
572             }
573
574           Context = new AESEncContext;
575           result = Context->InitKey(Options.key_value);
576
577           if ( ASDCP_SUCCESS(result) )
578             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
579
580           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
581             {
582               Info.UsesHMAC = true;
583               HMAC = new HMACContext;
584               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
585             }
586         }
587
588       if ( ASDCP_SUCCESS(result) )
589         {
590           result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
591                                     Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
592         }
593     }
594
595   if ( ASDCP_SUCCESS(result) )
596     {
597       ui32_t duration = 0;
598       result = Parser.Reset();
599
600       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
601         {
602           result = Parser.ReadFrame(FrameBuffer);
603           
604           if ( ASDCP_SUCCESS(result) )
605             {
606               if ( Options.verbose_flag )
607                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
608               
609               if ( Options.encrypt_header_flag )
610                 FrameBuffer.PlaintextOffset(0);
611             }
612
613           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
614             {
615               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
616
617               // The Writer class will forward the last block of ciphertext
618               // to the encryption context for use as the IV for the next
619               // frame. If you want to use non-sequitur IV values, un-comment
620               // the following  line of code.
621               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
622               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
623             }
624         }
625
626       if ( result == RESULT_ENDOFFILE )
627         result = RESULT_OK;
628     }
629
630   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
631     result = Writer.Finalize();
632
633   return result;
634 }
635
636 //------------------------------------------------------------------------------------------
637 // PCM essence
638
639
640 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
641 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
642 //
643 Result_t
644 write_PCM_file(CommandOptions& Options)
645 {
646   AESEncContext*    Context = 0;
647   HMACContext*      HMAC = 0;
648   PCMParserList     Parser;
649   AS_02::PCM::MXFWriter    Writer;
650   PCM::FrameBuffer  FrameBuffer;
651   byte_t            IV_buf[CBC_BLOCK_SIZE];
652   Kumu::FortunaRNG  RNG;
653   ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
654
655   // set up essence parser
656   Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
657
658   // set up MXF writer
659   if ( ASDCP_SUCCESS(result) )
660     {
661       ASDCP::PCM::AudioDescriptor ADesc;
662       Parser.FillAudioDescriptor(ADesc);
663
664       ADesc.EditRate = Options.edit_rate;
665       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
666
667       if ( Options.verbose_flag )
668         {
669           char buf[64];
670           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
671                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
672                   RationalToString(Options.edit_rate, buf, 64),
673                   PCM::CalcSamplesPerFrame(ADesc));
674           fputs("AudioDescriptor:\n", stderr);
675           PCM::AudioDescriptorDump(ADesc);
676         }
677
678       essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
679
680       result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
681
682       if ( Options.mca_config.empty() )
683         {
684           essence_descriptor->ChannelAssignment = Options.channel_assignment;
685         }
686       else
687         {
688           if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
689             {
690               fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
691                       Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
692               return RESULT_FAIL;
693             }
694
695           // this is the d-cinema MCA label, what is the one for IMF?
696           essence_descriptor->ChannelAssignment = g_dict->ul(MDD_IMFAudioChannelCfg_MCA);
697         }
698     }
699
700   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
701     {
702       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
703       Info.LabelSetType = LS_MXF_SMPTE;
704
705       if ( Options.asset_id_flag )
706         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
707       else
708         Kumu::GenRandomUUID(Info.AssetUUID);
709
710       // configure encryption
711       if( Options.key_flag )
712         {
713           Kumu::GenRandomUUID(Info.ContextID);
714           Info.EncryptedEssence = true;
715
716           if ( Options.key_id_flag )
717             {
718               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
719             }
720           else
721             {
722               create_random_uuid(Info.CryptographicKeyID);
723             }
724
725           Context = new AESEncContext;
726           result = Context->InitKey(Options.key_value);
727
728           if ( ASDCP_SUCCESS(result) )
729             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
730
731           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
732             {
733               Info.UsesHMAC = true;
734               HMAC = new HMACContext;
735               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
736             }
737         }
738
739       if ( ASDCP_SUCCESS(result) )
740         {
741           result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
742                                     Options.mca_config, Options.edit_rate);
743         }
744     }
745
746   if ( ASDCP_SUCCESS(result) )
747     {
748       result = Parser.Reset();
749       ui32_t duration = 0;
750
751       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
752         {
753           result = Parser.ReadFrame(FrameBuffer);
754
755           if ( ASDCP_SUCCESS(result) )
756             {
757               if ( Options.verbose_flag )
758                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
759
760               if ( ! Options.no_write_flag )
761                 {
762                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
763
764                   // The Writer class will forward the last block of ciphertext
765                   // to the encryption context for use as the IV for the next
766                   // frame. If you want to use non-sequitur IV values, un-comment
767                   // the following  line of code.
768                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
769                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
770                 }
771             }
772         }
773
774       if ( result == RESULT_ENDOFFILE )
775         result = RESULT_OK;
776     }
777
778   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
779     result = Writer.Finalize();
780
781   return result;
782 }
783
784
785
786
787 //------------------------------------------------------------------------------------------
788 // TimedText essence
789
790
791 // Write one or more plaintext timed text streams to a plaintext AS-02 file
792 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
793 //
794 Result_t
795 write_timed_text_file(CommandOptions& Options)
796 {
797   AESEncContext*    Context = 0;
798   HMACContext*      HMAC = 0;
799   AS_02::TimedText::ST2052_TextParser  Parser;
800   AS_02::TimedText::MXFWriter    Writer;
801   TimedText::FrameBuffer  FrameBuffer;
802   TimedText::TimedTextDescriptor TDesc;
803   byte_t            IV_buf[CBC_BLOCK_SIZE];
804   Kumu::FortunaRNG  RNG;
805
806   // set up essence parser
807   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
808
809   // set up MXF writer
810   if ( ASDCP_SUCCESS(result) )
811     {
812       Parser.FillTimedTextDescriptor(TDesc);
813       TDesc.EditRate = Options.edit_rate;
814       TDesc.ContainerDuration = Options.duration;
815       FrameBuffer.Capacity(Options.fb_size);
816
817       if ( Options.verbose_flag )
818         {
819           fputs("IMF Timed-Text Descriptor:\n", stderr);
820           TimedText::DescriptorDump(TDesc);
821         }
822     }
823
824   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
825     {
826       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
827       Info.LabelSetType = LS_MXF_SMPTE;
828
829       if ( Options.asset_id_flag )
830         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
831       else
832         Kumu::GenRandomUUID(Info.AssetUUID);
833
834       // configure encryption
835       if( Options.key_flag )
836         {
837           Kumu::GenRandomUUID(Info.ContextID);
838           Info.EncryptedEssence = true;
839
840           if ( Options.key_id_flag )
841             {
842               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
843             }
844           else
845             {
846               create_random_uuid(Info.CryptographicKeyID);
847             }
848
849           Context = new AESEncContext;
850           result = Context->InitKey(Options.key_value);
851
852           if ( ASDCP_SUCCESS(result) )
853             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
854
855           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
856             {
857               Info.UsesHMAC = true;
858               HMAC = new HMACContext;
859               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
860             }
861         }
862
863       if ( ASDCP_SUCCESS(result) )
864         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
865     }
866
867   if ( ASDCP_FAILURE(result) )
868     return result;
869
870   std::string XMLDoc;
871   TimedText::ResourceList_t::const_iterator ri;
872
873   result = Parser.ReadTimedTextResource(XMLDoc);
874
875   if ( ASDCP_SUCCESS(result) )
876     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
877
878   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
879     {
880       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
881
882       if ( ASDCP_SUCCESS(result) )
883         {
884           if ( Options.verbose_flag )
885             FrameBuffer.Dump(stderr, Options.fb_dump_size);
886
887           if ( ! Options.no_write_flag )
888             {
889               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
890
891               // The Writer class will forward the last block of ciphertext
892               // to the encryption context for use as the IV for the next
893               // frame. If you want to use non-sequitur IV values, un-comment
894               // the following  line of code.
895               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
896               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
897             }
898         }
899
900       if ( result == RESULT_ENDOFFILE )
901         result = RESULT_OK;
902     }
903
904   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
905     result = Writer.Finalize();
906
907   return result;
908 }
909
910 //
911 int
912 main(int argc, const char** argv)
913 {
914   Result_t result = RESULT_OK;
915   char     str_buf[64];
916   g_dict = &ASDCP::DefaultSMPTEDict();
917   assert(g_dict);
918
919   CommandOptions Options(argc, argv);
920
921   if ( Options.version_flag )
922     banner();
923
924   if ( Options.help_flag )
925     usage();
926
927   if ( Options.show_ul_values_flag )
928     {
929       g_dict->Dump(stdout);
930     }
931
932   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
933     return 0;
934
935   if ( Options.error_flag )
936     {
937       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
938       return 3;
939     }
940
941   EssenceType_t EssenceType;
942   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
943
944   if ( ASDCP_SUCCESS(result) )
945     {
946       switch ( EssenceType )
947         {
948         case ESS_JPEG_2000:
949           result = write_JP2K_file(Options);
950           break;
951
952         case ESS_PCM_24b_48k:
953         case ESS_PCM_24b_96k:
954           result = write_PCM_file(Options);
955           break;
956
957         case ESS_TIMED_TEXT:
958           result = write_timed_text_file(Options);
959           break;
960
961         default:
962           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
963                   Options.filenames.front().c_str());
964           return 5;
965         }
966     }
967
968   if ( ASDCP_FAILURE(result) )
969     {
970       fputs("Program stopped on error.\n", stderr);
971
972       if ( result != RESULT_FAIL )
973         {
974           fputs(result, stderr);
975           fputc('\n', stderr);
976         }
977
978       return 1;
979     }
980
981   return 0;
982 }
983
984
985 //
986 // end as-02-wrap.cpp
987 //