e414e9ec44d123dc2085d6c26363e963d9de722b
[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\n\
155   -m <expr>         - Write MCA labels using <expr>.  Example:\n\
156                         51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
157   -M                - Do not create HMAC values when writing\n\
158   -n <UL>           - Set the TransferCharacteristic UL\n\
159   -o <min>,<max>    - Mastering Display luminance, cd*m*m, e.g., \".05,100\"\n\
160   -O <rx>,<ry>,<gx>,<gy>,<bx>,<by>,<wx>,<wy>\n\
161                     - Mastering Display Color Primaries and white point\n\
162                       e.g., \".64,.33,.3,.6,.15,.06,.3457,.3585\"\n\
163   -p <ul>           - Set broadcast profile\n\
164   -P <string>       - Set NamespaceURI property when creating timed text MXF\n\
165   -q <UL>           - Set the CodingEquations UL\n\
166   -r <n>/<d>        - Edit Rate of the output file.  24/1 is the default\n\
167   -R                - Indicates RGB image essence (default except with -c)\n\
168   -s <seconds>      - Duration of a frame-wrapped partition (default 60)\n\
169   -t <min>          - Set RGB component minimum code value (default: 0)\n\
170   -T <max>          - Set RGB component maximum code value (default: 1023)\n\
171   -u                - Print UL catalog to stdout\n\
172   -u <URI>          - ISXD (RDD47) document URI (use 'auto' to read the\n\
173                       namespace name from the first edit unit\n\
174   -v                - Verbose, prints informative messages to stderr\n\
175   -W                - Read input file only, do not write source file\n\
176   -x <int>          - Horizontal subsampling degree (default: 2)\n\
177   -X <int>          - Vertical subsampling degree (default: 2)\n\
178   -y <white-ref>[,<black-ref>[,<color-range>]]\n\
179                     - Same as -Y but White Ref, Black Ref and Color Range are\n\
180                       set from the given argument\n\
181   -Y                - Indicates YCbCr image essence (default with -c), uses\n\
182                       default values for White Ref, Black Ref and Color Range,\n\
183                        940,64,897, indicating 10 bit standard Video Range\n\
184   -z                - Fail if j2c inputs have unequal parameters (default)\n\
185   -Z                - Ignore unequal parameters in j2c inputs\n\
186 \n\
187   --mca-audio-content-kind <string>\n\
188                     - UL value for MCA descriptor MCAAudioContentKind property\n\
189   --mca-audio-element-kind <string>\n\
190                     - UL value for MCA descriptor MCAAudioElementKind property\n\
191 \n\
192   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
193          o All option arguments must be separated from the option by whitespace.\n\n");
194 }
195
196 const float chromaticity_scale = 50000.0;
197 //
198 ui32_t
199 set_primary_from_token(const std::string& token, ui16_t& primary)
200 {
201   float raw_value = strtod(token.c_str(),0);
202
203   if ( raw_value == 0.0 || raw_value > 1.0 )
204     {
205       fprintf(stderr, "Invalid coordinate value \"%s\".\n", token.c_str());
206       return false;
207     }
208
209   primary = floor(0.5 + ( raw_value * chromaticity_scale ));
210   return true;
211 }
212
213 const float luminance_scale = 10000.0;
214 //
215 ui32_t
216 set_luminance_from_token(const std::string& token, ui32_t& luminance)
217 {
218   float raw_value = strtod(token.c_str(),0);
219
220   if ( raw_value == 0.0 || raw_value > 400000.0 )
221     {
222       fprintf(stderr, "Invalid luminance value \"%s\".\n", token.c_str());
223       return false;
224     }
225
226   luminance = floor(0.5 + ( raw_value * luminance_scale ));
227   return true;
228 }
229
230 #define SET_LUMINANCE(p,t)                      \
231   if ( ! set_luminance_from_token(t, p) ) {     \
232     return false;                               \
233   }
234
235 //
236 class CommandOptions
237 {
238   CommandOptions();
239
240 public:
241   bool   error_flag;     // true if the given options are in error or not complete
242   bool   key_flag;       // true if an encryption key was given
243   bool   asset_id_flag;  // true if an asset ID was given
244   bool   encrypt_header_flag; // true if j2c headers are to be encrypted
245   bool   write_hmac;     // true if HMAC values are to be generated and written
246   bool   verbose_flag;   // true if the verbose option was selected
247   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
248   bool   no_write_flag;  // true if no output files are to be written
249   bool   version_flag;   // true if the version display option was selected
250   bool   help_flag;      // true if the help display option was selected
251   ui32_t duration;       // number of frames to be processed
252   bool   j2c_pedantic;   // passed to JP2K::SequenceParser::OpenRead
253   bool use_cdci_descriptor; // 
254   Rational edit_rate;    // edit rate of JP2K sequence
255   ui32_t fb_size;        // size of picture frame buffer
256   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
257   bool   key_id_flag;    // true if a key ID was given
258   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
259   byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
260   bool show_ul_values_flag;    // if true, dump the UL table before going tp work.
261   Kumu::PathList_t filenames;  // list of filenames to be processed
262
263   UL channel_assignment, picture_coding, transfer_characteristic, color_primaries, coding_equations;
264   ASDCP::MXF::AS02_MCAConfigParser mca_config;
265   std::string language;
266
267   ui32_t rgba_MaxRef;
268   ui32_t rgba_MinRef;
269
270   ui32_t horizontal_subsampling;
271   ui32_t vertical_subsampling;
272   ui32_t component_depth;
273   ui8_t frame_layout;
274   ASDCP::Rational aspect_ratio;
275   ui8_t field_dominance;
276   ui32_t mxf_header_size;
277   ui32_t cdci_BlackRefLevel; 
278   ui32_t cdci_WhiteRefLevel;
279   ui32_t cdci_ColorRange;
280
281   ui32_t md_min_luminance, md_max_luminance;
282   ASDCP::MXF::ThreeColorPrimaries md_primaries;
283   ASDCP::MXF::ColorPrimary md_white_point;
284
285   //new attributes for AS-02 support 
286   AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
287   ui32_t partition_space; //Shim parameter partition_spacing
288
289   // ISXD
290   std::string isxd_document_namespace;
291   std::list<std::string> global_isxd_metadata;
292
293   //
294   MXF::LineMapPair line_map;
295   bool line_map_flag;
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), line_map_flag(false)
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                   } else {
598                                 line_map_flag = true;
599                   }
600                 break;
601
602               case 'M': write_hmac = false; break;
603
604               case 'm':
605                 TEST_EXTRA_ARG(i, 'm');
606                 mca_config_str = argv[i];
607                 break;
608
609               case 'n':
610                 TEST_EXTRA_ARG(i, 'n');
611                 if ( ! transfer_characteristic.DecodeHex(argv[i]) )
612                   {
613                     fprintf(stderr, "Error decoding TransferCharacteristic UL value: %s\n", argv[i]);
614                     return;
615                   }
616                 break;
617
618               case 'O':
619                 TEST_EXTRA_ARG(i, 'O');
620                 if ( ! set_display_primaries(argv[i]) )
621                   {
622                     return;
623                   }
624                 break;
625
626               case 'o':
627                 TEST_EXTRA_ARG(i, 'o');
628                 if ( ! set_display_luminance(argv[i]) )
629                   {
630                     return;
631                   }
632                 break;
633
634               case 'P':
635                 TEST_EXTRA_ARG(i, 'P');
636                 profile_name = argv[i];
637                 break;
638
639               case 'p':
640                 TEST_EXTRA_ARG(i, 'p');
641                 if ( ! picture_coding.DecodeHex(argv[i]) )
642                   {
643                     fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
644                     return;
645                   }
646                 break;
647
648               case 'q':
649                 TEST_EXTRA_ARG(i, 'q');
650                 if ( ! coding_equations.DecodeHex(argv[i]) )
651                   {
652                     fprintf(stderr, "Error decoding CodingEquations UL value: %s\n", argv[i]);
653                     return;
654                   }
655                 break;
656
657               case 'r':
658                 TEST_EXTRA_ARG(i, 'r');
659                 if ( ! DecodeRational(argv[i], edit_rate) )
660                   {
661                     fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
662                     return;
663                   }
664                 
665                 break;
666
667               case 'R':
668                 use_cdci_descriptor = false;
669                 break;
670
671               case 's':
672                 TEST_EXTRA_ARG(i, 's');
673                 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
674                 break;
675
676               case 't':
677                 TEST_EXTRA_ARG(i, 't');
678                 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
679                 break;
680
681               case 'T':
682                 TEST_EXTRA_ARG(i, 'T');
683                 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
684                 break;
685
686               case 'u': show_ul_values_flag = true; break;
687
688               case 'U':
689                 TEST_EXTRA_ARG(i, 'U');
690                 isxd_document_namespace = argv[i];
691                 break;
692
693               case 'V': version_flag = true; break;
694               case 'v': verbose_flag = true; break;
695               case 'W': no_write_flag = true; break;
696
697               case 'x':
698                 TEST_EXTRA_ARG(i, 'x');
699                 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
700                 break;
701
702               case 'X':
703                 TEST_EXTRA_ARG(i, 'X');
704                 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
705                 break;
706
707               case 'Y':
708                 use_cdci_descriptor = true;
709                 // default 10 bit video range YUV, ref levels already set
710                 break;
711
712               case 'y':
713                 // Use values provided as argument, sharp tool, be careful
714                 use_cdci_descriptor = true;
715                 TEST_EXTRA_ARG(i, 'y');
716                 if ( ! set_video_ref(argv[i]) )
717                   {
718                     return;
719                   }
720                 break;
721
722               case 'Z': j2c_pedantic = false; break;
723               case 'z': j2c_pedantic = true; break;
724
725               default:
726                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
727                 return;
728               }
729           }
730         else if ( argv[i][0] == '-' && argv[i][1] == '-' && isalpha(argv[i][2]) )
731           {
732             if ( strcmp(argv[i]+2, "mca-audio-content-kind") == 0 )
733               {
734                 if ( ++i >= argc || argv[(i)][0] == '-' )
735                   {
736                     fprintf(stderr, "Argument not found for option -mca-audio-content-kind.\n");
737                     return;
738                   }
739                 
740                 mca_audio_content_kind = argv[i];
741               }
742             else if ( strcmp(argv[i]+2, "mca-audio-element-kind") == 0 )
743               {
744                 if ( ++i >= argc || argv[(i)][0] == '-' )
745                   {
746                     fprintf(stderr, "Argument not found for option -mca-audio-element-kind.\n");
747                     return;
748                   }
749
750                 mca_audio_element_kind = argv[i];
751               }
752             else
753               {
754                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
755                 return;
756               }
757           }
758         else
759           {
760             if ( argv[i][0] != '-' )
761               {
762                 filenames.push_back(argv[i]);
763               }
764             else
765               {
766                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
767                 return;
768               }
769           }
770       }
771
772     if ( ! mca_config_str.empty() )
773       {
774         if ( language.empty() )
775           {
776             if ( ! mca_config.DecodeString(mca_config_str) )
777               {
778                 return;
779               }
780           }
781         else
782           {
783             if ( ! mca_config.DecodeString(mca_config_str, language) )
784               {
785                 return;
786               }
787           }
788       }
789
790     if ( help_flag || version_flag || show_ul_values_flag )
791       {
792         return;
793       }
794
795     if ( filenames.size() < 2 )
796       {
797         fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
798         return;
799       }
800
801     out_file = filenames.back();
802     filenames.pop_back();
803
804     if ( ! picture_coding.HasValue() )
805       {
806         picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
807       }
808
809     error_flag = false;
810   }
811 };
812
813
814 //------------------------------------------------------------------------------------------
815 // JPEG 2000 essence
816
817 namespace ASDCP {
818   Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
819                             const ASDCP::Dictionary& dict,
820                             ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
821                             ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
822
823   Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
824 }
825
826 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
827 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
828 //
829 Result_t
830 write_JP2K_file(CommandOptions& Options)
831 {
832   AESEncContext*          Context = 0;
833   HMACContext*            HMAC = 0;
834   AS_02::JP2K::MXFWriter  Writer;
835   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
836   JP2K::SequenceParser    Parser;
837   byte_t                  IV_buf[CBC_BLOCK_SIZE];
838   Kumu::FortunaRNG        RNG;
839   ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
840   ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
841
842   // set up essence parser
843   Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
844
845   // set up MXF writer
846   if ( ASDCP_SUCCESS(result) )
847     {
848       ASDCP::JP2K::PictureDescriptor PDesc;
849       Parser.FillPictureDescriptor(PDesc);
850       PDesc.EditRate = Options.edit_rate;
851
852       if ( Options.verbose_flag )
853         {
854           fprintf(stderr, "JPEG 2000 pictures\n");
855           fputs("PictureDescriptor:\n", stderr);
856           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
857           JP2K::PictureDescriptorDump(PDesc);
858         }
859
860       if ( Options.use_cdci_descriptor )
861         {
862           ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
863           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
864           
865           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
866                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
867                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
868
869           if ( ASDCP_SUCCESS(result) )
870             {
871               tmp_dscr->CodingEquations = Options.coding_equations;
872               tmp_dscr->TransferCharacteristic = Options.transfer_characteristic;
873               tmp_dscr->ColorPrimaries = Options.color_primaries;
874               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
875               tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
876               tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
877               tmp_dscr->ComponentDepth = Options.component_depth;
878               tmp_dscr->FrameLayout = Options.frame_layout;
879               tmp_dscr->AspectRatio = Options.aspect_ratio;
880               tmp_dscr->FieldDominance = Options.field_dominance;
881               tmp_dscr->WhiteReflevel = Options.cdci_WhiteRefLevel;
882               tmp_dscr->BlackRefLevel = Options.cdci_BlackRefLevel;
883               tmp_dscr->ColorRange = Options.cdci_ColorRange;
884               if (Options.line_map_flag)  tmp_dscr->VideoLineMap = Options.line_map;
885
886               if ( Options.md_min_luminance || Options.md_max_luminance )
887                 {
888                   tmp_dscr->MasteringDisplayMinimumLuminance = Options.md_min_luminance;
889                   tmp_dscr->MasteringDisplayMaximumLuminance = Options.md_max_luminance;
890                 }
891
892               if ( Options.md_primaries.HasValue() )
893                 {
894                   tmp_dscr->MasteringDisplayPrimaries = Options.md_primaries;
895                   tmp_dscr->MasteringDisplayWhitePointChromaticity = Options.md_white_point;
896                 }
897
898               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
899             }
900         }
901       else
902         { // use RGB
903           ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
904           essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
905           
906           result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
907                                            *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
908                                            *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
909
910           if ( ASDCP_SUCCESS(result) )
911             {
912               tmp_dscr->CodingEquations = Options.coding_equations;
913               tmp_dscr->TransferCharacteristic = Options.transfer_characteristic;
914               tmp_dscr->ColorPrimaries = Options.color_primaries;
915               tmp_dscr->ScanningDirection = 0;
916               tmp_dscr->PictureEssenceCoding = Options.picture_coding;
917               tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
918               tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
919               if (Options.line_map_flag)  tmp_dscr->VideoLineMap = Options.line_map;
920
921               if ( Options.md_min_luminance || Options.md_max_luminance )
922                 {
923                   tmp_dscr->MasteringDisplayMinimumLuminance = Options.md_min_luminance;
924                   tmp_dscr->MasteringDisplayMaximumLuminance = Options.md_max_luminance;
925                 }
926
927               if ( Options.md_primaries.HasValue() )
928                 {
929                   tmp_dscr->MasteringDisplayPrimaries = Options.md_primaries;
930                   tmp_dscr->MasteringDisplayWhitePointChromaticity = Options.md_white_point;
931                 }
932
933               essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
934             }
935         }
936     }
937
938   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
939     {
940       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
941       Info.LabelSetType = LS_MXF_SMPTE;
942
943       if ( Options.asset_id_flag )
944         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
945       else
946         Kumu::GenRandomUUID(Info.AssetUUID);
947
948       // configure encryption
949       if( Options.key_flag )
950         {
951           Kumu::GenRandomUUID(Info.ContextID);
952           Info.EncryptedEssence = true;
953
954           if ( Options.key_id_flag )
955             {
956               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
957             }
958           else
959             {
960               create_random_uuid(Info.CryptographicKeyID);
961             }
962
963           Context = new AESEncContext;
964           result = Context->InitKey(Options.key_value);
965
966           if ( ASDCP_SUCCESS(result) )
967             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
968
969           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
970             {
971               Info.UsesHMAC = true;
972               HMAC = new HMACContext;
973               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
974             }
975         }
976
977       if ( ASDCP_SUCCESS(result) )
978         {
979           result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
980                                     Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
981         }
982     }
983
984   if ( ASDCP_SUCCESS(result) )
985     {
986       ui32_t duration = 0;
987       result = Parser.Reset();
988
989       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
990         {
991           result = Parser.ReadFrame(FrameBuffer);
992           
993           if ( ASDCP_SUCCESS(result) )
994             {
995               if ( Options.verbose_flag )
996                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
997               
998               if ( Options.encrypt_header_flag )
999                 FrameBuffer.PlaintextOffset(0);
1000             }
1001
1002           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1003             {
1004               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1005
1006               // The Writer class will forward the last block of ciphertext
1007               // to the encryption context for use as the IV for the next
1008               // frame. If you want to use non-sequitur IV values, un-comment
1009               // the following  line of code.
1010               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1011               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1012             }
1013         }
1014
1015       if ( result == RESULT_ENDOFFILE )
1016         result = RESULT_OK;
1017     }
1018
1019   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1020     result = Writer.Finalize();
1021
1022   return result;
1023 }
1024
1025 //------------------------------------------------------------------------------------------
1026 // PCM essence
1027 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
1028 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
1029 //
1030 Result_t
1031 write_PCM_file(CommandOptions& Options)
1032 {
1033   AESEncContext*    Context = 0;
1034   HMACContext*      HMAC = 0;
1035   PCMParserList     Parser;
1036   AS_02::PCM::MXFWriter    Writer;
1037   PCM::FrameBuffer  FrameBuffer;
1038   byte_t            IV_buf[CBC_BLOCK_SIZE];
1039   Kumu::FortunaRNG  RNG;
1040   ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
1041
1042   // set up essence parser
1043   Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
1044
1045   // set up MXF writer
1046   if ( ASDCP_SUCCESS(result) )
1047     {
1048       ASDCP::PCM::AudioDescriptor ADesc;
1049       Parser.FillAudioDescriptor(ADesc);
1050
1051       ADesc.EditRate = Options.edit_rate;
1052       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1053
1054       if ( Options.verbose_flag )
1055         {
1056           char buf[64];
1057           fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1058                   ADesc.AudioSamplingRate.Quotient() / 1000.0,
1059                   RationalToString(Options.edit_rate, buf, 64),
1060                   PCM::CalcSamplesPerFrame(ADesc));
1061           fputs("AudioDescriptor:\n", stderr);
1062           PCM::AudioDescriptorDump(ADesc);
1063         }
1064
1065       essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
1066
1067       result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
1068
1069       if ( Options.mca_config.empty() )
1070         {
1071           essence_descriptor->ChannelAssignment = Options.channel_assignment;
1072         }
1073       else
1074         {
1075           if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
1076             {
1077               fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
1078                       Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
1079               return RESULT_FAIL;
1080             }
1081
1082           // This marks all soundfield groups using the same MCA property values
1083           MXF::InterchangeObject_list_t::iterator i;
1084           for ( i = Options.mca_config.begin(); i != Options.mca_config.end(); ++i )
1085             {
1086               MXF::SoundfieldGroupLabelSubDescriptor * desc = dynamic_cast<MXF::SoundfieldGroupLabelSubDescriptor*>(*i);
1087               if ( desc != 0 )
1088                 {
1089                   if ( ! Options.mca_audio_content_kind.empty() )
1090                     {
1091                       desc->MCAAudioContentKind = Options.mca_audio_content_kind;
1092                     }
1093                   if ( ! Options.mca_audio_element_kind.empty() )
1094                     {
1095                       desc->MCAAudioElementKind = Options.mca_audio_element_kind;
1096                     }
1097                 }
1098             }
1099
1100           essence_descriptor->ChannelAssignment = g_dict->ul(MDD_IMFAudioChannelCfg_MCA);
1101         }
1102     }
1103
1104   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1105     {
1106       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1107       Info.LabelSetType = LS_MXF_SMPTE;
1108
1109       if ( Options.asset_id_flag )
1110         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1111       else
1112         Kumu::GenRandomUUID(Info.AssetUUID);
1113
1114       // configure encryption
1115       if( Options.key_flag )
1116         {
1117           Kumu::GenRandomUUID(Info.ContextID);
1118           Info.EncryptedEssence = true;
1119
1120           if ( Options.key_id_flag )
1121             {
1122               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1123             }
1124           else
1125             {
1126               create_random_uuid(Info.CryptographicKeyID);
1127             }
1128
1129           Context = new AESEncContext;
1130           result = Context->InitKey(Options.key_value);
1131
1132           if ( ASDCP_SUCCESS(result) )
1133             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1134
1135           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1136             {
1137               Info.UsesHMAC = true;
1138               HMAC = new HMACContext;
1139               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1140             }
1141         }
1142
1143       if ( ASDCP_SUCCESS(result) )
1144         {
1145           result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
1146                                     Options.mca_config, Options.edit_rate);
1147         }
1148     }
1149
1150   if ( ASDCP_SUCCESS(result) )
1151     {
1152       result = Parser.Reset();
1153       ui32_t duration = 0;
1154
1155       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1156         {
1157           result = Parser.ReadFrame(FrameBuffer);
1158
1159           if ( ASDCP_SUCCESS(result) )
1160             {
1161               if ( Options.verbose_flag )
1162                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1163
1164               if ( ! Options.no_write_flag )
1165                 {
1166                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1167
1168                   // The Writer class will forward the last block of ciphertext
1169                   // to the encryption context for use as the IV for the next
1170                   // frame. If you want to use non-sequitur IV values, un-comment
1171                   // the following  line of code.
1172                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1173                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1174                 }
1175             }
1176         }
1177
1178       if ( result == RESULT_ENDOFFILE )
1179         result = RESULT_OK;
1180     }
1181
1182   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1183     result = Writer.Finalize();
1184
1185   return result;
1186 }
1187
1188
1189
1190
1191 //------------------------------------------------------------------------------------------
1192 // TimedText essence
1193
1194
1195 // Write one or more plaintext timed text streams to a plaintext AS-02 file
1196 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
1197 //
1198 Result_t
1199 write_timed_text_file(CommandOptions& Options)
1200 {
1201   AESEncContext*    Context = 0;
1202   HMACContext*      HMAC = 0;
1203   AS_02::TimedText::ST2052_TextParser  Parser;
1204   AS_02::TimedText::MXFWriter    Writer;
1205   TimedText::FrameBuffer  FrameBuffer;
1206   TimedText::TimedTextDescriptor TDesc;
1207   byte_t            IV_buf[CBC_BLOCK_SIZE];
1208   Kumu::FortunaRNG  RNG;
1209
1210   // set up essence parser
1211   Result_t result = Parser.OpenRead(Options.filenames.front());
1212
1213   // set up MXF writer
1214   if ( ASDCP_SUCCESS(result) )
1215     {
1216       Parser.FillTimedTextDescriptor(TDesc);
1217       TDesc.EditRate = Options.edit_rate;
1218       TDesc.ContainerDuration = Options.duration;
1219       FrameBuffer.Capacity(Options.fb_size);
1220
1221       if ( ! Options.profile_name.empty() )
1222         {
1223           TDesc.NamespaceName = Options.profile_name;
1224         }
1225
1226       if ( Options.verbose_flag )
1227         {
1228           fputs("IMF Timed-Text Descriptor:\n", stderr);
1229           TimedText::DescriptorDump(TDesc);
1230         }
1231     }
1232
1233   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1234     {
1235       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1236       Info.LabelSetType = LS_MXF_SMPTE;
1237
1238       if ( Options.asset_id_flag )
1239         memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1240       else
1241         Kumu::GenRandomUUID(Info.AssetUUID);
1242
1243       // configure encryption
1244       if( Options.key_flag )
1245         {
1246           Kumu::GenRandomUUID(Info.ContextID);
1247           Info.EncryptedEssence = true;
1248
1249           if ( Options.key_id_flag )
1250             {
1251               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1252             }
1253           else
1254             {
1255               create_random_uuid(Info.CryptographicKeyID);
1256             }
1257
1258           Context = new AESEncContext;
1259           result = Context->InitKey(Options.key_value);
1260
1261           if ( ASDCP_SUCCESS(result) )
1262             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1263
1264           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1265             {
1266               Info.UsesHMAC = true;
1267               HMAC = new HMACContext;
1268               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1269             }
1270         }
1271
1272       if ( ASDCP_SUCCESS(result) )
1273         result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
1274     }
1275
1276   if ( ASDCP_FAILURE(result) )
1277     return result;
1278
1279   std::string XMLDoc;
1280   TimedText::ResourceList_t::const_iterator ri;
1281
1282   result = Parser.ReadTimedTextResource(XMLDoc);
1283
1284   if ( ASDCP_SUCCESS(result) )
1285     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1286
1287   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1288     {
1289       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1290
1291       if ( ASDCP_SUCCESS(result) )
1292         {
1293           if ( Options.verbose_flag )
1294             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1295
1296           if ( ! Options.no_write_flag )
1297             {
1298               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1299
1300               // The Writer class will forward the last block of ciphertext
1301               // to the encryption context for use as the IV for the next
1302               // frame. If you want to use non-sequitur IV values, un-comment
1303               // the following  line of code.
1304               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1305               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1306             }
1307         }
1308
1309       if ( result == RESULT_ENDOFFILE )
1310         result = RESULT_OK;
1311     }
1312
1313   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1314     result = Writer.Finalize();
1315
1316   return result;
1317 }
1318
1319 //
1320 bool
1321 get_current_dms_text_descriptor(AS_02::ISXD::MXFWriter& writer, ASDCP::MXF::GenericStreamTextBasedSet *&text_object)
1322 {
1323   std::list<MXF::InterchangeObject*> object_list;
1324   writer.OP1aHeader().GetMDObjectsByType(DefaultSMPTEDict().ul(MDD_GenericStreamTextBasedSet), object_list);
1325
1326   if ( object_list.empty() )
1327     {
1328       return false;
1329     }
1330
1331   text_object = dynamic_cast<MXF::GenericStreamTextBasedSet*>(object_list.back());
1332   assert(text_object != 0);
1333   return true;
1334 }
1335                       
1336
1337 // Write one or more plaintext Aux Data bytestreams to a plaintext AS-02 file
1338 // Write one or more plaintext Aux Data bytestreams to a ciphertext AS-02 file
1339 //
1340 Result_t
1341 write_isxd_file(CommandOptions& Options)
1342 {
1343   AESEncContext*          Context = 0;
1344   HMACContext*            HMAC = 0;
1345   AS_02::ISXD::MXFWriter Writer;
1346   DCData::FrameBuffer     FrameBuffer(Options.fb_size);
1347   DCData::SequenceParser  Parser;
1348   byte_t                  IV_buf[CBC_BLOCK_SIZE];
1349   Kumu::FortunaRNG        RNG;
1350
1351   // set up essence parser
1352   Result_t result = Parser.OpenRead(Options.filenames.front());
1353
1354   // set up MXF writer
1355   if ( ASDCP_SUCCESS(result) )
1356     {
1357
1358       if ( Options.verbose_flag )
1359         {
1360           fprintf(stderr, "ISXD Data\n");
1361           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1362         }
1363     }
1364
1365   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1366   {
1367     WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1368     if ( Options.asset_id_flag )
1369       memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1370     else
1371       Kumu::GenRandomUUID(Info.AssetUUID);
1372
1373     Info.LabelSetType = LS_MXF_SMPTE;
1374
1375       // configure encryption
1376     if( Options.key_flag )
1377         {
1378           Kumu::GenRandomUUID(Info.ContextID);
1379           Info.EncryptedEssence = true;
1380
1381           if ( Options.key_id_flag )
1382             {
1383               memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1384             }
1385           else
1386             {
1387               create_random_uuid(Info.CryptographicKeyID);
1388             }
1389
1390           Context = new AESEncContext;
1391           result = Context->InitKey(Options.key_value);
1392
1393           if ( ASDCP_SUCCESS(result) )
1394             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1395
1396           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1397       {
1398         Info.UsesHMAC = true;
1399         HMAC = new HMACContext;
1400         result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1401       }
1402         }
1403
1404     if ( ASDCP_SUCCESS(result) )
1405       {
1406         if ( Options.isxd_document_namespace == "auto" )
1407           {
1408             // get ns of first item
1409             std::string ns_prefix, type_name, namespace_name;
1410             result = Parser.ReadFrame(FrameBuffer);
1411
1412             if ( ASDCP_SUCCESS(result) )
1413               {
1414                 Kumu::AttributeList doc_attr_list;
1415                 result = GetXMLDocType(FrameBuffer.RoData(), FrameBuffer.Size(), ns_prefix, type_name,
1416                                        namespace_name, doc_attr_list) ? RESULT_OK : RESULT_FAIL;
1417               }
1418
1419             if ( ASDCP_SUCCESS(result) && ! namespace_name.empty() )
1420               {
1421                 Options.isxd_document_namespace = namespace_name;
1422               }
1423             else
1424               {
1425                 fprintf(stderr, "Unable to parse an XML namespace name from the input document.\n");
1426                 return RESULT_FAIL;
1427               }
1428           }
1429
1430         result = Writer.OpenWrite(Options.out_file, Info, Options.isxd_document_namespace, Options.edit_rate);
1431       }
1432   }
1433
1434   if ( ASDCP_SUCCESS(result) )
1435     {
1436       ui32_t duration = 0;
1437       result = Parser.Reset();
1438
1439       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1440         {
1441           result = Parser.ReadFrame(FrameBuffer);
1442
1443           if ( ASDCP_SUCCESS(result) )
1444             {
1445               if ( Options.verbose_flag )
1446                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1447
1448               if ( Options.encrypt_header_flag )
1449                 FrameBuffer.PlaintextOffset(0);
1450             }
1451
1452           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1453             {
1454               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1455
1456               // The Writer class will forward the last block of ciphertext
1457               // to the encryption context for use as the IV for the next
1458               // frame. If you want to use non-sequitur IV values, un-comment
1459               // the following  line of code.
1460               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1461               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1462             }
1463         }
1464
1465       if ( result == RESULT_ENDOFFILE )
1466         {
1467           result = RESULT_OK;
1468         }
1469     }
1470   
1471   if ( KM_SUCCESS(result) && ! Options.no_write_flag )
1472     {
1473       ASDCP::FrameBuffer global_metadata;
1474       std::list<std::string>::iterator i;
1475       
1476       for ( i = Options.global_isxd_metadata.begin(); i != Options.global_isxd_metadata.end(); ++i )
1477         {
1478           ui32_t file_size = Kumu::FileSize(*i);
1479           result = global_metadata.Capacity(file_size);
1480
1481           if ( KM_SUCCESS(result) )
1482             {
1483               ui32_t read_count = 0;
1484               Kumu::FileReader Reader;
1485               std::string namespace_name;
1486
1487               result = Reader.OpenRead(*i);
1488
1489               if ( KM_SUCCESS(result) )
1490                 {
1491                   result = Reader.Read(global_metadata.Data(), file_size, &read_count);
1492                 }
1493
1494               if ( KM_SUCCESS(result) )
1495                 {
1496                   if ( file_size != read_count) 
1497                     return RESULT_READFAIL;
1498
1499                   global_metadata.Size(read_count);
1500
1501                   std::string ns_prefix, type_name;
1502                   Kumu::AttributeList doc_attr_list;
1503                   result = GetXMLDocType(global_metadata.RoData(), global_metadata.Size(), ns_prefix, type_name,
1504                                          namespace_name, doc_attr_list) ? RESULT_OK : RESULT_FAIL;
1505                 }
1506
1507               if ( KM_SUCCESS(result) )
1508                 {
1509                   result = Writer.AddDmsGenericPartUtf8Text(global_metadata, Context, HMAC);
1510                 }
1511
1512               if ( KM_SUCCESS(result) )
1513                 {
1514                   ASDCP::MXF::GenericStreamTextBasedSet *text_object = 0;
1515                   get_current_dms_text_descriptor(Writer, text_object);
1516                   assert(text_object);
1517                   text_object->TextMIMEMediaType = "text/xml";
1518                   text_object->TextDataDescription = namespace_name;
1519
1520                   // this is not really useful when inserting multiple objects because
1521                   // it cannot be set per object without some other CLI syntax for
1522                   // associating language codes with 2057 blobs, e.g., <filename>:<lang>
1523                   text_object->RFC5646TextLanguageCode = Options.language;
1524                 }
1525             }
1526         }
1527
1528       if ( KM_SUCCESS(result) )
1529         {
1530           result = Writer.Finalize();
1531         }
1532     }
1533
1534   return result;
1535 }
1536
1537 //
1538 int
1539 main(int argc, const char** argv)
1540 {
1541   Result_t result = RESULT_OK;
1542   char     str_buf[64];
1543   g_dict = &ASDCP::DefaultSMPTEDict();
1544   assert(g_dict);
1545
1546   CommandOptions Options(argc, argv);
1547
1548   if ( Options.version_flag )
1549     banner();
1550
1551   if ( Options.help_flag )
1552     usage();
1553
1554   if ( Options.show_ul_values_flag )
1555     {
1556       g_dict->Dump(stdout);
1557     }
1558
1559   if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
1560     return 0;
1561
1562   if ( Options.error_flag )
1563     {
1564       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1565       return 3;
1566     }
1567
1568   EssenceType_t EssenceType;
1569   result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
1570
1571   if ( ASDCP_SUCCESS(result) )
1572     {
1573       switch ( EssenceType )
1574         {
1575         case ESS_JPEG_2000:
1576           result = write_JP2K_file(Options);
1577           break;
1578
1579         case ESS_PCM_24b_48k:
1580         case ESS_PCM_24b_96k:
1581           result = write_PCM_file(Options);
1582           break;
1583
1584         case ESS_TIMED_TEXT:
1585           result = write_timed_text_file(Options);
1586           break;
1587
1588         case ESS_DCDATA_UNKNOWN:
1589           if ( ! Options.isxd_document_namespace.empty() )
1590             {
1591               result = write_isxd_file(Options);      
1592             }
1593           else
1594             {
1595               fprintf(stderr, "%s: Unknown synchronous data file type, not AS-02-compatible essence.\n",
1596                       Options.filenames.front().c_str());
1597               return 5;
1598             }
1599           break;
1600
1601         default:
1602           fprintf(stderr, "%s: Unknown file type, not AS-02-compatible essence.\n",
1603                   Options.filenames.front().c_str());
1604           return 5;
1605         }
1606     }
1607
1608   if ( ASDCP_FAILURE(result) )
1609     {
1610       fputs("Program stopped on error.\n", stderr);
1611
1612       if ( result != RESULT_FAIL )
1613         {
1614           fputs(result, stderr);
1615           fputc('\n', stderr);
1616         }
1617
1618       return 1;
1619     }
1620
1621   return 0;
1622 }
1623
1624
1625 //
1626 // end as-02-wrap.cpp
1627 //