Change History
-YYYY-MM-DD - bug fix, first publication of AS-02 EC support 1.10.46
- o Added support for much of IMF Essence Component (SMPTE draft ST 2067-5,
- AKA "AS-02". This code was developed and contributed by Robert Scheler,
- Fraunhofer IIS. Very special thanks to Siegfried Foessel and Heiko
- Sparenberg for their support of this essential IMF component.
+YYYY-MM-DD - bug fixes, enhancements M.N.RR
+ o Refactored internals of the AS-DCP file readers. While no
+ changes in behavior are intended, users are cautioned to test
+ thouroughly before use in production.
+ o Fixed a bug in ReadAncillaryResource that was causing bogus HMAC
+ failures when reading resources from a file.
+ o Fixed premature-release bug in the Expat version of the XML parser.
+ Thanks to Carsten Feldheim (IIS) for the tip.
+ o Fixed -W option in asdcp-unwrap. Thanks to RGB.
+ o Added P-HFR support to asdcp-wrap (see URL for details:
+ http://isdcf.com/papers/ISDCF-HighFrameRate-DCP.pdf).
+ o Added support for SMPTE ST 428-21 "Archival Frame Rates".
+ o Added -P option to asdcp-wrap (inserts arbitrary UL into the
+ PictureEssenceCoding property when wrapping JP2K files.)
+ o Added support for 96 kHz files to blackwave.
+ o Added new path and string manglers to Kumu.
+ o Updated MCA ULs (I warned you...). Again please take some
+ time to proof this work against ST 477-4 including the latest
+ drafts of the registries.
+ Changed the version byte (8 0f 16) to 0x0e:
+ MCALabelSubDescriptor
+ AudioChannelLabelSubDescriptor
+ SoundfieldGroupLabelSubDescriptor
+ GroupOfSoundfieldGroupsLabelSubDescriptor
+ GroupOfSoundfieldGroupsLinkID
+ Changed bytes 8 and and 13 of SoundfieldGroupLinkID
+ Added items to the UL dictionary:
+ MCAPartitionKind
+ MCAPartitionNumber
+ MCATitle
+ MCATitleVersion
+ MCATitleSubVersion
+ MCAEpisode
+ MCAAudioContentKind
+ MCAAudioElementKind
2012-08-07 - bug fix, 1.10.46
- o Added zero-initializers to time values when parsing a timestamp string
- (in the case where the (T...) option was not present the time was
- uninitialized).
+ o Added missing zero-initializers to time values when parsing a
+ timestamp string (in the case where the optional [Thh:mm.[:ss]]
+ syntax is not present in an encoded string).
2012-03-06 - bug fixes, enhancements 1.9.45
o Removed ASDCP::Timestamp, all items that were of that class are now
o ST 429-5 files have corrected ULs for DCTimedTextDescriptor and
GenericStream DataElement. Files made with previous versions of
the library are incompatible with this and future versions.
- o Fixed File Package TrackNumber values. Thanks to Sankar.
+ o Fixed File Package TrackNumber values. Th
+anks to Sankar.
o Added edit rate constants to AS_DCP.h (25, 30, 50, 60).
o Changed AudioDescriptor "SampleRate" element name to "EditRate"
to make it consistent with the other types.
const Rational EditRate_100 = Rational(100,1);
const Rational EditRate_120 = Rational(120,1);
+ // Archival frame rates, see ST 428-21
+ // These rates are new and not supported by all systems. Do not assume that
+ // a package made using one of these rates will work just anywhere!
+ const Rational EditRate_16 = Rational(16,1);
+ const Rational EditRate_18 = Rational(200,11); // 18.182
+ const Rational EditRate_20 = Rational(20,1);
+ const Rational EditRate_22 = Rational(240,11); // 21.818
+
+
// Non-reference counting container for internal member objects.
// Please do not use this class for any other purpose.
template <class T>
// hidden, internal implementation of JPEG 2000 reader
-class lh__Reader : public ASDCP::h__Reader
+class lh__Reader : public ASDCP::h__ASDCPReader
{
RGBAEssenceDescriptor* m_EssenceDescriptor;
JPEG2000PictureSubDescriptor* m_EssenceSubDescriptor;
PictureDescriptor m_PDesc; // codestream parameter list
lh__Reader(const Dictionary& d) :
- ASDCP::h__Reader(d), m_EssenceDescriptor(0), m_EssenceSubDescriptor(0), m_Format(ESS_UNKNOWN) {}
+ ASDCP::h__ASDCPReader(d), m_EssenceDescriptor(0), m_EssenceSubDescriptor(0), m_Format(ESS_UNKNOWN) {}
Result_t OpenRead(const char*, EssenceType_t);
Result_t ReadFrame(ui32_t, JP2K::FrameBuffer&, AESDecContext*, HMACContext*);
Result_t MD_to_JP2K_PDesc(JP2K::PictureDescriptor& PDesc);
}
// get frame position
- Kumu::fpos_t FilePosition = m_EssenceStart + TmpEntry.StreamOffset;
+ Kumu::fpos_t FilePosition = m_HeaderPart.BodyOffset + TmpEntry.StreamOffset;
Result_t result = RESULT_OK;
if ( phase == SP_LEFT )
//
// hidden, internal implementation of MPEG2 reader
-class ASDCP::MPEG2::MXFReader::h__Reader : public ASDCP::h__Reader
+class ASDCP::MPEG2::MXFReader::h__Reader : public ASDCP::h__ASDCPReader
{
ASDCP_NO_COPY_CONSTRUCT(h__Reader);
h__Reader();
public:
VideoDescriptor m_VDesc; // video parameter list
- h__Reader(const Dictionary& d) : ASDCP::h__Reader(d) {}
+ h__Reader(const Dictionary& d) : ASDCP::h__ASDCPReader(d) {}
~h__Reader() {}
Result_t OpenRead(const char*);
Result_t ReadFrame(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
//
Result_t
-ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
+ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
ui32_t sequence, HMACContext* HMAC)
{
ASDCP_TEST_NULL(AssetID);
Result_t
-ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
+ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
ui32_t sequence, HMACContext* HMAC)
{
ASDCP_TEST_NULL(AssetID);
//------------------------------------------------------------------------------------------
-class ASDCP::PCM::MXFReader::h__Reader : public ASDCP::h__Reader
+class ASDCP::PCM::MXFReader::h__Reader : public ASDCP::h__ASDCPReader
{
ASDCP_NO_COPY_CONSTRUCT(h__Reader);
h__Reader();
public:
AudioDescriptor m_ADesc;
- h__Reader(const Dictionary& d) : ASDCP::h__Reader(d) {}
+ h__Reader(const Dictionary& d) : ASDCP::h__ASDCPReader(d) {}
~h__Reader() {}
Result_t OpenRead(const char*);
Result_t ReadFrame(ui32_t, FrameBuffer&, AESDecContext*, HMACContext*);
&& m_ADesc.EditRate != EditRate_96
&& m_ADesc.EditRate != EditRate_100
&& m_ADesc.EditRate != EditRate_120
+ && m_ADesc.EditRate != EditRate_16
+ && m_ADesc.EditRate != EditRate_18
+ && m_ADesc.EditRate != EditRate_20
+ && m_ADesc.EditRate != EditRate_22
&& m_ADesc.EditRate != EditRate_23_98 )
{
DefaultLogSink().Error("PCM file EditRate is not a supported value: %d/%d\n", // lu
&& ADesc.EditRate != EditRate_96
&& ADesc.EditRate != EditRate_100
&& ADesc.EditRate != EditRate_120
+ && ADesc.EditRate != EditRate_16
+ && ADesc.EditRate != EditRate_18
+ && ADesc.EditRate != EditRate_20
+ && ADesc.EditRate != EditRate_22
&& ADesc.EditRate != EditRate_23_98 )
{
DefaultLogSink().Error("AudioDescriptor.EditRate is not a supported value: %d/%d\n",
if ( ASDCP_SUCCESS(result) )
{
- ui32_t TCFrameRate = ( m_ADesc.EditRate == EditRate_23_98 ) ? 24 : m_ADesc.EditRate.Numerator;
+ ui32_t TCFrameRate = m_ADesc.EditRate.Numerator;
+
+ if ( m_ADesc.EditRate == EditRate_23_98 )
+ TCFrameRate = 24;
+ else if ( m_ADesc.EditRate == EditRate_18 )
+ TCFrameRate = 18;
+ else if ( m_ADesc.EditRate == EditRate_22 )
+ TCFrameRate = 22;
result = WriteMXFHeader(PCM_PACKAGE_LABEL, UL(m_Dict->ul(MDD_WAVWrapping)),
SOUND_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_SoundDataDef)),
typedef std::map<UUID, UUID> ResourceMap_t;
-class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__Reader
+class ASDCP::TimedText::MXFReader::h__Reader : public ASDCP::h__ASDCPReader
{
MXF::TimedTextDescriptor* m_EssenceDescriptor;
ResourceMap_t m_ResourceMap;
public:
TimedTextDescriptor m_TDesc;
- h__Reader(const Dictionary& d) : ASDCP::h__Reader(d), m_EssenceDescriptor(0) {
+ h__Reader(const Dictionary& d) : ASDCP::h__ASDCPReader(d), m_EssenceDescriptor(0) {
memset(&m_TDesc.AssetID, 0, UUIDlen);
}
void AddDMScrypt(Partition& HeaderPart, SourcePackage& Package,
WriterInfo& Descr, const UL& WrappingUL, const Dictionary*& Dict);
+ Result_t Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict, const MXF::OPAtomHeader& HeaderPart,
+ const ASDCP::WriterInfo& Info, Kumu::fpos_t& LastPosition, ASDCP::FrameBuffer& CtFrameBuf,
+ ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
+ const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC);
+
//
class KLReader : public ASDCP::KLVPacket
{
Result_t ReadKLFromFile(Kumu::FileReader& Reader);
};
+ namespace MXF
+ {
+ //---------------------------------------------------------------------------------
+ //
+
+ /// void default_md_object_init();
+
+ template <class HeaderType, class FooterType>
+ class TrackFileReader
+ {
+ KM_NO_COPY_CONSTRUCT(TrackFileReader);
+ TrackFileReader();
+
+ public:
+ const Dictionary* m_Dict;
+ Kumu::FileReader m_File;
+ HeaderType m_HeaderPart;
+ FooterType m_FooterPart;
+ WriterInfo m_Info;
+ ASDCP::FrameBuffer m_CtFrameBuf;
+ Kumu::fpos_t m_LastPosition;
+
+ TrackFileReader(const Dictionary& d) :
+ m_HeaderPart(m_Dict), m_FooterPart(m_Dict), m_Dict(&d)
+ {
+ default_md_object_init();
+ }
+
+ virtual ~TrackFileReader() {
+ Close();
+ }
+
+ Result_t InitInfo()
+ {
+ assert(m_Dict);
+ InterchangeObject* Object;
+
+ // Identification
+ Result_t result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(Identification), &Object);
+
+ // Writer Info and SourcePackage
+ if ( KM_SUCCESS(result) )
+ {
+ MD_to_WriterInfo((Identification*)Object, m_Info);
+ result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(SourcePackage), &Object);
+ }
+
+ if ( KM_SUCCESS(result) )
+ {
+ SourcePackage* SP = (SourcePackage*)Object;
+ memcpy(m_Info.AssetUUID, SP->PackageUID.Value() + 16, UUIDlen);
+ }
+
+ // optional CryptographicContext
+ if ( KM_SUCCESS(result) )
+ {
+ Result_t cr_result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(CryptographicContext), &Object);
+
+ if ( KM_SUCCESS(cr_result) )
+ MD_to_CryptoInfo((CryptographicContext*)Object, m_Info, *m_Dict);
+ }
+
+ return result;
+ }
+
+ //
+ Result_t OpenMXFRead(const char* filename)
+ {
+ m_LastPosition = 0;
+ Result_t result = m_File.OpenRead(filename);
+
+ if ( KM_SUCCESS(result) )
+ result = m_HeaderPart.InitFromFile(m_File);
+
+ return result;
+ }
+
+ // positions file before reading
+ Result_t ReadEKLVFrame(const ASDCP::MXF::Partition& CurrentPartition,
+ ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
+ const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
+ {
+ // look up frame index node
+ IndexTableSegment::IndexEntry TmpEntry;
+
+ if ( KM_FAILURE(m_FooterPart.Lookup(FrameNum, TmpEntry)) )
+ {
+ DefaultLogSink().Error("Frame value out of range: %u\n", FrameNum);
+ return RESULT_RANGE;
+ }
+
+ // get frame position and go read the frame's key and length
+ Kumu::fpos_t FilePosition = CurrentPartition.BodyOffset + TmpEntry.StreamOffset;
+ Result_t result = RESULT_OK;
+
+ if ( FilePosition != m_LastPosition )
+ {
+ m_LastPosition = FilePosition;
+ result = m_File.Seek(FilePosition);
+ }
+
+ if( KM_SUCCESS(result) )
+ result = ReadEKLVPacket(FrameNum, FrameNum + 1, FrameBuf, EssenceUL, Ctx, HMAC);
+
+ return result;
+ }
+
+ // reads from current position
+ Result_t ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
+ const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
+ {
+ assert(m_Dict);
+ return Read_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_LastPosition, m_CtFrameBuf,
+ FrameNum, SequenceNum, FrameBuf, EssenceUL, Ctx, HMAC);
+ }
+
+ //
+ void Close() {
+ m_File.Close();
+ }
+ };
+
+ }/// namespace MXF
+
//
- class h__Reader
+ class h__ASDCPReader : public MXF::TrackFileReader<OPAtomHeader, OPAtomIndexFooter>
{
- ASDCP_NO_COPY_CONSTRUCT(h__Reader);
- h__Reader();
+ ASDCP_NO_COPY_CONSTRUCT(h__ASDCPReader);
+ h__ASDCPReader();
public:
- const Dictionary* m_Dict;
- Kumu::FileReader m_File;
- OPAtomHeader m_HeaderPart;
- Partition m_BodyPart;
- OPAtomIndexFooter m_FooterPart;
- ui64_t m_EssenceStart;
- WriterInfo m_Info;
- ASDCP::FrameBuffer m_CtFrameBuf;
- Kumu::fpos_t m_LastPosition;
+ Partition m_BodyPart;
- h__Reader(const Dictionary&);
- virtual ~h__Reader();
+ h__ASDCPReader(const Dictionary&);
+ virtual ~h__ASDCPReader();
- Result_t InitInfo();
Result_t OpenMXFRead(const char* filename);
+ Result_t InitInfo();
Result_t InitMXFIndex();
-
- // positions file before reading
Result_t ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC);
-
- // reads from current position
- Result_t ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
- const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC);
- void Close();
};
~IntegrityPack() {}
- Result_t CalcValues(const ASDCP::FrameBuffer&, byte_t* AssetID, ui32_t sequence, HMACContext* HMAC);
- Result_t TestValues(const ASDCP::FrameBuffer&, byte_t* AssetID, ui32_t sequence, HMACContext* HMAC);
+ Result_t CalcValues(const ASDCP::FrameBuffer&, const byte_t* AssetID, ui32_t sequence, HMACContext* HMAC);
+ Result_t TestValues(const ASDCP::FrameBuffer&, const byte_t* AssetID, ui32_t sequence, HMACContext* HMAC);
};
/*
-Copyright (c) 2004-2011, John Hurst
+Copyright (c) 2004-2012, John Hurst
All rights reserved.
Redistribution and use in source and binary forms, with or without
typedef struct stat fstat_t;
#endif
-//
-static void
-split(const std::string& str, char separator, std::list<std::string>& components)
-{
- const char* pstr = str.c_str();
- const char* r = strchr(pstr, separator);
-
- while ( r != 0 )
- {
- assert(r >= pstr);
- if ( r > pstr )
- {
- std::string tmp_str;
- tmp_str.assign(pstr, (r - pstr));
- components.push_back(tmp_str);
- }
-
- pstr = r + 1;
- r = strchr(pstr, separator);
- }
-
- if( strlen(pstr) > 0 )
- components.push_back(std::string(pstr));
-}
-
//
static Kumu::Result_t
}
//
-static PathCompList_t&
-s_PathMakeCanonical(PathCompList_t& CList, bool is_absolute)
+static void
+make_canonical_list(const PathCompList_t& in_list, PathCompList_t& out_list)
{
- PathCompList_t::iterator ci, ri; // component and removal iterators
-
- for ( ci = CList.begin(); ci != CList.end(); ci++ )
+ PathCompList_t::const_iterator i;
+ for ( i = in_list.begin(); i != in_list.end(); ++i )
{
- if ( *ci == "." && ( CList.size() > 1 || is_absolute ) )
- {
- ri = ci++;
- CList.erase(ri);
- }
- else if ( *ci == ".." && ci != CList.begin() )
+ if ( *i == ".." )
{
- ri = ci;
- ri--;
-
- if ( *ri != ".." )
+ if ( ! out_list.empty() )
{
- CList.erase(ri);
- ri = ci++;
- CList.erase(ri);
- }
- }
+ out_list.pop_back();
+ }
+ }
+ else if ( *i != "." )
+ {
+ out_list.push_back(*i);
+ }
}
-
- return CList;
}
//
std::string
Kumu::PathMakeCanonical(const std::string& Path, char separator)
{
- PathCompList_t CList;
+ PathCompList_t in_list, out_list;
bool is_absolute = PathIsAbsolute(Path, separator);
- s_PathMakeCanonical(PathToComponents(Path, CList, separator), is_absolute);
+ PathToComponents(Path, in_list, separator);
+ make_canonical_list(in_list, out_list);
if ( is_absolute )
- return ComponentsToAbsolutePath(CList, separator);
+ return ComponentsToAbsolutePath(out_list, separator);
- return ComponentsToPath(CList, separator);
+ return ComponentsToPath(out_list, separator);
}
//
bool
Kumu::PathsAreEquivalent(const std::string& lhs, const std::string& rhs)
{
- return PathMakeCanonical(lhs) == PathMakeCanonical(rhs);
+ return PathMakeAbsolute(lhs) == PathMakeAbsolute(rhs);
}
//
Kumu::PathCompList_t&
Kumu::PathToComponents(const std::string& Path, PathCompList_t& CList, char separator)
{
- split(Path, separator, CList);
+ std::string s;
+ s = separator;
+ CList = km_token_split(Path, s);
return CList;
}
//
std::string
-Kumu::PathMakeAbsolute(const std::string& Path, char separator)
+Kumu::PathCwd()
{
- if ( Path.empty() )
- {
- std::string out_path;
- out_path = separator;
- return out_path;
- }
-
- if ( PathIsAbsolute(Path, separator) )
- return Path;
-
char cwd_buf [MaxFilePath];
if ( _getcwd(cwd_buf, MaxFilePath) == 0 )
{
return "";
}
- PathCompList_t CList;
- PathToComponents(cwd_buf, CList);
- CList.push_back(Path);
+ return cwd_buf;
+}
- return ComponentsToAbsolutePath(s_PathMakeCanonical(CList, true), separator);
+//
+std::string
+Kumu::PathMakeAbsolute(const std::string& Path, char separator)
+{
+ if ( Path.empty() )
+ {
+ std::string tmpstr;
+ tmpstr = separator;
+ return tmpstr;
+ }
+
+ if ( PathIsAbsolute(Path, separator) )
+ return PathMakeCanonical(Path);
+
+ PathCompList_t in_list, out_list;
+ PathToComponents(PathJoin(PathCwd(), Path), in_list);
+ make_canonical_list(in_list, out_list);
+
+ return ComponentsToAbsolutePath(out_list);
}
//
return Path1 + separator + Path2 + separator + Path3 + separator + Path4;
}
+#ifndef KM_WIN32
+// returns false if link cannot be read
+//
+bool
+Kumu::PathResolveLinks(const std::string& link_path, std::string& resolved_path, char separator)
+{
+ PathCompList_t in_list, out_list;
+ PathToComponents(PathMakeCanonical(link_path), in_list, separator);
+ PathCompList_t::iterator i;
+ char link_buf[MaxFilePath];
+
+ for ( i = in_list.begin(); i != in_list.end(); ++i )
+ {
+ assert ( *i != ".." && *i != "." );
+ out_list.push_back(*i);
+
+ for (;;)
+ {
+ std::string next_link = ComponentsToAbsolutePath(out_list, separator);
+ ssize_t link_size = readlink(next_link.c_str(), link_buf, MaxFilePath);
+
+ if ( link_size == -1 )
+ {
+ if ( errno == EINVAL )
+ break;
+
+ DefaultLogSink().Error("%s: readlink: %s\n", next_link.c_str(), strerror(errno));
+ return false;
+ }
+
+ assert(link_size < MaxFilePath);
+ link_buf[link_size] = 0;
+ std::string tmp_path;
+ out_list.clear();
+
+ if ( PathIsAbsolute(link_buf) )
+ {
+ tmp_path = link_buf;
+ }
+ else
+ {
+ tmp_path = PathJoin(PathDirname(next_link), link_buf);
+ }
+
+ PathToComponents(PathMakeCanonical(tmp_path), out_list, separator);
+ }
+ }
+
+ resolved_path = ComponentsToAbsolutePath(out_list, separator);
+ return true;
+}
+
+#else // KM_WIN32
+// TODO: is there a reasonable equivalent to be written for win32?
+//
+bool
+Kumu::PathResolveLinks(const std::string& link_path, std::string& resolved_path, char separator)
+{
+ resolved_path = link_path;
+ return true;
+}
+#endif
+
//
Kumu::PathList_t&
Kumu::FindInPaths(const IPathMatch& Pattern, const Kumu::PathList_t& SearchPaths,
Result_t
Kumu::DeletePath(const std::string& pathname)
{
- std::string c_pathname = PathMakeAbsolute(PathMakeCanonical(pathname));
+ std::string c_pathname = PathMakeCanonical(PathMakeAbsolute(pathname));
DefaultLogSink().Debug("DeletePath (%s) c(%s)\n", pathname.c_str(), c_pathname.c_str());
return h__DeletePath(c_pathname);
}
bool PathIsFile(const std::string& Path); // true if the path exists in the filesystem and is a file
bool PathIsDirectory(const std::string& Path); // true if the path exists in the filesystem and is a directory
fsize_t FileSize(const std::string& Path); // returns the size of a regular file, 0 for a directory or device
+ std::string PathCwd();
bool PathsAreEquivalent(const std::string& lhs, const std::string& rhs); // true if paths point to the same filesystem entry
// Returns free space and total space available for the given path
std::string PathMakeAbsolute(const std::string& Path, char separator = '/'); // compute position of relative path using getcwd()
std::string PathMakeLocal(const std::string& Path, const std::string& Parent); // remove Parent from front of Path, if it exists
std::string PathMakeCanonical(const std::string& Path, char separator = '/'); // remove '.' and '..'
+ bool PathResolveLinks(const std::string& link_path, std::string& resolved_path, char separator = '/');
// common operations
std::string PathBasename(const std::string& Path, char separator = '/'); // returns right-most path element (list back())
return result;
}
+//------------------------------------------------------------------------------------------
+
+//
+const char*
+Kumu::km_strnstr(const char *s, const char *find, size_t slen)
+{
+ char c, sc;
+ size_t len;
+
+ if ( ( c = *find++ ) != '\0' )
+ {
+ len = strlen(find);
+ do
+ {
+ do
+ {
+ if ( slen-- < 1 || ( sc = *s++ ) == '\0' )
+ return 0;
+ }
+ while ( sc != c );
+
+ if ( len > slen )
+ return 0;
+ }
+ while ( strncmp(s, find, len) != 0 );
+ --s;
+ }
+
+ return s;
+}
+
+//
+std::list<std::string>
+Kumu::km_token_split(const std::string& str, const std::string& separator)
+{
+ std::list<std::string> components;
+ const char* pstr = str.c_str();
+ const char* r = strstr(pstr, separator.c_str());
+
+ while ( r != 0 )
+ {
+ assert(r >= pstr);
+ if ( r > pstr )
+ {
+ std::string tmp_str;
+ tmp_str.assign(pstr, r - pstr);
+ components.push_back(tmp_str);
+ }
+
+ pstr = r + separator.size();
+ r = strstr(pstr, separator.c_str());
+ }
+
+ if( strlen(pstr) > 0 )
+ components.push_back(std::string(pstr));
+
+ return components;
+}
//
// end KM_util.cpp
hexdump(buf.RoData(), buf.Length());
}
+ // Locates the first occurrence of the null-terminated string s2 in the string s1, where not more
+ // than n characters are searched. Characters that appear after a `\0' character are not searched.
+ // Reproduced here from BSD for portability.
+ const char *km_strnstr(const char *s1, const char *s2, size_t n);
+
+ // Split the input string into tokens using the given separator. If the separator is not found the
+ // entire string will be returned as a single-item list.
+ std::list<std::string> km_token_split(const std::string& str, const std::string& separator);
} // namespace Kumu
endif
# list of all the header files that should be installed
-include_HEADERS = KM_error.h KM_fileio.h KM_log.h KM_memio.h KM_mutex.h \
- KM_platform.h KM_prng.h KM_util.h KM_tai.h KM_xml.h AS_DCP.h AS_02.h
+include_HEADERS = \
+ KM_error.h \
+ KM_fileio.h \
+ KM_log.h \
+ KM_memio.h \
+ KM_mutex.h \
+ KM_platform.h \
+ KM_prng.h \
+ KM_util.h \
+ KM_tai.h \
+ KM_xml.h \
+ AS_DCP.h
+
if DEV_HEADERS
-include_HEADERS += S12MTimecode.h MDD.h Metadata.h KLV.h MXFTypes.h MXF.h Wav.h \
- PCMParserList.h
+include_HEADERS += \
+ S12MTimecode.h \
+ MDD.h \
+ Metadata.h \
+ KLV.h \
+ MXFTypes.h \
+ MXF.h \
+ Wav.h \
+ PCMParserList.h
nodist_include_HEADERS = TimedText_Transform.h
endif
-
# list of the libraries to build and install
-lib_LTLIBRARIES = libkumu.la libasdcp.la libas02.la
+lib_LTLIBRARIES = libkumu.la libasdcp.la
# sources for kumu library
libkumu_la_SOURCES = KM_error.h KM_fileio.cpp KM_fileio.h KM_log.cpp KM_log.h \
if DEV_HEADERS
nodist_libasdcp_la_SOURCES += TimedText_Transform.h TimedText_Transform.cpp
endif
+
libasdcp_la_LDFLAGS = -release @VERSION@
# additional libraries to link against for a library
libasdcp_la_LIBADD = libkumu.la
libasdcp_la_CPPFLAGS = -DASDCP_PLATFORM=\"@host@\"
-
-# sources for as-02 library
-libas02_la_SOURCES = \
- AS_02.h AS_02_MXF.cpp AS_02_JP2K.cpp AS_02_PCM.cpp h__02_Reader.cpp h__02_Writer.cpp AS_02_internal.h
-libas02_la_LDFLAGS = -release @VERSION@
-libas02_la_LIBADD = libasdcp.la libkumu.la
-libas02_la_CPPFLAGS = -DASDCP_PLATFORM=\"@host@\"
-
-
# Python extension
if PYTHON_USE
lib_LTLIBRARIES += libpyasdcp.la
# list of programs to be built and installed
bin_PROGRAMS = \
asdcp-wrap asdcp-unwrap asdcp-util asdcp-info asdcp-test \
- as-02-wrap as-02-unwrap \
j2c-test blackwave klvwalk wavesplit \
kmfilegen kmrandgen kmuuidgen
asdcp_test_SOURCES = asdcp-test.cpp
asdcp_test_LDADD = libasdcp.la
-as_02_wrap_SOURCES = as-02-wrap.cpp
-as_02_wrap_LDADD = libas02.la
-
-as_02_unwrap_SOURCES = as-02-unwrap.cpp
-as_02_unwrap_LDADD = libas02.la
-
asdcp_wrap_SOURCES = asdcp-wrap.cpp
asdcp_wrap_LDADD = libasdcp.la
%s [options] <input-file>+\n\
\n\
Options:\n\
- -3 - Force stereoscopic interpretation of a JP2K file\n\
- -C - Do not show essence coding UL\n\
- -d - Show essence descriptor info\n\
- -h | -help - Show help\n\
- -H - Show MXF header metadata\n\
- -i - Show identity info\n\
- -n - Show index\n\
- -R - Do not show bit-rate (Mb/s)\n\
- -V - Show version information\n\
+ -3 - Force stereoscopic interpretation of a JP2K file\n\
+ -C - Do not show essence coding UL\n\
+ -d - Show essence descriptor info\n\
+ -h | -help - Show help\n\
+ -H - Show MXF header metadata\n\
+ -i - Show identity info\n\
+ -n - Show index\n\
+ -r - Show bit-rate (Mb/s)\n\
+ -t <int> - Set high-bitrate threshold (Mb/s)\n\
+ -V - Show version information\n\
\n\
NOTES: o There is no option grouping, all options must be distinct arguments.\n\
o All option arguments must be separated from the option by whitespace.\n\n",
bool showindex_flag; // true if index is to be displayed
bool showheader_flag; // true if MXF file header is to be displayed
bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
- bool showid_flag;
- bool showdescriptor_flag;
- bool showcoding_flag;
- bool showrate_flag;
+ bool showid_flag; // if true, show file identity info (the WriterInfo struct)
+ bool showdescriptor_flag; // if true, show the essence descriptor
+ bool showcoding_flag; // if true, show the coding UL
+ bool showrate_flag; // if true and is image file, show bit rate
+ bool max_bitrate_flag; // true if -t option given
+ double max_bitrate; // if true and is image file, max bit rate for rate test
//
CommandOptions(int argc, const char** argv) :
error_flag(true), version_flag(false), help_flag(false), verbose_flag(false),
showindex_flag(), showheader_flag(), stereo_image_flag(false),
- showid_flag(false), showdescriptor_flag(false), showcoding_flag(true),
- showrate_flag(true)
+ showid_flag(false), showdescriptor_flag(false), showcoding_flag(false),
+ showrate_flag(false), max_bitrate_flag(false), max_bitrate(0.0)
{
for ( int i = 1; i < argc; ++i )
{
switch ( argv[i][1] )
{
case '3': stereo_image_flag = true; break;
- case 'C': showcoding_flag = false; break;
+ case 'c': showcoding_flag = true; break;
case 'd': showdescriptor_flag = true; break;
case 'H': showheader_flag = true; break;
case 'h': help_flag = true; break;
case 'i': showid_flag = true; break;
case 'n': showindex_flag = true; break;
- case 'R': showrate_flag = false; break;
+ case 'r': showrate_flag = true; break;
+
+ case 't':
+ TEST_EXTRA_ARG(i, 't');
+ max_bitrate = abs(atoi(argv[i]));
+ max_bitrate_flag = true;
+ break;
+
case 'V': version_flag = true; break;
case 'v': verbose_flag = true; break;
}
};
-// MSVC didn't like the function template, so now it's a static class method
+//
+//
template<class ReaderT, class DescriptorT>
class FileInfoWrapper
{
ReaderT m_Reader;
DescriptorT m_Desc;
+ WriterInfo m_WriterInfo;
+ double m_MaxBitrate, m_AvgBitrate;
+ UL m_PictureEssenceCoding;
+
KM_NO_COPY_CONSTRUCT(FileInfoWrapper);
public:
- FileInfoWrapper() {}
+ FileInfoWrapper() : m_MaxBitrate(0.0), m_AvgBitrate(0.0) {}
virtual ~FileInfoWrapper() {}
Result_t
if ( ASDCP_SUCCESS(result) )
{
m_Desc.FillDescriptor(m_Reader);
+ m_Reader.FillWriterInfo(m_WriterInfo);
- fprintf(stdout, "File essence type is %s, (%d frame%s).\n",
+ fprintf(stdout, "File essence type is %s, (%d edit unit%s).\n",
type_string, m_Desc.ContainerDuration, (m_Desc.ContainerDuration==1?"":"s"));
if ( Options.showheader_flag )
m_Reader.DumpHeaderMetadata(stream);
if ( Options.showid_flag )
- {
- WriterInfo WI;
- m_Reader.FillWriterInfo(WI);
- WriterInfoDump(WI, stream);
- }
+ WriterInfoDump(m_WriterInfo, stream);
if ( Options.showdescriptor_flag )
m_Desc.Dump(stream);
}
//
- void dump_PictureEssenceCoding(FILE* stream = 0)
+ void get_PictureEssenceCoding(FILE* stream = 0)
{
const Dictionary& Dict = DefaultCompositeDict();
MXF::RGBAEssenceDescriptor *descriptor = 0;
reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
if ( KM_SUCCESS(result) )
+ m_PictureEssenceCoding = descriptor->PictureEssenceCoding;
+ }
+
+
+ //
+ void dump_PictureEssenceCoding(FILE* stream = 0)
+ {
+ char buf[64];
+
+ if ( m_PictureEssenceCoding.HasValue() )
{
const char *encoding_ul_type = "**UNKNOWN**";
- if ( descriptor->PictureEssenceCoding == UL(P_HFR_UL_2K) )
+ if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
encoding_ul_type = "P-HFR-2K";
- else if ( descriptor->PictureEssenceCoding == UL(P_HFR_UL_4K) )
+ else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
encoding_ul_type = "**P-HFR-4K**";
- else if ( descriptor->PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
+ else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
encoding_ul_type = "ST-429-4-2K";
- else if ( descriptor->PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
+ else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
encoding_ul_type = "ST-429-4-4K";
- char buf[64];
- fprintf(stream, "PictureEssenceCoding: %s (%s)\n", descriptor->PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
+ fprintf(stream, "PictureEssenceCoding: %s (%s)\n", m_PictureEssenceCoding.EncodeString(buf, 64), encoding_ul_type);
}
}
+ //
+ Result_t
+ test_rates(CommandOptions& Options, FILE* stream = 0)
+ {
+ static const double dci_max_bitrate = 250.0;
+ static const double p_hfr_max_bitrate = 400.0;
+
+ double max_bitrate = Options.max_bitrate_flag ? Options.max_bitrate : dci_max_bitrate;
+ ui32_t errors = 0;
+
+ if ( m_PictureEssenceCoding == UL(P_HFR_UL_2K) )
+ {
+ if ( m_Desc.StoredWidth > 2048 ) // 4k
+ {
+ fprintf(stream, "4k images marked as 2k HFR.\n");
+ ++errors;
+ }
+
+ if ( m_Desc.SampleRate < ASDCP::EditRate_96 )
+ {
+ fprintf(stream, "HFR UL used for fps < 96.\n");
+ ++errors;
+ }
+
+ if ( ! Options.max_bitrate_flag )
+ max_bitrate = p_hfr_max_bitrate;
+ }
+ else if ( m_PictureEssenceCoding == UL(P_HFR_UL_4K) )
+ {
+ fprintf(stream, "4k HFR support undefined.\n");
+ ++errors;
+
+ if ( m_Desc.StoredWidth <= 2048 ) // 2k
+ {
+ fprintf(stream, "2k images marked as 4k HFR.\n");
+ ++errors;
+ }
+ }
+ else if ( m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K)
+ && m_PictureEssenceCoding != DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
+ {
+ fprintf(stream, "Unknown PictureEssenceCoding UL value.\n");
+ ++errors;
+ }
+ else
+ {
+ if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_2K) )
+ {
+ if ( m_Desc.StoredWidth > 2048 ) // 4k
+ {
+ fprintf(stream, "4k images marked as 2k ST 429-4.\n");
+ ++errors;
+ }
+ }
+ else if ( m_PictureEssenceCoding == DefaultCompositeDict().ul(MDD_JP2KEssenceCompression_4K) )
+ {
+ if ( m_Desc.StoredWidth <= 2048 ) // 2k
+ {
+ fprintf(stream, "2k images marked as 4k ST 429-4.\n");
+ ++errors;
+ }
+ }
+ }
+
+ if ( m_MaxBitrate > max_bitrate )
+ {
+ fprintf(stream, "Bitrate %0.0f exceeds maximum %0.0f (see option -r).\n", m_MaxBitrate, max_bitrate);
+ ++errors;
+ }
+
+ return errors ? RESULT_FAIL : RESULT_OK;
+ }
+
//
void
- dump_Bitrate(FILE* stream = 0)
+ calc_Bitrate(FILE* stream = 0)
{
MXF::OPAtomIndexFooter& footer = m_Reader.OPAtomIndexFooter();
ui64_t total_frame_bytes = 0, last_stream_offset = 0;
}
}
- // we did not test the first or last frame; scale to return bits when the input is bytes
+ // scale bytes to megabits
static const double mega_const = 1 / ( 1024.0 * 1024.0 / 8.0 );
+
+ // we did not accumulate the first or last frame, so duration -= 2
double avg_bytes_frame = total_frame_bytes / ( m_Desc.ContainerDuration - 2 );
- double avg_mbits_second = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
- fprintf(stream, "Max BitRate: %0.2f Mb/s\n", largest_frame * mega_const * m_Desc.EditRate.Quotient());
- fprintf(stream, "Average BitRate: %0.2f Mb/s\n", avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient());
+ m_MaxBitrate = largest_frame * mega_const * m_Desc.EditRate.Quotient();
+ m_AvgBitrate = avg_bytes_frame * mega_const * m_Desc.EditRate.Quotient();
+ }
+
+ //
+ void
+ dump_Bitrate(FILE* stream = 0)
+ {
+ fprintf(stream, "Max BitRate: %0.2f Mb/s\n", m_MaxBitrate);
+ fprintf(stream, "Average BitRate: %0.2f Mb/s\n", m_AvgBitrate);
}
//
FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor> wrapper;
result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
- if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
- wrapper.dump_PictureEssenceCoding(stdout);
+ if ( KM_SUCCESS(result) )
+ {
+ wrapper.get_PictureEssenceCoding();
+ wrapper.calc_Bitrate();
- if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
- wrapper.dump_Bitrate(stdout);
+ if ( Options.showcoding_flag )
+ wrapper.dump_PictureEssenceCoding(stdout);
+
+ if ( Options.showrate_flag )
+ wrapper.dump_Bitrate(stdout);
+
+ result = wrapper.test_rates(Options, stdout);
+ }
}
else
{
FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>wrapper;
result = wrapper.file_info(Options, "JPEG 2000 pictures");
- if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
- wrapper.dump_PictureEssenceCoding(stdout);
+ if ( KM_SUCCESS(result) )
+ {
+ wrapper.get_PictureEssenceCoding();
+ wrapper.calc_Bitrate();
- if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
- wrapper.dump_Bitrate(stdout);
+ if ( Options.showcoding_flag )
+ wrapper.dump_PictureEssenceCoding(stdout);
+
+ if ( Options.showrate_flag )
+ wrapper.dump_Bitrate(stdout);
+
+ result = wrapper.test_rates(Options, stdout);
+ }
}
}
else if ( EssenceType == ESS_JPEG_2000_S )
FileInfoWrapper<ASDCP::JP2K::MXFSReader, MyStereoPictureDescriptor>wrapper;
result = wrapper.file_info(Options, "JPEG 2000 stereoscopic pictures");
- if ( ASDCP_SUCCESS(result) && Options.showcoding_flag )
- wrapper.dump_PictureEssenceCoding(stdout);
+ if ( KM_SUCCESS(result) )
+ {
+ wrapper.get_PictureEssenceCoding();
+ wrapper.calc_Bitrate();
- if ( ASDCP_SUCCESS(result) && Options.showrate_flag )
- wrapper.dump_Bitrate(stdout);
+ if ( Options.showcoding_flag )
+ wrapper.dump_PictureEssenceCoding(stdout);
+
+ if ( Options.showrate_flag )
+ wrapper.dump_Bitrate(stdout);
+
+ result = wrapper.test_rates(Options, stdout);
+ }
}
else if ( EssenceType == ESS_TIMED_TEXT )
{
//
Rational PictureRate()
{
+ if ( picture_rate == 16 ) return EditRate_16;
+ if ( picture_rate == 18 ) return EditRate_18;
+ if ( picture_rate == 20 ) return EditRate_20;
+ if ( picture_rate == 22 ) return EditRate_22;
if ( picture_rate == 23 ) return EditRate_23_98;
if ( picture_rate == 24 ) return EditRate_24;
if ( picture_rate == 25 ) return EditRate_25;
//
const char* szPictureRate()
{
+ if ( picture_rate == 16 ) return "16";
+ if ( picture_rate == 18 ) return "18.182";
+ if ( picture_rate == 20 ) return "20";
+ if ( picture_rate == 22 ) return "21.818";
if ( picture_rate == 23 ) return "23.976";
if ( picture_rate == 24 ) return "24";
if ( picture_rate == 25 ) return "25";
//
Rational PictureRate()
{
+ if ( picture_rate == 16 ) return EditRate_16;
+ if ( picture_rate == 18 ) return EditRate_18;
+ if ( picture_rate == 20 ) return EditRate_20;
+ if ( picture_rate == 22 ) return EditRate_22;
if ( picture_rate == 23 ) return EditRate_23_98;
if ( picture_rate == 24 ) return EditRate_24;
if ( picture_rate == 25 ) return EditRate_25;
case 'b':
TEST_EXTRA_ARG(i, 'b');
fb_size = abs(atoi(argv[i]));
-
- if ( verbose_flag )
- fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
-
break;
case 'd':
}
}
- if ( ASDCP_SUCCESS(result) )
+ if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
{
char filename[256];
snprintf(filename, 256, "%s.ves", Options.file_prefix);
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
- ui32_t write_count = 0;
- result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ if ( ! Options.no_write_flag )
+ {
+ ui32_t write_count = 0;
+ result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ }
}
}
if ( ASDCP_SUCCESS(result) )
{
- Kumu::FileWriter OutFile;
- ui32_t write_count;
- snprintf(filename, filename_max, left_format, Options.file_prefix, i);
- result = OutFile.OpenWrite(filename);
+ if ( ! Options.no_write_flag )
+ {
+ Kumu::FileWriter OutFile;
+ ui32_t write_count;
+ snprintf(filename, filename_max, left_format, Options.file_prefix, i);
+ result = OutFile.OpenWrite(filename);
- if ( ASDCP_SUCCESS(result) )
- result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ if ( ASDCP_SUCCESS(result) )
+ result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ }
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
if ( ASDCP_SUCCESS(result) )
{
- Kumu::FileWriter OutFile;
- ui32_t write_count;
- snprintf(filename, filename_max, right_format, Options.file_prefix, i);
- result = OutFile.OpenWrite(filename);
+ if ( ! Options.no_write_flag )
+ {
+ Kumu::FileWriter OutFile;
+ ui32_t write_count;
+ snprintf(filename, filename_max, right_format, Options.file_prefix, i);
+ result = OutFile.OpenWrite(filename);
- if ( ASDCP_SUCCESS(result) )
- result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ if ( ASDCP_SUCCESS(result) )
+ result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ }
+
+ if ( Options.verbose_flag )
+ FrameBuffer.Dump(stderr, Options.fb_dump_size);
}
}
if ( ASDCP_SUCCESS(result) )
{
- Kumu::FileWriter OutFile;
- char filename[256];
- ui32_t write_count;
- snprintf(filename, 256, name_format, Options.file_prefix, i);
- result = OutFile.OpenWrite(filename);
-
- if ( ASDCP_SUCCESS(result) )
- result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ if ( ! Options.no_write_flag )
+ {
+ Kumu::FileWriter OutFile;
+ char filename[256];
+ ui32_t write_count;
+ snprintf(filename, 256, name_format, Options.file_prefix, i);
+ result = OutFile.OpenWrite(filename);
+
+ if ( ASDCP_SUCCESS(result) )
+ result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
+ }
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
&& ADesc.EditRate != EditRate_60 )
ADesc.EditRate = Options.PictureRate();
- FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
+ if ( Options.fb_size != FRAME_BUFFER_SIZE )
+ {
+ FrameBuffer.Capacity(Options.fb_size);
+ }
+ else
+ {
+ FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
+ }
if ( Options.verbose_flag )
- PCM::AudioDescriptorDump(ADesc);
+ {
+ fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
+ PCM::AudioDescriptorDump(ADesc);
+ }
}
if ( ASDCP_SUCCESS(result) )
}
ADesc.ContainerDuration = last_frame - Options.start_frame;
- OutWave.OpenWrite(ADesc, Options.file_prefix,
- ( Options.split_wav ? WavFileWriter::ST_STEREO :
- ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
+
+ if ( ! Options.no_write_flag )
+ {
+ OutWave.OpenWrite(ADesc, Options.file_prefix,
+ ( Options.split_wav ? WavFileWriter::ST_STEREO :
+ ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
+ }
}
if ( ASDCP_SUCCESS(result) && Options.key_flag )
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
- result = OutWave.WriteFrame(FrameBuffer);
+ if ( ! Options.no_write_flag )
+ {
+ result = OutWave.WriteFrame(FrameBuffer);
+ }
}
}
result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
- if ( ASDCP_SUCCESS(result) )
+ if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
{
Kumu::FileWriter Writer;
result = Writer.OpenWrite(Options.file_prefix);
{
result = Reader.ReadAncillaryResource(ri->ResourceID, FrameBuffer, Context, HMAC);
- if ( ASDCP_SUCCESS(result) )
+ if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
{
Kumu::FileWriter Writer;
result = Writer.OpenWrite(Kumu::PathJoin(out_path, Kumu::UUID(ri->ResourceID).EncodeHex(buf, 64)).c_str());
if ( ASDCP_SUCCESS(result) )
- {
- if ( Options.verbose_flag )
- FrameBuffer.Dump(stderr, Options.fb_dump_size);
+ result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
- result = Writer.Write(FrameBuffer.RoData(), FrameBuffer.Size(), &write_count);
- }
+ if ( Options.verbose_flag )
+ FrameBuffer.Dump(stderr, Options.fb_dump_size);
}
}
const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
+const byte_t P_HFR_UL_2K[16] = {
+ 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
+ 0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03
+};
+
//------------------------------------------------------------------------------------------
//
// command line option parser class
Options:\n\
-3 - Create a stereoscopic image file. Expects two\n\
directories of JP2K codestreams (directories must have\n\
- an equal number of frames; left eye is first).\n\
- -C <UL> - Set ChannelAssignment UL value\n\
+ an equal number of frames; the left eye is first)\n\
+ -C <UL> - Set ChannelAssignment UL value in a PCM file\n\
-h | -help - Show help\n\
-V - Show version information\n\
-e - Encrypt MPEG or JP2K headers (default)\n\
'7.1DS', 'WTF'\n\
Default is no label (valid for Interop only).\n\
-L - Write SMPTE UL values instead of MXF Interop\n\
+ -P <UL> - Set PictureEssenceCoding UL value in a JP2K file\n\
-p <rate> - fps of picture when wrapping PCM or JP2K:\n\
Use one of [23|24|25|30|48|50|60], 24 is default\n\
-v - Verbose, prints informative messages to stderr\n\
bool asset_id_flag; // true if an asset ID was given
bool encrypt_header_flag; // true if mpeg headers are to be encrypted
bool write_hmac; // true if HMAC values are to be generated and written
- /// bool read_hmac; // true if HMAC values are to be validated
- /// bool split_wav; // true if PCM is to be extracted to stereo WAV files
- /// bool mono_wav; // true if PCM is to be extracted to mono WAV files
bool verbose_flag; // true if the verbose option was selected
ui32_t fb_dump_size; // number of bytes of frame buffer to dump
- /// bool showindex_flag; // true if index is to be displayed
- /// bool showheader_flag; // true if MXF file header is to be displayed
bool no_write_flag; // true if no output files are to be written
bool version_flag; // true if the version display option was selected
bool help_flag; // true if the help display option was selected
bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
- /// ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
ui32_t start_frame; // frame number to begin processing
ui32_t duration; // number of frames to be processed
bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
bool show_ul_values; /// if true, dump the UL table before going tp work.
Kumu::PathList_t filenames; // list of filenames to be processed
UL channel_assignment;
+ UL picture_coding;
//
Rational PictureRate()
{
+ if ( picture_rate == 16 ) return EditRate_16;
+ if ( picture_rate == 18 ) return EditRate_18;
+ if ( picture_rate == 20 ) return EditRate_20;
+ if ( picture_rate == 22 ) return EditRate_22;
if ( picture_rate == 23 ) return EditRate_23_98;
if ( picture_rate == 24 ) return EditRate_24;
if ( picture_rate == 25 ) return EditRate_25;
//
const char* szPictureRate()
{
+ if ( picture_rate == 16 ) return "16";
+ if ( picture_rate == 18 ) return "18.182";
+ if ( picture_rate == 20 ) return "20";
+ if ( picture_rate == 22 ) return "21.818";
if ( picture_rate == 23 ) return "23.976";
if ( picture_rate == 24 ) return "24";
if ( picture_rate == 25 ) return "25";
break;
case 'C':
- TEST_EXTRA_ARG(i, 'U');
+ TEST_EXTRA_ARG(i, 'C');
if ( ! channel_assignment.DecodeHex(argv[i]) )
{
fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
case 'L': use_smpte_labels = true; break;
case 'M': write_hmac = false; break;
+ case 'P':
+ TEST_EXTRA_ARG(i, 'P');
+ if ( ! picture_coding.DecodeHex(argv[i]) )
+ {
+ fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
+ return;
+ }
+ break;
+
case 'p':
TEST_EXTRA_ARG(i, 'p');
picture_rate = abs(atoi(argv[i]));
}
+//------------------------------------------------------------------------------------------
+
+// return false if an error is discovered
+bool
+check_phfr_params(CommandOptions& Options, JP2K::PictureDescriptor& PDesc)
+{
+ Rational rate = Options.PictureRate();
+ if ( rate != EditRate_96 && rate != EditRate_100 && rate != EditRate_120 )
+ return true;
+
+ if ( PDesc.StoredWidth > 2048 )
+ {
+ fprintf(stderr, "P-HFR files currently limited to 2K.\n");
+ return false;
+ }
+
+ if ( ! Options.use_smpte_labels )
+ {
+ fprintf(stderr, "P-HFR files must be written using SMPTE labels. Use option '-L'.\n");
+ return false;
+ }
+
+ // do not set the label if the user has already done so
+ if ( ! Options.picture_coding.HasValue() )
+ Options.picture_coding = UL(P_HFR_UL_2K);
+
+ return true;
+}
+
//------------------------------------------------------------------------------------------
// JPEG 2000 essence
}
}
+ if ( ! check_phfr_params(Options, PDesc) )
+ return RESULT_FAIL;
+
if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
{
WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
if ( ASDCP_SUCCESS(result) )
result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
+
+ if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
+ {
+ MXF::RGBAEssenceDescriptor *descriptor = 0;
+ Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_RGBAEssenceDescriptor),
+ reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
+ descriptor->PictureEssenceCoding = Options.picture_coding;
+ }
}
if ( ASDCP_SUCCESS(result) )
}
}
+ if ( ! check_phfr_params(Options, PDesc) )
+ return RESULT_FAIL;
+
if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
{
WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
if ( ASDCP_SUCCESS(result) )
result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
+
+ if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
+ {
+ MXF::RGBAEssenceDescriptor *descriptor = 0;
+ Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_RGBAEssenceDescriptor),
+ reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
+ descriptor->PictureEssenceCoding = Options.picture_coding;
+ }
}
if ( ASDCP_SUCCESS(result) )
/*
-Copyright (c) 2005-2009, John Hurst
+Copyright (c) 2005-2012, John Hurst
All rights reserved.
Redistribution and use in source and binary forms, with or without
{
fprintf(stream, "\n\
%s (asdcplib %s)\n\n\
-Copyright (c) 2005-2009 John Hurst\n\n\
+Copyright (c) 2005-2012 John Hurst\n\n\
%s is part of asdcplib.\n\
asdcplib may be copied only under the terms of the license found at\n\
the top of every file in the asdcplib distribution kit.\n\n\
\n\
-V - Show version\n\
-h - Show help\n\
- -d <duration> - Number of 2k-sample frames to process, default 1440\n\
+ -d <duration> - Number of edit units to process, default 1440\n\
+ -9 - Make a 96 kHz file (default 48 kHz)\n\
\n\
Other Options:\n\
-v - Verbose, show extra detail during run\n\
bool verbose_flag; // true if the verbose option was selected
bool version_flag; // true if the version display option was selected
bool help_flag; // true if the help display option was selected
+ bool s96_flag; // true if the samples should be at 96 kHz
ui32_t duration; // number of frames to be processed
const char* filename; // filename prefix for files written by the extract mode
{
for ( int i = 1; i < argc; i++ )
{
- if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
+ if ( argv[i][0] == '-' && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) ) && argv[i][2] == 0 )
{
switch ( argv[i][1] )
{
duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
break;
+ case '9':
+ s96_flag = true;
+ break;
+
default:
fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
return;
PCM::AudioDescriptor ADesc;
ADesc.EditRate = Rational(24,1);
- ADesc.AudioSamplingRate = ASDCP::SampleRate_48k;
+ ADesc.AudioSamplingRate = Options.s96_flag ? ASDCP::SampleRate_96k : ASDCP::SampleRate_48k;
ADesc.Locked = 0;
ADesc.ChannelCount = 1;
ADesc.QuantizationBits = 24;
ADesc.BlockAlign = 3;
- ADesc.AvgBps = 14400;
+ ADesc.AvgBps = ADesc.BlockAlign * ADesc.AudioSamplingRate.Quotient();
ADesc.LinkedTrackID = 1;
ADesc.ContainerDuration = Options.duration;
if ( Options.verbose_flag )
{
- fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n", "24",
- PCM::CalcSamplesPerFrame(ADesc));
+ fprintf(stderr, "%s kHz PCM Audio, %s fps (%u spf)\n", "24",
+ (Options.s96_flag?"96":"48"), PCM::CalcSamplesPerFrame(ADesc));
fputs("AudioDescriptor:\n", stderr);
PCM::AudioDescriptorDump(ADesc);
}
}
-//
-ASDCP::h__Reader::h__Reader(const Dictionary& d) :
- m_HeaderPart(m_Dict), m_BodyPart(m_Dict), m_FooterPart(m_Dict), m_Dict(&d), m_EssenceStart(0)
-{
- default_md_object_init();
-}
-
-ASDCP::h__Reader::~h__Reader()
-{
- Close();
-}
-
-void
-ASDCP::h__Reader::Close()
-{
- m_File.Close();
-}
-
//------------------------------------------------------------------------------------------
//
//
-Result_t
-ASDCP::h__Reader::InitInfo()
-{
- assert(m_Dict);
- InterchangeObject* Object;
-
- m_Info.LabelSetType = LS_MXF_UNKNOWN;
-
- if ( m_HeaderPart.OperationalPattern.ExactMatch(MXFInterop_OPAtom_Entry().ul) )
- m_Info.LabelSetType = LS_MXF_INTEROP;
- else if ( m_HeaderPart.OperationalPattern.ExactMatch(SMPTE_390_OPAtom_Entry().ul) )
- m_Info.LabelSetType = LS_MXF_SMPTE;
-
- // Identification
- Result_t result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(Identification), &Object);
-
- if( ASDCP_SUCCESS(result) )
- MD_to_WriterInfo((Identification*)Object, m_Info);
-
- // SourcePackage
- if( ASDCP_SUCCESS(result) )
- result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(SourcePackage), &Object);
-
- if( ASDCP_SUCCESS(result) )
- {
- SourcePackage* SP = (SourcePackage*)Object;
- memcpy(m_Info.AssetUUID, SP->PackageUID.Value() + 16, UUIDlen);
- }
-
- // optional CryptographicContext
- if( ASDCP_SUCCESS(result) )
- {
- Result_t cr_result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(CryptographicContext), &Object);
-
- if( ASDCP_SUCCESS(cr_result) )
- MD_to_CryptoInfo((CryptographicContext*)Object, m_Info, *m_Dict);
- }
+ASDCP::h__ASDCPReader::h__ASDCPReader(const Dictionary& d) : MXF::TrackFileReader<OPAtomHeader, OPAtomIndexFooter>(d), m_BodyPart(m_Dict) {}
+ASDCP::h__ASDCPReader::~h__ASDCPReader() {}
- return result;
-}
-
-// standard method of opening an MXF file for read
+// AS-DCP method of opening an MXF file for read
Result_t
-ASDCP::h__Reader::OpenMXFRead(const char* filename)
+ASDCP::h__ASDCPReader::OpenMXFRead(const char* filename)
{
- m_LastPosition = 0;
- Result_t result = m_File.OpenRead(filename);
+ Result_t result = ASDCP::MXF::TrackFileReader<OPAtomHeader, OPAtomIndexFooter>::OpenMXFRead(filename);
- if ( ASDCP_SUCCESS(result) )
- result = m_HeaderPart.InitFromFile(m_File);
-
- if ( ASDCP_SUCCESS(result) )
+ if ( KM_SUCCESS(result) )
{
// if this is a three partition file, go to the body
// partition and read the partition pack
Array<RIP::Pair>::iterator r_i = m_HeaderPart.m_RIP.PairArray.begin();
r_i++;
m_File.Seek((*r_i).ByteOffset);
-
result = m_BodyPart.InitFromFile(m_File);
}
-
- m_EssenceStart = m_File.Tell();
}
+ if ( KM_SUCCESS(result) )
+ m_HeaderPart.BodyOffset = m_File.Tell();
+
return result;
}
+//
+Result_t
+ASDCP::h__ASDCPReader::InitInfo()
+{
+ Result_t result = ASDCP::MXF::TrackFileReader<OPAtomHeader, OPAtomIndexFooter>::InitInfo();
+
+ if( KM_SUCCESS(result) )
+ {
+ InterchangeObject* Object;
+
+ m_Info.LabelSetType = LS_MXF_UNKNOWN;
+
+ if ( m_HeaderPart.OperationalPattern.ExactMatch(MXFInterop_OPAtom_Entry().ul) )
+ {
+ m_Info.LabelSetType = LS_MXF_INTEROP;
+ }
+ else if ( m_HeaderPart.OperationalPattern.ExactMatch(SMPTE_390_OPAtom_Entry().ul) )
+ {
+ m_Info.LabelSetType = LS_MXF_SMPTE;
+ }
+ }
-// standard method of populating the in-memory index
+ return result;
+}
+
+// AS-DCP method of populating the in-memory index
Result_t
-ASDCP::h__Reader::InitMXFIndex()
+ASDCP::h__ASDCPReader::InitMXFIndex()
{
if ( ! m_File.IsOpen() )
return RESULT_INIT;
}
if ( ASDCP_SUCCESS(result) )
- m_File.Seek(m_EssenceStart);
+ m_File.Seek(m_HeaderPart.BodyOffset);
return result;
}
+
+// AS-DCP method of reading a plaintext or encrypted frame
+Result_t
+ASDCP::h__ASDCPReader::ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
+ const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
+{
+ return ASDCP::MXF::TrackFileReader<OPAtomHeader, OPAtomIndexFooter>::ReadEKLVFrame(m_HeaderPart, FrameNum, FrameBuf,
+ EssenceUL, Ctx, HMAC);
+}
+
+
+//------------------------------------------------------------------------------------------
+//
+
+
//
Result_t
ASDCP::KLReader::ReadKLFromFile(Kumu::FileReader& Reader)
return InitFromBuffer(m_KeyBuf, header_length);
}
-// standard method of reading a plaintext or encrypted frame
+// base subroutine for reading a KLV packet, assumes file position is at the first byte of the packet
Result_t
-ASDCP::h__Reader::ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
- const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
-{
- // look up frame index node
- IndexTableSegment::IndexEntry TmpEntry;
-
- if ( ASDCP_FAILURE(m_FooterPart.Lookup(FrameNum, TmpEntry)) )
- {
- DefaultLogSink().Error("Frame value out of range: %u\n", FrameNum);
- return RESULT_RANGE;
- }
-
- // get frame position and go read the frame's key and length
- Kumu::fpos_t FilePosition = m_EssenceStart + TmpEntry.StreamOffset;
- Result_t result = RESULT_OK;
-
- if ( FilePosition != m_LastPosition )
- {
- m_LastPosition = FilePosition;
- result = m_File.Seek(FilePosition);
- }
-
- if( ASDCP_SUCCESS(result) )
- result = ReadEKLVPacket(FrameNum, FrameNum + 1, FrameBuf, EssenceUL, Ctx, HMAC);
-
- return result;
-}
-
-
-Result_t
-ASDCP::h__Reader::ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
- const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
+ASDCP::Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict, const MXF::OPAtomHeader& HeaderPart,
+ const ASDCP::WriterInfo& Info, Kumu::fpos_t& LastPosition, ASDCP::FrameBuffer& CtFrameBuf,
+ ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
+ const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
{
KLReader Reader;
- Result_t result = Reader.ReadKLFromFile(m_File);
+ Result_t result = Reader.ReadKLFromFile(File);
- if ( ASDCP_FAILURE(result) )
+ if ( KM_FAILURE(result) )
return result;
UL Key(Reader.Key());
ui64_t PacketLength = Reader.Length();
- m_LastPosition = m_LastPosition + Reader.KLLength() + PacketLength;
- assert(m_Dict);
+ LastPosition = LastPosition + Reader.KLLength() + PacketLength;
- if ( Key.MatchIgnoreStream(m_Dict->ul(MDD_CryptEssence)) ) // ignore the stream numbers
+ if ( Key.MatchIgnoreStream(Dict.ul(MDD_CryptEssence)) ) // ignore the stream numbers
{
- if ( ! m_Info.EncryptedEssence )
+ if ( ! Info.EncryptedEssence )
{
DefaultLogSink().Error("EKLV packet found, no Cryptographic Context in header.\n");
return RESULT_FORMAT;
// read encrypted triplet value into internal buffer
assert(PacketLength <= 0xFFFFFFFFL);
- m_CtFrameBuf.Capacity((ui32_t) PacketLength);
+ CtFrameBuf.Capacity((ui32_t) PacketLength);
ui32_t read_count;
- result = m_File.Read(m_CtFrameBuf.Data(), (ui32_t) PacketLength,
- &read_count);
+ result = File.Read(CtFrameBuf.Data(), (ui32_t) PacketLength, &read_count);
if ( ASDCP_FAILURE(result) )
return result;
return RESULT_FORMAT;
}
- m_CtFrameBuf.Size((ui32_t) PacketLength);
+ CtFrameBuf.Size((ui32_t) PacketLength);
// should be const but mxflib::ReadBER is not
- byte_t* ess_p = m_CtFrameBuf.Data();
+ byte_t* ess_p = CtFrameBuf.Data();
// read context ID length
if ( ! Kumu::read_test_BER(&ess_p, UUIDlen) )
return RESULT_FORMAT;
// test the context ID
- if ( memcmp(ess_p, m_Info.ContextID, UUIDlen) != 0 )
+ if ( memcmp(ess_p, Info.ContextID, UUIDlen) != 0 )
{
DefaultLogSink().Error("Packet's Cryptographic Context ID does not match the header.\n");
return RESULT_FORMAT;
if ( ! UL(ess_p).MatchIgnoreStream(EssenceUL) ) // ignore the stream number
{
char strbuf[IntBufferLen];
- const MDDEntry* Entry = m_Dict->FindUL(Key.Value());
+ const MDDEntry* Entry = Dict.FindUL(Key.Value());
if ( Entry == 0 )
- DefaultLogSink().Warn("Unexpected Encrypted Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
+ DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
else
- DefaultLogSink().Warn("Unexpected Encrypted Essence UL found: %s.\n", Entry->name);
+ DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
return RESULT_FORMAT;
}
ess_p += SMPTE_UL_LENGTH;
return RESULT_FORMAT;
}
- ui32_t tmp_len = esv_length + (m_Info.UsesHMAC ? klv_intpack_size : 0);
+ ui32_t tmp_len = esv_length + (Info.UsesHMAC ? klv_intpack_size : 0);
if ( PacketLength < tmp_len )
{
FrameBuf.FrameNumber(FrameNum);
// detect and test integrity pack
- if ( ASDCP_SUCCESS(result) && m_Info.UsesHMAC && HMAC )
+ if ( ASDCP_SUCCESS(result) && Info.UsesHMAC && HMAC )
{
IntegrityPack IntPack;
- result = IntPack.TestValues(TmpWrapper, m_Info.AssetUUID, SequenceNum, HMAC);
+ result = IntPack.TestValues(TmpWrapper, Info.AssetUUID, SequenceNum, HMAC);
}
}
else // return ciphertext to caller
// read the data into the supplied buffer
ui32_t read_count;
assert(PacketLength <= 0xFFFFFFFFL);
- result = m_File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count);
+ result = File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count);
if ( ASDCP_FAILURE(result) )
return result;
else
{
char strbuf[IntBufferLen];
- const MDDEntry* Entry = m_Dict->FindUL(Key.Value());
+ const MDDEntry* Entry = Dict.FindUL(Key.Value());
if ( Entry == 0 )
DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
else
DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
+
return RESULT_FORMAT;
}
FindInPaths(PathMatchAny(), InList, OutList);
PathList_t::iterator pi;
- for ( pi = OutList.begin(); pi != OutList.end(); pi++ )
- cerr << *pi << endl;
+ if ( false )
+ {
+ for ( pi = OutList.begin(); pi != OutList.end(); pi++ )
+ cerr << *pi << endl;
+ }
+ else
+ {
+ cerr << OutList.size() << ( ( OutList.size() == 1 ) ? " file" : " files" ) << endl;
+ }
cerr << "----------------------------------" << endl;
OutList.clear();