updated readme

This commit is contained in:
Martchus 2015-08-16 23:39:42 +02:00
parent 5e550eebb0
commit 8b91b41801
17 changed files with 286 additions and 143 deletions

View File

@ -1,4 +1,4 @@
# tagparser
# Tag Parser
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags.
## Supported formats
@ -18,3 +18,7 @@ For examples check out the CLI interface of my Tag Editor (which is also on Git)
## Build instructions
The tagparser library depends on c++utilities and is built in the same way.
It also depends on zlib.
## TODO
- Use padding to prevent rewriting the entire file to save tags.
- Support more tag formats (EXIF, PDF metadata, ...).

View File

@ -39,14 +39,14 @@ public:
virtual void removeAllFields();
const std::multimap<typename FieldType::identifierType, FieldType, Compare> &fields() const;
std::multimap<typename FieldType::identifierType, FieldType, Compare> &fields();
virtual int fieldCount() const;
virtual unsigned int fieldCount() const;
virtual typename FieldType::identifierType fieldId(KnownField value) const = 0;
virtual KnownField knownField(const typename FieldType::identifierType &id) const = 0;
virtual bool supportsField(KnownField field) const;
using Tag::proposedDataType;
virtual TagDataType proposedDataType(const typename FieldType::identifierType &id) const;
virtual int insertFields(const FieldMapBasedTag<FieldType, Compare> &from, bool overwrite);
virtual int insertValues(const Tag &from, bool overwrite);
virtual unsigned int insertValues(const Tag &from, bool overwrite);
typedef FieldType fieldType;
private:
@ -185,9 +185,15 @@ inline std::multimap<typename FieldType::identifierType, FieldType, Compare> &Fi
}
template <class FieldType, class Compare>
inline int FieldMapBasedTag<FieldType, Compare>::fieldCount() const
unsigned int FieldMapBasedTag<FieldType, Compare>::fieldCount() const
{
return m_fields.size();
int count = 0;
for(const auto &field : m_fields) {
if(!field.second.value().isEmpty()) {
++count;
}
}
return count;
}
template <class FieldType, class Compare>
@ -244,7 +250,7 @@ int FieldMapBasedTag<FieldType, Compare>::insertFields(const FieldMapBasedTag<Fi
}
template <class FieldType, class Compare>
int FieldMapBasedTag<FieldType, Compare>::insertValues(const Tag &from, bool overwrite)
unsigned int FieldMapBasedTag<FieldType, Compare>::insertValues(const Tag &from, bool overwrite)
{
if(type() == from.type()) {
// the tags are of the same type, we can insert the fields directly

View File

@ -232,13 +232,14 @@ void Id3v1Tag::removeAllFields()
m_genre.clearDataAndMetadata();
}
int Id3v1Tag::fieldCount() const
unsigned int Id3v1Tag::fieldCount() const
{
int count = 0;
for(const TagValue &value : {m_title, m_artist, m_album,
for(const auto &value : {m_title, m_artist, m_album,
m_year, m_comment, m_trackPos, m_genre}) {
if(!value.isEmpty())
if(!value.isEmpty()) {
++count;
}
}
return count;
}

View File

@ -20,7 +20,7 @@ public:
virtual bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo);
virtual bool hasField(KnownField field) const;
virtual void removeAllFields();
virtual int fieldCount() const;
virtual unsigned int fieldCount() const;
virtual bool supportsField(KnownField field) const;
void parse(std::istream &sourceStream, bool autoSeek);

View File

@ -4,6 +4,7 @@
#include "../exceptions.h"
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/misc/memory.h>
#include <zlib.h>
@ -268,53 +269,53 @@ void Id3v2Frame::make(IoUtilities::BinaryWriter &writer, int32 version)
addNotification(NotificationType::Warning, "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
}
// create actual data, depending on the frame type
vector<char> buffer;
unique_ptr<char[]> buffer;
uint32 decompressedSize;
// check if the frame to be written is a text frame
try {
if(Id3v2FrameIds::isTextfield(frameId)) {
if((version >= 3 && (frameId == Id3v2FrameIds::lTrackPosition || frameId == Id3v2FrameIds::lDiskPosition))
|| (version < 3 && frameId == Id3v2FrameIds::sTrackPosition)) {
// the track number or the disk number frame
helper.makeString(buffer, value().toString(), TagTextEncoding::Latin1);
helper.makeString(buffer, decompressedSize, value().toString(), TagTextEncoding::Latin1);
} else if((version >= 3 && frameId == Id3v2FrameIds::lLength)
|| (version < 3 && frameId == Id3v2FrameIds::sLength)) {
// the length
helper.makeString(buffer, ConversionUtilities::numberToString(value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
helper.makeString(buffer, decompressedSize, ConversionUtilities::numberToString(value().toTimeSpan().totalMilliseconds()), TagTextEncoding::Latin1);
} else if(value().type() == TagDataType::StandardGenreIndex && ((version >= 3 && frameId == Id3v2FrameIds::lGenre)
|| (version < 3 && frameId == Id3v2FrameIds::sGenre))) {
// genre/content type as standard genre index
helper.makeString(buffer, ConversionUtilities::numberToString(value().toStandardGenreIndex()), TagTextEncoding::Latin1);
helper.makeString(buffer, decompressedSize, ConversionUtilities::numberToString(value().toStandardGenreIndex()), TagTextEncoding::Latin1);
} else {
// any other text frame
helper.makeString(buffer, value().toString(), value().dataEncoding()); // the same as a normal text frame
helper.makeString(buffer, decompressedSize, value().toString(), value().dataEncoding()); // the same as a normal text frame
}
} else if((version >= 3 && frameId == Id3v2FrameIds::lCover)
|| (version < 3 && frameId == Id3v2FrameIds::sCover)) {
// picture frame
helper.makePicture(buffer, value(), isTypeInfoAssigned() ? typeInfo() : 0);
helper.makePicture(buffer, decompressedSize, value(), isTypeInfoAssigned() ? typeInfo() : 0);
} else if(((version >= 3 && id() == Id3v2FrameIds::lComment)
|| (version < 3 && id() == Id3v2FrameIds::sComment))
|| ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics)
|| (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
// the comment frame or the unsynchronized lyrics frame
helper.makeComment(buffer, value());
helper.makeComment(buffer, decompressedSize, value());
} else {
// an unknown frame
// create buffer
buffer.resize(value().dataSize());
buffer = make_unique<char[]>(decompressedSize = value().dataSize());
// just write the data
copy(value().dataPointer(), value().dataPointer() + value().dataSize(), buffer.data());
copy(value().dataPointer(), value().dataPointer() + value().dataSize(), buffer.get());
}
} catch(ConversionException &) {
addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
throw InvalidDataException();
}
uint32 decompressedSize = buffer.size();
unsigned long actualSize;
if(version >= 3 && isCompressed()) {
uLongf destLen = compressBound(buffer.size());
vector<char> compressedBuffer;
compressedBuffer.resize(destLen);
switch(compress(reinterpret_cast<Bytef *>(compressedBuffer.data()), &destLen, reinterpret_cast<Bytef *>(buffer.data()), buffer.size())) {
actualSize = compressBound(decompressedSize);
auto compressedBuffer = make_unique<char[]>(actualSize);
switch(compress(reinterpret_cast<Bytef *>(compressedBuffer.get()), &actualSize, reinterpret_cast<Bytef *>(buffer.get()), decompressedSize)) {
case Z_MEM_ERROR:
addNotification(NotificationType::Critical, "Decompressing failed. The source buffer was too small.", context);
throw InvalidDataException();
@ -325,17 +326,18 @@ void Id3v2Frame::make(IoUtilities::BinaryWriter &writer, int32 version)
;
}
buffer.swap(compressedBuffer);
buffer.resize(destLen);
} else {
actualSize = decompressedSize;
}
if(version < 3) {
writer.writeUInt24BE(frameId);
writer.writeUInt24BE(buffer.size());
writer.writeUInt24BE(actualSize);
} else {
writer.writeUInt32BE(frameId);
if(version >= 4) {
writer.writeSynchsafeUInt32BE(buffer.size());
writer.writeSynchsafeUInt32BE(actualSize);
} else {
writer.writeUInt32BE(buffer.size());
writer.writeUInt32BE(actualSize);
}
writer.writeUInt16BE(m_flag);
if(hasGroupInformation()) {
@ -349,7 +351,7 @@ void Id3v2Frame::make(IoUtilities::BinaryWriter &writer, int32 version)
}
}
}
writer.write(buffer.data(), buffer.size());
writer.write(buffer.get(), actualSize);
}
/*!
@ -605,9 +607,9 @@ void Id3v2FrameHelper::parseComment(const char *buffer, size_t dataSize, TagValu
* \param value Specifies the string to make.
* \param encoding Specifies the encoding of the string to make.
*/
void Id3v2FrameHelper::makeString(vector<char> &buffer, const string &value, TagTextEncoding encoding)
void Id3v2FrameHelper::makeString(unique_ptr<char[]> &buffer, uint32 &bufferSize, const string &value, TagTextEncoding encoding)
{
makeEncodingAndData(buffer, encoding, value.c_str(), value.length());
makeEncodingAndData(buffer, bufferSize, encoding, value.c_str(), value.length());
}
/*!
@ -617,7 +619,7 @@ void Id3v2FrameHelper::makeString(vector<char> &buffer, const string &value, Tag
* \param data Specifies the data.
* \param dataSize Specifies the data size.
*/
void Id3v2FrameHelper::makeEncodingAndData(vector<char> &buffer, TagTextEncoding encoding, const char *data, size_t dataSize)
void Id3v2FrameHelper::makeEncodingAndData(unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, size_t dataSize)
{
// calculate buffer size
if(!data) {
@ -627,23 +629,25 @@ void Id3v2FrameHelper::makeEncodingAndData(vector<char> &buffer, TagTextEncoding
case TagTextEncoding::Latin1:
case TagTextEncoding::Utf8:
case TagTextEncoding::Unspecified: // assumption
buffer.resize(1 + dataSize + 1); // allocate buffer
// allocate buffer
buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 1);
break;
case TagTextEncoding::Utf16LittleEndian:
case TagTextEncoding::Utf16BigEndian:
buffer.resize(1 + dataSize + 2); // allocate buffer
// allocate buffer
buffer = make_unique<char[]>(bufferSize = 1 + dataSize + 2);
break;
}
buffer[0] = makeTextEncodingByte(encoding); // set text encoding byte
if(dataSize > 0) {
copy(data, data + dataSize, buffer.data() + 1); // write string data
copy(data, data + dataSize, buffer.get() + 1); // write string data
}
}
/*!
* \brief Writes the specified picture to the specified buffer.
*/
void Id3v2FrameHelper::makePicture(std::vector<char> &buffer, const TagValue &picture, byte typeInfo)
void Id3v2FrameHelper::makePicture(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo)
{
// calculate needed buffer size and create buffer
TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
@ -656,9 +660,9 @@ void Id3v2FrameHelper::makePicture(std::vector<char> &buffer, const TagValue &pi
if(descriptionLength == string::npos) {
descriptionLength = picture.description().length();
}
buffer.resize(1 + mimeTypeLength + 1 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
// note: encoding byte + mime type length + 0 byte + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size
char *offset = buffer.data();
buffer = make_unique<char[]>(bufferSize = 1 + mimeTypeLength + 1 + 1 + descriptionLength + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
// note: encoding byte + mime type length + 0 byte + picture type byte + description length + 1 or 2 null bytes (depends on encoding) + data size
char *offset = buffer.get();
// write encoding byte
*offset = makeTextEncodingByte(descriptionEncoding);
// write mime type
@ -681,7 +685,7 @@ void Id3v2FrameHelper::makePicture(std::vector<char> &buffer, const TagValue &pi
/*!
* \brief Writes the specified comment to the specified buffer.
*/
void Id3v2FrameHelper::makeComment(std::vector<char> &buffer, const TagValue &comment)
void Id3v2FrameHelper::makeComment(unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment)
{
static const string context("making comment frame");
// check type and other values are valid
@ -700,9 +704,9 @@ void Id3v2FrameHelper::makeComment(std::vector<char> &buffer, const TagValue &co
if(descriptionLength == string::npos)
descriptionLength = comment.description().length();
uint32 dataSize = comment.dataSize();
buffer.resize(1 + 3 + descriptionLength + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
// note: encoding byte + language + description length + 1 or 2 null bytes + data size
char *offset = buffer.data();
buffer = make_unique<char[]>(bufferSize = 1 + 3 + descriptionLength + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1) + dataSize);
// note: encoding byte + language + description length + 1 or 2 null bytes + data size
char *offset = buffer.get();
// write encoding
*offset = makeTextEncodingByte(encoding);
// write language
@ -717,7 +721,7 @@ void Id3v2FrameHelper::makeComment(std::vector<char> &buffer, const TagValue &co
*(++offset) = 0x00;
}
// write actual data
string data = comment.toString();
const auto data = comment.toString();
data.copy(++offset, data.length());
}

View File

@ -32,10 +32,10 @@ public:
void parsePicture(const char *buffer, size_t maxSize, TagValue &tagValue, byte &typeInfo);
void parseComment(const char *buffer, size_t maxSize, TagValue &tagValue);
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding);
void makeString(std::vector<char> &buffer, const std::string &value, TagTextEncoding encoding);
void makeEncodingAndData(std::vector<char> &buffer, TagTextEncoding encoding, const char *data, size_t m_dataSize);
void makePicture(std::vector<char> &buffer, const TagValue &picture, byte typeInfo);
void makeComment(std::vector<char> &buffer, const TagValue &comment);
void makeString(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const std::string &value, TagTextEncoding encoding);
void makeEncodingAndData(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, TagTextEncoding encoding, const char *data, size_t m_dataSize);
void makePicture(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &picture, byte typeInfo);
void makeComment(std::unique_ptr<char[]> &buffer, uint32 &bufferSize, const TagValue &comment);
private:
std::string m_id;

View File

@ -191,6 +191,7 @@ startParsingSignature:
break;
} case ContainerFormat::Ogg:
m_container = make_unique<OggContainer>(*this, m_containerOffset);
static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
break;
default:
;

View File

@ -266,9 +266,7 @@ void Mp4Tag::make(ostream &stream)
int tagFieldsWritten = 0;
for(auto i = fields().begin(), end = fields().end(); i != end; ++i) {
Mp4TagField &field = i->second;
if(field.value().isEmpty()) {
continue;
} else {
if(!field.value().isEmpty()) {
field.invalidateNotifications();
try {
field.make(stream);

View File

@ -51,7 +51,7 @@ void MpegAudioFrameStream::internalParseHeader()
if(frame.isXingBytesfieldPresent()) {
uint32 xingSize = frame.xingBytesfield();
if(m_size && xingSize != m_size) {
addNotification(NotificationType::Warning, "Real length MPEG of audio frames is not equal with value provided by Xing header. The Xing header value will be used.", context);
addNotification(NotificationType::Warning, "Real length of MPEG audio frames is not equal with value provided by Xing header. The Xing header value will be used.", context);
m_size = xingSize;
}
}

View File

@ -36,6 +36,7 @@ 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();
@ -49,6 +50,14 @@ void OggContainer::internalParseHeader()
m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
m_tracks.emplace_back(new OggStream(*this, m_iterator.currentPageIndex()));
}
if(pageSequenceNumber != page.sequenceNumber()) {
if(pageSequenceNumber != 0) {
addNotification(NotificationType::Warning, "Page is missing (page sequence number omitted).", context);
pageSequenceNumber = page.sequenceNumber();
}
} else {
++pageSequenceNumber;
}
}
} catch(TruncatedDataException &) {
// thrown when page exceeds max size
@ -64,11 +73,13 @@ void OggContainer::internalParseHeader()
void OggContainer::internalParseTags()
{
parseTracks(); // tracks needs to be parsed because tags are stored at stream level
for(auto i : m_commentTable) {
for(auto &i : m_commentTable) {
//fileInfo().stream().seekg(get<1>(i));
m_iterator.setPageIndex(get<0>(i));
m_iterator.setSegmentIndex(get<1>(i));
m_tags[get<2>(i)]->parse(m_iterator);
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();
}
}
@ -100,6 +111,7 @@ void OggContainer::internalMakeFile()
{
const string context("making OGG file");
updateStatus("Prepare for rewriting OGG file ...");
parseTags(); // tags need to be parsed before the file can be rewritten
fileInfo().close();
string backupPath;
fstream backupStream;
@ -110,10 +122,16 @@ void OggContainer::internalMakeFile()
CopyHelper<65307> copy;
auto commentTableIterator = m_commentTable.cbegin(), commentTableEnd = m_commentTable.cend();
vector<uint64> updatedPageOffsets;
uint32 pageSequenceNumber = 0;
for(m_iterator.setStream(backupStream), m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage()) {
const auto &currentPage = m_iterator.currentPage();
auto pageSize = currentPage.totalSize();
if(commentTableIterator != commentTableEnd && m_iterator.currentPageIndex() == get<0>(*commentTableIterator) && !currentPage.segmentSizes().empty()) {
// check whether the Vorbis Comment is present in this Ogg page
// -> then the page needs to be rewritten
if(commentTableIterator != commentTableEnd
&& m_iterator.currentPageIndex() >= commentTableIterator->firstPageIndex
&& m_iterator.currentPageIndex() <= commentTableIterator->lastPageIndex
&& !currentPage.segmentSizes().empty()) {
// page needs to be rewritten (not just copied)
// -> write segments to a buffer first
stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
@ -121,67 +139,109 @@ void OggContainer::internalMakeFile()
newSegmentSizes.reserve(currentPage.segmentSizes().size());
uint64 segmentOffset = m_iterator.currentSegmentOffset();
vector<uint32>::size_type segmentIndex = 0;
for(auto segmentSize : currentPage.segmentSizes()) {
if(segmentIndex == get<1>(*commentTableIterator)) {
// make vorbis comment segment
auto offset = buffer.tellp();
m_tags[get<2>(*commentTableIterator)]->make(buffer);
newSegmentSizes.push_back(buffer.tellp() - offset);
} else {
// copy other segments unchanged
backupStream.seekg(segmentOffset);
copy.copy(backupStream, buffer, segmentSize);
newSegmentSizes.push_back(segmentSize);
for(const auto segmentSize : currentPage.segmentSizes()) {
if(segmentSize) {
// check whether this segment contains the Vorbis Comment
if((m_iterator.currentPageIndex() > commentTableIterator->firstPageIndex || segmentIndex >= commentTableIterator->firstSegmentIndex)
&& (m_iterator.currentPageIndex() < commentTableIterator->lastPageIndex || segmentIndex <= commentTableIterator->lastSegmentIndex)) {
// prevent making the comment twice if it spreads over multiple pages
if(m_iterator.currentPageIndex() == commentTableIterator->firstPageIndex) {
// make Vorbis Comment segment
auto offset = buffer.tellp();
m_tags[commentTableIterator->tagIndex]->make(buffer);
newSegmentSizes.push_back(buffer.tellp() - offset);
}
if(m_iterator.currentPageIndex() > commentTableIterator->lastPageIndex
|| (m_iterator.currentPageIndex() == commentTableIterator->lastPageIndex && segmentIndex > commentTableIterator->lastSegmentIndex)) {
++commentTableIterator;
}
} else {
// copy other segments unchanged
backupStream.seekg(segmentOffset);
copy.copy(backupStream, buffer, segmentSize);
newSegmentSizes.push_back(segmentSize);
}
segmentOffset += segmentSize;
}
segmentOffset += segmentSize;
++segmentIndex;
}
// write buffered data to actual stream
auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
uint32 bytesLeft, currentSize;
// write pages until all data in the buffer is written
while(newSegmentSizesIterator != newSegmentSizesEnd) {
// write header
backupStream.seekg(currentPage.startOffset());
copy.copy(backupStream, stream(), 27); // just copy from original file
updatedPageOffsets.push_back(currentPage.startOffset()); // memorize offset to update checksum later
int16 segmentSizesWritten = 0; // in the current page header only
// write segment sizes as long as there are segment sizes to be written and
// the max number of segment sizes is not exceeded
bytesLeft = *newSegmentSizesIterator;
currentSize = 0;
while(bytesLeft > 0 && segmentSizesWritten < 0xFF) {
while(bytesLeft >= 0xFF && segmentSizesWritten < 0xFF) {
stream().put(0xFF);
bytesLeft -= 0xFF;
++segmentSizesWritten;
bool continuePreviousSegment = false;
if(newSegmentSizesIterator != newSegmentSizesEnd) {
uint32 bytesLeft = *newSegmentSizesIterator;
// write pages until all data in the buffer is written
while(newSegmentSizesIterator != newSegmentSizesEnd) {
// write header
backupStream.seekg(currentPage.startOffset());
updatedPageOffsets.push_back(stream().tellp()); // memorize offset to update checksum later
copy.copy(backupStream, stream(), 27); // just copy header from original file
// set continue flag
stream().seekp(-22, ios_base::cur);
stream().put(currentPage.headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE));
continuePreviousSegment = true;
// adjust page sequence number
stream().seekp(12, ios_base::cur);
writer().writeUInt32LE(pageSequenceNumber);
stream().seekp(5, ios_base::cur);
int16 segmentSizesWritten = 0; // in the current page header only
// write segment sizes as long as there are segment sizes to be written and
// the max number of segment sizes (255) is not exceeded
uint32 currentSize = 0;
while(bytesLeft > 0 && segmentSizesWritten < 0xFF) {
while(bytesLeft >= 0xFF && segmentSizesWritten < 0xFF) {
stream().put(0xFF);
currentSize += 0xFF;
bytesLeft -= 0xFF;
++segmentSizesWritten;
}
if(bytesLeft > 0 && segmentSizesWritten < 0xFF) {
// bytes left is here < 0xFF
stream().put(bytesLeft);
currentSize += bytesLeft;
bytesLeft = 0;
++segmentSizesWritten;
}
if(bytesLeft == 0) {
// sizes for the segment have been written
// -> continue with next segment
if(++newSegmentSizesIterator != newSegmentSizesEnd) {
bytesLeft = *newSegmentSizesIterator;
continuePreviousSegment = false;
}
}
}
if(bytesLeft > 0 && segmentSizesWritten < 0xFF) {
stream().put(bytesLeft);
bytesLeft = 0;
++segmentSizesWritten;
}
currentSize += *newSegmentSizesIterator - bytesLeft;
// there are no bytes left in the current segment; remove continue flag
if(bytesLeft == 0) {
// sizes for the current segemnt have been written -> continue with next segment
++newSegmentSizesIterator;
bytesLeft = newSegmentSizesIterator != newSegmentSizesEnd ? *newSegmentSizesIterator : 0;
continuePreviousSegment = false;
}
}
// page is full or all segment data has already been written -> write data
if(bytesLeft == 0 || newSegmentSizesIterator != newSegmentSizesEnd) {
// seek back and write updated page segment number
// page is full or all segment data has been covered
// -> write segment table size (segmentSizesWritten) and segment data
// -> seek back and write updated page segment number
stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
stream().put(segmentSizesWritten);
stream().seekp(segmentSizesWritten, ios_base::cur);
// write actual page data
// -> write actual page data
copy.copy(buffer, stream(), currentSize);
++pageSequenceNumber;
}
}
} else {
// copy page unchanged
backupStream.seekg(currentPage.startOffset());
copy.copy(backupStream, stream(), pageSize);
if(pageSequenceNumber != m_iterator.currentPageIndex()) {
// just update page sequence number
backupStream.seekg(currentPage.startOffset());
updatedPageOffsets.push_back(stream().tellp()); // memorize offset to update checksum later
copy.copy(backupStream, stream(), 27);
stream().seekp(-9, ios_base::cur);
writer().writeUInt32LE(pageSequenceNumber);
stream().seekp(5, ios_base::cur);
copy.copy(backupStream, stream(), pageSize - 27);
} else {
// copy page unchanged
backupStream.seekg(currentPage.startOffset());
copy.copy(backupStream, stream(), pageSize);
}
++pageSequenceNumber;
}
}
// close backups stream; reopen new file as readable stream

View File

@ -16,6 +16,25 @@ namespace Media {
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);
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;
};
inline VorbisCommentInfo::VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex) :
firstPageIndex(firstPageIndex),
firstSegmentIndex(firstSegmentIndex),
lastPageIndex(0),
lastSegmentIndex(0),
tagIndex(tagIndex)
{}
class LIB_EXPORT OggContainer : public GenericContainer<MediaFileInfo, VorbisComment, OggStream, OggPage>
{
friend class OggStream;
@ -37,7 +56,12 @@ private:
void ariseComment(std::vector<OggPage>::size_type pageIndex, std::vector<uint32>::size_type segmentIndex);
std::unordered_map<uint32, std::vector<std::unique_ptr<OggStream> >::size_type> m_streamsBySerialNo;
std::list<std::tuple<std::vector<OggPage>::size_type, std::vector<uint32>::size_type, std::vector<std::unique_ptr<VorbisComment> >::size_type> > m_commentTable;
/*!
* \brief Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
*/
std::list<VorbisCommentInfo> m_commentTable;
OggIterator m_iterator;
bool m_validateChecksums;
};

View File

@ -77,28 +77,28 @@ uint32 OggPage::computeChecksum(istream &stream, uint64 startOffset)
stream.seekg(startOffset);
uint32 crc = 0x0;
uint32 *crc32Table = BinaryReader::crc32Table();
byte val;
for(uint32 i = 0, len = 27, si = 0, sc = 0; i < len; ++i) {
byte value, segmentTableSize = 0, segmentTableIndex = 0;
for(uint32 i = 0, segmentLength = 27; i < segmentLength; ++i) {
switch(i) {
case 22:
// bytes 22, 23, 24, 25 hold denoted checksum and must be set to zero
stream.seekg(4, ios_base::cur);
case 23: case 24: case 25:
val = 0;
value = 0;
break;
case 26:
// byte 26 holds the number of segment sizes
len += (sc = (val = stream.get()));
segmentLength += (segmentTableSize = (value = stream.get()));
break;
default:
val = stream.get();
if(i > 26 && si < sc) {
value = stream.get();
if(i > 26 && segmentTableIndex < segmentTableSize) {
// bytes 27 to (27 + segment size count) hold page size
len += val;
++si;
segmentLength += value;
++segmentTableIndex;
}
}
crc = (crc << 8) ^ crc32Table[((crc >> 24) & 0xff) ^ val];
crc = (crc << 8) ^ crc32Table[((crc >> 24) & 0xFF) ^ value];
}
return crc;
}

View File

@ -92,9 +92,9 @@ string Tag::toString() const
* \param overwrite Indicates whether existing values should be overwritten.
* \return Returns the number of values that have been inserted.
*/
int Tag::insertValues(const Tag &from, bool overwrite)
unsigned int Tag::insertValues(const Tag &from, bool overwrite)
{
int count = 0;
unsigned int count = 0;
for(int i = static_cast<int>(KnownField::Invalid) + 1, last = static_cast<int>(KnownField::Description);
i <= last; ++i) {
KnownField field = static_cast<KnownField>(i);

4
tag.h
View File

@ -115,12 +115,12 @@ public:
virtual bool supportsTarget() const;
const TagTarget &target() const;
void setTarget(const TagTarget &target);
virtual int fieldCount() const = 0;
virtual unsigned int fieldCount() const = 0;
virtual bool supportsField(KnownField field) const = 0;
virtual TagDataType proposedDataType(KnownField field) const;
virtual bool supportsDescription(KnownField field) const;
virtual bool supportsMimeType(KnownField field) const;
virtual int insertValues(const Tag &from, bool overwrite);
virtual unsigned int insertValues(const Tag &from, bool overwrite);
// Tag *parent() const;
// bool setParent(Tag *tag);
// Tag *nestedTag(size_t index) const;

View File

@ -111,7 +111,7 @@ void VorbisComment::parse(OggIterator &iterator)
invalidateStatus();
static const string context("parsing Vorbis comment");
iterator.stream().seekg(iterator.currentSegmentOffset());
uint64 startOffset = iterator.currentSegmentOffset();
auto startOffset = iterator.currentSegmentOffset();
try {
// read signature: 0x3 + "vorbis"
char sig[8];
@ -120,8 +120,8 @@ void VorbisComment::parse(OggIterator &iterator)
// read vendor (length prefixed string)
{
iterator.read(sig, 4);
uint32 vendorSize = LE::toUInt32(sig);
unique_ptr<char []> buff = make_unique<char []>(vendorSize);
auto vendorSize = LE::toUInt32(sig);
auto buff = make_unique<char []>(vendorSize);
iterator.read(buff.get(), vendorSize);
m_vendor = string(buff.get(), vendorSize);
}
@ -151,6 +151,7 @@ void VorbisComment::parse(OggIterator &iterator)
throw InvalidDataException();
}
} catch(TruncatedDataException &) {
m_size = static_cast<uint32>(static_cast<uint64>(iterator.currentCharacterOffset()) - startOffset);
addNotification(NotificationType::Critical, "Vorbis comment is truncated.", context);
throw;
}
@ -186,14 +187,16 @@ void VorbisComment::make(std::ostream &stream)
// write fields
for(auto i : fields()) {
VorbisCommentField &field = i.second;
try {
field.make(writer);
} catch(Failure &) {
// nothing to do here since notifications will be added anyways
if(!field.value().isEmpty()) {
try {
field.make(writer);
} catch(Failure &) {
// nothing to do here since notifications will be added anyways
}
// add making notifications
addNotifications(context, field);
field.invalidateNotifications();
}
// add making notifications
addNotifications(context, field);
field.invalidateNotifications();
}
// write framing byte
stream.put(0x01);

View File

@ -13,6 +13,8 @@
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/misc/memory.h>
#include <iostream>
using namespace std;
using namespace IoUtilities;
using namespace ConversionUtilities;
@ -52,13 +54,12 @@ void VorbisCommentField::parse(OggIterator &iterator)
static const string context("parsing Vorbis comment field");
char buff[4];
iterator.read(buff, 4);
if(uint32 size = LE::toUInt32(buff)) { // read size
if(auto size = LE::toUInt32(buff)) { // read size
// read data
unique_ptr<char[]> data = make_unique<char []>(size);
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)
;
for(const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize);
// extract id
setId(string(data.get(), idSize));
if(!idSize) {
@ -67,11 +68,30 @@ void VorbisCommentField::parse(OggIterator &iterator)
throw InvalidDataException();
} else if(id() == VorbisCommentIds::cover()) {
// extract cover value
vector<char> buffer = decodeBase64(string(data.get() + idSize + 1, size - idSize - 1));
Id3v2FrameHelper helper(id(), *this);
byte type;
helper.parsePicture(buffer.data(), buffer.size(), value(), type);
setTypeInfo(type);
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));
@ -92,20 +112,42 @@ void VorbisCommentField::make(BinaryWriter &writer)
if(id().empty()) {
addNotification(NotificationType::Critical, "The field ID is empty.", context);
}
// try to convert value to string
try {
// try to convert value to string
string valueString;
if(id() == VorbisCommentIds::cover()) {
// make cover
Id3v2FrameHelper helper(id(), *this);
vector<char> buffer;
helper.makePicture(buffer, value(), isTypeInfoAssigned() ? typeInfo() : 0);
valueString = encodeBase64(buffer);
if(value().type() != TagDataType::Picture) {
addNotification(NotificationType::Critical, "Assigned value of cover field is not picture data.", context);
throw InvalidDataException();
}
try {
uint32 dataSize = 32 + value().mimeType().size() + value().description().size() + value().dataSize();
auto buffer = make_unique<char[]>(dataSize);
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
ss.exceptions(ios_base::failbit | ios_base::badbit);
ss.rdbuf()->pubsetbuf(buffer.get(), dataSize);
BinaryWriter writer(&ss);
writer.writeUInt32BE(typeInfo());
writer.writeUInt32BE(value().mimeType().size());
writer.writeString(value().mimeType());
writer.writeUInt32BE(value().description().size());
writer.writeString(value().description());
writer.writeUInt32BE(0); // skip width
writer.writeUInt32BE(0); // skip height
writer.writeUInt32BE(0); // skip color depth
writer.writeUInt32BE(0); // skip number of colors used
writer.writeUInt32BE(value().dataSize());
writer.write(value().dataPointer(), value().dataSize());
valueString = encodeBase64(reinterpret_cast<byte *>(buffer.get()), dataSize);
} catch (const ios_base::failure &) {
addNotification(NotificationType::Critical, "An IO error occured when writing the METADATA_BLOCK_PICTURE struct.", context);
throw Failure();
}
} else {
// make normal string value
valueString = value().toString();
}
// write size
writer.writeUInt32LE(id().size() + 1 + valueString.size());
writer.writeString(id());
writer.writeChar('=');

View File

@ -27,7 +27,7 @@ public:
/*!
* \brief The type info is stored using 32-bit unsigned integers.
*/
typedef byte typeInfoType;
typedef uint32 typeInfoType;
/*!
* \brief The implementation type is VorbisCommentField.
*/