o Added preliminary support for timed-text wrapping for AS-02. This
[asdcplib.git] / src / asdcp-wrap.cpp
1 /*
2 Copyright (c) 2003-2013, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    asdcp-wrap.cpp
28     \version $Id$
29     \brief   AS-DCP file manipulation utility
30
31   This program wraps d-cinema essence (picture, sound or text) into an AS-DCP
32   MXF file.
33
34   For more information about asdcplib, please refer to the header file AS_DCP.h
35
36   WARNING: While the asdcplib library attempts to provide a complete and secure
37   implementation of the cryptographic features of the AS-DCP file formats, this
38   unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
39   production environment without some modification.
40
41   In particular, this program uses weak IV generation and externally generated
42   plaintext keys. These shortcomings exist because cryptographic-quality
43   random number generation and key management are outside the scope of the
44   asdcplib library. Developers using asdcplib for commercial implementations
45   claiming SMPTE conformance are expected to provide proper implementations of
46   these features.
47 */
48
49 #include <KM_fileio.h>
50 #include <KM_prng.h>
51 #include <AtmosSyncChannel_Mixer.h>
52 #include <AS_DCP.h>
53 #include <PCMParserList.h>
54 #include <Metadata.h>
55
56 using namespace ASDCP;
57
58 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
59
60 const byte_t P_HFR_UL_2K[16] = {
61   0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
62   0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03
63 };
64
65 const ASDCP::Dictionary *g_dict = 0;
66
67 //------------------------------------------------------------------------------------------
68 //
69 // command line option parser class
70
71 static const char* PROGRAM_NAME = "asdcp-wrap";  // program name for messages
72
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
75 {
76 public:
77   MyInfo()
78   {
79       static byte_t default_ProductUUID_Data[UUIDlen] =
80       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
82
83       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84       CompanyName = "WidgetCo";
85       ProductName = "asdcp-wrap";
86       ProductVersion = ASDCP::Version();
87   }
88 } s_MyInfo;
89
90
91
92 // Increment the iterator, test for an additional non-option command line argument.
93 // Causes the caller to return if there are no remaining arguments or if the next
94 // argument begins with '-'.
95 #define TEST_EXTRA_ARG(i,c)                                             \
96   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
97     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
98     return;                                                             \
99   }
100
101 //
102 void
103 banner(FILE* stream = stdout)
104 {
105   fprintf(stream, "\n\
106 %s (asdcplib %s)\n\n\
107 Copyright (c) 2003-2012 John Hurst\n\n\
108 asdcplib may be copied only under the terms of the license found at\n\
109 the top of every file in the asdcplib distribution kit.\n\n\
110 Specify the -h (help) option for further information about %s\n\n",
111           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
112 }
113
114 //
115 void
116 usage(FILE* stream = stdout)
117 {
118   fprintf(stream, "\
119 USAGE: %s [-h|-help] [-V]\n\
120 \n\
121        %s [-3] [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
122           [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
123           [-l <label>] [-L] [-M] [-m <expr>] [-p <frame-rate>] [-s] [-v]\n\
124           [-W] [-z|-Z] <input-file>+ <output-file>\n\n",
125           PROGRAM_NAME, PROGRAM_NAME);
126
127   fprintf(stream, "\
128 Options:\n\
129   -3                - Create a stereoscopic image file. Expects two\n\
130                       directories of JP2K codestreams (directories must have\n\
131                       an equal number of frames; the left eye is first)\n\
132   -C <UL>           - Set ChannelAssignment UL value in a PCM file\n\
133   -h | -help        - Show help\n\
134   -V                - Show version information\n\
135   -e                - Encrypt MPEG or JP2K headers (default)\n\
136   -E                - Do not encrypt MPEG or JP2K headers\n\
137   -j <key-id-str>   - Write key ID instead of creating a random value\n\
138   -k <key-string>   - Use key for ciphertext operations\n\
139   -M                - Do not create HMAC values when writing\n\
140   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
141                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
142   -a <UUID>         - Specify the Asset ID of the file\n\
143   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
144                       Defaults to 4,194,304 (4MB)\n\
145   -d <duration>     - Number of frames to process, default all\n\
146   -f <start-frame>  - Starting frame number, default 0\n\
147   -l <label>        - Use given channel format label when writing MXF sound\n\
148                       files. SMPTE 429-2 labels: '5.1', '6.1', '7.1',\n\
149                       '7.1DS', 'WTF'\n\
150                       Default is no label (valid for Interop only).\n\
151   -L                - Write SMPTE UL values instead of MXF Interop\n\
152   -P <UL>           - Set PictureEssenceCoding UL value in a JP2K file\n\
153   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
154                       Use one of [23|24|25|30|48|50|60], 24 is default\n\
155   -s                - Insert a Dolby Atmos synchronization channel when\n\
156                       wrapping PCM. This implies a -L option(SMPTE ULs) and \n\
157                       will overide -C and -l options with Configuration 4 \n\
158                       Channel Assigment and no format label respectively. \n\
159   -v                - Verbose, prints informative messages to stderr\n\
160   -W                - Read input file only, do not write source file\n\
161   -z                - Fail if j2c inputs have unequal parameters (default)\n\
162   -Z                - Ignore unequal parameters in j2c inputs\n\
163 \n\
164   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
165          o All option arguments must be separated from the option by whitespace.\n\
166          o An argument of \"23\" to the -p option will be interpreted\n\
167            as 24000/1001 fps.\n\
168 \n");
169 }
170
171 //
172 PCM::ChannelFormat_t
173 decode_channel_fmt(const std::string& label_name)
174 {
175   if ( label_name == "5.1" )
176     return PCM::CF_CFG_1;
177
178   else if ( label_name == "6.1" )
179     return PCM::CF_CFG_2;
180
181   else if ( label_name == "7.1" )
182     return PCM::CF_CFG_3;
183
184   else if ( label_name == "WTF" )
185     return PCM::CF_CFG_4;
186
187   else if ( label_name == "7.1DS" )
188     return PCM::CF_CFG_5;
189
190   fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
191   fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
192   return PCM::CF_NONE;
193 }
194
195 //
196 //
197 class CommandOptions
198 {
199   CommandOptions();
200
201 public:
202   bool   error_flag;     // true if the given options are in error or not complete
203   bool   key_flag;       // true if an encryption key was given
204   bool   asset_id_flag;  // true if an asset ID was given
205   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
206   bool   write_hmac;     // true if HMAC values are to be generated and written
207   bool   verbose_flag;   // true if the verbose option was selected
208   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
209   bool   no_write_flag;  // true if no output files are to be written
210   bool   version_flag;   // true if the version display option was selected
211   bool   help_flag;      // true if the help display option was selected
212   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
213   ui32_t start_frame;    // frame number to begin processing
214   ui32_t duration;       // number of frames to be processed
215   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
216   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
217   ui32_t picture_rate;   // fps of picture when wrapping PCM
218   ui32_t fb_size;        // size of picture frame buffer
219   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
220   bool   key_id_flag;    // true if a key ID was given
221   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
222   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
223   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
224   std::string out_file; //
225   bool show_ul_values_flag;    /// if true, dump the UL table before going to work.
226   Kumu::PathList_t filenames;  // list of filenames to be processed
227   UL channel_assignment;
228   UL picture_coding;
229   bool dolby_atmos_sync_flag;  // if true, insert a Dolby Atmos Synchronization channel.
230   ui32_t ffoa;  /// first frame of action for atmos wrapping
231   ui32_t max_channel_count; /// max channel count for atmos wrapping
232   ui32_t max_object_count; /// max object count for atmos wrapping
233   ASDCP::MXF::ASDCP_MCAConfigParser mca_config;
234
235   //
236   Rational PictureRate()
237   {
238     if ( picture_rate == 16 ) return EditRate_16;
239     if ( picture_rate == 18 ) return EditRate_18;
240     if ( picture_rate == 20 ) return EditRate_20;
241     if ( picture_rate == 22 ) return EditRate_22;
242     if ( picture_rate == 23 ) return EditRate_23_98;
243     if ( picture_rate == 24 ) return EditRate_24;
244     if ( picture_rate == 25 ) return EditRate_25;
245     if ( picture_rate == 30 ) return EditRate_30;
246     if ( picture_rate == 48 ) return EditRate_48;
247     if ( picture_rate == 50 ) return EditRate_50;
248     if ( picture_rate == 60 ) return EditRate_60;
249     if ( picture_rate == 96 ) return EditRate_96;
250     if ( picture_rate == 100 ) return EditRate_100;
251     if ( picture_rate == 120 ) return EditRate_120;
252     return EditRate_24;
253   }
254
255   //
256   const char* szPictureRate()
257   {
258     if ( picture_rate == 16 ) return "16";
259     if ( picture_rate == 18 ) return "18.182";
260     if ( picture_rate == 20 ) return "20";
261     if ( picture_rate == 22 ) return "21.818";
262     if ( picture_rate == 23 ) return "23.976";
263     if ( picture_rate == 24 ) return "24";
264     if ( picture_rate == 25 ) return "25";
265     if ( picture_rate == 30 ) return "30";
266     if ( picture_rate == 48 ) return "48";
267     if ( picture_rate == 50 ) return "50";
268     if ( picture_rate == 60 ) return "60";
269     if ( picture_rate == 96 ) return "96";
270     if ( picture_rate == 100 ) return "100";
271     if ( picture_rate == 120 ) return "120";
272     return "24";
273   }
274
275   //
276   CommandOptions(int argc, const char** argv) :
277     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
278     encrypt_header_flag(true), write_hmac(true),
279     verbose_flag(false), fb_dump_size(0),
280     no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
281     start_frame(0),
282     duration(0xffffffff), use_smpte_labels(false), j2c_pedantic(true),
283     fb_size(FRAME_BUFFER_SIZE),
284     channel_fmt(PCM::CF_NONE),
285     ffoa(0), max_channel_count(10), max_object_count(118), // hard-coded sample atmos properties
286     dolby_atmos_sync_flag(false),
287     show_ul_values_flag(false),
288     mca_config(g_dict)
289   {
290     memset(key_value, 0, KeyLen);
291     memset(key_id_value, 0, UUIDlen);
292
293     for ( int i = 1; i < argc; i++ )
294       {
295
296         if ( (strcmp( argv[i], "-help") == 0) )
297           {
298             help_flag = true;
299             continue;
300           }
301
302         if ( argv[i][0] == '-'
303              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
304              && argv[i][2] == 0 )
305           {
306             switch ( argv[i][1] )
307               {
308               case '3': stereo_image_flag = true; break;
309
310               case 'a':
311                 asset_id_flag = true;
312                 TEST_EXTRA_ARG(i, 'a');
313                 {
314                   ui32_t length;
315                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
316
317                   if ( length != UUIDlen )
318                     {
319                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
320                       return;
321                     }
322                 }
323                 break;
324
325               case 'b':
326                 TEST_EXTRA_ARG(i, 'b');
327                 fb_size = abs(atoi(argv[i]));
328
329                 if ( verbose_flag )
330                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
331
332                 break;
333
334               case 'C':
335                 TEST_EXTRA_ARG(i, 'C');
336                 if ( ! channel_assignment.DecodeHex(argv[i]) )
337                   {
338                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
339                     return;
340                   }
341                 break;
342
343               case 'd':
344                 TEST_EXTRA_ARG(i, 'd');
345                 duration = abs(atoi(argv[i]));
346                 break;
347
348               case 'E': encrypt_header_flag = false; break;
349               case 'e': encrypt_header_flag = true; break;
350
351               case 'f':
352                 TEST_EXTRA_ARG(i, 'f');
353                 start_frame = abs(atoi(argv[i]));
354                 break;
355
356               case 'h': help_flag = true; break;
357
358               case 'j': key_id_flag = true;
359                 TEST_EXTRA_ARG(i, 'j');
360                 {
361                   ui32_t length;
362                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
363
364                   if ( length != UUIDlen )
365                     {
366                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
367                       return;
368                     }
369                 }
370                 break;
371
372               case 'k': key_flag = true;
373                 TEST_EXTRA_ARG(i, 'k');
374                 {
375                   ui32_t length;
376                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
377
378                   if ( length != KeyLen )
379                     {
380                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
381                       return;
382                     }
383                 }
384                 break;
385
386               case 'l':
387                 TEST_EXTRA_ARG(i, 'l');
388                 channel_fmt = decode_channel_fmt(argv[i]);
389                 break;
390
391               case 'L': use_smpte_labels = true; break;
392               case 'M': write_hmac = false; break;
393
394               case 'm':
395                 TEST_EXTRA_ARG(i, 'm');
396                 if ( ! mca_config.DecodeString(argv[i]) )
397                   {
398                     return;
399                   }
400                 break;
401
402               case 'P':
403                 TEST_EXTRA_ARG(i, 'P');
404                 if ( ! picture_coding.DecodeHex(argv[i]) )
405                   {
406                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
407                     return;
408                   }
409                 break;
410
411               case 'p':
412                 TEST_EXTRA_ARG(i, 'p');
413                 picture_rate = abs(atoi(argv[i]));
414                 break;
415
416               case 's': dolby_atmos_sync_flag = true; break;
417               case 'u': show_ul_values_flag = true; break;
418               case 'V': version_flag = true; break;
419               case 'v': verbose_flag = true; break;
420               case 'W': no_write_flag = true; break;
421               case 'Z': j2c_pedantic = false; break;
422               case 'z': j2c_pedantic = true; break;
423
424               default:
425                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
426                 return;
427               }
428           }
429         else
430           {
431
432             if ( argv[i][0] != '-' )
433               {
434                 filenames.push_back(argv[i]);
435               }
436             else
437               {
438                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
439                 return;
440               }
441           }
442       }
443
444     if ( help_flag || version_flag )
445       return;
446
447     if ( filenames.size() < 2 )
448       {
449         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
450         return;
451       }
452
453     out_file = filenames.back();
454     filenames.pop_back();
455     error_flag = false;
456   }
457 };
458
459 //------------------------------------------------------------------------------------------
460 // MPEG2 essence
461
462 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
463 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
464 //
465 Result_t
466 write_MPEG2_file(CommandOptions& Options)
467 {
468   AESEncContext*     Context = 0;
469   HMACContext*       HMAC = 0;
470   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
471   MPEG2::Parser      Parser;
472   MPEG2::MXFWriter   Writer;
473   MPEG2::VideoDescriptor VDesc;
474   byte_t             IV_buf[CBC_BLOCK_SIZE];
475   Kumu::FortunaRNG   RNG;
476
477   // set up essence parser
478   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
479
480   // set up MXF writer
481   if ( ASDCP_SUCCESS(result) )
482     {
483       Parser.FillVideoDescriptor(VDesc);
484
485       if ( Options.verbose_flag )
486         {
487           fputs("MPEG-2 Pictures\n", stderr);
488           fputs("VideoDescriptor:\n", stderr);
489           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
490           MPEG2::VideoDescriptorDump(VDesc);
491         }
492     }
493
494   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
495     {
496       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
497       if ( Options.asset_id_flag )
498         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
499       else
500         Kumu::GenRandomUUID(Info.AssetUUID);
501
502       if ( Options.use_smpte_labels )
503         {
504           Info.LabelSetType = LS_MXF_SMPTE;
505           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
506         }
507
508       // configure encryption
509       if( Options.key_flag )
510         {
511           Kumu::GenRandomUUID(Info.ContextID);
512           Info.EncryptedEssence = true;
513
514           if ( Options.key_id_flag )
515             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
516           else
517             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
518
519           Context = new AESEncContext;
520           result = Context->InitKey(Options.key_value);
521
522           if ( ASDCP_SUCCESS(result) )
523             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
524
525           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
526             {
527               Info.UsesHMAC = true;
528               HMAC = new HMACContext;
529               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
530             }
531         }
532
533       if ( ASDCP_SUCCESS(result) )
534         result = Writer.OpenWrite(Options.out_file.c_str(), Info, VDesc);
535     }
536
537   if ( ASDCP_SUCCESS(result) )
538     // loop through the frames
539     {
540       result = Parser.Reset();
541       ui32_t duration = 0;
542
543       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
544         {
545               result = Parser.ReadFrame(FrameBuffer);
546
547               if ( ASDCP_SUCCESS(result) )
548                 {
549                   if ( Options.verbose_flag )
550                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
551
552                   if ( Options.encrypt_header_flag )
553                     FrameBuffer.PlaintextOffset(0);
554                 }
555
556           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
557             {
558               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
559
560               // The Writer class will forward the last block of ciphertext
561               // to the encryption context for use as the IV for the next
562               // frame. If you want to use non-sequitur IV values, un-comment
563               // the following  line of code.
564               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
565               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
566             }
567         }
568
569       if ( result == RESULT_ENDOFFILE )
570         result = RESULT_OK;
571     }
572
573   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
574     result = Writer.Finalize();
575
576   return result;
577 }
578
579
580 //------------------------------------------------------------------------------------------
581
582 // return false if an error is discovered
583 bool
584 check_phfr_params(CommandOptions& Options, JP2K::PictureDescriptor& PDesc)
585 {
586   Rational rate = Options.PictureRate();
587   if ( rate != EditRate_96 && rate != EditRate_100 && rate != EditRate_120 )
588     return true;
589
590   if ( PDesc.StoredWidth > 2048 )
591     {
592       fprintf(stderr, "P-HFR files currently limited to 2K.\n");
593       return false;
594     }
595
596   if ( ! Options.use_smpte_labels )
597     {
598       fprintf(stderr, "P-HFR files must be written using SMPTE labels. Use option '-L'.\n");
599       return false;
600     }
601
602   // do not set the label if the user has already done so
603   if ( ! Options.picture_coding.HasValue() )
604     Options.picture_coding = UL(P_HFR_UL_2K);
605
606   return true;
607 }
608
609 //------------------------------------------------------------------------------------------
610 // JPEG 2000 essence
611
612 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
613 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
614 //
615 Result_t
616 write_JP2K_S_file(CommandOptions& Options)
617 {
618   AESEncContext*          Context = 0;
619   HMACContext*            HMAC = 0;
620   JP2K::MXFSWriter        Writer;
621   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
622   JP2K::PictureDescriptor PDesc;
623   JP2K::SequenceParser    ParserLeft, ParserRight;
624   byte_t                  IV_buf[CBC_BLOCK_SIZE];
625   Kumu::FortunaRNG        RNG;
626
627   if ( Options.filenames.size() != 2 )
628     {
629       fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
630       return RESULT_FAIL;
631     }
632
633   // set up essence parser
634   Result_t result = ParserLeft.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
635
636   if ( ASDCP_SUCCESS(result) )
637     {
638       Options.filenames.pop_front();
639       result = ParserRight.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
640     }
641
642   // set up MXF writer
643   if ( ASDCP_SUCCESS(result) )
644     {
645       ParserLeft.FillPictureDescriptor(PDesc);
646       PDesc.EditRate = Options.PictureRate();
647
648       if ( Options.verbose_flag )
649         {
650           fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
651           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
652           JP2K::PictureDescriptorDump(PDesc);
653         }
654     }
655
656   if ( ! check_phfr_params(Options, PDesc) )
657     return RESULT_FAIL;
658
659   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
660     {
661       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
662       if ( Options.asset_id_flag )
663         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
664       else
665         Kumu::GenRandomUUID(Info.AssetUUID);
666
667       if ( Options.use_smpte_labels )
668         {
669           Info.LabelSetType = LS_MXF_SMPTE;
670           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
671         }
672
673       // configure encryption
674       if( Options.key_flag )
675         {
676           Kumu::GenRandomUUID(Info.ContextID);
677           Info.EncryptedEssence = true;
678
679           if ( Options.key_id_flag )
680             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
681           else
682             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
683
684           Context = new AESEncContext;
685           result = Context->InitKey(Options.key_value);
686
687           if ( ASDCP_SUCCESS(result) )
688             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
689
690           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
691             {
692               Info.UsesHMAC = true;
693               HMAC = new HMACContext;
694               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
695             }
696         }
697
698       if ( ASDCP_SUCCESS(result) )
699         result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
700
701       if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
702         {
703           MXF::RGBAEssenceDescriptor *descriptor = 0;
704           Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor),
705                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
706           descriptor->PictureEssenceCoding = Options.picture_coding;
707         }
708     }
709
710   if ( ASDCP_SUCCESS(result) )
711     {
712       ui32_t duration = 0;
713       result = ParserLeft.Reset();
714       if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
715
716       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
717         {
718           result = ParserLeft.ReadFrame(FrameBuffer);
719
720           if ( ASDCP_SUCCESS(result) )
721             {
722               if ( Options.verbose_flag )
723                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
724
725               if ( Options.encrypt_header_flag )
726                 FrameBuffer.PlaintextOffset(0);
727             }
728
729           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
730             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
731
732           if ( ASDCP_SUCCESS(result) )
733             result = ParserRight.ReadFrame(FrameBuffer);
734
735           if ( ASDCP_SUCCESS(result) )
736             {
737               if ( Options.verbose_flag )
738                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
739
740               if ( Options.encrypt_header_flag )
741                 FrameBuffer.PlaintextOffset(0);
742             }
743
744           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
745             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
746         }
747
748       if ( result == RESULT_ENDOFFILE )
749         result = RESULT_OK;
750     }
751
752   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
753     result = Writer.Finalize();
754
755   return result;
756 }
757
758 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
759 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
760 //
761 Result_t
762 write_JP2K_file(CommandOptions& Options)
763 {
764   AESEncContext*          Context = 0;
765   HMACContext*            HMAC = 0;
766   JP2K::MXFWriter         Writer;
767   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
768   JP2K::PictureDescriptor PDesc;
769   JP2K::SequenceParser    Parser;
770   byte_t                  IV_buf[CBC_BLOCK_SIZE];
771   Kumu::FortunaRNG        RNG;
772
773   // set up essence parser
774   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
775
776   // set up MXF writer
777   if ( ASDCP_SUCCESS(result) )
778     {
779       Parser.FillPictureDescriptor(PDesc);
780       PDesc.EditRate = Options.PictureRate();
781
782       if ( Options.verbose_flag )
783         {
784           fprintf(stderr, "JPEG 2000 pictures\n");
785           fputs("PictureDescriptor:\n", stderr);
786           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
787           JP2K::PictureDescriptorDump(PDesc);
788         }
789     }
790
791   if ( ! check_phfr_params(Options, PDesc) )
792     return RESULT_FAIL;
793
794   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
795     {
796       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
797       if ( Options.asset_id_flag )
798         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
799       else
800         Kumu::GenRandomUUID(Info.AssetUUID);
801
802       if ( Options.use_smpte_labels )
803         {
804           Info.LabelSetType = LS_MXF_SMPTE;
805           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
806         }
807
808       // configure encryption
809       if( Options.key_flag )
810         {
811           Kumu::GenRandomUUID(Info.ContextID);
812           Info.EncryptedEssence = true;
813
814           if ( Options.key_id_flag )
815             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
816           else
817             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
818
819           Context = new AESEncContext;
820           result = Context->InitKey(Options.key_value);
821
822           if ( ASDCP_SUCCESS(result) )
823             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
824
825           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
826             {
827               Info.UsesHMAC = true;
828               HMAC = new HMACContext;
829               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
830             }
831         }
832
833       if ( ASDCP_SUCCESS(result) )
834         result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
835
836       if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
837         {
838           MXF::RGBAEssenceDescriptor *descriptor = 0;
839           Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor),
840                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
841           descriptor->PictureEssenceCoding = Options.picture_coding;
842         }
843     }
844
845   if ( ASDCP_SUCCESS(result) )
846     {
847       ui32_t duration = 0;
848       result = Parser.Reset();
849
850       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
851         {
852               result = Parser.ReadFrame(FrameBuffer);
853
854               if ( ASDCP_SUCCESS(result) )
855                 {
856                   if ( Options.verbose_flag )
857                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
858
859                   if ( Options.encrypt_header_flag )
860                     FrameBuffer.PlaintextOffset(0);
861                 }
862
863           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
864             {
865               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
866
867               // The Writer class will forward the last block of ciphertext
868               // to the encryption context for use as the IV for the next
869               // frame. If you want to use non-sequitur IV values, un-comment
870               // the following  line of code.
871               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
872               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
873             }
874         }
875
876       if ( result == RESULT_ENDOFFILE )
877         result = RESULT_OK;
878     }
879
880   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
881     result = Writer.Finalize();
882
883   return result;
884 }
885
886 //------------------------------------------------------------------------------------------
887 // PCM essence
888
889
890 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
891 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
892 //
893 Result_t
894 write_PCM_file(CommandOptions& Options)
895 {
896   AESEncContext*    Context = 0;
897   HMACContext*      HMAC = 0;
898   PCMParserList     Parser;
899   PCM::MXFWriter    Writer;
900   PCM::FrameBuffer  FrameBuffer;
901   PCM::AudioDescriptor ADesc;
902   Rational          PictureRate = Options.PictureRate();
903   byte_t            IV_buf[CBC_BLOCK_SIZE];
904   Kumu::FortunaRNG  RNG;
905
906   // set up essence parser
907   Result_t result = Parser.OpenRead(Options.filenames, PictureRate);
908
909   // set up MXF writer
910   if ( ASDCP_SUCCESS(result) )
911     {
912       Parser.FillAudioDescriptor(ADesc);
913
914       ADesc.EditRate = PictureRate;
915       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
916       ADesc.ChannelFormat = Options.channel_fmt;
917
918       if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
919         {
920           fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
921         }
922
923       if ( Options.verbose_flag )
924         {
925           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
926                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
927                   Options.szPictureRate(),
928                   PCM::CalcSamplesPerFrame(ADesc));
929           fputs("AudioDescriptor:\n", stderr);
930           PCM::AudioDescriptorDump(ADesc);
931         }
932     }
933
934   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
935     {
936       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
937       if ( Options.asset_id_flag )
938         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
939       else
940         Kumu::GenRandomUUID(Info.AssetUUID);
941
942       if ( Options.use_smpte_labels )
943         {
944           Info.LabelSetType = LS_MXF_SMPTE;
945           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
946         }
947
948       // configure encryption
949       if( Options.key_flag )
950         {
951           Kumu::GenRandomUUID(Info.ContextID);
952           Info.EncryptedEssence = true;
953
954           if ( Options.key_id_flag )
955             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
956           else
957             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
958
959           Context = new AESEncContext;
960           result = Context->InitKey(Options.key_value);
961
962           if ( ASDCP_SUCCESS(result) )
963             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
964
965           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
966             {
967               Info.UsesHMAC = true;
968               HMAC = new HMACContext;
969               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
970             }
971         }
972
973       if ( ASDCP_SUCCESS(result) )
974         result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
975
976       if ( ASDCP_SUCCESS(result)
977            && ( Options.channel_assignment.HasValue()
978                 || ! Options.mca_config.empty() ) )
979         {
980           MXF::WaveAudioDescriptor *essence_descriptor = 0;
981           Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_WaveAudioDescriptor),
982                                                 reinterpret_cast<MXF::InterchangeObject**>(&essence_descriptor));
983           assert(essence_descriptor);
984
985           if ( Options.mca_config.empty() )
986             {
987               essence_descriptor->ChannelAssignment = Options.channel_assignment;
988             }
989           else
990             {
991               if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
992                 {
993                   fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
994                           Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
995                   return RESULT_FAIL;
996                 }
997
998               essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA);
999
1000               // add descriptors to the essence_descriptor and header
1001               ASDCP::MXF::InterchangeObject_list_t::iterator i;
1002               for ( i = Options.mca_config.begin(); i != Options.mca_config.end(); ++i )
1003                 {
1004                   if ( (*i)->GetUL() != UL(g_dict->ul(MDD_AudioChannelLabelSubDescriptor))
1005                        && (*i)->GetUL() != UL(g_dict->ul(MDD_SoundfieldGroupLabelSubDescriptor))
1006                        && (*i)->GetUL() != UL(g_dict->ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor)) )
1007                     {
1008                       fprintf(stderr, "Essence sub-descriptor is not an MCALabelSubDescriptor.\n");
1009                       (*i)->Dump();
1010                     }
1011
1012                   Writer.OP1aHeader().AddChildObject(*i);
1013                   essence_descriptor->SubDescriptors.push_back((*i)->InstanceUID);
1014                   *i = 0; // parent will only free the ones we don't keep
1015                 }
1016             }
1017         }
1018     }
1019
1020   if ( ASDCP_SUCCESS(result) )
1021     {
1022       result = Parser.Reset();
1023       ui32_t duration = 0;
1024
1025       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1026         {
1027           result = Parser.ReadFrame(FrameBuffer);
1028
1029           if ( ASDCP_SUCCESS(result) )
1030             {
1031               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1032                 {
1033                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1034                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1035                   result = RESULT_ENDOFFILE;
1036                   continue;
1037                 }
1038
1039               if ( Options.verbose_flag )
1040                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1041
1042               if ( ! Options.no_write_flag )
1043                 {
1044                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1045
1046                   // The Writer class will forward the last block of ciphertext
1047                   // to the encryption context for use as the IV for the next
1048                   // frame. If you want to use non-sequitur IV values, un-comment
1049                   // the following  line of code.
1050                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1051                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1052                 }
1053             }
1054         }
1055
1056       if ( result == RESULT_ENDOFFILE )
1057         result = RESULT_OK;
1058     }
1059
1060   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1061     result = Writer.Finalize();
1062
1063   return result;
1064 }
1065
1066 // Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a plaintext ASDCP file
1067 // Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a ciphertext ASDCP file
1068 //
1069 Result_t
1070 write_PCM_with_ATMOS_sync_file(CommandOptions& Options)
1071 {
1072   AESEncContext*        Context = 0;
1073   HMACContext*          HMAC = 0;
1074   PCM::MXFWriter        Writer;
1075   PCM::FrameBuffer      FrameBuffer;
1076   PCM::AudioDescriptor  ADesc;
1077   Rational              PictureRate = Options.PictureRate();
1078   byte_t                IV_buf[CBC_BLOCK_SIZE];
1079   Kumu::FortunaRNG      RNG;
1080
1081   WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1082   if ( Options.asset_id_flag )
1083         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1084   else
1085         Kumu::GenRandomUUID(Info.AssetUUID);
1086   AtmosSyncChannelMixer Mixer(Info.AssetUUID);
1087
1088   // set up essence parser
1089   Result_t result = Mixer.OpenRead(Options.filenames, PictureRate);
1090
1091   // set up MXF writer
1092   if ( ASDCP_SUCCESS(result) )
1093   {
1094     Mixer.FillAudioDescriptor(ADesc);
1095
1096     ADesc.EditRate = PictureRate;
1097     FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1098     ADesc.ChannelFormat = PCM::CF_CFG_4;
1099
1100     if ( Options.verbose_flag )
1101         {
1102           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1103               ADesc.AudioSamplingRate.Quotient() / 1000.0,
1104               Options.szPictureRate(),
1105               PCM::CalcSamplesPerFrame(ADesc));
1106           fputs("AudioDescriptor:\n", stderr);
1107           PCM::AudioDescriptorDump(ADesc);
1108         }
1109   }
1110
1111   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1112   {
1113     Info.LabelSetType = LS_MXF_SMPTE;
1114     fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1115
1116     // configure encryption
1117     if( Options.key_flag )
1118         {
1119           Kumu::GenRandomUUID(Info.ContextID);
1120           Info.EncryptedEssence = true;
1121
1122           if ( Options.key_id_flag )
1123             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1124           else
1125             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1126
1127           Context = new AESEncContext;
1128           result = Context->InitKey(Options.key_value);
1129
1130           if ( ASDCP_SUCCESS(result) )
1131             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1132
1133           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1134       {
1135         Info.UsesHMAC = true;
1136         HMAC = new HMACContext;
1137         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1138       }
1139         }
1140
1141     if ( ASDCP_SUCCESS(result) )
1142       result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
1143   }
1144
1145   if ( ASDCP_SUCCESS(result) )
1146   {
1147     result = Mixer.Reset();
1148     ui32_t duration = 0;
1149
1150     while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1151         {
1152           result = Mixer.ReadFrame(FrameBuffer);
1153
1154           if ( ASDCP_SUCCESS(result) )
1155       {
1156         if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1157                 {
1158                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1159                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1160                   result = RESULT_ENDOFFILE;
1161                   continue;
1162                 }
1163
1164         if ( Options.verbose_flag )
1165           FrameBuffer.Dump(stderr, Options.fb_dump_size);
1166
1167         if ( ! Options.no_write_flag )
1168                 {
1169                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1170
1171                   // The Writer class will forward the last block of ciphertext
1172                   // to the encryption context for use as the IV for the next
1173                   // frame. If you want to use non-sequitur IV values, un-comment
1174                   // the following  line of code.
1175                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1176                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1177                 }
1178       }
1179         }
1180
1181     if ( result == RESULT_ENDOFFILE )
1182       result = RESULT_OK;
1183   }
1184
1185   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1186     result = Writer.Finalize();
1187
1188   return result;
1189 }
1190
1191
1192 //------------------------------------------------------------------------------------------
1193 // TimedText essence
1194
1195
1196 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1197 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1198 //
1199 Result_t
1200 write_timed_text_file(CommandOptions& Options)
1201 {
1202   AESEncContext*    Context = 0;
1203   HMACContext*      HMAC = 0;
1204   TimedText::DCSubtitleParser  Parser;
1205   TimedText::MXFWriter    Writer;
1206   TimedText::FrameBuffer  FrameBuffer;
1207   TimedText::TimedTextDescriptor TDesc;
1208   byte_t            IV_buf[CBC_BLOCK_SIZE];
1209   Kumu::FortunaRNG  RNG;
1210
1211   // set up essence parser
1212   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
1213
1214   // set up MXF writer
1215   if ( ASDCP_SUCCESS(result) )
1216     {
1217       Parser.FillTimedTextDescriptor(TDesc);
1218       FrameBuffer.Capacity(Options.fb_size);
1219
1220       if ( Options.verbose_flag )
1221         {
1222           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1223           TimedText::DescriptorDump(TDesc);
1224         }
1225     }
1226
1227   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1228     {
1229       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1230       if ( Options.asset_id_flag )
1231         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1232       else
1233         Kumu::GenRandomUUID(Info.AssetUUID);
1234
1235       // 428-7 IN 429-5 always uses SMPTE labels
1236       Info.LabelSetType = LS_MXF_SMPTE;
1237       fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1238
1239       // configure encryption
1240       if( Options.key_flag )
1241         {
1242           Kumu::GenRandomUUID(Info.ContextID);
1243           Info.EncryptedEssence = true;
1244
1245           if ( Options.key_id_flag )
1246             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1247           else
1248             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1249
1250           Context = new AESEncContext;
1251           result = Context->InitKey(Options.key_value);
1252
1253           if ( ASDCP_SUCCESS(result) )
1254             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1255
1256           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1257             {
1258               Info.UsesHMAC = true;
1259               HMAC = new HMACContext;
1260               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1261             }
1262         }
1263
1264       if ( ASDCP_SUCCESS(result) )
1265         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
1266     }
1267
1268   if ( ASDCP_FAILURE(result) )
1269     return result;
1270
1271   std::string XMLDoc;
1272   TimedText::ResourceList_t::const_iterator ri;
1273
1274   result = Parser.ReadTimedTextResource(XMLDoc);
1275
1276   if ( ASDCP_SUCCESS(result) )
1277     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1278
1279   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1280     {
1281       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1282
1283       if ( ASDCP_SUCCESS(result) )
1284         {
1285           if ( Options.verbose_flag )
1286             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1287
1288           if ( ! Options.no_write_flag )
1289             {
1290               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1291
1292               // The Writer class will forward the last block of ciphertext
1293               // to the encryption context for use as the IV for the next
1294               // frame. If you want to use non-sequitur IV values, un-comment
1295               // the following  line of code.
1296               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1297               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1298             }
1299         }
1300
1301       if ( result == RESULT_ENDOFFILE )
1302         result = RESULT_OK;
1303     }
1304
1305   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1306     result = Writer.Finalize();
1307
1308   return result;
1309 }
1310
1311 // Write one or more plaintext Dolby ATMOS bytestreams to a plaintext ASDCP file
1312 // Write one or more plaintext Dolby ATMOS bytestreams to a ciphertext ASDCP file
1313 //
1314 Result_t
1315 write_dolby_atmos_file(CommandOptions& Options)
1316 {
1317   AESEncContext*          Context = 0;
1318   HMACContext*            HMAC = 0;
1319   ATMOS::MXFWriter         Writer;
1320   DCData::FrameBuffer       FrameBuffer(Options.fb_size);
1321   ATMOS::AtmosDescriptor ADesc;
1322   DCData::SequenceParser    Parser;
1323   byte_t                  IV_buf[CBC_BLOCK_SIZE];
1324   Kumu::FortunaRNG        RNG;
1325
1326   // set up essence parser
1327   Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
1328
1329   // set up MXF writer
1330   if ( ASDCP_SUCCESS(result) )
1331   {
1332     Parser.FillDCDataDescriptor(ADesc);
1333     ADesc.EditRate = Options.PictureRate();
1334     // TODO: fill AtmosDescriptor
1335     ADesc.FirstFrame = Options.ffoa;
1336     ADesc.MaxChannelCount = Options.max_channel_count;
1337     ADesc.MaxObjectCount = Options.max_object_count;
1338     Kumu::GenRandomUUID(ADesc.AtmosID);
1339     ADesc.AtmosVersion = 1;
1340     if ( Options.verbose_flag )
1341         {
1342           fprintf(stderr, "Dolby ATMOS Data\n");
1343           fputs("AtmosDescriptor:\n", stderr);
1344       fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1345       ATMOS::AtmosDescriptorDump(ADesc);
1346         }
1347   }
1348
1349   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1350   {
1351     WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1352     if ( Options.asset_id_flag )
1353       memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1354     else
1355       Kumu::GenRandomUUID(Info.AssetUUID);
1356
1357     Info.LabelSetType = LS_MXF_SMPTE;
1358
1359       // configure encryption
1360     if( Options.key_flag )
1361         {
1362           Kumu::GenRandomUUID(Info.ContextID);
1363           Info.EncryptedEssence = true;
1364
1365           if ( Options.key_id_flag )
1366             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1367           else
1368             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1369
1370           Context = new AESEncContext;
1371           result = Context->InitKey(Options.key_value);
1372
1373           if ( ASDCP_SUCCESS(result) )
1374             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1375
1376           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1377       {
1378         Info.UsesHMAC = true;
1379         HMAC = new HMACContext;
1380         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1381       }
1382         }
1383
1384     if ( ASDCP_SUCCESS(result) )
1385       result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
1386   }
1387
1388   if ( ASDCP_SUCCESS(result) )
1389   {
1390     ui32_t duration = 0;
1391     result = Parser.Reset();
1392
1393     while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1394         {
1395       result = Parser.ReadFrame(FrameBuffer);
1396
1397       if ( ASDCP_SUCCESS(result) )
1398       {
1399         if ( Options.verbose_flag )
1400           FrameBuffer.Dump(stderr, Options.fb_dump_size);
1401
1402         if ( Options.encrypt_header_flag )
1403           FrameBuffer.PlaintextOffset(0);
1404       }
1405
1406       if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1407       {
1408         result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1409
1410         // The Writer class will forward the last block of ciphertext
1411         // to the encryption context for use as the IV for the next
1412         // frame. If you want to use non-sequitur IV values, un-comment
1413         // the following  line of code.
1414         // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1415         //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1416       }
1417         }
1418
1419     if ( result == RESULT_ENDOFFILE )
1420       result = RESULT_OK;
1421   }
1422
1423   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1424     result = Writer.Finalize();
1425
1426   return result;
1427 }
1428
1429 //
1430 int
1431 main(int argc, const char** argv)
1432 {
1433   Result_t result = RESULT_OK;
1434   char     str_buf[64];
1435   g_dict = &ASDCP::DefaultSMPTEDict();
1436
1437   CommandOptions Options(argc, argv);
1438
1439   if ( Options.version_flag )
1440     banner();
1441
1442   if ( Options.help_flag )
1443     usage();
1444
1445   if ( Options.show_ul_values_flag )
1446     {
1447       if ( Options.use_smpte_labels )
1448         DefaultSMPTEDict().Dump(stdout);
1449       else
1450         DefaultInteropDict().Dump(stdout);
1451     }
1452
1453   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
1454     return 0;
1455
1456   if ( Options.error_flag )
1457     {
1458       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1459       return 3;
1460     }
1461
1462   EssenceType_t EssenceType;
1463   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
1464
1465   if ( ASDCP_SUCCESS(result) )
1466     {
1467       switch ( EssenceType )
1468         {
1469         case ESS_MPEG2_VES:
1470           result = write_MPEG2_file(Options);
1471           break;
1472
1473         case ESS_JPEG_2000:
1474           if ( Options.stereo_image_flag )
1475             {
1476               result = write_JP2K_S_file(Options);
1477             }
1478           else
1479             {
1480               result = write_JP2K_file(Options);
1481             }
1482           break;
1483
1484         case ESS_PCM_24b_48k:
1485         case ESS_PCM_24b_96k:
1486       if ( Options.dolby_atmos_sync_flag )
1487       {
1488         result = write_PCM_with_ATMOS_sync_file(Options);
1489       }
1490       else
1491       {
1492         result = write_PCM_file(Options);
1493       }
1494           break;
1495
1496         case ESS_TIMED_TEXT:
1497           result = write_timed_text_file(Options);
1498           break;
1499
1500         case ESS_DCDATA_DOLBY_ATMOS:
1501           result = write_dolby_atmos_file(Options);
1502           break;
1503
1504         default:
1505           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1506                   Options.filenames.front().c_str());
1507           return 5;
1508         }
1509     }
1510
1511   if ( ASDCP_FAILURE(result) )
1512     {
1513       fputs("Program stopped on error.\n", stderr);
1514
1515       if ( result != RESULT_FAIL )
1516         {
1517           fputs(result, stderr);
1518           fputc('\n', stderr);
1519         }
1520
1521       return 1;
1522     }
1523
1524   return 0;
1525 }
1526
1527
1528 //
1529 // end asdcp-wrap.cpp
1530 //