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