support Opus in OGG

This commit is contained in:
Martchus 2016-01-17 19:32:58 +01:00
parent 098a0bdef8
commit 240e7d0b42
11 changed files with 246 additions and 90 deletions

View File

@ -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}\"")

View File

@ -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

View File

@ -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;

View File

@ -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.
*/

View File

@ -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) {

View File

@ -26,6 +26,7 @@ protected:
private:
std::vector<OggPage>::size_type m_startPage;
OggContainer &m_container;
uint32 m_currentSequenceNumber;
};
inline TrackType OggStream::type() const

View File

@ -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 \

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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";
}