Change History
+2006.10.18 - Bug fixes v1.1.11
+ o Increased index table entry list size to 5000.
+ o Added length checking to TLV writer (returns error if TLV
+ payload exceeds 64kB).
+ o Fixed partition header and RIP errors related to 2-partition
+ files (MXF Interop mode).
+ o Added -t option, SHA-1 digest with Base64 output on stdout.
+
2006.10.05 - Bug fixes v1.1.10
o Changed RM_RELEASE to RL_RELEASE in MXFTypes.h.
o Changed the MXF writer to use RL_RELEASE (was RL_DEVELOPMENT).
// 1.0.1. If changes were also required in AS_DCP.h, the new version would be 1.1.1.
const ui32_t VERSION_MAJOR = 1;
const ui32_t VERSION_APIMINOR = 1;
- const ui32_t VERSION_IMPMINOR = 10;
+ const ui32_t VERSION_IMPMINOR = 11;
const char* Version();
// UUIDs are passed around as strings of UUIDlen bytes
return ( CompareFileTime(&lft, &rft) == -1 );
}
+//
+bool
+Kumu::Timestamp::operator>(const Timestamp& rhs) const
+{
+ SYSTEMTIME lhst, rhst;
+ FILETIME lft, rft;
+
+ TIMESTAMP_TO_SYSTIME(*this, &lhst);
+ TIMESTAMP_TO_SYSTIME(rhs, &rhst);
+ SystemTimeToFileTime(&lhst, &lft);
+ SystemTimeToFileTime(&rhst, &rft);
+ return ( CompareFileTime(&lft, &rft) == 1 );
+}
+
inline ui64_t
seconds_to_ns100(ui32_t seconds)
{
return ( timegm(&lhtm) < timegm(&rhtm) );
}
+//
+bool
+Kumu::Timestamp::operator>(const Timestamp& rhs) const
+{
+ struct tm lhtm, rhtm;
+ TIMESTAMP_TO_TM(*this, &lhtm);
+ TIMESTAMP_TO_TM(rhs, &rhtm);
+ return ( timegm(&lhtm) > timegm(&rhtm) );
+}
+
//
void
Kumu::Timestamp::AddDays(i32_t days)
UUID(const UUID& rhs) : Identifier<UUID_Length>(rhs) {}
virtual ~UUID() {}
+ inline const char* EncodeString(char* buf, ui32_t buf_len) const {
+ return bin2UUIDhex(m_Value, Size(), buf, buf_len);
+ }
+
inline const char* EncodeHex(char* buf, ui32_t buf_len) const {
return bin2UUIDhex(m_Value, Size(), buf, buf_len);
}
const Timestamp& operator=(const Timestamp& rhs);
bool operator<(const Timestamp& rhs) const;
+ bool operator>(const Timestamp& rhs) const;
bool operator==(const Timestamp& rhs) const;
bool operator!=(const Timestamp& rhs) const;
{ { 0x06, 0x0e, 0x2b, 0x34, 0x01, 0x02, 0x01, 0x01, // 16
0x0d, 0x01, 0x03, 0x01, 0x16, 0x01, 0x01, 0x00 },
{0}, false, "WAVEssence" },
- { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x09, // 17
- 0x04, 0x01, 0x02, 0x02, 0x03, 0x01, 0x01, 0x01 },
+ { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x07,
+ 0x04, 0x01, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03 },
+ // 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x09, // 17
+ // 0x04, 0x01, 0x02, 0x02, 0x03, 0x01, 0x01, 0x01 },
{0}, false, "JP2KEssenceCompression" },
{ { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x07, // 18
0x02, 0x09, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00 },
#include <KM_log.h>
using Kumu::DefaultLogSink;
+// index segments must be < 64K
+// NOTE: this value may too high if advanced index entry elements are used.
+const ui32_t CBRIndexEntriesPerSegment = 5000;
+
//------------------------------------------------------------------------------------------
//
m_CurrentSegment->IndexEditRate = m_EditRate;
m_CurrentSegment->IndexStartPosition = 0;
}
- else if ( m_CurrentSegment->IndexEntryArray.size() >= 1486 ) // 1486 gets us 16K packets
+ else if ( m_CurrentSegment->IndexEntryArray.size() >= CBRIndexEntriesPerSegment )
{ // no, this one is full, start another
m_CurrentSegment->IndexDuration = m_CurrentSegment->IndexEntryArray.size();
ui64_t StartPosition = m_CurrentSegment->IndexStartPosition + m_CurrentSegment->IndexDuration;
i8_t KeyFrameOffset;
ui8_t Flags;
ui64_t StreamOffset;
+
+ // if you use these, you will need to change CBRIndexEntriesPerSegment in MXF.cpp
+ // to a more suitable value
// std::list<ui32_t> SliceOffset;
// Array<Rational> PosTable;
ui32_t before = Length();
if ( ! Object->Archive(this) ) return RESULT_KLV_CODING;
- Kumu::i2p<ui16_t>(KM_i16_BE( Length() - before), l_p);
+ if ( (Length() - before) > 0xffffL ) return RESULT_KLV_CODING;
+ Kumu::i2p<ui16_t>(KM_i16_BE(Length() - before), l_p);
}
return result;
#include <WavFileWriter.h>
#include <MXF.h>
#include <Metadata.h>
+#include <openssl/sha.h>
using namespace ASDCP;
} s_MyInfo;
-// Macros used to test command option data state.
-
-// True if a major mode has already been selected.
-#define TEST_MAJOR_MODE() ( info_flag||create_flag||extract_flag||genkey_flag||genid_flag||gop_start_flag )
-
-// Causes the caller to return if a major mode has already been selected,
-// otherwise sets the given flag.
-#define TEST_SET_MAJOR_MODE(f) if ( TEST_MAJOR_MODE() ) \
- { \
- fputs("Conflicting major mode, choose one of -(gcixG)).\n", stderr); \
- return; \
- } \
- (f) = true;
// Increment the iterator, test for an additional non-option command line argument.
// Causes the caller to return if there are no remaining arguments or if the next
%s -g | -u\n\
\n\
%s -G [-v] <input-file>\n\
+\n\
+ %s -t <input-file>\n\
\n\
%s -x <file-prefix> [-b <buffer-size>] [-d <duration>]\n\
[-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S]\n\
[-v] [-W] <input-file>\n\
-\n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
+\n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
fprintf(stream, "\
Major modes:\n\
-G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
-h | -help - Show help\n\
-i - Show file info\n\
+ -t - Calculate message digest of input file\n\
-u - Generate a random UUID value to stdout\n\
-V - Show version information\n\
-x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
\n");
}
+//
+enum MajorMode_t
+{
+ MMT_NONE,
+ MMT_INFO,
+ MMT_CREATE,
+ MMT_EXTRACT,
+ MMT_GEN_ID,
+ MMT_GEN_KEY,
+ MMT_GOP_START,
+ MMT_DIGEST
+};
+
+
//
//
class CommandOptions
CommandOptions();
public:
+ MajorMode_t mode;
bool error_flag; // true if the given options are in error or not complete
- bool info_flag; // true if the file info mode was selected
- bool create_flag; // true if the file create mode was selected
- bool extract_flag; // true if the file extract mode was selected
- bool genkey_flag; // true if we are to generate a new key value
- bool genid_flag; // true if we are to generate a new UUID value
- bool gop_start_flag; // true if we are to perform a GOP start lookup test
bool key_flag; // true if an encryption key was given
bool key_id_flag; // true if a key ID was given
bool encrypt_header_flag; // true if mpeg headers are to be encrypted
//
CommandOptions(int argc, const char** argv) :
- error_flag(true), info_flag(false), create_flag(false),
- extract_flag(false), genkey_flag(false), genid_flag(false), gop_start_flag(false),
- key_flag(false), key_id_flag(false), encrypt_header_flag(true),
+ mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
write_hmac(true), read_hmac(false), split_wav(false),
verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
{
switch ( argv[i][1] )
{
- case 'i': TEST_SET_MAJOR_MODE(info_flag); break;
- case 'G': TEST_SET_MAJOR_MODE(gop_start_flag); break;
+ case 'i': mode = MMT_INFO; break;
+ case 'G': mode = MMT_GOP_START; break;
case 'W': no_write_flag = true; break;
case 'n': showindex_flag = true; break;
case 'H': showheader_flag = true; break;
case 'V': version_flag = true; break;
case 'h': help_flag = true; break;
case 'v': verbose_flag = true; break;
- case 'g': genkey_flag = true; break;
- case 'u': genid_flag = true; break;
+ case 'g': mode = MMT_GEN_KEY; break;
+ case 'u': mode = MMT_GEN_ID; break;
case 'e': encrypt_header_flag = true; break;
case 'E': encrypt_header_flag = false; break;
case 'M': write_hmac = false; break;
case 'L': use_smpte_labels = true; break;
case 'c':
- TEST_SET_MAJOR_MODE(create_flag);
TEST_EXTRA_ARG(i, 'c');
+ mode = MMT_CREATE;
out_file = argv[i];
break;
case 'x':
- TEST_SET_MAJOR_MODE(extract_flag);
TEST_EXTRA_ARG(i, 'x');
+ mode = MMT_EXTRACT;
file_root = argv[i];
break;
fb_dump_size = atoi(argv[i]);
break;
+ case 't': mode = MMT_DIGEST; break;
+
case 'b':
TEST_EXTRA_ARG(i, 'b');
fb_size = atoi(argv[i]);
if ( help_flag || version_flag )
return;
- if ( TEST_MAJOR_MODE() )
+ if ( ( mode == MMT_INFO
+ || mode == MMT_CREATE
+ || mode == MMT_EXTRACT
+ || mode == MMT_GOP_START
+ || mode == MMT_DIGEST ) && file_count == 0 )
{
- if ( ! genkey_flag && ! genid_flag && file_count == 0 )
- {
- fputs("Option requires at least one filename argument.\n", stderr);
- return;
- }
+ fputs("Option requires at least one filename argument.\n", stderr);
+ return;
}
- if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
+ if ( mode == MMT_NONE && ! help_flag && ! version_flag )
{
- fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
+ fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
return;
}
}
+//
+Result_t
+digest_file(const char* filename)
+{
+ using namespace Kumu;
+
+ ASDCP_TEST_NULL_STR(filename);
+ FileReader Reader;
+ SHA_CTX Ctx;
+ SHA1_Init(&Ctx);
+ ByteString Buf(8192);
+
+ Result_t result = Reader.OpenRead(filename);
+
+ while ( ASDCP_SUCCESS(result) )
+ {
+ ui32_t read_count = 0;
+ result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
+
+ if ( result == RESULT_ENDOFFILE )
+ {
+ result = RESULT_OK;
+ break;
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ SHA1_Update(&Ctx, Buf.Data(), read_count);
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ const ui32_t sha_len = 20;
+ byte_t bin_buf[sha_len];
+ char sha_buf[64];
+ SHA1_Final(bin_buf, &Ctx);
+
+ fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
+ }
+
+ return result;
+}
+
//
int
main(int argc, const char** argv)
return 3;
}
- if ( Options.info_flag )
+ if ( Options.mode == MMT_INFO )
{
result = show_file_info(Options);
}
- else if ( Options.gop_start_flag )
+ else if ( Options.mode == MMT_GOP_START )
{
result = gop_start_test(Options);
}
- else if ( Options.genkey_flag )
+ else if ( Options.mode == MMT_GEN_KEY )
{
Kumu::FortunaRNG RNG;
byte_t bin_buf[KeyLen];
RNG.FillRandom(bin_buf, KeyLen);
printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
}
- else if ( Options.genid_flag )
+ else if ( Options.mode == MMT_GEN_ID )
{
UUID TmpID;
Kumu::GenRandomValue(TmpID);
char str_buf[40];
printf("%s\n", TmpID.EncodeHex(str_buf, 40));
}
- else if ( Options.extract_flag )
+ else if ( Options.mode == MMT_DIGEST )
+ {
+ for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
+ result = digest_file(Options.filenames[i]);
+ }
+ else if ( Options.mode == MMT_EXTRACT )
{
EssenceType_t EssenceType;
result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
}
}
}
- else if ( Options.create_flag )
+ else if ( Options.mode == MMT_CREATE )
{
if ( Options.do_repeat && ! Options.duration_flag )
{
ADesc.Locked = 0;
ADesc.ChannelCount = 1;
ADesc.QuantizationBits = 24;
- ADesc.BlockAlign = 18;
- ADesc.AvgBps = 86400;
+ ADesc.BlockAlign = 3;
+ ADesc.AvgBps = 14400;
ADesc.LinkedTrackID = 1;
ADesc.ContainerDuration = Options.duration;
// so we tell the world by using OP1a
m_HeaderPart.m_Preface->OperationalPattern = UL(Dict::ul(MDD_OP1a));
m_HeaderPart.OperationalPattern = m_HeaderPart.m_Preface->OperationalPattern;
- m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(0, 0)); // First RIP Entry
+
+ // First RIP Entry
+ if ( m_Info.LabelSetType == LS_MXF_SMPTE )
+ m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(0, 0)); // 3-part, no essence in header
+ else
+ m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(1, 0)); // 2-part, essence in header
//
// Identification
UL BodyUL(Dict::ul(MDD_ClosedCompleteBodyPartition));
result = m_BodyPart.WriteToFile(m_File, BodyUL);
}
+ else
+ {
+ m_HeaderPart.BodySID = 1;
+ }
if ( ASDCP_SUCCESS(result) )
{
// Index setup
Kumu::fpos_t ECoffset = m_File.Tell();
+ m_FooterPart.IndexSID = 129;
if ( BytesPerEditUnit == 0 )
m_FooterPart.SetIndexParamsVBR(&m_HeaderPart.m_Primer, EditRate, ECoffset);
m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
Kumu::fpos_t here = m_File.Tell();
- m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(0, here)); // Third RIP Entry
+ m_HeaderPart.m_RIP.PairArray.push_back(RIP::Pair(0, here)); // Last RIP Entry
m_HeaderPart.FooterPartition = here;
// re-label the partition
m_HeaderPart.OperationalPattern = OPAtomUL;
m_HeaderPart.m_Preface->OperationalPattern = m_HeaderPart.OperationalPattern;
- m_FooterPart.PreviousPartition = m_BodyPart.ThisPartition;
+ if ( m_Info.LabelSetType == LS_MXF_SMPTE )
+ m_FooterPart.PreviousPartition = m_BodyPart.ThisPartition;
+ else
+ m_FooterPart.PreviousPartition = m_HeaderPart.ThisPartition;
+
m_FooterPart.OperationalPattern = m_HeaderPart.OperationalPattern;
m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
m_FooterPart.FooterPartition = here;