2 Copyright (c) 2011-2016, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 3. The name of the author may not be used to endorse or promote products
16 derived from this software without specific prior written permission.
18 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 /*! \file as-02-unwrap.cpp
31 \brief AS-02 file manipulation utility
33 This program extracts picture and sound from AS-02 files.
35 For more information about AS-02, please refer to the header file AS_02.h
36 For more information about asdcplib, please refer to the header file AS_DCP.h
39 #include <KM_fileio.h>
41 #include <WavFileWriter.h>
44 Result_t MD_to_PCM_ADesc(ASDCP::MXF::WaveAudioDescriptor* ADescObj, ASDCP::PCM::AudioDescriptor& ADesc);
47 using namespace ASDCP;
49 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
51 //------------------------------------------------------------------------------------------
53 // command line option parser class
55 static const char* PROGRAM_NAME = "as-02-unwrap"; // program name for messages
57 // Increment the iterator, test for an additional non-option command line argument.
58 // Causes the caller to return if there are no remaining arguments or if the next
59 // argument begins with '-'.
60 #define TEST_EXTRA_ARG(i,c) \
61 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
62 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
68 banner(FILE* stream = stdout)
72 Copyright (c) 2011-2015, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
73 asdcplib may be copied only under the terms of the license found at\n\
74 the top of every file in the asdcplib distribution kit.\n\n\
75 Specify the -h (help) option for further information about %s\n\n",
76 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
81 usage(FILE* stream = stdout)
84 USAGE: %s [-h|-help] [-V]\n\
86 %s [-1|-2] [-b <buffer-size>] [-d <duration>]\n\
87 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <size>] [-v] [-W]\n\
88 [-w] <input-file> [<file-prefix>]\n\n",
89 PROGRAM_NAME, PROGRAM_NAME);
93 -1 - Split Wave essence to mono WAV files during extract.\n\
94 Default is multichannel WAV\n\
95 -2 - Split Wave essence to stereo WAV files during extract.\n\
96 Default is multichannel WAV\n\
97 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
98 Defaults to 4,194,304 (4MB)\n\
99 -d <duration> - Number of frames to process, default all\n\
100 -f <start-frame> - Starting frame number, default 0\n\
101 -h | -help - Show help\n\
102 -k <key-string> - Use key for ciphertext operations\n\
103 -m - verify HMAC values when reading\n\
104 -s <size> - Number of bytes to dump to output when -v is given\n\
105 -V - Show version information\n\
106 -v - Verbose, prints informative messages to stderr\n\
107 -W - Read input file only, do not write destination file\n\
108 -w <width> - Width of numeric element in a series of frame file names\n\
110 -z - Fail if j2c inputs have unequal parameters (default)\n\
111 -Z - Ignore unequal parameters in j2c inputs\n\
113 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
114 o All option arguments must be separated from the option by whitespace.\n\n");
123 bool error_flag; // true if the given options are in error or not complete
124 bool key_flag; // true if an encryption key was given
125 bool read_hmac; // true if HMAC values are to be validated
126 bool split_wav; // true if PCM is to be extracted to stereo WAV files
127 bool mono_wav; // true if PCM is to be extracted to mono WAV files
128 bool verbose_flag; // true if the verbose option was selected
129 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
130 bool no_write_flag; // true if no output files are to be written
131 bool version_flag; // true if the version display option was selected
132 bool help_flag; // true if the help display option was selected
133 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
134 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
135 ui32_t start_frame; // frame number to begin processing
136 ui32_t duration; // number of frames to be processed
137 bool duration_flag; // true if duration argument given
138 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
139 ui32_t picture_rate; // fps of picture when wrapping PCM
140 ui32_t fb_size; // size of picture frame buffer
141 Rational edit_rate; // frame buffer size for reading clip-wrapped PCM
142 const char* file_prefix; // filename pre for files written by the extract mode
143 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
144 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
145 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
146 const char* input_filename;
147 const char* extension;
148 std::string prefix_buffer;
151 CommandOptions(int argc, const char** argv) :
152 error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
153 mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
154 version_flag(false), help_flag(false), number_width(6),
155 start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
156 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
159 memset(key_value, 0, KeyLen);
160 memset(key_id_value, 0, UUIDlen);
162 for ( int i = 1; i < argc; ++i )
165 if ( (strcmp( argv[i], "-help") == 0) )
171 if ( argv[i][0] == '-'
172 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
175 switch ( argv[i][1] )
177 case '1': mono_wav = true; break;
178 case '2': split_wav = true; break;
181 TEST_EXTRA_ARG(i, 'b');
182 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
185 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
190 TEST_EXTRA_ARG(i, 'd');
191 duration_flag = true;
192 duration = Kumu::xabs(strtol(argv[i], 0, 10));
196 TEST_EXTRA_ARG(i, 'e');
201 TEST_EXTRA_ARG(i, 'f');
202 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
205 case 'h': help_flag = true; break;
206 case 'm': read_hmac = true; break;
209 TEST_EXTRA_ARG(i, 'p');
210 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
214 TEST_EXTRA_ARG(i, 's');
215 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
218 case 'V': version_flag = true; break;
219 case 'v': verbose_flag = true; break;
220 case 'W': no_write_flag = true; break;
223 TEST_EXTRA_ARG(i, 'w');
224 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
227 case 'Z': j2c_pedantic = false; break;
228 case 'z': j2c_pedantic = true; break;
231 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
237 if ( argv[i][0] != '-' )
239 if ( input_filename == 0 )
241 input_filename = argv[i];
243 else if ( file_prefix == 0 )
245 file_prefix = argv[i];
250 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
256 if ( help_flag || version_flag )
259 if ( input_filename == 0 )
261 fputs("At least one filename argument is required.\n", stderr);
265 if ( file_prefix == 0 )
267 prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
268 file_prefix = prefix_buffer.c_str();
276 //------------------------------------------------------------------------------------------
280 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
281 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
282 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
285 read_JP2K_file(CommandOptions& Options)
287 AESDecContext* Context = 0;
288 HMACContext* HMAC = 0;
289 AS_02::JP2K::MXFReader Reader;
290 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
291 ui32_t frame_count = 0;
293 Result_t result = Reader.OpenRead(Options.input_filename);
295 if ( ASDCP_SUCCESS(result) )
297 if ( Options.verbose_flag )
299 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
302 ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
303 ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
305 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
306 reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
308 if ( KM_SUCCESS(result) )
310 assert(rgba_descriptor);
311 frame_count = rgba_descriptor->ContainerDuration;
313 if ( Options.verbose_flag )
315 rgba_descriptor->Dump();
320 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
321 reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
323 if ( KM_SUCCESS(result) )
325 assert(cdci_descriptor);
326 frame_count = cdci_descriptor->ContainerDuration;
328 if ( Options.verbose_flag )
330 cdci_descriptor->Dump();
335 fprintf(stderr, "File does not contain an essence descriptor.\n");
336 frame_count = Reader.AS02IndexReader().GetDuration();
340 if ( frame_count == 0 )
342 frame_count = Reader.AS02IndexReader().GetDuration();
345 if ( frame_count == 0 )
347 fprintf(stderr, "Unable to determine file duration.\n");
352 if ( ASDCP_SUCCESS(result) && Options.key_flag )
354 Context = new AESDecContext;
355 result = Context->InitKey(Options.key_value);
357 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
360 Reader.FillWriterInfo(Info);
364 HMAC = new HMACContext;
365 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
369 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
374 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
375 if ( last_frame > frame_count )
376 last_frame = frame_count;
378 char name_format[64];
379 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
381 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
383 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
386 snprintf(filename, 1024, name_format, Options.file_prefix, i);
388 if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
390 printf("Frame %d, %d bytes", i, FrameBuffer.Size());
392 if ( ! Options.no_write_flag )
394 printf(" -> %s", filename);
400 if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
402 Kumu::FileWriter OutFile;
404 result = OutFile.OpenWrite(filename);
406 if ( ASDCP_SUCCESS(result) )
407 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
409 if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
411 FrameBuffer.Dump(stderr, Options.fb_dump_size);
419 //------------------------------------------------------------------------------------------
422 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
423 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
424 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
427 read_PCM_file(CommandOptions& Options)
429 AESDecContext* Context = 0;
430 HMACContext* HMAC = 0;
431 AS_02::PCM::MXFReader Reader;
432 PCM::FrameBuffer FrameBuffer;
433 WavFileWriter OutWave;
434 ui32_t last_frame = 0;
435 ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0;
437 if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI
439 Options.edit_rate = EditRate_24;
442 Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
444 if ( KM_SUCCESS(result) )
446 if ( Options.verbose_flag )
448 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
451 ASDCP::MXF::InterchangeObject* tmp_obj = 0;
453 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), &tmp_obj);
455 if ( KM_SUCCESS(result) )
457 wave_descriptor = dynamic_cast<ASDCP::MXF::WaveAudioDescriptor*>(tmp_obj);
459 if ( wave_descriptor == 0 )
461 fprintf(stderr, "File does not contain an essence descriptor.\n");
465 if ( Options.verbose_flag )
467 wave_descriptor->Dump();
470 if ( wave_descriptor->ContainerDuration.get() == 0 )
472 fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
473 last_frame = Reader.AS02IndexReader().GetDuration();
477 last_frame = wave_descriptor->ContainerDuration;
480 if ( last_frame == 0 )
482 fprintf(stderr, "ContainerDuration not set in index, attempting to use Duration from SourceClip.\n");
483 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_SourceClip), &tmp_obj);
484 if ( KM_SUCCESS(result))
486 ASDCP::MXF::SourceClip *sourceClip = dynamic_cast<ASDCP::MXF::SourceClip*>(tmp_obj);
487 if ( ! sourceClip->Duration.empty() )
489 last_frame = sourceClip->Duration;
494 if ( last_frame == 0 )
496 fprintf(stderr, "Unable to determine file duration.\n");
500 assert(wave_descriptor);
501 FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
502 last_frame = AS_02::MXF::CalcFramesFromDurationInSamples(last_frame, *wave_descriptor, Options.edit_rate);
506 if ( ASDCP_SUCCESS(result) )
508 if ( Options.duration > 0 && Options.duration < last_frame )
509 last_frame = Options.duration;
511 if ( Options.start_frame > 0 )
513 if ( Options.start_frame > last_frame )
515 fprintf(stderr, "Start value greater than file duration.\n");
519 last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame);
522 last_frame = last_frame - Options.start_frame;
524 PCM::AudioDescriptor ADesc;
526 result = MD_to_PCM_ADesc(wave_descriptor, ADesc);
528 if ( ASDCP_SUCCESS(result) )
530 ADesc.ContainerDuration = last_frame;
531 ADesc.EditRate = Options.edit_rate;
533 result = OutWave.OpenWrite(ADesc, Options.file_prefix,
534 ( Options.split_wav ? WavFileWriter::ST_STEREO :
535 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
539 if ( ASDCP_SUCCESS(result) && Options.key_flag )
541 Context = new AESDecContext;
542 result = Context->InitKey(Options.key_value);
544 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
547 Reader.FillWriterInfo(Info);
551 HMAC = new HMACContext;
552 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
556 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
561 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
563 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
565 if ( ASDCP_SUCCESS(result) )
567 if ( Options.verbose_flag )
569 FrameBuffer.FrameNumber(i);
570 FrameBuffer.Dump(stderr, Options.fb_dump_size);
573 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
575 fprintf(stderr, "Last frame is incomplete, padding with zeros.\n");
576 // actually, it has already been zeroed for us, we just need to recognize the appropriate size
577 FrameBuffer.Size(FrameBuffer.Capacity());
580 result = OutWave.WriteFrame(FrameBuffer);
588 //------------------------------------------------------------------------------------------
591 // Read one or more timed text streams from a plaintext AS-02 file
594 read_timed_text_file(CommandOptions& Options)
596 AESDecContext* Context = 0;
597 HMACContext* HMAC = 0;
598 AS_02::TimedText::MXFReader Reader;
599 TimedText::FrameBuffer FrameBuffer(Options.fb_size);
600 //ASDCP::TimedText::FrameBuffer FrameBuffer(Options.fb_size);
601 AS_02::TimedText::TimedTextDescriptor TDesc;
602 ASDCP::MXF::TimedTextDescriptor *tt_descriptor = 0;
604 Result_t result = Reader.OpenRead(Options.input_filename);
606 if ( ASDCP_SUCCESS(result) )
608 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_TimedTextDescriptor),
609 reinterpret_cast<MXF::InterchangeObject**>(&tt_descriptor));
610 if ( Options.verbose_flag ) {
611 tt_descriptor->Dump();
615 if ( ASDCP_FAILURE(result) )
619 std::string out_path = Kumu::PathDirname(Options.file_prefix);
622 TimedText::ResourceList_t::const_iterator ri;
624 result = Reader.ReadTimedTextResource(XMLDoc);
626 if ( ASDCP_SUCCESS(result) )
628 Reader.FillTimedTextDescriptor(TDesc);
629 FrameBuffer.Capacity(Options.fb_size);
631 if ( Options.verbose_flag )
632 TimedText::DescriptorDump(TDesc);
635 if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
637 Kumu::FileWriter Writer;
638 result = Writer.OpenWrite(Options.file_prefix);
640 if ( ASDCP_SUCCESS(result) )
641 result = Writer.Write(reinterpret_cast<const byte_t*>(XMLDoc.c_str()), XMLDoc.size(), &write_count);
644 for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
646 result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
648 if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
650 Kumu::FileWriter Writer;
651 if (out_path != "") {
652 result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
654 // Workaround for a bug in Kumu::PathJoin
655 result = Writer.OpenWrite(Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64));
658 if ( ASDCP_SUCCESS(result) )
659 result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
661 if ( Options.verbose_flag )
662 FrameBuffer.Dump(stderr, Options.fb_dump_size);
671 main(int argc, const char** argv)
674 CommandOptions Options(argc, argv);
676 if ( Options.version_flag )
679 if ( Options.help_flag )
682 if ( Options.version_flag || Options.help_flag )
685 if ( Options.error_flag )
687 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
691 EssenceType_t EssenceType;
692 Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
694 if ( ASDCP_SUCCESS(result) )
696 switch ( EssenceType )
698 case ESS_AS02_JPEG_2000:
699 result = read_JP2K_file(Options);
702 case ESS_AS02_PCM_24b_48k:
703 case ESS_AS02_PCM_24b_96k:
704 result = read_PCM_file(Options);
707 case ESS_AS02_TIMED_TEXT:
708 result = read_timed_text_file(Options);
712 fprintf(stderr, "%s: Unknown file type (%d), not AS-02 essence.\n", Options.input_filename, EssenceType);
717 if ( ASDCP_FAILURE(result) )
719 fputs("Program stopped on error.\n", stderr);
721 if ( result != RESULT_FAIL )
723 fputs(result, stderr);
735 // end as-02-unwrap.cpp