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