2 Copyright (c) 2003-2009, 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-2009 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] <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'. Default\n\
176 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|48], 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\
193 -H - Show MXF header metadata, used with option -i\n\
194 -n - Show index, used with option -i\n\
197 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
198 stderr, used with option -v\n\
199 -v - Verbose, prints informative messages to stderr\n\
201 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
202 o All option arguments must be separated from the option by whitespace.\n\
203 o An argument of \"23\" to the -p option will be interpreted\n\
204 as 23000/1001 fps.\n\
224 decode_channel_fmt(const std::string& label_name)
226 if ( label_name == "5.1" )
227 return PCM::CF_CFG_1;
229 else if ( label_name == "6.1" )
230 return PCM::CF_CFG_2;
232 else if ( label_name == "7.1" )
233 return PCM::CF_CFG_3;
235 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
236 fprintf(stderr, "Expecting '5.1', '6.1', or '7.1'\n");
248 bool error_flag; // true if the given options are in error or not complete
249 bool key_flag; // true if an encryption key was given
250 bool key_id_flag; // true if a key ID was given
251 bool asset_id_flag; // true if an asset ID was given
252 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
253 bool write_hmac; // true if HMAC values are to be generated and written
254 bool read_hmac; // true if HMAC values are to be validated
255 bool split_wav; // true if PCM is to be extracted to stereo WAV files
256 bool mono_wav; // true if PCM is to be extracted to mono WAV files
257 bool verbose_flag; // true if the verbose option was selected
258 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
259 bool showindex_flag; // true if index is to be displayed
260 bool showheader_flag; // true if MXF file header is to be displayed
261 bool no_write_flag; // true if no output files are to be written
262 bool version_flag; // true if the version display option was selected
263 bool help_flag; // true if the help display option was selected
264 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
265 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
266 ui32_t start_frame; // frame number to begin processing
267 ui32_t duration; // number of frames to be processed
268 bool duration_flag; // true if duration argument given
269 bool do_repeat; // if true and -c -d, repeat first input frame
270 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
271 ui32_t picture_rate; // fps of picture when wrapping PCM
272 ui32_t fb_size; // size of picture frame buffer
273 ui32_t file_count; // number of elements in filenames[]
274 const char* file_root; // filename pre for files written by the extract mode
275 const char* out_file; // name of mxf file created by create mode
276 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
277 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
278 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
279 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
280 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
283 Rational PictureRate()
285 if ( picture_rate == 23 ) return EditRate_23_98;
286 if ( picture_rate == 48 ) return EditRate_48;
291 const char* szPictureRate()
293 if ( picture_rate == 23 ) return "23.976";
294 if ( picture_rate == 48 ) return "48";
299 CommandOptions(int argc, const char** argv) :
300 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
301 encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
302 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
303 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
304 number_width(6), start_frame(0),
305 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
306 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
307 channel_fmt(PCM::CF_NONE)
309 memset(key_value, 0, KeyLen);
310 memset(key_id_value, 0, UUIDlen);
312 for ( int i = 1; i < argc; i++ )
315 if ( (strcmp( argv[i], "-help") == 0) )
321 if ( argv[i][0] == '-'
322 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
325 switch ( argv[i][1] )
327 case '1': mono_wav = true; break;
328 case '2': split_wav = true; break;
329 case '3': stereo_image_flag = true; break;
332 asset_id_flag = true;
333 TEST_EXTRA_ARG(i, 'a');
336 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
338 if ( length != UUIDlen )
340 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
347 TEST_EXTRA_ARG(i, 'b');
348 fb_size = abs(atoi(argv[i]));
351 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
356 TEST_EXTRA_ARG(i, 'c');
362 TEST_EXTRA_ARG(i, 'd');
363 duration_flag = true;
364 duration = abs(atoi(argv[i]));
367 case 'E': encrypt_header_flag = false; break;
368 case 'e': encrypt_header_flag = true; break;
371 TEST_EXTRA_ARG(i, 'f');
372 start_frame = abs(atoi(argv[i]));
375 case 'G': mode = MMT_GOP_START; break;
376 case 'g': mode = MMT_GEN_KEY; break;
377 case 'H': showheader_flag = true; break;
378 case 'h': help_flag = true; break;
379 case 'i': mode = MMT_INFO; break;
381 case 'j': key_id_flag = true;
382 TEST_EXTRA_ARG(i, 'j');
385 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
387 if ( length != UUIDlen )
389 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
395 case 'k': key_flag = true;
396 TEST_EXTRA_ARG(i, 'k');
399 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
401 if ( length != KeyLen )
403 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
410 TEST_EXTRA_ARG(i, 'l');
411 channel_fmt = decode_channel_fmt(argv[i]);
414 case 'L': use_smpte_labels = true; break;
415 case 'M': write_hmac = false; break;
416 case 'm': read_hmac = true; break;
417 case 'n': showindex_flag = true; break;
420 TEST_EXTRA_ARG(i, 'p');
421 picture_rate = abs(atoi(argv[i]));
424 case 'R': do_repeat = true; break;
425 case 'S': split_wav = true; break;
428 TEST_EXTRA_ARG(i, 's');
429 fb_dump_size = abs(atoi(argv[i]));
432 case 't': mode = MMT_DIGEST; break;
433 case 'U': mode = MMT_UL_LIST; break;
434 case 'u': mode = MMT_GEN_ID; break;
435 case 'V': version_flag = true; break;
436 case 'v': verbose_flag = true; break;
437 case 'W': no_write_flag = true; break;
440 TEST_EXTRA_ARG(i, 'w');
441 number_width = abs(atoi(argv[i]));
445 TEST_EXTRA_ARG(i, 'x');
451 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
458 if ( argv[i][0] != '-' )
460 filenames[file_count++] = argv[i];
464 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
468 if ( file_count >= MAX_IN_FILES )
470 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
476 if ( help_flag || version_flag )
479 if ( ( mode == MMT_INFO
480 || mode == MMT_CREATE
481 || mode == MMT_EXTRACT
482 || mode == MMT_GOP_START
483 || mode == MMT_DIGEST ) && file_count == 0 )
485 fputs("Option requires at least one filename argument.\n", stderr);
489 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
491 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
499 //------------------------------------------------------------------------------------------
502 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
503 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
506 write_MPEG2_file(CommandOptions& Options)
508 AESEncContext* Context = 0;
509 HMACContext* HMAC = 0;
510 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
511 MPEG2::Parser Parser;
512 MPEG2::MXFWriter Writer;
513 MPEG2::VideoDescriptor VDesc;
514 byte_t IV_buf[CBC_BLOCK_SIZE];
515 Kumu::FortunaRNG RNG;
517 // set up essence parser
518 Result_t result = Parser.OpenRead(Options.filenames[0]);
521 if ( ASDCP_SUCCESS(result) )
523 Parser.FillVideoDescriptor(VDesc);
525 if ( Options.verbose_flag )
527 fputs("MPEG-2 Pictures\n", stderr);
528 fputs("VideoDescriptor:\n", stderr);
529 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
530 MPEG2::VideoDescriptorDump(VDesc);
534 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
536 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
537 if ( Options.asset_id_flag )
538 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
540 Kumu::GenRandomUUID(Info.AssetUUID);
542 if ( Options.use_smpte_labels )
544 Info.LabelSetType = LS_MXF_SMPTE;
545 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
548 // configure encryption
549 if( Options.key_flag )
551 Kumu::GenRandomUUID(Info.ContextID);
552 Info.EncryptedEssence = true;
554 if ( Options.key_id_flag )
555 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
557 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
559 Context = new AESEncContext;
560 result = Context->InitKey(Options.key_value);
562 if ( ASDCP_SUCCESS(result) )
563 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
565 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
567 Info.UsesHMAC = true;
568 HMAC = new HMACContext;
569 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
573 if ( ASDCP_SUCCESS(result) )
574 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
577 if ( ASDCP_SUCCESS(result) )
578 // loop through the frames
580 result = Parser.Reset();
583 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
585 if ( ! Options.do_repeat || duration == 1 )
587 result = Parser.ReadFrame(FrameBuffer);
589 if ( ASDCP_SUCCESS(result) )
591 if ( Options.verbose_flag )
592 FrameBuffer.Dump(stderr, Options.fb_dump_size);
594 if ( Options.encrypt_header_flag )
595 FrameBuffer.PlaintextOffset(0);
599 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
601 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
603 // The Writer class will forward the last block of ciphertext
604 // to the encryption context for use as the IV for the next
605 // frame. If you want to use non-sequitur IV values, un-comment
606 // the following line of code.
607 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
608 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
612 if ( result == RESULT_ENDOFFILE )
616 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
617 result = Writer.Finalize();
622 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
623 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
624 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
627 read_MPEG2_file(CommandOptions& Options)
629 AESDecContext* Context = 0;
630 HMACContext* HMAC = 0;
631 MPEG2::MXFReader Reader;
632 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
633 Kumu::FileWriter OutFile;
634 ui32_t frame_count = 0;
636 Result_t result = Reader.OpenRead(Options.filenames[0]);
638 if ( ASDCP_SUCCESS(result) )
640 MPEG2::VideoDescriptor VDesc;
641 Reader.FillVideoDescriptor(VDesc);
642 frame_count = VDesc.ContainerDuration;
644 if ( Options.verbose_flag )
646 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
647 MPEG2::VideoDescriptorDump(VDesc);
651 if ( ASDCP_SUCCESS(result) )
654 snprintf(filename, 256, "%s.ves", Options.file_root);
655 result = OutFile.OpenWrite(filename);
658 if ( ASDCP_SUCCESS(result) && Options.key_flag )
660 Context = new AESDecContext;
661 result = Context->InitKey(Options.key_value);
663 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
666 Reader.FillWriterInfo(Info);
670 HMAC = new HMACContext;
671 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
675 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
680 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
681 if ( last_frame > frame_count )
682 last_frame = frame_count;
684 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
686 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
688 if ( ASDCP_SUCCESS(result) )
690 if ( Options.verbose_flag )
691 FrameBuffer.Dump(stderr, Options.fb_dump_size);
693 ui32_t write_count = 0;
694 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
704 gop_start_test(CommandOptions& Options)
706 using namespace ASDCP::MPEG2;
709 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
710 ui32_t frame_count = 0;
712 Result_t result = Reader.OpenRead(Options.filenames[0]);
714 if ( ASDCP_SUCCESS(result) )
716 MPEG2::VideoDescriptor VDesc;
717 Reader.FillVideoDescriptor(VDesc);
718 frame_count = VDesc.ContainerDuration;
720 if ( Options.verbose_flag )
722 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
723 MPEG2::VideoDescriptorDump(VDesc);
727 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
728 if ( last_frame > frame_count )
729 last_frame = frame_count;
731 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
733 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
735 if ( ASDCP_SUCCESS(result) )
737 if ( Options.verbose_flag )
738 FrameBuffer.Dump(stderr, Options.fb_dump_size);
740 if ( FrameBuffer.FrameType() != FRAME_I )
741 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
743 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
750 //------------------------------------------------------------------------------------------
753 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
754 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
757 write_JP2K_S_file(CommandOptions& Options)
759 AESEncContext* Context = 0;
760 HMACContext* HMAC = 0;
761 JP2K::MXFSWriter Writer;
762 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
763 JP2K::PictureDescriptor PDesc;
764 JP2K::SequenceParser ParserLeft, ParserRight;
765 byte_t IV_buf[CBC_BLOCK_SIZE];
766 Kumu::FortunaRNG RNG;
768 if ( Options.file_count != 2 )
770 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
774 // set up essence parser
775 Result_t result = ParserLeft.OpenRead(Options.filenames[0]);
777 if ( ASDCP_SUCCESS(result) )
778 result = ParserRight.OpenRead(Options.filenames[1]);
781 if ( ASDCP_SUCCESS(result) )
783 ParserLeft.FillPictureDescriptor(PDesc);
784 PDesc.EditRate = Options.PictureRate();
786 if ( Options.verbose_flag )
788 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
789 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
790 JP2K::PictureDescriptorDump(PDesc);
794 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
796 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
797 if ( Options.asset_id_flag )
798 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
800 Kumu::GenRandomUUID(Info.AssetUUID);
802 if ( Options.use_smpte_labels )
804 Info.LabelSetType = LS_MXF_SMPTE;
805 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
808 // configure encryption
809 if( Options.key_flag )
811 Kumu::GenRandomUUID(Info.ContextID);
812 Info.EncryptedEssence = true;
814 if ( Options.key_id_flag )
815 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
817 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
819 Context = new AESEncContext;
820 result = Context->InitKey(Options.key_value);
822 if ( ASDCP_SUCCESS(result) )
823 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
825 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
827 Info.UsesHMAC = true;
828 HMAC = new HMACContext;
829 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
833 if ( ASDCP_SUCCESS(result) )
834 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
837 if ( ASDCP_SUCCESS(result) )
840 result = ParserLeft.Reset();
841 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
843 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
845 result = ParserLeft.ReadFrame(FrameBuffer);
847 if ( ASDCP_SUCCESS(result) )
849 if ( Options.verbose_flag )
850 FrameBuffer.Dump(stderr, Options.fb_dump_size);
852 if ( Options.encrypt_header_flag )
853 FrameBuffer.PlaintextOffset(0);
856 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
857 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
859 if ( ASDCP_SUCCESS(result) )
860 result = ParserRight.ReadFrame(FrameBuffer);
862 if ( ASDCP_SUCCESS(result) )
864 if ( Options.verbose_flag )
865 FrameBuffer.Dump(stderr, Options.fb_dump_size);
867 if ( Options.encrypt_header_flag )
868 FrameBuffer.PlaintextOffset(0);
871 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
872 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
875 if ( result == RESULT_ENDOFFILE )
879 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
880 result = Writer.Finalize();
885 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
886 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
887 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
889 read_JP2K_S_file(CommandOptions& Options)
891 AESDecContext* Context = 0;
892 HMACContext* HMAC = 0;
893 JP2K::MXFSReader Reader;
894 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
895 ui32_t frame_count = 0;
897 Result_t result = Reader.OpenRead(Options.filenames[0]);
899 if ( ASDCP_SUCCESS(result) )
901 JP2K::PictureDescriptor PDesc;
902 Reader.FillPictureDescriptor(PDesc);
904 frame_count = PDesc.ContainerDuration;
906 if ( Options.verbose_flag )
908 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
909 JP2K::PictureDescriptorDump(PDesc);
913 if ( ASDCP_SUCCESS(result) && Options.key_flag )
915 Context = new AESDecContext;
916 result = Context->InitKey(Options.key_value);
918 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
921 Reader.FillWriterInfo(Info);
925 HMAC = new HMACContext;
926 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
930 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
935 const int filename_max = 1024;
936 char filename[filename_max];
937 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
938 if ( last_frame > frame_count )
939 last_frame = frame_count;
941 char left_format[64]; char right_format[64];
942 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
943 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
945 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
947 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
949 if ( ASDCP_SUCCESS(result) )
951 Kumu::FileWriter OutFile;
953 snprintf(filename, filename_max, left_format, Options.file_root, i);
954 result = OutFile.OpenWrite(filename);
956 if ( ASDCP_SUCCESS(result) )
957 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
959 if ( Options.verbose_flag )
960 FrameBuffer.Dump(stderr, Options.fb_dump_size);
963 if ( ASDCP_SUCCESS(result) )
964 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
966 if ( ASDCP_SUCCESS(result) )
968 Kumu::FileWriter OutFile;
970 snprintf(filename, filename_max, right_format, Options.file_root, i);
971 result = OutFile.OpenWrite(filename);
973 if ( ASDCP_SUCCESS(result) )
974 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
983 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
984 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
987 write_JP2K_file(CommandOptions& Options)
989 AESEncContext* Context = 0;
990 HMACContext* HMAC = 0;
991 JP2K::MXFWriter Writer;
992 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
993 JP2K::PictureDescriptor PDesc;
994 JP2K::SequenceParser Parser;
995 byte_t IV_buf[CBC_BLOCK_SIZE];
996 Kumu::FortunaRNG RNG;
998 // set up essence parser
999 Result_t result = Parser.OpenRead(Options.filenames[0]);
1001 // set up MXF writer
1002 if ( ASDCP_SUCCESS(result) )
1004 Parser.FillPictureDescriptor(PDesc);
1005 PDesc.EditRate = Options.PictureRate();
1007 if ( Options.verbose_flag )
1009 fprintf(stderr, "JPEG 2000 pictures\n");
1010 fputs("PictureDescriptor:\n", stderr);
1011 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1012 JP2K::PictureDescriptorDump(PDesc);
1016 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1018 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1019 if ( Options.asset_id_flag )
1020 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1022 Kumu::GenRandomUUID(Info.AssetUUID);
1024 if ( Options.use_smpte_labels )
1026 Info.LabelSetType = LS_MXF_SMPTE;
1027 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1030 // configure encryption
1031 if( Options.key_flag )
1033 Kumu::GenRandomUUID(Info.ContextID);
1034 Info.EncryptedEssence = true;
1036 if ( Options.key_id_flag )
1037 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1039 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1041 Context = new AESEncContext;
1042 result = Context->InitKey(Options.key_value);
1044 if ( ASDCP_SUCCESS(result) )
1045 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1047 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1049 Info.UsesHMAC = true;
1050 HMAC = new HMACContext;
1051 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1055 if ( ASDCP_SUCCESS(result) )
1056 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1059 if ( ASDCP_SUCCESS(result) )
1061 ui32_t duration = 0;
1062 result = Parser.Reset();
1064 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1066 if ( ! Options.do_repeat || duration == 1 )
1068 result = Parser.ReadFrame(FrameBuffer);
1070 if ( ASDCP_SUCCESS(result) )
1072 if ( Options.verbose_flag )
1073 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1075 if ( Options.encrypt_header_flag )
1076 FrameBuffer.PlaintextOffset(0);
1080 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1082 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1084 // The Writer class will forward the last block of ciphertext
1085 // to the encryption context for use as the IV for the next
1086 // frame. If you want to use non-sequitur IV values, un-comment
1087 // the following line of code.
1088 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1089 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1093 if ( result == RESULT_ENDOFFILE )
1097 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1098 result = Writer.Finalize();
1103 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1104 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1105 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1108 read_JP2K_file(CommandOptions& Options)
1110 AESDecContext* Context = 0;
1111 HMACContext* HMAC = 0;
1112 JP2K::MXFReader Reader;
1113 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1114 ui32_t frame_count = 0;
1116 Result_t result = Reader.OpenRead(Options.filenames[0]);
1118 if ( ASDCP_SUCCESS(result) )
1120 JP2K::PictureDescriptor PDesc;
1121 Reader.FillPictureDescriptor(PDesc);
1123 frame_count = PDesc.ContainerDuration;
1125 if ( Options.verbose_flag )
1127 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1128 JP2K::PictureDescriptorDump(PDesc);
1132 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1134 Context = new AESDecContext;
1135 result = Context->InitKey(Options.key_value);
1137 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1140 Reader.FillWriterInfo(Info);
1142 if ( Info.UsesHMAC )
1144 HMAC = new HMACContext;
1145 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1149 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1154 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1155 if ( last_frame > frame_count )
1156 last_frame = frame_count;
1158 char name_format[64];
1159 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1161 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1163 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1165 if ( ASDCP_SUCCESS(result) )
1167 Kumu::FileWriter OutFile;
1170 snprintf(filename, 256, name_format, Options.file_root, i);
1171 result = OutFile.OpenWrite(filename);
1173 if ( ASDCP_SUCCESS(result) )
1174 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1176 if ( Options.verbose_flag )
1177 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1184 //------------------------------------------------------------------------------------------
1188 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1189 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1192 write_PCM_file(CommandOptions& Options)
1194 AESEncContext* Context = 0;
1195 HMACContext* HMAC = 0;
1196 PCMParserList Parser;
1197 PCM::MXFWriter Writer;
1198 PCM::FrameBuffer FrameBuffer;
1199 PCM::AudioDescriptor ADesc;
1200 Rational PictureRate = Options.PictureRate();
1201 byte_t IV_buf[CBC_BLOCK_SIZE];
1202 Kumu::FortunaRNG RNG;
1204 // set up essence parser
1205 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1207 // set up MXF writer
1208 if ( ASDCP_SUCCESS(result) )
1210 Parser.FillAudioDescriptor(ADesc);
1212 ADesc.SampleRate = PictureRate;
1213 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1214 ADesc.ChannelFormat = Options.channel_fmt;
1216 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1218 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1221 if ( Options.verbose_flag )
1223 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1224 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1225 Options.szPictureRate(),
1226 PCM::CalcSamplesPerFrame(ADesc));
1227 fputs("AudioDescriptor:\n", stderr);
1228 PCM::AudioDescriptorDump(ADesc);
1232 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1234 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1235 if ( Options.asset_id_flag )
1236 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1238 Kumu::GenRandomUUID(Info.AssetUUID);
1240 if ( Options.use_smpte_labels )
1242 Info.LabelSetType = LS_MXF_SMPTE;
1243 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1246 // configure encryption
1247 if( Options.key_flag )
1249 Kumu::GenRandomUUID(Info.ContextID);
1250 Info.EncryptedEssence = true;
1252 if ( Options.key_id_flag )
1253 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1255 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1257 Context = new AESEncContext;
1258 result = Context->InitKey(Options.key_value);
1260 if ( ASDCP_SUCCESS(result) )
1261 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1263 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1265 Info.UsesHMAC = true;
1266 HMAC = new HMACContext;
1267 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1271 if ( ASDCP_SUCCESS(result) )
1272 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1275 if ( ASDCP_SUCCESS(result) )
1277 result = Parser.Reset();
1278 ui32_t duration = 0;
1280 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1282 result = Parser.ReadFrame(FrameBuffer);
1284 if ( ASDCP_SUCCESS(result) )
1286 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1288 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1289 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1290 result = RESULT_ENDOFFILE;
1294 if ( Options.verbose_flag )
1295 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1297 if ( ! Options.no_write_flag )
1299 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1301 // The Writer class will forward the last block of ciphertext
1302 // to the encryption context for use as the IV for the next
1303 // frame. If you want to use non-sequitur IV values, un-comment
1304 // the following line of code.
1305 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1306 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1311 if ( result == RESULT_ENDOFFILE )
1315 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1316 result = Writer.Finalize();
1321 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1322 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1323 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1326 read_PCM_file(CommandOptions& Options)
1328 AESDecContext* Context = 0;
1329 HMACContext* HMAC = 0;
1330 PCM::MXFReader Reader;
1331 PCM::FrameBuffer FrameBuffer;
1332 WavFileWriter OutWave;
1333 PCM::AudioDescriptor ADesc;
1334 ui32_t last_frame = 0;
1336 Result_t result = Reader.OpenRead(Options.filenames[0]);
1338 if ( ASDCP_SUCCESS(result) )
1340 Reader.FillAudioDescriptor(ADesc);
1342 if ( ADesc.SampleRate != EditRate_23_98
1343 && ADesc.SampleRate != EditRate_24
1344 && ADesc.SampleRate != EditRate_48 )
1345 ADesc.SampleRate = Options.PictureRate();
1347 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1349 if ( Options.verbose_flag )
1350 PCM::AudioDescriptorDump(ADesc);
1353 if ( ASDCP_SUCCESS(result) )
1355 last_frame = ADesc.ContainerDuration;
1357 if ( Options.duration > 0 && Options.duration < last_frame )
1358 last_frame = Options.duration;
1360 if ( Options.start_frame > 0 )
1362 if ( Options.start_frame > ADesc.ContainerDuration )
1364 fprintf(stderr, "Start value greater than file duration.\n");
1368 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1371 ADesc.ContainerDuration = last_frame - Options.start_frame;
1372 OutWave.OpenWrite(ADesc, Options.file_root,
1373 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1374 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1377 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1379 Context = new AESDecContext;
1380 result = Context->InitKey(Options.key_value);
1382 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1385 Reader.FillWriterInfo(Info);
1387 if ( Info.UsesHMAC )
1389 HMAC = new HMACContext;
1390 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1394 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1399 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1401 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1403 if ( ASDCP_SUCCESS(result) )
1405 if ( Options.verbose_flag )
1406 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1408 result = OutWave.WriteFrame(FrameBuffer);
1416 //------------------------------------------------------------------------------------------
1417 // TimedText essence
1420 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1421 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1424 write_timed_text_file(CommandOptions& Options)
1426 AESEncContext* Context = 0;
1427 HMACContext* HMAC = 0;
1428 TimedText::DCSubtitleParser Parser;
1429 TimedText::MXFWriter Writer;
1430 TimedText::FrameBuffer FrameBuffer;
1431 TimedText::TimedTextDescriptor TDesc;
1432 byte_t IV_buf[CBC_BLOCK_SIZE];
1433 Kumu::FortunaRNG RNG;
1435 // set up essence parser
1436 Result_t result = Parser.OpenRead(Options.filenames[0]);
1438 // set up MXF writer
1439 if ( ASDCP_SUCCESS(result) )
1441 Parser.FillTimedTextDescriptor(TDesc);
1442 FrameBuffer.Capacity(2*Kumu::Megabyte);
1444 if ( Options.verbose_flag )
1446 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1447 TimedText::DescriptorDump(TDesc);
1451 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1453 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1454 if ( Options.asset_id_flag )
1455 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
1457 Kumu::GenRandomUUID(Info.AssetUUID);
1459 if ( Options.use_smpte_labels )
1461 Info.LabelSetType = LS_MXF_SMPTE;
1462 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1465 // configure encryption
1466 if( Options.key_flag )
1468 Kumu::GenRandomUUID(Info.ContextID);
1469 Info.EncryptedEssence = true;
1471 if ( Options.key_id_flag )
1472 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1474 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1476 Context = new AESEncContext;
1477 result = Context->InitKey(Options.key_value);
1479 if ( ASDCP_SUCCESS(result) )
1480 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1482 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1484 Info.UsesHMAC = true;
1485 HMAC = new HMACContext;
1486 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1490 if ( ASDCP_SUCCESS(result) )
1491 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1494 if ( ASDCP_FAILURE(result) )
1498 TimedText::ResourceList_t::const_iterator ri;
1500 result = Parser.ReadTimedTextResource(XMLDoc);
1502 if ( ASDCP_SUCCESS(result) )
1503 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1505 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1507 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1509 if ( ASDCP_SUCCESS(result) )
1511 if ( Options.verbose_flag )
1512 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1514 if ( ! Options.no_write_flag )
1516 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1518 // The Writer class will forward the last block of ciphertext
1519 // to the encryption context for use as the IV for the next
1520 // frame. If you want to use non-sequitur IV values, un-comment
1521 // the following line of code.
1522 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1523 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1527 if ( result == RESULT_ENDOFFILE )
1531 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1532 result = Writer.Finalize();
1538 // Read one or more timed text streams from a plaintext ASDCP file
1539 // Read one or more timed text streams from a ciphertext ASDCP file
1540 // Read one or more timed text streams from a ciphertext ASDCP file
1543 read_timed_text_file(CommandOptions& Options)
1545 AESDecContext* Context = 0;
1546 HMACContext* HMAC = 0;
1547 TimedText::MXFReader Reader;
1548 TimedText::FrameBuffer FrameBuffer;
1549 TimedText::TimedTextDescriptor TDesc;
1551 Result_t result = Reader.OpenRead(Options.filenames[0]);
1553 if ( ASDCP_SUCCESS(result) )
1555 Reader.FillTimedTextDescriptor(TDesc);
1556 FrameBuffer.Capacity(2*Kumu::Megabyte);
1558 if ( Options.verbose_flag )
1559 TimedText::DescriptorDump(TDesc);
1562 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1564 Context = new AESDecContext;
1565 result = Context->InitKey(Options.key_value);
1567 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1570 Reader.FillWriterInfo(Info);
1572 if ( Info.UsesHMAC )
1574 HMAC = new HMACContext;
1575 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1579 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1584 if ( ASDCP_FAILURE(result) )
1588 TimedText::ResourceList_t::const_iterator ri;
1590 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1592 // do something with the XML here
1593 fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1595 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1597 result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1599 if ( ASDCP_SUCCESS(result) )
1601 // if ( Options.verbose_flag )
1602 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1604 // do something with the resource data here
1611 //------------------------------------------------------------------------------------------
1615 // These classes wrap the irregular names in the asdcplib API
1616 // so that I can use a template to simplify the implementation
1617 // of show_file_info()
1619 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1622 void FillDescriptor(MPEG2::MXFReader& Reader) {
1623 Reader.FillVideoDescriptor(*this);
1626 void Dump(FILE* stream) {
1627 MPEG2::VideoDescriptorDump(*this, stream);
1631 class MyPictureDescriptor : public JP2K::PictureDescriptor
1634 void FillDescriptor(JP2K::MXFReader& Reader) {
1635 Reader.FillPictureDescriptor(*this);
1638 void Dump(FILE* stream) {
1639 JP2K::PictureDescriptorDump(*this, stream);
1643 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1646 void FillDescriptor(JP2K::MXFSReader& Reader) {
1647 Reader.FillPictureDescriptor(*this);
1650 void Dump(FILE* stream) {
1651 JP2K::PictureDescriptorDump(*this, stream);
1655 class MyAudioDescriptor : public PCM::AudioDescriptor
1658 void FillDescriptor(PCM::MXFReader& Reader) {
1659 Reader.FillAudioDescriptor(*this);
1662 void Dump(FILE* stream) {
1663 PCM::AudioDescriptorDump(*this, stream);
1667 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1670 void FillDescriptor(TimedText::MXFReader& Reader) {
1671 Reader.FillTimedTextDescriptor(*this);
1674 void Dump(FILE* stream) {
1675 TimedText::DescriptorDump(*this, stream);
1679 // MSVC didn't like the function template, so now it's a static class method
1680 template<class ReaderT, class DescriptorT>
1681 class FileInfoWrapper
1685 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1687 assert(type_string);
1691 Result_t result = RESULT_OK;
1693 if ( Options.verbose_flag || Options.showheader_flag )
1696 result = Reader.OpenRead(Options.filenames[0]);
1698 if ( ASDCP_SUCCESS(result) )
1700 fprintf(stdout, "File essence type is %s.\n", type_string);
1702 if ( Options.showheader_flag )
1703 Reader.DumpHeaderMetadata(stream);
1706 Reader.FillWriterInfo(WI);
1707 WriterInfoDump(WI, stream);
1710 Desc.FillDescriptor(Reader);
1713 if ( Options.showindex_flag )
1714 Reader.DumpIndex(stream);
1716 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1718 Reader.DumpHeaderMetadata(stream);
1726 // Read header metadata from an ASDCP file
1729 show_file_info(CommandOptions& Options)
1731 EssenceType_t EssenceType;
1732 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1734 if ( ASDCP_FAILURE(result) )
1737 if ( EssenceType == ESS_MPEG2_VES )
1738 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1740 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1741 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1743 else if ( EssenceType == ESS_JPEG_2000 )
1745 if ( Options.stereo_image_flag )
1746 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1747 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1750 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1751 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1753 else if ( EssenceType == ESS_JPEG_2000_S )
1754 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1755 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1757 else if ( EssenceType == ESS_TIMED_TEXT )
1758 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1762 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1763 Kumu::FileReader Reader;
1764 const Dictionary* Dict = &DefaultCompositeDict();
1765 MXF::OPAtomHeader TestHeader(Dict);
1767 result = Reader.OpenRead(Options.filenames[0]);
1769 if ( ASDCP_SUCCESS(result) )
1770 result = TestHeader.InitFromFile(Reader); // test UL and OP
1772 if ( ASDCP_SUCCESS(result) )
1774 TestHeader.Partition::Dump(stdout);
1776 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1779 fputs("File contains no Identification object.\n", stdout);
1781 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1784 fputs("File contains no SourcePackage object.\n", stdout);
1788 fputs("File is not MXF.\n", stdout);
1798 digest_file(const char* filename)
1800 using namespace Kumu;
1802 ASDCP_TEST_NULL_STR(filename);
1806 ByteString Buf(8192);
1808 Result_t result = Reader.OpenRead(filename);
1810 while ( ASDCP_SUCCESS(result) )
1812 ui32_t read_count = 0;
1813 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1815 if ( result == RESULT_ENDOFFILE )
1821 if ( ASDCP_SUCCESS(result) )
1822 SHA1_Update(&Ctx, Buf.Data(), read_count);
1825 if ( ASDCP_SUCCESS(result) )
1827 const ui32_t sha_len = 20;
1828 byte_t bin_buf[sha_len];
1830 SHA1_Final(bin_buf, &Ctx);
1832 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1840 main(int argc, const char** argv)
1842 Result_t result = RESULT_OK;
1844 CommandOptions Options(argc, argv);
1846 if ( Options.version_flag )
1849 if ( Options.help_flag )
1852 if ( Options.version_flag || Options.help_flag )
1855 if ( Options.error_flag )
1857 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1861 if ( Options.mode == MMT_INFO )
1863 result = show_file_info(Options);
1865 else if ( Options.mode == MMT_GOP_START )
1867 result = gop_start_test(Options);
1869 else if ( Options.mode == MMT_GEN_KEY )
1871 Kumu::FortunaRNG RNG;
1872 byte_t bin_buf[KeyLen];
1874 RNG.FillRandom(bin_buf, KeyLen);
1875 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1877 else if ( Options.mode == MMT_GEN_ID )
1880 Kumu::GenRandomValue(TmpID);
1881 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1883 else if ( Options.mode == MMT_DIGEST )
1885 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1886 result = digest_file(Options.filenames[i]);
1888 else if ( Options.mode == MMT_UL_LIST )
1890 if ( Options.use_smpte_labels )
1891 DefaultSMPTEDict().Dump(stdout);
1893 DefaultInteropDict().Dump(stdout);
1895 else if ( Options.mode == MMT_EXTRACT )
1897 EssenceType_t EssenceType;
1898 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1900 if ( ASDCP_SUCCESS(result) )
1902 switch ( EssenceType )
1905 result = read_MPEG2_file(Options);
1909 if ( Options.stereo_image_flag )
1910 result = read_JP2K_S_file(Options);
1912 result = read_JP2K_file(Options);
1915 case ESS_JPEG_2000_S:
1916 result = read_JP2K_S_file(Options);
1919 case ESS_PCM_24b_48k:
1920 case ESS_PCM_24b_96k:
1921 result = read_PCM_file(Options);
1924 case ESS_TIMED_TEXT:
1925 result = read_timed_text_file(Options);
1929 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1934 else if ( Options.mode == MMT_CREATE )
1936 if ( Options.do_repeat && ! Options.duration_flag )
1938 fputs("Option -R requires -d <duration>\n", stderr);
1942 EssenceType_t EssenceType;
1943 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1945 if ( ASDCP_SUCCESS(result) )
1947 switch ( EssenceType )
1950 result = write_MPEG2_file(Options);
1954 if ( Options.stereo_image_flag )
1955 result = write_JP2K_S_file(Options);
1958 result = write_JP2K_file(Options);
1962 case ESS_PCM_24b_48k:
1963 case ESS_PCM_24b_96k:
1964 result = write_PCM_file(Options);
1967 case ESS_TIMED_TEXT:
1968 result = write_timed_text_file(Options);
1972 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1973 Options.filenames[0]);
1980 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
1984 if ( ASDCP_FAILURE(result) )
1986 fputs("Program stopped on error.\n", stderr);
1988 if ( result == RESULT_SFORMAT )
1990 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
1992 else if ( result != RESULT_FAIL )
1994 fputs(result, stderr);
1995 fputc('\n', stderr);
2006 // end asdcp-test.cpp