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] [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
119 [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-l <label>]\n\
120 [-L] [-M] [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
121 <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 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
170 Defaults to 4,194,304 (4MB)\n\
171 -d <duration> - Number of frames to process, default all\n\
172 -f <start-frame> - Starting frame number, default 0\n\
173 -l <label> - Use given channel format label when writing MXF sound\n\
174 files. SMPTE 429-2 labels: '5.1', '6.1', '7.1'. Default\n\
175 is no label (valid for Interop only).\n\
176 -L - Write SMPTE UL values instead of MXF Interop\n\
177 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
178 Use one of [23|24|48], 24 is default\n\
179 -R - Repeat the first frame over the entire file (picture\n\
180 essence only, requires -c, -d)\n\
181 -S - Split Wave essence to stereo WAV files during extract.\n\
182 Default is multichannel WAV\n\
183 -1 - Split Wave essence to mono WAV files during extract.\n\
184 Default is multichannel WAV\n\
185 -W - Read input file only, do not write source file\n\
186 -w <width> - Width of numeric element in a series of frame file names\n\
187 (use with -x, default 6).\n\
192 -H - Show MXF header metadata, used with option -i\n\
193 -n - Show index, used with option -i\n\
196 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
197 stderr, used with option -v\n\
198 -v - Verbose, prints informative messages to stderr\n\
200 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
201 o All option arguments must be separated from the option by whitespace.\n\
202 o An argument of \"23\" to the -p option will be interpreted\n\
203 as 23000/1001 fps.\n\
223 decode_channel_fmt(const std::string& label_name)
225 if ( label_name == "5.1" )
226 return PCM::CF_CFG_1;
228 else if ( label_name == "6.1" )
229 return PCM::CF_CFG_2;
231 else if ( label_name == "7.1" )
232 return PCM::CF_CFG_3;
234 fprintf(stderr, "Error decoding channel format string: %s\n", label_name.c_str());
235 fprintf(stderr, "Expecting '5.1', '6.1', or '7.1'\n");
247 bool error_flag; // true if the given options are in error or not complete
248 bool key_flag; // true if an encryption key was given
249 bool key_id_flag; // true if a key ID was given
250 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
251 bool write_hmac; // true if HMAC values are to be generated and written
252 bool read_hmac; // true if HMAC values are to be validated
253 bool split_wav; // true if PCM is to be extracted to stereo WAV files
254 bool mono_wav; // true if PCM is to be extracted to mono WAV files
255 bool verbose_flag; // true if the verbose option was selected
256 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
257 bool showindex_flag; // true if index is to be displayed
258 bool showheader_flag; // true if MXF file header is to be displayed
259 bool no_write_flag; // true if no output files are to be written
260 bool version_flag; // true if the version display option was selected
261 bool help_flag; // true if the help display option was selected
262 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
263 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
264 ui32_t start_frame; // frame number to begin processing
265 ui32_t duration; // number of frames to be processed
266 bool duration_flag; // true if duration argument given
267 bool do_repeat; // if true and -c -d, repeat first input frame
268 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
269 ui32_t picture_rate; // fps of picture when wrapping PCM
270 ui32_t fb_size; // size of picture frame buffer
271 ui32_t file_count; // number of elements in filenames[]
272 const char* file_root; // filename pre for files written by the extract mode
273 const char* out_file; // name of mxf file created by create mode
274 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
275 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
276 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
277 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
280 Rational PictureRate()
282 if ( picture_rate == 23 ) return EditRate_23_98;
283 if ( picture_rate == 48 ) return EditRate_48;
288 const char* szPictureRate()
290 if ( picture_rate == 23 ) return "23.976";
291 if ( picture_rate == 48 ) return "48";
296 CommandOptions(int argc, const char** argv) :
297 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
298 write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
299 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
300 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
301 number_width(6), start_frame(0),
302 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
303 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0),
304 channel_fmt(PCM::CF_NONE)
306 memset(key_value, 0, KeyLen);
307 memset(key_id_value, 0, UUIDlen);
309 for ( int i = 1; i < argc; i++ )
312 if ( (strcmp( argv[i], "-help") == 0) )
318 if ( argv[i][0] == '-'
319 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
322 switch ( argv[i][1] )
324 case '1': mono_wav = true; break;
325 case '2': split_wav = true; break;
326 case '3': stereo_image_flag = true; break;
329 TEST_EXTRA_ARG(i, 'b');
330 fb_size = abs(atoi(argv[i]));
333 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
338 TEST_EXTRA_ARG(i, 'c');
344 TEST_EXTRA_ARG(i, 'd');
345 duration_flag = true;
346 duration = abs(atoi(argv[i]));
349 case 'E': encrypt_header_flag = false; break;
350 case 'e': encrypt_header_flag = true; break;
353 TEST_EXTRA_ARG(i, 'f');
354 start_frame = abs(atoi(argv[i]));
357 case 'G': mode = MMT_GOP_START; break;
358 case 'g': mode = MMT_GEN_KEY; break;
359 case 'H': showheader_flag = true; break;
360 case 'h': help_flag = true; break;
361 case 'i': mode = MMT_INFO; break;
363 case 'j': key_id_flag = true;
364 TEST_EXTRA_ARG(i, 'j');
367 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
369 if ( length != UUIDlen )
371 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
377 case 'k': key_flag = true;
378 TEST_EXTRA_ARG(i, 'k');
381 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
383 if ( length != KeyLen )
385 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
392 TEST_EXTRA_ARG(i, 'l');
393 channel_fmt = decode_channel_fmt(argv[i]);
396 case 'L': use_smpte_labels = true; break;
397 case 'M': write_hmac = false; break;
398 case 'm': read_hmac = true; break;
399 case 'n': showindex_flag = true; break;
402 TEST_EXTRA_ARG(i, 'p');
403 picture_rate = abs(atoi(argv[i]));
406 case 'R': do_repeat = true; break;
407 case 'S': split_wav = true; break;
410 TEST_EXTRA_ARG(i, 's');
411 fb_dump_size = abs(atoi(argv[i]));
414 case 't': mode = MMT_DIGEST; break;
415 case 'U': mode = MMT_UL_LIST; break;
416 case 'u': mode = MMT_GEN_ID; break;
417 case 'V': version_flag = true; break;
418 case 'v': verbose_flag = true; break;
419 case 'W': no_write_flag = true; break;
422 TEST_EXTRA_ARG(i, 'w');
423 number_width = abs(atoi(argv[i]));
427 TEST_EXTRA_ARG(i, 'x');
433 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
440 if ( argv[i][0] != '-' )
442 filenames[file_count++] = argv[i];
446 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
450 if ( file_count >= MAX_IN_FILES )
452 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
458 if ( help_flag || version_flag )
461 if ( ( mode == MMT_INFO
462 || mode == MMT_CREATE
463 || mode == MMT_EXTRACT
464 || mode == MMT_GOP_START
465 || mode == MMT_DIGEST ) && file_count == 0 )
467 fputs("Option requires at least one filename argument.\n", stderr);
471 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
473 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
481 //------------------------------------------------------------------------------------------
484 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
485 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
488 write_MPEG2_file(CommandOptions& Options)
490 AESEncContext* Context = 0;
491 HMACContext* HMAC = 0;
492 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
493 MPEG2::Parser Parser;
494 MPEG2::MXFWriter Writer;
495 MPEG2::VideoDescriptor VDesc;
496 byte_t IV_buf[CBC_BLOCK_SIZE];
497 Kumu::FortunaRNG RNG;
499 // set up essence parser
500 Result_t result = Parser.OpenRead(Options.filenames[0]);
503 if ( ASDCP_SUCCESS(result) )
505 Parser.FillVideoDescriptor(VDesc);
507 if ( Options.verbose_flag )
509 fputs("MPEG-2 Pictures\n", stderr);
510 fputs("VideoDescriptor:\n", stderr);
511 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
512 MPEG2::VideoDescriptorDump(VDesc);
516 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
518 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
519 Kumu::GenRandomUUID(Info.AssetUUID);
521 if ( Options.use_smpte_labels )
523 Info.LabelSetType = LS_MXF_SMPTE;
524 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
527 // configure encryption
528 if( Options.key_flag )
530 Kumu::GenRandomUUID(Info.ContextID);
531 Info.EncryptedEssence = true;
533 if ( Options.key_id_flag )
534 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
536 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
538 Context = new AESEncContext;
539 result = Context->InitKey(Options.key_value);
541 if ( ASDCP_SUCCESS(result) )
542 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
544 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
546 Info.UsesHMAC = true;
547 HMAC = new HMACContext;
548 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
552 if ( ASDCP_SUCCESS(result) )
553 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
556 if ( ASDCP_SUCCESS(result) )
557 // loop through the frames
559 result = Parser.Reset();
562 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
564 if ( ! Options.do_repeat || duration == 1 )
566 result = Parser.ReadFrame(FrameBuffer);
568 if ( ASDCP_SUCCESS(result) )
570 if ( Options.verbose_flag )
571 FrameBuffer.Dump(stderr, Options.fb_dump_size);
573 if ( Options.encrypt_header_flag )
574 FrameBuffer.PlaintextOffset(0);
578 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
580 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
582 // The Writer class will forward the last block of ciphertext
583 // to the encryption context for use as the IV for the next
584 // frame. If you want to use non-sequitur IV values, un-comment
585 // the following line of code.
586 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
587 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
591 if ( result == RESULT_ENDOFFILE )
595 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
596 result = Writer.Finalize();
601 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
602 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
603 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
606 read_MPEG2_file(CommandOptions& Options)
608 AESDecContext* Context = 0;
609 HMACContext* HMAC = 0;
610 MPEG2::MXFReader Reader;
611 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
612 Kumu::FileWriter OutFile;
613 ui32_t frame_count = 0;
615 Result_t result = Reader.OpenRead(Options.filenames[0]);
617 if ( ASDCP_SUCCESS(result) )
619 MPEG2::VideoDescriptor VDesc;
620 Reader.FillVideoDescriptor(VDesc);
621 frame_count = VDesc.ContainerDuration;
623 if ( Options.verbose_flag )
625 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
626 MPEG2::VideoDescriptorDump(VDesc);
630 if ( ASDCP_SUCCESS(result) )
633 snprintf(filename, 256, "%s.ves", Options.file_root);
634 result = OutFile.OpenWrite(filename);
637 if ( ASDCP_SUCCESS(result) && Options.key_flag )
639 Context = new AESDecContext;
640 result = Context->InitKey(Options.key_value);
642 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
645 Reader.FillWriterInfo(Info);
649 HMAC = new HMACContext;
650 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
654 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
659 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
660 if ( last_frame > frame_count )
661 last_frame = frame_count;
663 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
665 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
667 if ( ASDCP_SUCCESS(result) )
669 if ( Options.verbose_flag )
670 FrameBuffer.Dump(stderr, Options.fb_dump_size);
672 ui32_t write_count = 0;
673 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
683 gop_start_test(CommandOptions& Options)
685 using namespace ASDCP::MPEG2;
688 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
689 ui32_t frame_count = 0;
691 Result_t result = Reader.OpenRead(Options.filenames[0]);
693 if ( ASDCP_SUCCESS(result) )
695 MPEG2::VideoDescriptor VDesc;
696 Reader.FillVideoDescriptor(VDesc);
697 frame_count = VDesc.ContainerDuration;
699 if ( Options.verbose_flag )
701 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
702 MPEG2::VideoDescriptorDump(VDesc);
706 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
707 if ( last_frame > frame_count )
708 last_frame = frame_count;
710 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
712 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
714 if ( ASDCP_SUCCESS(result) )
716 if ( Options.verbose_flag )
717 FrameBuffer.Dump(stderr, Options.fb_dump_size);
719 if ( FrameBuffer.FrameType() != FRAME_I )
720 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
722 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
729 //------------------------------------------------------------------------------------------
732 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
733 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
736 write_JP2K_S_file(CommandOptions& Options)
738 AESEncContext* Context = 0;
739 HMACContext* HMAC = 0;
740 JP2K::MXFSWriter Writer;
741 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
742 JP2K::PictureDescriptor PDesc;
743 JP2K::SequenceParser ParserLeft, ParserRight;
744 byte_t IV_buf[CBC_BLOCK_SIZE];
745 Kumu::FortunaRNG RNG;
747 if ( Options.file_count != 2 )
749 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
753 // set up essence parser
754 Result_t result = ParserLeft.OpenRead(Options.filenames[0]);
756 if ( ASDCP_SUCCESS(result) )
757 result = ParserRight.OpenRead(Options.filenames[1]);
760 if ( ASDCP_SUCCESS(result) )
762 ParserLeft.FillPictureDescriptor(PDesc);
763 PDesc.EditRate = Options.PictureRate();
765 if ( Options.verbose_flag )
767 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
768 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
769 JP2K::PictureDescriptorDump(PDesc);
773 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
775 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
776 Kumu::GenRandomUUID(Info.AssetUUID);
778 if ( Options.use_smpte_labels )
780 Info.LabelSetType = LS_MXF_SMPTE;
781 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
784 // configure encryption
785 if( Options.key_flag )
787 Kumu::GenRandomUUID(Info.ContextID);
788 Info.EncryptedEssence = true;
790 if ( Options.key_id_flag )
791 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
793 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
795 Context = new AESEncContext;
796 result = Context->InitKey(Options.key_value);
798 if ( ASDCP_SUCCESS(result) )
799 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
801 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
803 Info.UsesHMAC = true;
804 HMAC = new HMACContext;
805 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
809 if ( ASDCP_SUCCESS(result) )
810 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
813 if ( ASDCP_SUCCESS(result) )
816 result = ParserLeft.Reset();
817 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
819 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
821 result = ParserLeft.ReadFrame(FrameBuffer);
823 if ( ASDCP_SUCCESS(result) )
825 if ( Options.verbose_flag )
826 FrameBuffer.Dump(stderr, Options.fb_dump_size);
828 if ( Options.encrypt_header_flag )
829 FrameBuffer.PlaintextOffset(0);
832 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
833 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
835 if ( ASDCP_SUCCESS(result) )
836 result = ParserRight.ReadFrame(FrameBuffer);
838 if ( ASDCP_SUCCESS(result) )
840 if ( Options.verbose_flag )
841 FrameBuffer.Dump(stderr, Options.fb_dump_size);
843 if ( Options.encrypt_header_flag )
844 FrameBuffer.PlaintextOffset(0);
847 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
848 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
851 if ( result == RESULT_ENDOFFILE )
855 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
856 result = Writer.Finalize();
861 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
862 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
863 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
865 read_JP2K_S_file(CommandOptions& Options)
867 AESDecContext* Context = 0;
868 HMACContext* HMAC = 0;
869 JP2K::MXFSReader Reader;
870 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
871 ui32_t frame_count = 0;
873 Result_t result = Reader.OpenRead(Options.filenames[0]);
875 if ( ASDCP_SUCCESS(result) )
877 JP2K::PictureDescriptor PDesc;
878 Reader.FillPictureDescriptor(PDesc);
880 frame_count = PDesc.ContainerDuration;
882 if ( Options.verbose_flag )
884 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
885 JP2K::PictureDescriptorDump(PDesc);
889 if ( ASDCP_SUCCESS(result) && Options.key_flag )
891 Context = new AESDecContext;
892 result = Context->InitKey(Options.key_value);
894 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
897 Reader.FillWriterInfo(Info);
901 HMAC = new HMACContext;
902 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
906 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
911 const int filename_max = 1024;
912 char filename[filename_max];
913 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
914 if ( last_frame > frame_count )
915 last_frame = frame_count;
917 char left_format[64]; char right_format[64];
918 snprintf(left_format, 64, "%%s%%0%duL.j2c", Options.number_width);
919 snprintf(right_format, 64, "%%s%%0%duR.j2c", Options.number_width);
921 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
923 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
925 if ( ASDCP_SUCCESS(result) )
927 Kumu::FileWriter OutFile;
929 snprintf(filename, filename_max, left_format, Options.file_root, i);
930 result = OutFile.OpenWrite(filename);
932 if ( ASDCP_SUCCESS(result) )
933 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
935 if ( Options.verbose_flag )
936 FrameBuffer.Dump(stderr, Options.fb_dump_size);
939 if ( ASDCP_SUCCESS(result) )
940 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
942 if ( ASDCP_SUCCESS(result) )
944 Kumu::FileWriter OutFile;
946 snprintf(filename, filename_max, right_format, Options.file_root, i);
947 result = OutFile.OpenWrite(filename);
949 if ( ASDCP_SUCCESS(result) )
950 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
959 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
960 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
963 write_JP2K_file(CommandOptions& Options)
965 AESEncContext* Context = 0;
966 HMACContext* HMAC = 0;
967 JP2K::MXFWriter Writer;
968 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
969 JP2K::PictureDescriptor PDesc;
970 JP2K::SequenceParser Parser;
971 byte_t IV_buf[CBC_BLOCK_SIZE];
972 Kumu::FortunaRNG RNG;
974 // set up essence parser
975 Result_t result = Parser.OpenRead(Options.filenames[0]);
978 if ( ASDCP_SUCCESS(result) )
980 Parser.FillPictureDescriptor(PDesc);
981 PDesc.EditRate = Options.PictureRate();
983 if ( Options.verbose_flag )
985 fprintf(stderr, "JPEG 2000 pictures\n");
986 fputs("PictureDescriptor:\n", stderr);
987 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
988 JP2K::PictureDescriptorDump(PDesc);
992 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
994 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
995 Kumu::GenRandomUUID(Info.AssetUUID);
997 if ( Options.use_smpte_labels )
999 Info.LabelSetType = LS_MXF_SMPTE;
1000 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1003 // configure encryption
1004 if( Options.key_flag )
1006 Kumu::GenRandomUUID(Info.ContextID);
1007 Info.EncryptedEssence = true;
1009 if ( Options.key_id_flag )
1010 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1012 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1014 Context = new AESEncContext;
1015 result = Context->InitKey(Options.key_value);
1017 if ( ASDCP_SUCCESS(result) )
1018 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1020 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1022 Info.UsesHMAC = true;
1023 HMAC = new HMACContext;
1024 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1028 if ( ASDCP_SUCCESS(result) )
1029 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
1032 if ( ASDCP_SUCCESS(result) )
1034 ui32_t duration = 0;
1035 result = Parser.Reset();
1037 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1039 if ( ! Options.do_repeat || duration == 1 )
1041 result = Parser.ReadFrame(FrameBuffer);
1043 if ( ASDCP_SUCCESS(result) )
1045 if ( Options.verbose_flag )
1046 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1048 if ( Options.encrypt_header_flag )
1049 FrameBuffer.PlaintextOffset(0);
1053 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1055 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1057 // The Writer class will forward the last block of ciphertext
1058 // to the encryption context for use as the IV for the next
1059 // frame. If you want to use non-sequitur IV values, un-comment
1060 // the following line of code.
1061 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1062 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1066 if ( result == RESULT_ENDOFFILE )
1070 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1071 result = Writer.Finalize();
1076 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1077 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1078 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1081 read_JP2K_file(CommandOptions& Options)
1083 AESDecContext* Context = 0;
1084 HMACContext* HMAC = 0;
1085 JP2K::MXFReader Reader;
1086 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1087 ui32_t frame_count = 0;
1089 Result_t result = Reader.OpenRead(Options.filenames[0]);
1091 if ( ASDCP_SUCCESS(result) )
1093 JP2K::PictureDescriptor PDesc;
1094 Reader.FillPictureDescriptor(PDesc);
1096 frame_count = PDesc.ContainerDuration;
1098 if ( Options.verbose_flag )
1100 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1101 JP2K::PictureDescriptorDump(PDesc);
1105 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1107 Context = new AESDecContext;
1108 result = Context->InitKey(Options.key_value);
1110 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1113 Reader.FillWriterInfo(Info);
1115 if ( Info.UsesHMAC )
1117 HMAC = new HMACContext;
1118 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1122 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1127 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1128 if ( last_frame > frame_count )
1129 last_frame = frame_count;
1131 char name_format[64];
1132 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
1134 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1136 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1138 if ( ASDCP_SUCCESS(result) )
1140 Kumu::FileWriter OutFile;
1143 snprintf(filename, 256, name_format, Options.file_root, i);
1144 result = OutFile.OpenWrite(filename);
1146 if ( ASDCP_SUCCESS(result) )
1147 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1149 if ( Options.verbose_flag )
1150 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1157 //------------------------------------------------------------------------------------------
1161 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1162 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1165 write_PCM_file(CommandOptions& Options)
1167 AESEncContext* Context = 0;
1168 HMACContext* HMAC = 0;
1169 PCMParserList Parser;
1170 PCM::MXFWriter Writer;
1171 PCM::FrameBuffer FrameBuffer;
1172 PCM::AudioDescriptor ADesc;
1173 Rational PictureRate = Options.PictureRate();
1174 byte_t IV_buf[CBC_BLOCK_SIZE];
1175 Kumu::FortunaRNG RNG;
1177 // set up essence parser
1178 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1180 // set up MXF writer
1181 if ( ASDCP_SUCCESS(result) )
1183 Parser.FillAudioDescriptor(ADesc);
1185 ADesc.SampleRate = PictureRate;
1186 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1187 ADesc.ChannelFormat = Options.channel_fmt;
1189 if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
1191 fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
1194 if ( Options.verbose_flag )
1196 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
1197 ADesc.AudioSamplingRate.Quotient() / 1000.0,
1198 Options.szPictureRate(),
1199 PCM::CalcSamplesPerFrame(ADesc));
1200 fputs("AudioDescriptor:\n", stderr);
1201 PCM::AudioDescriptorDump(ADesc);
1205 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1207 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1208 Kumu::GenRandomUUID(Info.AssetUUID);
1210 if ( Options.use_smpte_labels )
1212 Info.LabelSetType = LS_MXF_SMPTE;
1213 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1216 // configure encryption
1217 if( Options.key_flag )
1219 Kumu::GenRandomUUID(Info.ContextID);
1220 Info.EncryptedEssence = true;
1222 if ( Options.key_id_flag )
1223 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1225 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1227 Context = new AESEncContext;
1228 result = Context->InitKey(Options.key_value);
1230 if ( ASDCP_SUCCESS(result) )
1231 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1233 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1235 Info.UsesHMAC = true;
1236 HMAC = new HMACContext;
1237 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1241 if ( ASDCP_SUCCESS(result) )
1242 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1245 if ( ASDCP_SUCCESS(result) )
1247 result = Parser.Reset();
1248 ui32_t duration = 0;
1250 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1252 result = Parser.ReadFrame(FrameBuffer);
1254 if ( ASDCP_SUCCESS(result) )
1256 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1258 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1259 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1260 result = RESULT_ENDOFFILE;
1264 if ( Options.verbose_flag )
1265 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1267 if ( ! Options.no_write_flag )
1269 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1271 // The Writer class will forward the last block of ciphertext
1272 // to the encryption context for use as the IV for the next
1273 // frame. If you want to use non-sequitur IV values, un-comment
1274 // the following line of code.
1275 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1276 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1281 if ( result == RESULT_ENDOFFILE )
1285 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1286 result = Writer.Finalize();
1291 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1292 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1293 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1296 read_PCM_file(CommandOptions& Options)
1298 AESDecContext* Context = 0;
1299 HMACContext* HMAC = 0;
1300 PCM::MXFReader Reader;
1301 PCM::FrameBuffer FrameBuffer;
1302 WavFileWriter OutWave;
1303 PCM::AudioDescriptor ADesc;
1304 ui32_t last_frame = 0;
1306 Result_t result = Reader.OpenRead(Options.filenames[0]);
1308 if ( ASDCP_SUCCESS(result) )
1310 Reader.FillAudioDescriptor(ADesc);
1312 if ( ADesc.SampleRate != EditRate_23_98
1313 && ADesc.SampleRate != EditRate_24
1314 && ADesc.SampleRate != EditRate_48 )
1315 ADesc.SampleRate = Options.PictureRate();
1317 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1319 if ( Options.verbose_flag )
1320 PCM::AudioDescriptorDump(ADesc);
1323 if ( ASDCP_SUCCESS(result) )
1325 last_frame = ADesc.ContainerDuration;
1327 if ( Options.duration > 0 && Options.duration < last_frame )
1328 last_frame = Options.duration;
1330 if ( Options.start_frame > 0 )
1332 if ( Options.start_frame > ADesc.ContainerDuration )
1334 fprintf(stderr, "Start value greater than file duration.\n");
1338 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1341 ADesc.ContainerDuration = last_frame - Options.start_frame;
1342 OutWave.OpenWrite(ADesc, Options.file_root,
1343 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1344 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1347 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1349 Context = new AESDecContext;
1350 result = Context->InitKey(Options.key_value);
1352 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1355 Reader.FillWriterInfo(Info);
1357 if ( Info.UsesHMAC )
1359 HMAC = new HMACContext;
1360 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1364 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1369 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1371 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1373 if ( ASDCP_SUCCESS(result) )
1375 if ( Options.verbose_flag )
1376 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1378 result = OutWave.WriteFrame(FrameBuffer);
1386 //------------------------------------------------------------------------------------------
1387 // TimedText essence
1390 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1391 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1394 write_timed_text_file(CommandOptions& Options)
1396 AESEncContext* Context = 0;
1397 HMACContext* HMAC = 0;
1398 TimedText::DCSubtitleParser Parser;
1399 TimedText::MXFWriter Writer;
1400 TimedText::FrameBuffer FrameBuffer;
1401 TimedText::TimedTextDescriptor TDesc;
1402 byte_t IV_buf[CBC_BLOCK_SIZE];
1403 Kumu::FortunaRNG RNG;
1405 // set up essence parser
1406 Result_t result = Parser.OpenRead(Options.filenames[0]);
1408 // set up MXF writer
1409 if ( ASDCP_SUCCESS(result) )
1411 Parser.FillTimedTextDescriptor(TDesc);
1412 FrameBuffer.Capacity(2*Kumu::Megabyte);
1414 if ( Options.verbose_flag )
1416 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1417 TimedText::DescriptorDump(TDesc);
1421 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1423 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1424 Kumu::GenRandomUUID(Info.AssetUUID);
1426 if ( Options.use_smpte_labels )
1428 Info.LabelSetType = LS_MXF_SMPTE;
1429 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1432 // configure encryption
1433 if( Options.key_flag )
1435 Kumu::GenRandomUUID(Info.ContextID);
1436 Info.EncryptedEssence = true;
1438 if ( Options.key_id_flag )
1439 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1441 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1443 Context = new AESEncContext;
1444 result = Context->InitKey(Options.key_value);
1446 if ( ASDCP_SUCCESS(result) )
1447 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1449 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1451 Info.UsesHMAC = true;
1452 HMAC = new HMACContext;
1453 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1457 if ( ASDCP_SUCCESS(result) )
1458 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1461 if ( ASDCP_FAILURE(result) )
1465 TimedText::ResourceList_t::const_iterator ri;
1467 result = Parser.ReadTimedTextResource(XMLDoc);
1469 if ( ASDCP_SUCCESS(result) )
1470 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1472 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1474 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1476 if ( ASDCP_SUCCESS(result) )
1478 if ( Options.verbose_flag )
1479 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1481 if ( ! Options.no_write_flag )
1483 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1485 // The Writer class will forward the last block of ciphertext
1486 // to the encryption context for use as the IV for the next
1487 // frame. If you want to use non-sequitur IV values, un-comment
1488 // the following line of code.
1489 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1490 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1494 if ( result == RESULT_ENDOFFILE )
1498 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1499 result = Writer.Finalize();
1505 // Read one or more timed text streams from a plaintext ASDCP file
1506 // Read one or more timed text streams from a ciphertext ASDCP file
1507 // Read one or more timed text streams from a ciphertext ASDCP file
1510 read_timed_text_file(CommandOptions& Options)
1512 AESDecContext* Context = 0;
1513 HMACContext* HMAC = 0;
1514 TimedText::MXFReader Reader;
1515 TimedText::FrameBuffer FrameBuffer;
1516 TimedText::TimedTextDescriptor TDesc;
1518 Result_t result = Reader.OpenRead(Options.filenames[0]);
1520 if ( ASDCP_SUCCESS(result) )
1522 Reader.FillTimedTextDescriptor(TDesc);
1523 FrameBuffer.Capacity(2*Kumu::Megabyte);
1525 if ( Options.verbose_flag )
1526 TimedText::DescriptorDump(TDesc);
1529 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1531 Context = new AESDecContext;
1532 result = Context->InitKey(Options.key_value);
1534 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1537 Reader.FillWriterInfo(Info);
1539 if ( Info.UsesHMAC )
1541 HMAC = new HMACContext;
1542 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1546 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1551 if ( ASDCP_FAILURE(result) )
1555 TimedText::ResourceList_t::const_iterator ri;
1557 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1559 // do something with the XML here
1560 fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1562 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1564 result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1566 if ( ASDCP_SUCCESS(result) )
1568 // if ( Options.verbose_flag )
1569 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1571 // do something with the resource data here
1578 //------------------------------------------------------------------------------------------
1582 // These classes wrap the irregular names in the asdcplib API
1583 // so that I can use a template to simplify the implementation
1584 // of show_file_info()
1586 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1589 void FillDescriptor(MPEG2::MXFReader& Reader) {
1590 Reader.FillVideoDescriptor(*this);
1593 void Dump(FILE* stream) {
1594 MPEG2::VideoDescriptorDump(*this, stream);
1598 class MyPictureDescriptor : public JP2K::PictureDescriptor
1601 void FillDescriptor(JP2K::MXFReader& Reader) {
1602 Reader.FillPictureDescriptor(*this);
1605 void Dump(FILE* stream) {
1606 JP2K::PictureDescriptorDump(*this, stream);
1610 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1613 void FillDescriptor(JP2K::MXFSReader& Reader) {
1614 Reader.FillPictureDescriptor(*this);
1617 void Dump(FILE* stream) {
1618 JP2K::PictureDescriptorDump(*this, stream);
1622 class MyAudioDescriptor : public PCM::AudioDescriptor
1625 void FillDescriptor(PCM::MXFReader& Reader) {
1626 Reader.FillAudioDescriptor(*this);
1629 void Dump(FILE* stream) {
1630 PCM::AudioDescriptorDump(*this, stream);
1634 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1637 void FillDescriptor(TimedText::MXFReader& Reader) {
1638 Reader.FillTimedTextDescriptor(*this);
1641 void Dump(FILE* stream) {
1642 TimedText::DescriptorDump(*this, stream);
1646 // MSVC didn't like the function template, so now it's a static class method
1647 template<class ReaderT, class DescriptorT>
1648 class FileInfoWrapper
1652 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1654 assert(type_string);
1658 Result_t result = RESULT_OK;
1660 if ( Options.verbose_flag || Options.showheader_flag )
1663 result = Reader.OpenRead(Options.filenames[0]);
1665 if ( ASDCP_SUCCESS(result) )
1667 fprintf(stdout, "File essence type is %s.\n", type_string);
1669 if ( Options.showheader_flag )
1670 Reader.DumpHeaderMetadata(stream);
1673 Reader.FillWriterInfo(WI);
1674 WriterInfoDump(WI, stream);
1677 Desc.FillDescriptor(Reader);
1680 if ( Options.showindex_flag )
1681 Reader.DumpIndex(stream);
1683 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1685 Reader.DumpHeaderMetadata(stream);
1693 // Read header metadata from an ASDCP file
1696 show_file_info(CommandOptions& Options)
1698 EssenceType_t EssenceType;
1699 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1701 if ( ASDCP_FAILURE(result) )
1704 if ( EssenceType == ESS_MPEG2_VES )
1705 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1707 else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
1708 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1710 else if ( EssenceType == ESS_JPEG_2000 )
1712 if ( Options.stereo_image_flag )
1713 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1714 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1717 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1718 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1720 else if ( EssenceType == ESS_JPEG_2000_S )
1721 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1722 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1724 else if ( EssenceType == ESS_TIMED_TEXT )
1725 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1729 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1730 Kumu::FileReader Reader;
1731 MXF::OPAtomHeader TestHeader(DefaultCompositeDict());
1733 result = Reader.OpenRead(Options.filenames[0]);
1735 if ( ASDCP_SUCCESS(result) )
1736 result = TestHeader.InitFromFile(Reader); // test UL and OP
1738 if ( ASDCP_SUCCESS(result) )
1740 TestHeader.Partition::Dump(stdout);
1742 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1745 fputs("File contains no Identification object.\n", stdout);
1747 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1750 fputs("File contains no SourcePackage object.\n", stdout);
1754 fputs("File is not MXF.\n", stdout);
1764 digest_file(const char* filename)
1766 using namespace Kumu;
1768 ASDCP_TEST_NULL_STR(filename);
1772 ByteString Buf(8192);
1774 Result_t result = Reader.OpenRead(filename);
1776 while ( ASDCP_SUCCESS(result) )
1778 ui32_t read_count = 0;
1779 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1781 if ( result == RESULT_ENDOFFILE )
1787 if ( ASDCP_SUCCESS(result) )
1788 SHA1_Update(&Ctx, Buf.Data(), read_count);
1791 if ( ASDCP_SUCCESS(result) )
1793 const ui32_t sha_len = 20;
1794 byte_t bin_buf[sha_len];
1796 SHA1_Final(bin_buf, &Ctx);
1798 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1806 main(int argc, const char** argv)
1808 Result_t result = RESULT_OK;
1810 CommandOptions Options(argc, argv);
1812 if ( Options.version_flag )
1815 if ( Options.help_flag )
1818 if ( Options.version_flag || Options.help_flag )
1821 if ( Options.error_flag )
1823 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1827 if ( Options.mode == MMT_INFO )
1829 result = show_file_info(Options);
1831 else if ( Options.mode == MMT_GOP_START )
1833 result = gop_start_test(Options);
1835 else if ( Options.mode == MMT_GEN_KEY )
1837 Kumu::FortunaRNG RNG;
1838 byte_t bin_buf[KeyLen];
1840 RNG.FillRandom(bin_buf, KeyLen);
1841 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1843 else if ( Options.mode == MMT_GEN_ID )
1846 Kumu::GenRandomValue(TmpID);
1847 printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1849 else if ( Options.mode == MMT_DIGEST )
1851 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1852 result = digest_file(Options.filenames[i]);
1854 else if ( Options.mode == MMT_UL_LIST )
1856 if ( Options.use_smpte_labels )
1857 DefaultSMPTEDict().Dump(stdout);
1859 DefaultInteropDict().Dump(stdout);
1861 else if ( Options.mode == MMT_EXTRACT )
1863 EssenceType_t EssenceType;
1864 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1866 if ( ASDCP_SUCCESS(result) )
1868 switch ( EssenceType )
1871 result = read_MPEG2_file(Options);
1875 if ( Options.stereo_image_flag )
1876 result = read_JP2K_S_file(Options);
1878 result = read_JP2K_file(Options);
1881 case ESS_JPEG_2000_S:
1882 result = read_JP2K_S_file(Options);
1885 case ESS_PCM_24b_48k:
1886 case ESS_PCM_24b_96k:
1887 result = read_PCM_file(Options);
1890 case ESS_TIMED_TEXT:
1891 result = read_timed_text_file(Options);
1895 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1900 else if ( Options.mode == MMT_CREATE )
1902 if ( Options.do_repeat && ! Options.duration_flag )
1904 fputs("Option -R requires -d <duration>\n", stderr);
1908 EssenceType_t EssenceType;
1909 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1911 if ( ASDCP_SUCCESS(result) )
1913 switch ( EssenceType )
1916 result = write_MPEG2_file(Options);
1920 if ( Options.stereo_image_flag )
1921 result = write_JP2K_S_file(Options);
1924 result = write_JP2K_file(Options);
1928 case ESS_PCM_24b_48k:
1929 case ESS_PCM_24b_96k:
1930 result = write_PCM_file(Options);
1933 case ESS_TIMED_TEXT:
1934 result = write_timed_text_file(Options);
1938 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1939 Options.filenames[0]);
1946 fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
1950 if ( ASDCP_FAILURE(result) )
1952 fputs("Program stopped on error.\n", stderr);
1954 if ( result == RESULT_SFORMAT )
1956 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
1958 else if ( result != RESULT_FAIL )
1960 fputs(result, stderr);
1961 fputc('\n', stderr);
1972 // end asdcp-test.cpp