2 Copyright (c) 2011-2016, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
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.
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.
29 /*! \file as-02-wrap.cpp
31 \brief AS-02 file manipulation utility
33 This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
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
39 #include <KM_fileio.h>
42 #include <PCMParserList.h>
45 using namespace ASDCP;
47 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
48 const ASDCP::Dictionary *g_dict = 0;
51 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
53 snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
58 //------------------------------------------------------------------------------------------
60 // command line option parser class
62 static const char* PROGRAM_NAME = "as-02-wrap"; // program name for messages
64 // local program identification info written to file headers
65 class MyInfo : public WriterInfo
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 };
74 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
75 CompanyName = "WidgetCo";
76 ProductName = "as-02-wrap";
77 ProductVersion = ASDCP::Version();
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)); \
95 create_random_uuid(byte_t* uuidbuf)
98 GenRandomValue(tmp_id);
99 memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size());
104 banner(FILE* stream = stdout)
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);
117 usage(FILE* stream = stdout)
120 USAGE: %s [-h|-help] [-V]\n\
122 %s [-a <uuid>] [-A <w>/<h>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
123 [-D <depth>] [-e|-E] [-i] [-j <key-id-string>] [-k <key-string>]\n\
124 [-M] [-m <expr>] [-p <ul>] [-r <n>/<d>] [-R] [-s <seconds>]\n\
125 [-t <min>] [-T <max>] [-u] [-v] [-W] [-x <int>] [-X <int>] [-Y]\n\
126 [-y <white-ref>[,<black-ref>[,<color-range>]]]\n\
127 [-z|-Z] <input-file>+ <output-file>\n\n",
128 PROGRAM_NAME, PROGRAM_NAME);
132 -h | -help - Show help\n\
133 -V - Show version information\n\
134 -a <uuid> - Specify the Asset ID of the file\n\
135 -A <w>/<h> - Set aspect ratio for image (default 4/3)\n\
136 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
137 Defaults to 4,194,304 (4MB)\n\
138 -C <ul> - Set ChannelAssignment UL value\n\
139 -d <duration> - Number of frames to process, default all\n\
140 -D <depth> - Component depth for YCbCr images (default: 10)\n\
141 -e - Encrypt JP2K headers (default)\n\
142 -E - Do not encrypt JP2K headers\n\
143 -F (0|1) - Set field dominance for interlaced image (default: 0)\n\
144 -i - Indicates input essence is interlaced fields (forces -Y)\n\
145 -j <key-id-str> - Write key ID instead of creating a random value\n\
146 -k <key-string> - Use key for ciphertext operations\n\
147 -l <first>,<second>\n\
148 - Integer values that set the VideoLineMap when creating\n\
149 interlaced YCbCr files\n\
150 -M - Do not create HMAC values when writing\n\
151 -m <expr> - Write MCA labels using <expr>. Example:\n\
152 51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
153 -P <string> - Set NamespaceURI property when creating timed text MXF\n\
154 -p <ul> - Set broadcast profile\n\
155 -r <n>/<d> - Edit Rate of the output file. 24/1 is the default\n\
156 -R - Indicates RGB image essence (default)\n\
157 -s <seconds> - Duration of a frame-wrapped partition (default 60)\n\
158 -t <min> - Set RGB component minimum code value (default: 0)\n\
159 -T <max> - Set RGB component maximum code value (default: 1023)\n\
160 -u - Print UL catalog to stderr\n\
161 -v - Verbose, prints informative messages to stderr\n\
162 -W - Read input file only, do not write source file\n\
163 -x <int> - Horizontal subsampling degree (default: 2)\n\
164 -X <int> - Vertical subsampling degree (default: 2)\n\
165 -Y - Indicates YCbCr image essence (default: RGB), uses\n\
166 default values for White Ref, Black Ref and Color Range,\n\
167 940,64,897, indicating 10 bit standard Video Range\n\
168 -y <white-ref>[,<black-ref>[,<color-range>]]\n\
169 - Same as -Y but White Ref, Black Ref and Color Range are\n\
170 set from the given argument\n\
171 -z - Fail if j2c inputs have unequal parameters (default)\n\
172 -Z - Ignore unequal parameters in j2c inputs\n\
174 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
175 o All option arguments must be separated from the option by whitespace.\n\n");
186 bool error_flag; // true if the given options are in error or not complete
187 bool key_flag; // true if an encryption key was given
188 bool asset_id_flag; // true if an asset ID was given
189 bool encrypt_header_flag; // true if j2c headers are to be encrypted
190 bool write_hmac; // true if HMAC values are to be generated and written
191 bool verbose_flag; // true if the verbose option was selected
192 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
193 bool no_write_flag; // true if no output files are to be written
194 bool version_flag; // true if the version display option was selected
195 bool help_flag; // true if the help display option was selected
196 ui32_t duration; // number of frames to be processed
197 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
198 bool use_cdci_descriptor; //
199 Rational edit_rate; // edit rate of JP2K sequence
200 ui32_t fb_size; // size of picture frame buffer
201 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
202 bool key_id_flag; // true if a key ID was given
203 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
204 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
205 bool show_ul_values_flag; /// if true, dump the UL table before going tp work.
206 Kumu::PathList_t filenames; // list of filenames to be processed
208 UL channel_assignment;
209 ASDCP::MXF::AS02_MCAConfigParser mca_config;
215 ui32_t horizontal_subsampling;
216 ui32_t vertical_subsampling;
217 ui32_t component_depth;
219 ASDCP::Rational aspect_ratio;
220 ui8_t field_dominance;
221 ui32_t mxf_header_size;
222 ui32_t cdci_BlackRefLevel;
223 ui32_t cdci_WhiteRefLevel;
224 ui32_t cdci_ColorRange;
226 //new attributes for AS-02 support
227 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
228 ui32_t partition_space; //Shim parameter partition_spacing
231 MXF::LineMapPair line_map;
232 std::string out_file, profile_name; //
235 bool set_video_line_map(const std::string& arg)
237 const char* sep_str = strrchr(arg.c_str(), ',');
241 fprintf(stderr, "Expecting <first>,<second>\n");
245 line_map.First = Kumu::xabs(strtol(arg.c_str(), 0, 10));
246 line_map.Second = Kumu::xabs(strtol(sep_str+1, 0, 10));
251 bool set_video_ref(const std::string& arg)
253 std::list<std::string> ref_tokens = Kumu::km_token_split(arg, ",");
255 switch ( ref_tokens.size() )
258 cdci_ColorRange = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
259 ref_tokens.pop_back();
261 cdci_BlackRefLevel = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
262 ref_tokens.pop_back();
264 cdci_WhiteRefLevel = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
268 fprintf(stderr, "Expecting <white-ref>[,<black-ref>[,<color-range>]]\n");
272 if ( cdci_WhiteRefLevel > 65535 || cdci_BlackRefLevel > 65535 || cdci_ColorRange > 65535 )
274 fprintf(stderr, "Unexpected CDCI video referece levels.\n");
282 CommandOptions(int argc, const char** argv) :
283 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
284 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
285 no_write_flag(false), version_flag(false), help_flag(false),
286 duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
287 show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
288 mca_config(g_dict), rgba_MaxRef(1023), rgba_MinRef(0),
289 horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
290 frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
291 mxf_header_size(16384), cdci_WhiteRefLevel(940), cdci_BlackRefLevel(64), cdci_ColorRange(897),
294 memset(key_value, 0, KeyLen);
295 memset(key_id_value, 0, UUIDlen);
297 for ( int i = 1; i < argc; i++ )
300 if ( (strcmp( argv[i], "-help") == 0) )
306 if ( argv[i][0] == '-'
307 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
310 switch ( argv[i][1] )
313 TEST_EXTRA_ARG(i, 'A');
314 if ( ! DecodeRational(argv[i], aspect_ratio) )
316 fprintf(stderr, "Error decoding aspect ratio value: %s\n", argv[i]);
322 asset_id_flag = true;
323 TEST_EXTRA_ARG(i, 'a');
326 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
328 if ( length != UUIDlen )
330 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
337 TEST_EXTRA_ARG(i, 'b');
338 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
341 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
345 TEST_EXTRA_ARG(i, 'C');
346 if ( ! channel_assignment.DecodeHex(argv[i]) )
348 fprintf(stderr, "Error decoding ChannelAssignment UL value: %s\n", argv[i]);
354 TEST_EXTRA_ARG(i, 'D');
355 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
359 TEST_EXTRA_ARG(i, 'd');
360 duration = Kumu::xabs(strtol(argv[i], 0, 10));
363 case 'E': encrypt_header_flag = false; break;
364 case 'e': encrypt_header_flag = true; break;
367 TEST_EXTRA_ARG(i, 'F');
368 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
369 if ( field_dominance > 1 )
371 fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
376 case 'h': help_flag = true; break;
380 use_cdci_descriptor = true;
385 TEST_EXTRA_ARG(i, 'j');
388 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
390 if ( length != UUIDlen )
392 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
398 case 'k': key_flag = true;
399 TEST_EXTRA_ARG(i, 'k');
402 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
404 if ( length != KeyLen )
406 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
413 TEST_EXTRA_ARG(i, 'y');
414 if ( ! set_video_line_map(argv[i]) )
420 case 'M': write_hmac = false; break;
423 TEST_EXTRA_ARG(i, 'm');
424 if ( ! mca_config.DecodeString(argv[i]) )
431 TEST_EXTRA_ARG(i, 'P');
432 profile_name = argv[i];
436 TEST_EXTRA_ARG(i, 'p');
437 if ( ! picture_coding.DecodeHex(argv[i]) )
439 fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
445 TEST_EXTRA_ARG(i, 'r');
446 if ( ! DecodeRational(argv[i], edit_rate) )
448 fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
455 use_cdci_descriptor = false;
459 TEST_EXTRA_ARG(i, 's');
460 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
464 TEST_EXTRA_ARG(i, 't');
465 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
469 TEST_EXTRA_ARG(i, 'T');
470 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
473 case 'u': show_ul_values_flag = true; break;
475 case 'V': version_flag = true; break;
476 case 'v': verbose_flag = true; break;
477 case 'W': no_write_flag = true; break;
480 TEST_EXTRA_ARG(i, 'x');
481 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
485 TEST_EXTRA_ARG(i, 'X');
486 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
490 use_cdci_descriptor = true;
491 // default 10 bit video range YUV, ref levels already set
495 // Use values provded as argument, sharp tool, be careful
496 use_cdci_descriptor = true;
497 TEST_EXTRA_ARG(i, 'y');
498 if ( ! set_video_ref(argv[i]) )
504 case 'Z': j2c_pedantic = false; break;
505 case 'z': j2c_pedantic = true; break;
508 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
515 if ( argv[i][0] != '-' )
517 filenames.push_back(argv[i]);
521 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
527 if ( help_flag || version_flag )
530 if ( filenames.size() < 2 )
532 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
536 out_file = filenames.back();
537 filenames.pop_back();
539 if ( ! picture_coding.HasValue() )
541 picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
549 //------------------------------------------------------------------------------------------
553 Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
554 const ASDCP::Dictionary& dict,
555 ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
556 ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
558 Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
561 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
562 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
565 write_JP2K_file(CommandOptions& Options)
567 AESEncContext* Context = 0;
568 HMACContext* HMAC = 0;
569 AS_02::JP2K::MXFWriter Writer;
570 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
571 JP2K::SequenceParser Parser;
572 byte_t IV_buf[CBC_BLOCK_SIZE];
573 Kumu::FortunaRNG RNG;
574 ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
575 ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
577 // set up essence parser
578 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
581 if ( ASDCP_SUCCESS(result) )
583 ASDCP::JP2K::PictureDescriptor PDesc;
584 Parser.FillPictureDescriptor(PDesc);
585 PDesc.EditRate = Options.edit_rate;
587 if ( Options.verbose_flag )
589 fprintf(stderr, "JPEG 2000 pictures\n");
590 fputs("PictureDescriptor:\n", stderr);
591 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
592 JP2K::PictureDescriptorDump(PDesc);
595 if ( Options.use_cdci_descriptor )
597 ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
598 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
600 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
601 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
602 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
604 if ( ASDCP_SUCCESS(result) )
606 tmp_dscr->PictureEssenceCoding = Options.picture_coding;
607 tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
608 tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
609 tmp_dscr->ComponentDepth = Options.component_depth;
610 tmp_dscr->FrameLayout = Options.frame_layout;
611 tmp_dscr->AspectRatio = Options.aspect_ratio;
612 tmp_dscr->FieldDominance = Options.field_dominance;
613 tmp_dscr->WhiteReflevel = Options.cdci_WhiteRefLevel;
614 tmp_dscr->BlackRefLevel = Options.cdci_BlackRefLevel;
615 tmp_dscr->ColorRange = Options.cdci_ColorRange;
616 tmp_dscr->VideoLineMap = Options.line_map;
617 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
622 ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
623 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
625 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
626 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
627 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
629 if ( ASDCP_SUCCESS(result) )
631 tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
632 tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
633 tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
634 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
639 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
641 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
642 Info.LabelSetType = LS_MXF_SMPTE;
644 if ( Options.asset_id_flag )
645 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
647 Kumu::GenRandomUUID(Info.AssetUUID);
649 // configure encryption
650 if( Options.key_flag )
652 Kumu::GenRandomUUID(Info.ContextID);
653 Info.EncryptedEssence = true;
655 if ( Options.key_id_flag )
657 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
661 create_random_uuid(Info.CryptographicKeyID);
664 Context = new AESEncContext;
665 result = Context->InitKey(Options.key_value);
667 if ( ASDCP_SUCCESS(result) )
668 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
670 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
672 Info.UsesHMAC = true;
673 HMAC = new HMACContext;
674 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
678 if ( ASDCP_SUCCESS(result) )
680 result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
681 Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
685 if ( ASDCP_SUCCESS(result) )
688 result = Parser.Reset();
690 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
692 result = Parser.ReadFrame(FrameBuffer);
694 if ( ASDCP_SUCCESS(result) )
696 if ( Options.verbose_flag )
697 FrameBuffer.Dump(stderr, Options.fb_dump_size);
699 if ( Options.encrypt_header_flag )
700 FrameBuffer.PlaintextOffset(0);
703 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
705 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
707 // The Writer class will forward the last block of ciphertext
708 // to the encryption context for use as the IV for the next
709 // frame. If you want to use non-sequitur IV values, un-comment
710 // the following line of code.
711 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
712 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
716 if ( result == RESULT_ENDOFFILE )
720 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
721 result = Writer.Finalize();
726 //------------------------------------------------------------------------------------------
730 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
731 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
734 write_PCM_file(CommandOptions& Options)
736 AESEncContext* Context = 0;
737 HMACContext* HMAC = 0;
738 PCMParserList Parser;
739 AS_02::PCM::MXFWriter Writer;
740 PCM::FrameBuffer FrameBuffer;
741 byte_t IV_buf[CBC_BLOCK_SIZE];
742 Kumu::FortunaRNG RNG;
743 ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
745 // set up essence parser
746 Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
749 if ( ASDCP_SUCCESS(result) )
751 ASDCP::PCM::AudioDescriptor ADesc;
752 Parser.FillAudioDescriptor(ADesc);
754 ADesc.EditRate = Options.edit_rate;
755 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
757 if ( Options.verbose_flag )
760 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
761 ADesc.AudioSamplingRate.Quotient() / 1000.0,
762 RationalToString(Options.edit_rate, buf, 64),
763 PCM::CalcSamplesPerFrame(ADesc));
764 fputs("AudioDescriptor:\n", stderr);
765 PCM::AudioDescriptorDump(ADesc);
768 essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
770 result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
772 if ( Options.mca_config.empty() )
774 essence_descriptor->ChannelAssignment = Options.channel_assignment;
778 if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
780 fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
781 Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
785 // this is the d-cinema MCA label, what is the one for IMF?
786 essence_descriptor->ChannelAssignment = g_dict->ul(MDD_IMFAudioChannelCfg_MCA);
790 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
792 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
793 Info.LabelSetType = LS_MXF_SMPTE;
795 if ( Options.asset_id_flag )
796 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
798 Kumu::GenRandomUUID(Info.AssetUUID);
800 // configure encryption
801 if( Options.key_flag )
803 Kumu::GenRandomUUID(Info.ContextID);
804 Info.EncryptedEssence = true;
806 if ( Options.key_id_flag )
808 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
812 create_random_uuid(Info.CryptographicKeyID);
815 Context = new AESEncContext;
816 result = Context->InitKey(Options.key_value);
818 if ( ASDCP_SUCCESS(result) )
819 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
821 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
823 Info.UsesHMAC = true;
824 HMAC = new HMACContext;
825 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
829 if ( ASDCP_SUCCESS(result) )
831 result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
832 Options.mca_config, Options.edit_rate);
836 if ( ASDCP_SUCCESS(result) )
838 result = Parser.Reset();
841 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
843 result = Parser.ReadFrame(FrameBuffer);
845 if ( ASDCP_SUCCESS(result) )
847 if ( Options.verbose_flag )
848 FrameBuffer.Dump(stderr, Options.fb_dump_size);
850 if ( ! Options.no_write_flag )
852 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
854 // The Writer class will forward the last block of ciphertext
855 // to the encryption context for use as the IV for the next
856 // frame. If you want to use non-sequitur IV values, un-comment
857 // the following line of code.
858 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
859 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
864 if ( result == RESULT_ENDOFFILE )
868 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
869 result = Writer.Finalize();
877 //------------------------------------------------------------------------------------------
881 // Write one or more plaintext timed text streams to a plaintext AS-02 file
882 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
885 write_timed_text_file(CommandOptions& Options)
887 AESEncContext* Context = 0;
888 HMACContext* HMAC = 0;
889 AS_02::TimedText::ST2052_TextParser Parser;
890 AS_02::TimedText::MXFWriter Writer;
891 TimedText::FrameBuffer FrameBuffer;
892 TimedText::TimedTextDescriptor TDesc;
893 byte_t IV_buf[CBC_BLOCK_SIZE];
894 Kumu::FortunaRNG RNG;
896 // set up essence parser
897 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(),
898 Options.profile_name);
901 if ( ASDCP_SUCCESS(result) )
903 Parser.FillTimedTextDescriptor(TDesc);
904 TDesc.EditRate = Options.edit_rate;
905 TDesc.ContainerDuration = Options.duration;
906 FrameBuffer.Capacity(Options.fb_size);
908 if ( Options.verbose_flag )
910 fputs("IMF Timed-Text Descriptor:\n", stderr);
911 TimedText::DescriptorDump(TDesc);
915 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
917 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
918 Info.LabelSetType = LS_MXF_SMPTE;
920 if ( Options.asset_id_flag )
921 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
923 Kumu::GenRandomUUID(Info.AssetUUID);
925 // configure encryption
926 if( Options.key_flag )
928 Kumu::GenRandomUUID(Info.ContextID);
929 Info.EncryptedEssence = true;
931 if ( Options.key_id_flag )
933 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
937 create_random_uuid(Info.CryptographicKeyID);
940 Context = new AESEncContext;
941 result = Context->InitKey(Options.key_value);
943 if ( ASDCP_SUCCESS(result) )
944 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
946 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
948 Info.UsesHMAC = true;
949 HMAC = new HMACContext;
950 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
954 if ( ASDCP_SUCCESS(result) )
955 result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
958 if ( ASDCP_FAILURE(result) )
962 TimedText::ResourceList_t::const_iterator ri;
964 result = Parser.ReadTimedTextResource(XMLDoc);
966 if ( ASDCP_SUCCESS(result) )
967 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
969 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
971 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
973 if ( ASDCP_SUCCESS(result) )
975 if ( Options.verbose_flag )
976 FrameBuffer.Dump(stderr, Options.fb_dump_size);
978 if ( ! Options.no_write_flag )
980 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
982 // The Writer class will forward the last block of ciphertext
983 // to the encryption context for use as the IV for the next
984 // frame. If you want to use non-sequitur IV values, un-comment
985 // the following line of code.
986 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
987 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
991 if ( result == RESULT_ENDOFFILE )
995 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
996 result = Writer.Finalize();
1004 main(int argc, const char** argv)
1006 Result_t result = RESULT_OK;
1008 g_dict = &ASDCP::DefaultSMPTEDict();
1011 CommandOptions Options(argc, argv);
1013 if ( Options.version_flag )
1016 if ( Options.help_flag )
1019 if ( Options.show_ul_values_flag )
1021 g_dict->Dump(stdout);
1024 if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
1027 if ( Options.error_flag )
1029 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1033 EssenceType_t EssenceType;
1034 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
1036 if ( ASDCP_SUCCESS(result) )
1038 switch ( EssenceType )
1041 result = write_JP2K_file(Options);
1044 case ESS_PCM_24b_48k:
1045 case ESS_PCM_24b_96k:
1046 result = write_PCM_file(Options);
1049 case ESS_TIMED_TEXT:
1050 result = write_timed_text_file(Options);
1054 fprintf(stderr, "%s: Unknown file type, not AS-02-compatible essence.\n",
1055 Options.filenames.front().c_str());
1060 if ( ASDCP_FAILURE(result) )
1062 fputs("Program stopped on error.\n", stderr);
1064 if ( result != RESULT_FAIL )
1066 fputs(result, stderr);
1067 fputc('\n', stderr);
1078 // end as-02-wrap.cpp