o Cleaned up definition of UL WaveAudioDescriptor_PeakEnvelopeData (currently unused)
[asdcplib.git] / src / as-02-wrap.cpp
1 /*
2 Copyright (c) 2011-2018, 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 <KM_xml.h>
42 #include <AS_02.h>
43 #include <PCMParserList.h>
44 #include <Metadata.h>
45
46 using namespace ASDCP;
47
48 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
49 const ASDCP::Dictionary *g_dict = 0;
50  
51 const char*
52 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
53 {
54   snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
55   return buf;
56 }
57
58
59 //------------------------------------------------------------------------------------------
60 //
61 // command line option parser class
62
63 static const char* PROGRAM_NAME = "as-02-wrap";  // program name for messages
64
65 // local program identification info written to file headers
66 class MyInfo : public WriterInfo
67 {
68 public:
69   MyInfo()
70   {
71       static byte_t default_ProductUUID_Data[UUIDlen] =
72       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
73         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
74       
75       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
76       CompanyName = "WidgetCo";
77       ProductName = "as-02-wrap";
78       ProductVersion = ASDCP::Version();
79   }
80 } s_MyInfo;
81
82
83
84 // Increment the iterator, test for an additional non-option command line argument.
85 // Causes the caller to return if there are no remaining arguments or if the next
86 // argument begins with '-'.
87 #define TEST_EXTRA_ARG(i,c)                                             \
88   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
89     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
90     return;                                                             \
91   }
92
93
94 //
95 static void
96 create_random_uuid(byte_t* uuidbuf)
97 {
98   Kumu::UUID tmp_id;
99   GenRandomValue(tmp_id);
100   memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size());
101 }
102
103 //
104 void
105 banner(FILE* stream = stdout)
106 {
107   fprintf(stream, "\n\
108 %s (asdcplib %s)\n\n\
109 Copyright (c) 2011-2018, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
110 asdcplib may be copied only under the terms of the license found at\n\
111 the top of every file in the asdcplib distribution kit.\n\n\
112 Specify the -h (help) option for further information about %s\n\n",
113           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
114 }
115
116 //
117 void
118 usage(FILE* stream = stdout)
119 {
120   fprintf(stream, "\
121 USAGE: %s [-h|-help] [-V]\n\
122 \n\
123        %s [options] <input-file>+ <output-file>\n\n",
124           PROGRAM_NAME, PROGRAM_NAME);
125
126   fprintf(stream, "\
127 Options:\n\
128   -h | -help        - Show help\n\
129   -V                - Show version information\n\
130   -a <uuid>         - Specify the Asset ID of the file\n\
131   -A <w>/<h>        - Set aspect ratio for image (default 4/3)\n\
132   -b <buffer-size>  - Specify size in bytes of picture frame buffer\n\
133                       Defaults to 4,194,304 (4MB)\n\
134   -c <num>          - Select the IMF color system to be signaled:\n\
135                       Application 2 (2067-20): 1, 2, or 3\n\
136                       Application 2e (2067-21): 4 or 5\n\
137                       All color system values assume YCbCr; also use -R for RGB\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   -g <rfc-5646-code>\n\
145                     - Create MCA labels having the given RFC 5646 language code\n\
146                       (requires option \"-m\") -- Also used with -G to set the\n\
147                       value of the TextMIMEMediaType property\n\
148   -G <filename>     - Filename of XML resource to be carried per RP 2057 Generic\n\
149                       Stream. May be issued multiple times.\n\
150   -i                - Indicates input essence is interlaced fields (forces -Y)\n\
151   -j <key-id-str>   - Write key ID instead of creating a random value\n\
152   -k <key-string>   - Use key for ciphertext operations\n\
153   -l <first>,<second>\n\
154                     - Integer values that set the VideoLineMap when creating\n\
155                       interlaced YCbCr files\n\
156   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
157                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
158   -M                - Do not create HMAC values when writing\n\
159   -n <UL>           - Set the TransferCharacteristic UL\n\
160   -o <min>,<max>    - Mastering Display luminance, cd*m*m, e.g., \".05,100\"\n\
161   -O <rx>,<ry>,<gx>,<gy>,<bx>,<by>,<wx>,<wy>\n\
162                     - Mastering Display Color Primaries and white point\n\
163                       e.g., \".64,.33,.3,.6,.15,.06,.3457,.3585\"\n\
164   -p <ul>           - Set broadcast profile\n\
165   -P <string>       - Set NamespaceURI property when creating timed text MXF\n\
166   -q <UL>           - Set the CodingEquations UL\n\
167   -r <n>/<d>        - Edit Rate of the output file.  24/1 is the default\n\
168   -R                - Indicates RGB image essence (default except with -c)\n\
169   -s <seconds>      - Duration of a frame-wrapped partition (default 60)\n\
170   -t <min>          - Set RGB component minimum code value (default: 0)\n\
171   -T <max>          - Set RGB component maximum code value (default: 1023)\n\
172   -u                - Print UL catalog to stdout\n\
173   -u <URI>          - ISXD (RDD47) document URI (use 'auto' to read the\n\
174                       namespace name from the first edit unit\n\
175   -v                - Verbose, prints informative messages to stderr\n\
176   -W                - Read input file only, do not write source file\n\
177   -x <int>          - Horizontal subsampling degree (default: 2)\n\
178   -X <int>          - Vertical subsampling degree (default: 2)\n\
179   -y <white-ref>[,<black-ref>[,<color-range>]]\n\
180                     - Same as -Y but White Ref, Black Ref and Color Range are\n\
181                       set from the given argument\n\
182   -Y                - Indicates YCbCr image essence (default with -c), uses\n\
183                       default values for White Ref, Black Ref and Color Range,\n\
184                        940,64,897, indicating 10 bit standard Video Range\n\
185   -z                - Fail if j2c inputs have unequal parameters (default)\n\
186   -Z                - Ignore unequal parameters in j2c inputs\n\
187 \n\
188   --mca-audio-content-kind <string>\n\
189                     - UL value for MCA descriptor MCAAudioContentKind property\n\
190   --mca-audio-element-kind <string>\n\
191                     - UL value for MCA descriptor MCAAudioElementKind property\n\
192 \n\
193   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
194          o All option arguments must be separated from the option by whitespace.\n\n");
195 }
196
197 const float chromaticity_scale = 50000.0;
198 //
199 ui32_t
200 set_primary_from_token(const std::string& token, ui16_t& primary)
201 {
202   float raw_value = strtod(token.c_str(),0);
203
204   if ( raw_value == 0.0 || raw_value > 1.0 )
205     {
206       fprintf(stderr, "Invalid coordinate value \"%s\".\n", token.c_str());
207       return false;
208     }
209
210   primary = floor(0.5 + ( raw_value * chromaticity_scale ));
211   return true;
212 }
213
214 const float luminance_scale = 10000.0;
215 //
216 ui32_t
217 set_luminance_from_token(const std::string& token, ui32_t& luminance)
218 {
219   float raw_value = strtod(token.c_str(),0);
220
221   if ( raw_value == 0.0 || raw_value > 400000.0 )
222     {
223       fprintf(stderr, "Invalid luminance value \"%s\".\n", token.c_str());
224       return false;
225     }
226
227   luminance = floor(0.5 + ( raw_value * luminance_scale ));
228   return true;
229 }
230
231 #define SET_LUMINANCE(p,t)                      \
232   if ( ! set_luminance_from_token(t, p) ) {     \
233     return false;                               \
234   }
235
236 //
237 class CommandOptions
238 {
239   CommandOptions();
240
241 public:
242   bool   error_flag;     // true if the given options are in error or not complete
243   bool   key_flag;       // true if an encryption key was given
244   bool   asset_id_flag;  // true if an asset ID was given
245   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
246   bool   write_hmac;     // true if HMAC values are to be generated and written
247   bool   verbose_flag;   // true if the verbose option was selected
248   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
249   bool   no_write_flag;  // true if no output files are to be written
250   bool   version_flag;   // true if the version display option was selected
251   bool   help_flag;      // true if the help display option was selected
252   ui32_t duration;       // number of frames to be processed
253   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
254   bool use_cdci_descriptor; // 
255   Rational edit_rate;    // edit rate of JP2K sequence
256   ui32_t fb_size;        // size of picture frame buffer
257   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
258   bool   key_id_flag;    // true if a key ID was given
259   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
260   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
261   bool show_ul_values_flag;    // if true, dump the UL table before going tp work.
262   Kumu::PathList_t filenames;  // list of filenames to be processed
263
264   UL channel_assignment, picture_coding, transfer_characteristic, color_primaries, coding_equations;
265   ASDCP::MXF::AS02_MCAConfigParser mca_config;
266   std::string language;
267
268   ui32_t rgba_MaxRef;
269   ui32_t rgba_MinRef;
270
271   ui32_t horizontal_subsampling;
272   ui32_t vertical_subsampling;
273   ui32_t component_depth;
274   ui8_t frame_layout;
275   ASDCP::Rational aspect_ratio;
276   ui8_t field_dominance;
277   ui32_t mxf_header_size;
278   ui32_t cdci_BlackRefLevel; 
279   ui32_t cdci_WhiteRefLevel;
280   ui32_t cdci_ColorRange;
281
282   ui32_t md_min_luminance, md_max_luminance;
283   ASDCP::MXF::ThreeColorPrimaries md_primaries;
284   ASDCP::MXF::ColorPrimary md_white_point;
285
286   //new attributes for AS-02 support 
287   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
288   ui32_t partition_space; //Shim parameter partition_spacing
289
290   // ISXD
291   std::string isxd_document_namespace;
292   std::list<std::string> global_isxd_metadata;
293
294   //
295   MXF::LineMapPair line_map;
296   std::string out_file, profile_name; //
297   std::string mca_audio_element_kind, mca_audio_content_kind;
298   
299   //
300   bool set_video_line_map(const std::string& arg)
301   {
302     const char* sep_str = strrchr(arg.c_str(), ',');
303
304     if ( sep_str == 0 )
305       {
306         fprintf(stderr, "Expecting <first>,<second>\n");
307         return false;
308       }
309
310     line_map.First = Kumu::xabs(strtol(arg.c_str(), 0, 10));
311     line_map.Second = Kumu::xabs(strtol(sep_str+1, 0, 10));
312     return true;
313   }
314
315   //
316   bool set_video_ref(const std::string& arg)
317   {
318     std::list<std::string> ref_tokens = Kumu::km_token_split(arg, ",");
319
320     switch ( ref_tokens.size() )
321       {
322       case 3:
323         cdci_ColorRange = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
324         ref_tokens.pop_back();
325       case 2:
326         cdci_BlackRefLevel = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
327         ref_tokens.pop_back();
328       case 1:
329         cdci_WhiteRefLevel = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
330         break;
331
332       default:
333         fprintf(stderr, "Expecting <white-ref>[,<black-ref>[,<color-range>]]\n");
334         return false;
335       }
336
337     if ( cdci_WhiteRefLevel > 65535 || cdci_BlackRefLevel > 65535 || cdci_ColorRange > 65535 )
338       {
339         fprintf(stderr, "Unexpected CDCI video referece levels.\n");
340         return false;
341       }
342
343     return true;
344   }
345
346   //
347   bool set_display_primaries(const std::string& arg)
348   {
349     std::list<std::string> coordinate_tokens = Kumu::km_token_split(arg, ",");
350     if ( coordinate_tokens.size() != 8 )
351       {
352         fprintf(stderr, "Expecting four coordinate pairs.\n");
353         return false;
354       }
355
356     std::list<std::string>::const_iterator i = coordinate_tokens.begin();
357     if ( ! set_primary_from_token(*(i++), md_primaries.First.X) ) return false;
358     if ( ! set_primary_from_token(*(i++), md_primaries.First.Y) ) return false;
359     if ( ! set_primary_from_token(*(i++), md_primaries.Second.X) ) return false;
360     if ( ! set_primary_from_token(*(i++), md_primaries.Second.Y) ) return false;
361     if ( ! set_primary_from_token(*(i++), md_primaries.Third.X) ) return false;
362     if ( ! set_primary_from_token(*(i++), md_primaries.Third.Y) ) return false;
363     if ( ! set_primary_from_token(*(i++), md_white_point.X) ) return false;
364     if ( ! set_primary_from_token(*i, md_white_point.Y) ) return false;
365
366     return true;
367   }
368
369   //
370   bool set_display_luminance(const std::string& arg)
371   {
372     std::list<std::string> luminance_tokens = Kumu::km_token_split(arg, ",");
373     if ( luminance_tokens.size() != 2 )
374       {
375         fprintf(stderr, "Expecting a luminance pair.\n");
376         return false;
377       }
378
379     if ( ! set_luminance_from_token(luminance_tokens.front(), md_min_luminance) ) return false;
380     if ( ! set_luminance_from_token(luminance_tokens.back(), md_max_luminance) ) return false;
381
382     return true;
383   }
384
385   //
386   bool set_color_system_from_arg(const char* arg)
387   {
388     assert(arg);
389
390     switch ( *arg )
391       {
392         // Application 2 (ST 2067-20)
393       case '1':
394         coding_equations = g_dict->ul(MDD_CodingEquations_601);
395         transfer_characteristic = g_dict->ul(MDD_TransferCharacteristics_709);
396         color_primaries = g_dict->ul(MDD_ColorPrimaries_ITU470_PAL);
397         use_cdci_descriptor = true;
398         break;
399
400       case '2':
401         coding_equations = g_dict->ul(MDD_CodingEquations_601);
402         transfer_characteristic = g_dict->ul(MDD_TransferCharacteristics_709);
403         color_primaries = g_dict->ul(MDD_ColorPrimaries_SMPTE170M);
404         use_cdci_descriptor = true;
405         break;
406
407       case '3':
408         coding_equations = g_dict->ul(MDD_CodingEquations_709);
409         transfer_characteristic = g_dict->ul(MDD_TransferCharacteristics_709);
410         color_primaries = g_dict->ul(MDD_ColorPrimaries_BT709);
411         use_cdci_descriptor = true;
412         break;
413
414         // Application 2e (ST 2067-21)
415       case '4':
416         coding_equations = g_dict->ul(MDD_CodingEquations_709);
417         transfer_characteristic = g_dict->ul(MDD_TransferCharacteristics_xvYCC);
418         color_primaries = g_dict->ul(MDD_ColorPrimaries_BT709);
419         use_cdci_descriptor = true;
420         break;
421
422       case '5':
423         coding_equations = g_dict->ul(MDD_CodingEquations_709);
424         transfer_characteristic = g_dict->ul(MDD_TransferCharacteristics_2020);
425         color_primaries = g_dict->ul(MDD_ColorPrimaries_BT2020);
426         use_cdci_descriptor = true;
427         break;
428
429       default:
430         fprintf(stderr, "Unrecognized color system number, expecting one of 1-5.\n");
431         return false;
432       }
433     
434     return true;
435   }
436
437   CommandOptions(int argc, const char** argv) :
438     error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
439     encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
440     no_write_flag(false), version_flag(false), help_flag(false),
441     duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false),
442     edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
443     show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
444     mca_config(g_dict), rgba_MaxRef(1023), rgba_MinRef(0),
445     horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
446     frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
447     mxf_header_size(16384), cdci_WhiteRefLevel(940), cdci_BlackRefLevel(64), cdci_ColorRange(897),
448     md_min_luminance(0), md_max_luminance(0), line_map(0,0)
449   {
450     memset(key_value, 0, KeyLen);
451     memset(key_id_value, 0, UUIDlen);
452
453     coding_equations = g_dict->ul(MDD_CodingEquations_709);
454     color_primaries = g_dict->ul(MDD_ColorPrimaries_BT709);
455     transfer_characteristic = g_dict->ul(MDD_TransferCharacteristics_709);
456     std::string mca_config_str;
457
458     for ( int i = 1; i < argc; i++ )
459       {
460
461         if ( (strcmp( argv[i], "-help") == 0) )
462           {
463             help_flag = true;
464             continue;
465           }
466          
467         if ( argv[i][0] == '-'
468              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
469              && argv[i][2] == 0 )
470           {
471             switch ( argv[i][1] )
472               {
473               case 'A':
474                 TEST_EXTRA_ARG(i, 'A');
475                 if ( ! DecodeRational(argv[i], aspect_ratio) )
476                   {
477                     fprintf(stderr, "Error decoding aspect ratio value: %s\n", argv[i]);
478                     return;
479                   }
480                 break;
481
482               case 'a':
483                 asset_id_flag = true;
484                 TEST_EXTRA_ARG(i, 'a');
485                 {
486                   ui32_t length;
487                   Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
488
489                   if ( length != UUIDlen )
490                     {
491                       fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
492                       return;
493                     }
494                 }
495                 break;
496
497               case 'b':
498                 TEST_EXTRA_ARG(i, 'b');
499                 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
500
501                 if ( verbose_flag )
502                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
503
504                 break;
505
506               case 'c':
507                 TEST_EXTRA_ARG(i, 'c');
508                 if ( ! set_color_system_from_arg(argv[i]) )
509                   {
510                     return;
511                   }
512                 break;
513
514               case 'C':
515                 TEST_EXTRA_ARG(i, 'C');
516                 if ( ! channel_assignment.DecodeHex(argv[i]) )
517                   {
518                     fprintf(stderr, "Error decoding ChannelAssignment UL value: %s\n", argv[i]);
519                     return;
520                   }
521                 break;
522
523               case 'D':
524                 TEST_EXTRA_ARG(i, 'D');
525                 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
526                 break;
527
528               case 'd':
529                 TEST_EXTRA_ARG(i, 'd');
530                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
531                 break;
532
533               case 'E': encrypt_header_flag = false; break;
534               case 'e': encrypt_header_flag = true; break;
535
536               case 'F':
537                 TEST_EXTRA_ARG(i, 'F');
538                 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
539                 if ( field_dominance > 1 )
540                   {
541                     fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
542                     return;
543                   }
544                 break;
545
546               case 'g':
547                 TEST_EXTRA_ARG(i, 'g');
548                 language = argv[i];
549                 break;
550
551               case 'G':
552                 TEST_EXTRA_ARG(i, 'G');
553                 global_isxd_metadata.push_back(argv[i]);
554                 break;
555
556               case 'h': help_flag = true; break;
557
558               case 'i':
559                 frame_layout = 1;
560                 use_cdci_descriptor = true;
561                 break;
562
563               case 'j':
564                 key_id_flag = true;
565                 TEST_EXTRA_ARG(i, 'j');
566                 {
567                   ui32_t length;
568                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
569
570                   if ( length != UUIDlen )
571                     {
572                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
573                       return;
574                     }
575                 }
576                 break;
577
578               case 'k': key_flag = true;
579                 TEST_EXTRA_ARG(i, 'k');
580                 {
581                   ui32_t length;
582                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
583
584                   if ( length != KeyLen )
585                     {
586                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
587                       return;
588                     }
589                 }
590                 break;
591
592               case 'l':
593                 TEST_EXTRA_ARG(i, 'y');
594                 if ( ! set_video_line_map(argv[i]) )
595                   {
596                     return;
597                   }
598                 break;
599
600               case 'M': write_hmac = false; break;
601
602               case 'm':
603                 TEST_EXTRA_ARG(i, 'm');
604                 mca_config_str = argv[i];
605                 break;
606
607               case 'n':
608                 TEST_EXTRA_ARG(i, 'n');
609                 if ( ! transfer_characteristic.DecodeHex(argv[i]) )
610                   {
611                     fprintf(stderr, "Error decoding TransferCharacteristic UL value: %s\n", argv[i]);
612                     return;
613                   }
614                 break;
615
616               case 'O':
617                 TEST_EXTRA_ARG(i, 'O');
618                 if ( ! set_display_primaries(argv[i]) )
619                   {
620                     return;
621                   }
622                 break;
623
624               case 'o':
625                 TEST_EXTRA_ARG(i, 'o');
626                 if ( ! set_display_luminance(argv[i]) )
627                   {
628                     return;
629                   }
630                 break;
631
632               case 'P':
633                 TEST_EXTRA_ARG(i, 'P');
634                 profile_name = argv[i];
635                 break;
636
637               case 'p':
638                 TEST_EXTRA_ARG(i, 'p');
639                 if ( ! picture_coding.DecodeHex(argv[i]) )
640                   {
641                     fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
642                     return;
643                   }
644                 break;
645
646               case 'q':
647                 TEST_EXTRA_ARG(i, 'q');
648                 if ( ! coding_equations.DecodeHex(argv[i]) )
649                   {
650                     fprintf(stderr, "Error decoding CodingEquations UL value: %s\n", argv[i]);
651                     return;
652                   }
653                 break;
654
655               case 'r':
656                 TEST_EXTRA_ARG(i, 'r');
657                 if ( ! DecodeRational(argv[i], edit_rate) )
658                   {
659                     fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
660                     return;
661                   }
662                 
663                 break;
664
665               case 'R':
666                 use_cdci_descriptor = false;
667                 break;
668
669               case 's':
670                 TEST_EXTRA_ARG(i, 's');
671                 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
672                 break;
673
674               case 't':
675                 TEST_EXTRA_ARG(i, 't');
676                 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
677                 break;
678
679               case 'T':
680                 TEST_EXTRA_ARG(i, 'T');
681                 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
682                 break;
683
684               case 'u': show_ul_values_flag = true; break;
685
686               case 'U':
687                 TEST_EXTRA_ARG(i, 'U');
688                 isxd_document_namespace = argv[i];
689                 break;
690
691               case 'V': version_flag = true; break;
692               case 'v': verbose_flag = true; break;
693               case 'W': no_write_flag = true; break;
694
695               case 'x':
696                 TEST_EXTRA_ARG(i, 'x');
697                 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
698                 break;
699
700               case 'X':
701                 TEST_EXTRA_ARG(i, 'X');
702                 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
703                 break;
704
705               case 'Y':
706                 use_cdci_descriptor = true;
707                 // default 10 bit video range YUV, ref levels already set
708                 break;
709
710               case 'y':
711                 // Use values provided as argument, sharp tool, be careful
712                 use_cdci_descriptor = true;
713                 TEST_EXTRA_ARG(i, 'y');
714                 if ( ! set_video_ref(argv[i]) )
715                   {
716                     return;
717                   }
718                 break;
719
720               case 'Z': j2c_pedantic = false; break;
721               case 'z': j2c_pedantic = true; break;
722
723               default:
724                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
725                 return;
726               }
727           }
728         else if ( argv[i][0] == '-' && argv[i][1] == '-' && isalpha(argv[i][2]) )
729           {
730             if ( strcmp(argv[i]+2, "mca-audio-content-kind") == 0 )
731               {
732                 if ( ++i >= argc || argv[(i)][0] == '-' )
733                   {
734                     fprintf(stderr, "Argument not found for option -mca-audio-content-kind.\n");
735                     return;
736                   }
737                 
738                 mca_audio_content_kind = argv[i];
739               }
740             else if ( strcmp(argv[i]+2, "mca-audio-element-kind") == 0 )
741               {
742                 if ( ++i >= argc || argv[(i)][0] == '-' )
743                   {
744                     fprintf(stderr, "Argument not found for option -mca-audio-element-kind.\n");
745                     return;
746                   }
747
748                 mca_audio_element_kind = argv[i];
749               }
750             else
751               {
752                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
753                 return;
754               }
755           }
756         else
757           {
758             if ( argv[i][0] != '-' )
759               {
760                 filenames.push_back(argv[i]);
761               }
762             else
763               {
764                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
765                 return;
766               }
767           }
768       }
769
770     if ( ! mca_config_str.empty() )
771       {
772         if ( language.empty() )
773           {
774             if ( ! mca_config.DecodeString(mca_config_str) )
775               {
776                 return;
777               }
778           }
779         else
780           {
781             if ( ! mca_config.DecodeString(mca_config_str, language) )
782               {
783                 return;
784               }
785           }
786       }
787
788     if ( help_flag || version_flag || show_ul_values_flag )
789       {
790         return;
791       }
792
793     if ( filenames.size() < 2 )
794       {
795         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
796         return;
797       }
798
799     out_file = filenames.back();
800     filenames.pop_back();
801
802     if ( ! picture_coding.HasValue() )
803       {
804         picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
805       }
806
807     error_flag = false;
808   }
809 };
810
811
812 //------------------------------------------------------------------------------------------
813 // JPEG 2000 essence
814
815 namespace ASDCP {
816   Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
817                             const ASDCP::Dictionary& dict,
818                             ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
819                             ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
820
821   Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
822 }
823
824 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
825 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
826 //
827 Result_t
828 write_JP2K_file(CommandOptions& Options)
829 {
830   AESEncContext*          Context = 0;
831   HMACContext*            HMAC = 0;
832   AS_02::JP2K::MXFWriter  Writer;
833   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
834   JP2K::SequenceParser    Parser;
835   byte_t                  IV_buf[CBC_BLOCK_SIZE];
836   Kumu::FortunaRNG        RNG;
837   ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
838   ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
839
840   // set up essence parser
841   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
842
843   // set up MXF writer
844   if ( ASDCP_SUCCESS(result) )
845     {
846       ASDCP::JP2K::PictureDescriptor PDesc;
847       Parser.FillPictureDescriptor(PDesc);
848       PDesc.EditRate = Options.edit_rate;
849
850       if ( Options.verbose_flag )
851         {
852           fprintf(stderr, "JPEG 2000 pictures\n");
853           fputs("PictureDescriptor:\n", stderr);
854           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
855           JP2K::PictureDescriptorDump(PDesc);
856         }
857
858       if ( Options.use_cdci_descriptor )
859         {
860           ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
861           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
862           
863           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
864                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
865                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
866
867           if ( ASDCP_SUCCESS(result) )
868             {
869               tmp_dscr->CodingEquations = Options.coding_equations;
870               tmp_dscr->TransferCharacteristic = Options.transfer_characteristic;
871               tmp_dscr->ColorPrimaries = Options.color_primaries;
872               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
873               tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
874               tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
875               tmp_dscr->ComponentDepth = Options.component_depth;
876               tmp_dscr->FrameLayout = Options.frame_layout;
877               tmp_dscr->AspectRatio = Options.aspect_ratio;
878               tmp_dscr->FieldDominance = Options.field_dominance;
879               tmp_dscr->WhiteReflevel = Options.cdci_WhiteRefLevel;
880               tmp_dscr->BlackRefLevel = Options.cdci_BlackRefLevel;
881               tmp_dscr->ColorRange = Options.cdci_ColorRange;
882               tmp_dscr->VideoLineMap = Options.line_map;
883
884               if ( Options.md_min_luminance || Options.md_max_luminance )
885                 {
886                   tmp_dscr->MasteringDisplayMinimumLuminance = Options.md_min_luminance;
887                   tmp_dscr->MasteringDisplayMaximumLuminance = Options.md_max_luminance;
888                 }
889
890               if ( Options.md_primaries.HasValue() )
891                 {
892                   tmp_dscr->MasteringDisplayPrimaries = Options.md_primaries;
893                   tmp_dscr->MasteringDisplayWhitePointChromaticity = Options.md_white_point;
894                 }
895
896               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
897             }
898         }
899       else
900         { // use RGB
901           ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
902           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
903           
904           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
905                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
906                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
907
908           if ( ASDCP_SUCCESS(result) )
909             {
910               tmp_dscr->CodingEquations = Options.coding_equations;
911               tmp_dscr->TransferCharacteristic = Options.transfer_characteristic;
912               tmp_dscr->ColorPrimaries = Options.color_primaries;
913               tmp_dscr->ScanningDirection = 0;
914               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
915               tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
916               tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
917
918               if ( Options.md_min_luminance || Options.md_max_luminance )
919                 {
920                   tmp_dscr->MasteringDisplayMinimumLuminance = Options.md_min_luminance;
921                   tmp_dscr->MasteringDisplayMaximumLuminance = Options.md_max_luminance;
922                 }
923
924               if ( Options.md_primaries.HasValue() )
925                 {
926                   tmp_dscr->MasteringDisplayPrimaries = Options.md_primaries;
927                   tmp_dscr->MasteringDisplayWhitePointChromaticity = Options.md_white_point;
928                 }
929
930               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
931             }
932         }
933     }
934
935   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
936     {
937       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
938       Info.LabelSetType = LS_MXF_SMPTE;
939
940       if ( Options.asset_id_flag )
941         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
942       else
943         Kumu::GenRandomUUID(Info.AssetUUID);
944
945       // configure encryption
946       if( Options.key_flag )
947         {
948           Kumu::GenRandomUUID(Info.ContextID);
949           Info.EncryptedEssence = true;
950
951           if ( Options.key_id_flag )
952             {
953               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
954             }
955           else
956             {
957               create_random_uuid(Info.CryptographicKeyID);
958             }
959
960           Context = new AESEncContext;
961           result = Context->InitKey(Options.key_value);
962
963           if ( ASDCP_SUCCESS(result) )
964             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
965
966           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
967             {
968               Info.UsesHMAC = true;
969               HMAC = new HMACContext;
970               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
971             }
972         }
973
974       if ( ASDCP_SUCCESS(result) )
975         {
976           result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
977                                     Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
978         }
979     }
980
981   if ( ASDCP_SUCCESS(result) )
982     {
983       ui32_t duration = 0;
984       result = Parser.Reset();
985
986       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
987         {
988           result = Parser.ReadFrame(FrameBuffer);
989           
990           if ( ASDCP_SUCCESS(result) )
991             {
992               if ( Options.verbose_flag )
993                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
994               
995               if ( Options.encrypt_header_flag )
996                 FrameBuffer.PlaintextOffset(0);
997             }
998
999           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1000             {
1001               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1002
1003               // The Writer class will forward the last block of ciphertext
1004               // to the encryption context for use as the IV for the next
1005               // frame. If you want to use non-sequitur IV values, un-comment
1006               // the following  line of code.
1007               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1008               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1009             }
1010         }
1011
1012       if ( result == RESULT_ENDOFFILE )
1013         result = RESULT_OK;
1014     }
1015
1016   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1017     result = Writer.Finalize();
1018
1019   return result;
1020 }
1021
1022 //------------------------------------------------------------------------------------------
1023 // PCM essence
1024 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
1025 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
1026 //
1027 Result_t
1028 write_PCM_file(CommandOptions& Options)
1029 {
1030   AESEncContext*    Context = 0;
1031   HMACContext*      HMAC = 0;
1032   PCMParserList     Parser;
1033   AS_02::PCM::MXFWriter    Writer;
1034   PCM::FrameBuffer  FrameBuffer;
1035   byte_t            IV_buf[CBC_BLOCK_SIZE];
1036   Kumu::FortunaRNG  RNG;
1037   ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
1038
1039   // set up essence parser
1040   Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
1041
1042   // set up MXF writer
1043   if ( ASDCP_SUCCESS(result) )
1044     {
1045       ASDCP::PCM::AudioDescriptor ADesc;
1046       Parser.FillAudioDescriptor(ADesc);
1047
1048       ADesc.EditRate = Options.edit_rate;
1049       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1050
1051       if ( Options.verbose_flag )
1052         {
1053           char buf[64];
1054           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1055                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
1056                   RationalToString(Options.edit_rate, buf, 64),
1057                   PCM::CalcSamplesPerFrame(ADesc));
1058           fputs("AudioDescriptor:\n", stderr);
1059           PCM::AudioDescriptorDump(ADesc);
1060         }
1061
1062       essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
1063
1064       result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
1065
1066       if ( Options.mca_config.empty() )
1067         {
1068           essence_descriptor->ChannelAssignment = Options.channel_assignment;
1069         }
1070       else
1071         {
1072           if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
1073             {
1074               fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
1075                       Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
1076               return RESULT_FAIL;
1077             }
1078
1079           // This marks all soundfield groups using the same MCA property values
1080           MXF::InterchangeObject_list_t::iterator i;
1081           for ( i = Options.mca_config.begin(); i != Options.mca_config.end(); ++i )
1082             {
1083               MXF::SoundfieldGroupLabelSubDescriptor * desc = dynamic_cast<MXF::SoundfieldGroupLabelSubDescriptor*>(*i);
1084               if ( desc != 0 )
1085                 {
1086                   if ( ! Options.mca_audio_content_kind.empty() )
1087                     {
1088                       desc->MCAAudioContentKind = Options.mca_audio_content_kind;
1089                     }
1090                   if ( ! Options.mca_audio_element_kind.empty() )
1091                     {
1092                       desc->MCAAudioElementKind = Options.mca_audio_element_kind;
1093                     }
1094                 }
1095             }
1096
1097           essence_descriptor->ChannelAssignment = g_dict->ul(MDD_IMFAudioChannelCfg_MCA);
1098         }
1099     }
1100
1101   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1102     {
1103       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1104       Info.LabelSetType = LS_MXF_SMPTE;
1105
1106       if ( Options.asset_id_flag )
1107         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1108       else
1109         Kumu::GenRandomUUID(Info.AssetUUID);
1110
1111       // configure encryption
1112       if( Options.key_flag )
1113         {
1114           Kumu::GenRandomUUID(Info.ContextID);
1115           Info.EncryptedEssence = true;
1116
1117           if ( Options.key_id_flag )
1118             {
1119               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1120             }
1121           else
1122             {
1123               create_random_uuid(Info.CryptographicKeyID);
1124             }
1125
1126           Context = new AESEncContext;
1127           result = Context->InitKey(Options.key_value);
1128
1129           if ( ASDCP_SUCCESS(result) )
1130             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1131
1132           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1133             {
1134               Info.UsesHMAC = true;
1135               HMAC = new HMACContext;
1136               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1137             }
1138         }
1139
1140       if ( ASDCP_SUCCESS(result) )
1141         {
1142           result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
1143                                     Options.mca_config, Options.edit_rate);
1144         }
1145     }
1146
1147   if ( ASDCP_SUCCESS(result) )
1148     {
1149       result = Parser.Reset();
1150       ui32_t duration = 0;
1151
1152       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1153         {
1154           result = Parser.ReadFrame(FrameBuffer);
1155
1156           if ( ASDCP_SUCCESS(result) )
1157             {
1158               if ( Options.verbose_flag )
1159                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1160
1161               if ( ! Options.no_write_flag )
1162                 {
1163                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1164
1165                   // The Writer class will forward the last block of ciphertext
1166                   // to the encryption context for use as the IV for the next
1167                   // frame. If you want to use non-sequitur IV values, un-comment
1168                   // the following  line of code.
1169                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1170                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1171                 }
1172             }
1173         }
1174
1175       if ( result == RESULT_ENDOFFILE )
1176         result = RESULT_OK;
1177     }
1178
1179   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1180     result = Writer.Finalize();
1181
1182   return result;
1183 }
1184
1185
1186
1187
1188 //------------------------------------------------------------------------------------------
1189 // TimedText essence
1190
1191
1192 // Write one or more plaintext timed text streams to a plaintext AS-02 file
1193 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
1194 //
1195 Result_t
1196 write_timed_text_file(CommandOptions& Options)
1197 {
1198   AESEncContext*    Context = 0;
1199   HMACContext*      HMAC = 0;
1200   AS_02::TimedText::ST2052_TextParser  Parser;
1201   AS_02::TimedText::MXFWriter    Writer;
1202   TimedText::FrameBuffer  FrameBuffer;
1203   TimedText::TimedTextDescriptor TDesc;
1204   byte_t            IV_buf[CBC_BLOCK_SIZE];
1205   Kumu::FortunaRNG  RNG;
1206
1207   // set up essence parser
1208   Result_t result = Parser.OpenRead(Options.filenames.front());
1209
1210   // set up MXF writer
1211   if ( ASDCP_SUCCESS(result) )
1212     {
1213       Parser.FillTimedTextDescriptor(TDesc);
1214       TDesc.EditRate = Options.edit_rate;
1215       TDesc.ContainerDuration = Options.duration;
1216       FrameBuffer.Capacity(Options.fb_size);
1217
1218       if ( ! Options.profile_name.empty() )
1219         {
1220           TDesc.NamespaceName = Options.profile_name;
1221         }
1222
1223       if ( Options.verbose_flag )
1224         {
1225           fputs("IMF Timed-Text Descriptor:\n", stderr);
1226           TimedText::DescriptorDump(TDesc);
1227         }
1228     }
1229
1230   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1231     {
1232       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1233       Info.LabelSetType = LS_MXF_SMPTE;
1234
1235       if ( Options.asset_id_flag )
1236         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1237       else
1238         Kumu::GenRandomUUID(Info.AssetUUID);
1239
1240       // configure encryption
1241       if( Options.key_flag )
1242         {
1243           Kumu::GenRandomUUID(Info.ContextID);
1244           Info.EncryptedEssence = true;
1245
1246           if ( Options.key_id_flag )
1247             {
1248               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1249             }
1250           else
1251             {
1252               create_random_uuid(Info.CryptographicKeyID);
1253             }
1254
1255           Context = new AESEncContext;
1256           result = Context->InitKey(Options.key_value);
1257
1258           if ( ASDCP_SUCCESS(result) )
1259             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1260
1261           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1262             {
1263               Info.UsesHMAC = true;
1264               HMAC = new HMACContext;
1265               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1266             }
1267         }
1268
1269       if ( ASDCP_SUCCESS(result) )
1270         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
1271     }
1272
1273   if ( ASDCP_FAILURE(result) )
1274     return result;
1275
1276   std::string XMLDoc;
1277   TimedText::ResourceList_t::const_iterator ri;
1278
1279   result = Parser.ReadTimedTextResource(XMLDoc);
1280
1281   if ( ASDCP_SUCCESS(result) )
1282     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1283
1284   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1285     {
1286       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1287
1288       if ( ASDCP_SUCCESS(result) )
1289         {
1290           if ( Options.verbose_flag )
1291             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1292
1293           if ( ! Options.no_write_flag )
1294             {
1295               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1296
1297               // The Writer class will forward the last block of ciphertext
1298               // to the encryption context for use as the IV for the next
1299               // frame. If you want to use non-sequitur IV values, un-comment
1300               // the following  line of code.
1301               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1302               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1303             }
1304         }
1305
1306       if ( result == RESULT_ENDOFFILE )
1307         result = RESULT_OK;
1308     }
1309
1310   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1311     result = Writer.Finalize();
1312
1313   return result;
1314 }
1315
1316 //
1317 bool
1318 get_current_dms_text_descriptor(AS_02::ISXD::MXFWriter& writer, ASDCP::MXF::GenericStreamTextBasedSet *&text_object)
1319 {
1320   std::list<MXF::InterchangeObject*> object_list;
1321   writer.OP1aHeader().GetMDObjectsByType(DefaultSMPTEDict().ul(MDD_GenericStreamTextBasedSet), object_list);
1322
1323   if ( object_list.empty() )
1324     {
1325       return false;
1326     }
1327
1328   text_object = dynamic_cast<MXF::GenericStreamTextBasedSet*>(object_list.back());
1329   assert(text_object != 0);
1330   return true;
1331 }
1332                       
1333
1334 // Write one or more plaintext Aux Data bytestreams to a plaintext AS-02 file
1335 // Write one or more plaintext Aux Data bytestreams to a ciphertext AS-02 file
1336 //
1337 Result_t
1338 write_isxd_file(CommandOptions& Options)
1339 {
1340   AESEncContext*          Context = 0;
1341   HMACContext*            HMAC = 0;
1342   AS_02::ISXD::MXFWriter Writer;
1343   DCData::FrameBuffer     FrameBuffer(Options.fb_size);
1344   DCData::SequenceParser  Parser;
1345   byte_t                  IV_buf[CBC_BLOCK_SIZE];
1346   Kumu::FortunaRNG        RNG;
1347
1348   // set up essence parser
1349   Result_t result = Parser.OpenRead(Options.filenames.front());
1350
1351   // set up MXF writer
1352   if ( ASDCP_SUCCESS(result) )
1353     {
1354
1355       if ( Options.verbose_flag )
1356         {
1357           fprintf(stderr, "ISXD Data\n");
1358           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1359         }
1360     }
1361
1362   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1363   {
1364     WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1365     if ( Options.asset_id_flag )
1366       memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1367     else
1368       Kumu::GenRandomUUID(Info.AssetUUID);
1369
1370     Info.LabelSetType = LS_MXF_SMPTE;
1371
1372       // configure encryption
1373     if( Options.key_flag )
1374         {
1375           Kumu::GenRandomUUID(Info.ContextID);
1376           Info.EncryptedEssence = true;
1377
1378           if ( Options.key_id_flag )
1379             {
1380               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1381             }
1382           else
1383             {
1384               create_random_uuid(Info.CryptographicKeyID);
1385             }
1386
1387           Context = new AESEncContext;
1388           result = Context->InitKey(Options.key_value);
1389
1390           if ( ASDCP_SUCCESS(result) )
1391             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1392
1393           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1394       {
1395         Info.UsesHMAC = true;
1396         HMAC = new HMACContext;
1397         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1398       }
1399         }
1400
1401     if ( ASDCP_SUCCESS(result) )
1402       {
1403         if ( Options.isxd_document_namespace == "auto" )
1404           {
1405             // get ns of first item
1406             std::string ns_prefix, type_name, namespace_name;
1407             result = Parser.ReadFrame(FrameBuffer);
1408
1409             if ( ASDCP_SUCCESS(result) )
1410               {
1411                 Kumu::AttributeList doc_attr_list;
1412                 result = GetXMLDocType(FrameBuffer.RoData(), FrameBuffer.Size(), ns_prefix, type_name,
1413                                        namespace_name, doc_attr_list) ? RESULT_OK : RESULT_FAIL;
1414               }
1415
1416             if ( ASDCP_SUCCESS(result) && ! namespace_name.empty() )
1417               {
1418                 Options.isxd_document_namespace = namespace_name;
1419               }
1420             else
1421               {
1422                 fprintf(stderr, "Unable to parse an XML namespace name from the input document.\n");
1423                 return RESULT_FAIL;
1424               }
1425           }
1426
1427         result = Writer.OpenWrite(Options.out_file, Info, Options.isxd_document_namespace, Options.edit_rate);
1428       }
1429   }
1430
1431   if ( ASDCP_SUCCESS(result) )
1432     {
1433       ui32_t duration = 0;
1434       result = Parser.Reset();
1435
1436       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1437         {
1438           result = Parser.ReadFrame(FrameBuffer);
1439
1440           if ( ASDCP_SUCCESS(result) )
1441             {
1442               if ( Options.verbose_flag )
1443                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1444
1445               if ( Options.encrypt_header_flag )
1446                 FrameBuffer.PlaintextOffset(0);
1447             }
1448
1449           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1450             {
1451               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1452
1453               // The Writer class will forward the last block of ciphertext
1454               // to the encryption context for use as the IV for the next
1455               // frame. If you want to use non-sequitur IV values, un-comment
1456               // the following  line of code.
1457               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1458               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1459             }
1460         }
1461
1462       if ( result == RESULT_ENDOFFILE )
1463         {
1464           result = RESULT_OK;
1465         }
1466     }
1467   
1468   if ( KM_SUCCESS(result) && ! Options.no_write_flag )
1469     {
1470       ASDCP::FrameBuffer global_metadata;
1471       std::list<std::string>::iterator i;
1472       
1473       for ( i = Options.global_isxd_metadata.begin(); i != Options.global_isxd_metadata.end(); ++i )
1474         {
1475           ui32_t file_size = Kumu::FileSize(*i);
1476           result = global_metadata.Capacity(file_size);
1477
1478           if ( KM_SUCCESS(result) )
1479             {
1480               ui32_t read_count = 0;
1481               Kumu::FileReader Reader;
1482               std::string namespace_name;
1483
1484               result = Reader.OpenRead(*i);
1485
1486               if ( KM_SUCCESS(result) )
1487                 {
1488                   result = Reader.Read(global_metadata.Data(), file_size, &read_count);
1489                 }
1490
1491               if ( KM_SUCCESS(result) )
1492                 {
1493                   if ( file_size != read_count) 
1494                     return RESULT_READFAIL;
1495
1496                   global_metadata.Size(read_count);
1497
1498                   std::string ns_prefix, type_name;
1499                   Kumu::AttributeList doc_attr_list;
1500                   result = GetXMLDocType(global_metadata.RoData(), global_metadata.Size(), ns_prefix, type_name,
1501                                          namespace_name, doc_attr_list) ? RESULT_OK : RESULT_FAIL;
1502                 }
1503
1504               if ( KM_SUCCESS(result) )
1505                 {
1506                   result = Writer.AddDmsGenericPartUtf8Text(global_metadata, Context, HMAC);
1507                 }
1508
1509               if ( KM_SUCCESS(result) )
1510                 {
1511                   ASDCP::MXF::GenericStreamTextBasedSet *text_object = 0;
1512                   get_current_dms_text_descriptor(Writer, text_object);
1513                   assert(text_object);
1514                   text_object->TextMIMEMediaType = "text/xml";
1515                   text_object->TextDataDescription = namespace_name;
1516
1517                   // this is not really useful when inserting multiple objects because
1518                   // it cannot be set per object without some other CLI syntax for
1519                   // associating language codes with 2057 blobs, e.g., <filename>:<lang>
1520                   text_object->RFC5646TextLanguageCode = Options.language;
1521                 }
1522             }
1523         }
1524
1525       if ( KM_SUCCESS(result) )
1526         {
1527           result = Writer.Finalize();
1528         }
1529     }
1530
1531   return result;
1532 }
1533
1534 //
1535 int
1536 main(int argc, const char** argv)
1537 {
1538   Result_t result = RESULT_OK;
1539   char     str_buf[64];
1540   g_dict = &ASDCP::DefaultSMPTEDict();
1541   assert(g_dict);
1542
1543   CommandOptions Options(argc, argv);
1544
1545   if ( Options.version_flag )
1546     banner();
1547
1548   if ( Options.help_flag )
1549     usage();
1550
1551   if ( Options.show_ul_values_flag )
1552     {
1553       g_dict->Dump(stdout);
1554     }
1555
1556   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
1557     return 0;
1558
1559   if ( Options.error_flag )
1560     {
1561       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1562       return 3;
1563     }
1564
1565   EssenceType_t EssenceType;
1566   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
1567
1568   if ( ASDCP_SUCCESS(result) )
1569     {
1570       switch ( EssenceType )
1571         {
1572         case ESS_JPEG_2000:
1573           result = write_JP2K_file(Options);
1574           break;
1575
1576         case ESS_PCM_24b_48k:
1577         case ESS_PCM_24b_96k:
1578           result = write_PCM_file(Options);
1579           break;
1580
1581         case ESS_TIMED_TEXT:
1582           result = write_timed_text_file(Options);
1583           break;
1584
1585         case ESS_DCDATA_UNKNOWN:
1586           if ( ! Options.isxd_document_namespace.empty() )
1587             {
1588               result = write_isxd_file(Options);      
1589             }
1590           else
1591             {
1592               fprintf(stderr, "%s: Unknown synchronous data file type, not AS-02-compatible essence.\n",
1593                       Options.filenames.front().c_str());
1594               return 5;
1595             }
1596           break;
1597
1598         default:
1599           fprintf(stderr, "%s: Unknown file type, not AS-02-compatible essence.\n",
1600                   Options.filenames.front().c_str());
1601           return 5;
1602         }
1603     }
1604
1605   if ( ASDCP_FAILURE(result) )
1606     {
1607       fputs("Program stopped on error.\n", stderr);
1608
1609       if ( result != RESULT_FAIL )
1610         {
1611           fputs(result, stderr);
1612           fputc('\n', stderr);
1613         }
1614
1615       return 1;
1616     }
1617
1618   return 0;
1619 }
1620
1621
1622 //
1623 // end as-02-wrap.cpp
1624 //