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 <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\
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");
185 bool error_flag; // true if the given options are in error or not complete
186 bool key_flag; // true if an encryption key was given
187 bool asset_id_flag; // true if an asset ID was given
188 bool encrypt_header_flag; // true if j2c headers are to be encrypted
189 bool write_hmac; // true if HMAC values are to be generated and written
190 bool verbose_flag; // true if the verbose option was selected
191 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
192 bool no_write_flag; // true if no output files are to be written
193 bool version_flag; // true if the version display option was selected
194 bool help_flag; // true if the help display option was selected
195 ui32_t duration; // number of frames to be processed
196 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
197 bool use_cdci_descriptor; //
198 Rational edit_rate; // edit rate of JP2K sequence
199 ui32_t fb_size; // size of picture frame buffer
200 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
201 bool key_id_flag; // true if a key ID was given
202 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
203 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
204 bool show_ul_values_flag; /// if true, dump the UL table before going tp work.
205 Kumu::PathList_t filenames; // list of filenames to be processed
207 UL channel_assignment;
208 ASDCP::MXF::AS02_MCAConfigParser mca_config;
214 ui32_t horizontal_subsampling;
215 ui32_t vertical_subsampling;
216 ui32_t component_depth;
218 ASDCP::Rational aspect_ratio;
219 ui8_t field_dominance;
220 ui32_t mxf_header_size;
221 ui32_t cdci_BlackRefLevel;
222 ui32_t cdci_WhiteRefLevel;
223 ui32_t cdci_ColorRange;
225 //new attributes for AS-02 support
226 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
227 ui32_t partition_space; //Shim parameter partition_spacing
230 MXF::LineMapPair line_map;
231 std::string out_file; //
234 bool set_video_line_map(const std::string& arg)
236 const char* sep_str = strrchr(arg.c_str(), ',');
240 fprintf(stderr, "Expecting <first>,<second>\n");
244 line_map.First = Kumu::xabs(strtol(arg.c_str(), 0, 10));
245 line_map.Second = Kumu::xabs(strtol(sep_str+1, 0, 10));
250 bool set_video_ref(const std::string& arg)
252 std::list<std::string> ref_tokens = Kumu::km_token_split(arg, ",");
254 switch ( ref_tokens.size() )
257 cdci_ColorRange = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
258 ref_tokens.pop_back();
260 cdci_BlackRefLevel = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
261 ref_tokens.pop_back();
263 cdci_WhiteRefLevel = Kumu::xabs(strtol(ref_tokens.back().c_str(), 0, 10));
267 fprintf(stderr, "Expecting <white-ref>[,<black-ref>[,<color-range>]]\n");
271 if ( cdci_WhiteRefLevel > 65535 || cdci_BlackRefLevel > 65535 || cdci_ColorRange > 65535 )
273 fprintf(stderr, "Unexpected CDCI video referece levels.\n");
281 CommandOptions(int argc, const char** argv) :
282 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
283 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
284 no_write_flag(false), version_flag(false), help_flag(false),
285 duration(0xffffffff), j2c_pedantic(true), use_cdci_descriptor(false), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
286 show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
287 mca_config(g_dict), rgba_MaxRef(1023), rgba_MinRef(0),
288 horizontal_subsampling(2), vertical_subsampling(2), component_depth(10),
289 frame_layout(0), aspect_ratio(ASDCP::Rational(4,3)), field_dominance(0),
290 mxf_header_size(16384), cdci_WhiteRefLevel(940), cdci_BlackRefLevel(64), cdci_ColorRange(897),
293 memset(key_value, 0, KeyLen);
294 memset(key_id_value, 0, UUIDlen);
296 for ( int i = 1; i < argc; i++ )
299 if ( (strcmp( argv[i], "-help") == 0) )
305 if ( argv[i][0] == '-'
306 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
309 switch ( argv[i][1] )
312 TEST_EXTRA_ARG(i, 'A');
313 if ( ! DecodeRational(argv[i], aspect_ratio) )
315 fprintf(stderr, "Error decoding aspect ratio value: %s\n", argv[i]);
321 asset_id_flag = true;
322 TEST_EXTRA_ARG(i, 'a');
325 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
327 if ( length != UUIDlen )
329 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
336 TEST_EXTRA_ARG(i, 'b');
337 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
340 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
344 TEST_EXTRA_ARG(i, 'C');
345 if ( ! channel_assignment.DecodeHex(argv[i]) )
347 fprintf(stderr, "Error decoding ChannelAssignment UL value: %s\n", argv[i]);
353 TEST_EXTRA_ARG(i, 'D');
354 component_depth = Kumu::xabs(strtol(argv[i], 0, 10));
358 TEST_EXTRA_ARG(i, 'd');
359 duration = Kumu::xabs(strtol(argv[i], 0, 10));
362 case 'E': encrypt_header_flag = false; break;
363 case 'e': encrypt_header_flag = true; break;
366 TEST_EXTRA_ARG(i, 'F');
367 field_dominance = Kumu::xabs(strtol(argv[i], 0, 10));
368 if ( field_dominance > 1 )
370 fprintf(stderr, "Field dominance value must be \"0\" or \"1\"\n");
375 case 'h': help_flag = true; break;
379 use_cdci_descriptor = true;
384 TEST_EXTRA_ARG(i, 'j');
387 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
389 if ( length != UUIDlen )
391 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
397 case 'k': key_flag = true;
398 TEST_EXTRA_ARG(i, 'k');
401 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
403 if ( length != KeyLen )
405 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
412 TEST_EXTRA_ARG(i, 'y');
413 if ( ! set_video_line_map(argv[i]) )
419 case 'M': write_hmac = false; break;
422 TEST_EXTRA_ARG(i, 'm');
423 if ( ! mca_config.DecodeString(argv[i]) )
430 TEST_EXTRA_ARG(i, 'p');
431 if ( ! picture_coding.DecodeHex(argv[i]) )
433 fprintf(stderr, "Error decoding PictureEssenceCoding UL value: %s\n", argv[i]);
439 TEST_EXTRA_ARG(i, 'r');
440 if ( ! DecodeRational(argv[i], edit_rate) )
442 fprintf(stderr, "Error decoding edit rate value: %s\n", argv[i]);
449 use_cdci_descriptor = false;
453 TEST_EXTRA_ARG(i, 's');
454 partition_space = Kumu::xabs(strtol(argv[i], 0, 10));
458 TEST_EXTRA_ARG(i, 't');
459 rgba_MinRef = Kumu::xabs(strtol(argv[i], 0, 10));
463 TEST_EXTRA_ARG(i, 'T');
464 rgba_MaxRef = Kumu::xabs(strtol(argv[i], 0, 10));
467 case 'u': show_ul_values_flag = true; break;
469 case 'V': version_flag = true; break;
470 case 'v': verbose_flag = true; break;
471 case 'W': no_write_flag = true; break;
474 TEST_EXTRA_ARG(i, 'x');
475 horizontal_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
479 TEST_EXTRA_ARG(i, 'X');
480 vertical_subsampling = Kumu::xabs(strtol(argv[i], 0, 10));
484 use_cdci_descriptor = true;
485 // default 10 bit video range YUV, ref levels already set
489 // Use values provded as argument, sharp tool, be careful
490 use_cdci_descriptor = true;
491 TEST_EXTRA_ARG(i, 'y');
492 if ( ! set_video_ref(argv[i]) )
498 case 'Z': j2c_pedantic = false; break;
499 case 'z': j2c_pedantic = true; break;
502 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
509 if ( argv[i][0] != '-' )
511 filenames.push_back(argv[i]);
515 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
521 if ( help_flag || version_flag )
524 if ( filenames.size() < 2 )
526 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
530 out_file = filenames.back();
531 filenames.pop_back();
533 if ( ! picture_coding.HasValue() )
535 picture_coding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
543 //------------------------------------------------------------------------------------------
547 Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
548 const ASDCP::Dictionary& dict,
549 ASDCP::MXF::GenericPictureEssenceDescriptor& GenericPictureEssenceDescriptor,
550 ASDCP::MXF::JPEG2000PictureSubDescriptor& EssenceSubDescriptor);
552 Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
555 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
556 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
559 write_JP2K_file(CommandOptions& Options)
561 AESEncContext* Context = 0;
562 HMACContext* HMAC = 0;
563 AS_02::JP2K::MXFWriter Writer;
564 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
565 JP2K::SequenceParser Parser;
566 byte_t IV_buf[CBC_BLOCK_SIZE];
567 Kumu::FortunaRNG RNG;
568 ASDCP::MXF::FileDescriptor *essence_descriptor = 0;
569 ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
571 // set up essence parser
572 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
575 if ( ASDCP_SUCCESS(result) )
577 ASDCP::JP2K::PictureDescriptor PDesc;
578 Parser.FillPictureDescriptor(PDesc);
579 PDesc.EditRate = Options.edit_rate;
581 if ( Options.verbose_flag )
583 fprintf(stderr, "JPEG 2000 pictures\n");
584 fputs("PictureDescriptor:\n", stderr);
585 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
586 JP2K::PictureDescriptorDump(PDesc);
589 if ( Options.use_cdci_descriptor )
591 ASDCP::MXF::CDCIEssenceDescriptor* tmp_dscr = new ASDCP::MXF::CDCIEssenceDescriptor(g_dict);
592 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
594 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
595 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
596 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
598 if ( ASDCP_SUCCESS(result) )
600 tmp_dscr->PictureEssenceCoding = Options.picture_coding;
601 tmp_dscr->HorizontalSubsampling = Options.horizontal_subsampling;
602 tmp_dscr->VerticalSubsampling = Options.vertical_subsampling;
603 tmp_dscr->ComponentDepth = Options.component_depth;
604 tmp_dscr->FrameLayout = Options.frame_layout;
605 tmp_dscr->AspectRatio = Options.aspect_ratio;
606 tmp_dscr->FieldDominance = Options.field_dominance;
607 tmp_dscr->WhiteReflevel = Options.cdci_WhiteRefLevel;
608 tmp_dscr->BlackRefLevel = Options.cdci_BlackRefLevel;
609 tmp_dscr->ColorRange = Options.cdci_ColorRange;
610 tmp_dscr->VideoLineMap = Options.line_map;
611 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
616 ASDCP::MXF::RGBAEssenceDescriptor* tmp_dscr = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
617 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
619 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict,
620 *static_cast<ASDCP::MXF::GenericPictureEssenceDescriptor*>(tmp_dscr),
621 *static_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
623 if ( ASDCP_SUCCESS(result) )
625 tmp_dscr->PictureEssenceCoding = UL(g_dict->ul(MDD_JP2KEssenceCompression_BroadcastProfile_1));
626 tmp_dscr->ComponentMaxRef = Options.rgba_MaxRef;
627 tmp_dscr->ComponentMinRef = Options.rgba_MinRef;
628 essence_descriptor = static_cast<ASDCP::MXF::FileDescriptor*>(tmp_dscr);
633 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
635 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
636 Info.LabelSetType = LS_MXF_SMPTE;
638 if ( Options.asset_id_flag )
639 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
641 Kumu::GenRandomUUID(Info.AssetUUID);
643 // configure encryption
644 if( Options.key_flag )
646 Kumu::GenRandomUUID(Info.ContextID);
647 Info.EncryptedEssence = true;
649 if ( Options.key_id_flag )
651 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
655 create_random_uuid(Info.CryptographicKeyID);
658 Context = new AESEncContext;
659 result = Context->InitKey(Options.key_value);
661 if ( ASDCP_SUCCESS(result) )
662 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
664 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
666 Info.UsesHMAC = true;
667 HMAC = new HMACContext;
668 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
672 if ( ASDCP_SUCCESS(result) )
674 result = Writer.OpenWrite(Options.out_file, Info, essence_descriptor, essence_sub_descriptors,
675 Options.edit_rate, Options.mxf_header_size, Options.index_strategy, Options.partition_space);
679 if ( ASDCP_SUCCESS(result) )
682 result = Parser.Reset();
684 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
686 result = Parser.ReadFrame(FrameBuffer);
688 if ( ASDCP_SUCCESS(result) )
690 if ( Options.verbose_flag )
691 FrameBuffer.Dump(stderr, Options.fb_dump_size);
693 if ( Options.encrypt_header_flag )
694 FrameBuffer.PlaintextOffset(0);
697 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
699 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
701 // The Writer class will forward the last block of ciphertext
702 // to the encryption context for use as the IV for the next
703 // frame. If you want to use non-sequitur IV values, un-comment
704 // the following line of code.
705 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
706 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
710 if ( result == RESULT_ENDOFFILE )
714 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
715 result = Writer.Finalize();
720 //------------------------------------------------------------------------------------------
724 // Write one or more plaintext PCM audio streams to a plaintext AS-02 file
725 // Write one or more plaintext PCM audio streams to a ciphertext AS-02 file
728 write_PCM_file(CommandOptions& Options)
730 AESEncContext* Context = 0;
731 HMACContext* HMAC = 0;
732 PCMParserList Parser;
733 AS_02::PCM::MXFWriter Writer;
734 PCM::FrameBuffer FrameBuffer;
735 byte_t IV_buf[CBC_BLOCK_SIZE];
736 Kumu::FortunaRNG RNG;
737 ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
739 // set up essence parser
740 Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
743 if ( ASDCP_SUCCESS(result) )
745 ASDCP::PCM::AudioDescriptor ADesc;
746 Parser.FillAudioDescriptor(ADesc);
748 ADesc.EditRate = Options.edit_rate;
749 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
751 if ( Options.verbose_flag )
754 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
755 ADesc.AudioSamplingRate.Quotient() / 1000.0,
756 RationalToString(Options.edit_rate, buf, 64),
757 PCM::CalcSamplesPerFrame(ADesc));
758 fputs("AudioDescriptor:\n", stderr);
759 PCM::AudioDescriptorDump(ADesc);
762 essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
764 result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
766 if ( Options.mca_config.empty() )
768 essence_descriptor->ChannelAssignment = Options.channel_assignment;
772 if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
774 fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
775 Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
779 // this is the d-cinema MCA label, what is the one for IMF?
780 essence_descriptor->ChannelAssignment = g_dict->ul(MDD_IMFAudioChannelCfg_MCA);
784 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
786 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
787 Info.LabelSetType = LS_MXF_SMPTE;
789 if ( Options.asset_id_flag )
790 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
792 Kumu::GenRandomUUID(Info.AssetUUID);
794 // configure encryption
795 if( Options.key_flag )
797 Kumu::GenRandomUUID(Info.ContextID);
798 Info.EncryptedEssence = true;
800 if ( Options.key_id_flag )
802 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
806 create_random_uuid(Info.CryptographicKeyID);
809 Context = new AESEncContext;
810 result = Context->InitKey(Options.key_value);
812 if ( ASDCP_SUCCESS(result) )
813 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
815 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
817 Info.UsesHMAC = true;
818 HMAC = new HMACContext;
819 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
823 if ( ASDCP_SUCCESS(result) )
825 result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
826 Options.mca_config, Options.edit_rate);
830 if ( ASDCP_SUCCESS(result) )
832 result = Parser.Reset();
835 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
837 result = Parser.ReadFrame(FrameBuffer);
839 if ( ASDCP_SUCCESS(result) )
841 if ( Options.verbose_flag )
842 FrameBuffer.Dump(stderr, Options.fb_dump_size);
844 if ( ! Options.no_write_flag )
846 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
848 // The Writer class will forward the last block of ciphertext
849 // to the encryption context for use as the IV for the next
850 // frame. If you want to use non-sequitur IV values, un-comment
851 // the following line of code.
852 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
853 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
858 if ( result == RESULT_ENDOFFILE )
862 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
863 result = Writer.Finalize();
871 //------------------------------------------------------------------------------------------
875 // Write one or more plaintext timed text streams to a plaintext AS-02 file
876 // Write one or more plaintext timed text streams to a ciphertext AS-02 file
879 write_timed_text_file(CommandOptions& Options)
881 AESEncContext* Context = 0;
882 HMACContext* HMAC = 0;
883 AS_02::TimedText::ST2052_TextParser Parser;
884 AS_02::TimedText::MXFWriter Writer;
885 TimedText::FrameBuffer FrameBuffer;
886 TimedText::TimedTextDescriptor TDesc;
887 byte_t IV_buf[CBC_BLOCK_SIZE];
888 Kumu::FortunaRNG RNG;
890 // set up essence parser
891 Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
894 if ( ASDCP_SUCCESS(result) )
896 Parser.FillTimedTextDescriptor(TDesc);
897 TDesc.EditRate = Options.edit_rate;
898 TDesc.ContainerDuration = Options.duration;
899 FrameBuffer.Capacity(Options.fb_size);
901 if ( Options.verbose_flag )
903 fputs("IMF Timed-Text Descriptor:\n", stderr);
904 TimedText::DescriptorDump(TDesc);
908 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
910 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
911 Info.LabelSetType = LS_MXF_SMPTE;
913 if ( Options.asset_id_flag )
914 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
916 Kumu::GenRandomUUID(Info.AssetUUID);
918 // configure encryption
919 if( Options.key_flag )
921 Kumu::GenRandomUUID(Info.ContextID);
922 Info.EncryptedEssence = true;
924 if ( Options.key_id_flag )
926 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
930 create_random_uuid(Info.CryptographicKeyID);
933 Context = new AESEncContext;
934 result = Context->InitKey(Options.key_value);
936 if ( ASDCP_SUCCESS(result) )
937 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
939 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
941 Info.UsesHMAC = true;
942 HMAC = new HMACContext;
943 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
947 if ( ASDCP_SUCCESS(result) )
948 result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
951 if ( ASDCP_FAILURE(result) )
955 TimedText::ResourceList_t::const_iterator ri;
957 result = Parser.ReadTimedTextResource(XMLDoc);
959 if ( ASDCP_SUCCESS(result) )
960 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
962 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
964 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
966 if ( ASDCP_SUCCESS(result) )
968 if ( Options.verbose_flag )
969 FrameBuffer.Dump(stderr, Options.fb_dump_size);
971 if ( ! Options.no_write_flag )
973 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
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));
984 if ( result == RESULT_ENDOFFILE )
988 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
989 result = Writer.Finalize();
997 main(int argc, const char** argv)
999 Result_t result = RESULT_OK;
1001 g_dict = &ASDCP::DefaultSMPTEDict();
1004 CommandOptions Options(argc, argv);
1006 if ( Options.version_flag )
1009 if ( Options.help_flag )
1012 if ( Options.show_ul_values_flag )
1014 g_dict->Dump(stdout);
1017 if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
1020 if ( Options.error_flag )
1022 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1026 EssenceType_t EssenceType;
1027 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
1029 if ( ASDCP_SUCCESS(result) )
1031 switch ( EssenceType )
1034 result = write_JP2K_file(Options);
1037 case ESS_PCM_24b_48k:
1038 case ESS_PCM_24b_96k:
1039 result = write_PCM_file(Options);
1042 case ESS_TIMED_TEXT:
1043 result = write_timed_text_file(Options);
1047 fprintf(stderr, "%s: Unknown file type, not AS-02-compatible essence.\n",
1048 Options.filenames.front().c_str());
1053 if ( ASDCP_FAILURE(result) )
1055 fputs("Program stopped on error.\n", stderr);
1057 if ( result != RESULT_FAIL )
1059 fputs(result, stderr);
1060 fputc('\n', stderr);
1071 // end as-02-wrap.cpp