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>
60 #ifdef ASDCP_WITH_TIMED_TEXT
61 #include <AS_DCP_TimedText.h>
64 #include <openssl/sha.h>
66 using namespace ASDCP;
68 const ui32_t FRAME_BUFFER_SIZE = 4*1024*1024;
70 //------------------------------------------------------------------------------------------
72 // command line option parser class
74 static const char* PACKAGE = "asdcp-test"; // program name for messages
75 const ui32_t MAX_IN_FILES = 16; // maximum number of input files handled by
76 // the command option parser
78 // local program identification info written to file headers
79 class MyInfo : public WriterInfo
84 static byte_t default_ProductUUID_Data[UUIDlen] =
85 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
86 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
88 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
89 CompanyName = "WidgetCo";
90 ProductName = "asdcp-test";
93 snprintf(s_buf, 128, "%u.%u.%u", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
94 ProductVersion = s_buf;
100 // Increment the iterator, test for an additional non-option command line argument.
101 // Causes the caller to return if there are no remaining arguments or if the next
102 // argument begins with '-'.
103 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
105 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
110 banner(FILE* stream = stdout)
113 %s (asdcplib %s)\n\n\
114 Copyright (c) 2003-2006 John Hurst\n\n\
115 asdcplib may be copied only under the terms of the license found at\n\
116 the top of every file in the asdcplib distribution kit.\n\n\
117 Specify the -h (help) option for further information about %s\n\n",
118 PACKAGE, ASDCP::Version(), PACKAGE);
123 usage(FILE* stream = stdout)
126 USAGE: %s -c <output-file> [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
127 [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
128 [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
129 <input-file> [<input-file2> ...]\n\
131 %s [-h|-help] [-V]\n\
133 %s -i [-H] [-n] [-v] <input-file>\n\
137 %s -G [-v] <input-file>\n\
139 %s -t <input-file>\n\
141 %s -x <file-prefix> [-b <buffer-size>] [-d <duration>]\n\
142 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
143 [-v] [-W] <input-file>\n\
144 \n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
148 -c <output-file> - Create 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 ui32_t start_frame; // frame number to begin processing
242 ui32_t duration; // number of frames to be processed
243 bool duration_flag; // true if duration argument given
244 bool do_repeat; // if true and -c -d, repeat first input frame
245 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
246 ui32_t picture_rate; // fps of picture when wrapping PCM
247 ui32_t fb_size; // size of picture frame buffer
248 ui32_t file_count; // number of elements in filenames[]
249 const char* file_root; // filename pre for files written by the extract mode
250 const char* out_file; // name of mxf file created by create mode
251 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
252 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
253 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
256 Rational PictureRate()
258 if ( picture_rate == 23 ) return EditRate_23_98;
259 if ( picture_rate == 48 ) return EditRate_48;
264 const char* szPictureRate()
266 if ( picture_rate == 23 ) return "23.976";
267 if ( picture_rate == 48 ) return "48";
272 CommandOptions(int argc, const char** argv) :
273 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
274 write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
275 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
276 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
277 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
278 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
280 memset(key_value, 0, KeyLen);
281 memset(key_id_value, 0, UUIDlen);
283 for ( int i = 1; i < argc; i++ )
286 if ( (strcmp( argv[i], "-help") == 0) )
292 if ( argv[i][0] == '-'
293 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
296 switch ( argv[i][1] )
298 case '1': mono_wav = true; break;
299 case '2': split_wav = true; break;
300 case 'i': mode = MMT_INFO; break;
301 case 'G': mode = MMT_GOP_START; break;
302 case 'W': no_write_flag = true; break;
303 case 'n': showindex_flag = true; break;
304 case 'H': showheader_flag = true; break;
305 case 'R': do_repeat = true; break;
306 case 'S': split_wav = true; break;
307 case 'V': version_flag = true; break;
308 case 'h': help_flag = true; break;
309 case 'v': verbose_flag = true; break;
310 case 'g': mode = MMT_GEN_KEY; break;
311 case 'u': mode = MMT_GEN_ID; break;
312 case 'e': encrypt_header_flag = true; break;
313 case 'E': encrypt_header_flag = false; break;
314 case 'M': write_hmac = false; break;
315 case 'm': read_hmac = true; break;
316 case 'L': use_smpte_labels = true; break;
319 TEST_EXTRA_ARG(i, 'c');
325 TEST_EXTRA_ARG(i, 'x');
330 case 'k': key_flag = true;
331 TEST_EXTRA_ARG(i, 'k');
334 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
336 if ( length != KeyLen )
338 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
344 case 'j': key_id_flag = true;
345 TEST_EXTRA_ARG(i, 'j');
348 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
350 if ( length != UUIDlen )
352 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
359 TEST_EXTRA_ARG(i, 'f');
360 start_frame = abs(atoi(argv[i]));
364 TEST_EXTRA_ARG(i, 'd');
365 duration_flag = true;
366 duration = abs(atoi(argv[i]));
370 TEST_EXTRA_ARG(i, 'p');
371 picture_rate = abs(atoi(argv[i]));
375 TEST_EXTRA_ARG(i, 's');
376 fb_dump_size = abs(atoi(argv[i]));
379 case 't': mode = MMT_DIGEST; break;
382 TEST_EXTRA_ARG(i, 'b');
383 fb_size = abs(atoi(argv[i]));
386 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
391 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
398 if ( argv[i][0] != '-' )
400 filenames[file_count++] = argv[i];
404 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
408 if ( file_count >= MAX_IN_FILES )
410 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
416 if ( help_flag || version_flag )
419 if ( ( mode == MMT_INFO
420 || mode == MMT_CREATE
421 || mode == MMT_EXTRACT
422 || mode == MMT_GOP_START
423 || mode == MMT_DIGEST ) && file_count == 0 )
425 fputs("Option requires at least one filename argument.\n", stderr);
429 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
431 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
439 //------------------------------------------------------------------------------------------
442 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
443 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
446 write_MPEG2_file(CommandOptions& Options)
448 AESEncContext* Context = 0;
449 HMACContext* HMAC = 0;
450 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
451 MPEG2::Parser Parser;
452 MPEG2::MXFWriter Writer;
453 MPEG2::VideoDescriptor VDesc;
454 byte_t IV_buf[CBC_BLOCK_SIZE];
455 Kumu::FortunaRNG RNG;
457 // set up essence parser
458 Result_t result = Parser.OpenRead(Options.filenames[0]);
461 if ( ASDCP_SUCCESS(result) )
463 Parser.FillVideoDescriptor(VDesc);
465 if ( Options.verbose_flag )
467 fputs("MPEG-2 Pictures\n", stderr);
468 fputs("VideoDescriptor:\n", stderr);
469 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
470 MPEG2::VideoDescriptorDump(VDesc);
474 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
476 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
477 Kumu::GenRandomUUID(Info.AssetUUID);
479 if ( Options.use_smpte_labels )
481 Info.LabelSetType = LS_MXF_SMPTE;
482 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
485 // configure encryption
486 if( Options.key_flag )
488 Kumu::GenRandomUUID(Info.ContextID);
489 Info.EncryptedEssence = true;
491 if ( Options.key_id_flag )
492 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
494 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
496 Context = new AESEncContext;
497 result = Context->InitKey(Options.key_value);
499 if ( ASDCP_SUCCESS(result) )
500 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
502 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
504 Info.UsesHMAC = true;
505 HMAC = new HMACContext;
506 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
510 if ( ASDCP_SUCCESS(result) )
511 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
514 if ( ASDCP_SUCCESS(result) )
515 // loop through the frames
517 result = Parser.Reset();
520 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
522 if ( ! Options.do_repeat || duration == 1 )
524 result = Parser.ReadFrame(FrameBuffer);
526 if ( ASDCP_SUCCESS(result) )
528 if ( Options.verbose_flag )
529 FrameBuffer.Dump(stderr, Options.fb_dump_size);
531 if ( Options.encrypt_header_flag )
532 FrameBuffer.PlaintextOffset(0);
536 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
538 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
540 // The Writer class will forward the last block of ciphertext
541 // to the encryption context for use as the IV for the next
542 // frame. If you want to use non-sequitur IV values, un-comment
543 // the following line of code.
544 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
545 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
549 if ( result == RESULT_ENDOFFILE )
553 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
554 result = Writer.Finalize();
559 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
560 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
561 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
564 read_MPEG2_file(CommandOptions& Options)
566 AESDecContext* Context = 0;
567 HMACContext* HMAC = 0;
568 MPEG2::MXFReader Reader;
569 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
570 Kumu::FileWriter OutFile;
571 ui32_t frame_count = 0;
573 Result_t result = Reader.OpenRead(Options.filenames[0]);
575 if ( ASDCP_SUCCESS(result) )
577 MPEG2::VideoDescriptor VDesc;
578 Reader.FillVideoDescriptor(VDesc);
579 frame_count = VDesc.ContainerDuration;
581 if ( Options.verbose_flag )
583 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
584 MPEG2::VideoDescriptorDump(VDesc);
588 if ( ASDCP_SUCCESS(result) )
591 snprintf(filename, 256, "%s.ves", Options.file_root);
592 result = OutFile.OpenWrite(filename);
595 if ( ASDCP_SUCCESS(result) && Options.key_flag )
597 Context = new AESDecContext;
598 result = Context->InitKey(Options.key_value);
600 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
603 Reader.FillWriterInfo(Info);
607 HMAC = new HMACContext;
608 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
612 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
617 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
618 if ( last_frame > frame_count )
619 last_frame = frame_count;
621 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
623 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
625 if ( ASDCP_SUCCESS(result) )
627 if ( Options.verbose_flag )
628 FrameBuffer.Dump(stderr, Options.fb_dump_size);
630 ui32_t write_count = 0;
631 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
641 gop_start_test(CommandOptions& Options)
643 using namespace ASDCP::MPEG2;
646 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
647 ui32_t frame_count = 0;
649 Result_t result = Reader.OpenRead(Options.filenames[0]);
651 if ( ASDCP_SUCCESS(result) )
653 MPEG2::VideoDescriptor VDesc;
654 Reader.FillVideoDescriptor(VDesc);
655 frame_count = VDesc.ContainerDuration;
657 if ( Options.verbose_flag )
659 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
660 MPEG2::VideoDescriptorDump(VDesc);
664 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
665 if ( last_frame > frame_count )
666 last_frame = frame_count;
668 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
670 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
672 if ( ASDCP_SUCCESS(result) )
674 if ( Options.verbose_flag )
675 FrameBuffer.Dump(stderr, Options.fb_dump_size);
677 if ( FrameBuffer.FrameType() != FRAME_I )
678 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
680 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
687 //------------------------------------------------------------------------------------------
690 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
691 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
694 write_JP2K_file(CommandOptions& Options)
696 AESEncContext* Context = 0;
697 HMACContext* HMAC = 0;
698 JP2K::MXFWriter Writer;
699 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
700 JP2K::PictureDescriptor PDesc;
701 JP2K::SequenceParser Parser;
702 byte_t IV_buf[CBC_BLOCK_SIZE];
703 Kumu::FortunaRNG RNG;
705 // set up essence parser
706 Result_t result = Parser.OpenRead(Options.filenames[0]);
709 if ( ASDCP_SUCCESS(result) )
711 Parser.FillPictureDescriptor(PDesc);
712 PDesc.EditRate = Options.PictureRate();
714 if ( Options.verbose_flag )
716 fprintf(stderr, "JPEG 2000 pictures\n");
717 fputs("PictureDescriptor:\n", stderr);
718 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
719 JP2K::PictureDescriptorDump(PDesc);
723 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
725 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
726 Kumu::GenRandomUUID(Info.AssetUUID);
728 if ( Options.use_smpte_labels )
730 Info.LabelSetType = LS_MXF_SMPTE;
731 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
734 // configure encryption
735 if( Options.key_flag )
737 Kumu::GenRandomUUID(Info.ContextID);
738 Info.EncryptedEssence = true;
740 if ( Options.key_id_flag )
741 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
743 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
745 Context = new AESEncContext;
746 result = Context->InitKey(Options.key_value);
748 if ( ASDCP_SUCCESS(result) )
749 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
751 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
753 Info.UsesHMAC = true;
754 HMAC = new HMACContext;
755 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
759 if ( ASDCP_SUCCESS(result) )
760 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
763 if ( ASDCP_SUCCESS(result) )
766 result = Parser.Reset();
768 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
770 if ( ! Options.do_repeat || duration == 1 )
772 result = Parser.ReadFrame(FrameBuffer);
774 if ( ASDCP_SUCCESS(result) )
776 if ( Options.verbose_flag )
777 FrameBuffer.Dump(stderr, Options.fb_dump_size);
779 if ( Options.encrypt_header_flag )
780 FrameBuffer.PlaintextOffset(0);
784 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
786 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
788 // The Writer class will forward the last block of ciphertext
789 // to the encryption context for use as the IV for the next
790 // frame. If you want to use non-sequitur IV values, un-comment
791 // the following line of code.
792 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
793 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
797 if ( result == RESULT_ENDOFFILE )
801 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
802 result = Writer.Finalize();
807 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
808 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
809 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
812 read_JP2K_file(CommandOptions& Options)
814 AESDecContext* Context = 0;
815 HMACContext* HMAC = 0;
816 JP2K::MXFReader Reader;
817 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
818 ui32_t frame_count = 0;
820 Result_t result = Reader.OpenRead(Options.filenames[0]);
822 if ( ASDCP_SUCCESS(result) )
824 JP2K::PictureDescriptor PDesc;
825 Reader.FillPictureDescriptor(PDesc);
827 frame_count = PDesc.ContainerDuration;
829 if ( Options.verbose_flag )
831 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
832 JP2K::PictureDescriptorDump(PDesc);
836 if ( ASDCP_SUCCESS(result) && Options.key_flag )
838 Context = new AESDecContext;
839 result = Context->InitKey(Options.key_value);
841 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
844 Reader.FillWriterInfo(Info);
848 HMAC = new HMACContext;
849 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
853 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
858 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
859 if ( last_frame > frame_count )
860 last_frame = frame_count;
862 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
864 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
866 if ( ASDCP_SUCCESS(result) )
868 Kumu::FileWriter OutFile;
871 snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
872 result = OutFile.OpenWrite(filename);
874 if ( ASDCP_SUCCESS(result) )
875 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
877 if ( Options.verbose_flag )
878 FrameBuffer.Dump(stderr, Options.fb_dump_size);
885 //------------------------------------------------------------------------------------------
889 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
890 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
893 write_PCM_file(CommandOptions& Options)
895 AESEncContext* Context = 0;
896 HMACContext* HMAC = 0;
897 PCMParserList Parser;
898 PCM::MXFWriter Writer;
899 PCM::FrameBuffer FrameBuffer;
900 PCM::AudioDescriptor ADesc;
901 Rational PictureRate = Options.PictureRate();
902 byte_t IV_buf[CBC_BLOCK_SIZE];
903 Kumu::FortunaRNG RNG;
905 // set up essence parser
906 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
909 if ( ASDCP_SUCCESS(result) )
911 Parser.FillAudioDescriptor(ADesc);
913 ADesc.SampleRate = PictureRate;
914 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
916 if ( Options.verbose_flag )
918 fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
919 Options.szPictureRate(),
920 PCM::CalcSamplesPerFrame(ADesc));
921 fputs("AudioDescriptor:\n", stderr);
922 PCM::AudioDescriptorDump(ADesc);
926 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
928 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
929 Kumu::GenRandomUUID(Info.AssetUUID);
931 if ( Options.use_smpte_labels )
933 Info.LabelSetType = LS_MXF_SMPTE;
934 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
937 // configure encryption
938 if( Options.key_flag )
940 Kumu::GenRandomUUID(Info.ContextID);
941 Info.EncryptedEssence = true;
943 if ( Options.key_id_flag )
944 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
946 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
948 Context = new AESEncContext;
949 result = Context->InitKey(Options.key_value);
951 if ( ASDCP_SUCCESS(result) )
952 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
954 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
956 Info.UsesHMAC = true;
957 HMAC = new HMACContext;
958 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
962 if ( ASDCP_SUCCESS(result) )
963 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
966 if ( ASDCP_SUCCESS(result) )
968 result = Parser.Reset();
971 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
973 result = Parser.ReadFrame(FrameBuffer);
975 if ( ASDCP_SUCCESS(result) )
977 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
979 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
980 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
981 result = RESULT_ENDOFFILE;
985 if ( Options.verbose_flag )
986 FrameBuffer.Dump(stderr, Options.fb_dump_size);
988 if ( ! Options.no_write_flag )
990 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
992 // The Writer class will forward the last block of ciphertext
993 // to the encryption context for use as the IV for the next
994 // frame. If you want to use non-sequitur IV values, un-comment
995 // the following line of code.
996 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
997 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1002 if ( result == RESULT_ENDOFFILE )
1006 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1007 result = Writer.Finalize();
1012 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1013 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1014 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1017 read_PCM_file(CommandOptions& Options)
1019 AESDecContext* Context = 0;
1020 HMACContext* HMAC = 0;
1021 PCM::MXFReader Reader;
1022 PCM::FrameBuffer FrameBuffer;
1023 WavFileWriter OutWave;
1024 PCM::AudioDescriptor ADesc;
1025 ui32_t last_frame = 0;
1027 Result_t result = Reader.OpenRead(Options.filenames[0]);
1029 if ( ASDCP_SUCCESS(result) )
1031 Reader.FillAudioDescriptor(ADesc);
1033 if ( ADesc.SampleRate != EditRate_23_98
1034 && ADesc.SampleRate != EditRate_24
1035 && ADesc.SampleRate != EditRate_48 )
1036 ADesc.SampleRate = Options.PictureRate();
1038 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1040 if ( Options.verbose_flag )
1041 PCM::AudioDescriptorDump(ADesc);
1044 if ( ASDCP_SUCCESS(result) )
1046 last_frame = ADesc.ContainerDuration;
1048 if ( Options.duration > 0 && Options.duration < last_frame )
1049 last_frame = Options.duration;
1051 if ( Options.start_frame > 0 )
1053 if ( Options.start_frame > ADesc.ContainerDuration )
1055 fprintf(stderr, "Start value greater than file duration.\n");
1059 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1062 ADesc.ContainerDuration = last_frame - Options.start_frame;
1063 OutWave.OpenWrite(ADesc, Options.file_root,
1064 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1065 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1068 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1070 Context = new AESDecContext;
1071 result = Context->InitKey(Options.key_value);
1073 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1076 Reader.FillWriterInfo(Info);
1078 if ( Info.UsesHMAC )
1080 HMAC = new HMACContext;
1081 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1085 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1090 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1092 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1094 if ( ASDCP_SUCCESS(result) )
1096 if ( Options.verbose_flag )
1097 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1099 result = OutWave.WriteFrame(FrameBuffer);
1107 #ifdef ASDCP_WITH_TIMED_TEXT
1109 //------------------------------------------------------------------------------------------
1110 // TimedText essence
1113 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1114 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1117 write_timed_text_file(CommandOptions& Options)
1119 AESEncContext* Context = 0;
1120 HMACContext* HMAC = 0;
1121 TimedText::DCSubtitleParser Parser;
1122 TimedText::MXFWriter Writer;
1123 TimedText::FrameBuffer FrameBuffer;
1124 TimedText::TimedTextDescriptor TDesc;
1125 byte_t IV_buf[CBC_BLOCK_SIZE];
1126 Kumu::FortunaRNG RNG;
1128 // set up essence parser
1129 Result_t result = Parser.OpenRead(Options.filenames[0]);
1131 // set up MXF writer
1132 if ( ASDCP_SUCCESS(result) )
1134 Parser.FillDescriptor(TDesc);
1135 FrameBuffer.Capacity(2*Kumu::Megabyte);
1137 if ( Options.verbose_flag )
1139 fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1140 TimedText::DescriptorDump(TDesc);
1144 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1146 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
1147 Kumu::GenRandomUUID(Info.AssetUUID);
1149 if ( Options.use_smpte_labels )
1151 Info.LabelSetType = LS_MXF_SMPTE;
1152 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1155 // configure encryption
1156 if( Options.key_flag )
1158 Kumu::GenRandomUUID(Info.ContextID);
1159 Info.EncryptedEssence = true;
1161 if ( Options.key_id_flag )
1162 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1164 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1166 Context = new AESEncContext;
1167 result = Context->InitKey(Options.key_value);
1169 if ( ASDCP_SUCCESS(result) )
1170 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1172 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1174 Info.UsesHMAC = true;
1175 HMAC = new HMACContext;
1176 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1180 if ( ASDCP_SUCCESS(result) )
1181 result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1184 if ( ASDCP_FAILURE(result) )
1188 TimedText::ResourceList_t::const_iterator ri;
1190 result = Parser.ReadTimedTextResource(XMLDoc);
1192 if ( ASDCP_SUCCESS(result) )
1193 result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1195 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1197 result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1199 if ( ASDCP_SUCCESS(result) )
1201 if ( Options.verbose_flag )
1202 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1204 if ( ! Options.no_write_flag )
1206 result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1208 // The Writer class will forward the last block of ciphertext
1209 // to the encryption context for use as the IV for the next
1210 // frame. If you want to use non-sequitur IV values, un-comment
1211 // the following line of code.
1212 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1213 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1217 if ( result == RESULT_ENDOFFILE )
1221 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1222 result = Writer.Finalize();
1228 // Read one or more timed text streams from a plaintext ASDCP file
1229 // Read one or more timed text streams from a ciphertext ASDCP file
1230 // Read one or more timed text streams from a ciphertext ASDCP file
1233 read_timed_text_file(CommandOptions& Options)
1235 AESDecContext* Context = 0;
1236 HMACContext* HMAC = 0;
1237 TimedText::MXFReader Reader;
1238 TimedText::FrameBuffer FrameBuffer;
1239 TimedText::TimedTextDescriptor TDesc;
1241 Result_t result = Reader.OpenRead(Options.filenames[0]);
1243 if ( ASDCP_SUCCESS(result) )
1245 Reader.FillDescriptor(TDesc);
1246 FrameBuffer.Capacity(2*Kumu::Megabyte);
1248 if ( Options.verbose_flag )
1249 TimedText::DescriptorDump(TDesc);
1252 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1254 Context = new AESDecContext;
1255 result = Context->InitKey(Options.key_value);
1257 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1260 Reader.FillWriterInfo(Info);
1262 if ( Info.UsesHMAC )
1264 HMAC = new HMACContext;
1265 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1269 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1274 if ( ASDCP_FAILURE(result) )
1278 TimedText::ResourceList_t::const_iterator ri;
1280 result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1282 // do something with the XML here
1283 fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1285 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1287 result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1289 if ( ASDCP_SUCCESS(result) )
1291 // if ( Options.verbose_flag )
1292 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1294 // do something with the resource data here
1300 #endif // ASDCP_WITH_TIMED_TEXT
1302 //------------------------------------------------------------------------------------------
1306 // These classes wrap the irregular names in the asdcplib API
1307 // so that I can use a template to simplify the implementation
1308 // of show_file_info()
1310 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1313 void FillDescriptor(MPEG2::MXFReader& Reader) {
1314 Reader.FillVideoDescriptor(*this);
1317 void Dump(FILE* stream) {
1318 MPEG2::VideoDescriptorDump(*this, stream);
1322 class MyPictureDescriptor : public JP2K::PictureDescriptor
1325 void FillDescriptor(JP2K::MXFReader& Reader) {
1326 Reader.FillPictureDescriptor(*this);
1329 void Dump(FILE* stream) {
1330 JP2K::PictureDescriptorDump(*this, stream);
1334 class MyAudioDescriptor : public PCM::AudioDescriptor
1337 void FillDescriptor(PCM::MXFReader& Reader) {
1338 Reader.FillAudioDescriptor(*this);
1341 void Dump(FILE* stream) {
1342 PCM::AudioDescriptorDump(*this, stream);
1346 #ifdef ASDCP_WITH_TIMED_TEXT
1347 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1350 void FillDescriptor(TimedText::MXFReader& Reader) {
1351 Reader.FillDescriptor(*this);
1354 void Dump(FILE* stream) {
1355 TimedText::DescriptorDump(*this, stream);
1360 // MSVC didn't like the function template, so now it's a static class method
1361 template<class ReaderT, class DescriptorT>
1362 class FileInfoWrapper
1365 static void file_info(CommandOptions& Options, FILE* stream = 0)
1370 if ( Options.verbose_flag || Options.showheader_flag )
1373 Result_t result = Reader.OpenRead(Options.filenames[0]);
1375 if ( ASDCP_SUCCESS(result) )
1377 if ( Options.showheader_flag )
1378 Reader.DumpHeaderMetadata(stream);
1381 Reader.FillWriterInfo(WI);
1382 WriterInfoDump(WI, stream);
1385 Desc.FillDescriptor(Reader);
1388 if ( Options.showindex_flag )
1389 Reader.DumpIndex(stream);
1391 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1393 Reader.DumpHeaderMetadata(stream);
1399 // Read header metadata from an ASDCP file
1402 show_file_info(CommandOptions& Options)
1404 EssenceType_t EssenceType;
1405 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1407 if ( ASDCP_FAILURE(result) )
1410 if ( EssenceType == ESS_MPEG2_VES )
1412 fputs("File essence type is MPEG2 video.\n", stdout);
1413 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1415 else if ( EssenceType == ESS_PCM_24b_48k )
1417 fputs("File essence type is PCM audio.\n", stdout);
1418 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1420 else if ( EssenceType == ESS_JPEG_2000 )
1422 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1423 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1425 #ifdef ASDCP_WITH_TIMED_TEXT
1426 else if ( EssenceType == ESS_TIMED_TEXT )
1428 fputs("File essence type is Timed Text.\n", stdout);
1429 FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options);
1434 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1435 Kumu::FileReader Reader;
1436 MXF::OPAtomHeader TestHeader;
1438 result = Reader.OpenRead(Options.filenames[0]);
1440 if ( ASDCP_SUCCESS(result) )
1441 result = TestHeader.InitFromFile(Reader); // test UL and OP
1443 if ( ASDCP_SUCCESS(result) )
1445 TestHeader.Partition::Dump();
1447 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1450 fputs("File contains no Identification object.\n", stdout);
1452 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1455 fputs("File contains no SourcePackage object.\n", stdout);
1459 fputs("File is not MXF.\n", stdout);
1469 digest_file(const char* filename)
1471 using namespace Kumu;
1473 ASDCP_TEST_NULL_STR(filename);
1477 ByteString Buf(8192);
1479 Result_t result = Reader.OpenRead(filename);
1481 while ( ASDCP_SUCCESS(result) )
1483 ui32_t read_count = 0;
1484 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1486 if ( result == RESULT_ENDOFFILE )
1492 if ( ASDCP_SUCCESS(result) )
1493 SHA1_Update(&Ctx, Buf.Data(), read_count);
1496 if ( ASDCP_SUCCESS(result) )
1498 const ui32_t sha_len = 20;
1499 byte_t bin_buf[sha_len];
1501 SHA1_Final(bin_buf, &Ctx);
1503 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1511 main(int argc, const char** argv)
1513 Result_t result = RESULT_OK;
1514 CommandOptions Options(argc, argv);
1516 if ( Options.version_flag )
1519 if ( Options.help_flag )
1522 if ( Options.version_flag || Options.help_flag )
1525 if ( Options.error_flag )
1527 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1531 if ( Options.mode == MMT_INFO )
1533 result = show_file_info(Options);
1535 else if ( Options.mode == MMT_GOP_START )
1537 result = gop_start_test(Options);
1539 else if ( Options.mode == MMT_GEN_KEY )
1541 Kumu::FortunaRNG RNG;
1542 byte_t bin_buf[KeyLen];
1545 RNG.FillRandom(bin_buf, KeyLen);
1546 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1548 else if ( Options.mode == MMT_GEN_ID )
1551 Kumu::GenRandomValue(TmpID);
1553 printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1555 else if ( Options.mode == MMT_DIGEST )
1557 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1558 result = digest_file(Options.filenames[i]);
1560 else if ( Options.mode == MMT_EXTRACT )
1562 EssenceType_t EssenceType;
1563 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1565 if ( ASDCP_SUCCESS(result) )
1567 switch ( EssenceType )
1570 result = read_MPEG2_file(Options);
1574 result = read_JP2K_file(Options);
1577 case ESS_PCM_24b_48k:
1578 result = read_PCM_file(Options);
1581 case ESS_TIMED_TEXT:
1582 #ifdef ASDCP_WITH_TIMED_TEXT
1583 result = read_timed_text_file(Options);
1586 fprintf(stderr, "asdcplib compiled without timed text support.\n");
1590 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1595 else if ( Options.mode == MMT_CREATE )
1597 if ( Options.do_repeat && ! Options.duration_flag )
1599 fputs("Option -R requires -d <duration>\n", stderr);
1603 EssenceType_t EssenceType;
1604 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1606 if ( ASDCP_SUCCESS(result) )
1608 switch ( EssenceType )
1611 result = write_MPEG2_file(Options);
1615 result = write_JP2K_file(Options);
1618 case ESS_PCM_24b_48k:
1619 result = write_PCM_file(Options);
1622 case ESS_TIMED_TEXT:
1623 #ifdef ASDCP_WITH_TIMED_TEXT
1624 result = write_timed_text_file(Options);
1627 fprintf(stderr, "asdcplib compiled without timed text support.\n");
1632 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1633 Options.filenames[0]);
1639 if ( ASDCP_FAILURE(result) )
1641 fputs("Program stopped on error.\n", stderr);
1643 if ( result != RESULT_FAIL )
1645 fputs(result, stderr);
1646 fputc('\n', stderr);
1657 // end asdcp-test.cpp