2 Copyright (c) 2003-2011, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14 derived from this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /*! \file asdcp-test.cpp
29 \brief AS-DCP file manipulation utility
31 This program provides command line access to the major features of the asdcplib
32 library, and serves as a library unit test which provides the functionality of
33 the supported use cases.
35 For more information about asdcplib, please refer to the header file AS_DCP.h
37 WARNING: While the asdcplib library attempts to provide a complete and secure
38 implementation of the cryptographic features of the AS-DCP file formats, this
39 unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40 production environment without some modification.
42 In particular, this program uses weak IV generation and externally generated
43 plaintext keys. These shortcomings exist because cryptographic-quality
44 random number generation and key management are outside the scope of the
45 asdcplib library. Developers using asdcplib for commercial implementations
46 claiming SMPTE conformance are expected to provide proper implementations of
50 #include <KM_fileio.h>
52 #include <PCMParserList.h>
53 #include <WavFileWriter.h>
56 #include <openssl/sha.h>
61 using namespace ASDCP;
63 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
65 //------------------------------------------------------------------------------------------
67 // command line option parser class
69 static const char* PROGRAM_NAME = "asdcp-test"; // program name for messages
70 const ui32_t MAX_IN_FILES = 16; // maximum number of input files handled by
71 // the command option parser
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
79 static byte_t default_ProductUUID_Data[UUIDlen] =
80 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
83 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84 CompanyName = "WidgetCo";
85 ProductName = "asdcp-test";
86 ProductVersion = ASDCP::Version();
92 // Increment the iterator, test for an additional non-option command line argument.
93 // Causes the caller to return if there are no remaining arguments or if the next
94 // argument begins with '-'.
95 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
97 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
102 banner(FILE* stream = stdout)
105 %s (asdcplib %s)\n\n\
106 Copyright (c) 2003-2011 John Hurst\n\n\
107 asdcplib may be copied only under the terms of the license found at\n\
108 the top of every file in the asdcplib distribution kit.\n\n\
109 Specify the -h (help) option for further information about %s\n\n",
110 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
115 usage(FILE* stream = stdout)
118 USAGE: %s -c <output-file> [-3] [-a <uuid>] [-b <buffer-size>]\n\
119 [-d <duration>] [-e|-E] [-f <start-frame>] [-j <key-id-string>]\n\
120 [-k <key-string>] [-l <label>] [-L] [-M] [-p <frame-rate>] [-R]\n\
121 [-s <num>] [-v] [-W] [-z|-Z] <input-file> [<input-file-2> ...]\n\
123 %s [-h|-help] [-V]\n\
125 %s -i [-H] [-n] [-v] <input-file>\n\
129 %s -G [-v] <input-file>\n\
131 %s -t <input-file>\n\
133 %s -x <file-prefix> [-3] [-b <buffer-size>] [-d <duration>]\n\
134 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
135 [-v] [-W] [-w] <input-file>\n\
136 \n", PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
140 -3 - With -c, create a stereoscopic image file. Expects two\n\
141 directories of JP2K codestreams (directories must have\n\
142 an equal number of frames; left eye is first).\n\
143 - With -x, force stereoscopic interpretation of a JP2K\n\
145 -c <output-file> - Create an AS-DCP track file from input(s)\n\
146 -g - Generate a random 16 byte value to stdout\n\
147 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
148 -h | -help - Show help\n\
149 -i - Show file info\n\
150 -t - Calculate message digest of input file\n\
151 -U - Dump UL catalog to stdout\n\
152 -u - Generate a random UUID value to stdout\n\
153 -V - Show version information\n\
154 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
159 -e - Encrypt MPEG or JP2K headers (default)\n\
160 -E - Do not encrypt MPEG or JP2K headers\n\
161 -j <key-id-str> - Write key ID instead of creating a random value\n\
162 -k <key-string> - Use key for ciphertext operations\n\
163 -m - verify HMAC values when reading\n\
164 -M - Do not create HMAC values when writing\n\
168 Read/Write Options:\n\
169 -a <UUID> - Specify the Asset ID of a file (with -c)\n\
170 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
171 Defaults to 4,194,304 (4MB)\n\
172 -d <duration> - Number of frames to process, default all\n\
173 -f <start-frame> - Starting frame number, default 0\n\
174 -l <label> - Use given channel format label when writing MXF sound\n\
175 files. SMPTE 429-2 labels: '5.1', '6.1', '7.1', '7.1DS', 'WTF'.\n\
176 Default is no label (valid for Interop only).\n\
177 -L - Write SMPTE UL values instead of MXF Interop\n\
178 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
179 Use one of [23|24|25|30|48|50|60], 24 is default\n\
180 -R - Repeat the first frame over the entire file (picture\n\
181 essence only, requires -c, -d)\n\
182 -S - Split Wave essence to stereo WAV files during extract.\n\
183 Default is multichannel WAV\n\
184 -1 - Split Wave essence to mono WAV files during extract.\n\
185 Default is multichannel WAV\n\
186 -W - Read input file only, do not write source file\n\
187 -w <width> - Width of numeric element in a series of frame file names\n\
188 (use with -x, default 6).\n\
189 -z - Fail if j2c inputs have unequal parameters (default)\n\
190 -Z - Ignore unequal parameters in j2c inputs\n\
195 -H - Show MXF header metadata, used with option -i\n\
196 -n - Show index, used with option -i\n\
199 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
200 stderr, used with option -v\n\
201 -v - Verbose, prints informative messages to stderr\n\
203 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
204 o All option arguments must be separated from the option by whitespace.\n\
205 o An argument of \"23\" to the -p option will be interpreted\n\
206 as 24000/1001 fps.\n\
226 decode_channel_fmt(const std::string& label_name)
228 if ( label_name == "5.1" )
229 return PCM::CF_CFG_1;
231 else if ( label_name == "6.1" )
232 return PCM::CF_CFG_2;
234 else if ( label_name == "7.1" )
235 return PCM::CF_CFG_3;
237 else if ( label_name == "WTF" )
238 return PCM::CF_CFG_4;
240 else if ( label_name == "7.1DS" )
241 return PCM::CF_CFG_5;
243 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
244 fprintf(stderr, "Expecting '5.1', '6.1', '7.1', '7.1DS' or 'WTF'\n");
256 bool error_flag; // true if the given options are in error or not complete
257 bool key_flag; // true if an encryption key was given
258 bool key_id_flag; // true if a key ID was given
259 bool asset_id_flag; // true if an asset ID was given
260 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
261 bool write_hmac; // true if HMAC values are to be generated and written
262 bool read_hmac; // true if HMAC values are to be validated
263 bool split_wav; // true if PCM is to be extracted to stereo WAV files
264 bool mono_wav; // true if PCM is to be extracted to mono WAV files
265 bool verbose_flag; // true if the verbose option was selected
266 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
267 bool showindex_flag; // true if index is to be displayed
268 bool showheader_flag; // true if MXF file header is to be displayed
269 bool no_write_flag; // true if no output files are to be written
270 bool version_flag; // true if the version display option was selected
271 bool help_flag; // true if the help display option was selected
272 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
273 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
274 ui32_t start_frame; // frame number to begin processing
275 ui32_t duration; // number of frames to be processed
276 bool duration_flag; // true if duration argument given
277 bool do_repeat; // if true and -c -d, repeat first input frame
278 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
279 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
280 ui32_t picture_rate; // fps of picture when wrapping PCM
281 ui32_t fb_size; // size of picture frame buffer
282 ui32_t file_count; // number of elements in filenames[]
283 const char* file_root; // filename pre for files written by the extract mode
284 const char* out_file; // name of mxf file created by create mode
285 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
286 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
287 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
288 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
289 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
292 Rational PictureRate()
294 if ( picture_rate == 23 ) return EditRate_23_98;
295 if ( picture_rate == 24 ) return EditRate_24;
296 if ( picture_rate == 25 ) return EditRate_25;
297 if ( picture_rate == 30 ) return EditRate_30;
298 if ( picture_rate == 48 ) return EditRate_48;
299 if ( picture_rate == 50 ) return EditRate_50;
300 if ( picture_rate == 60 ) return EditRate_60;
301 if ( picture_rate == 96 ) return EditRate_96;
302 if ( picture_rate == 100 ) return EditRate_100;
303 if ( picture_rate == 120 ) return EditRate_120;
308 const char* szPictureRate()
310 if ( picture_rate == 23 ) return "23.976";
311 if ( picture_rate == 24 ) return "24";
312 if ( picture_rate == 25 ) return "25";
313 if ( picture_rate == 30 ) return "30";
314 if ( picture_rate == 48 ) return "48";
315 if ( picture_rate == 50 ) return "50";
316 if ( picture_rate == 60 ) return "60";
317 if ( picture_rate == 96 ) return "96";
318 if ( picture_rate == 100 ) return "100";
319 if ( picture_rate == 120 ) return "120";
324 CommandOptions(int argc, const char** argv) :
325 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
326 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
327 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
328 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
329 number_width(6), start_frame(0),
330 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false), j2c_pedantic(true),
331 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
332 channel_fmt(PCM::CF_NONE)
334 memset(key_value, 0, KeyLen);
335 memset(key_id_value, 0, UUIDlen);
337 for ( int i = 1; i < argc; i++ )
340 if ( (strcmp( argv[i], "-help") == 0) )
346 if ( argv[i][0] == '-'
347 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
350 switch ( argv[i][1] )
352 case '1': mono_wav = true; break;
353 case '2': split_wav = true; break;
354 case '3': stereo_image_flag = true; break;
357 asset_id_flag = true;
358 TEST_EXTRA_ARG(i, 'a');
361 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
363 if ( length != UUIDlen )
365 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
372 TEST_EXTRA_ARG(i, 'b');
373 fb_size = abs(atoi(argv[i]));
376 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
381 TEST_EXTRA_ARG(i, 'c');
387 TEST_EXTRA_ARG(i, 'd');
388 duration_flag = true;
389 duration = abs(atoi(argv[i]));
392 case 'E': encrypt_header_flag = false; break;
393 case 'e': encrypt_header_flag = true; break;
396 TEST_EXTRA_ARG(i, 'f');
397 start_frame = abs(atoi(argv[i]));
400 case 'G': mode = MMT_GOP_START; break;
401 case 'g': mode = MMT_GEN_KEY; break;
402 case 'H': showheader_flag = true; break;
403 case 'h': help_flag = true; break;
404 case 'i': mode = MMT_INFO; break;
406 case 'j': key_id_flag = true;
407 TEST_EXTRA_ARG(i, 'j');
410 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
412 if ( length != UUIDlen )
414 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
420 case 'k': key_flag = true;
421 TEST_EXTRA_ARG(i, 'k');
424 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
426 if ( length != KeyLen )
428 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
435 TEST_EXTRA_ARG(i, 'l');
436 channel_fmt = decode_channel_fmt(argv[i]);
439 case 'L': use_smpte_labels = true; break;
440 case 'M': write_hmac = false; break;
441 case 'm': read_hmac = true; break;
442 case 'n': showindex_flag = true; break;
445 TEST_EXTRA_ARG(i, 'p');
446 picture_rate = abs(atoi(argv[i]));
449 case 'R': do_repeat = true; break;
450 case 'S': split_wav = true; break;
453 TEST_EXTRA_ARG(i, 's');
454 fb_dump_size = abs(atoi(argv[i]));
457 case 't': mode = MMT_DIGEST; break;
458 case 'U': mode = MMT_UL_LIST; break;
459 case 'u': mode = MMT_GEN_ID; break;
460 case 'V': version_flag = true; break;
461 case 'v': verbose_flag = true; break;
462 case 'W': no_write_flag = true; break;
465 TEST_EXTRA_ARG(i, 'w');
466 number_width = abs(atoi(argv[i]));
470 TEST_EXTRA_ARG(i, 'x');
475 case 'Z': j2c_pedantic = false; break;
476 case 'z': j2c_pedantic = true; break;
479 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
486 if ( argv[i][0] != '-' )
488 filenames[file_count++] = argv[i];
492 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
496 if ( file_count >= MAX_IN_FILES )
498 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
504 if ( help_flag || version_flag )
507 if ( ( mode == MMT_INFO
508 || mode == MMT_CREATE
509 || mode == MMT_EXTRACT
510 || mode == MMT_GOP_START
511 || mode == MMT_DIGEST ) && file_count == 0 )
513 fputs("Option requires at least one filename argument.\n", stderr);
517 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
519 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
527 //------------------------------------------------------------------------------------------
530 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
531 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
534 write_MPEG2_file(CommandOptions& Options)
536 AESEncContext* Context = 0;
537 HMACContext* HMAC = 0;
538 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
539 MPEG2::Parser Parser;
540 MPEG2::MXFWriter Writer;
541 MPEG2::VideoDescriptor VDesc;
542 byte_t IV_buf[CBC_BLOCK_SIZE];
543 Kumu::FortunaRNG RNG;
545 // set up essence parser
546 Result_t result = Parser.OpenRead(Options.filenames[0]);
549 if ( ASDCP_SUCCESS(result) )
551 Parser.FillVideoDescriptor(VDesc);
553 if ( Options.verbose_flag )
555 fputs("MPEG-2 Pictures\n", stderr);
556 fputs("VideoDescriptor:\n", stderr);
557 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
558 MPEG2::VideoDescriptorDump(VDesc);
562 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
564 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
565 if ( Options.asset_id_flag )
566 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
568 Kumu::GenRandomUUID(Info.AssetUUID);
570 if ( Options.use_smpte_labels )
572 Info.LabelSetType = LS_MXF_SMPTE;
573 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
576 // configure encryption
577 if( Options.key_flag )
579 Kumu::GenRandomUUID(Info.ContextID);
580 Info.EncryptedEssence = true;
582 if ( Options.key_id_flag )
583 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
585 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
587 Context = new AESEncContext;
588 result = Context->InitKey(Options.key_value);
590 if ( ASDCP_SUCCESS(result) )
591 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
593 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
595 Info.UsesHMAC = true;
596 HMAC = new HMACContext;
597 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
601 if ( ASDCP_SUCCESS(result) )
602 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
605 if ( ASDCP_SUCCESS(result) )
606 // loop through the frames
608 result = Parser.Reset();
611 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
613 if ( ! Options.do_repeat || duration == 1 )
615 result = Parser.ReadFrame(FrameBuffer);
617 if ( ASDCP_SUCCESS(result) )
619 if ( Options.verbose_flag )
620 FrameBuffer.Dump(stderr, Options.fb_dump_size);
622 if ( Options.encrypt_header_flag )
623 FrameBuffer.PlaintextOffset(0);
627 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
629 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
631 // The Writer class will forward the last block of ciphertext
632 // to the encryption context for use as the IV for the next
633 // frame. If you want to use non-sequitur IV values, un-comment
634 // the following line of code.
635 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
636 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
640 if ( result == RESULT_ENDOFFILE )
644 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
645 result = Writer.Finalize();
650 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
651 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
652 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
655 read_MPEG2_file(CommandOptions& Options)
657 AESDecContext* Context = 0;
658 HMACContext* HMAC = 0;
659 MPEG2::MXFReader Reader;
660 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
661 Kumu::FileWriter OutFile;
662 ui32_t frame_count = 0;
664 Result_t result = Reader.OpenRead(Options.filenames[0]);
666 if ( ASDCP_SUCCESS(result) )
668 MPEG2::VideoDescriptor VDesc;
669 Reader.FillVideoDescriptor(VDesc);
670 frame_count = VDesc.ContainerDuration;
672 if ( Options.verbose_flag )
674 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
675 MPEG2::VideoDescriptorDump(VDesc);
679 if ( ASDCP_SUCCESS(result) )
682 snprintf(filename, 256, "%s.ves", Options.file_root);
683 result = OutFile.OpenWrite(filename);
686 if ( ASDCP_SUCCESS(result) && Options.key_flag )
688 Context = new AESDecContext;
689 result = Context->InitKey(Options.key_value);
691 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
694 Reader.FillWriterInfo(Info);
698 HMAC = new HMACContext;
699 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
703 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
708 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
709 if ( last_frame > frame_count )
710 last_frame = frame_count;
712 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
714 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
716 if ( ASDCP_SUCCESS(result) )
718 if ( Options.verbose_flag )
719 FrameBuffer.Dump(stderr, Options.fb_dump_size);
721 ui32_t write_count = 0;
722 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
732 gop_start_test(CommandOptions& Options)
734 using namespace ASDCP::MPEG2;
737 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
738 ui32_t frame_count = 0;
740 Result_t result = Reader.OpenRead(Options.filenames[0]);
742 if ( ASDCP_SUCCESS(result) )
744 MPEG2::VideoDescriptor VDesc;
745 Reader.FillVideoDescriptor(VDesc);
746 frame_count = VDesc.ContainerDuration;
748 if ( Options.verbose_flag )
750 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
751 MPEG2::VideoDescriptorDump(VDesc);
755 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
756 if ( last_frame > frame_count )
757 last_frame = frame_count;
759 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
761 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
763 if ( ASDCP_SUCCESS(result) )
765 if ( Options.verbose_flag )
766 FrameBuffer.Dump(stderr, Options.fb_dump_size);
768 if ( FrameBuffer.FrameType() != FRAME_I )
769 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
771 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
778 //------------------------------------------------------------------------------------------
781 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
782 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
785 write_JP2K_S_file(CommandOptions& Options)
787 AESEncContext* Context = 0;
788 HMACContext* HMAC = 0;
789 JP2K::MXFSWriter Writer;
790 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
791 JP2K::PictureDescriptor PDesc;
792 JP2K::SequenceParser ParserLeft, ParserRight;
793 byte_t IV_buf[CBC_BLOCK_SIZE];
794 Kumu::FortunaRNG RNG;
796 if ( Options.file_count != 2 )
798 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
802 // set up essence parser
803 Result_t result = ParserLeft.OpenRead(Options.filenames[0], Options.j2c_pedantic);
805 if ( ASDCP_SUCCESS(result) )
806 result = ParserRight.OpenRead(Options.filenames[1], Options.j2c_pedantic);
809 if ( ASDCP_SUCCESS(result) )
811 ParserLeft.FillPictureDescriptor(PDesc);
812 PDesc.EditRate = Options.PictureRate();
814 if ( Options.verbose_flag )
816 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
817 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
818 JP2K::PictureDescriptorDump(PDesc);
822 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
824 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
825 if ( Options.asset_id_flag )
826 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
828 Kumu::GenRandomUUID(Info.AssetUUID);
830 if ( Options.use_smpte_labels )
832 Info.LabelSetType = LS_MXF_SMPTE;
833 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
836 // configure encryption
837 if( Options.key_flag )
839 Kumu::GenRandomUUID(Info.ContextID);
840 Info.EncryptedEssence = true;
842 if ( Options.key_id_flag )
843 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
845 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
847 Context = new AESEncContext;
848 result = Context->InitKey(Options.key_value);
850 if ( ASDCP_SUCCESS(result) )
851 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
853 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
855 Info.UsesHMAC = true;
856 HMAC = new HMACContext;
857 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
861 if ( ASDCP_SUCCESS(result) )
862 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
865 if ( ASDCP_SUCCESS(result) )
868 result = ParserLeft.Reset();
869 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
871 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
873 result = ParserLeft.ReadFrame(FrameBuffer);
875 if ( ASDCP_SUCCESS(result) )
877 if ( Options.verbose_flag )
878 FrameBuffer.Dump(stderr, Options.fb_dump_size);
880 if ( Options.encrypt_header_flag )
881 FrameBuffer.PlaintextOffset(0);
884 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
885 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
887 if ( ASDCP_SUCCESS(result) )
888 result = ParserRight.ReadFrame(FrameBuffer);
890 if ( ASDCP_SUCCESS(result) )
892 if ( Options.verbose_flag )
893 FrameBuffer.Dump(stderr, Options.fb_dump_size);
895 if ( Options.encrypt_header_flag )
896 FrameBuffer.PlaintextOffset(0);
899 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
900 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
903 if ( result == RESULT_ENDOFFILE )
907 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
908 result = Writer.Finalize();
913 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
914 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
915 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
917 read_JP2K_S_file(CommandOptions& Options)
919 AESDecContext* Context = 0;
920 HMACContext* HMAC = 0;
921 JP2K::MXFSReader Reader;
922 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
923 ui32_t frame_count = 0;
925 Result_t result = Reader.OpenRead(Options.filenames[0]);
927 if ( ASDCP_SUCCESS(result) )
929 JP2K::PictureDescriptor PDesc;
930 Reader.FillPictureDescriptor(PDesc);
932 frame_count = PDesc.ContainerDuration;
934 if ( Options.verbose_flag )
936 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
937 JP2K::PictureDescriptorDump(PDesc);
941 if ( ASDCP_SUCCESS(result) && Options.key_flag )
943 Context = new AESDecContext;
944 result = Context->InitKey(Options.key_value);
946 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
949 Reader.FillWriterInfo(Info);
953 HMAC = new HMACContext;
954 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
958 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
963 const int filename_max = 1024;
964 char filename[filename_max];
965 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
966 if ( last_frame > frame_count )
967 last_frame = frame_count;
969 char left_format[64]; char right_format[64];
970 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
971 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
973 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
975 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
977 if ( ASDCP_SUCCESS(result) )
979 Kumu::FileWriter OutFile;
981 snprintf(filename, filename_max, left_format, Options.file_root, i);
982 result = OutFile.OpenWrite(filename);
984 if ( ASDCP_SUCCESS(result) )
985 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
987 if ( Options.verbose_flag )
988 FrameBuffer.Dump(stderr, Options.fb_dump_size);
991 if ( ASDCP_SUCCESS(result) )
992 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
994 if ( ASDCP_SUCCESS(result) )
996 Kumu::FileWriter OutFile;
998 snprintf(filename, filename_max, right_format, Options.file_root, i);
999 result = OutFile.OpenWrite(filename);
1001 if ( ASDCP_SUCCESS(result) )
1002 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1011 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
1012 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
1015 write_JP2K_file(CommandOptions& Options)
1017 AESEncContext* Context = 0;
1018 HMACContext* HMAC = 0;
1019 JP2K::MXFWriter Writer;
1020 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1021 JP2K::PictureDescriptor PDesc;
1022 JP2K::SequenceParser Parser;
1023 byte_t IV_buf[CBC_BLOCK_SIZE];
1024 Kumu::FortunaRNG RNG;
1026 // set up essence parser
1027 Result_t result = Parser.OpenRead(Options.filenames[0], Options.j2c_pedantic);
1029 // set up MXF writer
1030 if ( ASDCP_SUCCESS(result) )
1032 Parser.FillPictureDescriptor(PDesc);
1033 PDesc.EditRate = Options.PictureRate();
1035 if ( Options.verbose_flag )
1037 fprintf(stderr, "JPEG 2000 pictures\n");
1038 fputs("PictureDescriptor:\n", stderr);
1039 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1040 JP2K::PictureDescriptorDump(PDesc);
1044 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1046 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1047 if ( Options.asset_id_flag )
1048 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1050 Kumu::GenRandomUUID(Info.AssetUUID);
1052 if ( Options.use_smpte_labels )
1054 Info.LabelSetType = LS_MXF_SMPTE;
1055 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1058 // configure encryption
1059 if( Options.key_flag )
1061 Kumu::GenRandomUUID(Info.ContextID);
1062 Info.EncryptedEssence = true;
1064 if ( Options.key_id_flag )
1065 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1067 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1069 Context = new AESEncContext;
1070 result = Context->InitKey(Options.key_value);
1072 if ( ASDCP_SUCCESS(result) )
1073 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1075 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1077 Info.UsesHMAC = true;
1078 HMAC = new HMACContext;
1079 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1083 if ( ASDCP_SUCCESS(result) )
1084 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1087 if ( ASDCP_SUCCESS(result) )
1089 ui32_t duration = 0;
1090 result = Parser.Reset();
1092 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1094 if ( ! Options.do_repeat || duration == 1 )
1096 result = Parser.ReadFrame(FrameBuffer);
1098 if ( ASDCP_SUCCESS(result) )
1100 if ( Options.verbose_flag )
1101 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1103 if ( Options.encrypt_header_flag )
1104 FrameBuffer.PlaintextOffset(0);
1108 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1110 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1112 // The Writer class will forward the last block of ciphertext
1113 // to the encryption context for use as the IV for the next
1114 // frame. If you want to use non-sequitur IV values, un-comment
1115 // the following line of code.
1116 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1117 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1121 if ( result == RESULT_ENDOFFILE )
1125 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1126 result = Writer.Finalize();
1131 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1132 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1133 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1136 read_JP2K_file(CommandOptions& Options)
1138 AESDecContext* Context = 0;
1139 HMACContext* HMAC = 0;
1140 JP2K::MXFReader Reader;
1141 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1142 ui32_t frame_count = 0;
1144 Result_t result = Reader.OpenRead(Options.filenames[0]);
1146 if ( ASDCP_SUCCESS(result) )
1148 JP2K::PictureDescriptor PDesc;
1149 Reader.FillPictureDescriptor(PDesc);
1151 frame_count = PDesc.ContainerDuration;
1153 if ( Options.verbose_flag )
1155 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1156 JP2K::PictureDescriptorDump(PDesc);
1160 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1162 Context = new AESDecContext;
1163 result = Context->InitKey(Options.key_value);
1165 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1168 Reader.FillWriterInfo(Info);
1170 if ( Info.UsesHMAC )
1172 HMAC = new HMACContext;
1173 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1177 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1182 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1183 if ( last_frame > frame_count )
1184 last_frame = frame_count;
1186 char name_format[64];
1187 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1189 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1191 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1193 if ( ASDCP_SUCCESS(result) )
1195 Kumu::FileWriter OutFile;
1198 snprintf(filename, 256, name_format, Options.file_root, i);
1199 result = OutFile.OpenWrite(filename);
1201 if ( ASDCP_SUCCESS(result) )
1202 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1204 if ( Options.verbose_flag )
1205 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1212 //------------------------------------------------------------------------------------------
1216 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1217 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1220 write_PCM_file(CommandOptions& Options)
1222 AESEncContext* Context = 0;
1223 HMACContext* HMAC = 0;
1224 PCMParserList Parser;
1225 PCM::MXFWriter Writer;
1226 PCM::FrameBuffer FrameBuffer;
1227 PCM::AudioDescriptor ADesc;
1228 Rational PictureRate = Options.PictureRate();
1229 byte_t IV_buf[CBC_BLOCK_SIZE];
1230 Kumu::FortunaRNG RNG;
1232 // set up essence parser
1233 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1235 // set up MXF writer
1236 if ( ASDCP_SUCCESS(result) )
1238 Parser.FillAudioDescriptor(ADesc);
1240 ADesc.EditRate = PictureRate;
1241 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1242 ADesc.ChannelFormat = Options.channel_fmt;
1244 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1246 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1249 if ( Options.verbose_flag )
1251 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1252 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1253 Options.szPictureRate(),
1254 PCM::CalcSamplesPerFrame(ADesc));
1255 fputs("AudioDescriptor:\n", stderr);
1256 PCM::AudioDescriptorDump(ADesc);
1260 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1262 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1263 if ( Options.asset_id_flag )
1264 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1266 Kumu::GenRandomUUID(Info.AssetUUID);
1268 if ( Options.use_smpte_labels )
1270 Info.LabelSetType = LS_MXF_SMPTE;
1271 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1274 // configure encryption
1275 if( Options.key_flag )
1277 Kumu::GenRandomUUID(Info.ContextID);
1278 Info.EncryptedEssence = true;
1280 if ( Options.key_id_flag )
1281 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1283 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1285 Context = new AESEncContext;
1286 result = Context->InitKey(Options.key_value);
1288 if ( ASDCP_SUCCESS(result) )
1289 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1291 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1293 Info.UsesHMAC = true;
1294 HMAC = new HMACContext;
1295 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1299 if ( ASDCP_SUCCESS(result) )
1300 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1303 if ( ASDCP_SUCCESS(result) )
1305 result = Parser.Reset();
1306 ui32_t duration = 0;
1308 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1310 result = Parser.ReadFrame(FrameBuffer);
1312 if ( ASDCP_SUCCESS(result) )
1314 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1316 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1317 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1318 result = RESULT_ENDOFFILE;
1322 if ( Options.verbose_flag )
1323 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1325 if ( ! Options.no_write_flag )
1327 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1329 // The Writer class will forward the last block of ciphertext
1330 // to the encryption context for use as the IV for the next
1331 // frame. If you want to use non-sequitur IV values, un-comment
1332 // the following line of code.
1333 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1334 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1339 if ( result == RESULT_ENDOFFILE )
1343 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1344 result = Writer.Finalize();
1349 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1350 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1351 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1354 read_PCM_file(CommandOptions& Options)
1356 AESDecContext* Context = 0;
1357 HMACContext* HMAC = 0;
1358 PCM::MXFReader Reader;
1359 PCM::FrameBuffer FrameBuffer;
1360 WavFileWriter OutWave;
1361 PCM::AudioDescriptor ADesc;
1362 ui32_t last_frame = 0;
1364 Result_t result = Reader.OpenRead(Options.filenames[0]);
1366 if ( ASDCP_SUCCESS(result) )
1368 Reader.FillAudioDescriptor(ADesc);
1370 if ( ADesc.EditRate != EditRate_23_98
1371 && ADesc.EditRate != EditRate_24
1372 && ADesc.EditRate != EditRate_25
1373 && ADesc.EditRate != EditRate_30
1374 && ADesc.EditRate != EditRate_48
1375 && ADesc.EditRate != EditRate_50
1376 && ADesc.EditRate != EditRate_60 )
1377 ADesc.EditRate = Options.PictureRate();
1379 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1381 if ( Options.verbose_flag )
1382 PCM::AudioDescriptorDump(ADesc);
1385 if ( ASDCP_SUCCESS(result) )
1387 last_frame = ADesc.ContainerDuration;
1389 if ( Options.duration > 0 && Options.duration < last_frame )
1390 last_frame = Options.duration;
1392 if ( Options.start_frame > 0 )
1394 if ( Options.start_frame > ADesc.ContainerDuration )
1396 fprintf(stderr, "Start value greater than file duration.\n");
1400 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1403 ADesc.ContainerDuration = last_frame - Options.start_frame;
1404 OutWave.OpenWrite(ADesc, Options.file_root,
1405 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1406 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1409 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1411 Context = new AESDecContext;
1412 result = Context->InitKey(Options.key_value);
1414 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1417 Reader.FillWriterInfo(Info);
1419 if ( Info.UsesHMAC )
1421 HMAC = new HMACContext;
1422 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1426 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1431 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1433 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1435 if ( ASDCP_SUCCESS(result) )
1437 if ( Options.verbose_flag )
1438 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1440 result = OutWave.WriteFrame(FrameBuffer);
1448 //------------------------------------------------------------------------------------------
1449 // TimedText essence
1452 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1453 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1456 write_timed_text_file(CommandOptions& Options)
1458 AESEncContext* Context = 0;
1459 HMACContext* HMAC = 0;
1460 TimedText::DCSubtitleParser Parser;
1461 TimedText::MXFWriter Writer;
1462 TimedText::FrameBuffer FrameBuffer;
1463 TimedText::TimedTextDescriptor TDesc;
1464 byte_t IV_buf[CBC_BLOCK_SIZE];
1465 Kumu::FortunaRNG RNG;
1467 // set up essence parser
1468 Result_t result = Parser.OpenRead(Options.filenames[0]);
1470 // set up MXF writer
1471 if ( ASDCP_SUCCESS(result) )
1473 Parser.FillTimedTextDescriptor(TDesc);
1474 FrameBuffer.Capacity(Options.fb_size);
1476 if ( Options.verbose_flag )
1478 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1479 TimedText::DescriptorDump(TDesc);
1483 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1485 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1486 if ( Options.asset_id_flag )
1487 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1489 Kumu::GenRandomUUID(Info.AssetUUID);
1491 if ( Options.use_smpte_labels )
1493 Info.LabelSetType = LS_MXF_SMPTE;
1494 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1497 // configure encryption
1498 if( Options.key_flag )
1500 Kumu::GenRandomUUID(Info.ContextID);
1501 Info.EncryptedEssence = true;
1503 if ( Options.key_id_flag )
1504 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1506 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1508 Context = new AESEncContext;
1509 result = Context->InitKey(Options.key_value);
1511 if ( ASDCP_SUCCESS(result) )
1512 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1514 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1516 Info.UsesHMAC = true;
1517 HMAC = new HMACContext;
1518 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1522 if ( ASDCP_SUCCESS(result) )
1523 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1526 if ( ASDCP_FAILURE(result) )
1530 TimedText::ResourceList_t::const_iterator ri;
1532 result = Parser.ReadTimedTextResource(XMLDoc);
1534 if ( ASDCP_SUCCESS(result) )
1535 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1537 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1539 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1541 if ( ASDCP_SUCCESS(result) )
1543 if ( Options.verbose_flag )
1544 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1546 if ( ! Options.no_write_flag )
1548 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1550 // The Writer class will forward the last block of ciphertext
1551 // to the encryption context for use as the IV for the next
1552 // frame. If you want to use non-sequitur IV values, un-comment
1553 // the following line of code.
1554 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1555 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1559 if ( result == RESULT_ENDOFFILE )
1563 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1564 result = Writer.Finalize();
1570 // Read one or more timed text streams from a plaintext ASDCP file
1571 // Read one or more timed text streams from a ciphertext ASDCP file
1572 // Read one or more timed text streams from a ciphertext ASDCP file
1575 read_timed_text_file(CommandOptions& Options)
1577 AESDecContext* Context = 0;
1578 HMACContext* HMAC = 0;
1579 TimedText::MXFReader Reader;
1580 TimedText::FrameBuffer FrameBuffer;
1581 TimedText::TimedTextDescriptor TDesc;
1583 Result_t result = Reader.OpenRead(Options.filenames[0]);
1585 if ( ASDCP_SUCCESS(result) )
1587 Reader.FillTimedTextDescriptor(TDesc);
1588 FrameBuffer.Capacity(Options.fb_size);
1590 if ( Options.verbose_flag )
1591 TimedText::DescriptorDump(TDesc);
1594 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1596 Context = new AESDecContext;
1597 result = Context->InitKey(Options.key_value);
1599 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1602 Reader.FillWriterInfo(Info);
1604 if ( Info.UsesHMAC )
1606 HMAC = new HMACContext;
1607 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1611 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1616 if ( ASDCP_FAILURE(result) )
1620 std::string out_path = Kumu::PathDirname(Options.file_root);
1623 TimedText::ResourceList_t::const_iterator ri;
1625 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1627 if ( ASDCP_SUCCESS(result) )
1629 Kumu::FileWriter Writer;
1630 result = Writer.OpenWrite(Options.file_root);
1632 if ( ASDCP_SUCCESS(result) )
1633 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
1636 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1638 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
1640 if ( ASDCP_SUCCESS(result) )
1642 Kumu::FileWriter Writer;
1643 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
1645 if ( ASDCP_SUCCESS(result) )
1647 if ( Options.verbose_flag )
1648 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1650 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
1658 //------------------------------------------------------------------------------------------
1662 // These classes wrap the irregular names in the asdcplib API
1663 // so that I can use a template to simplify the implementation
1664 // of show_file_info()
1666 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1669 void FillDescriptor(MPEG2::MXFReader& Reader) {
1670 Reader.FillVideoDescriptor(*this);
1673 void Dump(FILE* stream) {
1674 MPEG2::VideoDescriptorDump(*this, stream);
1678 class MyPictureDescriptor : public JP2K::PictureDescriptor
1681 void FillDescriptor(JP2K::MXFReader& Reader) {
1682 Reader.FillPictureDescriptor(*this);
1685 void Dump(FILE* stream) {
1686 JP2K::PictureDescriptorDump(*this, stream);
1690 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1693 void FillDescriptor(JP2K::MXFSReader& Reader) {
1694 Reader.FillPictureDescriptor(*this);
1697 void Dump(FILE* stream) {
1698 JP2K::PictureDescriptorDump(*this, stream);
1702 class MyAudioDescriptor : public PCM::AudioDescriptor
1705 void FillDescriptor(PCM::MXFReader& Reader) {
1706 Reader.FillAudioDescriptor(*this);
1709 void Dump(FILE* stream) {
1710 PCM::AudioDescriptorDump(*this, stream);
1714 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1717 void FillDescriptor(TimedText::MXFReader& Reader) {
1718 Reader.FillTimedTextDescriptor(*this);
1721 void Dump(FILE* stream) {
1722 TimedText::DescriptorDump(*this, stream);
1726 // MSVC didn't like the function template, so now it's a static class method
1727 template<class ReaderT, class DescriptorT>
1728 class FileInfoWrapper
1732 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1734 assert(type_string);
1738 Result_t result = RESULT_OK;
1740 if ( Options.verbose_flag || Options.showheader_flag )
1743 result = Reader.OpenRead(Options.filenames[0]);
1745 if ( ASDCP_SUCCESS(result) )
1747 fprintf(stdout, "File essence type is %s.\n", type_string);
1749 if ( Options.showheader_flag )
1750 Reader.DumpHeaderMetadata(stream);
1753 Reader.FillWriterInfo(WI);
1754 WriterInfoDump(WI, stream);
1757 Desc.FillDescriptor(Reader);
1760 if ( Options.showindex_flag )
1761 Reader.DumpIndex(stream);
1763 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1765 Reader.DumpHeaderMetadata(stream);
1773 // Read header metadata from an ASDCP file
1776 show_file_info(CommandOptions& Options)
1778 EssenceType_t EssenceType;
1779 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1781 if ( ASDCP_FAILURE(result) )
1784 if ( EssenceType == ESS_MPEG2_VES )
1785 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1787 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1788 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1790 else if ( EssenceType == ESS_JPEG_2000 )
1792 if ( Options.stereo_image_flag )
1793 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1794 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1797 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1798 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1800 else if ( EssenceType == ESS_JPEG_2000_S )
1801 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1802 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1804 else if ( EssenceType == ESS_TIMED_TEXT )
1805 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1809 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1810 Kumu::FileReader Reader;
1811 const Dictionary* Dict = &DefaultCompositeDict();
1812 MXF::OPAtomHeader TestHeader(Dict);
1814 result = Reader.OpenRead(Options.filenames[0]);
1816 if ( ASDCP_SUCCESS(result) )
1817 result = TestHeader.InitFromFile(Reader); // test UL and OP
1819 if ( ASDCP_SUCCESS(result) )
1821 TestHeader.Partition::Dump(stdout);
1823 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1826 fputs("File contains no Identification object.\n", stdout);
1828 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1831 fputs("File contains no SourcePackage object.\n", stdout);
1835 fputs("File is not MXF.\n", stdout);
1845 digest_file(const char* filename)
1847 using namespace Kumu;
1849 ASDCP_TEST_NULL_STR(filename);
1853 ByteString Buf(8192);
1855 Result_t result = Reader.OpenRead(filename);
1857 while ( ASDCP_SUCCESS(result) )
1859 ui32_t read_count = 0;
1860 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1862 if ( result == RESULT_ENDOFFILE )
1868 if ( ASDCP_SUCCESS(result) )
1869 SHA1_Update(&Ctx, Buf.Data(), read_count);
1872 if ( ASDCP_SUCCESS(result) )
1874 const ui32_t sha_len = 20;
1875 byte_t bin_buf[sha_len];
1877 SHA1_Final(bin_buf, &Ctx);
1879 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1887 main(int argc, const char** argv)
1889 Result_t result = RESULT_OK;
1891 CommandOptions Options(argc, argv);
1893 if ( Options.version_flag )
1896 if ( Options.help_flag )
1899 if ( Options.version_flag || Options.help_flag )
1902 if ( Options.error_flag )
1904 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1908 if ( Options.mode == MMT_INFO )
1910 result = show_file_info(Options);
1912 for ( int i = 1; ASDCP_SUCCESS(result) && i < Options.file_count; ++i )
1914 Options.filenames[0] = Options.filenames[i]; // oh-so hackish
1915 result = show_file_info(Options);
1918 else if ( Options.mode == MMT_GOP_START )
1920 result = gop_start_test(Options);
1922 else if ( Options.mode == MMT_GEN_KEY )
1924 Kumu::FortunaRNG RNG;
1925 byte_t bin_buf[KeyLen];
1927 RNG.FillRandom(bin_buf, KeyLen);
1928 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1930 else if ( Options.mode == MMT_GEN_ID )
1933 Kumu::GenRandomValue(TmpID);
1934 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1936 else if ( Options.mode == MMT_DIGEST )
1938 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1939 result = digest_file(Options.filenames[i]);
1941 else if ( Options.mode == MMT_UL_LIST )
1943 if ( Options.use_smpte_labels )
1944 DefaultSMPTEDict().Dump(stdout);
1946 DefaultInteropDict().Dump(stdout);
1948 else if ( Options.mode == MMT_EXTRACT )
1950 EssenceType_t EssenceType;
1951 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1953 if ( ASDCP_SUCCESS(result) )
1955 switch ( EssenceType )
1958 result = read_MPEG2_file(Options);
1962 if ( Options.stereo_image_flag )
1963 result = read_JP2K_S_file(Options);
1965 result = read_JP2K_file(Options);
1968 case ESS_JPEG_2000_S:
1969 result = read_JP2K_S_file(Options);
1972 case ESS_PCM_24b_48k:
1973 case ESS_PCM_24b_96k:
1974 result = read_PCM_file(Options);
1977 case ESS_TIMED_TEXT:
1978 result = read_timed_text_file(Options);
1982 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1987 else if ( Options.mode == MMT_CREATE )
1989 if ( Options.do_repeat && ! Options.duration_flag )
1991 fputs("Option -R requires -d <duration>\n", stderr);
1995 EssenceType_t EssenceType;
1996 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1998 if ( ASDCP_SUCCESS(result) )
2000 switch ( EssenceType )
2003 result = write_MPEG2_file(Options);
2007 if ( Options.stereo_image_flag )
2008 result = write_JP2K_S_file(Options);
2011 result = write_JP2K_file(Options);
2015 case ESS_PCM_24b_48k:
2016 case ESS_PCM_24b_96k:
2017 result = write_PCM_file(Options);
2020 case ESS_TIMED_TEXT:
2021 result = write_timed_text_file(Options);
2025 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
2026 Options.filenames[0]);
2033 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
2037 if ( ASDCP_FAILURE(result) )
2039 fputs("Program stopped on error.\n", stderr);
2041 if ( result == RESULT_SFORMAT )
2043 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
2045 else if ( result != RESULT_FAIL )
2047 fputs(result, stderr);
2048 fputc('\n', stderr);
2059 // end asdcp-test.cpp