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