version roll
[asdcplib.git] / src / asdcp-wrap.cpp
1 /*
2 Copyright (c) 2003-2015, 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 static void
103 create_random_uuid(byte_t* uuidbuf)
104 {
105   Kumu::UUID tmp_id;
106   GenRandomValue(tmp_id);
107   memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size());
108 }
109
110 //
111 void
112 banner(FILE* stream = stdout)
113 {
114   fprintf(stream, "\n\
115 %s (asdcplib %s)\n\n\
116 Copyright (c) 2003-2015 John Hurst\n\n\
117 asdcplib may be copied only under the terms of the license found at\n\
118 the top of every file in the asdcplib distribution kit.\n\n\
119 Specify the -h (help) option for further information about %s\n\n",
120           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
121 }
122
123 //
124 void
125 usage(FILE* stream = stdout)
126 {
127   fprintf(stream, "\
128 USAGE: %s [-h|-help] [-V]\n\
129 \n\
130        %s [-3] [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
131           [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
132           [-l <label>] [-L] [-M] [-m <expr>] [-p <frame-rate>] [-s] [-v]\n\
133           [-W] [-z|-Z] <input-file>+ <output-file>\n\n",
134           PROGRAM_NAME, PROGRAM_NAME);
135
136   fprintf(stream, "\
137 Options:\n\
138   -3                - Create a stereoscopic image file. Expects two\n\
139                       directories of JP2K codestreams (directories must have\n\
140                       an equal number of frames; the left eye is first)\n\
141   -A <UL>           - Set DataEssenceCoding UL value in an Aux Data file\n\
142   -C <UL>           - Set ChannelAssignment UL value in a PCM file\n\
143   -h | -help        - Show help\n\
144   -V                - Show version information\n\
145   -e                - Encrypt MPEG or JP2K headers (default)\n\
146   -E                - Do not encrypt MPEG or JP2K headers\n\
147   -j <key-id-str>   - Write key ID instead of creating a random value\n\
148   -k <key-string>   - Use key for ciphertext operations\n\
149   -M                - Do not create HMAC values when writing\n\
150   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
151                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
152                         Note: The symbol '-' may be used for an unlabeled\n\
153                               channel, but not within a soundfield.\n\
154   -a <UUID>         - Specify the Asset ID of the file\n\
155   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
156                       Defaults to 4,194,304 (4MB)\n\
157   -d <duration>     - Number of frames to process, default all\n\
158   -f <start-frame>  - Starting frame number, default 0\n\
159   -l <label>        - Use given channel format label when writing MXF sound\n\
160                       files. SMPTE 429-2 labels: '5.1', '6.1', '7.1',\n\
161                       '7.1DS', 'WTF'\n\
162                       Default is no label (valid for Interop only).\n\
163   -L                - Write SMPTE UL values instead of MXF Interop\n\
164   -P <UL>           - Set PictureEssenceCoding UL value in a JP2K file\n\
165   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
166                       Use one of [23|24|25|30|48|50|60], 24 is default\n\
167   -s                - Insert a Dolby Atmos synchronization channel when\n\
168                       wrapping PCM. This implies a -L option(SMPTE ULs) and \n\
169                       will overide -C and -l options with Configuration 4 \n\
170                       Channel Assigment and no format label respectively. \n\
171   -v                - Verbose, prints informative messages to stderr\n\
172   -w                - When writing 377-4 MCA labels, use the WTF Channel\n\
173                       assignment label instead of the standard MCA label\n\
174   -W                - Read input file only, do not write output file\n\
175   -z                - Fail if j2c inputs have unequal parameters (default)\n\
176   -Z                - Ignore unequal parameters in j2c inputs\n\
177 \n\
178   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
179          o All option arguments must be separated from the option by whitespace.\n\
180          o An argument of \"23\" to the -p option will be interpreted\n\
181            as 24000/1001 fps.\n\
182 \n");
183 }
184
185 //
186 PCM::ChannelFormat_t
187 decode_channel_fmt(const std::string& label_name)
188 {
189   if ( label_name == "5.1" )
190     return PCM::CF_CFG_1;
191
192   else if ( label_name == "6.1" )
193     return PCM::CF_CFG_2;
194
195   else if ( label_name == "7.1" )
196     return PCM::CF_CFG_3;
197
198   else if ( label_name == "WTF" )
199     return PCM::CF_CFG_4;
200
201   else if ( label_name == "7.1DS" )
202     return PCM::CF_CFG_5;
203
204   fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
205   fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
206   return PCM::CF_NONE;
207 }
208
209 //
210 //
211 class CommandOptions
212 {
213   CommandOptions();
214
215 public:
216   bool   error_flag;     // true if the given options are in error or not complete
217   bool   key_flag;       // true if an encryption key was given
218   bool   asset_id_flag;  // true if an asset ID was given
219   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
220   bool   write_hmac;     // true if HMAC values are to be generated and written
221   bool   verbose_flag;   // true if the verbose option was selected
222   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
223   bool   no_write_flag;  // true if no output files are to be written
224   bool   version_flag;   // true if the version display option was selected
225   bool   help_flag;      // true if the help display option was selected
226   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
227   bool   write_partial_pcm_flag; // if true, write the last frame of PCM input even when it is incomplete
228   ui32_t start_frame;    // frame number to begin processing
229   ui32_t duration;       // number of frames to be processed
230   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
231   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
232   ui32_t picture_rate;   // fps of picture when wrapping PCM
233   ui32_t fb_size;        // size of picture frame buffer
234   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
235   bool   key_id_flag;    // true if a key ID was given
236   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
237   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
238   PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
239   std::string out_file; //
240   bool show_ul_values_flag;    /// if true, dump the UL table before going to work.
241   Kumu::PathList_t filenames;  // list of filenames to be processed
242   UL channel_assignment;
243   UL picture_coding;
244   UL aux_data_coding;
245   bool dolby_atmos_sync_flag;  // if true, insert a Dolby Atmos Synchronization channel.
246   ui32_t ffoa;                 // first frame of action for atmos wrapping
247   ui32_t max_channel_count;    // max channel count for atmos wrapping
248   ui32_t max_object_count;     // max object count for atmos wrapping
249   bool use_interop_sound_wtf;  // make true to force WTF assignment label instead of MCA
250   ASDCP::MXF::ASDCP_MCAConfigParser mca_config;
251
252   //
253   Rational PictureRate()
254   {
255     if ( picture_rate == 16 ) return EditRate_16;
256     if ( picture_rate == 18 ) return EditRate_18;
257     if ( picture_rate == 20 ) return EditRate_20;
258     if ( picture_rate == 22 ) return EditRate_22;
259     if ( picture_rate == 23 ) return EditRate_23_98;
260     if ( picture_rate == 24 ) return EditRate_24;
261     if ( picture_rate == 25 ) return EditRate_25;
262     if ( picture_rate == 30 ) return EditRate_30;
263     if ( picture_rate == 48 ) return EditRate_48;
264     if ( picture_rate == 50 ) return EditRate_50;
265     if ( picture_rate == 60 ) return EditRate_60;
266     if ( picture_rate == 96 ) return EditRate_96;
267     if ( picture_rate == 100 ) return EditRate_100;
268     if ( picture_rate == 120 ) return EditRate_120;
269     return EditRate_24;
270   }
271
272   //
273   const char* szPictureRate()
274   {
275     if ( picture_rate == 16 ) return "16";
276     if ( picture_rate == 18 ) return "18.182";
277     if ( picture_rate == 20 ) return "20";
278     if ( picture_rate == 22 ) return "21.818";
279     if ( picture_rate == 23 ) return "23.976";
280     if ( picture_rate == 24 ) return "24";
281     if ( picture_rate == 25 ) return "25";
282     if ( picture_rate == 30 ) return "30";
283     if ( picture_rate == 48 ) return "48";
284     if ( picture_rate == 50 ) return "50";
285     if ( picture_rate == 60 ) return "60";
286     if ( picture_rate == 96 ) return "96";
287     if ( picture_rate == 100 ) return "100";
288     if ( picture_rate == 120 ) return "120";
289     return "24";
290   }
291
292   //
293   CommandOptions(int argc, const char** argv) :
294     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
295     encrypt_header_flag(true), write_hmac(true),
296     verbose_flag(false), fb_dump_size(0),
297     no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
298     write_partial_pcm_flag(false), start_frame(0),
299     duration(0xffffffff), use_smpte_labels(false), j2c_pedantic(true),
300     fb_size(FRAME_BUFFER_SIZE),
301     channel_fmt(PCM::CF_NONE),
302     ffoa(0), max_channel_count(10), max_object_count(118), // hard-coded sample atmos properties
303     dolby_atmos_sync_flag(false),
304     show_ul_values_flag(false),
305     mca_config(g_dict),
306     use_interop_sound_wtf(false)
307   {
308     memset(key_value, 0, KeyLen);
309     memset(key_id_value, 0, UUIDlen);
310
311     for ( int i = 1; i < argc; i++ )
312       {
313
314         if ( (strcmp( argv[i], "-help") == 0) )
315           {
316             help_flag = true;
317             continue;
318           }
319
320         if ( argv[i][0] == '-'
321              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
322              && argv[i][2] == 0 )
323           {
324             switch ( argv[i][1] )
325               {
326               case '3': stereo_image_flag = true; break;
327
328               case 'A':
329                 TEST_EXTRA_ARG(i, 'A');
330                 if ( ! aux_data_coding.DecodeHex(argv[i]) )
331                   {
332                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
333                     return;
334                   }
335                 break;
336
337               case 'a':
338                 asset_id_flag = true;
339                 TEST_EXTRA_ARG(i, 'a');
340                 {
341                   ui32_t length;
342                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
343
344                   if ( length != UUIDlen )
345                     {
346                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
347                       return;
348                     }
349                 }
350                 break;
351
352               case 'b':
353                 TEST_EXTRA_ARG(i, 'b');
354                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
355
356                 if ( verbose_flag )
357                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
358
359                 break;
360
361               case 'C':
362                 TEST_EXTRA_ARG(i, 'C');
363                 if ( ! channel_assignment.DecodeHex(argv[i]) )
364                   {
365                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
366                     return;
367                   }
368                 break;
369
370               case 'd':
371                 TEST_EXTRA_ARG(i, 'd');
372                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
373                 break;
374
375               case 'E': encrypt_header_flag = false; break;
376               case 'e': encrypt_header_flag = true; break;
377
378               case 'f':
379                 TEST_EXTRA_ARG(i, 'f');
380                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
381                 break;
382
383               case 'g': write_partial_pcm_flag = true; break;
384               case 'h': help_flag = true; break;
385
386               case 'j': key_id_flag = true;
387                 TEST_EXTRA_ARG(i, 'j');
388                 {
389                   ui32_t length;
390                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
391
392                   if ( length != UUIDlen )
393                     {
394                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
395                       return;
396                     }
397                 }
398                 break;
399
400               case 'k': key_flag = true;
401                 TEST_EXTRA_ARG(i, 'k');
402                 {
403                   ui32_t length;
404                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
405
406                   if ( length != KeyLen )
407                     {
408                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
409                       return;
410                     }
411                 }
412                 break;
413
414               case 'l':
415                 TEST_EXTRA_ARG(i, 'l');
416                 channel_fmt = decode_channel_fmt(argv[i]);
417                 break;
418
419               case 'L': use_smpte_labels = true; break;
420               case 'M': write_hmac = false; break;
421
422               case 'm':
423                 TEST_EXTRA_ARG(i, 'm');
424                 if ( ! mca_config.DecodeString(argv[i]) )
425                   {
426                     return;
427                   }
428                 break;
429
430               case 'P':
431                 TEST_EXTRA_ARG(i, 'P');
432                 if ( ! picture_coding.DecodeHex(argv[i]) )
433                   {
434                     fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
435                     return;
436                   }
437                 break;
438
439               case 'p':
440                 TEST_EXTRA_ARG(i, 'p');
441                 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
442                 break;
443
444               case 's': dolby_atmos_sync_flag = true; break;
445               case 'u': show_ul_values_flag = true; break;
446               case 'V': version_flag = true; break;
447               case 'v': verbose_flag = true; break;
448               case 'w': use_interop_sound_wtf = true; break;
449               case 'W': no_write_flag = true; break;
450               case 'Z': j2c_pedantic = false; break;
451               case 'z': j2c_pedantic = true; break;
452
453               default:
454                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
455                 return;
456               }
457           }
458         else
459           {
460
461             if ( argv[i][0] != '-' )
462               {
463                 filenames.push_back(argv[i]);
464               }
465             else
466               {
467                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
468                 return;
469               }
470           }
471       }
472
473     if ( help_flag || version_flag )
474       return;
475
476     if ( filenames.size() < 2 )
477       {
478         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
479         return;
480       }
481
482     out_file = filenames.back();
483     filenames.pop_back();
484     error_flag = false;
485   }
486 };
487
488 //------------------------------------------------------------------------------------------
489 // MPEG2 essence
490
491 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
492 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
493 //
494 Result_t
495 write_MPEG2_file(CommandOptions& Options)
496 {
497   AESEncContext*     Context = 0;
498   HMACContext*       HMAC = 0;
499   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
500   MPEG2::Parser      Parser;
501   MPEG2::MXFWriter   Writer;
502   MPEG2::VideoDescriptor VDesc;
503   byte_t             IV_buf[CBC_BLOCK_SIZE];
504   Kumu::FortunaRNG   RNG;
505
506   // set up essence parser
507   Result_t result = Parser.OpenRead(Options.filenames.front());
508
509   // set up MXF writer
510   if ( ASDCP_SUCCESS(result) )
511     {
512       Parser.FillVideoDescriptor(VDesc);
513
514       if ( Options.verbose_flag )
515         {
516           fputs("MPEG-2 Pictures\n", stderr);
517           fputs("VideoDescriptor:\n", stderr);
518           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
519           MPEG2::VideoDescriptorDump(VDesc);
520         }
521     }
522
523   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
524     {
525       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
526       if ( Options.asset_id_flag )
527         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
528       else
529         Kumu::GenRandomUUID(Info.AssetUUID);
530
531       if ( Options.use_smpte_labels )
532         {
533           Info.LabelSetType = LS_MXF_SMPTE;
534           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
535         }
536
537       // configure encryption
538       if( Options.key_flag )
539         {
540           Kumu::GenRandomUUID(Info.ContextID);
541           Info.EncryptedEssence = true;
542
543           if ( Options.key_id_flag )
544             {
545               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
546             }
547           else
548             {
549               create_random_uuid(Info.CryptographicKeyID);
550             }
551
552           Context = new AESEncContext;
553           result = Context->InitKey(Options.key_value);
554
555           if ( ASDCP_SUCCESS(result) )
556             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
557
558           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
559             {
560               Info.UsesHMAC = true;
561               HMAC = new HMACContext;
562               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
563             }
564         }
565
566       if ( ASDCP_SUCCESS(result) )
567         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
568     }
569
570   if ( ASDCP_SUCCESS(result) )
571     // loop through the frames
572     {
573       result = Parser.Reset();
574       ui32_t duration = 0;
575
576       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
577         {
578               result = Parser.ReadFrame(FrameBuffer);
579
580               if ( ASDCP_SUCCESS(result) )
581                 {
582                   if ( Options.verbose_flag )
583                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
584
585                   if ( Options.encrypt_header_flag )
586                     FrameBuffer.PlaintextOffset(0);
587                 }
588
589           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
590             {
591               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
592
593               // The Writer class will forward the last block of ciphertext
594               // to the encryption context for use as the IV for the next
595               // frame. If you want to use non-sequitur IV values, un-comment
596               // the following  line of code.
597               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
598               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
599             }
600         }
601
602       if ( result == RESULT_ENDOFFILE )
603         result = RESULT_OK;
604     }
605
606   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
607     result = Writer.Finalize();
608
609   return result;
610 }
611
612
613 //------------------------------------------------------------------------------------------
614
615 // return false if an error is discovered
616 bool
617 check_phfr_params(CommandOptions& Options, JP2K::PictureDescriptor& PDesc)
618 {
619   Rational rate = Options.PictureRate();
620   if ( rate != EditRate_96 && rate != EditRate_100 && rate != EditRate_120 )
621     return true;
622
623   if ( PDesc.StoredWidth > 2048 )
624     {
625       fprintf(stderr, "P-HFR files currently limited to 2K.\n");
626       return false;
627     }
628
629   if ( ! Options.use_smpte_labels )
630     {
631       fprintf(stderr, "P-HFR files must be written using SMPTE labels. Use option '-L'.\n");
632       return false;
633     }
634
635   // do not set the label if the user has already done so
636   if ( ! Options.picture_coding.HasValue() )
637     Options.picture_coding = UL(P_HFR_UL_2K);
638
639   return true;
640 }
641
642 //------------------------------------------------------------------------------------------
643 // JPEG 2000 essence
644
645 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
646 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
647 //
648 Result_t
649 write_JP2K_S_file(CommandOptions& Options)
650 {
651   AESEncContext*          Context = 0;
652   HMACContext*            HMAC = 0;
653   JP2K::MXFSWriter        Writer;
654   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
655   JP2K::PictureDescriptor PDesc;
656   JP2K::SequenceParser    ParserLeft, ParserRight;
657   byte_t                  IV_buf[CBC_BLOCK_SIZE];
658   Kumu::FortunaRNG        RNG;
659
660   if ( Options.filenames.size() != 2 )
661     {
662       fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
663       return RESULT_FAIL;
664     }
665
666   // set up essence parser
667   Result_t result = ParserLeft.OpenRead(Options.filenames.front(), Options.j2c_pedantic);
668
669   if ( ASDCP_SUCCESS(result) )
670     {
671       Options.filenames.pop_front();
672       result = ParserRight.OpenRead(Options.filenames.front(), Options.j2c_pedantic);
673     }
674
675   // set up MXF writer
676   if ( ASDCP_SUCCESS(result) )
677     {
678       ParserLeft.FillPictureDescriptor(PDesc);
679       PDesc.EditRate = Options.PictureRate();
680
681       if ( Options.verbose_flag )
682         {
683           fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
684           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
685           JP2K::PictureDescriptorDump(PDesc);
686         }
687     }
688
689   if ( ! check_phfr_params(Options, PDesc) )
690     return RESULT_FAIL;
691
692   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
693     {
694       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
695       if ( Options.asset_id_flag )
696         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
697       else
698         Kumu::GenRandomUUID(Info.AssetUUID);
699
700       if ( Options.use_smpte_labels )
701         {
702           Info.LabelSetType = LS_MXF_SMPTE;
703           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
704         }
705
706       // configure encryption
707       if( Options.key_flag )
708         {
709           Kumu::GenRandomUUID(Info.ContextID);
710           Info.EncryptedEssence = true;
711
712           if ( Options.key_id_flag )
713             {
714               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
715             }
716           else
717             {
718               create_random_uuid(Info.CryptographicKeyID);
719             }
720
721           Context = new AESEncContext;
722           result = Context->InitKey(Options.key_value);
723
724           if ( ASDCP_SUCCESS(result) )
725             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
726
727           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
728             {
729               Info.UsesHMAC = true;
730               HMAC = new HMACContext;
731               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
732             }
733         }
734
735       if ( ASDCP_SUCCESS(result) )
736         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
737
738       if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
739         {
740           MXF::RGBAEssenceDescriptor *descriptor = 0;
741           Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor),
742                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
743           descriptor->PictureEssenceCoding = Options.picture_coding;
744         }
745     }
746
747   if ( ASDCP_SUCCESS(result) )
748     {
749       ui32_t duration = 0;
750       result = ParserLeft.Reset();
751       if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
752
753       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
754         {
755           result = ParserLeft.ReadFrame(FrameBuffer);
756
757           if ( ASDCP_SUCCESS(result) )
758             {
759               if ( Options.verbose_flag )
760                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
761
762               if ( Options.encrypt_header_flag )
763                 FrameBuffer.PlaintextOffset(0);
764             }
765
766           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
767             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
768
769           if ( ASDCP_SUCCESS(result) )
770             result = ParserRight.ReadFrame(FrameBuffer);
771
772           if ( ASDCP_SUCCESS(result) )
773             {
774               if ( Options.verbose_flag )
775                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
776
777               if ( Options.encrypt_header_flag )
778                 FrameBuffer.PlaintextOffset(0);
779             }
780
781           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
782             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
783         }
784
785       if ( result == RESULT_ENDOFFILE )
786         result = RESULT_OK;
787     }
788
789   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
790     result = Writer.Finalize();
791
792   return result;
793 }
794
795 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
796 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
797 //
798 Result_t
799 write_JP2K_file(CommandOptions& Options)
800 {
801   AESEncContext*          Context = 0;
802   HMACContext*            HMAC = 0;
803   JP2K::MXFWriter         Writer;
804   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
805   JP2K::PictureDescriptor PDesc;
806   JP2K::SequenceParser    Parser;
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(), Options.j2c_pedantic);
812
813   // set up MXF writer
814   if ( ASDCP_SUCCESS(result) )
815     {
816       Parser.FillPictureDescriptor(PDesc);
817       PDesc.EditRate = Options.PictureRate();
818
819       if ( Options.verbose_flag )
820         {
821           fprintf(stderr, "JPEG 2000 pictures\n");
822           fputs("PictureDescriptor:\n", stderr);
823           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
824           JP2K::PictureDescriptorDump(PDesc);
825         }
826     }
827
828   if ( ! check_phfr_params(Options, PDesc) )
829     return RESULT_FAIL;
830
831   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
832     {
833       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
834       if ( Options.asset_id_flag )
835         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
836       else
837         Kumu::GenRandomUUID(Info.AssetUUID);
838
839       if ( Options.use_smpte_labels )
840         {
841           Info.LabelSetType = LS_MXF_SMPTE;
842           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
843         }
844
845       // configure encryption
846       if( Options.key_flag )
847         {
848           Kumu::GenRandomUUID(Info.ContextID);
849           Info.EncryptedEssence = true;
850
851           if ( Options.key_id_flag )
852             {
853               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
854             }
855           else
856             {
857               create_random_uuid(Info.CryptographicKeyID);
858             }
859
860           Context = new AESEncContext;
861           result = Context->InitKey(Options.key_value);
862
863           if ( ASDCP_SUCCESS(result) )
864             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
865
866           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
867             {
868               Info.UsesHMAC = true;
869               HMAC = new HMACContext;
870               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
871             }
872         }
873
874       if ( ASDCP_SUCCESS(result) )
875         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
876
877       if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
878         {
879           MXF::RGBAEssenceDescriptor *descriptor = 0;
880           Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor),
881                                                 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
882           descriptor->PictureEssenceCoding = Options.picture_coding;
883         }
884     }
885
886   if ( ASDCP_SUCCESS(result) )
887     {
888       ui32_t duration = 0;
889       result = Parser.Reset();
890
891       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
892         {
893               result = Parser.ReadFrame(FrameBuffer);
894
895               if ( ASDCP_SUCCESS(result) )
896                 {
897                   if ( Options.verbose_flag )
898                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
899
900                   if ( Options.encrypt_header_flag )
901                     FrameBuffer.PlaintextOffset(0);
902                 }
903
904           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
905             {
906               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
907
908               // The Writer class will forward the last block of ciphertext
909               // to the encryption context for use as the IV for the next
910               // frame. If you want to use non-sequitur IV values, un-comment
911               // the following  line of code.
912               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
913               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
914             }
915         }
916
917       if ( result == RESULT_ENDOFFILE )
918         result = RESULT_OK;
919     }
920
921   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
922     result = Writer.Finalize();
923
924   return result;
925 }
926
927 //------------------------------------------------------------------------------------------
928 // PCM essence
929
930
931 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
932 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
933 //
934 Result_t
935 write_PCM_file(CommandOptions& Options)
936 {
937   AESEncContext*    Context = 0;
938   HMACContext*      HMAC = 0;
939   PCMParserList     Parser;
940   PCM::MXFWriter    Writer;
941   PCM::FrameBuffer  FrameBuffer;
942   PCM::AudioDescriptor ADesc;
943   Rational          PictureRate = Options.PictureRate();
944   byte_t            IV_buf[CBC_BLOCK_SIZE];
945   Kumu::FortunaRNG  RNG;
946
947   // set up essence parser
948   Result_t result = Parser.OpenRead(Options.filenames, PictureRate);
949
950   // set up MXF writer
951   if ( ASDCP_SUCCESS(result) )
952     {
953       Parser.FillAudioDescriptor(ADesc);
954
955       ADesc.EditRate = PictureRate;
956       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
957       ADesc.ChannelFormat = Options.channel_fmt;
958
959       if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE && Options.mca_config.empty() )
960         {
961           fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see options -C, -l and -m)\n");
962         }
963
964       if ( Options.verbose_flag )
965         {
966           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
967                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
968                   Options.szPictureRate(),
969                   PCM::CalcSamplesPerFrame(ADesc));
970           fputs("AudioDescriptor:\n", stderr);
971           PCM::AudioDescriptorDump(ADesc);
972         }
973     }
974
975   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
976     {
977       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
978       if ( Options.asset_id_flag )
979         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
980       else
981         Kumu::GenRandomUUID(Info.AssetUUID);
982
983       if ( Options.use_smpte_labels )
984         {
985           Info.LabelSetType = LS_MXF_SMPTE;
986           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
987         }
988
989       // configure encryption
990       if( Options.key_flag )
991         {
992           Kumu::GenRandomUUID(Info.ContextID);
993           Info.EncryptedEssence = true;
994
995           if ( Options.key_id_flag )
996             {
997               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
998             }
999           else
1000             {
1001               create_random_uuid(Info.CryptographicKeyID);
1002             }
1003
1004           Context = new AESEncContext;
1005           result = Context->InitKey(Options.key_value);
1006
1007           if ( ASDCP_SUCCESS(result) )
1008             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1009
1010           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1011             {
1012               Info.UsesHMAC = true;
1013               HMAC = new HMACContext;
1014               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1015             }
1016         }
1017
1018       if ( ASDCP_SUCCESS(result) )
1019         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1020
1021       if ( ASDCP_SUCCESS(result)
1022            && ( Options.channel_assignment.HasValue()
1023                 || ! Options.mca_config.empty() ) )
1024         {
1025           MXF::WaveAudioDescriptor *essence_descriptor = 0;
1026           Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_WaveAudioDescriptor),
1027                                                 reinterpret_cast<MXF::InterchangeObject**>(&essence_descriptor));
1028           assert(essence_descriptor);
1029
1030           if ( Options.mca_config.empty() )
1031             {
1032               essence_descriptor->ChannelAssignment = Options.channel_assignment;
1033             }
1034           else
1035             {
1036               if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
1037                 {
1038                   fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
1039                           Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
1040                   return RESULT_FAIL;
1041                 }
1042
1043               if ( Options.channel_assignment.HasValue() )
1044                 {
1045                   essence_descriptor->ChannelAssignment = Options.channel_assignment;
1046                 }
1047               else if ( Options.use_interop_sound_wtf )
1048                 {
1049                   essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_4_WTF);
1050                 }
1051               else
1052                 {
1053                   essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA);
1054                 }
1055
1056               // add descriptors to the essence_descriptor and header
1057               ASDCP::MXF::InterchangeObject_list_t::iterator i;
1058               for ( i = Options.mca_config.begin(); i != Options.mca_config.end(); ++i )
1059                 {
1060                   if ( (*i)->GetUL() != UL(g_dict->ul(MDD_AudioChannelLabelSubDescriptor))
1061                        && (*i)->GetUL() != UL(g_dict->ul(MDD_SoundfieldGroupLabelSubDescriptor))
1062                        && (*i)->GetUL() != UL(g_dict->ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor)) )
1063                     {
1064                       fprintf(stderr, "Essence sub-descriptor is not an MCALabelSubDescriptor.\n");
1065                       (*i)->Dump();
1066                     }
1067
1068                   Writer.OP1aHeader().AddChildObject(*i);
1069                   essence_descriptor->SubDescriptors.push_back((*i)->InstanceUID);
1070                   *i = 0; // parent will only free the ones we don't keep
1071                 }
1072             }
1073         }
1074     }
1075
1076   if ( ASDCP_SUCCESS(result) )
1077     {
1078       result = Parser.Reset();
1079       ui32_t duration = 0;
1080
1081       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1082         {
1083           result = Parser.ReadFrame(FrameBuffer);
1084
1085           if ( ASDCP_SUCCESS(result) )
1086             {
1087               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1088                 {
1089                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1090                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1091
1092                   if ( Options.write_partial_pcm_flag )
1093                     {
1094                       result = RESULT_ENDOFFILE;
1095                       continue;
1096                     }
1097                 }
1098
1099               if ( Options.verbose_flag )
1100                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1101
1102               if ( ! Options.no_write_flag )
1103                 {
1104                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1105
1106                   // The Writer class will forward the last block of ciphertext
1107                   // to the encryption context for use as the IV for the next
1108                   // frame. If you want to use non-sequitur IV values, un-comment
1109                   // the following  line of code.
1110                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1111                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1112                 }
1113             }
1114         }
1115
1116       if ( result == RESULT_ENDOFFILE )
1117         result = RESULT_OK;
1118     }
1119
1120   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1121     result = Writer.Finalize();
1122
1123   return result;
1124 }
1125
1126 // Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a plaintext ASDCP file
1127 // Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a ciphertext ASDCP file
1128 //
1129 Result_t
1130 write_PCM_with_ATMOS_sync_file(CommandOptions& Options)
1131 {
1132   AESEncContext*        Context = 0;
1133   HMACContext*          HMAC = 0;
1134   PCM::MXFWriter        Writer;
1135   PCM::FrameBuffer      FrameBuffer;
1136   PCM::AudioDescriptor  ADesc;
1137   Rational              PictureRate = Options.PictureRate();
1138   byte_t                IV_buf[CBC_BLOCK_SIZE];
1139   Kumu::FortunaRNG      RNG;
1140
1141   WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1142   if ( Options.asset_id_flag )
1143         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1144   else
1145         Kumu::GenRandomUUID(Info.AssetUUID);
1146   AtmosSyncChannelMixer Mixer(Info.AssetUUID);
1147
1148   // set up essence parser
1149   Result_t result = Mixer.OpenRead(Options.filenames, PictureRate);
1150
1151   // set up MXF writer
1152   if ( ASDCP_SUCCESS(result) )
1153   {
1154     if ( Mixer.ChannelCount() % 2 != 0 )
1155       {
1156         result = Mixer.AppendSilenceChannels(1);
1157       }
1158
1159     Mixer.FillAudioDescriptor(ADesc);
1160
1161     ADesc.EditRate = PictureRate;
1162     FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1163     ADesc.ChannelFormat = PCM::CF_CFG_4;
1164
1165     if ( Options.verbose_flag )
1166         {
1167           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1168               ADesc.AudioSamplingRate.Quotient() / 1000.0,
1169               Options.szPictureRate(),
1170               PCM::CalcSamplesPerFrame(ADesc));
1171           fputs("AudioDescriptor:\n", stderr);
1172           PCM::AudioDescriptorDump(ADesc);
1173         }
1174   }
1175
1176   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1177   {
1178     Info.LabelSetType = LS_MXF_SMPTE;
1179     fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1180
1181     // configure encryption
1182     if( Options.key_flag )
1183         {
1184           Kumu::GenRandomUUID(Info.ContextID);
1185           Info.EncryptedEssence = true;
1186
1187           if ( Options.key_id_flag )
1188             {
1189               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1190             }
1191           else
1192             {
1193               create_random_uuid(Info.CryptographicKeyID);
1194             }
1195
1196           Context = new AESEncContext;
1197           result = Context->InitKey(Options.key_value);
1198
1199           if ( ASDCP_SUCCESS(result) )
1200             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1201
1202           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1203       {
1204         Info.UsesHMAC = true;
1205         HMAC = new HMACContext;
1206         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1207       }
1208         }
1209
1210     if ( ASDCP_SUCCESS(result) )
1211       result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1212   }
1213
1214   if ( ASDCP_SUCCESS(result) )
1215   {
1216     result = Mixer.Reset();
1217     ui32_t duration = 0;
1218
1219     while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1220         {
1221           result = Mixer.ReadFrame(FrameBuffer);
1222
1223           if ( ASDCP_SUCCESS(result) )
1224       {
1225         if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1226                 {
1227                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1228                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1229
1230                   if ( Options.write_partial_pcm_flag )
1231                     {
1232                       result = RESULT_ENDOFFILE;
1233                       continue;
1234                     }
1235                 }
1236
1237         if ( Options.verbose_flag )
1238           FrameBuffer.Dump(stderr, Options.fb_dump_size);
1239
1240         if ( ! Options.no_write_flag )
1241                 {
1242                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1243
1244                   // The Writer class will forward the last block of ciphertext
1245                   // to the encryption context for use as the IV for the next
1246                   // frame. If you want to use non-sequitur IV values, un-comment
1247                   // the following  line of code.
1248                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1249                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1250                 }
1251       }
1252         }
1253
1254     if ( result == RESULT_ENDOFFILE )
1255       result = RESULT_OK;
1256   }
1257
1258   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1259     result = Writer.Finalize();
1260
1261   return result;
1262 }
1263
1264
1265 //------------------------------------------------------------------------------------------
1266 // TimedText essence
1267
1268
1269 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1270 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1271 //
1272 Result_t
1273 write_timed_text_file(CommandOptions& Options)
1274 {
1275   AESEncContext*    Context = 0;
1276   HMACContext*      HMAC = 0;
1277   TimedText::DCSubtitleParser  Parser;
1278   TimedText::MXFWriter    Writer;
1279   TimedText::FrameBuffer  FrameBuffer;
1280   TimedText::TimedTextDescriptor TDesc;
1281   byte_t            IV_buf[CBC_BLOCK_SIZE];
1282   Kumu::FortunaRNG  RNG;
1283
1284   // set up essence parser
1285   Result_t result = Parser.OpenRead(Options.filenames.front());
1286
1287   // set up MXF writer
1288   if ( ASDCP_SUCCESS(result) )
1289     {
1290       Parser.FillTimedTextDescriptor(TDesc);
1291       FrameBuffer.Capacity(Options.fb_size);
1292
1293       if ( Options.verbose_flag )
1294         {
1295           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1296           TimedText::DescriptorDump(TDesc);
1297         }
1298     }
1299
1300   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1301     {
1302       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1303       if ( Options.asset_id_flag )
1304         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1305       else
1306         Kumu::GenRandomUUID(Info.AssetUUID);
1307
1308       // 428-7 IN 429-5 always uses SMPTE labels
1309       Info.LabelSetType = LS_MXF_SMPTE;
1310       fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1311
1312       // configure encryption
1313       if( Options.key_flag )
1314         {
1315           Kumu::GenRandomUUID(Info.ContextID);
1316           Info.EncryptedEssence = true;
1317
1318           if ( Options.key_id_flag )
1319             {
1320               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1321             }
1322           else
1323             {
1324               create_random_uuid(Info.CryptographicKeyID);
1325             }
1326
1327           Context = new AESEncContext;
1328           result = Context->InitKey(Options.key_value);
1329
1330           if ( ASDCP_SUCCESS(result) )
1331             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1332
1333           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1334             {
1335               Info.UsesHMAC = true;
1336               HMAC = new HMACContext;
1337               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1338             }
1339         }
1340
1341       if ( ASDCP_SUCCESS(result) )
1342         result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1343     }
1344
1345   if ( ASDCP_FAILURE(result) )
1346     return result;
1347
1348   std::string XMLDoc;
1349   TimedText::ResourceList_t::const_iterator ri;
1350
1351   result = Parser.ReadTimedTextResource(XMLDoc);
1352
1353   if ( ASDCP_SUCCESS(result) )
1354     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1355
1356   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1357     {
1358       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1359
1360       if ( ASDCP_SUCCESS(result) )
1361         {
1362           if ( Options.verbose_flag )
1363             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1364
1365           if ( ! Options.no_write_flag )
1366             {
1367               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1368
1369               // The Writer class will forward the last block of ciphertext
1370               // to the encryption context for use as the IV for the next
1371               // frame. If you want to use non-sequitur IV values, un-comment
1372               // the following  line of code.
1373               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1374               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1375             }
1376         }
1377
1378       if ( result == RESULT_ENDOFFILE )
1379         result = RESULT_OK;
1380     }
1381
1382   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1383     result = Writer.Finalize();
1384
1385   return result;
1386 }
1387
1388 // Write one or more plaintext Dolby ATMOS bytestreams to a plaintext ASDCP file
1389 // Write one or more plaintext Dolby ATMOS bytestreams to a ciphertext ASDCP file
1390 //
1391 Result_t
1392 write_dolby_atmos_file(CommandOptions& Options)
1393 {
1394   AESEncContext*          Context = 0;
1395   HMACContext*            HMAC = 0;
1396   ATMOS::MXFWriter         Writer;
1397   DCData::FrameBuffer       FrameBuffer(Options.fb_size);
1398   ATMOS::AtmosDescriptor ADesc;
1399   DCData::SequenceParser    Parser;
1400   byte_t                  IV_buf[CBC_BLOCK_SIZE];
1401   Kumu::FortunaRNG        RNG;
1402
1403   // set up essence parser
1404   Result_t result = Parser.OpenRead(Options.filenames.front());
1405
1406   // set up MXF writer
1407   if ( ASDCP_SUCCESS(result) )
1408   {
1409     Parser.FillDCDataDescriptor(ADesc);
1410     ADesc.EditRate = Options.PictureRate();
1411     // TODO: fill AtmosDescriptor
1412     ADesc.FirstFrame = Options.ffoa;
1413     ADesc.MaxChannelCount = Options.max_channel_count;
1414     ADesc.MaxObjectCount = Options.max_object_count;
1415     Kumu::GenRandomUUID(ADesc.AtmosID);
1416     ADesc.AtmosVersion = 1;
1417     if ( Options.verbose_flag )
1418         {
1419           fprintf(stderr, "Dolby ATMOS Data\n");
1420           fputs("AtmosDescriptor:\n", stderr);
1421       fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1422       ATMOS::AtmosDescriptorDump(ADesc);
1423         }
1424   }
1425
1426   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1427   {
1428     WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1429     if ( Options.asset_id_flag )
1430       memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1431     else
1432       Kumu::GenRandomUUID(Info.AssetUUID);
1433
1434     Info.LabelSetType = LS_MXF_SMPTE;
1435
1436       // configure encryption
1437     if( Options.key_flag )
1438         {
1439           Kumu::GenRandomUUID(Info.ContextID);
1440           Info.EncryptedEssence = true;
1441
1442           if ( Options.key_id_flag )
1443             {
1444               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1445             }
1446           else
1447             {
1448               create_random_uuid(Info.CryptographicKeyID);
1449             }
1450
1451           Context = new AESEncContext;
1452           result = Context->InitKey(Options.key_value);
1453
1454           if ( ASDCP_SUCCESS(result) )
1455             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1456
1457           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1458       {
1459         Info.UsesHMAC = true;
1460         HMAC = new HMACContext;
1461         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1462       }
1463         }
1464
1465     if ( ASDCP_SUCCESS(result) )
1466       result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1467   }
1468
1469   if ( ASDCP_SUCCESS(result) )
1470   {
1471     ui32_t duration = 0;
1472     result = Parser.Reset();
1473
1474     while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1475         {
1476       result = Parser.ReadFrame(FrameBuffer);
1477
1478       if ( ASDCP_SUCCESS(result) )
1479       {
1480         if ( Options.verbose_flag )
1481           FrameBuffer.Dump(stderr, Options.fb_dump_size);
1482
1483         if ( Options.encrypt_header_flag )
1484           FrameBuffer.PlaintextOffset(0);
1485       }
1486
1487       if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1488       {
1489         result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1490
1491         // The Writer class will forward the last block of ciphertext
1492         // to the encryption context for use as the IV for the next
1493         // frame. If you want to use non-sequitur IV values, un-comment
1494         // the following  line of code.
1495         // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1496         //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1497       }
1498         }
1499
1500     if ( result == RESULT_ENDOFFILE )
1501       result = RESULT_OK;
1502   }
1503
1504   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1505     result = Writer.Finalize();
1506
1507   return result;
1508 }
1509
1510 // Write one or more plaintext Aux Data (ST 429-14) bytestreams to a plaintext ASDCP file
1511 // Write one or more plaintext Aux Data (ST 429-14) bytestreams to a ciphertext ASDCP file
1512 //
1513 Result_t
1514 write_aux_data_file(CommandOptions& Options)
1515 {
1516   AESEncContext*          Context = 0;
1517   HMACContext*            HMAC = 0;
1518   DCData::MXFWriter       Writer;
1519   DCData::FrameBuffer     FrameBuffer(Options.fb_size);
1520   DCData::DCDataDescriptor DDesc;
1521   DCData::SequenceParser  Parser;
1522   byte_t                  IV_buf[CBC_BLOCK_SIZE];
1523   Kumu::FortunaRNG        RNG;
1524
1525   // set up essence parser
1526   Result_t result = Parser.OpenRead(Options.filenames.front());
1527
1528   // set up MXF writer
1529   if ( ASDCP_SUCCESS(result) )
1530   {
1531     Parser.FillDCDataDescriptor(DDesc);
1532     memcpy(DDesc.DataEssenceCoding, Options.aux_data_coding.Value(), Options.aux_data_coding.Size());
1533     DDesc.EditRate = Options.PictureRate();
1534
1535     if ( Options.verbose_flag )
1536         {
1537           fprintf(stderr, "Aux Data\n");
1538           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1539           DCData::DCDataDescriptorDump(DDesc);
1540         }
1541   }
1542
1543   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1544   {
1545     WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1546     if ( Options.asset_id_flag )
1547       memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1548     else
1549       Kumu::GenRandomUUID(Info.AssetUUID);
1550
1551     Info.LabelSetType = LS_MXF_SMPTE;
1552
1553       // configure encryption
1554     if( Options.key_flag )
1555         {
1556           Kumu::GenRandomUUID(Info.ContextID);
1557           Info.EncryptedEssence = true;
1558
1559           if ( Options.key_id_flag )
1560             {
1561               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1562             }
1563           else
1564             {
1565               create_random_uuid(Info.CryptographicKeyID);
1566             }
1567
1568           Context = new AESEncContext;
1569           result = Context->InitKey(Options.key_value);
1570
1571           if ( ASDCP_SUCCESS(result) )
1572             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1573
1574           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1575       {
1576         Info.UsesHMAC = true;
1577         HMAC = new HMACContext;
1578         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1579       }
1580         }
1581
1582     if ( ASDCP_SUCCESS(result) )
1583       result = Writer.OpenWrite(Options.out_file, Info, DDesc);
1584   }
1585
1586   if ( ASDCP_SUCCESS(result) )
1587   {
1588     ui32_t duration = 0;
1589     result = Parser.Reset();
1590
1591     while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1592         {
1593       result = Parser.ReadFrame(FrameBuffer);
1594
1595       if ( ASDCP_SUCCESS(result) )
1596       {
1597         if ( Options.verbose_flag )
1598           FrameBuffer.Dump(stderr, Options.fb_dump_size);
1599
1600         if ( Options.encrypt_header_flag )
1601           FrameBuffer.PlaintextOffset(0);
1602       }
1603
1604       if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1605       {
1606         result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1607
1608         // The Writer class will forward the last block of ciphertext
1609         // to the encryption context for use as the IV for the next
1610         // frame. If you want to use non-sequitur IV values, un-comment
1611         // the following  line of code.
1612         // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1613         //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1614       }
1615         }
1616
1617     if ( result == RESULT_ENDOFFILE )
1618       result = RESULT_OK;
1619   }
1620
1621   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1622     result = Writer.Finalize();
1623
1624   return result;
1625 }
1626
1627 //
1628 int
1629 main(int argc, const char** argv)
1630 {
1631   Result_t result = RESULT_OK;
1632   char     str_buf[64];
1633   g_dict = &ASDCP::DefaultSMPTEDict();
1634
1635   CommandOptions Options(argc, argv);
1636
1637   if ( Options.version_flag )
1638     banner();
1639
1640   if ( Options.help_flag )
1641     usage();
1642
1643   if ( Options.show_ul_values_flag )
1644     {
1645       if ( Options.use_smpte_labels )
1646         DefaultSMPTEDict().Dump(stdout);
1647       else
1648         DefaultInteropDict().Dump(stdout);
1649     }
1650
1651   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
1652     return 0;
1653
1654   if ( Options.error_flag )
1655     {
1656       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1657       return 3;
1658     }
1659
1660   EssenceType_t EssenceType;
1661   result = ASDCP::RawEssenceType(Options.filenames.front(), EssenceType);
1662
1663   if ( ASDCP_SUCCESS(result) )
1664     {
1665       switch ( EssenceType )
1666         {
1667         case ESS_MPEG2_VES:
1668           result = write_MPEG2_file(Options);
1669           break;
1670
1671         case ESS_JPEG_2000:
1672           if ( Options.stereo_image_flag )
1673             {
1674               result = write_JP2K_S_file(Options);
1675             }
1676           else
1677             {
1678               result = write_JP2K_file(Options);
1679             }
1680           break;
1681
1682         case ESS_PCM_24b_48k:
1683         case ESS_PCM_24b_96k:
1684           if ( Options.dolby_atmos_sync_flag )
1685             {
1686               result = write_PCM_with_ATMOS_sync_file(Options);
1687             }
1688           else
1689             {
1690               result = write_PCM_file(Options);
1691             }
1692           break;
1693           
1694         case ESS_TIMED_TEXT:
1695           result = write_timed_text_file(Options);
1696           break;
1697
1698         case ESS_DCDATA_DOLBY_ATMOS:
1699           result = write_dolby_atmos_file(Options);
1700           break;
1701
1702         case ESS_DCDATA_UNKNOWN:
1703           if ( ! Options.aux_data_coding.HasValue() )
1704             {
1705               fprintf(stderr, "Option \"-A <UL>\" is required for Aux Data essence.\n");
1706               return 3;
1707             }
1708           else
1709             {
1710               result = write_aux_data_file(Options);
1711             }
1712           break;
1713
1714         default:
1715           fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1716                   Options.filenames.front().c_str());
1717           return 5;
1718         }
1719     }
1720
1721   if ( ASDCP_FAILURE(result) )
1722     {
1723       fputs("Program stopped on error.\n", stderr);
1724
1725       if ( result != RESULT_FAIL )
1726         {
1727           fputs(result, stderr);
1728           fputc('\n', stderr);
1729         }
1730
1731       return 1;
1732     }
1733
1734   return 0;
1735 }
1736
1737
1738 //
1739 // end asdcp-wrap.cpp
1740 //