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