2 Copyright (c) 2003-2007, 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
53 #include <KM_fileio.h>
55 #include <PCMParserList.h>
56 #include <WavFileWriter.h>
59 #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* PACKAGE = "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";
88 snprintf(s_buf, 128, "%u.%u.%u", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89 ProductVersion = s_buf;
95 // Increment the iterator, test for an additional non-option command line argument.
96 // Causes the caller to return if there are no remaining arguments or if the next
97 // argument begins with '-'.
98 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
100 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
105 banner(FILE* stream = stdout)
108 %s (asdcplib %s)\n\n\
109 Copyright (c) 2003-2006 John Hurst\n\n\
110 asdcplib may be copied only under the terms of the license found at\n\
111 the top of every file in the asdcplib distribution kit.\n\n\
112 Specify the -h (help) option for further information about %s\n\n",
113 PACKAGE, ASDCP::Version(), PACKAGE);
118 usage(FILE* stream = stdout)
121 USAGE: %s -c <output-file> [-3] [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
122 [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
123 [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
124 <input-file> [<input-file-2> ...]\n\
126 %s [-h|-help] [-V]\n\
128 %s -i [-H] [-n] [-v] <input-file>\n\
132 %s -G [-v] <input-file>\n\
134 %s -t <input-file>\n\
136 %s -x <file-prefix> [-3] [-b <buffer-size>] [-d <duration>]\n\
137 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
138 [-v] [-W] <input-file>\n\
139 \n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
143 -3 - With -c, create a stereoscopic image file. Expects two\n\
144 directories of JP2K codestreams (directories must have\n\
145 an equal number of frames; left eye is first).\n\
146 - With -x, force stereoscopic interpretation of a JP2K\n\
148 -c <output-file> - Create an AS-DCP track file from input(s)\n\
149 -g - Generate a random 16 byte value to stdout\n\
150 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
151 -h | -help - Show help\n\
152 -i - Show file info\n\
153 -t - Calculate message digest of input file\n\
154 -u - Generate a random UUID value to stdout\n\
155 -V - Show version information\n\
156 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
161 -e - Encrypt MPEG or JP2K headers (default)\n\
162 -E - Do not encrypt MPEG or JP2K headers\n\
163 -j <key-id-str> - Write key ID instead of creating a random value\n\
164 -k <key-string> - Use key for ciphertext operations\n\
165 -m - verify HMAC values when reading\n\
166 -M - Do not create HMAC values when writing\n\
170 Read/Write Options:\n\
171 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
172 Defaults to 4,194,304 (4MB)\n\
173 -d <duration> - Number of frames to process, default all\n\
174 -f <start-frame> - Starting frame number, default 0\n\
175 -L - Write SMPTE UL values instead of MXF Interop\n\
176 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
177 Use one of [23|24|48], 24 is default\n\
178 -R - Repeat the first frame over the entire file (picture\n\
179 essence only, requires -c, -d)\n\
180 -S - Split Wave essence to stereo WAV files during extract.\n\
181 Default is multichannel WAV\n\
182 -1 - Split Wave essence to mono WAV files during extract.\n\
183 Default is multichannel WAV\n\
184 -W - Read input file only, do not write source file\n\
189 -H - Show MXF header metadata, used with option -i\n\
190 -n - Show index, used with option -i\n\
193 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
194 stderr, used with option -v\n\
195 -v - Verbose, prints informative messages to stderr\n\
197 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
198 o All option arguments must be separated from the option by whitespace.\n\
199 o An argument of \"23\" to the -p option will be interpreted\n\
200 as 23000/1001 fps.\n\
226 bool error_flag; // true if the given options are in error or not complete
227 bool key_flag; // true if an encryption key was given
228 bool key_id_flag; // true if a key ID was given
229 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
230 bool write_hmac; // true if HMAC values are to be generated and written
231 bool read_hmac; // true if HMAC values are to be validated
232 bool split_wav; // true if PCM is to be extracted to stereo WAV files
233 bool mono_wav; // true if PCM is to be extracted to mono WAV files
234 bool verbose_flag; // true if the verbose option was selected
235 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
236 bool showindex_flag; // true if index is to be displayed
237 bool showheader_flag; // true if MXF file header is to be displayed
238 bool no_write_flag; // true if no output files are to be written
239 bool version_flag; // true if the version display option was selected
240 bool help_flag; // true if the help display option was selected
241 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
242 ui32_t start_frame; // frame number to begin processing
243 ui32_t duration; // number of frames to be processed
244 bool duration_flag; // true if duration argument given
245 bool do_repeat; // if true and -c -d, repeat first input frame
246 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
247 ui32_t picture_rate; // fps of picture when wrapping PCM
248 ui32_t fb_size; // size of picture frame buffer
249 ui32_t file_count; // number of elements in filenames[]
250 const char* file_root; // filename pre for files written by the extract mode
251 const char* out_file; // name of mxf file created by create mode
252 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
253 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
254 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
257 Rational PictureRate()
259 if ( picture_rate == 23 ) return EditRate_23_98;
260 if ( picture_rate == 48 ) return EditRate_48;
265 const char* szPictureRate()
267 if ( picture_rate == 23 ) return "23.976";
268 if ( picture_rate == 48 ) return "48";
273 CommandOptions(int argc, const char** argv) :
274 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
275 write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
276 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
277 no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false), start_frame(0),
278 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
279 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
281 memset(key_value, 0, KeyLen);
282 memset(key_id_value, 0, UUIDlen);
284 for ( int i = 1; i < argc; i++ )
287 if ( (strcmp( argv[i], "-help") == 0) )
293 if ( argv[i][0] == '-'
294 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
297 switch ( argv[i][1] )
299 case '1': mono_wav = true; break;
300 case '2': split_wav = true; break;
301 case '3': stereo_image_flag = true; break;
302 case 'i': mode = MMT_INFO; break;
303 case 'G': mode = MMT_GOP_START; break;
304 case 'W': no_write_flag = true; break;
305 case 'n': showindex_flag = true; break;
306 case 'H': showheader_flag = true; break;
307 case 'R': do_repeat = true; break;
308 case 'S': split_wav = true; break;
309 case 'V': version_flag = true; break;
310 case 'h': help_flag = true; break;
311 case 'v': verbose_flag = true; break;
312 case 'g': mode = MMT_GEN_KEY; break;
313 case 'u': mode = MMT_GEN_ID; break;
314 case 'e': encrypt_header_flag = true; break;
315 case 'E': encrypt_header_flag = false; break;
316 case 'M': write_hmac = false; break;
317 case 'm': read_hmac = true; break;
318 case 'L': use_smpte_labels = true; break;
321 TEST_EXTRA_ARG(i, 'c');
327 TEST_EXTRA_ARG(i, 'x');
332 case 'k': key_flag = true;
333 TEST_EXTRA_ARG(i, 'k');
336 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
338 if ( length != KeyLen )
340 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
346 case 'j': key_id_flag = true;
347 TEST_EXTRA_ARG(i, 'j');
350 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
352 if ( length != UUIDlen )
354 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
361 TEST_EXTRA_ARG(i, 'f');
362 start_frame = abs(atoi(argv[i]));
366 TEST_EXTRA_ARG(i, 'd');
367 duration_flag = true;
368 duration = abs(atoi(argv[i]));
372 TEST_EXTRA_ARG(i, 'p');
373 picture_rate = abs(atoi(argv[i]));
377 TEST_EXTRA_ARG(i, 's');
378 fb_dump_size = abs(atoi(argv[i]));
381 case 't': mode = MMT_DIGEST; break;
384 TEST_EXTRA_ARG(i, 'b');
385 fb_size = abs(atoi(argv[i]));
388 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
393 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
400 if ( argv[i][0] != '-' )
402 filenames[file_count++] = argv[i];
406 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
410 if ( file_count >= MAX_IN_FILES )
412 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
418 if ( help_flag || version_flag )
421 if ( ( mode == MMT_INFO
422 || mode == MMT_CREATE
423 || mode == MMT_EXTRACT
424 || mode == MMT_GOP_START
425 || mode == MMT_DIGEST ) && file_count == 0 )
427 fputs("Option requires at least one filename argument.\n", stderr);
431 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
433 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
441 //------------------------------------------------------------------------------------------
444 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
445 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
448 write_MPEG2_file(CommandOptions& Options)
450 AESEncContext* Context = 0;
451 HMACContext* HMAC = 0;
452 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
453 MPEG2::Parser Parser;
454 MPEG2::MXFWriter Writer;
455 MPEG2::VideoDescriptor VDesc;
456 byte_t IV_buf[CBC_BLOCK_SIZE];
457 Kumu::FortunaRNG RNG;
459 // set up essence parser
460 Result_t result = Parser.OpenRead(Options.filenames[0]);
463 if ( ASDCP_SUCCESS(result) )
465 Parser.FillVideoDescriptor(VDesc);
467 if ( Options.verbose_flag )
469 fputs("MPEG-2 Pictures\n", stderr);
470 fputs("VideoDescriptor:\n", stderr);
471 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
472 MPEG2::VideoDescriptorDump(VDesc);
476 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
478 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
479 Kumu::GenRandomUUID(Info.AssetUUID);
481 if ( Options.use_smpte_labels )
483 Info.LabelSetType = LS_MXF_SMPTE;
484 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
487 // configure encryption
488 if( Options.key_flag )
490 Kumu::GenRandomUUID(Info.ContextID);
491 Info.EncryptedEssence = true;
493 if ( Options.key_id_flag )
494 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
496 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
498 Context = new AESEncContext;
499 result = Context->InitKey(Options.key_value);
501 if ( ASDCP_SUCCESS(result) )
502 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
504 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
506 Info.UsesHMAC = true;
507 HMAC = new HMACContext;
508 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
512 if ( ASDCP_SUCCESS(result) )
513 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
516 if ( ASDCP_SUCCESS(result) )
517 // loop through the frames
519 result = Parser.Reset();
522 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
524 if ( ! Options.do_repeat || duration == 1 )
526 result = Parser.ReadFrame(FrameBuffer);
528 if ( ASDCP_SUCCESS(result) )
530 if ( Options.verbose_flag )
531 FrameBuffer.Dump(stderr, Options.fb_dump_size);
533 if ( Options.encrypt_header_flag )
534 FrameBuffer.PlaintextOffset(0);
538 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
540 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
542 // The Writer class will forward the last block of ciphertext
543 // to the encryption context for use as the IV for the next
544 // frame. If you want to use non-sequitur IV values, un-comment
545 // the following line of code.
546 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
547 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
551 if ( result == RESULT_ENDOFFILE )
555 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
556 result = Writer.Finalize();
561 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
562 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
563 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
566 read_MPEG2_file(CommandOptions& Options)
568 AESDecContext* Context = 0;
569 HMACContext* HMAC = 0;
570 MPEG2::MXFReader Reader;
571 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
572 Kumu::FileWriter OutFile;
573 ui32_t frame_count = 0;
575 Result_t result = Reader.OpenRead(Options.filenames[0]);
577 if ( ASDCP_SUCCESS(result) )
579 MPEG2::VideoDescriptor VDesc;
580 Reader.FillVideoDescriptor(VDesc);
581 frame_count = VDesc.ContainerDuration;
583 if ( Options.verbose_flag )
585 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
586 MPEG2::VideoDescriptorDump(VDesc);
590 if ( ASDCP_SUCCESS(result) )
593 snprintf(filename, 256, "%s.ves", Options.file_root);
594 result = OutFile.OpenWrite(filename);
597 if ( ASDCP_SUCCESS(result) && Options.key_flag )
599 Context = new AESDecContext;
600 result = Context->InitKey(Options.key_value);
602 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
605 Reader.FillWriterInfo(Info);
609 HMAC = new HMACContext;
610 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
614 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
619 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
620 if ( last_frame > frame_count )
621 last_frame = frame_count;
623 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
625 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
627 if ( ASDCP_SUCCESS(result) )
629 if ( Options.verbose_flag )
630 FrameBuffer.Dump(stderr, Options.fb_dump_size);
632 ui32_t write_count = 0;
633 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
643 gop_start_test(CommandOptions& Options)
645 using namespace ASDCP::MPEG2;
648 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
649 ui32_t frame_count = 0;
651 Result_t result = Reader.OpenRead(Options.filenames[0]);
653 if ( ASDCP_SUCCESS(result) )
655 MPEG2::VideoDescriptor VDesc;
656 Reader.FillVideoDescriptor(VDesc);
657 frame_count = VDesc.ContainerDuration;
659 if ( Options.verbose_flag )
661 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
662 MPEG2::VideoDescriptorDump(VDesc);
666 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
667 if ( last_frame > frame_count )
668 last_frame = frame_count;
670 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
672 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
674 if ( ASDCP_SUCCESS(result) )
676 if ( Options.verbose_flag )
677 FrameBuffer.Dump(stderr, Options.fb_dump_size);
679 if ( FrameBuffer.FrameType() != FRAME_I )
680 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
682 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
689 //------------------------------------------------------------------------------------------
692 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
693 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
696 write_JP2K_S_file(CommandOptions& Options)
698 AESEncContext* Context = 0;
699 HMACContext* HMAC = 0;
700 JP2K::MXFSWriter Writer;
701 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
702 JP2K::PictureDescriptor PDesc;
703 JP2K::SequenceParser ParserLeft, ParserRight;
704 byte_t IV_buf[CBC_BLOCK_SIZE];
705 Kumu::FortunaRNG RNG;
707 if ( Options.file_count != 2 )
709 fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
713 // set up essence parser
714 Result_t result = ParserLeft.OpenRead(Options.filenames[0]);
716 if ( ASDCP_SUCCESS(result) )
717 result = ParserRight.OpenRead(Options.filenames[1]);
720 if ( ASDCP_SUCCESS(result) )
722 ParserLeft.FillPictureDescriptor(PDesc);
723 PDesc.EditRate = Options.PictureRate();
725 if ( Options.verbose_flag )
727 fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
728 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
729 JP2K::PictureDescriptorDump(PDesc);
733 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
735 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
736 Kumu::GenRandomUUID(Info.AssetUUID);
738 if ( Options.use_smpte_labels )
740 Info.LabelSetType = LS_MXF_SMPTE;
741 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
744 // configure encryption
745 if( Options.key_flag )
747 Kumu::GenRandomUUID(Info.ContextID);
748 Info.EncryptedEssence = true;
750 if ( Options.key_id_flag )
751 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
753 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
755 Context = new AESEncContext;
756 result = Context->InitKey(Options.key_value);
758 if ( ASDCP_SUCCESS(result) )
759 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
761 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
763 Info.UsesHMAC = true;
764 HMAC = new HMACContext;
765 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
769 if ( ASDCP_SUCCESS(result) )
770 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
773 if ( ASDCP_SUCCESS(result) )
776 result = ParserLeft.Reset();
777 if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
779 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
781 result = ParserLeft.ReadFrame(FrameBuffer);
783 if ( ASDCP_SUCCESS(result) )
785 if ( Options.verbose_flag )
786 FrameBuffer.Dump(stderr, Options.fb_dump_size);
788 if ( Options.encrypt_header_flag )
789 FrameBuffer.PlaintextOffset(0);
792 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
793 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
795 if ( ASDCP_SUCCESS(result) )
796 result = ParserRight.ReadFrame(FrameBuffer);
798 if ( ASDCP_SUCCESS(result) )
800 if ( Options.verbose_flag )
801 FrameBuffer.Dump(stderr, Options.fb_dump_size);
803 if ( Options.encrypt_header_flag )
804 FrameBuffer.PlaintextOffset(0);
807 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
808 result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
811 if ( result == RESULT_ENDOFFILE )
815 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
816 result = Writer.Finalize();
821 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
822 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
823 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
825 read_JP2K_S_file(CommandOptions& Options)
827 AESDecContext* Context = 0;
828 HMACContext* HMAC = 0;
829 JP2K::MXFSReader Reader;
830 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
831 ui32_t frame_count = 0;
833 Result_t result = Reader.OpenRead(Options.filenames[0]);
835 if ( ASDCP_SUCCESS(result) )
837 JP2K::PictureDescriptor PDesc;
838 Reader.FillPictureDescriptor(PDesc);
840 frame_count = PDesc.ContainerDuration;
842 if ( Options.verbose_flag )
844 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
845 JP2K::PictureDescriptorDump(PDesc);
849 if ( ASDCP_SUCCESS(result) && Options.key_flag )
851 Context = new AESDecContext;
852 result = Context->InitKey(Options.key_value);
854 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
857 Reader.FillWriterInfo(Info);
861 HMAC = new HMACContext;
862 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
866 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
871 const int filename_max = 1024;
872 char filename[filename_max];
873 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
874 if ( last_frame > frame_count )
875 last_frame = frame_count;
877 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
879 result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
881 if ( ASDCP_SUCCESS(result) )
883 Kumu::FileWriter OutFile;
885 snprintf(filename, filename_max, "%s%06uL.j2c", Options.file_root, i);
886 result = OutFile.OpenWrite(filename);
888 if ( ASDCP_SUCCESS(result) )
889 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
891 if ( Options.verbose_flag )
892 FrameBuffer.Dump(stderr, Options.fb_dump_size);
895 if ( ASDCP_SUCCESS(result) )
896 result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
898 if ( ASDCP_SUCCESS(result) )
900 Kumu::FileWriter OutFile;
902 snprintf(filename, filename_max, "%s%06uR.j2c", Options.file_root, i);
903 result = OutFile.OpenWrite(filename);
905 if ( ASDCP_SUCCESS(result) )
906 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
915 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
916 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
919 write_JP2K_file(CommandOptions& Options)
921 AESEncContext* Context = 0;
922 HMACContext* HMAC = 0;
923 JP2K::MXFWriter Writer;
924 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
925 JP2K::PictureDescriptor PDesc;
926 JP2K::SequenceParser Parser;
927 byte_t IV_buf[CBC_BLOCK_SIZE];
928 Kumu::FortunaRNG RNG;
930 // set up essence parser
931 Result_t result = Parser.OpenRead(Options.filenames[0]);
934 if ( ASDCP_SUCCESS(result) )
936 Parser.FillPictureDescriptor(PDesc);
937 PDesc.EditRate = Options.PictureRate();
939 if ( Options.verbose_flag )
941 fprintf(stderr, "JPEG 2000 pictures\n");
942 fputs("PictureDescriptor:\n", stderr);
943 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
944 JP2K::PictureDescriptorDump(PDesc);
948 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
950 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
951 Kumu::GenRandomUUID(Info.AssetUUID);
953 if ( Options.use_smpte_labels )
955 Info.LabelSetType = LS_MXF_SMPTE;
956 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
959 // configure encryption
960 if( Options.key_flag )
962 Kumu::GenRandomUUID(Info.ContextID);
963 Info.EncryptedEssence = true;
965 if ( Options.key_id_flag )
966 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
968 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
970 Context = new AESEncContext;
971 result = Context->InitKey(Options.key_value);
973 if ( ASDCP_SUCCESS(result) )
974 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
976 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
978 Info.UsesHMAC = true;
979 HMAC = new HMACContext;
980 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
984 if ( ASDCP_SUCCESS(result) )
985 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
988 if ( ASDCP_SUCCESS(result) )
991 result = Parser.Reset();
993 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
995 if ( ! Options.do_repeat || duration == 1 )
997 result = Parser.ReadFrame(FrameBuffer);
999 if ( ASDCP_SUCCESS(result) )
1001 if ( Options.verbose_flag )
1002 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1004 if ( Options.encrypt_header_flag )
1005 FrameBuffer.PlaintextOffset(0);
1009 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1011 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1013 // The Writer class will forward the last block of ciphertext
1014 // to the encryption context for use as the IV for the next
1015 // frame. If you want to use non-sequitur IV values, un-comment
1016 // the following line of code.
1017 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1018 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1022 if ( result == RESULT_ENDOFFILE )
1026 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1027 result = Writer.Finalize();
1032 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1033 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1034 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1037 read_JP2K_file(CommandOptions& Options)
1039 AESDecContext* Context = 0;
1040 HMACContext* HMAC = 0;
1041 JP2K::MXFReader Reader;
1042 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
1043 ui32_t frame_count = 0;
1045 Result_t result = Reader.OpenRead(Options.filenames[0]);
1047 if ( ASDCP_SUCCESS(result) )
1049 JP2K::PictureDescriptor PDesc;
1050 Reader.FillPictureDescriptor(PDesc);
1052 frame_count = PDesc.ContainerDuration;
1054 if ( Options.verbose_flag )
1056 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1057 JP2K::PictureDescriptorDump(PDesc);
1061 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1063 Context = new AESDecContext;
1064 result = Context->InitKey(Options.key_value);
1066 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1069 Reader.FillWriterInfo(Info);
1071 if ( Info.UsesHMAC )
1073 HMAC = new HMACContext;
1074 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1078 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1083 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1084 if ( last_frame > frame_count )
1085 last_frame = frame_count;
1087 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1089 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1091 if ( ASDCP_SUCCESS(result) )
1093 Kumu::FileWriter OutFile;
1096 snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
1097 result = OutFile.OpenWrite(filename);
1099 if ( ASDCP_SUCCESS(result) )
1100 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1102 if ( Options.verbose_flag )
1103 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1110 //------------------------------------------------------------------------------------------
1114 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1115 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1118 write_PCM_file(CommandOptions& Options)
1120 AESEncContext* Context = 0;
1121 HMACContext* HMAC = 0;
1122 PCMParserList Parser;
1123 PCM::MXFWriter Writer;
1124 PCM::FrameBuffer FrameBuffer;
1125 PCM::AudioDescriptor ADesc;
1126 Rational PictureRate = Options.PictureRate();
1127 byte_t IV_buf[CBC_BLOCK_SIZE];
1128 Kumu::FortunaRNG RNG;
1130 // set up essence parser
1131 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1133 // set up MXF writer
1134 if ( ASDCP_SUCCESS(result) )
1136 Parser.FillAudioDescriptor(ADesc);
1138 ADesc.SampleRate = PictureRate;
1139 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1141 if ( Options.verbose_flag )
1143 fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
1144 Options.szPictureRate(),
1145 PCM::CalcSamplesPerFrame(ADesc));
1146 fputs("AudioDescriptor:\n", stderr);
1147 PCM::AudioDescriptorDump(ADesc);
1151 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1153 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1154 Kumu::GenRandomUUID(Info.AssetUUID);
1156 if ( Options.use_smpte_labels )
1158 Info.LabelSetType = LS_MXF_SMPTE;
1159 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1162 // configure encryption
1163 if( Options.key_flag )
1165 Kumu::GenRandomUUID(Info.ContextID);
1166 Info.EncryptedEssence = true;
1168 if ( Options.key_id_flag )
1169 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1171 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1173 Context = new AESEncContext;
1174 result = Context->InitKey(Options.key_value);
1176 if ( ASDCP_SUCCESS(result) )
1177 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1179 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1181 Info.UsesHMAC = true;
1182 HMAC = new HMACContext;
1183 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1187 if ( ASDCP_SUCCESS(result) )
1188 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1191 if ( ASDCP_SUCCESS(result) )
1193 result = Parser.Reset();
1194 ui32_t duration = 0;
1196 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1198 result = Parser.ReadFrame(FrameBuffer);
1200 if ( ASDCP_SUCCESS(result) )
1202 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1204 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1205 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1206 result = RESULT_ENDOFFILE;
1210 if ( Options.verbose_flag )
1211 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1213 if ( ! Options.no_write_flag )
1215 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1217 // The Writer class will forward the last block of ciphertext
1218 // to the encryption context for use as the IV for the next
1219 // frame. If you want to use non-sequitur IV values, un-comment
1220 // the following line of code.
1221 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1222 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1227 if ( result == RESULT_ENDOFFILE )
1231 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1232 result = Writer.Finalize();
1237 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1238 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1239 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1242 read_PCM_file(CommandOptions& Options)
1244 AESDecContext* Context = 0;
1245 HMACContext* HMAC = 0;
1246 PCM::MXFReader Reader;
1247 PCM::FrameBuffer FrameBuffer;
1248 WavFileWriter OutWave;
1249 PCM::AudioDescriptor ADesc;
1250 ui32_t last_frame = 0;
1252 Result_t result = Reader.OpenRead(Options.filenames[0]);
1254 if ( ASDCP_SUCCESS(result) )
1256 Reader.FillAudioDescriptor(ADesc);
1258 if ( ADesc.SampleRate != EditRate_23_98
1259 && ADesc.SampleRate != EditRate_24
1260 && ADesc.SampleRate != EditRate_48 )
1261 ADesc.SampleRate = Options.PictureRate();
1263 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1265 if ( Options.verbose_flag )
1266 PCM::AudioDescriptorDump(ADesc);
1269 if ( ASDCP_SUCCESS(result) )
1271 last_frame = ADesc.ContainerDuration;
1273 if ( Options.duration > 0 && Options.duration < last_frame )
1274 last_frame = Options.duration;
1276 if ( Options.start_frame > 0 )
1278 if ( Options.start_frame > ADesc.ContainerDuration )
1280 fprintf(stderr, "Start value greater than file duration.\n");
1284 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1287 ADesc.ContainerDuration = last_frame - Options.start_frame;
1288 OutWave.OpenWrite(ADesc, Options.file_root,
1289 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1290 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1293 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1295 Context = new AESDecContext;
1296 result = Context->InitKey(Options.key_value);
1298 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1301 Reader.FillWriterInfo(Info);
1303 if ( Info.UsesHMAC )
1305 HMAC = new HMACContext;
1306 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1310 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1315 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1317 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1319 if ( ASDCP_SUCCESS(result) )
1321 if ( Options.verbose_flag )
1322 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1324 result = OutWave.WriteFrame(FrameBuffer);
1332 //------------------------------------------------------------------------------------------
1333 // TimedText essence
1336 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1337 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1340 write_timed_text_file(CommandOptions& Options)
1342 AESEncContext* Context = 0;
1343 HMACContext* HMAC = 0;
1344 TimedText::DCSubtitleParser Parser;
1345 TimedText::MXFWriter Writer;
1346 TimedText::FrameBuffer FrameBuffer;
1347 TimedText::TimedTextDescriptor TDesc;
1348 byte_t IV_buf[CBC_BLOCK_SIZE];
1349 Kumu::FortunaRNG RNG;
1351 // set up essence parser
1352 Result_t result = Parser.OpenRead(Options.filenames[0]);
1354 // set up MXF writer
1355 if ( ASDCP_SUCCESS(result) )
1357 Parser.FillDescriptor(TDesc);
1358 FrameBuffer.Capacity(2*Kumu::Megabyte);
1360 if ( Options.verbose_flag )
1362 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1363 TimedText::DescriptorDump(TDesc);
1367 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1369 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1370 Kumu::GenRandomUUID(Info.AssetUUID);
1372 if ( Options.use_smpte_labels )
1374 Info.LabelSetType = LS_MXF_SMPTE;
1375 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1378 // configure encryption
1379 if( Options.key_flag )
1381 Kumu::GenRandomUUID(Info.ContextID);
1382 Info.EncryptedEssence = true;
1384 if ( Options.key_id_flag )
1385 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1387 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1389 Context = new AESEncContext;
1390 result = Context->InitKey(Options.key_value);
1392 if ( ASDCP_SUCCESS(result) )
1393 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1395 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1397 Info.UsesHMAC = true;
1398 HMAC = new HMACContext;
1399 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1403 if ( ASDCP_SUCCESS(result) )
1404 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1407 if ( ASDCP_FAILURE(result) )
1411 TimedText::ResourceList_t::const_iterator ri;
1413 result = Parser.ReadTimedTextResource(XMLDoc);
1415 if ( ASDCP_SUCCESS(result) )
1416 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1418 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1420 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1422 if ( ASDCP_SUCCESS(result) )
1424 if ( Options.verbose_flag )
1425 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1427 if ( ! Options.no_write_flag )
1429 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1431 // The Writer class will forward the last block of ciphertext
1432 // to the encryption context for use as the IV for the next
1433 // frame. If you want to use non-sequitur IV values, un-comment
1434 // the following line of code.
1435 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1436 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1440 if ( result == RESULT_ENDOFFILE )
1444 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1445 result = Writer.Finalize();
1451 // Read one or more timed text streams from a plaintext ASDCP file
1452 // Read one or more timed text streams from a ciphertext ASDCP file
1453 // Read one or more timed text streams from a ciphertext ASDCP file
1456 read_timed_text_file(CommandOptions& Options)
1458 AESDecContext* Context = 0;
1459 HMACContext* HMAC = 0;
1460 TimedText::MXFReader Reader;
1461 TimedText::FrameBuffer FrameBuffer;
1462 TimedText::TimedTextDescriptor TDesc;
1464 Result_t result = Reader.OpenRead(Options.filenames[0]);
1466 if ( ASDCP_SUCCESS(result) )
1468 Reader.FillDescriptor(TDesc);
1469 FrameBuffer.Capacity(2*Kumu::Megabyte);
1471 if ( Options.verbose_flag )
1472 TimedText::DescriptorDump(TDesc);
1475 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1477 Context = new AESDecContext;
1478 result = Context->InitKey(Options.key_value);
1480 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1483 Reader.FillWriterInfo(Info);
1485 if ( Info.UsesHMAC )
1487 HMAC = new HMACContext;
1488 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1492 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1497 if ( ASDCP_FAILURE(result) )
1501 TimedText::ResourceList_t::const_iterator ri;
1503 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1505 // do something with the XML here
1506 fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1508 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1510 result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1512 if ( ASDCP_SUCCESS(result) )
1514 // if ( Options.verbose_flag )
1515 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1517 // do something with the resource data here
1524 //------------------------------------------------------------------------------------------
1528 // These classes wrap the irregular names in the asdcplib API
1529 // so that I can use a template to simplify the implementation
1530 // of show_file_info()
1532 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1535 void FillDescriptor(MPEG2::MXFReader& Reader) {
1536 Reader.FillVideoDescriptor(*this);
1539 void Dump(FILE* stream) {
1540 MPEG2::VideoDescriptorDump(*this, stream);
1544 class MyPictureDescriptor : public JP2K::PictureDescriptor
1547 void FillDescriptor(JP2K::MXFReader& Reader) {
1548 Reader.FillPictureDescriptor(*this);
1551 void Dump(FILE* stream) {
1552 JP2K::PictureDescriptorDump(*this, stream);
1556 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1559 void FillDescriptor(JP2K::MXFSReader& Reader) {
1560 Reader.FillPictureDescriptor(*this);
1563 void Dump(FILE* stream) {
1564 JP2K::PictureDescriptorDump(*this, stream);
1568 class MyAudioDescriptor : public PCM::AudioDescriptor
1571 void FillDescriptor(PCM::MXFReader& Reader) {
1572 Reader.FillAudioDescriptor(*this);
1575 void Dump(FILE* stream) {
1576 PCM::AudioDescriptorDump(*this, stream);
1580 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1583 void FillDescriptor(TimedText::MXFReader& Reader) {
1584 Reader.FillDescriptor(*this);
1587 void Dump(FILE* stream) {
1588 TimedText::DescriptorDump(*this, stream);
1592 // MSVC didn't like the function template, so now it's a static class method
1593 template<class ReaderT, class DescriptorT>
1594 class FileInfoWrapper
1598 file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1600 assert(type_string);
1604 Result_t result = RESULT_OK;
1606 if ( Options.verbose_flag || Options.showheader_flag )
1609 result = Reader.OpenRead(Options.filenames[0]);
1611 if ( ASDCP_SUCCESS(result) )
1613 fprintf(stdout, "File essence type is %s.\n", type_string);
1615 if ( Options.showheader_flag )
1616 Reader.DumpHeaderMetadata(stream);
1619 Reader.FillWriterInfo(WI);
1620 WriterInfoDump(WI, stream);
1623 Desc.FillDescriptor(Reader);
1626 if ( Options.showindex_flag )
1627 Reader.DumpIndex(stream);
1629 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1631 Reader.DumpHeaderMetadata(stream);
1639 // Read header metadata from an ASDCP file
1642 show_file_info(CommandOptions& Options)
1644 EssenceType_t EssenceType;
1645 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1647 if ( ASDCP_FAILURE(result) )
1650 if ( EssenceType == ESS_MPEG2_VES )
1651 result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1653 else if ( EssenceType == ESS_PCM_24b_48k )
1654 result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1656 else if ( EssenceType == ESS_JPEG_2000 )
1658 if ( Options.stereo_image_flag )
1659 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1660 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1663 result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1664 MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1666 else if ( EssenceType == ESS_JPEG_2000_S )
1667 result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1668 MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1670 else if ( EssenceType == ESS_TIMED_TEXT )
1671 result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1675 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1676 Kumu::FileReader Reader;
1677 MXF::OPAtomHeader TestHeader;
1679 result = Reader.OpenRead(Options.filenames[0]);
1681 if ( ASDCP_SUCCESS(result) )
1682 result = TestHeader.InitFromFile(Reader); // test UL and OP
1684 if ( ASDCP_SUCCESS(result) )
1686 TestHeader.Partition::Dump();
1688 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1691 fputs("File contains no Identification object.\n", stdout);
1693 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1696 fputs("File contains no SourcePackage object.\n", stdout);
1700 fputs("File is not MXF.\n", stdout);
1710 digest_file(const char* filename)
1712 using namespace Kumu;
1714 ASDCP_TEST_NULL_STR(filename);
1718 ByteString Buf(8192);
1720 Result_t result = Reader.OpenRead(filename);
1722 while ( ASDCP_SUCCESS(result) )
1724 ui32_t read_count = 0;
1725 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1727 if ( result == RESULT_ENDOFFILE )
1733 if ( ASDCP_SUCCESS(result) )
1734 SHA1_Update(&Ctx, Buf.Data(), read_count);
1737 if ( ASDCP_SUCCESS(result) )
1739 const ui32_t sha_len = 20;
1740 byte_t bin_buf[sha_len];
1742 SHA1_Final(bin_buf, &Ctx);
1744 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1752 main(int argc, const char** argv)
1754 Result_t result = RESULT_OK;
1755 CommandOptions Options(argc, argv);
1757 if ( Options.version_flag )
1760 if ( Options.help_flag )
1763 if ( Options.version_flag || Options.help_flag )
1766 if ( Options.error_flag )
1768 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1772 if ( Options.mode == MMT_INFO )
1774 result = show_file_info(Options);
1776 else if ( Options.mode == MMT_GOP_START )
1778 result = gop_start_test(Options);
1780 else if ( Options.mode == MMT_GEN_KEY )
1782 Kumu::FortunaRNG RNG;
1783 byte_t bin_buf[KeyLen];
1786 RNG.FillRandom(bin_buf, KeyLen);
1787 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1789 else if ( Options.mode == MMT_GEN_ID )
1792 Kumu::GenRandomValue(TmpID);
1794 printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1796 else if ( Options.mode == MMT_DIGEST )
1798 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1799 result = digest_file(Options.filenames[i]);
1801 else if ( Options.mode == MMT_EXTRACT )
1803 EssenceType_t EssenceType;
1804 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1806 if ( ASDCP_SUCCESS(result) )
1808 switch ( EssenceType )
1811 result = read_MPEG2_file(Options);
1815 if ( Options.stereo_image_flag )
1816 result = read_JP2K_S_file(Options);
1818 result = read_JP2K_file(Options);
1821 case ESS_JPEG_2000_S:
1822 result = read_JP2K_S_file(Options);
1825 case ESS_PCM_24b_48k:
1826 result = read_PCM_file(Options);
1829 case ESS_TIMED_TEXT:
1830 result = read_timed_text_file(Options);
1834 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1839 else if ( Options.mode == MMT_CREATE )
1841 if ( Options.do_repeat && ! Options.duration_flag )
1843 fputs("Option -R requires -d <duration>\n", stderr);
1847 EssenceType_t EssenceType;
1848 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1850 if ( ASDCP_SUCCESS(result) )
1852 switch ( EssenceType )
1855 result = write_MPEG2_file(Options);
1859 if ( Options.stereo_image_flag )
1860 result = write_JP2K_S_file(Options);
1863 result = write_JP2K_file(Options);
1867 case ESS_PCM_24b_48k:
1868 result = write_PCM_file(Options);
1871 case ESS_TIMED_TEXT:
1872 result = write_timed_text_file(Options);
1876 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1877 Options.filenames[0]);
1883 if ( ASDCP_FAILURE(result) )
1885 fputs("Program stopped on error.\n", stderr);
1887 if ( result == RESULT_SFORMAT )
1889 fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
1891 else if ( result != RESULT_FAIL )
1893 fputs(result, stderr);
1894 fputc('\n', stderr);
1905 // end asdcp-test.cpp