support Opus in OGG
This commit is contained in:
parent
098a0bdef8
commit
240e7d0b42
|
@ -32,6 +32,7 @@ set(HEADER_FILES
|
|||
ogg/oggiterator.h
|
||||
ogg/oggpage.h
|
||||
ogg/oggstream.h
|
||||
opus/opusidentificationheader.h
|
||||
positioninset.h
|
||||
signature.h
|
||||
size.h
|
||||
|
@ -101,6 +102,7 @@ set(SRC_FILES
|
|||
ogg/oggiterator.cpp
|
||||
ogg/oggpage.cpp
|
||||
ogg/oggstream.cpp
|
||||
opus/opusidentificationheader.cpp
|
||||
signature.cpp
|
||||
statusprovider.cpp
|
||||
tag.cpp
|
||||
|
@ -139,9 +141,9 @@ set(META_APP_NAME "Tag Parser")
|
|||
set(META_APP_AUTHOR "Martchus")
|
||||
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
|
||||
set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags.")
|
||||
set(META_VERSION_MAJOR 4)
|
||||
set(META_VERSION_MAJOR 5)
|
||||
set(META_VERSION_MINOR 0)
|
||||
set(META_VERSION_PATCH 2)
|
||||
set(META_VERSION_PATCH 0)
|
||||
|
||||
# stringification of meta data
|
||||
set(META_PROJECT_NAME_STR "\"${META_PROJECT_NAME}\"")
|
||||
|
|
|
@ -25,9 +25,6 @@ OggContainer::OggContainer(MediaFileInfo &fileInfo, uint64 startOffset) :
|
|||
m_validateChecksums(false)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Destroys the container.
|
||||
*/
|
||||
OggContainer::~OggContainer()
|
||||
{}
|
||||
|
||||
|
@ -42,7 +39,6 @@ void OggContainer::internalParseHeader()
|
|||
static const string context("parsing OGG bitstream header");
|
||||
// iterate through pages using OggIterator helper class
|
||||
try {
|
||||
uint32 pageSequenceNumber = 0;
|
||||
// ensure iterator is setup properly
|
||||
for(m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage()) {
|
||||
const OggPage &page = m_iterator.currentPage();
|
||||
|
@ -51,25 +47,29 @@ void OggContainer::internalParseHeader()
|
|||
addNotification(NotificationType::Warning, "The denoted checksum of the OGG page at " + ConversionUtilities::numberToString(m_iterator.currentSegmentOffset()) + " does not match the computed checksum.", context);
|
||||
}
|
||||
}
|
||||
if(!m_streamsBySerialNo.count(page.streamSerialNumber())) {
|
||||
OggStream *stream;
|
||||
try {
|
||||
stream = m_tracks[m_streamsBySerialNo.at(page.streamSerialNumber())].get();
|
||||
} catch(const out_of_range &) {
|
||||
// new stream serial number recognized -> add new stream
|
||||
m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
|
||||
m_tracks.emplace_back(new OggStream(*this, m_iterator.currentPageIndex()));
|
||||
m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex()));
|
||||
stream = m_tracks.back().get();
|
||||
}
|
||||
if(pageSequenceNumber != page.sequenceNumber()) {
|
||||
if(pageSequenceNumber != 0) {
|
||||
if(stream->m_currentSequenceNumber != page.sequenceNumber()) {
|
||||
if(stream->m_currentSequenceNumber) {
|
||||
addNotification(NotificationType::Warning, "Page is missing (page sequence number omitted).", context);
|
||||
pageSequenceNumber = page.sequenceNumber();
|
||||
}
|
||||
stream->m_currentSequenceNumber = page.sequenceNumber() + 1;
|
||||
} else {
|
||||
++pageSequenceNumber;
|
||||
++stream->m_currentSequenceNumber;
|
||||
}
|
||||
}
|
||||
} catch(TruncatedDataException &) {
|
||||
} catch(const TruncatedDataException &) {
|
||||
// thrown when page exceeds max size
|
||||
addNotification(NotificationType::Critical, "The OGG file is truncated.", context);
|
||||
throw;
|
||||
} catch(InvalidDataException &) {
|
||||
} catch(const InvalidDataException &) {
|
||||
// thrown when first 4 byte do not match capture pattern
|
||||
addNotification(NotificationType::Critical, "Capture pattern \"OggS\" at " + ConversionUtilities::numberToString(m_iterator.currentSegmentOffset()) + " expected.", context);
|
||||
throw;
|
||||
|
@ -79,19 +79,28 @@ void OggContainer::internalParseHeader()
|
|||
void OggContainer::internalParseTags()
|
||||
{
|
||||
parseTracks(); // tracks needs to be parsed because tags are stored at stream level
|
||||
for(auto &i : m_commentTable) {
|
||||
//fileInfo().stream().seekg(get<1>(i));
|
||||
m_iterator.setPageIndex(i.firstPageIndex);
|
||||
m_iterator.setSegmentIndex(i.firstSegmentIndex);
|
||||
m_tags[i.tagIndex]->parse(m_iterator);
|
||||
i.lastPageIndex = m_iterator.currentPageIndex();
|
||||
i.lastSegmentIndex = m_iterator.currentSegmentIndex();
|
||||
for(VorbisCommentInfo &vorbisCommentInfo : m_commentTable) {
|
||||
m_iterator.setPageIndex(vorbisCommentInfo.firstPageIndex);
|
||||
m_iterator.setSegmentIndex(vorbisCommentInfo.firstSegmentIndex);
|
||||
switch(vorbisCommentInfo.streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
m_tags[vorbisCommentInfo.tagIndex]->parse(m_iterator);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
m_iterator.seekForward(8); // skip header (has already been detected by OggStream)
|
||||
m_tags[vorbisCommentInfo.tagIndex]->parse(m_iterator, true);
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Critical, "Stream format not supported.", "parsing tags from OGG streams");
|
||||
}
|
||||
vorbisCommentInfo.lastPageIndex = m_iterator.currentPageIndex();
|
||||
vorbisCommentInfo.lastSegmentIndex = m_iterator.currentSegmentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void OggContainer::ariseComment(vector<OggPage>::size_type pageIndex, vector<uint32>::size_type segmentIndex)
|
||||
void OggContainer::ariseComment(vector<OggPage>::size_type pageIndex, vector<uint32>::size_type segmentIndex, GeneralMediaFormat mediaFormat)
|
||||
{
|
||||
m_commentTable.emplace_back(pageIndex, segmentIndex, m_tags.size());
|
||||
m_commentTable.emplace_back(pageIndex, segmentIndex, m_tags.size(), mediaFormat);
|
||||
m_tags.emplace_back(make_unique<VorbisComment>());
|
||||
}
|
||||
|
||||
|
@ -99,14 +108,14 @@ void OggContainer::internalParseTracks()
|
|||
{
|
||||
if(!areTracksParsed()) {
|
||||
parseHeader();
|
||||
static const string context("parsing OGG bit streams");
|
||||
static const string context("parsing OGG stream");
|
||||
for(auto &stream : m_tracks) {
|
||||
try { // try to parse header
|
||||
stream->parseHeader();
|
||||
if(stream->duration() > m_duration) {
|
||||
m_duration = stream->duration();
|
||||
}
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
addNotification(NotificationType::Critical, "Unable to parse stream at " + ConversionUtilities::numberToString(stream->startOffset()) + ".", context);
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +163,18 @@ void OggContainer::internalMakeFile()
|
|||
if(m_iterator.currentPageIndex() == commentTableIterator->firstPageIndex) {
|
||||
// make Vorbis Comment segment
|
||||
auto offset = buffer.tellp();
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer);
|
||||
switch(commentTableIterator->streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
ConversionUtilities::BE::getBytes(0x4F70757354616773u, copy.buffer());
|
||||
buffer.write(copy.buffer(), 8);
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer, true);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
newSegmentSizes.push_back(buffer.tellp() - offset);
|
||||
}
|
||||
if(m_iterator.currentPageIndex() > commentTableIterator->lastPageIndex
|
||||
|
|
|
@ -18,21 +18,23 @@ class MediaFileInfo;
|
|||
|
||||
struct LIB_EXPORT VorbisCommentInfo
|
||||
{
|
||||
VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex);
|
||||
VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex, GeneralMediaFormat streamFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::vector<OggPage>::size_type firstPageIndex;
|
||||
std::vector<OggPage>::size_type firstSegmentIndex;
|
||||
std::vector<OggPage>::size_type lastPageIndex;
|
||||
std::vector<OggPage>::size_type lastSegmentIndex;
|
||||
std::vector<std::unique_ptr<VorbisComment> >::size_type tagIndex;
|
||||
GeneralMediaFormat streamFormat;
|
||||
};
|
||||
|
||||
inline VorbisCommentInfo::VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex) :
|
||||
inline VorbisCommentInfo::VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex, GeneralMediaFormat mediaFormat) :
|
||||
firstPageIndex(firstPageIndex),
|
||||
firstSegmentIndex(firstSegmentIndex),
|
||||
lastPageIndex(0),
|
||||
lastSegmentIndex(0),
|
||||
tagIndex(tagIndex)
|
||||
tagIndex(tagIndex),
|
||||
streamFormat(mediaFormat)
|
||||
{}
|
||||
|
||||
class LIB_EXPORT OggContainer : public GenericContainer<MediaFileInfo, VorbisComment, OggStream, OggPage>
|
||||
|
@ -54,7 +56,7 @@ protected:
|
|||
void internalMakeFile();
|
||||
|
||||
private:
|
||||
void ariseComment(std::vector<OggPage>::size_type pageIndex, std::vector<uint32>::size_type segmentIndex);
|
||||
void ariseComment(std::vector<OggPage>::size_type pageIndex, std::vector<uint32>::size_type segmentIndex, GeneralMediaFormat mediaFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::unordered_map<uint32, std::vector<std::unique_ptr<OggStream> >::size_type> m_streamsBySerialNo;
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ public:
|
|||
|
||||
std::istream &stream();
|
||||
void setStream(std::istream &stream);
|
||||
uint64 startOffset() const;
|
||||
uint64 streamSize() const;
|
||||
void reset();
|
||||
void nextPage();
|
||||
void nextSegment();
|
||||
|
@ -34,6 +36,7 @@ public:
|
|||
bool areAllPagesFetched() const;
|
||||
void read(char *buffer, size_t count);
|
||||
void seekForward(size_t count);
|
||||
bool bytesRemaining(size_t atLeast) const;
|
||||
|
||||
operator bool() const;
|
||||
OggIterator &operator ++();
|
||||
|
@ -92,6 +95,22 @@ inline void OggIterator::setStream(std::istream &stream)
|
|||
m_stream = &stream;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the start offset (which has been specified when constructing the iterator).
|
||||
*/
|
||||
inline uint64 OggIterator::startOffset() const
|
||||
{
|
||||
return m_startOffset;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the stream size (which has been specified when constructing the iterator).
|
||||
*/
|
||||
inline uint64 OggIterator::streamSize() const
|
||||
{
|
||||
return m_streamSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns a vector of containing the OGG pages that have been fetched yet.
|
||||
*/
|
||||
|
@ -226,6 +245,14 @@ inline bool OggIterator::areAllPagesFetched() const
|
|||
return (m_pages.empty() ? m_startOffset : m_pages.back().startOffset() + m_pages.back().totalSize()) >= m_streamSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether there are \a atLeast bytes remaining.
|
||||
*/
|
||||
inline bool OggIterator::bytesRemaining(size_t atLeast) const
|
||||
{
|
||||
return *this && currentCharacterOffset() + atLeast <= streamSize();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Increments the current position by one segment if the iterator is valid; otherwise nothing happens.
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "../vorbis/vorbispackagetypes.h"
|
||||
#include "../vorbis/vorbisidentificationheader.h"
|
||||
|
||||
#include "../opus/opusidentificationheader.h"
|
||||
|
||||
#include "../mediafileinfo.h"
|
||||
#include "../exceptions.h"
|
||||
#include "../mediaformat.h"
|
||||
|
@ -28,7 +30,8 @@ namespace Media {
|
|||
OggStream::OggStream(OggContainer &container, vector<OggPage>::size_type startPage) :
|
||||
AbstractTrack(container.stream(), container.m_iterator.pages()[startPage].startOffset()),
|
||||
m_startPage(startPage),
|
||||
m_container(container)
|
||||
m_container(container),
|
||||
m_currentSequenceNumber(0)
|
||||
{}
|
||||
|
||||
/*!
|
||||
|
@ -52,12 +55,12 @@ void OggStream::internalParseHeader()
|
|||
bool hasIdentificationHeader = false;
|
||||
bool hasCommentHeader = false;
|
||||
for(; iterator; ++iterator) {
|
||||
uint32 currentSize = iterator.currentSegmentSize();
|
||||
const uint32 currentSize = iterator.currentSegmentSize();
|
||||
m_size += currentSize;
|
||||
if(currentSize >= 8) {
|
||||
// determine stream format
|
||||
inputStream().seekg(iterator.currentSegmentOffset());
|
||||
uint64 sig = reader().readUInt64BE();
|
||||
const uint64 sig = reader().readUInt64BE();
|
||||
if((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) {
|
||||
// Vorbis header detected
|
||||
// set Vorbis as format
|
||||
|
@ -75,8 +78,10 @@ void OggStream::internalParseHeader()
|
|||
switch(sig >> 56) {
|
||||
case VorbisPackageTypes::Identification:
|
||||
if(!hasIdentificationHeader) {
|
||||
// parse identification header
|
||||
VorbisIdentificationHeader ind;
|
||||
ind.parseHeader(iterator);
|
||||
m_version = ind.version();
|
||||
m_channelCount = ind.channels();
|
||||
m_samplingFrequency = ind.sampleRate();
|
||||
if(ind.nominalBitrate()) {
|
||||
|
@ -87,6 +92,7 @@ void OggStream::internalParseHeader()
|
|||
if(m_bitrate) {
|
||||
m_bitrate = static_cast<double>(m_bitrate) / 1000.0;
|
||||
}
|
||||
// determine sample count and duration if all pages have been fetched
|
||||
if(iterator.areAllPagesFetched()) {
|
||||
auto pred = [this] (const OggPage &page) -> bool {
|
||||
return page.streamSerialNumber() == this->id();
|
||||
|
@ -105,9 +111,9 @@ void OggStream::internalParseHeader()
|
|||
}
|
||||
break;
|
||||
case VorbisPackageTypes::Comments:
|
||||
// Vorbis comment found -> notify container about comment
|
||||
if(!hasCommentHeader) {
|
||||
//m_container.m_commentOffsets.push_back(iterator.currentOffset());
|
||||
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex());
|
||||
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), GeneralMediaFormat::Vorbis);
|
||||
hasCommentHeader = true;
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Vorbis comment header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
|
@ -118,7 +124,84 @@ void OggStream::internalParseHeader()
|
|||
default:
|
||||
;
|
||||
}
|
||||
} // currently only Vorbis supported
|
||||
} else if(sig == 0x4F70757348656164u) {
|
||||
// Opus header detected
|
||||
// set Opus as format
|
||||
switch(m_format.general) {
|
||||
case GeneralMediaFormat::Unknown:
|
||||
m_format = GeneralMediaFormat::Opus;
|
||||
m_mediaType = MediaType::Audio;
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
if(!hasIdentificationHeader) {
|
||||
// parse identification header
|
||||
OpusIdentificationHeader ind;
|
||||
ind.parseHeader(iterator);
|
||||
m_version = ind.version();
|
||||
m_channelCount = ind.channels();
|
||||
m_samplingFrequency = ind.sampleRate();
|
||||
// determine sample count and duration if all pages have been fetched
|
||||
if(iterator.areAllPagesFetched()) {
|
||||
auto pred = [this] (const OggPage &page) -> bool {
|
||||
return page.streamSerialNumber() == this->id();
|
||||
};
|
||||
const auto &pages = iterator.pages();
|
||||
auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
|
||||
auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
|
||||
if(firstPage != pages.cend() && lastPage != pages.crend()) {
|
||||
m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
|
||||
// must apply "pre-skip" here do calculate effective sample count and duration?
|
||||
if(m_sampleCount > ind.preSkip()) {
|
||||
m_sampleCount -= ind.preSkip();
|
||||
} else {
|
||||
m_sampleCount = 0;
|
||||
}
|
||||
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
|
||||
}
|
||||
}
|
||||
hasIdentificationHeader = true;
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Opus identification header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
}
|
||||
} else if(sig == 0x4F70757354616773u) {
|
||||
// Opus comment detected
|
||||
// set Opus as format
|
||||
switch(m_format.general) {
|
||||
case GeneralMediaFormat::Unknown:
|
||||
m_format = GeneralMediaFormat::Opus;
|
||||
m_mediaType = MediaType::Audio;
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
// notify container about comment
|
||||
if(!hasCommentHeader) {
|
||||
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), GeneralMediaFormat::Opus);
|
||||
hasCommentHeader = true;
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Opus tags/comment header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
}
|
||||
} else if((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
|
||||
// Theora header detected
|
||||
// set Theora as format
|
||||
switch(m_format.general) {
|
||||
case GeneralMediaFormat::Unknown:
|
||||
m_format = GeneralMediaFormat::Theora;
|
||||
m_mediaType = MediaType::Video;
|
||||
break;
|
||||
case GeneralMediaFormat::Theora:
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
// TODO: read more information about Theora stream
|
||||
} // currently only Vorbis, Opus and Theora can be detected
|
||||
}
|
||||
}
|
||||
if(m_duration.isNull() && m_size && m_bitrate) {
|
||||
|
|
|
@ -26,6 +26,7 @@ protected:
|
|||
private:
|
||||
std::vector<OggPage>::size_type m_startPage;
|
||||
OggContainer &m_container;
|
||||
uint32 m_currentSequenceNumber;
|
||||
};
|
||||
|
||||
inline TrackType OggStream::type() const
|
||||
|
|
|
@ -4,7 +4,7 @@ appname = "Tag Parser"
|
|||
appauthor = Martchus
|
||||
appurl = "https://github.com/$${appauthor}/$${projectname}"
|
||||
QMAKE_TARGET_DESCRIPTION = "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags."
|
||||
VERSION = 4.0.2
|
||||
VERSION = 5.0.0
|
||||
|
||||
# include ../../common.pri when building as part of a subdirs project; otherwise include general.pri
|
||||
!include(../../common.pri) {
|
||||
|
@ -78,6 +78,7 @@ HEADERS += \
|
|||
ogg/oggcontainer.h \
|
||||
vorbis/vorbispackagetypes.h \
|
||||
vorbis/vorbisidentificationheader.h \
|
||||
opus/opusidentificationheader.h \
|
||||
ogg/oggiterator.h \
|
||||
vorbis/vorbiscommentids.h \
|
||||
abstractchapter.h \
|
||||
|
@ -136,6 +137,7 @@ SOURCES += \
|
|||
ogg/oggstream.cpp \
|
||||
ogg/oggcontainer.cpp \
|
||||
vorbis/vorbisidentificationheader.cpp \
|
||||
opus/opusidentificationheader.cpp \
|
||||
ogg/oggiterator.cpp \
|
||||
vorbis/vorbiscommentids.cpp \
|
||||
abstractchapter.cpp \
|
||||
|
|
|
@ -64,6 +64,7 @@ string VorbisComment::fieldId(KnownField field) const
|
|||
case KnownField::Description: return description();
|
||||
case KnownField::RecordLabel: return label();
|
||||
case KnownField::Performers: return performer();
|
||||
case KnownField::Language: return language();
|
||||
case KnownField::Lyricist: return lyricist();
|
||||
default: return string();
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ KnownField VorbisComment::knownField(const string &id) const
|
|||
* \throws Throws Media::Failure or a derived exception when a parsing
|
||||
* error occurs.
|
||||
*/
|
||||
void VorbisComment::parse(OggIterator &iterator)
|
||||
void VorbisComment::parse(OggIterator &iterator, bool skipSignature)
|
||||
{
|
||||
// prepare parsing
|
||||
invalidateStatus();
|
||||
|
@ -115,15 +116,23 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
try {
|
||||
// read signature: 0x3 + "vorbis"
|
||||
char sig[8];
|
||||
iterator.read(sig, 7);
|
||||
if((ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u) {
|
||||
if(!skipSignature) {
|
||||
iterator.read(sig, 7);
|
||||
skipSignature = (ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
|
||||
}
|
||||
if(skipSignature) {
|
||||
// read vendor (length prefixed string)
|
||||
{
|
||||
iterator.read(sig, 4);
|
||||
auto vendorSize = LE::toUInt32(sig);
|
||||
auto buff = make_unique<char []>(vendorSize);
|
||||
iterator.read(buff.get(), vendorSize);
|
||||
m_vendor = string(buff.get(), vendorSize);
|
||||
if(iterator.currentCharacterOffset() + vendorSize <= iterator.streamSize()) {
|
||||
auto buff = make_unique<char []>(vendorSize);
|
||||
iterator.read(buff.get(), vendorSize);
|
||||
m_vendor = string(buff.get(), vendorSize);
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Vendor information is truncated.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
}
|
||||
// read field count
|
||||
iterator.read(sig, 4);
|
||||
|
@ -135,10 +144,10 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
try {
|
||||
field.parse(iterator);
|
||||
fields().insert(pair<fieldType::identifierType, fieldType>(fieldId, field));
|
||||
} catch(TruncatedDataException &) {
|
||||
} catch(const TruncatedDataException &) {
|
||||
addNotifications(field);
|
||||
throw;
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
// nothing to do here since notifications will be added anyways
|
||||
}
|
||||
addNotifications(field);
|
||||
|
@ -150,7 +159,7 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
addNotification(NotificationType::Critical, "Signature is invalid.", context);
|
||||
throw InvalidDataException();
|
||||
}
|
||||
} catch(TruncatedDataException &) {
|
||||
} catch(const TruncatedDataException &) {
|
||||
m_size = static_cast<uint32>(static_cast<uint64>(iterator.currentCharacterOffset()) - startOffset);
|
||||
addNotification(NotificationType::Critical, "Vorbis comment is truncated.", context);
|
||||
throw;
|
||||
|
@ -164,7 +173,7 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
* \throws Throws Media::Failure or a derived exception when a making
|
||||
* error occurs.
|
||||
*/
|
||||
void VorbisComment::make(std::ostream &stream)
|
||||
void VorbisComment::make(std::ostream &stream, bool noSignature)
|
||||
{
|
||||
// prepare making
|
||||
invalidateStatus();
|
||||
|
@ -176,9 +185,11 @@ void VorbisComment::make(std::ostream &stream)
|
|||
addNotification(NotificationType::Warning, "Can not convert the assigned vendor to string.", context);
|
||||
}
|
||||
BinaryWriter writer(&stream);
|
||||
// write signature
|
||||
static const char sig[7] = {0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73};
|
||||
stream.write(sig, sizeof(sig));
|
||||
if(!noSignature) {
|
||||
// write signature
|
||||
static const char sig[7] = {0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73};
|
||||
stream.write(sig, sizeof(sig));
|
||||
}
|
||||
// write vendor
|
||||
writer.writeUInt32LE(vendor.size());
|
||||
writer.writeString(vendor);
|
||||
|
|
|
@ -25,8 +25,8 @@ public:
|
|||
std::string fieldId(KnownField field) const;
|
||||
KnownField knownField(const std::string &id) const;
|
||||
|
||||
void parse(OggIterator &iterator);
|
||||
void make(std::ostream &stream);
|
||||
void parse(OggIterator &iterator, bool skipSignature = false);
|
||||
void make(std::ostream &stream, bool noSignature = false);
|
||||
|
||||
const TagValue &vendor() const;
|
||||
void setVendor(const TagValue &vendor);
|
||||
|
|
|
@ -55,46 +55,51 @@ void VorbisCommentField::parse(OggIterator &iterator)
|
|||
char buff[4];
|
||||
iterator.read(buff, 4);
|
||||
if(auto size = LE::toUInt32(buff)) { // read size
|
||||
// read data
|
||||
auto data = make_unique<char []>(size);
|
||||
iterator.read(data.get(), size);
|
||||
uint32 idSize = 0;
|
||||
for(const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize);
|
||||
// extract id
|
||||
setId(string(data.get(), idSize));
|
||||
if(!idSize) {
|
||||
// empty field ID
|
||||
addNotification(NotificationType::Critical, "The field ID is empty.", context);
|
||||
throw InvalidDataException();
|
||||
} else if(id() == VorbisCommentIds::cover()) {
|
||||
// extract cover value
|
||||
try {
|
||||
auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
|
||||
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
|
||||
ss.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
ss.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
|
||||
BinaryReader reader(&ss);
|
||||
setTypeInfo(reader.readUInt32BE());
|
||||
auto size = reader.readUInt32BE();
|
||||
value().setMimeType(reader.readString(size));
|
||||
size = reader.readUInt32BE();
|
||||
value().setDescription(reader.readString(size));
|
||||
// skip width, height, color depth, number of colors used
|
||||
ss.seekg(4 * 4, ios_base::cur);
|
||||
size = reader.readUInt32BE();
|
||||
auto data = make_unique<char[]>(size);
|
||||
ss.read(data.get(), size);
|
||||
value().assignData(move(data), size, TagDataType::Picture);
|
||||
} catch (const ios_base::failure &) {
|
||||
addNotification(NotificationType::Critical, "An IO error occured when reading the METADATA_BLOCK_PICTURE struct.", context);
|
||||
throw Failure();
|
||||
} catch (const ConversionException &) {
|
||||
addNotification(NotificationType::Critical, "Base64 data from METADATA_BLOCK_PICTURE is invalid.", context);
|
||||
if(iterator.currentCharacterOffset() + size <= iterator.streamSize()) {
|
||||
// read data
|
||||
auto data = make_unique<char []>(size);
|
||||
iterator.read(data.get(), size);
|
||||
uint32 idSize = 0;
|
||||
for(const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize);
|
||||
// extract id
|
||||
setId(string(data.get(), idSize));
|
||||
if(!idSize) {
|
||||
// empty field ID
|
||||
addNotification(NotificationType::Critical, "The field ID is empty.", context);
|
||||
throw InvalidDataException();
|
||||
} else if(id() == VorbisCommentIds::cover()) {
|
||||
// extract cover value
|
||||
try {
|
||||
auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
|
||||
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
|
||||
ss.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
ss.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
|
||||
BinaryReader reader(&ss);
|
||||
setTypeInfo(reader.readUInt32BE());
|
||||
auto size = reader.readUInt32BE();
|
||||
value().setMimeType(reader.readString(size));
|
||||
size = reader.readUInt32BE();
|
||||
value().setDescription(reader.readString(size));
|
||||
// skip width, height, color depth, number of colors used
|
||||
ss.seekg(4 * 4, ios_base::cur);
|
||||
size = reader.readUInt32BE();
|
||||
auto data = make_unique<char[]>(size);
|
||||
ss.read(data.get(), size);
|
||||
value().assignData(move(data), size, TagDataType::Picture);
|
||||
} catch (const ios_base::failure &) {
|
||||
addNotification(NotificationType::Critical, "An IO error occured when reading the METADATA_BLOCK_PICTURE struct.", context);
|
||||
throw Failure();
|
||||
} catch (const ConversionException &) {
|
||||
addNotification(NotificationType::Critical, "Base64 data from METADATA_BLOCK_PICTURE is invalid.", context);
|
||||
throw InvalidDataException();
|
||||
}
|
||||
} else if(id().size() + 1 < size) {
|
||||
// extract other values (as string)
|
||||
setValue(string(data.get() + idSize + 1, size - idSize - 1));
|
||||
}
|
||||
} else if(id().size() + 1 < size) {
|
||||
// extract other values (as string)
|
||||
setValue(string(data.get() + idSize + 1, size - idSize - 1));
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Field is truncated.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ inline LIB_EXPORT const char *label() {
|
|||
inline LIB_EXPORT const char *labelNo() {
|
||||
return "LABELNO";
|
||||
}
|
||||
inline LIB_EXPORT const char *language() {
|
||||
return "LANGUAGE";
|
||||
}
|
||||
inline LIB_EXPORT const char *performer() {
|
||||
return "PERFORMER";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue