Merge pull request #5507 from Chocobozzz/qt5

Update qt5 branch
This commit is contained in:
John Maguire 2016-10-07 14:49:05 +01:00 committed by GitHub
commit 9cf3024435
309 changed files with 33012 additions and 29528 deletions

View File

@ -1,8 +1,9 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-delete-non-virtual-dtor") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-delete-non-virtual-dtor")
set(TAGLIB_SOVERSION_CURRENT 16) set(TAGLIB_SOVERSION_CURRENT 17)
set(TAGLIB_SOVERSION_REVISION 1) set(TAGLIB_SOVERSION_REVISION 0)
set(TAGLIB_SOVERSION_AGE 15) set(TAGLIB_SOVERSION_AGE 16)
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}") math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
@ -52,6 +53,10 @@ elseif(HAVE_ZLIB_SOURCE)
include_directories(${ZLIB_SOURCE}) include_directories(${ZLIB_SOURCE})
endif() endif()
if(HAVE_BOOST_BYTESWAP OR HAVE_BOOST_ATOMIC OR HAVE_BOOST_ZLIB)
include_directories(${Boost_INCLUDE_DIR})
endif()
set(tag_HDRS set(tag_HDRS
tag.h tag.h
fileref.h fileref.h
@ -103,6 +108,7 @@ set(tag_HDRS
mpeg/id3v2/frames/urllinkframe.h mpeg/id3v2/frames/urllinkframe.h
mpeg/id3v2/frames/chapterframe.h mpeg/id3v2/frames/chapterframe.h
mpeg/id3v2/frames/tableofcontentsframe.h mpeg/id3v2/frames/tableofcontentsframe.h
mpeg/id3v2/frames/podcastframe.h
ogg/oggfile.h ogg/oggfile.h
ogg/oggpage.h ogg/oggpage.h
ogg/oggpageheader.h ogg/oggpageheader.h
@ -197,6 +203,7 @@ set(frames_SRCS
mpeg/id3v2/frames/urllinkframe.cpp mpeg/id3v2/frames/urllinkframe.cpp
mpeg/id3v2/frames/chapterframe.cpp mpeg/id3v2/frames/chapterframe.cpp
mpeg/id3v2/frames/tableofcontentsframe.cpp mpeg/id3v2/frames/tableofcontentsframe.cpp
mpeg/id3v2/frames/podcastframe.cpp
) )
set(ogg_SRCS set(ogg_SRCS
@ -323,6 +330,7 @@ set(toolkit_SRCS
toolkit/tpropertymap.cpp toolkit/tpropertymap.cpp
toolkit/trefcounter.cpp toolkit/trefcounter.cpp
toolkit/tdebuglistener.cpp toolkit/tdebuglistener.cpp
toolkit/tzlib.cpp
) )
if(NOT WIN32) if(NOT WIN32)
@ -352,6 +360,7 @@ set(tag_LIB_SRCS
tagunion.cpp tagunion.cpp
fileref.cpp fileref.cpp
audioproperties.cpp audioproperties.cpp
tagutils.cpp
) )
add_library(tag STATIC ${tag_LIB_SRCS} ${tag_HDRS}) add_library(tag STATIC ${tag_LIB_SRCS} ${tag_HDRS})
@ -360,6 +369,14 @@ if(ZLIB_FOUND)
target_link_libraries(tag ${ZLIB_LIBRARIES}) target_link_libraries(tag ${ZLIB_LIBRARIES})
endif() endif()
if(HAVE_BOOST_ATOMIC)
target_link_libraries(tag ${Boost_ATOMIC_LIBRARY})
endif()
if(HAVE_BOOST_ZLIB)
target_link_libraries(tag ${Boost_IOSTREAMS_LIBRARY} ${Boost_ZLIB_LIBRARY})
endif()
set_target_properties(tag PROPERTIES set_target_properties(tag PROPERTIES
VERSION ${TAGLIB_SOVERSION_MAJOR}.${TAGLIB_SOVERSION_MINOR}.${TAGLIB_SOVERSION_PATCH} VERSION ${TAGLIB_SOVERSION_MAJOR}.${TAGLIB_SOVERSION_MINOR}.${TAGLIB_SOVERSION_PATCH}
SOVERSION ${TAGLIB_SOVERSION_MAJOR} SOVERSION ${TAGLIB_SOVERSION_MAJOR}

View File

@ -38,9 +38,9 @@
#include <id3v1tag.h> #include <id3v1tag.h>
#include <id3v2header.h> #include <id3v2header.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tagutils.h>
#include "apefile.h" #include "apefile.h"
#include "apetag.h" #include "apetag.h"
#include "apefooter.h" #include "apefooter.h"
@ -61,10 +61,7 @@ public:
ID3v2Header(0), ID3v2Header(0),
ID3v2Location(-1), ID3v2Location(-1),
ID3v2Size(0), ID3v2Size(0),
properties(0), properties(0) {}
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
~FilePrivate() ~FilePrivate()
{ {
@ -73,24 +70,17 @@ public:
} }
long APELocation; long APELocation;
uint APESize; long APESize;
long ID3v1Location; long ID3v1Location;
ID3v2::Header *ID3v2Header; ID3v2::Header *ID3v2Header;
long ID3v2Location; long ID3v2Location;
uint ID3v2Size; long ID3v2Size;
TagUnion tag; TagUnion tag;
Properties *properties; Properties *properties;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasAPE;
bool hasID3v1;
bool hasID3v2;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -125,26 +115,20 @@ TagLib::Tag *APE::File::tag() const
PropertyMap APE::File::properties() const PropertyMap APE::File::properties() const
{ {
if(d->hasAPE) return d->tag.properties();
return d->tag.access<APE::Tag>(ApeAPEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->properties();
return PropertyMap();
} }
void APE::File::removeUnsupportedProperties(const StringList &properties) void APE::File::removeUnsupportedProperties(const StringList &properties)
{ {
if(d->hasAPE) d->tag.removeUnsupportedProperties(properties);
d->tag.access<APE::Tag>(ApeAPEIndex, false)->removeUnsupportedProperties(properties);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->removeUnsupportedProperties(properties);
} }
PropertyMap APE::File::setProperties(const PropertyMap &properties) PropertyMap APE::File::setProperties(const PropertyMap &properties)
{ {
if(d->hasID3v1) if(ID3v1Tag())
d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->setProperties(properties); ID3v1Tag()->setProperties(properties);
return d->tag.access<APE::Tag>(ApeAPEIndex, true)->setProperties(properties);
return APETag(true)->setProperties(properties);
} }
APE::Properties *APE::File::audioProperties() const APE::Properties *APE::File::audioProperties() const
@ -161,60 +145,63 @@ bool APE::File::save()
// Update ID3v1 tag // Update ID3v1 tag
if(ID3v1Tag()) { if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
if(d->hasID3v1) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location); seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
} }
else { else {
seek(0, End); seek(0, End);
d->ID3v1Location = tell(); d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
} }
writeBlock(ID3v1Tag()->render());
} }
else { else {
if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128); // ID3v1 tag is empty. Remove the old one.
d->hasID3v1 = false;
if(d->hasAPE) { if(d->ID3v1Location >= 0) {
if(d->APELocation > d->ID3v1Location) truncate(d->ID3v1Location);
d->APELocation -= 128; d->ID3v1Location = -1;
}
} }
} }
// Update APE tag // Update APE tag
if(APETag()) { if(APETag() && !APETag()->isEmpty()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize); // APE tag is not empty. Update the old one or create a new one.
else {
if(d->hasID3v1) { if(d->APELocation < 0) {
insert(APETag()->render(), d->ID3v1Location, 0); if(d->ID3v1Location >= 0)
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->APELocation = d->ID3v1Location; d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APESize; else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, d->APESize);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
d->APESize = data.size();
} }
else { else {
seek(0, End);
d->APELocation = tell(); // APE tag is empty. Remove the old one.
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize(); if(d->APELocation >= 0) {
d->hasAPE = true;
}
}
}
else {
if(d->hasAPE) {
removeBlock(d->APELocation, d->APESize); removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) { if(d->ID3v1Location >= 0)
if(d->ID3v1Location > d->APELocation) {
d->ID3v1Location -= d->APESize; d->ID3v1Location -= d->APESize;
}
} d->APELocation = -1;
d->APESize = 0;
} }
} }
@ -233,27 +220,24 @@ APE::Tag *APE::File::APETag(bool create)
void APE::File::strip(int tags) void APE::File::strip(int tags)
{ {
if(tags & ID3v1) { if(tags & ID3v1)
d->tag.set(ApeID3v1Index, 0); d->tag.set(ApeID3v1Index, 0);
APETag(true);
}
if(tags & APE) { if(tags & APE)
d->tag.set(ApeAPEIndex, 0); d->tag.set(ApeAPEIndex, 0);
if(!ID3v1Tag()) if(!ID3v1Tag())
APETag(true); APETag(true);
}
} }
bool APE::File::hasAPETag() const bool APE::File::hasAPETag() const
{ {
return d->hasAPE; return (d->APELocation >= 0);
} }
bool APE::File::hasID3v1Tag() const bool APE::File::hasID3v1Tag() const
{ {
return d->hasID3v1; return (d->ID3v1Location >= 0);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -264,36 +248,32 @@ void APE::File::read(bool readProperties)
{ {
// Look for an ID3v2 tag // Look for an ID3v2 tag
d->ID3v2Location = findID3v2(); d->ID3v2Location = Utils::findID3v2(this);
if(d->ID3v2Location >= 0) { if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location); seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size())); d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize(); d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
} }
// Look for an ID3v1 tag // Look for an ID3v1 tag
d->ID3v1Location = findID3v1(); d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) { if(d->ID3v1Location >= 0)
d->tag.set(ApeID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); d->tag.set(ApeID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag // Look for an APE tag
d->APELocation = findAPE(); d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) { if(d->APELocation >= 0) {
d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation)); d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize(); d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize; d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
d->hasAPE = true;
} }
if(!d->hasID3v1) if(d->ID3v1Location < 0)
APETag(true); APETag(true);
// Look for APE audio properties // Look for APE audio properties
@ -302,14 +282,14 @@ void APE::File::read(bool readProperties)
long streamLength; long streamLength;
if(d->hasAPE) if(d->APELocation >= 0)
streamLength = d->APELocation; streamLength = d->APELocation;
else if(d->hasID3v1) else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location; streamLength = d->ID3v1Location;
else else
streamLength = length(); streamLength = length();
if(d->hasID3v2) { if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location + d->ID3v2Size); seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size); streamLength -= (d->ID3v2Location + d->ID3v2Size);
} }
@ -320,48 +300,3 @@ void APE::File::read(bool readProperties)
d->properties = new Properties(this, streamLength); d->properties = new Properties(this, streamLength);
} }
} }
long APE::File::findAPE()
{
if(!isValid())
return -1;
if(d->hasID3v1)
seek(-160, End);
else
seek(-32, End);
long p = tell();
if(readBlock(8) == APE::Tag::fileIdentifier())
return p;
return -1;
}
long APE::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
long APE::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -146,9 +146,6 @@ namespace TagLib {
* *
* \note According to the official Monkey's Audio SDK, an APE file * \note According to the official Monkey's Audio SDK, an APE file
* can only have either ID3V1 or APE tags, so a parameter is used here. * can only have either ID3V1 or APE tags, so a parameter is used here.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
@ -219,9 +216,6 @@ namespace TagLib {
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties); void read(bool readProperties);
long findAPE();
long findID3v1();
long findID3v2();
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -38,54 +38,51 @@ using namespace APE;
class APE::Footer::FooterPrivate class APE::Footer::FooterPrivate
{ {
public: public:
FooterPrivate() : version(0), FooterPrivate() :
version(0),
footerPresent(true), footerPresent(true),
headerPresent(false), headerPresent(false),
isHeader(false), isHeader(false),
itemCount(0), itemCount(0),
tagSize(0) {} tagSize(0) {}
~FooterPrivate() {} unsigned int version;
uint version;
bool footerPresent; bool footerPresent;
bool headerPresent; bool headerPresent;
bool isHeader; bool isHeader;
uint itemCount; unsigned int itemCount;
uint tagSize; unsigned int tagSize;
static const uint size = 32;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// static members // static members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
TagLib::uint APE::Footer::size() unsigned int APE::Footer::size()
{ {
return FooterPrivate::size; return 32;
} }
ByteVector APE::Footer::fileIdentifier() ByteVector APE::Footer::fileIdentifier()
{ {
return ByteVector::fromCString("APETAGEX"); return ByteVector("APETAGEX");
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
APE::Footer::Footer() APE::Footer::Footer() :
d(new FooterPrivate())
{ {
d = new FooterPrivate;
} }
APE::Footer::Footer(const ByteVector &data) APE::Footer::Footer(const ByteVector &data) :
d(new FooterPrivate())
{ {
d = new FooterPrivate;
parse(data); parse(data);
} }
@ -94,7 +91,7 @@ APE::Footer::~Footer()
delete d; delete d;
} }
TagLib::uint APE::Footer::version() const unsigned int APE::Footer::version() const
{ {
return d->version; return d->version;
} }
@ -119,30 +116,30 @@ void APE::Footer::setHeaderPresent(bool b) const
d->headerPresent = b; d->headerPresent = b;
} }
TagLib::uint APE::Footer::itemCount() const unsigned int APE::Footer::itemCount() const
{ {
return d->itemCount; return d->itemCount;
} }
void APE::Footer::setItemCount(uint s) void APE::Footer::setItemCount(unsigned int s)
{ {
d->itemCount = s; d->itemCount = s;
} }
TagLib::uint APE::Footer::tagSize() const unsigned int APE::Footer::tagSize() const
{ {
return d->tagSize; return d->tagSize;
} }
TagLib::uint APE::Footer::completeTagSize() const unsigned int APE::Footer::completeTagSize() const
{ {
if(d->headerPresent) if(d->headerPresent)
return d->tagSize + d->size; return d->tagSize + size();
else else
return d->tagSize; return d->tagSize;
} }
void APE::Footer::setTagSize(uint s) void APE::Footer::setTagSize(unsigned int s)
{ {
d->tagSize = s; d->tagSize = s;
} }
@ -159,8 +156,9 @@ ByteVector APE::Footer::renderFooter() const
ByteVector APE::Footer::renderHeader() const ByteVector APE::Footer::renderHeader() const
{ {
if (!d->headerPresent) return ByteVector(); if(!d->headerPresent)
return ByteVector();
else
return render(true); return render(true);
} }

View File

@ -64,7 +64,7 @@ namespace TagLib {
/*! /*!
* Returns the version number. (Note: This is the 1000 or 2000.) * Returns the version number. (Note: This is the 1000 or 2000.)
*/ */
uint version() const; unsigned int version() const;
/*! /*!
* Returns true if a header is present in the tag. * Returns true if a header is present in the tag.
@ -89,13 +89,13 @@ namespace TagLib {
/*! /*!
* Returns the number of items in the tag. * Returns the number of items in the tag.
*/ */
uint itemCount() const; unsigned int itemCount() const;
/*! /*!
* Set the item count to \a s. * Set the item count to \a s.
* \see itemCount() * \see itemCount()
*/ */
void setItemCount(uint s); void setItemCount(unsigned int s);
/*! /*!
* Returns the tag size in bytes. This is the size of the frame content and footer. * Returns the tag size in bytes. This is the size of the frame content and footer.
@ -103,7 +103,7 @@ namespace TagLib {
* *
* \see completeTagSize() * \see completeTagSize()
*/ */
uint tagSize() const; unsigned int tagSize() const;
/*! /*!
* Returns the tag size, including if present, the header * Returns the tag size, including if present, the header
@ -111,18 +111,18 @@ namespace TagLib {
* *
* \see tagSize() * \see tagSize()
*/ */
uint completeTagSize() const; unsigned int completeTagSize() const;
/*! /*!
* Set the tag size to \a s. * Set the tag size to \a s.
* \see tagSize() * \see tagSize()
*/ */
void setTagSize(uint s); void setTagSize(unsigned int s);
/*! /*!
* Returns the size of the footer. Presently this is always 32 bytes. * Returns the size of the footer. Presently this is always 32 bytes.
*/ */
static uint size(); static unsigned int size();
/*! /*!
* Returns the string used to identify an APE tag inside of a file. * Returns the string used to identify an APE tag inside of a file.

View File

@ -34,7 +34,9 @@ using namespace APE;
class APE::Item::ItemPrivate class APE::Item::ItemPrivate
{ {
public: public:
ItemPrivate() : type(Text), readOnly(false) {} ItemPrivate() :
type(Text),
readOnly(false) {}
Item::ItemTypes type; Item::ItemTypes type;
String key; String key;
@ -43,40 +45,45 @@ public:
bool readOnly; bool readOnly;
}; };
APE::Item::Item() ////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
APE::Item::Item() :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
} }
APE::Item::Item(const String &key, const String &value) APE::Item::Item(const String &key, const String &value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->key = key; d->key = key;
d->text.append(value); d->text.append(value);
} }
APE::Item::Item(const String &key, const StringList &values) APE::Item::Item(const String &key, const StringList &values) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->key = key; d->key = key;
d->text = values; d->text = values;
} }
APE::Item::Item(const String &key, const ByteVector &value, bool binary) APE::Item::Item(const String &key, const ByteVector &value, bool binary) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->key = key; d->key = key;
if(binary) { if(binary) {
d->type = Binary; d->type = Binary;
d->value = value; d->value = value;
} }
else else {
d->text.append(value); d->text.append(value);
}
} }
APE::Item::Item(const Item &item) APE::Item::Item(const Item &item) :
d(new ItemPrivate(*item.d))
{ {
d = new ItemPrivate(*item.d);
} }
APE::Item::~Item() APE::Item::~Item()
@ -86,13 +93,17 @@ APE::Item::~Item()
Item &APE::Item::operator=(const Item &item) Item &APE::Item::operator=(const Item &item)
{ {
if(&item != this) { Item(item).swap(*this);
delete d;
d = new ItemPrivate(*item.d);
}
return *this; return *this;
} }
void APE::Item::swap(Item &item)
{
using std::swap;
swap(d, item.d);
}
void APE::Item::setReadOnly(bool readOnly) void APE::Item::setReadOnly(bool readOnly)
{ {
d->readOnly = readOnly; d->readOnly = readOnly;
@ -173,11 +184,10 @@ void APE::Item::appendValues(const StringList &values)
int APE::Item::size() const int APE::Item::size() const
{ {
// SFB: Why is d->key.size() used when size() returns the length in UniChars and not UTF-8? int result = 8 + d->key.size() + 1;
int result = 8 + d->key.size() /* d->key.data(String::UTF8).size() */ + 1; switch(d->type) {
switch (d->type) {
case Text: case Text:
if(d->text.size()) { if(!d->text.isEmpty()) {
StringList::ConstIterator it = d->text.begin(); StringList::ConstIterator it = d->text.begin();
result += it->data(String::UTF8).size(); result += it->data(String::UTF8).size();
@ -210,7 +220,7 @@ String APE::Item::toString() const
if(d->type == Text && !isEmpty()) if(d->type == Text && !isEmpty())
return d->text.front(); return d->text.front();
else else
return String::null; return String();
} }
bool APE::Item::isEmpty() const bool APE::Item::isEmpty() const
@ -239,10 +249,13 @@ void APE::Item::parse(const ByteVector &data)
return; return;
} }
const uint valueLength = data.toUInt(0, false); const unsigned int valueLength = data.toUInt(0, false);
const uint flags = data.toUInt(4, false); const unsigned int flags = data.toUInt(4, false);
d->key = String(data.mid(8), String::UTF8); // An item key can contain ASCII characters from 0x20 up to 0x7E, not UTF-8.
// We assume that the validity of the given key has been checked.
d->key = String(&data[8], String::Latin1);
const ByteVector value = data.mid(8 + d->key.size() + 1, valueLength); const ByteVector value = data.mid(8 + d->key.size() + 1, valueLength);
@ -258,7 +271,7 @@ void APE::Item::parse(const ByteVector &data)
ByteVector APE::Item::render() const ByteVector APE::Item::render() const
{ {
ByteVector data; ByteVector data;
TagLib::uint flags = ((d->readOnly) ? 1 : 0) | (d->type << 1); unsigned int flags = ((d->readOnly) ? 1 : 0) | (d->type << 1);
ByteVector value; ByteVector value;
if(isEmpty()) if(isEmpty())
@ -280,7 +293,7 @@ ByteVector APE::Item::render() const
data.append(ByteVector::fromUInt(value.size(), false)); data.append(ByteVector::fromUInt(value.size(), false));
data.append(ByteVector::fromUInt(flags, false)); data.append(ByteVector::fromUInt(flags, false));
data.append(d->key.data(String::UTF8)); data.append(d->key.data(String::Latin1));
data.append(ByteVector('\0')); data.append(ByteVector('\0'));
data.append(value); data.append(value);

View File

@ -90,6 +90,11 @@ namespace TagLib {
*/ */
Item &operator=(const Item &item); Item &operator=(const Item &item);
/*!
* Exchanges the content of this item by the content of \a item.
*/
void swap(Item &item);
/*! /*!
* Returns the key. * Returns the key.
*/ */

View File

@ -56,7 +56,7 @@ public:
int channels; int channels;
int version; int version;
int bitsPerSample; int bitsPerSample;
uint sampleFrames; unsigned int sampleFrames;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -122,7 +122,7 @@ int APE::Properties::bitsPerSample() const
return d->bitsPerSample; return d->bitsPerSample;
} }
TagLib::uint APE::Properties::sampleFrames() const unsigned int APE::Properties::sampleFrames() const
{ {
return d->sampleFrames; return d->sampleFrames;
} }
@ -133,7 +133,7 @@ TagLib::uint APE::Properties::sampleFrames() const
namespace namespace
{ {
inline int headerVersion(const ByteVector &header) int headerVersion(const ByteVector &header)
{ {
if(header.size() < 6 || !header.startsWith("MAC ")) if(header.size() < 6 || !header.startsWith("MAC "))
return -1; return -1;
@ -184,7 +184,7 @@ void APE::Properties::analyzeCurrent(File *file)
return; return;
} }
const uint descriptorBytes = descriptor.toUInt(0, false); const unsigned int descriptorBytes = descriptor.toUInt(0, false);
if((descriptorBytes - 52) > 0) if((descriptorBytes - 52) > 0)
file->seek(descriptorBytes - 52, File::Current); file->seek(descriptorBytes - 52, File::Current);
@ -201,12 +201,12 @@ void APE::Properties::analyzeCurrent(File *file)
d->sampleRate = header.toUInt(20, false); d->sampleRate = header.toUInt(20, false);
d->bitsPerSample = header.toShort(16, false); d->bitsPerSample = header.toShort(16, false);
const uint totalFrames = header.toUInt(12, false); const unsigned int totalFrames = header.toUInt(12, false);
if(totalFrames == 0) if(totalFrames == 0)
return; return;
const uint blocksPerFrame = header.toUInt(4, false); const unsigned int blocksPerFrame = header.toUInt(4, false);
const uint finalFrameBlocks = header.toUInt(8, false); const unsigned int finalFrameBlocks = header.toUInt(8, false);
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
} }
@ -218,14 +218,14 @@ void APE::Properties::analyzeOld(File *file)
return; return;
} }
const uint totalFrames = header.toUInt(18, false); const unsigned int totalFrames = header.toUInt(18, false);
// Fail on 0 length APE files (catches non-finalized APE files) // Fail on 0 length APE files (catches non-finalized APE files)
if(totalFrames == 0) if(totalFrames == 0)
return; return;
const short compressionLevel = header.toShort(0, false); const short compressionLevel = header.toShort(0, false);
uint blocksPerFrame; unsigned int blocksPerFrame;
if(d->version >= 3950) if(d->version >= 3950)
blocksPerFrame = 73728 * 4; blocksPerFrame = 73728 * 4;
else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000)) else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000))
@ -237,7 +237,7 @@ void APE::Properties::analyzeOld(File *file)
d->channels = header.toShort(4, false); d->channels = header.toShort(4, false);
d->sampleRate = header.toUInt(6, false); d->sampleRate = header.toUInt(6, false);
const uint finalFrameBlocks = header.toUInt(22, false); const unsigned int finalFrameBlocks = header.toUInt(22, false);
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
// Get the bit depth from the RIFF-fmt chunk. // Get the bit depth from the RIFF-fmt chunk.

View File

@ -118,7 +118,7 @@ namespace TagLib {
/*! /*!
* Returns the total number of audio samples in file. * Returns the total number of audio samples in file.
*/ */
uint sampleFrames() const; unsigned int sampleFrames() const;
/*! /*!
* Returns APE version. * Returns APE version.

View File

@ -23,7 +23,7 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifdef __SUNPRO_CC #if defined(__SUNPRO_CC) && (__SUNPRO_CC < 0x5130)
// Sun Studio finds multiple specializations of Map because // Sun Studio finds multiple specializations of Map because
// it considers specializations with and without class types // it considers specializations with and without class types
// to be different; this define forces Map to use only the // to be different; this define forces Map to use only the
@ -35,6 +35,8 @@
#include <tstring.h> #include <tstring.h>
#include <tmap.h> #include <tmap.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tdebug.h>
#include <tutils.h>
#include "apetag.h" #include "apetag.h"
#include "apefooter.h" #include "apefooter.h"
@ -43,18 +45,43 @@
using namespace TagLib; using namespace TagLib;
using namespace APE; using namespace APE;
namespace
{
bool isKeyValid(const char *key, size_t length)
{
const char *invalidKeys[] = { "ID3", "TAG", "OGGS", "MP+", 0 };
if(length < 2 || length > 255)
return false;
// only allow printable ASCII including space (32..126)
for(const char *p = key; p < key + length; ++p) {
const int c = static_cast<unsigned char>(*p);
if(c < 32 || c > 126)
return false;
}
for(size_t i = 0; invalidKeys[i] != 0; ++i) {
if(Utils::equalsIgnoreCase(key, invalidKeys[i]))
return false;
}
return true;
}
}
class APE::Tag::TagPrivate class APE::Tag::TagPrivate
{ {
public: public:
TagPrivate() : TagPrivate() :
file(0), file(0),
footerLocation(-1) {} footerLocation(0) {}
TagLib::File *file; File *file;
long footerLocation; long footerLocation;
Footer footer; Footer footer;
ItemListMap itemListMap; ItemListMap itemListMap;
}; };
@ -91,46 +118,46 @@ ByteVector APE::Tag::fileIdentifier()
String APE::Tag::title() const String APE::Tag::title() const
{ {
if(d->itemListMap["TITLE"].isEmpty()) if(d->itemListMap["TITLE"].isEmpty())
return String::null; return String();
return d->itemListMap["TITLE"].values().toString(); return d->itemListMap["TITLE"].values().toString();
} }
String APE::Tag::artist() const String APE::Tag::artist() const
{ {
if(d->itemListMap["ARTIST"].isEmpty()) if(d->itemListMap["ARTIST"].isEmpty())
return String::null; return String();
return d->itemListMap["ARTIST"].values().toString(); return d->itemListMap["ARTIST"].values().toString();
} }
String APE::Tag::album() const String APE::Tag::album() const
{ {
if(d->itemListMap["ALBUM"].isEmpty()) if(d->itemListMap["ALBUM"].isEmpty())
return String::null; return String();
return d->itemListMap["ALBUM"].values().toString(); return d->itemListMap["ALBUM"].values().toString();
} }
String APE::Tag::comment() const String APE::Tag::comment() const
{ {
if(d->itemListMap["COMMENT"].isEmpty()) if(d->itemListMap["COMMENT"].isEmpty())
return String::null; return String();
return d->itemListMap["COMMENT"].values().toString(); return d->itemListMap["COMMENT"].values().toString();
} }
String APE::Tag::genre() const String APE::Tag::genre() const
{ {
if(d->itemListMap["GENRE"].isEmpty()) if(d->itemListMap["GENRE"].isEmpty())
return String::null; return String();
return d->itemListMap["GENRE"].values().toString(); return d->itemListMap["GENRE"].values().toString();
} }
TagLib::uint APE::Tag::year() const unsigned int APE::Tag::year() const
{ {
if(d->itemListMap["YEAR"].isEmpty()) if(d->itemListMap["YEAR"].isEmpty())
return 0; return 0;
return d->itemListMap["YEAR"].toString().toInt(); return d->itemListMap["YEAR"].toString().toInt();
} }
TagLib::uint APE::Tag::track() const unsigned int APE::Tag::track() const
{ {
if(d->itemListMap["TRACK"].isEmpty()) if(d->itemListMap["TRACK"].isEmpty())
return 0; return 0;
@ -162,7 +189,7 @@ void APE::Tag::setGenre(const String &s)
addValue("GENRE", s, true); addValue("GENRE", s, true);
} }
void APE::Tag::setYear(uint i) void APE::Tag::setYear(unsigned int i)
{ {
if(i <= 0) if(i <= 0)
removeItem("YEAR"); removeItem("YEAR");
@ -170,7 +197,7 @@ void APE::Tag::setYear(uint i)
addValue("YEAR", String::number(i), true); addValue("YEAR", String::number(i), true);
} }
void APE::Tag::setTrack(uint i) void APE::Tag::setTrack(unsigned int i)
{ {
if(i <= 0) if(i <= 0)
removeItem("TRACK"); removeItem("TRACK");
@ -178,14 +205,18 @@ void APE::Tag::setTrack(uint i)
addValue("TRACK", String::number(i), true); addValue("TRACK", String::number(i), true);
} }
// conversions of tag keys between what we use in PropertyMap and what's usual namespace
// for APE tags {
static const TagLib::uint keyConversionsSize = 5; //usual, APE // conversions of tag keys between what we use in PropertyMap and what's usual
static const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" }, // for APE tags
// usual, APE
const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" },
{"DATE", "YEAR" }, {"DATE", "YEAR" },
{"ALBUMARTIST", "ALBUM ARTIST"}, {"ALBUMARTIST", "ALBUM ARTIST"},
{"DISCNUMBER", "DISC" }, {"DISCNUMBER", "DISC" },
{"REMIXER", "MIXARTIST" }}; {"REMIXER", "MIXARTIST" }};
const size_t keyConversionsSize = sizeof(keyConversions) / sizeof(keyConversions[0]);
}
PropertyMap APE::Tag::properties() const PropertyMap APE::Tag::properties() const
{ {
@ -195,13 +226,15 @@ PropertyMap APE::Tag::properties() const
String tagName = it->first.upper(); String tagName = it->first.upper();
// if the item is Binary or Locator, or if the key is an invalid string, // if the item is Binary or Locator, or if the key is an invalid string,
// add to unsupportedData // add to unsupportedData
if(it->second.type() != Item::Text || tagName.isNull()) if(it->second.type() != Item::Text || tagName.isEmpty()) {
properties.unsupportedData().append(it->first); properties.unsupportedData().append(it->first);
}
else { else {
// Some tags need to be handled specially // Some tags need to be handled specially
for(uint i = 0; i < keyConversionsSize; ++i) for(size_t i = 0; i < keyConversionsSize; ++i) {
if(tagName == keyConversions[i][1]) if(tagName == keyConversions[i][1])
tagName = keyConversions[i][0]; tagName = keyConversions[i][0];
}
properties[tagName].append(it->second.toStringList()); properties[tagName].append(it->second.toStringList());
} }
} }
@ -220,7 +253,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
PropertyMap properties(origProps); // make a local copy that can be modified PropertyMap properties(origProps); // make a local copy that can be modified
// see comment in properties() // see comment in properties()
for(uint i = 0; i < keyConversionsSize; ++i) for(size_t i = 0; i < keyConversionsSize; ++i)
if(properties.contains(keyConversions[i][0])) { if(properties.contains(keyConversions[i][0])) {
properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]); properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]);
properties.erase(keyConversions[i][0]); properties.erase(keyConversions[i][0]);
@ -232,7 +265,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
for(; remIt != itemListMap().end(); ++remIt) { for(; remIt != itemListMap().end(); ++remIt) {
String key = remIt->first.upper(); String key = remIt->first.upper();
// only remove if a) key is valid, b) type is text, c) key not contained in new properties // only remove if a) key is valid, b) type is text, c) key not contained in new properties
if(!key.isNull() && remIt->second.type() == APE::Item::Text && !properties.contains(key)) if(!key.isEmpty() && remIt->second.type() == APE::Item::Text && !properties.contains(key))
toRemove.append(remIt->first); toRemove.append(remIt->first);
} }
@ -247,7 +280,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
if(!checkKey(tagName)) if(!checkKey(tagName))
invalid.insert(it->first, it->second); invalid.insert(it->first, it->second);
else if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { else if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) {
if(it->second.size() == 0) if(it->second.isEmpty())
removeItem(tagName); removeItem(tagName);
else { else {
StringList::ConstIterator valueIt = it->second.begin(); StringList::ConstIterator valueIt = it->second.begin();
@ -263,16 +296,11 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
bool APE::Tag::checkKey(const String &key) bool APE::Tag::checkKey(const String &key)
{ {
if(key.size() < 2 || key.size() > 16) if(!key.isLatin1())
return false; return false;
for(String::ConstIterator it = key.begin(); it != key.end(); it++)
// only allow printable ASCII including space (32..127) const std::string data = key.to8Bit(false);
if (*it < 32 || *it >= 128) return isKeyValid(data.c_str(), data.size());
return false;
String upperKey = key.upper();
if (upperKey=="ID3" || upperKey=="TAG" || upperKey=="OGGS" || upperKey=="MP+")
return false;
return true;
} }
APE::Footer *APE::Tag::footer() const APE::Footer *APE::Tag::footer() const
@ -294,31 +322,39 @@ void APE::Tag::addValue(const String &key, const String &value, bool replace)
{ {
if(replace) if(replace)
removeItem(key); removeItem(key);
if(!key.isEmpty() && !value.isEmpty()) {
if(!replace && d->itemListMap.contains(key)) { if(value.isEmpty())
// Text items may contain more than one value return;
if(APE::Item::Text == d->itemListMap.begin()->second.type())
d->itemListMap[key.upper()].appendValue(value); // Text items may contain more than one value.
// Binary or locator items may have only one value // Binary or locator items may have only one value, hence always replaced.
ItemListMap::Iterator it = d->itemListMap.find(key.upper());
if(it != d->itemListMap.end() && it->second.type() == Item::Text)
it->second.appendValue(value);
else else
setItem(key, Item(key, value)); setItem(key, Item(key, value));
}
else
setItem(key, Item(key, value));
}
} }
void APE::Tag::setData(const String &key, const ByteVector &value) void APE::Tag::setData(const String &key, const ByteVector &value)
{ {
removeItem(key); removeItem(key);
if(!key.isEmpty() && !value.isEmpty())
if(value.isEmpty())
return;
setItem(key, Item(key, value, true)); setItem(key, Item(key, value, true));
} }
void APE::Tag::setItem(const String &key, const Item &item) void APE::Tag::setItem(const String &key, const Item &item)
{ {
if(!key.isEmpty()) if(!checkKey(key)) {
d->itemListMap.insert(key.upper(), item); debug("APE::Tag::setItem() - Couldn't set an item due to an invalid key.");
return;
}
d->itemListMap[key.upper()] = item;
} }
bool APE::Tag::isEmpty() const bool APE::Tag::isEmpty() const
@ -338,7 +374,7 @@ void APE::Tag::read()
d->footer.setData(d->file->readBlock(Footer::size())); d->footer.setData(d->file->readBlock(Footer::size()));
if(d->footer.tagSize() <= Footer::size() || if(d->footer.tagSize() <= Footer::size() ||
d->footer.tagSize() > uint(d->file->length())) d->footer.tagSize() > static_cast<unsigned long>(d->file->length()))
return; return;
d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize()); d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize());
@ -349,16 +385,12 @@ void APE::Tag::read()
ByteVector APE::Tag::render() const ByteVector APE::Tag::render() const
{ {
ByteVector data; ByteVector data;
uint itemCount = 0; unsigned int itemCount = 0;
{ for(ItemListMap::ConstIterator it = d->itemListMap.begin(); it != d->itemListMap.end(); ++it) {
for(Map<const String, Item>::ConstIterator it = d->itemListMap.begin();
it != d->itemListMap.end(); ++it)
{
data.append(it->second.render()); data.append(it->second.render());
itemCount++; itemCount++;
} }
}
d->footer.setItemCount(itemCount); d->footer.setItemCount(itemCount);
d->footer.setTagSize(data.size() + Footer::size()); d->footer.setTagSize(data.size() + Footer::size());
@ -374,14 +406,29 @@ void APE::Tag::parse(const ByteVector &data)
if(data.size() < 11) if(data.size() < 11)
return; return;
uint pos = 0; unsigned int pos = 0;
for(uint i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) { for(unsigned int i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) {
const int nullPos = data.find('\0', pos + 8);
if(nullPos < 0) {
debug("APE::Tag::parse() - Couldn't find a key/value separator. Stopped parsing.");
return;
}
const unsigned int keyLength = nullPos - pos - 8;
const unsigned int valLegnth = data.toUInt(pos, false);
if(isKeyValid(&data[pos + 8], keyLength)){
APE::Item item; APE::Item item;
item.parse(data.mid(pos)); item.parse(data.mid(pos));
d->itemListMap.insert(item.key().upper(), item); d->itemListMap.insert(item.key().upper(), item);
}
else {
debug("APE::Tag::parse() - Skipped an item due to an invalid key.");
}
pos += item.size(); pos += keyLength + valLegnth + 9;
} }
} }

View File

@ -92,16 +92,16 @@ namespace TagLib {
virtual String album() const; virtual String album() const;
virtual String comment() const; virtual String comment() const;
virtual String genre() const; virtual String genre() const;
virtual uint year() const; virtual unsigned int year() const;
virtual uint track() const; virtual unsigned int track() const;
virtual void setTitle(const String &s); virtual void setTitle(const String &s);
virtual void setArtist(const String &s); virtual void setArtist(const String &s);
virtual void setAlbum(const String &s); virtual void setAlbum(const String &s);
virtual void setComment(const String &s); virtual void setComment(const String &s);
virtual void setGenre(const String &s); virtual void setGenre(const String &s);
virtual void setYear(uint i); virtual void setYear(unsigned int i);
virtual void setTrack(uint i); virtual void setTrack(unsigned int i);
/*! /*!
* Implements the unified tag dictionary interface -- export function. * Implements the unified tag dictionary interface -- export function.

View File

@ -58,84 +58,86 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ASF::Attribute::Attribute() ASF::Attribute::Attribute() :
d(new AttributePrivate())
{ {
d = new AttributePrivate;
d->type = UnicodeType; d->type = UnicodeType;
} }
ASF::Attribute::Attribute(const ASF::Attribute &other) ASF::Attribute::Attribute(const ASF::Attribute &other) :
: d(other.d) d(other.d)
{ {
d->ref(); d->ref();
} }
ASF::Attribute::Attribute(const String &value) :
d(new AttributePrivate())
{
d->type = UnicodeType;
d->stringValue = value;
}
ASF::Attribute::Attribute(const ByteVector &value) :
d(new AttributePrivate())
{
d->type = BytesType;
d->byteVectorValue = value;
}
ASF::Attribute::Attribute(const ASF::Picture &value) :
d(new AttributePrivate())
{
d->type = BytesType;
d->pictureValue = value;
}
ASF::Attribute::Attribute(unsigned int value) :
d(new AttributePrivate())
{
d->type = DWordType;
d->intValue = value;
}
ASF::Attribute::Attribute(unsigned long long value) :
d(new AttributePrivate())
{
d->type = QWordType;
d->longLongValue = value;
}
ASF::Attribute::Attribute(unsigned short value) :
d(new AttributePrivate())
{
d->type = WordType;
d->shortValue = value;
}
ASF::Attribute::Attribute(bool value) :
d(new AttributePrivate())
{
d->type = BoolType;
d->boolValue = value;
}
ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other) ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other)
{ {
if(&other != this) { Attribute(other).swap(*this);
if(d->deref())
delete d;
d = other.d;
d->ref();
}
return *this; return *this;
} }
void ASF::Attribute::swap(Attribute &other)
{
using std::swap;
swap(d, other.d);
}
ASF::Attribute::~Attribute() ASF::Attribute::~Attribute()
{ {
if(d->deref()) if(d->deref())
delete d; delete d;
} }
ASF::Attribute::Attribute(const String &value)
{
d = new AttributePrivate;
d->type = UnicodeType;
d->stringValue = value;
}
ASF::Attribute::Attribute(const ByteVector &value)
{
d = new AttributePrivate;
d->type = BytesType;
d->byteVectorValue = value;
}
ASF::Attribute::Attribute(const ASF::Picture &value)
{
d = new AttributePrivate;
d->type = BytesType;
d->pictureValue = value;
}
ASF::Attribute::Attribute(unsigned int value)
{
d = new AttributePrivate;
d->type = DWordType;
d->intValue = value;
}
ASF::Attribute::Attribute(unsigned long long value)
{
d = new AttributePrivate;
d->type = QWordType;
d->longLongValue = value;
}
ASF::Attribute::Attribute(unsigned short value)
{
d = new AttributePrivate;
d->type = WordType;
d->shortValue = value;
}
ASF::Attribute::Attribute(bool value)
{
d = new AttributePrivate;
d->type = BoolType;
d->boolValue = value;
}
ASF::Attribute::AttributeTypes ASF::Attribute::type() const ASF::Attribute::AttributeTypes ASF::Attribute::type() const
{ {
return d->type; return d->type;
@ -180,7 +182,7 @@ ASF::Picture ASF::Attribute::toPicture() const
String ASF::Attribute::parse(ASF::File &f, int kind) String ASF::Attribute::parse(ASF::File &f, int kind)
{ {
uint size, nameLength; unsigned int size, nameLength;
String name; String name;
d->pictureValue = Picture::fromInvalid(); d->pictureValue = Picture::fromInvalid();
// extended content descriptor // extended content descriptor
@ -351,4 +353,3 @@ void ASF::Attribute::setStream(int value)
{ {
d->stream = value; d->stream = value;
} }

View File

@ -115,6 +115,11 @@ namespace TagLib
*/ */
ASF::Attribute &operator=(const Attribute &other); ASF::Attribute &operator=(const Attribute &other);
/*!
* Exchanges the content of the Attribute by the content of \a other.
*/
void swap(Attribute &other);
/*! /*!
* Destroys the attribute. * Destroys the attribute.
*/ */

View File

@ -50,7 +50,7 @@ public:
class MetadataLibraryObject; class MetadataLibraryObject;
FilePrivate(): FilePrivate():
size(0), headerSize(0),
tag(0), tag(0),
properties(0), properties(0),
contentDescriptionObject(0), contentDescriptionObject(0),
@ -68,7 +68,7 @@ public:
delete properties; delete properties;
} }
unsigned long long size; unsigned long long headerSize;
ASF::Tag *tag; ASF::Tag *tag;
ASF::Properties *properties; ASF::Properties *properties;
@ -120,21 +120,21 @@ class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePriva
{ {
public: public:
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
}; };
class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
}; };
class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
@ -143,7 +143,7 @@ class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::Fil
public: public:
ByteVectorList attributeData; ByteVectorList attributeData;
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
@ -152,7 +152,7 @@ class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::Ba
public: public:
ByteVectorList attributeData; ByteVectorList attributeData;
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
@ -161,7 +161,7 @@ class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePriv
public: public:
ByteVectorList attributeData; ByteVectorList attributeData;
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
@ -171,7 +171,7 @@ public:
List<ASF::File::FilePrivate::BaseObject *> objects; List<ASF::File::FilePrivate::BaseObject *> objects;
HeaderExtensionObject(); HeaderExtensionObject();
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
@ -179,7 +179,7 @@ class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::B
{ {
public: public:
ByteVector guid() const; ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, unsigned int size);
private: private:
enum CodecType enum CodecType
@ -196,7 +196,7 @@ void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int siz
if(size > 24 && size <= (unsigned int)(file->length())) if(size > 24 && size <= (unsigned int)(file->length()))
data = file->readBlock(size - 24); data = file->readBlock(size - 24);
else else
data = ByteVector::null; data = ByteVector();
} }
ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/) ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/)
@ -218,7 +218,7 @@ ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const
return filePropertiesGuid; return filePropertiesGuid;
} }
void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, uint size) void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, unsigned int size)
{ {
BaseObject::parse(file, size); BaseObject::parse(file, size);
if(data.size() < 64) { if(data.size() < 64) {
@ -236,7 +236,7 @@ ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const
return streamPropertiesGuid; return streamPropertiesGuid;
} }
void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, uint size) void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, unsigned int size)
{ {
BaseObject::parse(file, size); BaseObject::parse(file, size);
if(data.size() < 70) { if(data.size() < 70) {
@ -256,7 +256,7 @@ ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const
return contentDescriptionGuid; return contentDescriptionGuid;
} }
void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/)
{ {
file->d->contentDescriptionObject = this; file->d->contentDescriptionObject = this;
const int titleLength = readWORD(file); const int titleLength = readWORD(file);
@ -297,7 +297,7 @@ ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() cons
return extendedContentDescriptionGuid; return extendedContentDescriptionGuid;
} }
void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/)
{ {
file->d->extendedContentDescriptionObject = this; file->d->extendedContentDescriptionObject = this;
int count = readWORD(file); int count = readWORD(file);
@ -312,7 +312,7 @@ ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF:
{ {
data.clear(); data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false)); data.append(ByteVector::fromShort(attributeData.size(), false));
data.append(attributeData.toByteVector(ByteVector::null)); data.append(attributeData.toByteVector(""));
return BaseObject::render(file); return BaseObject::render(file);
} }
@ -321,7 +321,7 @@ ByteVector ASF::File::FilePrivate::MetadataObject::guid() const
return metadataGuid; return metadataGuid;
} }
void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, unsigned int /*size*/)
{ {
file->d->metadataObject = this; file->d->metadataObject = this;
int count = readWORD(file); int count = readWORD(file);
@ -336,7 +336,7 @@ ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
{ {
data.clear(); data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false)); data.append(ByteVector::fromShort(attributeData.size(), false));
data.append(attributeData.toByteVector(ByteVector::null)); data.append(attributeData.toByteVector(""));
return BaseObject::render(file); return BaseObject::render(file);
} }
@ -345,7 +345,7 @@ ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const
return metadataLibraryGuid; return metadataLibraryGuid;
} }
void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, unsigned int /*size*/)
{ {
file->d->metadataLibraryObject = this; file->d->metadataLibraryObject = this;
int count = readWORD(file); int count = readWORD(file);
@ -360,7 +360,7 @@ ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file
{ {
data.clear(); data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false)); data.append(ByteVector::fromShort(attributeData.size(), false));
data.append(attributeData.toByteVector(ByteVector::null)); data.append(attributeData.toByteVector(""));
return BaseObject::render(file); return BaseObject::render(file);
} }
@ -374,7 +374,7 @@ ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const
return headerExtensionGuid; return headerExtensionGuid;
} }
void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, unsigned int /*size*/)
{ {
file->d->headerExtensionObject = this; file->d->headerExtensionObject = this;
file->seek(18, File::Current); file->seek(18, File::Current);
@ -423,7 +423,7 @@ ByteVector ASF::File::FilePrivate::CodecListObject::guid() const
return codecListGuid; return codecListGuid;
} }
void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size) void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, unsigned int size)
{ {
BaseObject::parse(file, size); BaseObject::parse(file, size);
if(data.size() <= 20) { if(data.size() <= 20) {
@ -431,7 +431,7 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size)
return; return;
} }
uint pos = 16; unsigned int pos = 16;
const int count = data.toUInt(pos, false); const int count = data.toUInt(pos, false);
pos += 4; pos += 4;
@ -447,13 +447,13 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size)
int nameLength = data.toUShort(pos, false); int nameLength = data.toUShort(pos, false);
pos += 2; pos += 2;
const uint namePos = pos; const unsigned int namePos = pos;
pos += nameLength * 2; pos += nameLength * 2;
const int descLength = data.toUShort(pos, false); const int descLength = data.toUShort(pos, false);
pos += 2; pos += 2;
const uint descPos = pos; const unsigned int descPos = pos;
pos += descLength * 2; pos += descLength * 2;
const int infoLength = data.toUShort(pos, false); const int infoLength = data.toUShort(pos, false);
@ -556,6 +556,10 @@ bool ASF::File::save()
d->headerExtensionObject->objects.append(d->metadataLibraryObject); d->headerExtensionObject->objects.append(d->metadataLibraryObject);
} }
d->extendedContentDescriptionObject->attributeData.clear();
d->metadataObject->attributeData.clear();
d->metadataLibraryObject->attributeData.clear();
const AttributeListMap allAttributes = d->tag->attributeListMap(); const AttributeListMap allAttributes = d->tag->attributeListMap();
for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) { for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) {
@ -591,8 +595,14 @@ bool ASF::File::save()
data.append((*it)->render(this)); data.append((*it)->render(this));
} }
data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data; seek(16);
insert(data, 0, (TagLib::ulong)d->size); writeBlock(ByteVector::fromLongLong(data.size() + 30, false));
writeBlock(ByteVector::fromUInt(d->objects.size(), false));
writeBlock(ByteVector("\x01\x02", 2));
insert(data, 30, static_cast<unsigned long>(d->headerSize - 30));
d->headerSize = data.size() + 30;
return true; return true;
} }
@ -617,7 +627,7 @@ void ASF::File::read()
d->properties = new ASF::Properties(); d->properties = new ASF::Properties();
bool ok; bool ok;
d->size = readQWORD(this, &ok); d->headerSize = readQWORD(this, &ok);
if(!ok) { if(!ok) {
setValid(false); setValid(false);
return; return;

View File

@ -112,9 +112,6 @@ namespace TagLib {
* Save the file. * Save the file.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();

View File

@ -48,14 +48,14 @@ public:
// Picture class members // Picture class members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ASF::Picture::Picture() ASF::Picture::Picture() :
d(new PicturePrivate())
{ {
d = new PicturePrivate();
d->valid = true; d->valid = true;
} }
ASF::Picture::Picture(const Picture& other) ASF::Picture::Picture(const Picture& other) :
: d(other.d) d(other.d)
{ {
d->ref(); d->ref();
} }
@ -120,19 +120,22 @@ int ASF::Picture::dataSize() const
ASF::Picture& ASF::Picture::operator=(const ASF::Picture& other) ASF::Picture& ASF::Picture::operator=(const ASF::Picture& other)
{ {
if(other.d != d) { Picture(other).swap(*this);
if(d->deref())
delete d;
d = other.d;
d->ref();
}
return *this; return *this;
} }
void ASF::Picture::swap(Picture &other)
{
using std::swap;
swap(d, other.d);
}
ByteVector ASF::Picture::render() const ByteVector ASF::Picture::render() const
{ {
if(!isValid()) if(!isValid())
return ByteVector::null; return ByteVector();
return return
ByteVector((char)d->type) + ByteVector((char)d->type) +
ByteVector::fromUInt(d->picture.size(), false) + ByteVector::fromUInt(d->picture.size(), false) +
@ -148,7 +151,7 @@ void ASF::Picture::parse(const ByteVector& bytes)
return; return;
int pos = 0; int pos = 0;
d->type = (Type)bytes[0]; ++pos; d->type = (Type)bytes[0]; ++pos;
const uint dataLen = bytes.toUInt(pos, false); pos+=4; const unsigned int dataLen = bytes.toUInt(pos, false); pos+=4;
const ByteVector nullStringTerminator(2, 0); const ByteVector nullStringTerminator(2, 0);
@ -178,4 +181,3 @@ ASF::Picture ASF::Picture::fromInvalid()
ret.d->valid = false; ret.d->valid = false;
return ret; return ret;
} }

View File

@ -117,6 +117,11 @@ namespace TagLib
*/ */
Picture& operator=(const Picture& other); Picture& operator=(const Picture& other);
/*!
* Exchanges the content of the Picture by the content of \a other.
*/
void swap(Picture &other);
/*! /*!
* Returns true if Picture stores valid picture * Returns true if Picture stores valid picture
*/ */

View File

@ -64,7 +64,7 @@ String ASF::Tag::album() const
{ {
if(d->attributeListMap.contains("WM/AlbumTitle")) if(d->attributeListMap.contains("WM/AlbumTitle"))
return d->attributeListMap["WM/AlbumTitle"][0].toString(); return d->attributeListMap["WM/AlbumTitle"][0].toString();
return String::null; return String();
} }
String ASF::Tag::copyright() const String ASF::Tag::copyright() const
@ -107,7 +107,7 @@ String ASF::Tag::genre() const
{ {
if(d->attributeListMap.contains("WM/Genre")) if(d->attributeListMap.contains("WM/Genre"))
return d->attributeListMap["WM/Genre"][0].toString(); return d->attributeListMap["WM/Genre"][0].toString();
return String::null; return String();
} }
void ASF::Tag::setTitle(const String &value) void ASF::Tag::setTitle(const String &value)
@ -145,12 +145,12 @@ void ASF::Tag::setGenre(const String &value)
setAttribute("WM/Genre", value); setAttribute("WM/Genre", value);
} }
void ASF::Tag::setYear(uint value) void ASF::Tag::setYear(unsigned int value)
{ {
setAttribute("WM/Year", String::number(value)); setAttribute("WM/Year", String::number(value));
} }
void ASF::Tag::setTrack(uint value) void ASF::Tag::setTrack(unsigned int value)
{ {
setAttribute("WM/TrackNumber", String::number(value)); setAttribute("WM/TrackNumber", String::number(value));
} }
@ -210,7 +210,9 @@ bool ASF::Tag::isEmpty() const
d->attributeListMap.isEmpty(); d->attributeListMap.isEmpty();
} }
static const char *keyTranslation[][2] = { namespace
{
const char *keyTranslation[][2] = {
{ "WM/AlbumTitle", "ALBUM" }, { "WM/AlbumTitle", "ALBUM" },
{ "WM/AlbumArtist", "ALBUMARTIST" }, { "WM/AlbumArtist", "ALBUMARTIST" },
{ "WM/Composer", "COMPOSER" }, { "WM/Composer", "COMPOSER" },
@ -250,18 +252,22 @@ static const char *keyTranslation[][2] = {
{ "MusicIP/PUID", "MUSICIP_PUID" }, { "MusicIP/PUID", "MUSICIP_PUID" },
{ "Acoustid/Id", "ACOUSTID_ID" }, { "Acoustid/Id", "ACOUSTID_ID" },
{ "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" }, { "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" },
}; };
const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
String translateKey(const String &key)
{
for(size_t i = 0; i < keyTranslationSize; ++i) {
if(key == keyTranslation[i][0])
return keyTranslation[i][1];
}
return String();
}
}
PropertyMap ASF::Tag::properties() const PropertyMap ASF::Tag::properties() const
{ {
static Map<String, String> keyMap;
if(keyMap.isEmpty()) {
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
for(int i = 0; i < numKeys; i++) {
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
}
}
PropertyMap props; PropertyMap props;
if(!d->title.isEmpty()) { if(!d->title.isEmpty()) {
@ -279,8 +285,8 @@ PropertyMap ASF::Tag::properties() const
ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin(); ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin();
for(; it != d->attributeListMap.end(); ++it) { for(; it != d->attributeListMap.end(); ++it) {
if(keyMap.contains(it->first)) { const String key = translateKey(it->first);
String key = keyMap[it->first]; if(!key.isEmpty()) {
AttributeList::ConstIterator it2 = it->second.begin(); AttributeList::ConstIterator it2 = it->second.begin();
for(; it2 != it->second.end(); ++it2) { for(; it2 != it->second.end(); ++it2) {
if(key == "TRACKNUMBER") { if(key == "TRACKNUMBER") {
@ -323,16 +329,16 @@ PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
for(; it != origProps.end(); ++it) { for(; it != origProps.end(); ++it) {
if(!props.contains(it->first) || props[it->first].isEmpty()) { if(!props.contains(it->first) || props[it->first].isEmpty()) {
if(it->first == "TITLE") { if(it->first == "TITLE") {
d->title = String::null; d->title.clear();
} }
else if(it->first == "ARTIST") { else if(it->first == "ARTIST") {
d->artist = String::null; d->artist.clear();
} }
else if(it->first == "COMMENT") { else if(it->first == "COMMENT") {
d->comment = String::null; d->comment.clear();
} }
else if(it->first == "COPYRIGHT") { else if(it->first == "COPYRIGHT") {
d->copyright = String::null; d->copyright.clear();
} }
else { else {
d->attributeListMap.erase(reverseKeyMap[it->first]); d->attributeListMap.erase(reverseKeyMap[it->first]);

View File

@ -90,13 +90,13 @@ namespace TagLib {
/*! /*!
* Returns the year; if there is no year set, this will return 0. * Returns the year; if there is no year set, this will return 0.
*/ */
virtual uint year() const; virtual unsigned int year() const;
/*! /*!
* Returns the track number; if there is no track number set, this will * Returns the track number; if there is no track number set, this will
* return 0. * return 0.
*/ */
virtual uint track() const; virtual unsigned int track() const;
/*! /*!
* Sets the title to \a s. * Sets the title to \a s.
@ -137,12 +137,12 @@ namespace TagLib {
/*! /*!
* Sets the year to \a i. If \a s is 0 then this value will be cleared. * Sets the year to \a i. If \a s is 0 then this value will be cleared.
*/ */
virtual void setYear(uint i); virtual void setYear(unsigned int i);
/*! /*!
* Sets the track to \a i. If \a s is 0 then this value will be cleared. * Sets the track to \a i. If \a s is 0 then this value will be cleared.
*/ */
virtual void setTrack(uint i); virtual void setTrack(unsigned int i);
/*! /*!
* Returns true if the tag does not contain any data. This should be * Returns true if the tag does not contain any data. This should be

View File

@ -33,9 +33,11 @@
namespace TagLib namespace TagLib
{ {
namespace ASF namespace ASF
{
namespace
{ {
inline ushort readWORD(File *file, bool *ok = 0) inline unsigned short readWORD(File *file, bool *ok = 0)
{ {
const ByteVector v = file->readBlock(2); const ByteVector v = file->readBlock(2);
if(v.size() != 2) { if(v.size() != 2) {
@ -46,7 +48,7 @@ namespace TagLib
return v.toUShort(false); return v.toUShort(false);
} }
inline uint readDWORD(File *file, bool *ok = 0) inline unsigned int readDWORD(File *file, bool *ok = 0)
{ {
const ByteVector v = file->readBlock(4); const ByteVector v = file->readBlock(4);
if(v.size() != 4) { if(v.size() != 4) {
@ -94,6 +96,7 @@ namespace TagLib
} }
} }
}
} }
#endif #endif

View File

@ -57,7 +57,7 @@ AudioProperties::~AudioProperties()
} }
int TagLib::AudioProperties::lengthInSeconds() const int AudioProperties::lengthInSeconds() const
{ {
// This is an ugly workaround but we can't add a virtual function. // This is an ugly workaround but we can't add a virtual function.
// Should be virtual in taglib2. // Should be virtual in taglib2.
@ -105,7 +105,7 @@ int TagLib::AudioProperties::lengthInSeconds() const
return 0; return 0;
} }
int TagLib::AudioProperties::lengthInMilliseconds() const int AudioProperties::lengthInMilliseconds() const
{ {
// This is an ugly workaround but we can't add a virtual function. // This is an ugly workaround but we can't add a virtual function.
// Should be virtual in taglib2. // Should be virtual in taglib2.

View File

@ -30,7 +30,7 @@
#include <tfile.h> #include <tfile.h>
#include <tstring.h> #include <tstring.h>
#include <tdebug.h> #include <tdebug.h>
#include "trefcounter.h" #include <trefcounter.h>
#include "fileref.h" #include "fileref.h"
#include "asffile.h" #include "asffile.h"
@ -54,41 +54,176 @@
using namespace TagLib; using namespace TagLib;
namespace
{
typedef List<const FileRef::FileTypeResolver *> ResolverList;
ResolverList fileTypeResolvers;
// Templatized internal functions. T should be String or IOStream*.
template <typename T>
FileName toFileName(T arg)
{
debug("FileRef::toFileName<T>(): This version should never be called.");
return FileName(L"");
}
template <>
FileName toFileName<IOStream *>(IOStream *arg)
{
return arg->name();
}
template <>
FileName toFileName<FileName>(FileName arg)
{
return arg;
}
template <typename T>
File *resolveFileType(T arg, bool readProperties,
AudioProperties::ReadStyle style)
{
debug("FileRef::resolveFileType<T>(): This version should never be called.");
return 0;
}
template <>
File *resolveFileType<IOStream *>(IOStream *arg, bool readProperties,
AudioProperties::ReadStyle style)
{
return 0;
}
template <>
File *resolveFileType<FileName>(FileName arg, bool readProperties,
AudioProperties::ReadStyle style)
{
ResolverList::ConstIterator it = fileTypeResolvers.begin();
for(; it != fileTypeResolvers.end(); ++it) {
File *file = (*it)->createFile(arg, readProperties, style);
if(file)
return file;
}
return 0;
}
template <typename T>
File* createInternal(T arg, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
File *file = resolveFileType(arg, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
#ifdef _WIN32
const String s = toFileName(arg).toString();
#else
const String s(toFileName(arg));
#endif
String ext;
const int pos = s.rfind(".");
if(pos != -1)
ext = s.substr(pos + 1).upper();
// If this list is updated, the method defaultFileExtensions() should also be
// updated. However at some point that list should be created at the same time
// that a default file type resolver is created.
if(ext.isEmpty())
return 0;
if(ext == "MP3")
return new MPEG::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
if(ext == "OGG")
return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "OGA") {
/* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
File *file = new Ogg::FLAC::File(arg, readAudioProperties, audioPropertiesStyle);
if(file->isValid())
return file;
delete file;
return new Ogg::Vorbis::File(arg, readAudioProperties, audioPropertiesStyle);
}
if(ext == "FLAC")
return new FLAC::File(arg, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
if(ext == "MPC")
return new MPC::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "WV")
return new WavPack::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "SPX")
return new Ogg::Speex::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "OPUS")
return new Ogg::Opus::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "TTA")
return new TrueAudio::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
return new MP4::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "WMA" || ext == "ASF")
return new ASF::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
return new RIFF::AIFF::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "WAV")
return new RIFF::WAV::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "APE")
return new APE::File(arg, readAudioProperties, audioPropertiesStyle);
// module, nst and wow are possible but uncommon extensions
if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
return new Mod::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "S3M")
return new S3M::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "IT")
return new IT::File(arg, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(arg, readAudioProperties, audioPropertiesStyle);
return 0;
}
}
class FileRef::FileRefPrivate : public RefCounter class FileRef::FileRefPrivate : public RefCounter
{ {
public: public:
FileRefPrivate(File *f) : RefCounter(), file(f) {} FileRefPrivate(File *f) :
RefCounter(),
file(f) {}
~FileRefPrivate() { ~FileRefPrivate() {
delete file; delete file;
} }
File *file; File *file;
static List<const FileTypeResolver *> fileTypeResolvers;
}; };
List<const FileRef::FileTypeResolver *> FileRef::FileRefPrivate::fileTypeResolvers;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
FileRef::FileRef() FileRef::FileRef() :
d(new FileRefPrivate(0))
{ {
d = new FileRefPrivate(0);
} }
FileRef::FileRef(FileName fileName, bool readAudioProperties, FileRef::FileRef(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle) AudioProperties::ReadStyle audioPropertiesStyle) :
d(new FileRefPrivate(createInternal(fileName, readAudioProperties, audioPropertiesStyle)))
{ {
d = new FileRefPrivate(create(fileName, readAudioProperties, audioPropertiesStyle));
} }
FileRef::FileRef(File *file) FileRef::FileRef(IOStream* stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) :
d(new FileRefPrivate(createInternal(stream, readAudioProperties, audioPropertiesStyle)))
{ {
d = new FileRefPrivate(file);
} }
FileRef::FileRef(const FileRef &ref) : d(ref.d) FileRef::FileRef(File *file) :
d(new FileRefPrivate(file))
{
}
FileRef::FileRef(const FileRef &ref) :
d(ref.d)
{ {
d->ref(); d->ref();
} }
@ -133,7 +268,7 @@ bool FileRef::save()
const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static
{ {
FileRefPrivate::fileTypeResolvers.prepend(resolver); fileTypeResolvers.prepend(resolver);
return resolver; return resolver;
} }
@ -155,6 +290,7 @@ StringList FileRef::defaultFileExtensions()
l.append("m4p"); l.append("m4p");
l.append("3g2"); l.append("3g2");
l.append("mp4"); l.append("mp4");
l.append("m4v");
l.append("wma"); l.append("wma");
l.append("asf"); l.append("asf");
l.append("aif"); l.append("aif");
@ -174,113 +310,34 @@ StringList FileRef::defaultFileExtensions()
bool FileRef::isNull() const bool FileRef::isNull() const
{ {
return !d->file || !d->file->isValid(); return (!d->file || !d->file->isValid());
} }
FileRef &FileRef::operator=(const FileRef &ref) FileRef &FileRef::operator=(const FileRef &ref)
{ {
if(&ref == this) FileRef(ref).swap(*this);
return *this; return *this;
}
if(d->deref()) void FileRef::swap(FileRef &ref)
delete d; {
using std::swap;
d = ref.d; swap(d, ref.d);
d->ref();
return *this;
} }
bool FileRef::operator==(const FileRef &ref) const bool FileRef::operator==(const FileRef &ref) const
{ {
return ref.d->file == d->file; return (ref.d->file == d->file);
} }
bool FileRef::operator!=(const FileRef &ref) const bool FileRef::operator!=(const FileRef &ref) const
{ {
return ref.d->file != d->file; return (ref.d->file != d->file);
} }
File *FileRef::create(FileName fileName, bool readAudioProperties, File *FileRef::create(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle) // static AudioProperties::ReadStyle audioPropertiesStyle) // static
{ {
return createInternal(fileName, readAudioProperties, audioPropertiesStyle);
List<const FileTypeResolver *>::ConstIterator it = FileRefPrivate::fileTypeResolvers.begin();
for(; it != FileRefPrivate::fileTypeResolvers.end(); ++it) {
File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
}
// Ok, this is really dumb for now, but it works for testing.
String ext;
{
#ifdef _WIN32
String s = fileName.toString();
#else
String s = fileName;
#endif
const int pos = s.rfind(".");
if(pos != -1)
ext = s.substr(pos + 1).upper();
}
// If this list is updated, the method defaultFileExtensions() should also be
// updated. However at some point that list should be created at the same time
// that a default file type resolver is created.
if(!ext.isEmpty()) {
if(ext == "MP3")
return new MPEG::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "OGG")
return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "OGA") {
/* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */
File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
if (file->isValid())
return file;
delete file;
return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle);
}
if(ext == "FLAC")
return new FLAC::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "MPC")
return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WV")
return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "SPX")
return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "OPUS")
return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "TTA")
return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2")
return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WMA" || ext == "ASF")
return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WAV")
return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "APE")
return new APE::File(fileName, readAudioProperties, audioPropertiesStyle);
// module, nst and wow are possible but uncommon extensions
if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "S3M")
return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "IT")
return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
}
return 0;
} }

View File

@ -127,6 +127,23 @@ namespace TagLib {
AudioProperties::ReadStyle AudioProperties::ReadStyle
audioPropertiesStyle = AudioProperties::Average); audioPropertiesStyle = AudioProperties::Average);
/*!
* Construct a FileRef from an opened \a IOStream. If \a readAudioProperties
* is true then the audio properties will be read using \a audioPropertiesStyle.
* If \a readAudioProperties is false then \a audioPropertiesStyle will be
* ignored.
*
* Also see the note in the class documentation about why you may not want to
* use this method in your application.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
explicit FileRef(IOStream* stream,
bool readAudioProperties = true,
AudioProperties::ReadStyle
audioPropertiesStyle = AudioProperties::Average);
/*! /*!
* Construct a FileRef using \a file. The FileRef now takes ownership of the * Construct a FileRef using \a file. The FileRef now takes ownership of the
* pointer and will delete the File when it passes out of scope. * pointer and will delete the File when it passes out of scope.
@ -226,6 +243,11 @@ namespace TagLib {
*/ */
FileRef &operator=(const FileRef &ref); FileRef &operator=(const FileRef &ref);
/*!
* Exchanges the content of the FileRef by the content of \a ref.
*/
void swap(FileRef &ref);
/*! /*!
* Returns true if this FileRef and \a ref point to the same File object. * Returns true if this FileRef and \a ref point to the same File object.
*/ */

View File

@ -29,6 +29,7 @@
#include <tdebug.h> #include <tdebug.h>
#include <tagunion.h> #include <tagunion.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tagutils.h>
#include <id3v2header.h> #include <id3v2header.h>
#include <id3v2tag.h> #include <id3v2tag.h>
@ -44,41 +45,42 @@ using namespace TagLib;
namespace namespace
{ {
typedef List<FLAC::MetadataBlock *> BlockList;
typedef BlockList::Iterator BlockIterator;
typedef BlockList::Iterator BlockConstIterator;
enum { FlacXiphIndex = 0, FlacID3v2Index = 1, FlacID3v1Index = 2 }; enum { FlacXiphIndex = 0, FlacID3v2Index = 1, FlacID3v1Index = 2 };
enum { MinPaddingLength = 4096 };
enum { LastBlockFlag = 0x80 }; const long MinPaddingLength = 4096;
const long MaxPaddingLegnth = 1024 * 1024;
const char LastBlockFlag = '\x80';
} }
class FLAC::File::FilePrivate class FLAC::File::FilePrivate
{ {
public: public:
FilePrivate() : FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
ID3v2FrameFactory(ID3v2::FrameFactory::instance()), ID3v2FrameFactory(frameFactory),
ID3v2Location(-1), ID3v2Location(-1),
ID3v2OriginalSize(0), ID3v2OriginalSize(0),
ID3v1Location(-1), ID3v1Location(-1),
properties(0), properties(0),
flacStart(0), flacStart(0),
streamStart(0), streamStart(0),
scanned(false), scanned(false)
hasXiphComment(false),
hasID3v2(false),
hasID3v1(false)
{ {
blocks.setAutoDelete(true);
} }
~FilePrivate() ~FilePrivate()
{ {
uint size = blocks.size();
for(uint i = 0; i < size; i++) {
delete blocks[i];
}
delete properties; delete properties;
} }
const ID3v2::FrameFactory *ID3v2FrameFactory; const ID3v2::FrameFactory *ID3v2FrameFactory;
long ID3v2Location; long ID3v2Location;
uint ID3v2OriginalSize; long ID3v2OriginalSize;
long ID3v1Location; long ID3v1Location;
@ -86,15 +88,11 @@ public:
Properties *properties; Properties *properties;
ByteVector xiphCommentData; ByteVector xiphCommentData;
List<MetadataBlock *> blocks; BlockList blocks;
long flacStart; long flacStart;
long streamStart; long streamStart;
bool scanned; bool scanned;
bool hasXiphComment;
bool hasID3v2;
bool hasID3v1;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -112,9 +110,8 @@ FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory, FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
bool readProperties, Properties::ReadStyle) : bool readProperties, Properties::ReadStyle) :
TagLib::File(file), TagLib::File(file),
d(new FilePrivate()) d(new FilePrivate(frameFactory))
{ {
d->ID3v2FrameFactory = frameFactory;
if(isOpen()) if(isOpen())
read(readProperties); read(readProperties);
} }
@ -122,9 +119,8 @@ FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
bool readProperties, Properties::ReadStyle) : bool readProperties, Properties::ReadStyle) :
TagLib::File(stream), TagLib::File(stream),
d(new FilePrivate()) d(new FilePrivate(frameFactory))
{ {
d->ID3v2FrameFactory = frameFactory;
if(isOpen()) if(isOpen())
read(readProperties); read(readProperties);
} }
@ -141,30 +137,17 @@ TagLib::Tag *FLAC::File::tag() const
PropertyMap FLAC::File::properties() const PropertyMap FLAC::File::properties() const
{ {
// once Tag::properties() is virtual, this case distinction could actually be done return d->tag.properties();
// within TagUnion.
if(d->hasXiphComment)
return d->tag.access<Ogg::XiphComment>(FlacXiphIndex, false)->properties();
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(FlacID3v2Index, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(FlacID3v1Index, false)->properties();
return PropertyMap();
} }
void FLAC::File::removeUnsupportedProperties(const StringList &unsupported) void FLAC::File::removeUnsupportedProperties(const StringList &unsupported)
{ {
if(d->hasXiphComment) d->tag.removeUnsupportedProperties(unsupported);
d->tag.access<Ogg::XiphComment>(FlacXiphIndex, false)->removeUnsupportedProperties(unsupported);
if(d->hasID3v2)
d->tag.access<ID3v2::Tag>(FlacID3v2Index, false)->removeUnsupportedProperties(unsupported);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(FlacID3v1Index, false)->removeUnsupportedProperties(unsupported);
} }
PropertyMap FLAC::File::setProperties(const PropertyMap &properties) PropertyMap FLAC::File::setProperties(const PropertyMap &properties)
{ {
return d->tag.access<Ogg::XiphComment>(FlacXiphIndex, true)->setProperties(properties); return xiphComment(true)->setProperties(properties);
} }
FLAC::Properties *FLAC::File::audioProperties() const FLAC::Properties *FLAC::File::audioProperties() const
@ -192,73 +175,105 @@ bool FLAC::File::save()
// Replace metadata blocks // Replace metadata blocks
bool foundVorbisCommentBlock = false; for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
List<MetadataBlock *> newBlocks; if((*it)->code() == MetadataBlock::VorbisComment) {
for(uint i = 0; i < d->blocks.size(); i++) {
MetadataBlock *block = d->blocks[i];
if(block->code() == MetadataBlock::VorbisComment) {
// Set the new Vorbis Comment block // Set the new Vorbis Comment block
delete block; delete *it;
block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData); d->blocks.erase(it);
foundVorbisCommentBlock = true; break;
} }
if(block->code() == MetadataBlock::Padding) {
delete block;
continue;
} }
newBlocks.append(block);
} d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
if(!foundVorbisCommentBlock) {
newBlocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
foundVorbisCommentBlock = true;
}
d->blocks = newBlocks;
// Render data for the metadata blocks // Render data for the metadata blocks
ByteVector data; ByteVector data;
for(uint i = 0; i < newBlocks.size(); i++) { for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
FLAC::MetadataBlock *block = newBlocks[i]; ByteVector blockData = (*it)->render();
ByteVector blockData = block->render();
ByteVector blockHeader = ByteVector::fromUInt(blockData.size()); ByteVector blockHeader = ByteVector::fromUInt(blockData.size());
blockHeader[0] = block->code(); blockHeader[0] = (*it)->code();
data.append(blockHeader); data.append(blockHeader);
data.append(blockData); data.append(blockData);
} }
// Adjust the padding block(s) // Compute the amount of padding, and append that to data.
// TODO: Should be calculated in offset_t in taglib2.
long originalLength = d->streamStart - d->flacStart; long originalLength = d->streamStart - d->flacStart;
int paddingLength = originalLength - data.size() - 4; long paddingLength = originalLength - data.size() - 4;
if(paddingLength <= 0) { if(paddingLength <= 0) {
paddingLength = MinPaddingLength; paddingLength = MinPaddingLength;
} }
ByteVector padding = ByteVector::fromUInt(paddingLength); else {
padding.resize(paddingLength + 4); // Padding won't increase beyond 1% of the file size or 1MB.
padding[0] = (char)(FLAC::MetadataBlock::Padding | LastBlockFlag);
data.append(padding); long threshold = length() / 100;
threshold = std::max(threshold, MinPaddingLength);
threshold = std::min(threshold, MaxPaddingLegnth);
if(paddingLength > threshold)
paddingLength = MinPaddingLength;
}
ByteVector paddingHeader = ByteVector::fromUInt(paddingLength);
paddingHeader[0] = static_cast<char>(MetadataBlock::Padding | LastBlockFlag);
data.append(paddingHeader);
data.resize(static_cast<unsigned int>(data.size() + paddingLength));
// Write the data to the file // Write the data to the file
insert(data, d->flacStart, originalLength); insert(data, d->flacStart, originalLength);
d->hasXiphComment = true;
d->streamStart += (static_cast<long>(data.size()) - originalLength);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - originalLength);
// Update ID3 tags // Update ID3 tags
if(ID3v2Tag()) { if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
if(d->hasID3v2) {
if(d->ID3v2Location < d->flacStart) // ID3v2 tag is not empty. Update the old one or create a new one.
debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the "
"start of the FLAC bytestream? Not writing the ID3v2 tag."); if(d->ID3v2Location < 0)
else d->ID3v2Location = 0;
insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
data = ID3v2Tag()->render();
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
d->flacStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
d->streamStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
d->ID3v2OriginalSize = data.size();
}
else {
// ID3v2 tag is empty. Remove the old one.
if(d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
d->flacStart -= d->ID3v2OriginalSize;
d->streamStart -= d->ID3v2OriginalSize;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2OriginalSize;
d->ID3v2Location = -1;
d->ID3v2OriginalSize = 0;
} }
else
insert(ID3v2Tag()->render(), 0, 0);
} }
if(ID3v1Tag()) { if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
if(d->hasID3v1) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location); seek(d->ID3v1Location);
} }
else { else {
@ -267,7 +282,15 @@ bool FLAC::File::save()
} }
writeBlock(ID3v1Tag()->render()); writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true; }
else {
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
} }
return true; return true;
@ -312,8 +335,8 @@ long FLAC::File::streamLength()
List<FLAC::Picture *> FLAC::File::pictureList() List<FLAC::Picture *> FLAC::File::pictureList()
{ {
List<Picture *> pictures; List<Picture *> pictures;
for(uint i = 0; i < d->blocks.size(); i++) { for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
Picture *picture = dynamic_cast<Picture *>(d->blocks[i]); Picture *picture = dynamic_cast<Picture *>(*it);
if(picture) { if(picture) {
pictures.append(picture); pictures.append(picture);
} }
@ -328,8 +351,7 @@ void FLAC::File::addPicture(Picture *picture)
void FLAC::File::removePicture(Picture *picture, bool del) void FLAC::File::removePicture(Picture *picture, bool del)
{ {
MetadataBlock *block = picture; BlockIterator it = d->blocks.find(picture);
List<MetadataBlock *>::Iterator it = d->blocks.find(block);
if(it != d->blocks.end()) if(it != d->blocks.end())
d->blocks.erase(it); d->blocks.erase(it);
@ -339,32 +361,44 @@ void FLAC::File::removePicture(Picture *picture, bool del)
void FLAC::File::removePictures() void FLAC::File::removePictures()
{ {
List<MetadataBlock *> newBlocks; for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ) {
for(uint i = 0; i < d->blocks.size(); i++) { if(dynamic_cast<Picture *>(*it)) {
Picture *picture = dynamic_cast<Picture *>(d->blocks[i]); delete *it;
if(picture) { it = d->blocks.erase(it);
delete picture;
} }
else { else {
newBlocks.append(d->blocks[i]); ++it;
} }
} }
d->blocks = newBlocks; }
void FLAC::File::strip(int tags)
{
if(tags & ID3v1)
d->tag.set(FlacID3v1Index, 0);
if(tags & ID3v2)
d->tag.set(FlacID3v2Index, 0);
if(tags & XiphComment) {
xiphComment()->removeAllFields();
xiphComment()->removeAllPictures();
}
} }
bool FLAC::File::hasXiphComment() const bool FLAC::File::hasXiphComment() const
{ {
return d->hasXiphComment; return !d->xiphCommentData.isEmpty();
} }
bool FLAC::File::hasID3v1Tag() const bool FLAC::File::hasID3v1Tag() const
{ {
return d->hasID3v1; return (d->ID3v1Location >= 0);
} }
bool FLAC::File::hasID3v2Tag() const bool FLAC::File::hasID3v2Tag() const
{ {
return d->hasID3v2; return (d->ID3v2Location >= 0);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -375,28 +409,19 @@ void FLAC::File::read(bool readProperties)
{ {
// Look for an ID3v2 tag // Look for an ID3v2 tag
d->ID3v2Location = findID3v2(); d->ID3v2Location = Utils::findID3v2(this);
if(d->ID3v2Location >= 0) { if(d->ID3v2Location >= 0) {
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(FlacID3v2Index, 0);
else
d->hasID3v2 = true;
} }
// Look for an ID3v1 tag // Look for an ID3v1 tag
d->ID3v1Location = findID3v1(); d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) { if(d->ID3v1Location >= 0)
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for FLAC metadata, including vorbis comments // Look for FLAC metadata, including vorbis comments
@ -405,10 +430,10 @@ void FLAC::File::read(bool readProperties)
if(!isValid()) if(!isValid())
return; return;
if(d->hasXiphComment) if(!d->xiphCommentData.isEmpty())
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData)); d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData));
else else
d->tag.set(FlacXiphIndex, new Ogg::XiphComment); d->tag.set(FlacXiphIndex, new Ogg::XiphComment());
if(readProperties) { if(readProperties) {
@ -418,10 +443,10 @@ void FLAC::File::read(bool readProperties)
long streamLength; long streamLength;
if(d->hasID3v1) if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location - d->streamStart; streamLength = d->ID3v1Location - d->streamStart;
else else
streamLength = File::length() - d->streamStart; streamLength = length() - d->streamStart;
d->properties = new Properties(infoData, streamLength); d->properties = new Properties(infoData, streamLength);
} }
@ -439,7 +464,7 @@ void FLAC::File::scan()
long nextBlockOffset; long nextBlockOffset;
if(d->hasID3v2) if(d->ID3v2Location >= 0)
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize); nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
else else
nextBlockOffset = find("fLaC"); nextBlockOffset = find("fLaC");
@ -453,9 +478,10 @@ void FLAC::File::scan()
nextBlockOffset += 4; nextBlockOffset += 4;
d->flacStart = nextBlockOffset; d->flacStart = nextBlockOffset;
seek(nextBlockOffset); while(true) {
ByteVector header = readBlock(4); seek(nextBlockOffset);
const ByteVector header = readBlock(4);
// Header format (from spec): // Header format (from spec):
// <1> Last-metadata-block flag // <1> Last-metadata-block flag
@ -465,39 +491,30 @@ void FLAC::File::scan()
// .. // ..
// 4 : VORBIS_COMMENT // 4 : VORBIS_COMMENT
// .. // ..
// 6 : PICTURE
// ..
// <24> Length of metadata to follow // <24> Length of metadata to follow
char blockType = header[0] & 0x7f; const char blockType = header[0] & ~LastBlockFlag;
bool isLastBlock = (header[0] & 0x80) != 0; const bool isLastBlock = (header[0] & LastBlockFlag) != 0;
uint length = header.toUInt(1U, 3U); const unsigned int blockLength = header.toUInt(1U, 3U);
// First block should be the stream_info metadata // First block should be the stream_info metadata
if(blockType != MetadataBlock::StreamInfo) { if(d->blocks.isEmpty() && blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- invalid FLAC stream"); debug("FLAC::File::scan() -- First block should be the stream_info metadata");
setValid(false); setValid(false);
return; return;
} }
d->blocks.append(new UnknownMetadataBlock(blockType, readBlock(length))); if(blockLength == 0 && blockType != MetadataBlock::Padding) {
nextBlockOffset += length + 4;
// Search through the remaining metadata
while(!isLastBlock) {
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;
length = header.toUInt(1U, 3U);
if(length == 0 && blockType != MetadataBlock::Padding) {
debug("FLAC::File::scan() -- Zero-sized metadata block found"); debug("FLAC::File::scan() -- Zero-sized metadata block found");
setValid(false); setValid(false);
return; return;
} }
const ByteVector data = readBlock(length); const ByteVector data = readBlock(blockLength);
if(data.size() != length) { if(data.size() != blockLength) {
debug("FLAC::File::scan() -- Failed to read a metadata block"); debug("FLAC::File::scan() -- Failed to read a metadata block");
setValid(false); setValid(false);
return; return;
@ -507,12 +524,12 @@ void FLAC::File::scan()
// Found the vorbis-comment // Found the vorbis-comment
if(blockType == MetadataBlock::VorbisComment) { if(blockType == MetadataBlock::VorbisComment) {
if(!d->hasXiphComment) { if(d->xiphCommentData.isEmpty()) {
d->xiphCommentData = data; d->xiphCommentData = data;
d->hasXiphComment = true; block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, data);
} }
else { else {
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one"); debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, discarding");
} }
} }
else if(blockType == MetadataBlock::Picture) { else if(blockType == MetadataBlock::Picture) {
@ -525,25 +542,20 @@ void FLAC::File::scan()
delete picture; delete picture;
} }
} }
else if(blockType == MetadataBlock::Padding) {
if(!block) { // Skip all padding blocks.
block = new UnknownMetadataBlock(blockType, data);
}
if(block->code() != MetadataBlock::Padding) {
d->blocks.append(block);
} }
else { else {
delete block; block = new UnknownMetadataBlock(blockType, data);
} }
nextBlockOffset += length + 4; if(block)
d->blocks.append(block);
if(nextBlockOffset >= File::length()) { nextBlockOffset += blockLength + 4;
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false); if(isLastBlock)
return; break;
}
seek(nextBlockOffset);
} }
// End of metadata, now comes the datastream // End of metadata, now comes the datastream
@ -552,30 +564,3 @@ void FLAC::File::scan()
d->scanned = true; d->scanned = true;
} }
long FLAC::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
long FLAC::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -66,6 +66,23 @@ namespace TagLib {
class TAGLIB_EXPORT File : public TagLib::File class TAGLIB_EXPORT File : public TagLib::File
{ {
public: public:
/*!
* This set of flags is used for various operations and is suitable for
* being OR-ed together.
*/
enum TagTypes {
//! Empty set. Matches no tag types.
NoTags = 0x0000,
//! Matches Vorbis comments.
XiphComment = 0x0001,
//! Matches ID3v1 tags.
ID3v1 = 0x0002,
//! Matches ID3v2 tags.
ID3v2 = 0x0004,
//! Matches all tag types.
AllTags = 0xffff
};
/*! /*!
* Constructs a FLAC file from \a file. If \a readProperties is true the * Constructs a FLAC file from \a file. If \a readProperties is true the
* file's audio properties will also be read. * file's audio properties will also be read.
@ -155,9 +172,6 @@ namespace TagLib {
* has no XiphComment, one will be constructed from the ID3-tags. * has no XiphComment, one will be constructed from the ID3-tags.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
@ -268,6 +282,21 @@ namespace TagLib {
*/ */
void addPicture(Picture *picture); void addPicture(Picture *picture);
/*!
* This will remove the tags that match the OR-ed together TagTypes from
* the file. By default it removes all tags.
*
* \warning This will also invalidate pointers to the tags as their memory
* will be freed.
*
* \note In order to make the removal permanent save() still needs to be
* called.
*
* \note This won't remove the Vorbis comment block completely. The
* vendor ID will be preserved.
*/
void strip(int tags = AllTags);
/*! /*!
* Returns whether or not the file on disk actually has a XiphComment. * Returns whether or not the file on disk actually has a XiphComment.
* *
@ -295,8 +324,6 @@ namespace TagLib {
void read(bool readProperties); void read(bool readProperties);
void scan(); void scan();
long findID3v2();
long findID3v1();
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -78,10 +78,10 @@ bool FLAC::Picture::parse(const ByteVector &data)
return false; return false;
} }
uint pos = 0; unsigned int pos = 0;
d->type = FLAC::Picture::Type(data.toUInt(pos)); d->type = FLAC::Picture::Type(data.toUInt(pos));
pos += 4; pos += 4;
uint mimeTypeLength = data.toUInt(pos); unsigned int mimeTypeLength = data.toUInt(pos);
pos += 4; pos += 4;
if(pos + mimeTypeLength + 24 > data.size()) { if(pos + mimeTypeLength + 24 > data.size()) {
debug("Invalid picture block."); debug("Invalid picture block.");
@ -89,7 +89,7 @@ bool FLAC::Picture::parse(const ByteVector &data)
} }
d->mimeType = String(data.mid(pos, mimeTypeLength), String::UTF8); d->mimeType = String(data.mid(pos, mimeTypeLength), String::UTF8);
pos += mimeTypeLength; pos += mimeTypeLength;
uint descriptionLength = data.toUInt(pos); unsigned int descriptionLength = data.toUInt(pos);
pos += 4; pos += 4;
if(pos + descriptionLength + 20 > data.size()) { if(pos + descriptionLength + 20 > data.size()) {
debug("Invalid picture block."); debug("Invalid picture block.");
@ -105,7 +105,7 @@ bool FLAC::Picture::parse(const ByteVector &data)
pos += 4; pos += 4;
d->numColors = data.toUInt(pos); d->numColors = data.toUInt(pos);
pos += 4; pos += 4;
uint dataLength = data.toUInt(pos); unsigned int dataLength = data.toUInt(pos);
pos += 4; pos += 4;
if(pos + dataLength > data.size()) { if(pos + dataLength > data.size()) {
debug("Invalid picture block."); debug("Invalid picture block.");

View File

@ -135,7 +135,7 @@ void FLAC::Properties::read(const ByteVector &data, long streamLength)
return; return;
} }
uint pos = 0; unsigned int pos = 0;
// Minimum block size (in samples) // Minimum block size (in samples)
pos += 2; pos += 2;
@ -149,7 +149,7 @@ void FLAC::Properties::read(const ByteVector &data, long streamLength)
// Maximum frame size (in bytes) // Maximum frame size (in bytes)
pos += 3; pos += 3;
const uint flags = data.toUInt(pos, true); const unsigned int flags = data.toUInt(pos, true);
pos += 4; pos += 4;
d->sampleRate = flags >> 12; d->sampleRate = flags >> 12;
@ -159,8 +159,8 @@ void FLAC::Properties::read(const ByteVector &data, long streamLength)
// The last 4 bits are the most significant 4 bits for the 36 bit // The last 4 bits are the most significant 4 bits for the 36 bit
// stream length in samples. (Audio files measured in days) // stream length in samples. (Audio files measured in days)
const ulonglong hi = flags & 0xf; const unsigned long long hi = flags & 0xf;
const ulonglong lo = data.toUInt(pos, true); const unsigned long long lo = data.toUInt(pos, true);
pos += 4; pos += 4;
d->sampleFrames = (hi << 32) | lo; d->sampleFrames = (hi << 32) | lo;

View File

@ -15,10 +15,15 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include "tstringlist.h" #include "tstringlist.h"
#include "itfile.h" #include "itfile.h"
#include "tdebug.h" #include "tdebug.h"
@ -96,9 +101,9 @@ bool IT::File::save()
seek(2, Current); seek(2, Current);
ushort length = 0; unsigned short length = 0;
ushort instrumentCount = 0; unsigned short instrumentCount = 0;
ushort sampleCount = 0; unsigned short sampleCount = 0;
if(!readU16L(length) || !readU16L(instrumentCount) || !readU16L(sampleCount)) if(!readU16L(length) || !readU16L(instrumentCount) || !readU16L(sampleCount))
return false; return false;
@ -107,9 +112,9 @@ bool IT::File::save()
// write comment as instrument and sample names: // write comment as instrument and sample names:
StringList lines = d->tag.comment().split("\n"); StringList lines = d->tag.comment().split("\n");
for(ushort i = 0; i < instrumentCount; ++ i) { for(unsigned short i = 0; i < instrumentCount; ++ i) {
seek(192L + length + ((long)i << 2)); seek(192L + length + ((long)i << 2));
ulong instrumentOffset = 0; unsigned long instrumentOffset = 0;
if(!readU32L(instrumentOffset)) if(!readU32L(instrumentOffset))
return false; return false;
@ -118,28 +123,28 @@ bool IT::File::save()
if(i < lines.size()) if(i < lines.size())
writeString(lines[i], 25); writeString(lines[i], 25);
else else
writeString(String::null, 25); writeString(String(), 25);
writeByte(0); writeByte(0);
} }
for(ushort i = 0; i < sampleCount; ++ i) { for(unsigned short i = 0; i < sampleCount; ++ i) {
seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2)); seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2));
ulong sampleOffset = 0; unsigned long sampleOffset = 0;
if(!readU32L(sampleOffset)) if(!readU32L(sampleOffset))
return false; return false;
seek(sampleOffset + 20); seek(sampleOffset + 20);
if((TagLib::uint)(i + instrumentCount) < lines.size()) if((unsigned int)(i + instrumentCount) < lines.size())
writeString(lines[i + instrumentCount], 25); writeString(lines[i + instrumentCount], 25);
else else
writeString(String::null, 25); writeString(String(), 25);
writeByte(0); writeByte(0);
} }
// write rest as message: // write rest as message:
StringList messageLines; StringList messageLines;
for(uint i = instrumentCount + sampleCount; i < lines.size(); ++ i) for(unsigned int i = instrumentCount + sampleCount; i < lines.size(); ++ i)
messageLines.append(lines[i]); messageLines.append(lines[i]);
ByteVector message = messageLines.toString("\r").data(String::Latin1); ByteVector message = messageLines.toString("\r").data(String::Latin1);
@ -149,15 +154,15 @@ bool IT::File::save()
message.resize(7999); message.resize(7999);
message.append((char)0); message.append((char)0);
ushort special = 0; unsigned short special = 0;
ushort messageLength = 0; unsigned short messageLength = 0;
ulong messageOffset = 0; unsigned long messageOffset = 0;
seek(46); seek(46);
if(!readU16L(special)) if(!readU16L(special))
return false; return false;
ulong fileSize = File::length(); unsigned long fileSize = File::length();
if(special & Properties::MessageAttached) { if(special & Properties::MessageAttached) {
seek(54); seek(54);
if(!readU16L(messageLength) || !readU32L(messageOffset)) if(!readU16L(messageLength) || !readU32L(messageOffset))
@ -259,8 +264,8 @@ void IT::File::read(bool)
d->properties.setChannels(channels); d->properties.setChannels(channels);
// real length might be shorter because of skips and terminator // real length might be shorter because of skips and terminator
ushort realLength = 0; unsigned short realLength = 0;
for(ushort i = 0; i < length; ++ i) { for(unsigned short i = 0; i < length; ++ i) {
READ_BYTE_AS(order); READ_BYTE_AS(order);
if(order == 255) break; if(order == 255) break;
if(order != 254) ++ realLength; if(order != 254) ++ realLength;
@ -274,7 +279,7 @@ void IT::File::read(bool)
// Currently I just discard anything after a nil, but // Currently I just discard anything after a nil, but
// e.g. VLC seems to interprete a nil as a space. I // e.g. VLC seems to interprete a nil as a space. I
// don't know what is the proper behaviour. // don't know what is the proper behaviour.
for(ushort i = 0; i < instrumentCount; ++ i) { for(unsigned short i = 0; i < instrumentCount; ++ i) {
seek(192L + length + ((long)i << 2)); seek(192L + length + ((long)i << 2));
READ_U32L_AS(instrumentOffset); READ_U32L_AS(instrumentOffset);
seek(instrumentOffset); seek(instrumentOffset);
@ -290,7 +295,7 @@ void IT::File::read(bool)
comment.append(instrumentName); comment.append(instrumentName);
} }
for(ushort i = 0; i < sampleCount; ++ i) { for(unsigned short i = 0; i < sampleCount; ++ i) {
seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2)); seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2));
READ_U32L_AS(sampleOffset); READ_U32L_AS(sampleOffset);

View File

@ -15,10 +15,15 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include "itproperties.h" #include "itproperties.h"
using namespace TagLib; using namespace TagLib;
@ -47,20 +52,20 @@ public:
} }
int channels; int channels;
ushort lengthInPatterns; unsigned short lengthInPatterns;
ushort instrumentCount; unsigned short instrumentCount;
ushort sampleCount; unsigned short sampleCount;
ushort patternCount; unsigned short patternCount;
ushort version; unsigned short version;
ushort compatibleVersion; unsigned short compatibleVersion;
ushort flags; unsigned short flags;
ushort special; unsigned short special;
uchar globalVolume; unsigned char globalVolume;
uchar mixVolume; unsigned char mixVolume;
uchar tempo; unsigned char tempo;
uchar bpmSpeed; unsigned char bpmSpeed;
uchar panningSeparation; unsigned char panningSeparation;
uchar pitchWheelDepth; unsigned char pitchWheelDepth;
}; };
IT::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : IT::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) :
@ -104,7 +109,7 @@ int IT::Properties::channels() const
return d->channels; return d->channels;
} }
TagLib::ushort IT::Properties::lengthInPatterns() const unsigned short IT::Properties::lengthInPatterns() const
{ {
return d->lengthInPatterns; return d->lengthInPatterns;
} }
@ -114,67 +119,67 @@ bool IT::Properties::stereo() const
return d->flags & Stereo; return d->flags & Stereo;
} }
TagLib::ushort IT::Properties::instrumentCount() const unsigned short IT::Properties::instrumentCount() const
{ {
return d->instrumentCount; return d->instrumentCount;
} }
TagLib::ushort IT::Properties::sampleCount() const unsigned short IT::Properties::sampleCount() const
{ {
return d->sampleCount; return d->sampleCount;
} }
TagLib::ushort IT::Properties::patternCount() const unsigned short IT::Properties::patternCount() const
{ {
return d->patternCount; return d->patternCount;
} }
TagLib::ushort IT::Properties::version() const unsigned short IT::Properties::version() const
{ {
return d->version; return d->version;
} }
TagLib::ushort IT::Properties::compatibleVersion() const unsigned short IT::Properties::compatibleVersion() const
{ {
return d->compatibleVersion; return d->compatibleVersion;
} }
TagLib::ushort IT::Properties::flags() const unsigned short IT::Properties::flags() const
{ {
return d->flags; return d->flags;
} }
TagLib::ushort IT::Properties::special() const unsigned short IT::Properties::special() const
{ {
return d->special; return d->special;
} }
uchar IT::Properties::globalVolume() const unsigned char IT::Properties::globalVolume() const
{ {
return d->globalVolume; return d->globalVolume;
} }
uchar IT::Properties::mixVolume() const unsigned char IT::Properties::mixVolume() const
{ {
return d->mixVolume; return d->mixVolume;
} }
uchar IT::Properties::tempo() const unsigned char IT::Properties::tempo() const
{ {
return d->tempo; return d->tempo;
} }
uchar IT::Properties::bpmSpeed() const unsigned char IT::Properties::bpmSpeed() const
{ {
return d->bpmSpeed; return d->bpmSpeed;
} }
uchar IT::Properties::panningSeparation() const unsigned char IT::Properties::panningSeparation() const
{ {
return d->panningSeparation; return d->panningSeparation;
} }
uchar IT::Properties::pitchWheelDepth() const unsigned char IT::Properties::pitchWheelDepth() const
{ {
return d->pitchWheelDepth; return d->pitchWheelDepth;
} }
@ -184,72 +189,72 @@ void IT::Properties::setChannels(int channels)
d->channels = channels; d->channels = channels;
} }
void IT::Properties::setLengthInPatterns(ushort lengthInPatterns) void IT::Properties::setLengthInPatterns(unsigned short lengthInPatterns)
{ {
d->lengthInPatterns = lengthInPatterns; d->lengthInPatterns = lengthInPatterns;
} }
void IT::Properties::setInstrumentCount(ushort instrumentCount) void IT::Properties::setInstrumentCount(unsigned short instrumentCount)
{ {
d->instrumentCount = instrumentCount; d->instrumentCount = instrumentCount;
} }
void IT::Properties::setSampleCount(ushort sampleCount) void IT::Properties::setSampleCount(unsigned short sampleCount)
{ {
d->sampleCount = sampleCount; d->sampleCount = sampleCount;
} }
void IT::Properties::setPatternCount(ushort patternCount) void IT::Properties::setPatternCount(unsigned short patternCount)
{ {
d->patternCount = patternCount; d->patternCount = patternCount;
} }
void IT::Properties::setFlags(ushort flags) void IT::Properties::setFlags(unsigned short flags)
{ {
d->flags = flags; d->flags = flags;
} }
void IT::Properties::setSpecial(ushort special) void IT::Properties::setSpecial(unsigned short special)
{ {
d->special = special; d->special = special;
} }
void IT::Properties::setCompatibleVersion(ushort compatibleVersion) void IT::Properties::setCompatibleVersion(unsigned short compatibleVersion)
{ {
d->compatibleVersion = compatibleVersion; d->compatibleVersion = compatibleVersion;
} }
void IT::Properties::setVersion(ushort version) void IT::Properties::setVersion(unsigned short version)
{ {
d->version = version; d->version = version;
} }
void IT::Properties::setGlobalVolume(uchar globalVolume) void IT::Properties::setGlobalVolume(unsigned char globalVolume)
{ {
d->globalVolume = globalVolume; d->globalVolume = globalVolume;
} }
void IT::Properties::setMixVolume(uchar mixVolume) void IT::Properties::setMixVolume(unsigned char mixVolume)
{ {
d->mixVolume = mixVolume; d->mixVolume = mixVolume;
} }
void IT::Properties::setTempo(uchar tempo) void IT::Properties::setTempo(unsigned char tempo)
{ {
d->tempo = tempo; d->tempo = tempo;
} }
void IT::Properties::setBpmSpeed(uchar bpmSpeed) void IT::Properties::setBpmSpeed(unsigned char bpmSpeed)
{ {
d->bpmSpeed = bpmSpeed; d->bpmSpeed = bpmSpeed;
} }
void IT::Properties::setPanningSeparation(uchar panningSeparation) void IT::Properties::setPanningSeparation(unsigned char panningSeparation)
{ {
d->panningSeparation = panningSeparation; d->panningSeparation = panningSeparation;
} }
void IT::Properties::setPitchWheelDepth(uchar pitchWheelDepth) void IT::Properties::setPitchWheelDepth(unsigned char pitchWheelDepth)
{ {
d->pitchWheelDepth = pitchWheelDepth; d->pitchWheelDepth = pitchWheelDepth;
} }

View File

@ -15,8 +15,12 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifndef TAGLIB_ITPROPERTIES_H #ifndef TAGLIB_ITPROPERTIES_H
@ -58,37 +62,37 @@ namespace TagLib {
int sampleRate() const; int sampleRate() const;
int channels() const; int channels() const;
ushort lengthInPatterns() const; unsigned short lengthInPatterns() const;
bool stereo() const; bool stereo() const;
ushort instrumentCount() const; unsigned short instrumentCount() const;
ushort sampleCount() const; unsigned short sampleCount() const;
ushort patternCount() const; unsigned short patternCount() const;
ushort version() const; unsigned short version() const;
ushort compatibleVersion() const; unsigned short compatibleVersion() const;
ushort flags() const; unsigned short flags() const;
ushort special() const; unsigned short special() const;
uchar globalVolume() const; unsigned char globalVolume() const;
uchar mixVolume() const; unsigned char mixVolume() const;
uchar tempo() const; unsigned char tempo() const;
uchar bpmSpeed() const; unsigned char bpmSpeed() const;
uchar panningSeparation() const; unsigned char panningSeparation() const;
uchar pitchWheelDepth() const; unsigned char pitchWheelDepth() const;
void setChannels(int channels); void setChannels(int channels);
void setLengthInPatterns(ushort lengthInPatterns); void setLengthInPatterns(unsigned short lengthInPatterns);
void setInstrumentCount(ushort instrumentCount); void setInstrumentCount(unsigned short instrumentCount);
void setSampleCount (ushort sampleCount); void setSampleCount (unsigned short sampleCount);
void setPatternCount(ushort patternCount); void setPatternCount(unsigned short patternCount);
void setVersion (ushort version); void setVersion (unsigned short version);
void setCompatibleVersion(ushort compatibleVersion); void setCompatibleVersion(unsigned short compatibleVersion);
void setFlags (ushort flags); void setFlags (unsigned short flags);
void setSpecial (ushort special); void setSpecial (unsigned short special);
void setGlobalVolume(uchar globalVolume); void setGlobalVolume(unsigned char globalVolume);
void setMixVolume (uchar mixVolume); void setMixVolume (unsigned char mixVolume);
void setTempo (uchar tempo); void setTempo (unsigned char tempo);
void setBpmSpeed (uchar bpmSpeed); void setBpmSpeed (unsigned char bpmSpeed);
void setPanningSeparation(uchar panningSeparation); void setPanningSeparation(unsigned char panningSeparation);
void setPitchWheelDepth (uchar pitchWheelDepth); void setPitchWheelDepth (unsigned char pitchWheelDepth);
private: private:
Properties(const Properties&); Properties(const Properties&);

View File

@ -15,10 +15,15 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include "modfile.h" #include "modfile.h"
#include "tstringlist.h" #include "tstringlist.h"
#include "tdebug.h" #include "tdebug.h"
@ -92,14 +97,14 @@ bool Mod::File::save()
seek(0); seek(0);
writeString(d->tag.title(), 20); writeString(d->tag.title(), 20);
StringList lines = d->tag.comment().split("\n"); StringList lines = d->tag.comment().split("\n");
uint n = std::min(lines.size(), d->properties.instrumentCount()); unsigned int n = std::min(lines.size(), d->properties.instrumentCount());
for(uint i = 0; i < n; ++ i) { for(unsigned int i = 0; i < n; ++ i) {
writeString(lines[i], 22); writeString(lines[i], 22);
seek(8, Current); seek(8, Current);
} }
for(uint i = n; i < d->properties.instrumentCount(); ++ i) { for(unsigned int i = n; i < d->properties.instrumentCount(); ++ i) {
writeString(String::null, 22); writeString(String(), 22);
seek(8, Current); seek(8, Current);
} }
return true; return true;
@ -115,7 +120,7 @@ void Mod::File::read(bool)
READ_ASSERT(modId.size() == 4); READ_ASSERT(modId.size() == 4);
int channels = 4; int channels = 4;
uint instruments = 31; unsigned int instruments = 31;
if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") { if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") {
d->tag.setTrackerName("ProTracker"); d->tag.setTrackerName("ProTracker");
channels = 4; channels = 4;
@ -159,7 +164,7 @@ void Mod::File::read(bool)
READ_STRING(d->tag.setTitle, 20); READ_STRING(d->tag.setTitle, 20);
StringList comment; StringList comment;
for(uint i = 0; i < instruments; ++ i) { for(unsigned int i = 0; i < instruments; ++ i) {
READ_STRING_AS(instrumentName, 22); READ_STRING_AS(instrumentName, 22);
// value in words, * 2 (<< 1) for bytes: // value in words, * 2 (<< 1) for bytes:
READ_U16B_AS(sampleLength); READ_U16B_AS(sampleLength);

View File

@ -15,8 +15,12 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifndef TAGLIB_MODFILE_H #ifndef TAGLIB_MODFILE_H

View File

@ -15,10 +15,15 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include "tdebug.h" #include "tdebug.h"
#include "modfilebase.h" #include "modfilebase.h"
@ -33,14 +38,14 @@ Mod::FileBase::FileBase(IOStream *stream) : TagLib::File(stream)
{ {
} }
void Mod::FileBase::writeString(const String &s, ulong size, char padding) void Mod::FileBase::writeString(const String &s, unsigned long size, char padding)
{ {
ByteVector data(s.data(String::Latin1)); ByteVector data(s.data(String::Latin1));
data.resize(size, padding); data.resize(size, padding);
writeBlock(data); writeBlock(data);
} }
bool Mod::FileBase::readString(String &s, ulong size) bool Mod::FileBase::readString(String &s, unsigned long size)
{ {
ByteVector data(readBlock(size)); ByteVector data(readBlock(size));
if(data.size() < size) return false; if(data.size() < size) return false;
@ -49,39 +54,39 @@ bool Mod::FileBase::readString(String &s, ulong size)
{ {
data.resize(index); data.resize(index);
} }
data.replace((char) 0xff, ' '); data.replace('\xff', ' ');
s = data; s = data;
return true; return true;
} }
void Mod::FileBase::writeByte(uchar byte) void Mod::FileBase::writeByte(unsigned char byte)
{ {
ByteVector data(1, byte); ByteVector data(1, byte);
writeBlock(data); writeBlock(data);
} }
void Mod::FileBase::writeU16L(ushort number) void Mod::FileBase::writeU16L(unsigned short number)
{ {
writeBlock(ByteVector::fromShort(number, false)); writeBlock(ByteVector::fromShort(number, false));
} }
void Mod::FileBase::writeU32L(ulong number) void Mod::FileBase::writeU32L(unsigned long number)
{ {
writeBlock(ByteVector::fromUInt(number, false)); writeBlock(ByteVector::fromUInt(number, false));
} }
void Mod::FileBase::writeU16B(ushort number) void Mod::FileBase::writeU16B(unsigned short number)
{ {
writeBlock(ByteVector::fromShort(number, true)); writeBlock(ByteVector::fromShort(number, true));
} }
void Mod::FileBase::writeU32B(ulong number) void Mod::FileBase::writeU32B(unsigned long number)
{ {
writeBlock(ByteVector::fromUInt(number, true)); writeBlock(ByteVector::fromUInt(number, true));
} }
bool Mod::FileBase::readByte(uchar &byte) bool Mod::FileBase::readByte(unsigned char &byte)
{ {
ByteVector data(readBlock(1)); ByteVector data(readBlock(1));
if(data.size() < 1) return false; if(data.size() < 1) return false;
@ -89,7 +94,7 @@ bool Mod::FileBase::readByte(uchar &byte)
return true; return true;
} }
bool Mod::FileBase::readU16L(ushort &number) bool Mod::FileBase::readU16L(unsigned short &number)
{ {
ByteVector data(readBlock(2)); ByteVector data(readBlock(2));
if(data.size() < 2) return false; if(data.size() < 2) return false;
@ -97,14 +102,14 @@ bool Mod::FileBase::readU16L(ushort &number)
return true; return true;
} }
bool Mod::FileBase::readU32L(ulong &number) { bool Mod::FileBase::readU32L(unsigned long &number) {
ByteVector data(readBlock(4)); ByteVector data(readBlock(4));
if(data.size() < 4) return false; if(data.size() < 4) return false;
number = data.toUInt(false); number = data.toUInt(false);
return true; return true;
} }
bool Mod::FileBase::readU16B(ushort &number) bool Mod::FileBase::readU16B(unsigned short &number)
{ {
ByteVector data(readBlock(2)); ByteVector data(readBlock(2));
if(data.size() < 2) return false; if(data.size() < 2) return false;
@ -112,7 +117,7 @@ bool Mod::FileBase::readU16B(ushort &number)
return true; return true;
} }
bool Mod::FileBase::readU32B(ulong &number) { bool Mod::FileBase::readU32B(unsigned long &number) {
ByteVector data(readBlock(4)); ByteVector data(readBlock(4));
if(data.size() < 4) return false; if(data.size() < 4) return false;
number = data.toUInt(true); number = data.toUInt(true);

View File

@ -15,8 +15,12 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifndef TAGLIB_MODFILEBASE_H #ifndef TAGLIB_MODFILEBASE_H
@ -40,19 +44,19 @@ namespace TagLib {
FileBase(FileName file); FileBase(FileName file);
FileBase(IOStream *stream); FileBase(IOStream *stream);
void writeString(const String &s, ulong size, char padding = 0); void writeString(const String &s, unsigned long size, char padding = 0);
void writeByte(uchar byte); void writeByte(unsigned char byte);
void writeU16L(ushort number); void writeU16L(unsigned short number);
void writeU32L(ulong number); void writeU32L(unsigned long number);
void writeU16B(ushort number); void writeU16B(unsigned short number);
void writeU32B(ulong number); void writeU32B(unsigned long number);
bool readString(String &s, ulong size); bool readString(String &s, unsigned long size);
bool readByte(uchar &byte); bool readByte(unsigned char &byte);
bool readU16L(ushort &number); bool readU16L(unsigned short &number);
bool readU32L(ulong &number); bool readU32L(unsigned long &number);
bool readU16B(ushort &number); bool readU16B(unsigned short &number);
bool readU32B(ulong &number); bool readU32B(unsigned long &number);
}; };
} }

View File

@ -37,11 +37,11 @@
setter(number); \ setter(number); \
} }
#define READ_BYTE(setter) READ(setter,uchar,readByte) #define READ_BYTE(setter) READ(setter,unsigned char,readByte)
#define READ_U16L(setter) READ(setter,ushort,readU16L) #define READ_U16L(setter) READ(setter,unsigned short,readU16L)
#define READ_U32L(setter) READ(setter,ulong,readU32L) #define READ_U32L(setter) READ(setter,unsigned long,readU32L)
#define READ_U16B(setter) READ(setter,ushort,readU16B) #define READ_U16B(setter) READ(setter,unsigned short,readU16B)
#define READ_U32B(setter) READ(setter,ulong,readU32B) #define READ_U32B(setter) READ(setter,unsigned long,readU32B)
#define READ_STRING(setter,size) \ #define READ_STRING(setter,size) \
{ \ { \
@ -54,11 +54,11 @@
type name = 0; \ type name = 0; \
READ_ASSERT(read(name)); READ_ASSERT(read(name));
#define READ_BYTE_AS(name) READ_AS(uchar,name,readByte) #define READ_BYTE_AS(name) READ_AS(unsigned char,name,readByte)
#define READ_U16L_AS(name) READ_AS(ushort,name,readU16L) #define READ_U16L_AS(name) READ_AS(unsigned short,name,readU16L)
#define READ_U32L_AS(name) READ_AS(ulong,name,readU32L) #define READ_U32L_AS(name) READ_AS(unsigned long,name,readU32L)
#define READ_U16B_AS(name) READ_AS(ushort,name,readU16B) #define READ_U16B_AS(name) READ_AS(unsigned short,name,readU16B)
#define READ_U32B_AS(name) READ_AS(ulong,name,readU32B) #define READ_U32B_AS(name) READ_AS(unsigned long,name,readU32B)
#define READ_STRING_AS(name,size) \ #define READ_STRING_AS(name,size) \
String name; \ String name; \

View File

@ -15,10 +15,15 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include "modproperties.h" #include "modproperties.h"
using namespace TagLib; using namespace TagLib;
@ -35,8 +40,8 @@ public:
} }
int channels; int channels;
uint instrumentCount; unsigned int instrumentCount;
uchar lengthInPatterns; unsigned char lengthInPatterns;
}; };
Mod::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : Mod::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) :
@ -80,12 +85,12 @@ int Mod::Properties::channels() const
return d->channels; return d->channels;
} }
TagLib::uint Mod::Properties::instrumentCount() const unsigned int Mod::Properties::instrumentCount() const
{ {
return d->instrumentCount; return d->instrumentCount;
} }
uchar Mod::Properties::lengthInPatterns() const unsigned char Mod::Properties::lengthInPatterns() const
{ {
return d->lengthInPatterns; return d->lengthInPatterns;
} }
@ -95,12 +100,12 @@ void Mod::Properties::setChannels(int channels)
d->channels = channels; d->channels = channels;
} }
void Mod::Properties::setInstrumentCount(uint instrumentCount) void Mod::Properties::setInstrumentCount(unsigned int instrumentCount)
{ {
d->instrumentCount = instrumentCount; d->instrumentCount = instrumentCount;
} }
void Mod::Properties::setLengthInPatterns(uchar lengthInPatterns) void Mod::Properties::setLengthInPatterns(unsigned char lengthInPatterns)
{ {
d->lengthInPatterns = lengthInPatterns; d->lengthInPatterns = lengthInPatterns;
} }

View File

@ -15,8 +15,12 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifndef TAGLIB_MODPROPERTIES_H #ifndef TAGLIB_MODPROPERTIES_H
@ -42,13 +46,13 @@ namespace TagLib {
int sampleRate() const; int sampleRate() const;
int channels() const; int channels() const;
uint instrumentCount() const; unsigned int instrumentCount() const;
uchar lengthInPatterns() const; unsigned char lengthInPatterns() const;
void setChannels(int channels); void setChannels(int channels);
void setInstrumentCount(uint sampleCount); void setInstrumentCount(unsigned int sampleCount);
void setLengthInPatterns(uchar lengthInPatterns); void setLengthInPatterns(unsigned char lengthInPatterns);
private: private:
friend class File; friend class File;

View File

@ -15,10 +15,15 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include "modtag.h" #include "modtag.h"
#include "tstringlist.h" #include "tstringlist.h"
#include "tpropertymap.h" #include "tpropertymap.h"
@ -55,12 +60,12 @@ String Mod::Tag::title() const
String Mod::Tag::artist() const String Mod::Tag::artist() const
{ {
return String::null; return String();
} }
String Mod::Tag::album() const String Mod::Tag::album() const
{ {
return String::null; return String();
} }
String Mod::Tag::comment() const String Mod::Tag::comment() const
@ -70,15 +75,15 @@ String Mod::Tag::comment() const
String Mod::Tag::genre() const String Mod::Tag::genre() const
{ {
return String::null; return String();
} }
TagLib::uint Mod::Tag::year() const unsigned int Mod::Tag::year() const
{ {
return 0; return 0;
} }
TagLib::uint Mod::Tag::track() const unsigned int Mod::Tag::track() const
{ {
return 0; return 0;
} }
@ -110,11 +115,11 @@ void Mod::Tag::setGenre(const String &)
{ {
} }
void Mod::Tag::setYear(uint) void Mod::Tag::setYear(unsigned int)
{ {
} }
void Mod::Tag::setTrack(uint) void Mod::Tag::setTrack(unsigned int)
{ {
} }
@ -128,7 +133,7 @@ PropertyMap Mod::Tag::properties() const
PropertyMap properties; PropertyMap properties;
properties["TITLE"] = d->title; properties["TITLE"] = d->title;
properties["COMMENT"] = d->comment; properties["COMMENT"] = d->comment;
if(!(d->trackerName.isNull())) if(!(d->trackerName.isEmpty()))
properties["TRACKERNAME"] = d->trackerName; properties["TRACKERNAME"] = d->trackerName;
return properties; return properties;
} }
@ -142,19 +147,19 @@ PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps)
d->title = properties["TITLE"].front(); d->title = properties["TITLE"].front();
oneValueSet.append("TITLE"); oneValueSet.append("TITLE");
} else } else
d->title = String::null; d->title.clear();
if(properties.contains("COMMENT")) { if(properties.contains("COMMENT")) {
d->comment = properties["COMMENT"].front(); d->comment = properties["COMMENT"].front();
oneValueSet.append("COMMENT"); oneValueSet.append("COMMENT");
} else } else
d->comment = String::null; d->comment.clear();
if(properties.contains("TRACKERNAME")) { if(properties.contains("TRACKERNAME")) {
d->trackerName = properties["TRACKERNAME"].front(); d->trackerName = properties["TRACKERNAME"].front();
oneValueSet.append("TRACKERNAME"); oneValueSet.append("TRACKERNAME");
} else } else
d->trackerName = String::null; d->trackerName.clear();
// for each tag that has been set above, remove the first entry in the corresponding // for each tag that has been set above, remove the first entry in the corresponding
// value list. The others will be returned as unsupported by this format. // value list. The others will be returned as unsupported by this format.

View File

@ -15,8 +15,12 @@
* * * *
* You should have received a copy of the GNU Lesser General Public * * You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software * * License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* MA 02110-1301 USA * * 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifndef TAGLIB_MODTAG_H #ifndef TAGLIB_MODTAG_H
@ -50,39 +54,39 @@ namespace TagLib {
* Returns the track name; if no track name is present in the tag * Returns the track name; if no track name is present in the tag
* String::null will be returned. * String::null will be returned.
*/ */
String title() const; virtual String title() const;
/*! /*!
* Not supported by module files. Therefore always returns String::null. * Not supported by module files. Therefore always returns String::null.
*/ */
String artist() const; virtual String artist() const;
/*! /*!
* Not supported by module files. Therefore always returns String::null. * Not supported by module files. Therefore always returns String::null.
*/ */
String album() const; virtual String album() const;
/*! /*!
* Returns the track comment derived from the instrument/sample/pattern * Returns the track comment derived from the instrument/sample/pattern
* names; if no comment is present in the tag String::null will be * names; if no comment is present in the tag String::null will be
* returned. * returned.
*/ */
String comment() const; virtual String comment() const;
/*! /*!
* Not supported by module files. Therefore always returns String::null. * Not supported by module files. Therefore always returns String::null.
*/ */
String genre() const; virtual String genre() const;
/*! /*!
* Not supported by module files. Therefore always returns 0. * Not supported by module files. Therefore always returns 0.
*/ */
uint year() const; virtual unsigned int year() const;
/*! /*!
* Not supported by module files. Therefore always returns 0. * Not supported by module files. Therefore always returns 0.
*/ */
uint track() const; virtual unsigned int track() const;
/*! /*!
* Returns the name of the tracker used to create/edit the module file. * Returns the name of the tracker used to create/edit the module file.
@ -101,17 +105,17 @@ namespace TagLib {
* Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20 * Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20
* characters. * characters.
*/ */
void setTitle(const String &title); virtual void setTitle(const String &title);
/*! /*!
* Not supported by module files and therefore ignored. * Not supported by module files and therefore ignored.
*/ */
void setArtist(const String &artist); virtual void setArtist(const String &artist);
/*! /*!
* Not supported by module files and therefore ignored. * Not supported by module files and therefore ignored.
*/ */
void setAlbum(const String &album); virtual void setAlbum(const String &album);
/*! /*!
* Sets the comment to \a comment. If \a comment is String::null then * Sets the comment to \a comment. If \a comment is String::null then
@ -130,22 +134,22 @@ namespace TagLib {
* Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22 * Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22
* characters. * characters.
*/ */
void setComment(const String &comment); virtual void setComment(const String &comment);
/*! /*!
* Not supported by module files and therefore ignored. * Not supported by module files and therefore ignored.
*/ */
void setGenre(const String &genre); virtual void setGenre(const String &genre);
/*! /*!
* Not supported by module files and therefore ignored. * Not supported by module files and therefore ignored.
*/ */
void setYear(uint year); virtual void setYear(unsigned int year);
/*! /*!
* Not supported by module files and therefore ignored. * Not supported by module files and therefore ignored.
*/ */
void setTrack(uint track); virtual void setTrack(unsigned int track);
/*! /*!
* Sets the tracker name to \a trackerName. If \a trackerName is * Sets the tracker name to \a trackerName. If \a trackerName is

View File

@ -23,10 +23,6 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <climits> #include <climits>
#include <tdebug.h> #include <tdebug.h>
@ -43,9 +39,11 @@ const char *MP4::Atom::containers[11] = {
MP4::Atom::Atom(File *file) MP4::Atom::Atom(File *file)
{ {
children.setAutoDelete(true);
offset = file->tell(); offset = file->tell();
ByteVector header = file->readBlock(8); ByteVector header = file->readBlock(8);
if (header.size() != 8) { if(header.size() != 8) {
// The atom header must be 8 bytes long, otherwise there is either // The atom header must be 8 bytes long, otherwise there is either
// trailing garbage or the file is truncated // trailing garbage or the file is truncated
debug("MP4: Couldn't read 8 bytes of data for atom header"); debug("MP4: Couldn't read 8 bytes of data for atom header");
@ -94,7 +92,7 @@ MP4::Atom::Atom(File *file)
while(file->tell() < offset + length) { while(file->tell() < offset + length) {
MP4::Atom *child = new MP4::Atom(file); MP4::Atom *child = new MP4::Atom(file);
children.append(child); children.append(child);
if (child->length == 0) if(child->length == 0)
return; return;
} }
return; return;
@ -106,10 +104,6 @@ MP4::Atom::Atom(File *file)
MP4::Atom::~Atom() MP4::Atom::~Atom()
{ {
for(unsigned int i = 0; i < children.size(); i++) {
delete children[i];
}
children.clear();
} }
MP4::Atom * MP4::Atom *
@ -118,9 +112,9 @@ MP4::Atom::find(const char *name1, const char *name2, const char *name3, const c
if(name1 == 0) { if(name1 == 0) {
return this; return this;
} }
for(unsigned int i = 0; i < children.size(); i++) { for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
if(children[i]->name == name1) { if((*it)->name == name1) {
return children[i]->find(name2, name3, name4); return (*it)->find(name2, name3, name4);
} }
} }
return 0; return 0;
@ -130,12 +124,12 @@ MP4::AtomList
MP4::Atom::findall(const char *name, bool recursive) MP4::Atom::findall(const char *name, bool recursive)
{ {
MP4::AtomList result; MP4::AtomList result;
for(unsigned int i = 0; i < children.size(); i++) { for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
if(children[i]->name == name) { if((*it)->name == name) {
result.append(children[i]); result.append(*it);
} }
if(recursive) { if(recursive) {
result.append(children[i]->findall(name, recursive)); result.append((*it)->findall(name, recursive));
} }
} }
return result; return result;
@ -148,9 +142,9 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const
if(name1 == 0) { if(name1 == 0) {
return true; return true;
} }
for(unsigned int i = 0; i < children.size(); i++) { for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) {
if(children[i]->name == name1) { if((*it)->name == name1) {
return children[i]->path(path, name2, name3); return (*it)->path(path, name2, name3);
} }
} }
return false; return false;
@ -158,6 +152,8 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const
MP4::Atoms::Atoms(File *file) MP4::Atoms::Atoms(File *file)
{ {
atoms.setAutoDelete(true);
file->seek(0, File::End); file->seek(0, File::End);
long end = file->tell(); long end = file->tell();
file->seek(0); file->seek(0);
@ -171,18 +167,14 @@ MP4::Atoms::Atoms(File *file)
MP4::Atoms::~Atoms() MP4::Atoms::~Atoms()
{ {
for(unsigned int i = 0; i < atoms.size(); i++) {
delete atoms[i];
}
atoms.clear();
} }
MP4::Atom * MP4::Atom *
MP4::Atoms::find(const char *name1, const char *name2, const char *name3, const char *name4) MP4::Atoms::find(const char *name1, const char *name2, const char *name3, const char *name4)
{ {
for(unsigned int i = 0; i < atoms.size(); i++) { for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) {
if(atoms[i]->name == name1) { if((*it)->name == name1) {
return atoms[i]->find(name2, name3, name4); return (*it)->find(name2, name3, name4);
} }
} }
return 0; return 0;
@ -192,9 +184,9 @@ MP4::AtomList
MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const char *name4) MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const char *name4)
{ {
MP4::AtomList path; MP4::AtomList path;
for(unsigned int i = 0; i < atoms.size(); i++) { for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) {
if(atoms[i]->name == name1) { if((*it)->name == name1) {
if(!atoms[i]->path(path, name2, name3, name4)) { if(!(*it)->path(path, name2, name3, name4)) {
path.clear(); path.clear();
} }
return path; return path;
@ -202,4 +194,3 @@ MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const
} }
return path; return path;
} }

View File

@ -33,20 +33,27 @@ using namespace TagLib;
class MP4::CoverArt::CoverArtPrivate : public RefCounter class MP4::CoverArt::CoverArtPrivate : public RefCounter
{ {
public: public:
CoverArtPrivate() : RefCounter(), format(MP4::CoverArt::JPEG) {} CoverArtPrivate() :
RefCounter(),
format(MP4::CoverArt::JPEG) {}
Format format; Format format;
ByteVector data; ByteVector data;
}; };
MP4::CoverArt::CoverArt(Format format, const ByteVector &data) ////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
MP4::CoverArt::CoverArt(Format format, const ByteVector &data) :
d(new CoverArtPrivate())
{ {
d = new CoverArtPrivate;
d->format = format; d->format = format;
d->data = data; d->data = data;
} }
MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d) MP4::CoverArt::CoverArt(const CoverArt &item) :
d(item.d)
{ {
d->ref(); d->ref();
} }
@ -54,15 +61,18 @@ MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d)
MP4::CoverArt & MP4::CoverArt &
MP4::CoverArt::operator=(const CoverArt &item) MP4::CoverArt::operator=(const CoverArt &item)
{ {
if(&item != this) { CoverArt(item).swap(*this);
if(d->deref())
delete d;
d = item.d;
d->ref();
}
return *this; return *this;
} }
void
MP4::CoverArt::swap(CoverArt &item)
{
using std::swap;
swap(d, item.d);
}
MP4::CoverArt::~CoverArt() MP4::CoverArt::~CoverArt()
{ {
if(d->deref()) { if(d->deref()) {
@ -81,4 +91,3 @@ MP4::CoverArt::data() const
{ {
return d->data; return d->data;
} }

View File

@ -53,8 +53,17 @@ namespace TagLib {
~CoverArt(); ~CoverArt();
CoverArt(const CoverArt &item); CoverArt(const CoverArt &item);
/*!
* Copies the contents of \a item into this CoverArt.
*/
CoverArt &operator=(const CoverArt &item); CoverArt &operator=(const CoverArt &item);
/*!
* Exchanges the content of the CoverArt by the content of \a item.
*/
void swap(CoverArt &item);
//! Format of the image //! Format of the image
Format format() const; Format format() const;

View File

@ -130,8 +130,7 @@ MP4::File::read(bool readProperties)
} }
// must have a moov atom, otherwise consider it invalid // must have a moov atom, otherwise consider it invalid
MP4::Atom *moov = d->atoms->find("moov"); if(!d->atoms->find("moov")) {
if(!moov) {
setValid(false); setValid(false);
return; return;
} }
@ -158,3 +157,8 @@ MP4::File::save()
return d->tag->save(); return d->tag->save();
} }
bool
MP4::File::hasMP4Tag() const
{
return (d->atoms->find("moov", "udta", "meta", "ilst") != 0);
}

View File

@ -111,12 +111,15 @@ namespace TagLib {
* Save the file. * Save the file.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
bool save(); bool save();
/*!
* Returns whether or not the file on disk actually has an MP4 tag, or the
* file has a Metadata Item List (ilst) atom.
*/
bool hasMP4Tag() const;
private: private:
void read(bool readProperties); void read(bool readProperties);

View File

@ -33,7 +33,10 @@ using namespace TagLib;
class MP4::Item::ItemPrivate : public RefCounter class MP4::Item::ItemPrivate : public RefCounter
{ {
public: public:
ItemPrivate() : RefCounter(), valid(true), atomDataType(TypeUndefined) {} ItemPrivate() :
RefCounter(),
valid(true),
atomDataType(TypeUndefined) {}
bool valid; bool valid;
AtomDataType atomDataType; AtomDataType atomDataType;
@ -41,8 +44,8 @@ public:
bool m_bool; bool m_bool;
int m_int; int m_int;
IntPair m_intPair; IntPair m_intPair;
uchar m_byte; unsigned char m_byte;
uint m_uint; unsigned int m_uint;
long long m_longlong; long long m_longlong;
}; };
StringList m_stringList; StringList m_stringList;
@ -50,13 +53,14 @@ public:
MP4::CoverArtList m_coverArtList; MP4::CoverArtList m_coverArtList;
}; };
MP4::Item::Item() MP4::Item::Item() :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->valid = false; d->valid = false;
} }
MP4::Item::Item(const Item &item) : d(item.d) MP4::Item::Item(const Item &item) :
d(item.d)
{ {
d->ref(); d->ref();
} }
@ -64,75 +68,76 @@ MP4::Item::Item(const Item &item) : d(item.d)
MP4::Item & MP4::Item &
MP4::Item::operator=(const Item &item) MP4::Item::operator=(const Item &item)
{ {
if(&item != this) { Item(item).swap(*this);
if(d->deref()) {
delete d;
}
d = item.d;
d->ref();
}
return *this; return *this;
} }
void
MP4::Item::swap(Item &item)
{
using std::swap;
swap(d, item.d);
}
MP4::Item::~Item() MP4::Item::~Item()
{ {
if(d->deref()) { if(d->deref())
delete d; delete d;
}
} }
MP4::Item::Item(bool value) MP4::Item::Item(bool value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_bool = value; d->m_bool = value;
} }
MP4::Item::Item(int value) MP4::Item::Item(int value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_int = value; d->m_int = value;
} }
MP4::Item::Item(uchar value) MP4::Item::Item(unsigned char value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_byte = value; d->m_byte = value;
} }
MP4::Item::Item(uint value) MP4::Item::Item(unsigned int value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_uint = value; d->m_uint = value;
} }
MP4::Item::Item(long long value) MP4::Item::Item(long long value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_longlong = value; d->m_longlong = value;
} }
MP4::Item::Item(int value1, int value2) MP4::Item::Item(int value1, int value2) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_intPair.first = value1; d->m_intPair.first = value1;
d->m_intPair.second = value2; d->m_intPair.second = value2;
} }
MP4::Item::Item(const ByteVectorList &value) MP4::Item::Item(const ByteVectorList &value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_byteVectorList = value; d->m_byteVectorList = value;
} }
MP4::Item::Item(const StringList &value) MP4::Item::Item(const StringList &value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_stringList = value; d->m_stringList = value;
} }
MP4::Item::Item(const MP4::CoverArtList &value) MP4::Item::Item(const MP4::CoverArtList &value) :
d(new ItemPrivate())
{ {
d = new ItemPrivate;
d->m_coverArtList = value; d->m_coverArtList = value;
} }
@ -158,13 +163,13 @@ MP4::Item::toInt() const
return d->m_int; return d->m_int;
} }
uchar unsigned char
MP4::Item::toByte() const MP4::Item::toByte() const
{ {
return d->m_byte; return d->m_byte;
} }
TagLib::uint unsigned int
MP4::Item::toUInt() const MP4::Item::toUInt() const
{ {
return d->m_uint; return d->m_uint;
@ -205,4 +210,3 @@ MP4::Item::isValid() const
{ {
return d->valid; return d->valid;
} }

View File

@ -43,12 +43,22 @@ namespace TagLib {
Item(); Item();
Item(const Item &item); Item(const Item &item);
/*!
* Copies the contents of \a item into this Item.
*/
Item &operator=(const Item &item); Item &operator=(const Item &item);
/*!
* Exchanges the content of the Item by the content of \a item.
*/
void swap(Item &item);
~Item(); ~Item();
Item(int value); Item(int value);
Item(uchar value); Item(unsigned char value);
Item(uint value); Item(unsigned int value);
Item(long long value); Item(long long value);
Item(bool value); Item(bool value);
Item(int first, int second); Item(int first, int second);
@ -60,8 +70,8 @@ namespace TagLib {
AtomDataType atomDataType() const; AtomDataType atomDataType() const;
int toInt() const; int toInt() const;
uchar toByte() const; unsigned char toByte() const;
uint toUInt() const; unsigned int toUInt() const;
long long toLongLong() const; long long toLongLong() const;
bool toBool() const; bool toBool() const;
IntPair toIntPair() const; IntPair toIntPair() const;

View File

@ -167,7 +167,7 @@ MP4::Properties::read(File *file, Atoms *atoms)
file->seek(mdhd->offset); file->seek(mdhd->offset);
data = file->readBlock(mdhd->length); data = file->readBlock(mdhd->length);
const uint version = data[8]; const unsigned int version = data[8];
long long unit; long long unit;
long long length; long long length;
if(version == 1) { if(version == 1) {
@ -187,7 +187,7 @@ MP4::Properties::read(File *file, Atoms *atoms)
length = data.toUInt(24U); length = data.toUInt(24U);
} }
if(unit > 0 && length > 0) if(unit > 0 && length > 0)
d->length = static_cast<int>(length * 1000.0 / unit); d->length = static_cast<int>(length * 1000.0 / unit + 0.5);
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
if(!atom) { if(!atom) {
@ -202,7 +202,7 @@ MP4::Properties::read(File *file, Atoms *atoms)
d->bitsPerSample = data.toShort(42U); d->bitsPerSample = data.toShort(42U);
d->sampleRate = data.toUInt(46U); d->sampleRate = data.toUInt(46U);
if(data.containsAt("esds", 56) && data[64] == 0x03) { if(data.containsAt("esds", 56) && data[64] == 0x03) {
uint pos = 65; unsigned int pos = 65;
if(data.containsAt("\x80\x80\x80", pos)) { if(data.containsAt("\x80\x80\x80", pos)) {
pos += 3; pos += 3;
} }

View File

@ -35,21 +35,23 @@ using namespace TagLib;
class MP4::Tag::TagPrivate class MP4::Tag::TagPrivate
{ {
public: public:
TagPrivate() : file(0), atoms(0) {} TagPrivate() :
~TagPrivate() {} file(0),
atoms(0) {}
TagLib::File *file; TagLib::File *file;
Atoms *atoms; Atoms *atoms;
ItemMap items; ItemMap items;
}; };
MP4::Tag::Tag() MP4::Tag::Tag() :
d(new TagPrivate())
{ {
d = new TagPrivate;
} }
MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) :
d(new TagPrivate())
{ {
d = new TagPrivate;
d->file = file; d->file = file;
d->atoms = atoms; d->atoms = atoms;
@ -59,8 +61,8 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
return; return;
} }
for(unsigned int i = 0; i < ilst->children.size(); i++) { for(AtomList::ConstIterator it = ilst->children.begin(); it != ilst->children.end(); ++it) {
MP4::Atom *atom = ilst->children[i]; MP4::Atom *atom = *it;
file->seek(atom->offset + 8); file->seek(atom->offset + 8);
if(atom->name == "----") { if(atom->name == "----") {
parseFreeForm(atom); parseFreeForm(atom);
@ -149,8 +151,8 @@ MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm)
{ {
AtomDataList data = parseData2(atom, expectedFlags, freeForm); AtomDataList data = parseData2(atom, expectedFlags, freeForm);
ByteVectorList result; ByteVectorList result;
for(uint i = 0; i < data.size(); i++) { for(AtomDataList::ConstIterator it = data.begin(); it != data.end(); ++it) {
result.append(data[i].data); result.append(it->data);
} }
return result; return result;
} }
@ -159,7 +161,7 @@ void
MP4::Tag::parseInt(const MP4::Atom *atom) MP4::Tag::parseInt(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
addItem(atom->name, (int)data[0].toShort()); addItem(atom->name, (int)data[0].toShort());
} }
} }
@ -168,7 +170,7 @@ void
MP4::Tag::parseUInt(const MP4::Atom *atom) MP4::Tag::parseUInt(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
addItem(atom->name, data[0].toUInt()); addItem(atom->name, data[0].toUInt());
} }
} }
@ -177,7 +179,7 @@ void
MP4::Tag::parseLongLong(const MP4::Atom *atom) MP4::Tag::parseLongLong(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
addItem(atom->name, data[0].toLongLong()); addItem(atom->name, data[0].toLongLong());
} }
} }
@ -186,8 +188,8 @@ void
MP4::Tag::parseByte(const MP4::Atom *atom) MP4::Tag::parseByte(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
addItem(atom->name, (uchar)data[0].at(0)); addItem(atom->name, static_cast<unsigned char>(data[0].at(0)));
} }
} }
@ -195,7 +197,7 @@ void
MP4::Tag::parseGnre(const MP4::Atom *atom) MP4::Tag::parseGnre(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
int idx = (int)data[0].toShort(); int idx = (int)data[0].toShort();
if(idx > 0) { if(idx > 0) {
addItem("\251gen", StringList(ID3v1::genre(idx - 1))); addItem("\251gen", StringList(ID3v1::genre(idx - 1)));
@ -207,7 +209,7 @@ void
MP4::Tag::parseIntPair(const MP4::Atom *atom) MP4::Tag::parseIntPair(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
const int a = data[0].toShort(2U); const int a = data[0].toShort(2U);
const int b = data[0].toShort(4U); const int b = data[0].toShort(4U);
addItem(atom->name, MP4::Item(a, b)); addItem(atom->name, MP4::Item(a, b));
@ -218,7 +220,7 @@ void
MP4::Tag::parseBool(const MP4::Atom *atom) MP4::Tag::parseBool(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom); ByteVectorList data = parseData(atom);
if(data.size()) { if(!data.isEmpty()) {
bool value = data[0].size() ? data[0][0] != '\0' : false; bool value = data[0].size() ? data[0][0] != '\0' : false;
addItem(atom->name, value); addItem(atom->name, value);
} }
@ -228,10 +230,10 @@ void
MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags) MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags)
{ {
ByteVectorList data = parseData(atom, expectedFlags); ByteVectorList data = parseData(atom, expectedFlags);
if(data.size()) { if(!data.isEmpty()) {
StringList value; StringList value;
for(unsigned int i = 0; i < data.size(); i++) { for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
value.append(String(data[i], String::UTF8)); value.append(String(*it, String::UTF8));
} }
addItem(atom->name, value); addItem(atom->name, value);
} }
@ -242,18 +244,25 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom)
{ {
AtomDataList data = parseData2(atom, -1, true); AtomDataList data = parseData2(atom, -1, true);
if(data.size() > 2) { if(data.size() > 2) {
String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8); AtomDataList::ConstIterator itBegin = data.begin();
AtomDataType type = data[2].type;
for(uint i = 2; i < data.size(); i++) { String name = "----:";
if(data[i].type != type) { name += String((itBegin++)->data, String::UTF8); // data[0].data
name += ':';
name += String((itBegin++)->data, String::UTF8); // data[1].data
AtomDataType type = itBegin->type; // data[2].type
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
if(it->type != type) {
debug("MP4: We currently don't support values with multiple types"); debug("MP4: We currently don't support values with multiple types");
break; break;
} }
} }
if(type == TypeUTF8) { if(type == TypeUTF8) {
StringList value; StringList value;
for(uint i = 2; i < data.size(); i++) { for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
value.append(String(data[i].data, String::UTF8)); value.append(String(it->data, String::UTF8));
} }
Item item(value); Item item(value);
item.setAtomDataType(type); item.setAtomDataType(type);
@ -261,8 +270,8 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom)
} }
else { else {
ByteVectorList value; ByteVectorList value;
for(uint i = 2; i < data.size(); i++) { for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
value.append(data[i].data); value.append(it->data);
} }
Item item(value); Item item(value);
item.setAtomDataType(type); item.setAtomDataType(type);
@ -300,7 +309,7 @@ MP4::Tag::parseCovr(const MP4::Atom *atom)
} }
pos += length; pos += length;
} }
if(value.size() > 0) if(!value.isEmpty())
addItem(atom->name, value); addItem(atom->name, value);
} }
@ -323,8 +332,8 @@ ByteVector
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const
{ {
ByteVector result; ByteVector result;
for(unsigned int i = 0; i < data.size(); i++) { for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + data[i])); result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + *it));
} }
return renderAtom(name, result); return renderAtom(name, result);
} }
@ -395,8 +404,8 @@ MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) c
{ {
ByteVectorList data; ByteVectorList data;
StringList value = item.toStringList(); StringList value = item.toStringList();
for(unsigned int i = 0; i < value.size(); i++) { for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(value[i].data(String::UTF8)); data.append(it->data(String::UTF8));
} }
return renderData(name, flags, data); return renderData(name, flags, data);
} }
@ -406,9 +415,9 @@ MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVector data; ByteVector data;
MP4::CoverArtList value = item.toCoverArtList(); MP4::CoverArtList value = item.toCoverArtList();
for(unsigned int i = 0; i < value.size(); i++) { for(MP4::CoverArtList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(renderAtom("data", ByteVector::fromUInt(value[i].format()) + data.append(renderAtom("data", ByteVector::fromUInt(it->format()) +
ByteVector(4, '\0') + value[i].data())); ByteVector(4, '\0') + it->data()));
} }
return renderAtom(name, data); return renderAtom(name, data);
} }
@ -417,9 +426,9 @@ ByteVector
MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
{ {
StringList header = StringList::split(name, ":"); StringList header = StringList::split(name, ":");
if (header.size() != 3) { if(header.size() != 3) {
debug("MP4: Invalid free-form item name \"" + name + "\""); debug("MP4: Invalid free-form item name \"" + name + "\"");
return ByteVector::null; return ByteVector();
} }
ByteVector data; ByteVector data;
data.append(renderAtom("mean", ByteVector::fromUInt(0) + header[1].data(String::UTF8))); data.append(renderAtom("mean", ByteVector::fromUInt(0) + header[1].data(String::UTF8)));
@ -435,14 +444,14 @@ MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
} }
if(type == TypeUTF8) { if(type == TypeUTF8) {
StringList value = item.toStringList(); StringList value = item.toStringList();
for(unsigned int i = 0; i < value.size(); i++) { for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i].data(String::UTF8))); data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + it->data(String::UTF8)));
} }
} }
else { else {
ByteVectorList value = item.toByteVectorList(); ByteVectorList value = item.toByteVectorList();
for(unsigned int i = 0; i < value.size(); i++) { for(ByteVectorList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i])); data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + *it));
} }
} }
return renderAtom("----", data); return renderAtom("----", data);
@ -505,20 +514,26 @@ MP4::Tag::save()
void void
MP4::Tag::updateParents(const AtomList &path, long delta, int ignore) MP4::Tag::updateParents(const AtomList &path, long delta, int ignore)
{ {
for(unsigned int i = 0; i < path.size() - ignore; i++) { if(static_cast<int>(path.size()) <= ignore)
d->file->seek(path[i]->offset); return;
AtomList::ConstIterator itEnd = path.end();
std::advance(itEnd, 0 - ignore);
for(AtomList::ConstIterator it = path.begin(); it != itEnd; ++it) {
d->file->seek((*it)->offset);
long size = d->file->readBlock(4).toUInt(); long size = d->file->readBlock(4).toUInt();
// 64-bit // 64-bit
if (size == 1) { if (size == 1) {
d->file->seek(4, File::Current); // Skip name d->file->seek(4, File::Current); // Skip name
long long longSize = d->file->readBlock(8).toLongLong(); long long longSize = d->file->readBlock(8).toLongLong();
// Seek the offset of the 64-bit size // Seek the offset of the 64-bit size
d->file->seek(path[i]->offset + 8); d->file->seek((*it)->offset + 8);
d->file->writeBlock(ByteVector::fromLongLong(longSize + delta)); d->file->writeBlock(ByteVector::fromLongLong(longSize + delta));
} }
// 32-bit // 32-bit
else { else {
d->file->seek(path[i]->offset); d->file->seek((*it)->offset);
d->file->writeBlock(ByteVector::fromUInt(size + delta)); d->file->writeBlock(ByteVector::fromUInt(size + delta));
} }
} }
@ -530,8 +545,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
MP4::Atom *moov = d->atoms->find("moov"); MP4::Atom *moov = d->atoms->find("moov");
if(moov) { if(moov) {
MP4::AtomList stco = moov->findall("stco", true); MP4::AtomList stco = moov->findall("stco", true);
for(unsigned int i = 0; i < stco.size(); i++) { for(MP4::AtomList::ConstIterator it = stco.begin(); it != stco.end(); ++it) {
MP4::Atom *atom = stco[i]; MP4::Atom *atom = *it;
if(atom->offset > offset) { if(atom->offset > offset) {
atom->offset += delta; atom->offset += delta;
} }
@ -539,7 +554,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
ByteVector data = d->file->readBlock(atom->length - 12); ByteVector data = d->file->readBlock(atom->length - 12);
unsigned int count = data.toUInt(); unsigned int count = data.toUInt();
d->file->seek(atom->offset + 16); d->file->seek(atom->offset + 16);
uint pos = 4; unsigned int pos = 4;
while(count--) { while(count--) {
long o = static_cast<long>(data.toUInt(pos)); long o = static_cast<long>(data.toUInt(pos));
if(o > offset) { if(o > offset) {
@ -551,8 +566,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
} }
MP4::AtomList co64 = moov->findall("co64", true); MP4::AtomList co64 = moov->findall("co64", true);
for(unsigned int i = 0; i < co64.size(); i++) { for(MP4::AtomList::ConstIterator it = co64.begin(); it != co64.end(); ++it) {
MP4::Atom *atom = co64[i]; MP4::Atom *atom = *it;
if(atom->offset > offset) { if(atom->offset > offset) {
atom->offset += delta; atom->offset += delta;
} }
@ -560,7 +575,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
ByteVector data = d->file->readBlock(atom->length - 12); ByteVector data = d->file->readBlock(atom->length - 12);
unsigned int count = data.toUInt(); unsigned int count = data.toUInt();
d->file->seek(atom->offset + 16); d->file->seek(atom->offset + 16);
uint pos = 4; unsigned int pos = 4;
while(count--) { while(count--) {
long long o = data.toLongLong(pos); long long o = data.toLongLong(pos);
if(o > offset) { if(o > offset) {
@ -575,8 +590,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
MP4::Atom *moof = d->atoms->find("moof"); MP4::Atom *moof = d->atoms->find("moof");
if(moof) { if(moof) {
MP4::AtomList tfhd = moof->findall("tfhd", true); MP4::AtomList tfhd = moof->findall("tfhd", true);
for(unsigned int i = 0; i < tfhd.size(); i++) { for(MP4::AtomList::ConstIterator it = tfhd.begin(); it != tfhd.end(); ++it) {
MP4::Atom *atom = tfhd[i]; MP4::Atom *atom = *it;
if(atom->offset > offset) { if(atom->offset > offset) {
atom->offset += delta; atom->offset += delta;
} }
@ -609,21 +624,28 @@ MP4::Tag::saveNew(ByteVector data)
data = renderAtom("udta", data); data = renderAtom("udta", data);
} }
long offset = path[path.size() - 1]->offset + 8; long offset = path.back()->offset + 8;
d->file->insert(data, offset, 0); d->file->insert(data, offset, 0);
updateParents(path, data.size()); updateParents(path, data.size());
updateOffsets(data.size(), offset); updateOffsets(data.size(), offset);
// Insert the newly created atoms into the tree to keep it up-to-date.
d->file->seek(offset);
path.back()->children.prepend(new Atom(d->file));
} }
void void
MP4::Tag::saveExisting(ByteVector data, const AtomList &path) MP4::Tag::saveExisting(ByteVector data, const AtomList &path)
{ {
MP4::Atom *ilst = path[path.size() - 1]; AtomList::ConstIterator it = path.end();
MP4::Atom *ilst = *(--it);
long offset = ilst->offset; long offset = ilst->offset;
long length = ilst->length; long length = ilst->length;
MP4::Atom *meta = path[path.size() - 2]; MP4::Atom *meta = *(--it);
AtomList::ConstIterator index = meta->children.find(ilst); AtomList::ConstIterator index = meta->children.find(ilst);
// check if there is an atom before 'ilst', and possibly use it as padding // check if there is an atom before 'ilst', and possibly use it as padding
@ -669,7 +691,7 @@ MP4::Tag::title() const
{ {
if(d->items.contains("\251nam")) if(d->items.contains("\251nam"))
return d->items["\251nam"].toStringList().toString(", "); return d->items["\251nam"].toStringList().toString(", ");
return String::null; return String();
} }
String String
@ -677,7 +699,7 @@ MP4::Tag::artist() const
{ {
if(d->items.contains("\251ART")) if(d->items.contains("\251ART"))
return d->items["\251ART"].toStringList().toString(", "); return d->items["\251ART"].toStringList().toString(", ");
return String::null; return String();
} }
String String
@ -685,7 +707,7 @@ MP4::Tag::album() const
{ {
if(d->items.contains("\251alb")) if(d->items.contains("\251alb"))
return d->items["\251alb"].toStringList().toString(", "); return d->items["\251alb"].toStringList().toString(", ");
return String::null; return String();
} }
String String
@ -693,7 +715,7 @@ MP4::Tag::comment() const
{ {
if(d->items.contains("\251cmt")) if(d->items.contains("\251cmt"))
return d->items["\251cmt"].toStringList().toString(", "); return d->items["\251cmt"].toStringList().toString(", ");
return String::null; return String();
} }
String String
@ -701,7 +723,7 @@ MP4::Tag::genre() const
{ {
if(d->items.contains("\251gen")) if(d->items.contains("\251gen"))
return d->items["\251gen"].toStringList().toString(", "); return d->items["\251gen"].toStringList().toString(", ");
return String::null; return String();
} }
unsigned int unsigned int
@ -751,13 +773,13 @@ MP4::Tag::setGenre(const String &value)
} }
void void
MP4::Tag::setYear(uint value) MP4::Tag::setYear(unsigned int value)
{ {
d->items["\251day"] = StringList(String::number(value)); d->items["\251day"] = StringList(String::number(value));
} }
void void
MP4::Tag::setTrack(uint value) MP4::Tag::setTrack(unsigned int value)
{ {
d->items["trkn"] = MP4::Item(value, 0); d->items["trkn"] = MP4::Item(value, 0);
} }
@ -797,7 +819,9 @@ bool MP4::Tag::contains(const String &key) const
return d->items.contains(key); return d->items.contains(key);
} }
static const char *keyTranslation[][2] = { namespace
{
const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" }, { "\251nam", "TITLE" },
{ "\251ART", "ARTIST" }, { "\251ART", "ARTIST" },
{ "\251alb", "ALBUM" }, { "\251alb", "ALBUM" },
@ -845,23 +869,26 @@ static const char *keyTranslation[][2] = {
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
{ "----:com.apple.iTunes:LICENSE", "LICENSE" }, { "----:com.apple.iTunes:LICENSE", "LICENSE" },
{ "----:com.apple.iTunes:MEDIA", "MEDIA" }, { "----:com.apple.iTunes:MEDIA", "MEDIA" },
}; };
const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
String translateKey(const String &key)
{
for(size_t i = 0; i < keyTranslationSize; ++i) {
if(key == keyTranslation[i][0])
return keyTranslation[i][1];
}
return String();
}
}
PropertyMap MP4::Tag::properties() const PropertyMap MP4::Tag::properties() const
{ {
static Map<String, String> keyMap;
if(keyMap.isEmpty()) {
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
for(int i = 0; i < numKeys; i++) {
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
}
}
PropertyMap props; PropertyMap props;
MP4::ItemMap::ConstIterator it = d->items.begin(); for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
for(; it != d->items.end(); ++it) { const String key = translateKey(it->first);
if(keyMap.contains(it->first)) { if(!key.isEmpty()) {
String key = keyMap[it->first];
if(key == "TRACKNUMBER" || key == "DISCNUMBER") { if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
MP4::Item::IntPair ip = it->second.toIntPair(); MP4::Item::IntPair ip = it->second.toIntPair();
String value = String::number(ip.first); String value = String::number(ip.first);
@ -889,8 +916,7 @@ PropertyMap MP4::Tag::properties() const
void MP4::Tag::removeUnsupportedProperties(const StringList &props) void MP4::Tag::removeUnsupportedProperties(const StringList &props)
{ {
StringList::ConstIterator it = props.begin(); for(StringList::ConstIterator it = props.begin(); it != props.end(); ++it)
for(; it != props.end(); ++it)
d->items.erase(*it); d->items.erase(*it);
} }
@ -905,22 +931,20 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
} }
PropertyMap origProps = properties(); PropertyMap origProps = properties();
PropertyMap::ConstIterator it = origProps.begin(); for(PropertyMap::ConstIterator it = origProps.begin(); it != origProps.end(); ++it) {
for(; it != origProps.end(); ++it) {
if(!props.contains(it->first) || props[it->first].isEmpty()) { if(!props.contains(it->first) || props[it->first].isEmpty()) {
d->items.erase(reverseKeyMap[it->first]); d->items.erase(reverseKeyMap[it->first]);
} }
} }
PropertyMap ignoredProps; PropertyMap ignoredProps;
it = props.begin(); for(PropertyMap::ConstIterator it = props.begin(); it != props.end(); ++it) {
for(; it != props.end(); ++it) {
if(reverseKeyMap.contains(it->first)) { if(reverseKeyMap.contains(it->first)) {
String name = reverseKeyMap[it->first]; String name = reverseKeyMap[it->first];
if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) { if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) {
int first = 0, second = 0; int first = 0, second = 0;
StringList parts = StringList::split(it->second.front(), "/"); StringList parts = StringList::split(it->second.front(), "/");
if(parts.size() > 0) { if(!parts.isEmpty()) {
first = parts[0].toInt(); first = parts[0].toInt();
if(parts.size() > 1) { if(parts.size() > 1) {
second = parts[1].toInt(); second = parts[1].toInt();

View File

@ -50,24 +50,24 @@ namespace TagLib {
public: public:
Tag(); Tag();
Tag(TagLib::File *file, Atoms *atoms); Tag(TagLib::File *file, Atoms *atoms);
~Tag(); virtual ~Tag();
bool save(); bool save();
String title() const; virtual String title() const;
String artist() const; virtual String artist() const;
String album() const; virtual String album() const;
String comment() const; virtual String comment() const;
String genre() const; virtual String genre() const;
uint year() const; virtual unsigned int year() const;
uint track() const; virtual unsigned int track() const;
void setTitle(const String &value); virtual void setTitle(const String &value);
void setArtist(const String &value); virtual void setArtist(const String &value);
void setAlbum(const String &value); virtual void setAlbum(const String &value);
void setComment(const String &value); virtual void setComment(const String &value);
void setGenre(const String &value); virtual void setGenre(const String &value);
void setYear(uint value); virtual void setYear(unsigned int value);
void setTrack(uint value); virtual void setTrack(unsigned int value);
virtual bool isEmpty() const; virtual bool isEmpty() const;

View File

@ -28,6 +28,7 @@
#include <tagunion.h> #include <tagunion.h>
#include <tdebug.h> #include <tdebug.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tagutils.h>
#include "mpcfile.h" #include "mpcfile.h"
#include "id3v1tag.h" #include "id3v1tag.h"
@ -52,10 +53,7 @@ public:
ID3v2Header(0), ID3v2Header(0),
ID3v2Location(-1), ID3v2Location(-1),
ID3v2Size(0), ID3v2Size(0),
properties(0), properties(0) {}
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
~FilePrivate() ~FilePrivate()
{ {
@ -64,24 +62,17 @@ public:
} }
long APELocation; long APELocation;
uint APESize; long APESize;
long ID3v1Location; long ID3v1Location;
ID3v2::Header *ID3v2Header; ID3v2::Header *ID3v2Header;
long ID3v2Location; long ID3v2Location;
uint ID3v2Size; long ID3v2Size;
TagUnion tag; TagUnion tag;
Properties *properties; Properties *properties;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasAPE;
bool hasID3v1;
bool hasID3v2;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -116,26 +107,20 @@ TagLib::Tag *MPC::File::tag() const
PropertyMap MPC::File::properties() const PropertyMap MPC::File::properties() const
{ {
if(d->hasAPE) return d->tag.properties();
return d->tag.access<APE::Tag>(MPCAPEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(MPCID3v1Index, false)->properties();
return PropertyMap();
} }
void MPC::File::removeUnsupportedProperties(const StringList &properties) void MPC::File::removeUnsupportedProperties(const StringList &properties)
{ {
if(d->hasAPE) d->tag.removeUnsupportedProperties(properties);
d->tag.access<APE::Tag>(MPCAPEIndex, false)->removeUnsupportedProperties(properties);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(MPCID3v1Index, false)->removeUnsupportedProperties(properties);
} }
PropertyMap MPC::File::setProperties(const PropertyMap &properties) PropertyMap MPC::File::setProperties(const PropertyMap &properties)
{ {
if(d->hasID3v1) if(ID3v1Tag())
d->tag.access<APE::Tag>(MPCID3v1Index, false)->setProperties(properties); ID3v1Tag()->setProperties(properties);
return d->tag.access<APE::Tag>(MPCAPEIndex, true)->setProperties(properties);
return APETag(true)->setProperties(properties);
} }
MPC::Properties *MPC::File::audioProperties() const MPC::Properties *MPC::File::audioProperties() const
@ -152,67 +137,78 @@ bool MPC::File::save()
// Possibly strip ID3v2 tag // Possibly strip ID3v2 tag
if(d->hasID3v2 && !d->ID3v2Header) { if(!d->ID3v2Header && d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, d->ID3v2Size); removeBlock(d->ID3v2Location, d->ID3v2Size);
d->hasID3v2 = false;
if(d->hasID3v1) if(d->APELocation >= 0)
d->ID3v1Location -= d->ID3v2Size;
if(d->hasAPE)
d->APELocation -= d->ID3v2Size; d->APELocation -= d->ID3v2Size;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2Size;
d->ID3v2Location = -1;
d->ID3v2Size = 0;
} }
// Update ID3v1 tag // Update ID3v1 tag
if(ID3v1Tag()) { if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
if(d->hasID3v1) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location); seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
} }
else { else {
seek(0, End); seek(0, End);
d->ID3v1Location = tell(); d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
} }
} else
if(d->hasID3v1) { writeBlock(ID3v1Tag()->render());
removeBlock(d->ID3v1Location, 128); }
d->hasID3v1 = false; else {
if(d->hasAPE) {
if(d->APELocation > d->ID3v1Location) // ID3v1 tag is empty. Remove the old one.
d->APELocation -= 128;
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
} }
} }
// Update APE tag // Update APE tag
if(APETag()) { if(APETag() && !APETag()->isEmpty()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize); // APE tag is not empty. Update the old one or create a new one.
else {
if(d->hasID3v1) { if(d->APELocation < 0) {
insert(APETag()->render(), d->ID3v1Location, 0); if(d->ID3v1Location >= 0)
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->APELocation = d->ID3v1Location; d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APESize; else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, d->APESize);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize);
d->APESize = data.size();
} }
else { else {
seek(0, End);
d->APELocation = tell(); // APE tag is empty. Remove the old one.
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize(); if(d->APELocation >= 0) {
d->hasAPE = true;
}
}
}
else
if(d->hasAPE) {
removeBlock(d->APELocation, d->APESize); removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) { if(d->ID3v1Location >= 0)
if(d->ID3v1Location > d->APELocation)
d->ID3v1Location -= d->APESize; d->ID3v1Location -= d->APESize;
d->APELocation = -1;
d->APESize = 0;
} }
} }
@ -231,21 +227,18 @@ APE::Tag *MPC::File::APETag(bool create)
void MPC::File::strip(int tags) void MPC::File::strip(int tags)
{ {
if(tags & ID3v1) { if(tags & ID3v1)
d->tag.set(MPCID3v1Index, 0); d->tag.set(MPCID3v1Index, 0);
APETag(true);
}
if(tags & ID3v2) { if(tags & APE)
delete d->ID3v2Header;
d->ID3v2Header = 0;
}
if(tags & APE) {
d->tag.set(MPCAPEIndex, 0); d->tag.set(MPCAPEIndex, 0);
if(!ID3v1Tag()) if(!ID3v1Tag())
APETag(true); APETag(true);
if(tags & ID3v2) {
delete d->ID3v2Header;
d->ID3v2Header = 0;
} }
} }
@ -256,12 +249,12 @@ void MPC::File::remove(int tags)
bool MPC::File::hasID3v1Tag() const bool MPC::File::hasID3v1Tag() const
{ {
return d->hasID3v1; return (d->ID3v1Location >= 0);
} }
bool MPC::File::hasAPETag() const bool MPC::File::hasAPETag() const
{ {
return d->hasAPE; return (d->APELocation >= 0);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -270,55 +263,50 @@ bool MPC::File::hasAPETag() const
void MPC::File::read(bool readProperties) void MPC::File::read(bool readProperties)
{ {
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
if(d->ID3v1Location >= 0) {
d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag
d->APELocation = findAPE();
if(d->APELocation >= 0) {
d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize;
d->hasAPE = true;
}
if(!d->hasID3v1)
APETag(true);
// Look for an ID3v2 tag // Look for an ID3v2 tag
d->ID3v2Location = findID3v2(); d->ID3v2Location = Utils::findID3v2(this);
if(d->ID3v2Location >= 0) { if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location); seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size())); d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize(); d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
} }
// Look for an ID3v1 tag
d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0)
d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
// Look for an APE tag
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) {
d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
}
if(d->ID3v1Location < 0)
APETag(true);
// Look for MPC metadata // Look for MPC metadata
if(readProperties) { if(readProperties) {
long streamLength; long streamLength;
if(d->hasAPE) if(d->APELocation >= 0)
streamLength = d->APELocation; streamLength = d->APELocation;
else if(d->hasID3v1) else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location; streamLength = d->ID3v1Location;
else else
streamLength = length(); streamLength = length();
if(d->hasID3v2) { if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location + d->ID3v2Size); seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size); streamLength -= (d->ID3v2Location + d->ID3v2Size);
} }
@ -329,48 +317,3 @@ void MPC::File::read(bool readProperties)
d->properties = new Properties(this, streamLength); d->properties = new Properties(this, streamLength);
} }
} }
long MPC::File::findAPE()
{
if(!isValid())
return -1;
if(d->hasID3v1)
seek(-160, End);
else
seek(-32, End);
long p = tell();
if(readBlock(8) == APE::Tag::fileIdentifier())
return p;
return -1;
}
long MPC::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
long MPC::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -141,9 +141,6 @@ namespace TagLib {
* Saves the file. * Saves the file.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
@ -222,9 +219,6 @@ namespace TagLib {
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties); void read(bool readProperties);
long findAPE();
long findID3v1();
long findID3v2();
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -54,12 +54,12 @@ public:
int bitrate; int bitrate;
int sampleRate; int sampleRate;
int channels; int channels;
uint totalFrames; unsigned int totalFrames;
uint sampleFrames; unsigned int sampleFrames;
uint trackGain; unsigned int trackGain;
uint trackPeak; unsigned int trackPeak;
uint albumGain; unsigned int albumGain;
uint albumPeak; unsigned int albumPeak;
String flags; String flags;
}; };
@ -129,12 +129,12 @@ int MPC::Properties::mpcVersion() const
return d->version; return d->version;
} }
TagLib::uint MPC::Properties::totalFrames() const unsigned int MPC::Properties::totalFrames() const
{ {
return d->totalFrames; return d->totalFrames;
} }
TagLib::uint MPC::Properties::sampleFrames() const unsigned int MPC::Properties::sampleFrames() const
{ {
return d->sampleFrames; return d->sampleFrames;
} }
@ -163,8 +163,10 @@ int MPC::Properties::albumPeak() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
unsigned long readSize(File *file, TagLib::uint &sizeLength, bool &eof) namespace
{ {
unsigned long readSize(File *file, unsigned int &sizeLength, bool &eof)
{
sizeLength = 0; sizeLength = 0;
eof = false; eof = false;
@ -183,10 +185,10 @@ unsigned long readSize(File *file, TagLib::uint &sizeLength, bool &eof)
sizeLength++; sizeLength++;
} while((tmp & 0x80)); } while((tmp & 0x80));
return size; return size;
} }
unsigned long readSize(const ByteVector &data, TagLib::uint &pos) unsigned long readSize(const ByteVector &data, unsigned int &pos)
{ {
unsigned char tmp; unsigned char tmp;
unsigned long size = 0; unsigned long size = 0;
@ -195,10 +197,8 @@ unsigned long readSize(const ByteVector &data, TagLib::uint &pos)
size = (size << 7) | (tmp & 0x7F); size = (size << 7) | (tmp & 0x7F);
} while((tmp & 0x80) && (pos < data.size())); } while((tmp & 0x80) && (pos < data.size()));
return size; return size;
} }
namespace
{
// This array looks weird, but the same as original MusePack code found at: // This array looks weird, but the same as original MusePack code found at:
// https://www.musepack.net/index.php?pg=src // https://www.musepack.net/index.php?pg=src
const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 };
@ -211,7 +211,7 @@ void MPC::Properties::readSV8(File *file, long streamLength)
while(!readSH && !readRG) { while(!readSH && !readRG) {
const ByteVector packetType = file->readBlock(2); const ByteVector packetType = file->readBlock(2);
uint packetSizeLength; unsigned int packetSizeLength;
bool eof; bool eof;
const unsigned long packetSize = readSize(file, packetSizeLength, eof); const unsigned long packetSize = readSize(file, packetSizeLength, eof);
if(eof) { if(eof) {
@ -238,7 +238,7 @@ void MPC::Properties::readSV8(File *file, long streamLength)
readSH = true; readSH = true;
TagLib::uint pos = 4; unsigned int pos = 4;
d->version = data[pos]; d->version = data[pos];
pos += 1; pos += 1;
d->sampleFrames = readSize(data, pos); d->sampleFrames = readSize(data, pos);
@ -247,19 +247,19 @@ void MPC::Properties::readSV8(File *file, long streamLength)
break; break;
} }
const ulong begSilence = readSize(data, pos); const unsigned long begSilence = readSize(data, pos);
if(pos > dataSize - 2) { if(pos > dataSize - 2) {
debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt.");
break; break;
} }
const ushort flags = data.toUShort(pos, true); const unsigned short flags = data.toUShort(pos, true);
pos += 2; pos += 2;
d->sampleRate = sftable[(flags >> 13) & 0x07]; d->sampleRate = sftable[(flags >> 13) & 0x07];
d->channels = ((flags >> 4) & 0x0F) + 1; d->channels = ((flags >> 4) & 0x0F) + 1;
const uint frameCount = d->sampleFrames - begSilence; const unsigned int frameCount = d->sampleFrames - begSilence;
if(frameCount > 0 && d->sampleRate > 0) { if(frameCount > 0 && d->sampleRate > 0) {
const double length = frameCount * 1000.0 / d->sampleRate; const double length = frameCount * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5); d->length = static_cast<int>(length + 0.5);
@ -305,11 +305,11 @@ void MPC::Properties::readSV7(const ByteVector &data, long streamLength)
d->totalFrames = data.toUInt(4, false); d->totalFrames = data.toUInt(4, false);
const uint flags = data.toUInt(8, false); const unsigned int flags = data.toUInt(8, false);
d->sampleRate = sftable[(flags >> 16) & 0x03]; d->sampleRate = sftable[(flags >> 16) & 0x03];
d->channels = 2; d->channels = 2;
const uint gapless = data.toUInt(5, false); const unsigned int gapless = data.toUInt(5, false);
d->trackGain = data.toShort(14, false); d->trackGain = data.toShort(14, false);
d->trackPeak = data.toShort(12, false); d->trackPeak = data.toShort(12, false);
@ -337,14 +337,14 @@ void MPC::Properties::readSV7(const ByteVector &data, long streamLength)
bool trueGapless = (gapless >> 31) & 0x0001; bool trueGapless = (gapless >> 31) & 0x0001;
if(trueGapless) { if(trueGapless) {
uint lastFrameSamples = (gapless >> 20) & 0x07FF; unsigned int lastFrameSamples = (gapless >> 20) & 0x07FF;
d->sampleFrames = d->totalFrames * 1152 - lastFrameSamples; d->sampleFrames = d->totalFrames * 1152 - lastFrameSamples;
} }
else else
d->sampleFrames = d->totalFrames * 1152 - 576; d->sampleFrames = d->totalFrames * 1152 - 576;
} }
else { else {
const uint headerData = data.toUInt(0, false); const unsigned int headerData = data.toUInt(0, false);
d->bitrate = (headerData >> 23) & 0x01ff; d->bitrate = (headerData >> 23) & 0x01ff;
d->version = (headerData >> 11) & 0x03ff; d->version = (headerData >> 11) & 0x03ff;

View File

@ -35,7 +35,7 @@ namespace TagLib {
class File; class File;
static const uint HeaderSize = 8*7; static const unsigned int HeaderSize = 8 * 7;
//! An implementation of audio property reading for MPC //! An implementation of audio property reading for MPC
@ -113,8 +113,8 @@ namespace TagLib {
*/ */
int mpcVersion() const; int mpcVersion() const;
uint totalFrames() const; unsigned int totalFrames() const;
uint sampleFrames() const; unsigned int sampleFrames() const;
/*! /*!
* Returns the track gain as an integer value, * Returns the track gain as an integer value,

View File

@ -27,237 +27,239 @@
using namespace TagLib; using namespace TagLib;
namespace TagLib { namespace
namespace ID3v1 { {
const wchar_t *genres[] = {
static const int genresSize = 192; L"Blues",
static const String genres[] = { L"Classic Rock",
"Blues", L"Country",
"Classic Rock", L"Dance",
"Country", L"Disco",
"Dance", L"Funk",
"Disco", L"Grunge",
"Funk", L"Hip-Hop",
"Grunge", L"Jazz",
"Hip-Hop", L"Metal",
"Jazz", L"New Age",
"Metal", L"Oldies",
"New Age", L"Other",
"Oldies", L"Pop",
"Other", L"R&B",
"Pop", L"Rap",
"R&B", L"Reggae",
"Rap", L"Rock",
"Reggae", L"Techno",
"Rock", L"Industrial",
"Techno", L"Alternative",
"Industrial", L"Ska",
"Alternative", L"Death Metal",
"Ska", L"Pranks",
"Death Metal", L"Soundtrack",
"Pranks", L"Euro-Techno",
"Soundtrack", L"Ambient",
"Euro-Techno", L"Trip-Hop",
"Ambient", L"Vocal",
"Trip-Hop", L"Jazz+Funk",
"Vocal", L"Fusion",
"Jazz+Funk", L"Trance",
"Fusion", L"Classical",
"Trance", L"Instrumental",
"Classical", L"Acid",
"Instrumental", L"House",
"Acid", L"Game",
"House", L"Sound Clip",
"Game", L"Gospel",
"Sound Clip", L"Noise",
"Gospel", L"Alternative Rock",
"Noise", L"Bass",
"Alternative Rock", L"Soul",
"Bass", L"Punk",
"Soul", L"Space",
"Punk", L"Meditative",
"Space", L"Instrumental Pop",
"Meditative", L"Instrumental Rock",
"Instrumental Pop", L"Ethnic",
"Instrumental Rock", L"Gothic",
"Ethnic", L"Darkwave",
"Gothic", L"Techno-Industrial",
"Darkwave", L"Electronic",
"Techno-Industrial", L"Pop-Folk",
"Electronic", L"Eurodance",
"Pop-Folk", L"Dream",
"Eurodance", L"Southern Rock",
"Dream", L"Comedy",
"Southern Rock", L"Cult",
"Comedy", L"Gangsta",
"Cult", L"Top 40",
"Gangsta", L"Christian Rap",
"Top 40", L"Pop/Funk",
"Christian Rap", L"Jungle",
"Pop/Funk", L"Native American",
"Jungle", L"Cabaret",
"Native American", L"New Wave",
"Cabaret", L"Psychedelic",
"New Wave", L"Rave",
"Psychedelic", L"Showtunes",
"Rave", L"Trailer",
"Showtunes", L"Lo-Fi",
"Trailer", L"Tribal",
"Lo-Fi", L"Acid Punk",
"Tribal", L"Acid Jazz",
"Acid Punk", L"Polka",
"Acid Jazz", L"Retro",
"Polka", L"Musical",
"Retro", L"Rock & Roll",
"Musical", L"Hard Rock",
"Rock & Roll", L"Folk",
"Hard Rock", L"Folk/Rock",
"Folk", L"National Folk",
"Folk/Rock", L"Swing",
"National Folk", L"Fusion",
"Swing", L"Bebob",
"Fusion", L"Latin",
"Bebob", L"Revival",
"Latin", L"Celtic",
"Revival", L"Bluegrass",
"Celtic", L"Avantgarde",
"Bluegrass", L"Gothic Rock",
"Avantgarde", L"Progressive Rock",
"Gothic Rock", L"Psychedelic Rock",
"Progressive Rock", L"Symphonic Rock",
"Psychedelic Rock", L"Slow Rock",
"Symphonic Rock", L"Big Band",
"Slow Rock", L"Chorus",
"Big Band", L"Easy Listening",
"Chorus", L"Acoustic",
"Easy Listening", L"Humour",
"Acoustic", L"Speech",
"Humour", L"Chanson",
"Speech", L"Opera",
"Chanson", L"Chamber Music",
"Opera", L"Sonata",
"Chamber Music", L"Symphony",
"Sonata", L"Booty Bass",
"Symphony", L"Primus",
"Booty Bass", L"Porn Groove",
"Primus", L"Satire",
"Porn Groove", L"Slow Jam",
"Satire", L"Club",
"Slow Jam", L"Tango",
"Club", L"Samba",
"Tango", L"Folklore",
"Samba", L"Ballad",
"Folklore", L"Power Ballad",
"Ballad", L"Rhythmic Soul",
"Power Ballad", L"Freestyle",
"Rhythmic Soul", L"Duet",
"Freestyle", L"Punk Rock",
"Duet", L"Drum Solo",
"Punk Rock", L"A Cappella",
"Drum Solo", L"Euro-House",
"A Cappella", L"Dance Hall",
"Euro-House", L"Goa",
"Dance Hall", L"Drum & Bass",
"Goa", L"Club-House",
"Drum & Bass", L"Hardcore",
"Club-House", L"Terror",
"Hardcore", L"Indie",
"Terror", L"BritPop",
"Indie", L"Negerpunk",
"BritPop", L"Polsk Punk",
"Negerpunk", L"Beat",
"Polsk Punk", L"Christian Gangsta Rap",
"Beat", L"Heavy Metal",
"Christian Gangsta Rap", L"Black Metal",
"Heavy Metal", L"Crossover",
"Black Metal", L"Contemporary Christian",
"Crossover", L"Christian Rock",
"Contemporary Christian", L"Merengue",
"Christian Rock", L"Salsa",
"Merengue", L"Thrash Metal",
"Salsa", L"Anime",
"Thrash Metal", L"Jpop",
"Anime", L"Synthpop",
"Jpop", L"Abstract",
"Synthpop", L"Art Rock",
"Abstract", L"Baroque",
"Art Rock", L"Bhangra",
"Baroque", L"Big Beat",
"Bhangra", L"Breakbeat",
"Big Beat", L"Chillout",
"Breakbeat", L"Downtempo",
"Chillout", L"Dub",
"Downtempo", L"EBM",
"Dub", L"Eclectic",
"EBM", L"Electro",
"Eclectic", L"Electroclash",
"Electro", L"Emo",
"Electroclash", L"Experimental",
"Emo", L"Garage",
"Experimental", L"Global",
"Garage", L"IDM",
"Global", L"Illbient",
"IDM", L"Industro-Goth",
"Illbient", L"Jam Band",
"Industro-Goth", L"Krautrock",
"Jam Band", L"Leftfield",
"Krautrock", L"Lounge",
"Leftfield", L"Math Rock",
"Lounge", L"New Romantic",
"Math Rock", L"Nu-Breakz",
"New Romantic", L"Post-Punk",
"Nu-Breakz", L"Post-Rock",
"Post-Punk", L"Psytrance",
"Post-Rock", L"Shoegaze",
"Psytrance", L"Space Rock",
"Shoegaze", L"Trop Rock",
"Space Rock", L"World Music",
"Trop Rock", L"Neoclassical",
"World Music", L"Audiobook",
"Neoclassical", L"Audio Theatre",
"Audiobook", L"Neue Deutsche Welle",
"Audio Theatre", L"Podcast",
"Neue Deutsche Welle", L"Indie Rock",
"Podcast", L"G-Funk",
"Indie Rock", L"Dubstep",
"G-Funk", L"Garage Rock",
"Dubstep", L"Psybient"
"Garage Rock",
"Psybient"
}; };
} const int genresSize = sizeof(genres) / sizeof(genres[0]);
} }
StringList ID3v1::genreList() StringList ID3v1::genreList()
{ {
static StringList l; StringList l;
if(l.isEmpty()) { for(int i = 0; i < genresSize; i++) {
for(int i = 0; i < genresSize; i++)
l.append(genres[i]); l.append(genres[i]);
} }
return l; return l;
} }
ID3v1::GenreMap ID3v1::genreMap() ID3v1::GenreMap ID3v1::genreMap()
{ {
static GenreMap m; GenreMap m;
if(m.isEmpty()) { for(int i = 0; i < genresSize; i++) {
for(int i = 0; i < genresSize; i++)
m.insert(genres[i], i); m.insert(genres[i], i);
} }
return m; return m;
} }
String ID3v1::genre(int i) String ID3v1::genre(int i)
{ {
if(i >= 0 && i < genresSize) if(i >= 0 && i < genresSize)
return genres[i] + String::null; // always make a copy return String(genres[i]); // always make a copy
return String::null; else
return String();
} }
int ID3v1::genreIndex(const String &name) int ID3v1::genreIndex(const String &name)
{ {
if(genreMap().contains(name)) for(int i = 0; i < genresSize; ++i) {
return genreMap()[name]; if(name == genres[i])
return i;
}
return 255; return 255;
} }

View File

@ -32,10 +32,20 @@
using namespace TagLib; using namespace TagLib;
using namespace ID3v1; using namespace ID3v1;
namespace
{
const ID3v1::StringHandler defaultStringHandler;
const ID3v1::StringHandler *stringHandler = &defaultStringHandler;
}
class ID3v1::Tag::TagPrivate class ID3v1::Tag::TagPrivate
{ {
public: public:
TagPrivate() : file(0), tagOffset(-1), track(0), genre(255) {} TagPrivate() :
file(0),
tagOffset(0),
track(0),
genre(255) {}
File *file; File *file;
long tagOffset; long tagOffset;
@ -45,15 +55,10 @@ public:
String album; String album;
String year; String year;
String comment; String comment;
uchar track; unsigned char track;
uchar genre; unsigned char genre;
static const StringHandler *stringHandler;
}; };
static const StringHandler defaultStringHandler;
const ID3v1::StringHandler *ID3v1::Tag::TagPrivate::stringHandler = &defaultStringHandler;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// StringHandler implementation // StringHandler implementation
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -69,26 +74,26 @@ String ID3v1::StringHandler::parse(const ByteVector &data) const
ByteVector ID3v1::StringHandler::render(const String &s) const ByteVector ID3v1::StringHandler::render(const String &s) const
{ {
if(!s.isLatin1()) if(s.isLatin1())
{
return ByteVector();
}
return s.data(String::Latin1); return s.data(String::Latin1);
else
return ByteVector();
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public methods // public methods
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ID3v1::Tag::Tag() : TagLib::Tag() ID3v1::Tag::Tag() :
TagLib::Tag(),
d(new TagPrivate())
{ {
d = new TagPrivate;
} }
ID3v1::Tag::Tag(File *file, long tagOffset) : TagLib::Tag() ID3v1::Tag::Tag(File *file, long tagOffset) :
TagLib::Tag(),
d(new TagPrivate())
{ {
d = new TagPrivate;
d->file = file; d->file = file;
d->tagOffset = tagOffset; d->tagOffset = tagOffset;
@ -105,11 +110,11 @@ ByteVector ID3v1::Tag::render() const
ByteVector data; ByteVector data;
data.append(fileIdentifier()); data.append(fileIdentifier());
data.append(TagPrivate::stringHandler->render(d->title).resize(30)); data.append(stringHandler->render(d->title).resize(30));
data.append(TagPrivate::stringHandler->render(d->artist).resize(30)); data.append(stringHandler->render(d->artist).resize(30));
data.append(TagPrivate::stringHandler->render(d->album).resize(30)); data.append(stringHandler->render(d->album).resize(30));
data.append(TagPrivate::stringHandler->render(d->year).resize(4)); data.append(stringHandler->render(d->year).resize(4));
data.append(TagPrivate::stringHandler->render(d->comment).resize(28)); data.append(stringHandler->render(d->comment).resize(28));
data.append(char(0)); data.append(char(0));
data.append(char(d->track)); data.append(char(d->track));
data.append(char(d->genre)); data.append(char(d->genre));
@ -147,12 +152,12 @@ String ID3v1::Tag::genre() const
return ID3v1::genre(d->genre); return ID3v1::genre(d->genre);
} }
TagLib::uint ID3v1::Tag::year() const unsigned int ID3v1::Tag::year() const
{ {
return d->year.toInt(); return d->year.toInt();
} }
TagLib::uint ID3v1::Tag::track() const unsigned int ID3v1::Tag::track() const
{ {
return d->track; return d->track;
} }
@ -182,32 +187,32 @@ void ID3v1::Tag::setGenre(const String &s)
d->genre = ID3v1::genreIndex(s); d->genre = ID3v1::genreIndex(s);
} }
void ID3v1::Tag::setYear(TagLib::uint i) void ID3v1::Tag::setYear(unsigned int i)
{ {
d->year = i > 0 ? String::number(i) : String::null; d->year = i > 0 ? String::number(i) : String();
} }
void ID3v1::Tag::setTrack(TagLib::uint i) void ID3v1::Tag::setTrack(unsigned int i)
{ {
d->track = i < 256 ? i : 0; d->track = i < 256 ? i : 0;
} }
TagLib::uint ID3v1::Tag::genreNumber() const unsigned int ID3v1::Tag::genreNumber() const
{ {
return d->genre; return d->genre;
} }
void ID3v1::Tag::setGenreNumber(TagLib::uint i) void ID3v1::Tag::setGenreNumber(unsigned int i)
{ {
d->genre = i < 256 ? i : 255; d->genre = i < 256 ? i : 255;
} }
void ID3v1::Tag::setStringHandler(const StringHandler *handler) void ID3v1::Tag::setStringHandler(const StringHandler *handler)
{ {
if (handler) if(handler)
TagPrivate::stringHandler = handler; stringHandler = handler;
else else
TagPrivate::stringHandler = &defaultStringHandler; stringHandler = &defaultStringHandler;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -219,7 +224,7 @@ void ID3v1::Tag::read()
if(d->file && d->file->isValid()) { if(d->file && d->file->isValid()) {
d->file->seek(d->tagOffset); d->file->seek(d->tagOffset);
// read the tag -- always 128 bytes // read the tag -- always 128 bytes
ByteVector data = d->file->readBlock(128); const ByteVector data = d->file->readBlock(128);
// some initial sanity checking // some initial sanity checking
if(data.size() == 128 && data.startsWith("TAG")) if(data.size() == 128 && data.startsWith("TAG"))
@ -233,16 +238,16 @@ void ID3v1::Tag::parse(const ByteVector &data)
{ {
int offset = 3; int offset = 3;
d->title = TagPrivate::stringHandler->parse(data.mid(offset, 30)); d->title = stringHandler->parse(data.mid(offset, 30));
offset += 30; offset += 30;
d->artist = TagPrivate::stringHandler->parse(data.mid(offset, 30)); d->artist = stringHandler->parse(data.mid(offset, 30));
offset += 30; offset += 30;
d->album = TagPrivate::stringHandler->parse(data.mid(offset, 30)); d->album = stringHandler->parse(data.mid(offset, 30));
offset += 30; offset += 30;
d->year = TagPrivate::stringHandler->parse(data.mid(offset, 4)); d->year = stringHandler->parse(data.mid(offset, 4));
offset += 4; offset += 4;
// Check for ID3v1.1 -- Note that ID3v1 *does not* support "track zero" -- this // Check for ID3v1.1 -- Note that ID3v1 *does not* support "track zero" -- this
@ -253,13 +258,13 @@ void ID3v1::Tag::parse(const ByteVector &data)
if(data[offset + 28] == 0 && data[offset + 29] != 0) { if(data[offset + 28] == 0 && data[offset + 29] != 0) {
// ID3v1.1 detected // ID3v1.1 detected
d->comment = TagPrivate::stringHandler->parse(data.mid(offset, 28)); d->comment = stringHandler->parse(data.mid(offset, 28));
d->track = uchar(data[offset + 29]); d->track = static_cast<unsigned char>(data[offset + 29]);
} }
else else
d->comment = data.mid(offset, 30); d->comment = data.mid(offset, 30);
offset += 30; offset += 30;
d->genre = uchar(data[offset]); d->genre = static_cast<unsigned char>(data[offset]);
} }

View File

@ -140,23 +140,23 @@ namespace TagLib {
virtual String album() const; virtual String album() const;
virtual String comment() const; virtual String comment() const;
virtual String genre() const; virtual String genre() const;
virtual TagLib::uint year() const; virtual unsigned int year() const;
virtual TagLib::uint track() const; virtual unsigned int track() const;
virtual void setTitle(const String &s); virtual void setTitle(const String &s);
virtual void setArtist(const String &s); virtual void setArtist(const String &s);
virtual void setAlbum(const String &s); virtual void setAlbum(const String &s);
virtual void setComment(const String &s); virtual void setComment(const String &s);
virtual void setGenre(const String &s); virtual void setGenre(const String &s);
virtual void setYear(TagLib::uint i); virtual void setYear(unsigned int i);
virtual void setTrack(TagLib::uint i); virtual void setTrack(unsigned int i);
/*! /*!
* Returns the genre in number. * Returns the genre in number.
* *
* \note Normally 255 indicates that this tag contains no genre. * \note Normally 255 indicates that this tag contains no genre.
*/ */
TagLib::uint genreNumber() const; unsigned int genreNumber() const;
/*! /*!
* Sets the genre in number to \a i. * Sets the genre in number to \a i.
@ -164,7 +164,7 @@ namespace TagLib {
* \note Valid value is from 0 up to 255. Normally 255 indicates that * \note Valid value is from 0 up to 255. Normally 255 indicates that
* this tag contains no genre. * this tag contains no genre.
*/ */
void setGenreNumber(TagLib::uint i); void setGenreNumber(unsigned int i);
/*! /*!
* Sets the string handler that decides how the ID3v1 data will be * Sets the string handler that decides how the ID3v1 data will be

View File

@ -137,7 +137,7 @@ void AttachedPictureFrame::parseFields(const ByteVector &data)
d->mimeType = readStringField(data, String::Latin1, &pos); d->mimeType = readStringField(data, String::Latin1, &pos);
/* Now we need at least two more bytes available */ /* Now we need at least two more bytes available */
if (uint(pos) + 1 >= data.size()) { if(static_cast<unsigned int>(pos) + 1 >= data.size()) {
debug("Truncated picture frame."); debug("Truncated picture frame.");
return; return;
} }

View File

@ -44,10 +44,10 @@ public:
const ID3v2::Header *tagHeader; const ID3v2::Header *tagHeader;
ByteVector elementID; ByteVector elementID;
TagLib::uint startTime; unsigned int startTime;
TagLib::uint endTime; unsigned int endTime;
TagLib::uint startOffset; unsigned int startOffset;
TagLib::uint endOffset; unsigned int endOffset;
FrameListMap embeddedFrameListMap; FrameListMap embeddedFrameListMap;
FrameList embeddedFrameList; FrameList embeddedFrameList;
}; };
@ -65,8 +65,8 @@ ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &dat
} }
ChapterFrame::ChapterFrame(const ByteVector &elementID, ChapterFrame::ChapterFrame(const ByteVector &elementID,
TagLib::uint startTime, TagLib::uint endTime, unsigned int startTime, unsigned int endTime,
TagLib::uint startOffset, TagLib::uint endOffset, unsigned int startOffset, unsigned int endOffset,
const FrameList &embeddedFrames) : const FrameList &embeddedFrames) :
ID3v2::Frame("CHAP") ID3v2::Frame("CHAP")
{ {
@ -97,22 +97,22 @@ ByteVector ChapterFrame::elementID() const
return d->elementID; return d->elementID;
} }
TagLib::uint ChapterFrame::startTime() const unsigned int ChapterFrame::startTime() const
{ {
return d->startTime; return d->startTime;
} }
TagLib::uint ChapterFrame::endTime() const unsigned int ChapterFrame::endTime() const
{ {
return d->endTime; return d->endTime;
} }
TagLib::uint ChapterFrame::startOffset() const unsigned int ChapterFrame::startOffset() const
{ {
return d->startOffset; return d->startOffset;
} }
TagLib::uint ChapterFrame::endOffset() const unsigned int ChapterFrame::endOffset() const
{ {
return d->endOffset; return d->endOffset;
} }
@ -125,22 +125,22 @@ void ChapterFrame::setElementID(const ByteVector &eID)
d->elementID = d->elementID.mid(0, d->elementID.size() - 1); d->elementID = d->elementID.mid(0, d->elementID.size() - 1);
} }
void ChapterFrame::setStartTime(const TagLib::uint &sT) void ChapterFrame::setStartTime(const unsigned int &sT)
{ {
d->startTime = sT; d->startTime = sT;
} }
void ChapterFrame::setEndTime(const TagLib::uint &eT) void ChapterFrame::setEndTime(const unsigned int &eT)
{ {
d->endTime = eT; d->endTime = eT;
} }
void ChapterFrame::setStartOffset(const TagLib::uint &sO) void ChapterFrame::setStartOffset(const unsigned int &sO)
{ {
d->startOffset = sO; d->startOffset = sO;
} }
void ChapterFrame::setEndOffset(const TagLib::uint &eO) void ChapterFrame::setEndOffset(const unsigned int &eO)
{ {
d->endOffset = eO; d->endOffset = eO;
} }
@ -238,7 +238,7 @@ ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVec
void ChapterFrame::parseFields(const ByteVector &data) void ChapterFrame::parseFields(const ByteVector &data)
{ {
TagLib::uint size = data.size(); unsigned int size = data.size();
if(size < 18) { if(size < 18) {
debug("A CHAP frame must contain at least 18 bytes (1 byte element ID " debug("A CHAP frame must contain at least 18 bytes (1 byte element ID "
"terminated by null and 4x4 bytes for start and end time and offset)."); "terminated by null and 4x4 bytes for start and end time and offset).");
@ -246,7 +246,7 @@ void ChapterFrame::parseFields(const ByteVector &data)
} }
int pos = 0; int pos = 0;
TagLib::uint embPos = 0; unsigned int embPos = 0;
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->startTime = data.toUInt(pos, true); d->startTime = data.toUInt(pos, true);
pos += 4; pos += 4;

View File

@ -62,8 +62,8 @@ namespace TagLib {
* All times are in milliseconds. * All times are in milliseconds.
*/ */
ChapterFrame(const ByteVector &elementID, ChapterFrame(const ByteVector &elementID,
uint startTime, uint endTime, unsigned int startTime, unsigned int endTime,
uint startOffset, uint endOffset, unsigned int startOffset, unsigned int endOffset,
const FrameList &embeddedFrames = FrameList()); const FrameList &embeddedFrames = FrameList());
/*! /*!
@ -84,14 +84,14 @@ namespace TagLib {
* *
* \see setStartTime() * \see setStartTime()
*/ */
uint startTime() const; unsigned int startTime() const;
/*! /*!
* Returns time of chapter's end (in milliseconds). * Returns time of chapter's end (in milliseconds).
* *
* \see setEndTime() * \see setEndTime()
*/ */
uint endTime() const; unsigned int endTime() const;
/*! /*!
* Returns zero based byte offset (count of bytes from the beginning * Returns zero based byte offset (count of bytes from the beginning
@ -100,7 +100,7 @@ namespace TagLib {
* \note If returned value is 0xFFFFFFFF, start time should be used instead. * \note If returned value is 0xFFFFFFFF, start time should be used instead.
* \see setStartOffset() * \see setStartOffset()
*/ */
uint startOffset() const; unsigned int startOffset() const;
/*! /*!
* Returns zero based byte offset (count of bytes from the beginning * Returns zero based byte offset (count of bytes from the beginning
@ -109,7 +109,7 @@ namespace TagLib {
* \note If returned value is 0xFFFFFFFF, end time should be used instead. * \note If returned value is 0xFFFFFFFF, end time should be used instead.
* \see setEndOffset() * \see setEndOffset()
*/ */
uint endOffset() const; unsigned int endOffset() const;
/*! /*!
* Sets the element ID of the frame to \a eID. If \a eID isn't * Sets the element ID of the frame to \a eID. If \a eID isn't
@ -124,14 +124,14 @@ namespace TagLib {
* *
* \see startTime() * \see startTime()
*/ */
void setStartTime(const uint &sT); void setStartTime(const unsigned int &sT);
/*! /*!
* Sets time of chapter's end (in milliseconds) to \a eT. * Sets time of chapter's end (in milliseconds) to \a eT.
* *
* \see endTime() * \see endTime()
*/ */
void setEndTime(const uint &eT); void setEndTime(const unsigned int &eT);
/*! /*!
* Sets zero based byte offset (count of bytes from the beginning * Sets zero based byte offset (count of bytes from the beginning
@ -139,7 +139,7 @@ namespace TagLib {
* *
* \see startOffset() * \see startOffset()
*/ */
void setStartOffset(const uint &sO); void setStartOffset(const unsigned int &sO);
/*! /*!
* Sets zero based byte offset (count of bytes from the beginning * Sets zero based byte offset (count of bytes from the beginning
@ -147,7 +147,7 @@ namespace TagLib {
* *
* \see endOffset() * \see endOffset()
*/ */
void setEndOffset(const uint &eO); void setEndOffset(const unsigned int &eO);
/*! /*!
* Returns a reference to the frame list map. This is an FrameListMap of * Returns a reference to the frame list map. This is an FrameListMap of

View File

@ -116,8 +116,6 @@ PropertyMap CommentsFrame::asProperties() const
PropertyMap map; PropertyMap map;
if(key.isEmpty() || key == "COMMENT") if(key.isEmpty() || key == "COMMENT")
map.insert("COMMENT", text()); map.insert("COMMENT", text());
else if(key.isNull())
map.unsupportedData().append(L"COMM/" + description());
else else
map.insert("COMMENT:" + key, text()); map.insert("COMMENT:" + key, text());
return map; return map;

View File

@ -109,8 +109,8 @@ void EventTimingCodesFrame::parseFields(const ByteVector &data)
int pos = 1; int pos = 1;
d->synchedEvents.clear(); d->synchedEvents.clear();
while(pos + 4 < end) { while(pos + 4 < end) {
EventType type = EventType(uchar(data[pos++])); EventType type = static_cast<EventType>(static_cast<unsigned char>(data[pos++]));
uint time = data.toUInt(pos, true); unsigned int time = data.toUInt(pos, true);
pos += 4; pos += 4;
d->synchedEvents.append(SynchedEvent(time, type)); d->synchedEvents.append(SynchedEvent(time, type));
} }

View File

@ -108,8 +108,8 @@ namespace TagLib {
* Single entry of time stamp and event. * Single entry of time stamp and event.
*/ */
struct SynchedEvent { struct SynchedEvent {
SynchedEvent(uint ms, EventType t) : time(ms), type(t) {} SynchedEvent(unsigned int ms, EventType t) : time(ms), type(t) {}
uint time; unsigned int time;
EventType type; EventType type;
}; };

View File

@ -1,6 +1,7 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org email : wheeler@kde.org
copyright : (C) 2006 by Aaron VonderHaar copyright : (C) 2006 by Aaron VonderHaar
email : avh4@users.sourceforge.net email : avh4@users.sourceforge.net
***************************************************************************/ ***************************************************************************/
@ -26,6 +27,7 @@
***************************************************************************/ ***************************************************************************/
#include <tdebug.h> #include <tdebug.h>
#include <tstringlist.h>
#include "generalencapsulatedobjectframe.h" #include "generalencapsulatedobjectframe.h"
@ -151,15 +153,21 @@ void GeneralEncapsulatedObjectFrame::parseFields(const ByteVector &data)
ByteVector GeneralEncapsulatedObjectFrame::renderFields() const ByteVector GeneralEncapsulatedObjectFrame::renderFields() const
{ {
StringList sl;
sl.append(d->fileName);
sl.append(d->description);
const String::Type encoding = checkTextEncoding(sl, d->textEncoding);
ByteVector data; ByteVector data;
data.append(char(d->textEncoding)); data.append(char(encoding));
data.append(d->mimeType.data(String::Latin1)); data.append(d->mimeType.data(String::Latin1));
data.append(textDelimiter(String::Latin1)); data.append(textDelimiter(String::Latin1));
data.append(d->fileName.data(d->textEncoding)); data.append(d->fileName.data(encoding));
data.append(textDelimiter(d->textEncoding)); data.append(textDelimiter(encoding));
data.append(d->description.data(d->textEncoding)); data.append(d->description.data(encoding));
data.append(textDelimiter(d->textEncoding)); data.append(textDelimiter(encoding));
data.append(d->data); data.append(d->data);
return data; return data;

View File

@ -1,6 +1,7 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org email : wheeler@kde.org
copyright : (C) 2006 by Aaron VonderHaar copyright : (C) 2006 by Aaron VonderHaar
email : avh4@users.sourceforge.net email : avh4@users.sourceforge.net
***************************************************************************/ ***************************************************************************/

View File

@ -24,9 +24,10 @@
***************************************************************************/ ***************************************************************************/
#include <tdebug.h> #include <tdebug.h>
#include <tstringlist.h>
#include <id3v2tag.h>
#include "ownershipframe.h" #include "ownershipframe.h"
#include <id3v2tag.h>
using namespace TagLib; using namespace TagLib;
using namespace ID3v2; using namespace ID3v2;
@ -140,13 +141,18 @@ void OwnershipFrame::parseFields(const ByteVector &data)
ByteVector OwnershipFrame::renderFields() const ByteVector OwnershipFrame::renderFields() const
{ {
StringList sl;
sl.append(d->seller);
const String::Type encoding = checkTextEncoding(sl, d->textEncoding);
ByteVector v; ByteVector v;
v.append(char(d->textEncoding)); v.append(char(encoding));
v.append(d->pricePaid.data(String::Latin1)); v.append(d->pricePaid.data(String::Latin1));
v.append(textDelimiter(String::Latin1)); v.append(textDelimiter(String::Latin1));
v.append(d->datePurchased.data(String::Latin1)); v.append(d->datePurchased.data(String::Latin1));
v.append(d->seller.data(d->textEncoding)); v.append(d->seller.data(encoding));
return v; return v;
} }

View File

@ -0,0 +1,79 @@
/***************************************************************************
copyright : (C) 2015 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "podcastframe.h"
using namespace TagLib;
using namespace ID3v2;
class PodcastFrame::PodcastFramePrivate
{
public:
ByteVector fieldData;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
PodcastFrame::PodcastFrame() : Frame("PCST")
{
d = new PodcastFramePrivate;
d->fieldData = ByteVector(4, '\0');
}
PodcastFrame::~PodcastFrame()
{
delete d;
}
String PodcastFrame::toString() const
{
return String();
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
void PodcastFrame::parseFields(const ByteVector &data)
{
d->fieldData = data;
}
ByteVector PodcastFrame::renderFields() const
{
return d->fieldData;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
PodcastFrame::PodcastFrame(const ByteVector &data, Header *h) : Frame(h)
{
d = new PodcastFramePrivate;
parseFields(fieldData(data));
}

View File

@ -0,0 +1,80 @@
/***************************************************************************
copyright : (C) 2015 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_PODCASTFRAME_H
#define TAGLIB_PODCASTFRAME_H
#include "id3v2frame.h"
#include "taglib_export.h"
namespace TagLib {
namespace ID3v2 {
//! ID3v2 podcast frame
/*!
* An implementation of ID3v2 podcast flag, a frame with four zero bytes.
*/
class TAGLIB_EXPORT PodcastFrame : public Frame
{
friend class FrameFactory;
public:
/*!
* Construct a podcast frame.
*/
PodcastFrame();
/*!
* Destroys this PodcastFrame instance.
*/
virtual ~PodcastFrame();
/*!
* Returns a null string.
*/
virtual String toString() const;
protected:
// Reimplementations.
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
private:
/*!
* The constructor used by the FrameFactory.
*/
PodcastFrame(const ByteVector &data, Header *h);
PodcastFrame(const PodcastFrame &);
PodcastFrame &operator=(const PodcastFrame &);
class PodcastFramePrivate;
PodcastFramePrivate *d;
};
}
}
#endif

View File

@ -36,7 +36,7 @@ public:
PopularimeterFramePrivate() : rating(0), counter(0) {} PopularimeterFramePrivate() : rating(0), counter(0) {}
String email; String email;
int rating; int rating;
TagLib::uint counter; unsigned int counter;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -84,12 +84,12 @@ void PopularimeterFrame::setRating(int s)
d->rating = s; d->rating = s;
} }
TagLib::uint PopularimeterFrame::counter() const unsigned int PopularimeterFrame::counter() const
{ {
return d->counter; return d->counter;
} }
void PopularimeterFrame::setCounter(TagLib::uint s) void PopularimeterFrame::setCounter(unsigned int s)
{ {
d->counter = s; d->counter = s;
} }
@ -109,7 +109,7 @@ void PopularimeterFrame::parseFields(const ByteVector &data)
if(pos < size) { if(pos < size) {
d->rating = (unsigned char)(data[pos++]); d->rating = (unsigned char)(data[pos++]);
if(pos < size) { if(pos < size) {
d->counter = data.toUInt(static_cast<uint>(pos)); d->counter = data.toUInt(static_cast<unsigned int>(pos));
} }
} }
} }

View File

@ -100,14 +100,14 @@ namespace TagLib {
* *
* \see setCounter() * \see setCounter()
*/ */
uint counter() const; unsigned int counter() const;
/*! /*!
* Set the counter. * Set the counter.
* *
* \see counter() * \see counter()
*/ */
void setCounter(uint counter); void setCounter(unsigned int counter);
protected: protected:
// Reimplementations. // Reimplementations.

View File

@ -31,11 +31,6 @@
using namespace TagLib; using namespace TagLib;
using namespace ID3v2; using namespace ID3v2;
static inline int bitsToBytes(int i)
{
return i % 8 == 0 ? i / 8 : (i - i % 8) / 8 + 1;
}
struct ChannelData struct ChannelData
{ {
ChannelData() : channelType(RelativeVolumeFrame::Other), volumeAdjustment(0) {} ChannelData() : channelType(RelativeVolumeFrame::Other), volumeAdjustment(0) {}
@ -185,19 +180,18 @@ void RelativeVolumeFrame::parseFields(const ByteVector &data)
while(pos <= (int)data.size() - 4) { while(pos <= (int)data.size() - 4) {
ChannelType type = ChannelType(data[pos]); ChannelType type = ChannelType(data[pos]);
pos += 1; pos += 1;
ChannelData &channel = d->channels[type]; ChannelData &channel = d->channels[type];
channel.volumeAdjustment = data.toShort(static_cast<uint>(pos)); channel.volumeAdjustment = data.toShort(static_cast<unsigned int>(pos));
pos += 2; pos += 2;
channel.peakVolume.bitsRepresentingPeak = data[pos]; channel.peakVolume.bitsRepresentingPeak = data[pos];
pos += 1; pos += 1;
int bytes = bitsToBytes(channel.peakVolume.bitsRepresentingPeak); const int bytes = (channel.peakVolume.bitsRepresentingPeak + 7) / 8;
channel.peakVolume.peakVolume = data.mid(pos, bytes); channel.peakVolume.peakVolume = data.mid(pos, bytes);
pos += bytes; pos += bytes;
} }

View File

@ -117,8 +117,7 @@ void SynchronizedLyricsFrame::setLanguage(const ByteVector &languageEncoding)
d->language = languageEncoding.mid(0, 3); d->language = languageEncoding.mid(0, 3);
} }
void SynchronizedLyricsFrame::setTimestampFormat( void SynchronizedLyricsFrame::setTimestampFormat(SynchronizedLyricsFrame::TimestampFormat f)
SynchronizedLyricsFrame::TimestampFormat f)
{ {
d->timestampFormat = f; d->timestampFormat = f;
} }
@ -159,7 +158,7 @@ void SynchronizedLyricsFrame::parseFields(const ByteVector &data)
int pos = 6; int pos = 6;
d->description = readStringField(data, d->textEncoding, &pos); d->description = readStringField(data, d->textEncoding, &pos);
if(d->description.isNull()) if(pos == 6)
return; return;
/* /*
@ -171,7 +170,7 @@ void SynchronizedLyricsFrame::parseFields(const ByteVector &data)
*/ */
String::Type encWithEndianness = d->textEncoding; String::Type encWithEndianness = d->textEncoding;
if(d->textEncoding == String::UTF16) { if(d->textEncoding == String::UTF16) {
ushort bom = data.toUShort(6, true); unsigned short bom = data.toUShort(6, true);
if(bom == 0xfffe) { if(bom == 0xfffe) {
encWithEndianness = String::UTF16LE; encWithEndianness = String::UTF16LE;
} else if(bom == 0xfeff) { } else if(bom == 0xfeff) {
@ -184,16 +183,16 @@ void SynchronizedLyricsFrame::parseFields(const ByteVector &data)
String::Type enc = d->textEncoding; String::Type enc = d->textEncoding;
// If a UTF16 string has no BOM, use the encoding found above. // If a UTF16 string has no BOM, use the encoding found above.
if(enc == String::UTF16 && pos + 1 < end) { if(enc == String::UTF16 && pos + 1 < end) {
ushort bom = data.toUShort(pos, true); unsigned short bom = data.toUShort(pos, true);
if(bom != 0xfffe && bom != 0xfeff) { if(bom != 0xfffe && bom != 0xfeff) {
enc = encWithEndianness; enc = encWithEndianness;
} }
} }
String text = readStringField(data, enc, &pos); String text = readStringField(data, enc, &pos);
if(text.isNull() || pos + 4 > end) if(text.isEmpty() || pos + 4 > end)
return; return;
uint time = data.toUInt(pos, true); unsigned int time = data.toUInt(pos, true);
pos += 4; pos += 4;
d->synchedText.append(SynchedText(time, text)); d->synchedText.append(SynchedText(time, text));

View File

@ -85,8 +85,8 @@ namespace TagLib {
* Single entry of time stamp and lyrics text. * Single entry of time stamp and lyrics text.
*/ */
struct SynchedText { struct SynchedText {
SynchedText(uint ms, String str) : time(ms), text(str) {} SynchedText(unsigned int ms, String str) : time(ms), text(str) {}
uint time; unsigned int time;
String text; String text;
}; };

View File

@ -121,7 +121,7 @@ bool TableOfContentsFrame::isOrdered() const
return d->isOrdered; return d->isOrdered;
} }
TagLib::uint TableOfContentsFrame::entryCount() const unsigned int TableOfContentsFrame::entryCount() const
{ {
return d->childElements.size(); return d->childElements.size();
} }
@ -214,7 +214,7 @@ void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
String TableOfContentsFrame::toString() const String TableOfContentsFrame::toString() const
{ {
return String::null; return String();
} }
PropertyMap TableOfContentsFrame::asProperties() const PropertyMap TableOfContentsFrame::asProperties() const
@ -261,7 +261,7 @@ TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag)
void TableOfContentsFrame::parseFields(const ByteVector &data) void TableOfContentsFrame::parseFields(const ByteVector &data)
{ {
TagLib::uint size = data.size(); unsigned int size = data.size();
if(size < 6) { if(size < 6) {
debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by " debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by "
"null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated " "null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated "
@ -270,12 +270,12 @@ void TableOfContentsFrame::parseFields(const ByteVector &data)
} }
int pos = 0; int pos = 0;
TagLib::uint embPos = 0; unsigned int embPos = 0;
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->isTopLevel = (data.at(pos) & 2) > 0; d->isTopLevel = (data.at(pos) & 2) > 0;
d->isOrdered = (data.at(pos++) & 1) > 0; d->isOrdered = (data.at(pos++) & 1) > 0;
TagLib::uint entryCount = data.at(pos++); unsigned int entryCount = data.at(pos++);
for(TagLib::uint i = 0; i < entryCount; i++) { for(unsigned int i = 0; i < entryCount; i++) {
ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->childElements.append(childElementID); d->childElements.append(childElementID);
} }

View File

@ -95,7 +95,7 @@ namespace TagLib {
* *
* \see childElements() * \see childElements()
*/ */
uint entryCount() const; unsigned int entryCount() const;
/*! /*!
* Returns list of child elements of the frame. * Returns list of child elements of the frame.

View File

@ -119,22 +119,26 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding; d->textEncoding = encoding;
} }
// array of allowed TIPL prefixes and their corresponding key value namespace
static const TagLib::uint involvedPeopleSize = 5; {
static const char* involvedPeople[][2] = { // array of allowed TIPL prefixes and their corresponding key value
const char* involvedPeople[][2] = {
{"ARRANGER", "ARRANGER"}, {"ARRANGER", "ARRANGER"},
{"ENGINEER", "ENGINEER"}, {"ENGINEER", "ENGINEER"},
{"PRODUCER", "PRODUCER"}, {"PRODUCER", "PRODUCER"},
{"DJ-MIX", "DJMIXER"}, {"DJ-MIX", "DJMIXER"},
{"MIX", "MIXER"}, {"MIX", "MIXER"},
}; };
const size_t involvedPeopleSize = sizeof(involvedPeople) / sizeof(involvedPeople[0]);
}
const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static
{ {
static KeyConversionMap m; static KeyConversionMap m;
if(m.isEmpty()) if(m.isEmpty()) {
for(uint i = 0; i < involvedPeopleSize; ++i) for(size_t i = 0; i < involvedPeopleSize; ++i)
m.insert(involvedPeople[i][1], involvedPeople[i][0]); m.insert(involvedPeople[i][1], involvedPeople[i][0]);
}
return m; return m;
} }
@ -146,7 +150,7 @@ PropertyMap TextIdentificationFrame::asProperties() const
return makeTMCLProperties(); return makeTMCLProperties();
PropertyMap map; PropertyMap map;
String tagName = frameIDToKey(frameID()); String tagName = frameIDToKey(frameID());
if(tagName.isNull()) { if(tagName.isEmpty()) {
map.unsupportedData().append(frameID()); map.unsupportedData().append(frameID());
return map; return map;
} }
@ -265,7 +269,7 @@ PropertyMap TextIdentificationFrame::makeTIPLProperties() const
StringList l = fieldList(); StringList l = fieldList();
for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) { for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
bool found = false; bool found = false;
for(uint i = 0; i < involvedPeopleSize; ++i) for(size_t i = 0; i < involvedPeopleSize; ++i)
if(*it == involvedPeople[i][0]) { if(*it == involvedPeople[i][0]) {
map.insert(involvedPeople[i][1], (++it)->split(",")); map.insert(involvedPeople[i][1], (++it)->split(","));
found = true; found = true;
@ -292,7 +296,7 @@ PropertyMap TextIdentificationFrame::makeTMCLProperties() const
StringList l = fieldList(); StringList l = fieldList();
for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) { for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
String instrument = it->upper(); String instrument = it->upper();
if(instrument.isNull()) { if(instrument.isEmpty()) {
// instrument is not a valid key -> frame unsupported // instrument is not a valid key -> frame unsupported
map.clear(); map.clear();
map.unsupportedData().append(frameID()); map.unsupportedData().append(frameID());
@ -312,8 +316,8 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(String::Type encoding)
d(0) d(0)
{ {
StringList l; StringList l;
l.append(String::null); l.append(String());
l.append(String::null); l.append(String());
setText(l); setText(l);
} }
@ -341,7 +345,7 @@ String UserTextIdentificationFrame::description() const
{ {
return !TextIdentificationFrame::fieldList().isEmpty() return !TextIdentificationFrame::fieldList().isEmpty()
? TextIdentificationFrame::fieldList().front() ? TextIdentificationFrame::fieldList().front()
: String::null; : String();
} }
StringList UserTextIdentificationFrame::fieldList() const StringList UserTextIdentificationFrame::fieldList() const
@ -354,7 +358,7 @@ StringList UserTextIdentificationFrame::fieldList() const
void UserTextIdentificationFrame::setText(const String &text) void UserTextIdentificationFrame::setText(const String &text)
{ {
if(description().isEmpty()) if(description().isEmpty())
setDescription(String::null); setDescription(String());
TextIdentificationFrame::setText(StringList(description()).append(text)); TextIdentificationFrame::setText(StringList(description()).append(text));
} }
@ -362,7 +366,7 @@ void UserTextIdentificationFrame::setText(const String &text)
void UserTextIdentificationFrame::setText(const StringList &fields) void UserTextIdentificationFrame::setText(const StringList &fields)
{ {
if(description().isEmpty()) if(description().isEmpty())
setDescription(String::null); setDescription(String());
TextIdentificationFrame::setText(StringList(description()).append(fields)); TextIdentificationFrame::setText(StringList(description()).append(fields));
} }
@ -417,7 +421,7 @@ void UserTextIdentificationFrame::checkFields()
int fields = fieldList().size(); int fields = fieldList().size();
if(fields == 0) if(fields == 0)
setDescription(String::null); setDescription(String());
if(fields <= 1) if(fields <= 1)
setText(String::null); setText(String());
} }

View File

@ -86,7 +86,7 @@ void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v)
String UniqueFileIdentifierFrame::toString() const String UniqueFileIdentifierFrame::toString() const
{ {
return String::null; return String();
} }
PropertyMap UniqueFileIdentifierFrame::asProperties() const PropertyMap UniqueFileIdentifierFrame::asProperties() const

View File

@ -51,7 +51,7 @@ UnknownFrame::~UnknownFrame()
String UnknownFrame::toString() const String UnknownFrame::toString() const
{ {
return String::null; return String();
} }
ByteVector UnknownFrame::data() const ByteVector UnknownFrame::data() const

View File

@ -1,6 +1,7 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org email : wheeler@kde.org
copyright : (C) 2006 by Urs Fleisch copyright : (C) 2006 by Urs Fleisch
email : ufleisch@users.sourceforge.net email : ufleisch@users.sourceforge.net
***************************************************************************/ ***************************************************************************/
@ -119,8 +120,6 @@ PropertyMap UnsynchronizedLyricsFrame::asProperties() const
String key = description().upper(); String key = description().upper();
if(key.isEmpty() || key.upper() == "LYRICS") if(key.isEmpty() || key.upper() == "LYRICS")
map.insert("LYRICS", text()); map.insert("LYRICS", text());
else if(key.isNull())
map.unsupportedData().append(L"USLT/" + description());
else else
map.insert("LYRICS:" + key, text()); map.insert("LYRICS:" + key, text());
return map; return map;
@ -170,13 +169,19 @@ void UnsynchronizedLyricsFrame::parseFields(const ByteVector &data)
ByteVector UnsynchronizedLyricsFrame::renderFields() const ByteVector UnsynchronizedLyricsFrame::renderFields() const
{ {
StringList sl;
sl.append(d->description);
sl.append(d->text);
const String::Type encoding = checkTextEncoding(sl, d->textEncoding);
ByteVector v; ByteVector v;
v.append(char(d->textEncoding)); v.append(char(encoding));
v.append(d->language.size() == 3 ? d->language : "XXX"); v.append(d->language.size() == 3 ? d->language : "XXX");
v.append(d->description.data(d->textEncoding)); v.append(d->description.data(encoding));
v.append(textDelimiter(d->textEncoding)); v.append(textDelimiter(encoding));
v.append(d->text.data(d->textEncoding)); v.append(d->text.data(encoding));
return v; return v;
} }

View File

@ -1,6 +1,7 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org email : wheeler@kde.org
copyright : (C) 2006 by Urs Fleisch copyright : (C) 2006 by Urs Fleisch
email : ufleisch@users.sourceforge.net email : ufleisch@users.sourceforge.net
***************************************************************************/ ***************************************************************************/
@ -84,7 +85,7 @@ PropertyMap UrlLinkFrame::asProperties() const
{ {
String key = frameIDToKey(frameID()); String key = frameIDToKey(frameID());
PropertyMap map; PropertyMap map;
if(key.isNull()) if(key.isEmpty())
// unknown W*** frame - this normally shouldn't happen // unknown W*** frame - this normally shouldn't happen
map.unsupportedData().append(frameID()); map.unsupportedData().append(frameID());
else else
@ -159,8 +160,6 @@ PropertyMap UserUrlLinkFrame::asProperties() const
String key = description().upper(); String key = description().upper();
if(key.isEmpty() || key.upper() == "URL") if(key.isEmpty() || key.upper() == "URL")
map.insert("URL", url()); map.insert("URL", url());
else if(key.isNull())
map.unsupportedData().append(L"WXXX/" + description());
else else
map.insert("URL:" + key, url()); map.insert("URL:" + key, url());
return map; return map;

View File

@ -1,6 +1,7 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org email : wheeler@kde.org
copyright : (C) 2006 by Urs Fleisch copyright : (C) 2006 by Urs Fleisch
email : ufleisch@users.sourceforge.net email : ufleisch@users.sourceforge.net
***************************************************************************/ ***************************************************************************/

View File

@ -34,7 +34,7 @@ class ExtendedHeader::ExtendedHeaderPrivate
public: public:
ExtendedHeaderPrivate() : size(0) {} ExtendedHeaderPrivate() : size(0) {}
uint size; unsigned int size;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -51,7 +51,7 @@ ExtendedHeader::~ExtendedHeader()
delete d; delete d;
} }
TagLib::uint ExtendedHeader::size() const unsigned int ExtendedHeader::size() const
{ {
return d->size; return d->size;
} }

View File

@ -62,7 +62,7 @@ namespace TagLib {
* Returns the size of the extended header. This is variable for the * Returns the size of the extended header. This is variable for the
* extended header. * extended header.
*/ */
uint size() const; unsigned int size() const;
/*! /*!
* Sets the data that will be used as the extended header. Since the * Sets the data that will be used as the extended header. Since the

View File

@ -31,23 +31,20 @@ using namespace ID3v2;
class Footer::FooterPrivate class Footer::FooterPrivate
{ {
public:
static const uint size = 10;
}; };
Footer::Footer() Footer::Footer() :
d(0)
{ {
} }
Footer::~Footer() Footer::~Footer()
{ {
} }
TagLib::uint Footer::size() unsigned int Footer::size()
{ {
return FooterPrivate::size; return 10;
} }
ByteVector Footer::render(const Header *header) const ByteVector Footer::render(const Header *header) const

View File

@ -62,7 +62,7 @@ namespace TagLib {
/*! /*!
* Returns the size of the footer. Presently this is always 10 bytes. * Returns the size of the footer. Presently this is always 10 bytes.
*/ */
static uint size(); static unsigned int size();
/*! /*!
* Renders the footer based on the data in \a header. * Renders the footer based on the data in \a header.

View File

@ -23,22 +23,16 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if HAVE_ZLIB
#include <zlib.h>
#endif
#include <bitset> #include <bitset>
#include <tdebug.h> #include <tdebug.h>
#include <tstringlist.h> #include <tstringlist.h>
#include <tzlib.h>
#include "id3v2tag.h" #include "id3v2tag.h"
#include "id3v2frame.h" #include "id3v2frame.h"
#include "id3v2synchdata.h" #include "id3v2synchdata.h"
#include "tpropertymap.h" #include "tpropertymap.h"
#include "frames/textidentificationframe.h" #include "frames/textidentificationframe.h"
#include "frames/urllinkframe.h" #include "frames/urllinkframe.h"
@ -85,22 +79,22 @@ namespace
// static methods // static methods
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
TagLib::uint Frame::headerSize() unsigned int Frame::headerSize()
{ {
return Header::size(); return Header::size();
} }
TagLib::uint Frame::headerSize(uint version) unsigned int Frame::headerSize(unsigned int version)
{ {
return Header::size(version); return Header::size(version);
} }
ByteVector Frame::textDelimiter(String::Type t) ByteVector Frame::textDelimiter(String::Type t)
{ {
ByteVector d = char(0);
if(t == String::UTF16 || t == String::UTF16BE || t == String::UTF16LE) if(t == String::UTF16 || t == String::UTF16BE || t == String::UTF16LE)
d.append(char(0)); return ByteVector(2, '\0');
return d; else
return ByteVector(1, '\0');
} }
const String Frame::instrumentPrefix("PERFORMER:"); const String Frame::instrumentPrefix("PERFORMER:");
@ -116,8 +110,9 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
{ {
// check if the key is contained in the key<=>frameID mapping // check if the key is contained in the key<=>frameID mapping
ByteVector frameID = keyToFrameID(key); ByteVector frameID = keyToFrameID(key);
if(!frameID.isNull()) { if(!frameID.isEmpty()) {
if(frameID[0] == 'T'){ // text frame // Apple proprietary WFED (Podcast URL) is in fact a text frame.
if(frameID[0] == 'T' || frameID == "WFED"){ // text frame
TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8); TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8);
frame->setText(values); frame->setText(values);
return frame; return frame;
@ -169,10 +164,10 @@ ByteVector Frame::frameID() const
if(d->header) if(d->header)
return d->header->frameID(); return d->header->frameID();
else else
return ByteVector::null; return ByteVector();
} }
TagLib::uint Frame::size() const unsigned int Frame::size() const
{ {
if(d->header) if(d->header)
return d->header->frameSize(); return d->header->frameSize();
@ -240,67 +235,30 @@ void Frame::parse(const ByteVector &data)
ByteVector Frame::fieldData(const ByteVector &frameData) const ByteVector Frame::fieldData(const ByteVector &frameData) const
{ {
uint headerSize = Header::size(d->header->version()); unsigned int headerSize = Header::size(d->header->version());
uint frameDataOffset = headerSize; unsigned int frameDataOffset = headerSize;
uint frameDataLength = size(); unsigned int frameDataLength = size();
if(d->header->compression() || d->header->dataLengthIndicator()) { if(d->header->compression() || d->header->dataLengthIndicator()) {
frameDataLength = SynchData::toUInt(frameData.mid(headerSize, 4)); frameDataLength = SynchData::toUInt(frameData.mid(headerSize, 4));
frameDataOffset += 4; frameDataOffset += 4;
} }
#if HAVE_ZLIB if(zlib::isAvailable() && d->header->compression() && !d->header->encryption()) {
if(d->header->compression() &&
!d->header->encryption())
{
if(frameData.size() <= frameDataOffset) { if(frameData.size() <= frameDataOffset) {
debug("Compressed frame doesn't have enough data to decode"); debug("Compressed frame doesn't have enough data to decode");
return ByteVector(); return ByteVector();
} }
z_stream stream = {}; const ByteVector outData = zlib::decompress(frameData.mid(frameDataOffset));
if(!outData.isEmpty() && frameDataLength != outData.size()) {
if(inflateInit(&stream) != Z_OK)
return ByteVector();
stream.avail_in = (uLongf) frameData.size() - frameDataOffset;
stream.next_in = (Bytef *) frameData.data() + frameDataOffset;
static const uint chunkSize = 1024;
ByteVector data;
ByteVector chunk(chunkSize);
do {
stream.avail_out = (uLongf) chunk.size();
stream.next_out = (Bytef *) chunk.data();
int result = inflate(&stream, Z_NO_FLUSH);
if(result == Z_STREAM_ERROR ||
result == Z_NEED_DICT ||
result == Z_DATA_ERROR ||
result == Z_MEM_ERROR)
{
if(result != Z_STREAM_ERROR)
inflateEnd(&stream);
debug("Error reading compressed stream");
return ByteVector();
}
data.append(stream.avail_out == 0 ? chunk : chunk.mid(0, chunk.size() - stream.avail_out));
} while(stream.avail_out == 0);
inflateEnd(&stream);
if(frameDataLength != data.size())
debug("frameDataLength does not match the data length returned by zlib"); debug("frameDataLength does not match the data length returned by zlib");
return data;
} }
else
#endif return outData;
}
return frameData.mid(frameDataOffset, frameDataLength); return frameData.mid(frameDataOffset, frameDataLength);
} }
@ -316,7 +274,7 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int
int end = data.find(delimiter, *position, delimiter.size()); int end = data.find(delimiter, *position, delimiter.size());
if(end < *position) if(end < *position)
return String::null; return String();
String str; String str;
if(encoding == String::Latin1) if(encoding == String::Latin1)
@ -334,7 +292,7 @@ String::Type Frame::checkEncoding(const StringList &fields, String::Type encodin
return checkEncoding(fields, encoding, 4); return checkEncoding(fields, encoding, 4);
} }
String::Type Frame::checkEncoding(const StringList &fields, String::Type encoding, uint version) // static String::Type Frame::checkEncoding(const StringList &fields, String::Type encoding, unsigned int version) // static
{ {
if((encoding == String::UTF8 || encoding == String::UTF16BE) && version != 4) if((encoding == String::UTF8 || encoding == String::UTF16BE) && version != 4)
return String::UTF16; return String::UTF16;
@ -363,8 +321,9 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
return checkEncoding(fields, encoding, header()->version()); return checkEncoding(fields, encoding, header()->version());
} }
static const TagLib::uint frameTranslationSize = 51; namespace
static const char *frameTranslation[][2] = { {
const char *frameTranslation[][2] = {
// Text information frames // Text information frames
{ "TALB", "ALBUM"}, { "TALB", "ALBUM"},
{ "TBPM", "BPM" }, { "TBPM", "BPM" },
@ -427,97 +386,80 @@ static const char *frameTranslation[][2] = {
// Other frames // Other frames
{ "COMM", "COMMENT" }, { "COMM", "COMMENT" },
//{ "USLT", "LYRICS" }, handled specially //{ "USLT", "LYRICS" }, handled specially
}; // Apple iTunes proprietary frames
{ "PCST", "PODCAST" },
{ "TCAT", "PODCASTCATEGORY" },
{ "TDES", "PODCASTDESC" },
{ "TGID", "PODCASTID" },
{ "WFED", "PODCASTURL" },
};
const size_t frameTranslationSize = sizeof(frameTranslation) / sizeof(frameTranslation[0]);
static const TagLib::uint txxxFrameTranslationSize = 8; const char *txxxFrameTranslation[][2] = {
static const char *txxxFrameTranslation[][2] = { { "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" },
{ "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, { "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" },
{ "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, { "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" },
{ "MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, { "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" },
{ "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, { "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" },
{ "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, { "ACOUSTID ID", "ACOUSTID_ID" },
{ "Acoustid Id", "ACOUSTID_ID" }, { "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" },
{ "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" }, { "MUSICIP PUID", "MUSICIP_PUID" },
{ "MusicIP PUID", "MUSICIP_PUID" }, };
}; const size_t txxxFrameTranslationSize = sizeof(txxxFrameTranslation) / sizeof(txxxFrameTranslation[0]);
Map<ByteVector, String> &idMap() // list of deprecated frames and their successors
{ const char *deprecatedFrames[][2] = {
static Map<ByteVector, String> m;
if(m.isEmpty())
for(size_t i = 0; i < frameTranslationSize; ++i)
m[frameTranslation[i][0]] = frameTranslation[i][1];
return m;
}
Map<String, String> &txxxMap()
{
static Map<String, String> m;
if(m.isEmpty()) {
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
String key = String(txxxFrameTranslation[i][0]).upper();
m[key] = txxxFrameTranslation[i][1];
}
}
return m;
}
// list of deprecated frames and their successors
static const TagLib::uint deprecatedFramesSize = 4;
static const char *deprecatedFrames[][2] = {
{"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3) {"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
{"TDAT", "TDRC"}, // 2.3 -> 2.4 {"TDAT", "TDRC"}, // 2.3 -> 2.4
{"TYER", "TDRC"}, // 2.3 -> 2.4 {"TYER", "TDRC"}, // 2.3 -> 2.4
{"TIME", "TDRC"}, // 2.3 -> 2.4 {"TIME", "TDRC"}, // 2.3 -> 2.4
}; };
const size_t deprecatedFramesSize = sizeof(deprecatedFrames) / sizeof(deprecatedFrames[0]);;
Map<ByteVector,ByteVector> &deprecationMap()
{
static Map<ByteVector,ByteVector> depMap;
if(depMap.isEmpty())
for(TagLib::uint i = 0; i < deprecatedFramesSize; ++i)
depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1];
return depMap;
} }
String Frame::frameIDToKey(const ByteVector &id) String Frame::frameIDToKey(const ByteVector &id)
{ {
Map<ByteVector, String> &m = idMap(); ByteVector id24 = id;
if(m.contains(id)) for(size_t i = 0; i < deprecatedFramesSize; ++i) {
return m[id]; if(id24 == deprecatedFrames[i][0]) {
if(deprecationMap().contains(id)) id24 = deprecatedFrames[i][1];
return m[deprecationMap()[id]]; break;
return String::null; }
}
for(size_t i = 0; i < frameTranslationSize; ++i) {
if(id24 == frameTranslation[i][0])
return frameTranslation[i][1];
}
return String();
} }
ByteVector Frame::keyToFrameID(const String &s) ByteVector Frame::keyToFrameID(const String &s)
{ {
static Map<String, ByteVector> m; const String key = s.upper();
if(m.isEmpty()) for(size_t i = 0; i < frameTranslationSize; ++i) {
for(size_t i = 0; i < frameTranslationSize; ++i) if(key == frameTranslation[i][1])
m[frameTranslation[i][1]] = frameTranslation[i][0]; return frameTranslation[i][0];
if(m.contains(s.upper())) }
return m[s]; return ByteVector();
return ByteVector::null;
} }
String Frame::txxxToKey(const String &description) String Frame::txxxToKey(const String &description)
{ {
Map<String, String> &m = txxxMap(); const String d = description.upper();
String d = description.upper(); for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
if(m.contains(d)) if(d == txxxFrameTranslation[i][0])
return m[d]; return txxxFrameTranslation[i][1];
}
return d; return d;
} }
String Frame::keyToTXXX(const String &s) String Frame::keyToTXXX(const String &s)
{ {
static Map<String, String> m; const String key = s.upper();
if(m.isEmpty()) for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) if(key == txxxFrameTranslation[i][1])
m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0]; return txxxFrameTranslation[i][0];
if(m.contains(s.upper())) }
return m[s];
return s; return s;
} }
@ -532,7 +474,8 @@ PropertyMap Frame::asProperties() const
// workaround until this function is virtual // workaround until this function is virtual
if(id == "TXXX") if(id == "TXXX")
return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties(); return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties();
else if(id[0] == 'T') // Apple proprietary WFED (Podcast URL) is in fact a text frame.
else if(id[0] == 'T' || id == "WFED")
return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties(); return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties();
else if(id == "WXXX") else if(id == "WXXX")
return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties(); return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties();
@ -552,7 +495,6 @@ PropertyMap Frame::asProperties() const
void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties, void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
PropertyMap &tiplProperties, PropertyMap &tmclProperties) PropertyMap &tiplProperties, PropertyMap &tmclProperties)
{ {
singleFrameProperties.clear(); singleFrameProperties.clear();
tiplProperties.clear(); tiplProperties.clear();
tmclProperties.clear(); tmclProperties.clear();
@ -587,8 +529,8 @@ public:
{} {}
ByteVector frameID; ByteVector frameID;
uint frameSize; unsigned int frameSize;
uint version; unsigned int version;
// flags // flags
@ -606,12 +548,12 @@ public:
// static members (Frame::Header) // static members (Frame::Header)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
TagLib::uint Frame::Header::size() unsigned int Frame::Header::size()
{ {
return size(4); return size(4);
} }
TagLib::uint Frame::Header::size(uint version) unsigned int Frame::Header::size(unsigned int version)
{ {
switch(version) { switch(version) {
case 0: case 0:
@ -635,7 +577,7 @@ Frame::Header::Header(const ByteVector &data, bool synchSafeInts)
setData(data, synchSafeInts); setData(data, synchSafeInts);
} }
Frame::Header::Header(const ByteVector &data, uint version) Frame::Header::Header(const ByteVector &data, unsigned int version)
{ {
d = new HeaderPrivate; d = new HeaderPrivate;
setData(data, version); setData(data, version);
@ -648,10 +590,10 @@ Frame::Header::~Header()
void Frame::Header::setData(const ByteVector &data, bool synchSafeInts) void Frame::Header::setData(const ByteVector &data, bool synchSafeInts)
{ {
setData(data, uint(synchSafeInts ? 4 : 3)); setData(data, static_cast<unsigned int>(synchSafeInts ? 4 : 3));
} }
void Frame::Header::setData(const ByteVector &data, uint version) void Frame::Header::setData(const ByteVector &data, unsigned int version)
{ {
d->version = version; d->version = version;
@ -792,22 +734,22 @@ void Frame::Header::setFrameID(const ByteVector &id)
d->frameID = id.mid(0, 4); d->frameID = id.mid(0, 4);
} }
TagLib::uint Frame::Header::frameSize() const unsigned int Frame::Header::frameSize() const
{ {
return d->frameSize; return d->frameSize;
} }
void Frame::Header::setFrameSize(uint size) void Frame::Header::setFrameSize(unsigned int size)
{ {
d->frameSize = size; d->frameSize = size;
} }
TagLib::uint Frame::Header::version() const unsigned int Frame::Header::version() const
{ {
return d->version; return d->version;
} }
void Frame::Header::setVersion(TagLib::uint version) void Frame::Header::setVersion(unsigned int version)
{ {
d->version = version; d->version = version;
} }

View File

@ -79,7 +79,7 @@ namespace TagLib {
/*! /*!
* Returns the size of the frame. * Returns the size of the frame.
*/ */
uint size() const; unsigned int size() const;
/*! /*!
* Returns the size of the frame header * Returns the size of the frame header
@ -89,14 +89,14 @@ namespace TagLib {
* non-binary compatible release this will be made into a non-static * non-binary compatible release this will be made into a non-static
* member that checks the internal ID3v2 version. * member that checks the internal ID3v2 version.
*/ */
static uint headerSize(); // BIC: remove and make non-static static unsigned int headerSize(); // BIC: remove and make non-static
/*! /*!
* Returns the size of the frame header for the given ID3v2 version. * Returns the size of the frame header for the given ID3v2 version.
* *
* \deprecated Please see the explanation above. * \deprecated Please see the explanation above.
*/ */
static uint headerSize(uint version); // BIC: remove and make non-static static unsigned int headerSize(unsigned int version); // BIC: remove and make non-static
/*! /*!
* Sets the data that will be used as the frame. Since the length is not * Sets the data that will be used as the frame. Since the length is not
@ -242,7 +242,7 @@ namespace TagLib {
*/ */
// BIC: remove and make non-static // BIC: remove and make non-static
static String::Type checkEncoding(const StringList &fields, static String::Type checkEncoding(const StringList &fields,
String::Type encoding, uint version); String::Type encoding, unsigned int version);
/*! /*!
* Checks a the list of string values to see if they can be used with the * Checks a the list of string values to see if they can be used with the
@ -264,13 +264,13 @@ namespace TagLib {
/*! /*!
* Returns an appropriate ID3 frame ID for the given free-form tag key. This method * Returns an appropriate ID3 frame ID for the given free-form tag key. This method
* will return ByteVector::null if no specialized translation is found. * will return an empty ByteVector if no specialized translation is found.
*/ */
static ByteVector keyToFrameID(const String &); static ByteVector keyToFrameID(const String &);
/*! /*!
* Returns a free-form tag name for the given ID3 frame ID. Note that this does not work * Returns a free-form tag name for the given ID3 frame ID. Note that this does not work
* for general frame IDs such as TXXX or WXXX; in such a case String::null is returned. * for general frame IDs such as TXXX or WXXX; in such a case an empty string is returned.
*/ */
static String frameIDToKey(const ByteVector &); static String frameIDToKey(const ByteVector &);
@ -343,7 +343,7 @@ namespace TagLib {
* *
* \a version should be the ID3v2 version of the tag. * \a version should be the ID3v2 version of the tag.
*/ */
explicit Header(const ByteVector &data, uint version = 4); explicit Header(const ByteVector &data, unsigned int version = 4);
/*! /*!
* Destroys this Header instance. * Destroys this Header instance.
@ -362,7 +362,7 @@ namespace TagLib {
* Sets the data for the Header. \a version should indicate the ID3v2 * Sets the data for the Header. \a version should indicate the ID3v2
* version number of the tag that this frame is contained in. * version number of the tag that this frame is contained in.
*/ */
void setData(const ByteVector &data, uint version = 4); void setData(const ByteVector &data, unsigned int version = 4);
/*! /*!
* Returns the Frame ID (Structure, <a href="id3v2-structure.html#4">4</a>) * Returns the Frame ID (Structure, <a href="id3v2-structure.html#4">4</a>)
@ -384,24 +384,24 @@ namespace TagLib {
* Returns the size of the frame data portion, as set when setData() was * Returns the size of the frame data portion, as set when setData() was
* called or set explicitly via setFrameSize(). * called or set explicitly via setFrameSize().
*/ */
uint frameSize() const; unsigned int frameSize() const;
/*! /*!
* Sets the size of the frame data portion. * Sets the size of the frame data portion.
*/ */
void setFrameSize(uint size); void setFrameSize(unsigned int size);
/*! /*!
* Returns the ID3v2 version of the header, as passed in from the * Returns the ID3v2 version of the header, as passed in from the
* construction of the header or set via setVersion(). * construction of the header or set via setVersion().
*/ */
uint version() const; unsigned int version() const;
/*! /*!
* Sets the ID3v2 version of the header, changing has impact on the * Sets the ID3v2 version of the header, changing has impact on the
* correct parsing/rendering of frame data. * correct parsing/rendering of frame data.
*/ */
void setVersion(uint version); void setVersion(unsigned int version);
/*! /*!
* Returns the size of the frame header in bytes. * Returns the size of the frame header in bytes.
@ -411,7 +411,7 @@ namespace TagLib {
* removed in the next binary incompatible release (2.0) and will be * removed in the next binary incompatible release (2.0) and will be
* replaced with a non-static method that checks the frame version. * replaced with a non-static method that checks the frame version.
*/ */
static uint size(); static unsigned int size();
/*! /*!
* Returns the size of the frame header in bytes for the ID3v2 version * Returns the size of the frame header in bytes for the ID3v2 version
@ -419,7 +419,7 @@ namespace TagLib {
* *
* \deprecated Please see the explanation in the version above. * \deprecated Please see the explanation in the version above.
*/ */
static uint size(uint version); static unsigned int size(unsigned int version);
/*! /*!
* Returns true if the flag for tag alter preservation is set. * Returns true if the flag for tag alter preservation is set.

View File

@ -23,11 +23,8 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <tdebug.h> #include <tdebug.h>
#include <tzlib.h>
#include "id3v2framefactory.h" #include "id3v2framefactory.h"
#include "id3v2synchdata.h" #include "id3v2synchdata.h"
@ -49,10 +46,45 @@
#include "frames/eventtimingcodesframe.h" #include "frames/eventtimingcodesframe.h"
#include "frames/chapterframe.h" #include "frames/chapterframe.h"
#include "frames/tableofcontentsframe.h" #include "frames/tableofcontentsframe.h"
#include "frames/podcastframe.h"
using namespace TagLib; using namespace TagLib;
using namespace ID3v2; using namespace ID3v2;
namespace
{
void updateGenre(TextIdentificationFrame *frame)
{
StringList fields = frame->fieldList();
StringList newfields;
for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
String s = *it;
int end = s.find(")");
if(s.startsWith("(") && end > 0) {
// "(12)Genre"
String text = s.substr(end + 1);
bool ok;
int number = s.substr(1, end - 1).toInt(&ok);
if(ok && number >= 0 && number <= 255 && !(ID3v1::genre(number) == text))
newfields.append(s.substr(1, end - 1));
if(!text.isEmpty())
newfields.append(text);
}
else {
// "Genre" or "12"
newfields.append(s);
}
}
if(newfields.isEmpty())
fields.append(String());
frame->setText(newfields);
}
}
class FrameFactory::FrameFactoryPrivate class FrameFactory::FrameFactoryPrivate
{ {
public: public:
@ -83,10 +115,10 @@ FrameFactory *FrameFactory::instance()
Frame *FrameFactory::createFrame(const ByteVector &data, bool synchSafeInts) const Frame *FrameFactory::createFrame(const ByteVector &data, bool synchSafeInts) const
{ {
return createFrame(data, uint(synchSafeInts ? 4 : 3)); return createFrame(data, static_cast<unsigned int>(synchSafeInts ? 4 : 3));
} }
Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const Frame *FrameFactory::createFrame(const ByteVector &data, unsigned int version) const
{ {
Header tagHeader; Header tagHeader;
tagHeader.setMajorVersion(version); tagHeader.setMajorVersion(version);
@ -96,7 +128,7 @@ Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const
Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const
{ {
ByteVector data = origData; ByteVector data = origData;
uint version = tagHeader->majorVersion(); unsigned int version = tagHeader->majorVersion();
Frame::Header *header = new Frame::Header(data, version); Frame::Header *header = new Frame::Header(data, version);
ByteVector frameID = header->frameID(); ByteVector frameID = header->frameID();
@ -104,7 +136,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
// characters. Also make sure that there is data in the frame. // characters. Also make sure that there is data in the frame.
if(frameID.size() != (version < 3 ? 3 : 4) || if(frameID.size() != (version < 3 ? 3 : 4) ||
header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) || header->frameSize() <= static_cast<unsigned int>(header->dataLengthIndicator() ? 4 : 0) ||
header->frameSize() > data.size()) header->frameSize() > data.size())
{ {
delete header; delete header;
@ -140,12 +172,11 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
// TagLib doesn't mess with encrypted frames, so just treat them // TagLib doesn't mess with encrypted frames, so just treat them
// as unknown frames. // as unknown frames.
#if !defined(HAVE_ZLIB) || HAVE_ZLIB == 0 if(!zlib::isAvailable() && header->compression()) {
if(header->compression()) {
debug("Compressed frames are currently not supported."); debug("Compressed frames are currently not supported.");
return new UnknownFrame(data, header); return new UnknownFrame(data, header);
} }
#endif
if(header->encryption()) { if(header->encryption()) {
debug("Encrypted frames are currently not supported."); debug("Encrypted frames are currently not supported.");
return new UnknownFrame(data, header); return new UnknownFrame(data, header);
@ -167,7 +198,8 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
// Text Identification (frames 4.2) // Text Identification (frames 4.2)
if(frameID.startsWith("T")) { // Apple proprietary WFED (Podcast URL) is in fact a text frame.
if(frameID.startsWith("T") || frameID == "WFED") {
TextIdentificationFrame *f = frameID != "TXXX" TextIdentificationFrame *f = frameID != "TXXX"
? new TextIdentificationFrame(data, header) ? new TextIdentificationFrame(data, header)
@ -287,6 +319,11 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
if(frameID == "CTOC") if(frameID == "CTOC")
return new TableOfContentsFrame(tagHeader, data, header); return new TableOfContentsFrame(tagHeader, data, header);
// Apple proprietary PCST (Podcast)
if(frameID == "PCST")
return new PodcastFrame(data, header);
return new UnknownFrame(data, header); return new UnknownFrame(data, header);
} }
@ -296,18 +333,27 @@ void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const
tag->frameList("TDRC").size() == 1 && tag->frameList("TDRC").size() == 1 &&
tag->frameList("TDAT").size() == 1) tag->frameList("TDAT").size() == 1)
{ {
TextIdentificationFrame *trdc = TextIdentificationFrame *tdrc =
static_cast<TextIdentificationFrame *>(tag->frameList("TDRC").front()); static_cast<TextIdentificationFrame *>(tag->frameList("TDRC").front());
UnknownFrame *tdat = UnknownFrame *tdat = static_cast<UnknownFrame *>(tag->frameList("TDAT").front());
static_cast<UnknownFrame *>(tag->frameList("TDAT").front());
if(trdc->fieldList().size() == 1 && if(tdrc->fieldList().size() == 1 &&
trdc->fieldList().front().size() == 4 && tdrc->fieldList().front().size() == 4 &&
tdat->data().size() >= 5) tdat->data().size() >= 5)
{ {
String date(tdat->data().mid(1), String::Type(tdat->data()[0])); String date(tdat->data().mid(1), String::Type(tdat->data()[0]));
if(date.length() == 4) if(date.length() == 4) {
trdc->setText(trdc->toString() + '-' + date.substr(2, 2) + '-' + date.substr(0, 2)); tdrc->setText(tdrc->toString() + '-' + date.substr(2, 2) + '-' + date.substr(0, 2));
if(tag->frameList("TIME").size() == 1) {
UnknownFrame *timeframe = static_cast<UnknownFrame *>(tag->frameList("TIME").front());
if(timeframe->data().size() >= 5) {
String time(timeframe->data().mid(1), String::Type(timeframe->data()[0]));
if(time.length() == 4) {
tdrc->setText(tdrc->toString() + 'T' + time.substr(0, 2) + ':' + time.substr(2, 2));
}
}
}
}
} }
} }
} }
@ -327,9 +373,9 @@ void FrameFactory::setDefaultTextEncoding(String::Type encoding)
// protected members // protected members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
FrameFactory::FrameFactory() FrameFactory::FrameFactory() :
d(new FrameFactoryPrivate())
{ {
d = new FrameFactoryPrivate;
} }
FrameFactory::~FrameFactory() FrameFactory::~FrameFactory()
@ -337,9 +383,94 @@ FrameFactory::~FrameFactory()
delete d; delete d;
} }
namespace
{
// Frame conversion table ID3v2.2 -> 2.4
const char *frameConversion2[][2] = {
{ "BUF", "RBUF" },
{ "CNT", "PCNT" },
{ "COM", "COMM" },
{ "CRA", "AENC" },
{ "ETC", "ETCO" },
{ "GEO", "GEOB" },
{ "IPL", "TIPL" },
{ "MCI", "MCDI" },
{ "MLL", "MLLT" },
{ "POP", "POPM" },
{ "REV", "RVRB" },
{ "SLT", "SYLT" },
{ "STC", "SYTC" },
{ "TAL", "TALB" },
{ "TBP", "TBPM" },
{ "TCM", "TCOM" },
{ "TCO", "TCON" },
{ "TCP", "TCMP" },
{ "TCR", "TCOP" },
{ "TDY", "TDLY" },
{ "TEN", "TENC" },
{ "TFT", "TFLT" },
{ "TKE", "TKEY" },
{ "TLA", "TLAN" },
{ "TLE", "TLEN" },
{ "TMT", "TMED" },
{ "TOA", "TOAL" },
{ "TOF", "TOFN" },
{ "TOL", "TOLY" },
{ "TOR", "TDOR" },
{ "TOT", "TOAL" },
{ "TP1", "TPE1" },
{ "TP2", "TPE2" },
{ "TP3", "TPE3" },
{ "TP4", "TPE4" },
{ "TPA", "TPOS" },
{ "TPB", "TPUB" },
{ "TRC", "TSRC" },
{ "TRD", "TDRC" },
{ "TRK", "TRCK" },
{ "TS2", "TSO2" },
{ "TSA", "TSOA" },
{ "TSC", "TSOC" },
{ "TSP", "TSOP" },
{ "TSS", "TSSE" },
{ "TST", "TSOT" },
{ "TT1", "TIT1" },
{ "TT2", "TIT2" },
{ "TT3", "TIT3" },
{ "TXT", "TOLY" },
{ "TXX", "TXXX" },
{ "TYE", "TDRC" },
{ "UFI", "UFID" },
{ "ULT", "USLT" },
{ "WAF", "WOAF" },
{ "WAR", "WOAR" },
{ "WAS", "WOAS" },
{ "WCM", "WCOM" },
{ "WCP", "WCOP" },
{ "WPB", "WPUB" },
{ "WXX", "WXXX" },
// Apple iTunes nonstandard frames
{ "PCS", "PCST" },
{ "TCT", "TCAT" },
{ "TDR", "TDRL" },
{ "TDS", "TDES" },
{ "TID", "TGID" },
{ "WFD", "WFED" },
};
const size_t frameConversion2Size = sizeof(frameConversion2) / sizeof(frameConversion2[0]);
// Frame conversion table ID3v2.3 -> 2.4
const char *frameConversion3[][2] = {
{ "TORY", "TDOR" },
{ "TYER", "TDRC" },
{ "IPLS", "TIPL" },
};
const size_t frameConversion3Size = sizeof(frameConversion3) / sizeof(frameConversion3[0]);
}
bool FrameFactory::updateFrame(Frame::Header *header) const bool FrameFactory::updateFrame(Frame::Header *header) const
{ {
TagLib::ByteVector frameID = header->frameID(); const ByteVector frameID = header->frameID();
switch(header->version()) { switch(header->version()) {
@ -361,67 +492,12 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
// ID3v2.2 only used 3 bytes for the frame ID, so we need to convert all of // ID3v2.2 only used 3 bytes for the frame ID, so we need to convert all of
// the frames to their 4 byte ID3v2.4 equivalent. // the frames to their 4 byte ID3v2.4 equivalent.
convertFrame("BUF", "RBUF", header); for(size_t i = 0; i < frameConversion2Size; ++i) {
convertFrame("CNT", "PCNT", header); if(frameID == frameConversion2[i][0]) {
convertFrame("COM", "COMM", header); header->setFrameID(frameConversion2[i][1]);
convertFrame("CRA", "AENC", header); break;
convertFrame("ETC", "ETCO", header); }
convertFrame("GEO", "GEOB", header); }
convertFrame("IPL", "TIPL", header);
convertFrame("MCI", "MCDI", header);
convertFrame("MLL", "MLLT", header);
convertFrame("POP", "POPM", header);
convertFrame("REV", "RVRB", header);
convertFrame("SLT", "SYLT", header);
convertFrame("STC", "SYTC", header);
convertFrame("TAL", "TALB", header);
convertFrame("TBP", "TBPM", header);
convertFrame("TCM", "TCOM", header);
convertFrame("TCO", "TCON", header);
convertFrame("TCP", "TCMP", header);
convertFrame("TCR", "TCOP", header);
convertFrame("TDY", "TDLY", header);
convertFrame("TEN", "TENC", header);
convertFrame("TFT", "TFLT", header);
convertFrame("TKE", "TKEY", header);
convertFrame("TLA", "TLAN", header);
convertFrame("TLE", "TLEN", header);
convertFrame("TMT", "TMED", header);
convertFrame("TOA", "TOAL", header);
convertFrame("TOF", "TOFN", header);
convertFrame("TOL", "TOLY", header);
convertFrame("TOR", "TDOR", header);
convertFrame("TOT", "TOAL", header);
convertFrame("TP1", "TPE1", header);
convertFrame("TP2", "TPE2", header);
convertFrame("TP3", "TPE3", header);
convertFrame("TP4", "TPE4", header);
convertFrame("TPA", "TPOS", header);
convertFrame("TPB", "TPUB", header);
convertFrame("TRC", "TSRC", header);
convertFrame("TRD", "TDRC", header);
convertFrame("TRK", "TRCK", header);
convertFrame("TS2", "TSO2", header);
convertFrame("TSA", "TSOA", header);
convertFrame("TSC", "TSOC", header);
convertFrame("TSP", "TSOP", header);
convertFrame("TSS", "TSSE", header);
convertFrame("TST", "TSOT", header);
convertFrame("TT1", "TIT1", header);
convertFrame("TT2", "TIT2", header);
convertFrame("TT3", "TIT3", header);
convertFrame("TXT", "TOLY", header);
convertFrame("TXX", "TXXX", header);
convertFrame("TYE", "TDRC", header);
convertFrame("UFI", "UFID", header);
convertFrame("ULT", "USLT", header);
convertFrame("WAF", "WOAF", header);
convertFrame("WAR", "WOAR", header);
convertFrame("WAS", "WOAS", header);
convertFrame("WCM", "WCOM", header);
convertFrame("WCP", "WCOP", header);
convertFrame("WPB", "WPUB", header);
convertFrame("WXX", "WXXX", header);
break; break;
} }
@ -440,9 +516,12 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
return false; return false;
} }
convertFrame("TORY", "TDOR", header); for(size_t i = 0; i < frameConversion3Size; ++i) {
convertFrame("TYER", "TDRC", header); if(frameID == frameConversion3[i][0]) {
convertFrame("IPLS", "TIPL", header); header->setFrameID(frameConversion3[i][1]);
break;
}
}
break; break;
} }
@ -452,57 +531,11 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
// This should catch a typo that existed in TagLib up to and including // This should catch a typo that existed in TagLib up to and including
// version 1.1 where TRDC was used for the year rather than TDRC. // version 1.1 where TRDC was used for the year rather than TDRC.
convertFrame("TRDC", "TDRC", header); if(frameID == "TRDC")
header->setFrameID("TDRC");
break; break;
} }
return true; return true;
} }
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FrameFactory::convertFrame(const char *from, const char *to,
Frame::Header *header) const
{
if(header->frameID() != from)
return;
// debug("ID3v2.4 no longer supports the frame type " + String(from) + " It has" +
// "been converted to the type " + String(to) + ".");
header->setFrameID(to);
}
void FrameFactory::updateGenre(TextIdentificationFrame *frame) const
{
StringList fields = frame->fieldList();
StringList newfields;
for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
String s = *it;
int end = s.find(")");
if(s.startsWith("(") && end > 0) {
// "(12)Genre"
String text = s.substr(end + 1);
bool ok;
int number = s.substr(1, end - 1).toInt(&ok);
if(ok && number >= 0 && number <= 255 && !(ID3v1::genre(number) == text))
newfields.append(s.substr(1, end - 1));
if(!text.isEmpty())
newfields.append(text);
}
else {
// "Genre" or "12"
newfields.append(s);
}
}
if(newfields.isEmpty())
fields.append(String::null);
frame->setText(newfields);
}

View File

@ -47,9 +47,9 @@ namespace TagLib {
* Reimplementing this factory is the key to adding support for frame types * Reimplementing this factory is the key to adding support for frame types
* not directly supported by TagLib to your application. To do so you would * not directly supported by TagLib to your application. To do so you would
* subclass this factory reimplement createFrame(). Then by setting your * subclass this factory reimplement createFrame(). Then by setting your
* factory to be the default factory in ID3v2::Tag constructor or with * factory to be the default factory in ID3v2::Tag constructor you can
* MPEG::File::setID3v2FrameFactory() you can implement behavior that will * implement behavior that will allow for new ID3v2::Frame subclasses (also
* allow for new ID3v2::Frame subclasses (also provided by you) to be used. * provided by you) to be used.
* *
* This implements both <i>abstract factory</i> and <i>singleton</i> patterns * This implements both <i>abstract factory</i> and <i>singleton</i> patterns
* of which more information is available on the web and in software design * of which more information is available on the web and in software design
@ -84,7 +84,7 @@ namespace TagLib {
* \deprecated Please use the method below that accepts a ID3v2::Header * \deprecated Please use the method below that accepts a ID3v2::Header
* instance in new code. * instance in new code.
*/ */
Frame *createFrame(const ByteVector &data, uint version = 4) const; Frame *createFrame(const ByteVector &data, unsigned int version = 4) const;
/*! /*!
* Create a frame based on \a data. \a tagHeader should be a valid * Create a frame based on \a data. \a tagHeader should be a valid
@ -152,16 +152,6 @@ namespace TagLib {
FrameFactory(const FrameFactory &); FrameFactory(const FrameFactory &);
FrameFactory &operator=(const FrameFactory &); FrameFactory &operator=(const FrameFactory &);
/*!
* This method is used internally to convert a frame from ID \a from to ID
* \a to. If the frame matches the \a from pattern and converts the frame
* ID in the \a header or simply does nothing if the frame ID does not match.
*/
void convertFrame(const char *from, const char *to,
Frame::Header *header) const;
void updateGenre(TextIdentificationFrame *frame) const;
static FrameFactory factory; static FrameFactory factory;
class FrameFactoryPrivate; class FrameFactoryPrivate;

View File

@ -39,7 +39,8 @@ using namespace ID3v2;
class Header::HeaderPrivate class Header::HeaderPrivate
{ {
public: public:
HeaderPrivate() : majorVersion(4), HeaderPrivate() :
majorVersion(4),
revisionNumber(0), revisionNumber(0),
unsynchronisation(false), unsynchronisation(false),
extendedHeader(false), extendedHeader(false),
@ -47,28 +48,24 @@ public:
footerPresent(false), footerPresent(false),
tagSize(0) {} tagSize(0) {}
~HeaderPrivate() {} unsigned int majorVersion;
unsigned int revisionNumber;
uint majorVersion;
uint revisionNumber;
bool unsynchronisation; bool unsynchronisation;
bool extendedHeader; bool extendedHeader;
bool experimentalIndicator; bool experimentalIndicator;
bool footerPresent; bool footerPresent;
uint tagSize; unsigned int tagSize;
static const uint size = 10;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// static members // static members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
TagLib::uint Header::size() unsigned int Header::size()
{ {
return HeaderPrivate::size; return 10;
} }
ByteVector Header::fileIdentifier() ByteVector Header::fileIdentifier()
@ -80,14 +77,14 @@ ByteVector Header::fileIdentifier()
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Header::Header() Header::Header() :
d(new HeaderPrivate())
{ {
d = new HeaderPrivate;
} }
Header::Header(const ByteVector &data) Header::Header(const ByteVector &data) :
d(new HeaderPrivate())
{ {
d = new HeaderPrivate;
parse(data); parse(data);
} }
@ -96,17 +93,17 @@ Header::~Header()
delete d; delete d;
} }
TagLib::uint Header::majorVersion() const unsigned int Header::majorVersion() const
{ {
return d->majorVersion; return d->majorVersion;
} }
void Header::setMajorVersion(TagLib::uint version) void Header::setMajorVersion(unsigned int version)
{ {
d->majorVersion = version; d->majorVersion = version;
} }
TagLib::uint Header::revisionNumber() const unsigned int Header::revisionNumber() const
{ {
return d->revisionNumber; return d->revisionNumber;
} }
@ -131,20 +128,20 @@ bool Header::footerPresent() const
return d->footerPresent; return d->footerPresent;
} }
TagLib::uint Header::tagSize() const unsigned int Header::tagSize() const
{ {
return d->tagSize; return d->tagSize;
} }
TagLib::uint Header::completeTagSize() const unsigned int Header::completeTagSize() const
{ {
if(d->footerPresent) if(d->footerPresent)
return d->tagSize + d->size + Footer::size(); return d->tagSize + size() + Footer::size();
else else
return d->tagSize + d->size; return d->tagSize + size();
} }
void Header::setTagSize(uint s) void Header::setTagSize(unsigned int s)
{ {
d->tagSize = s; d->tagSize = s;
} }
@ -199,7 +196,6 @@ void Header::parse(const ByteVector &data)
if(data.size() < size()) if(data.size() < size())
return; return;
// do some sanity checking -- even in ID3v2.3.0 and less the tag size is a // do some sanity checking -- even in ID3v2.3.0 and less the tag size is a
// synch-safe integer, so all bytes must be less than 128. If this is not // synch-safe integer, so all bytes must be less than 128. If this is not
// true then this is an invalid tag. // true then this is an invalid tag.
@ -216,7 +212,7 @@ void Header::parse(const ByteVector &data)
} }
for(ByteVector::ConstIterator it = sizeData.begin(); it != sizeData.end(); it++) { for(ByteVector::ConstIterator it = sizeData.begin(); it != sizeData.end(); it++) {
if(uchar(*it) >= 128) { if(static_cast<unsigned char>(*it) >= 128) {
d->tagSize = 0; d->tagSize = 0;
debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128."); debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128.");
return; return;

View File

@ -67,7 +67,7 @@ namespace TagLib {
* Returns the major version number. (Note: This is the 4, not the 2 in * Returns the major version number. (Note: This is the 4, not the 2 in
* ID3v2.4.0. The 2 is implied.) * ID3v2.4.0. The 2 is implied.)
*/ */
uint majorVersion() const; unsigned int majorVersion() const;
/*! /*!
* Set the the major version number to \a version. (Note: This is * Set the the major version number to \a version. (Note: This is
@ -78,13 +78,13 @@ namespace TagLib {
* version which is written and in general should not be called by API * version which is written and in general should not be called by API
* users. * users.
*/ */
void setMajorVersion(uint version); void setMajorVersion(unsigned int version);
/*! /*!
* Returns the revision number. (Note: This is the 0, not the 4 in * Returns the revision number. (Note: This is the 0, not the 4 in
* ID3v2.4.0. The 2 is implied.) * ID3v2.4.0. The 2 is implied.)
*/ */
uint revisionNumber() const; unsigned int revisionNumber() const;
/*! /*!
* Returns true if unsynchronisation has been applied to all frames. * Returns true if unsynchronisation has been applied to all frames.
@ -116,7 +116,7 @@ namespace TagLib {
* *
* \see completeTagSize() * \see completeTagSize()
*/ */
uint tagSize() const; unsigned int tagSize() const;
/*! /*!
* Returns the tag size, including the header and, if present, the footer * Returns the tag size, including the header and, if present, the footer
@ -124,18 +124,18 @@ namespace TagLib {
* *
* \see tagSize() * \see tagSize()
*/ */
uint completeTagSize() const; unsigned int completeTagSize() const;
/*! /*!
* Set the tag size to \a s. * Set the tag size to \a s.
* \see tagSize() * \see tagSize()
*/ */
void setTagSize(uint s); void setTagSize(unsigned int s);
/*! /*!
* Returns the size of the header. Presently this is always 10 bytes. * Returns the size of the header. Presently this is always 10 bytes.
*/ */
static uint size(); static unsigned int size();
/*! /*!
* Returns the string used to identify and ID3v2 tag inside of a file. * Returns the string used to identify and ID3v2 tag inside of a file.

View File

@ -30,9 +30,9 @@
using namespace TagLib; using namespace TagLib;
using namespace ID3v2; using namespace ID3v2;
TagLib::uint SynchData::toUInt(const ByteVector &data) unsigned int SynchData::toUInt(const ByteVector &data)
{ {
uint sum = 0; unsigned int sum = 0;
bool notSynchSafe = false; bool notSynchSafe = false;
int last = data.size() > 4 ? 3 : data.size() - 1; int last = data.size() > 4 ? 3 : data.size() - 1;
@ -62,23 +62,37 @@ TagLib::uint SynchData::toUInt(const ByteVector &data)
return sum; return sum;
} }
ByteVector SynchData::fromUInt(uint value) ByteVector SynchData::fromUInt(unsigned int value)
{ {
ByteVector v(4, 0); ByteVector v(4, 0);
for(int i = 0; i < 4; i++) for(int i = 0; i < 4; i++)
v[i] = uchar(value >> ((3 - i) * 7) & 0x7f); v[i] = static_cast<unsigned char>(value >> ((3 - i) * 7) & 0x7f);
return v; return v;
} }
ByteVector SynchData::decode(const ByteVector &data) ByteVector SynchData::decode(const ByteVector &data)
{ {
ByteVector result = data; // We have this optimized method instead of using ByteVector::replace(),
// since it makes a great difference when decoding huge unsynchronized frames.
ByteVector pattern(2, char(0)); ByteVector result(data.size());
pattern[0] = '\xFF';
pattern[1] = '\x00';
return result.replace(pattern, '\xFF'); ByteVector::ConstIterator src = data.begin();
ByteVector::Iterator dst = result.begin();
while(src < data.end() - 1) {
*dst++ = *src++;
if(*(src - 1) == '\xff' && *src == '\x00')
src++;
}
if(src < data.end())
*dst++ = *src++;
result.resize(static_cast<unsigned int>(dst - result.begin()));
return result;
} }

View File

@ -51,12 +51,12 @@ namespace TagLib {
* <a href="id3v2-structure.html#6.2">6.2</a>). The default \a length of * <a href="id3v2-structure.html#6.2">6.2</a>). The default \a length of
* 4 is used if another value is not specified. * 4 is used if another value is not specified.
*/ */
TAGLIB_EXPORT uint toUInt(const ByteVector &data); TAGLIB_EXPORT unsigned int toUInt(const ByteVector &data);
/*! /*!
* Returns a 4 byte (32 bit) synchsafe integer based on \a value. * Returns a 4 byte (32 bit) synchsafe integer based on \a value.
*/ */
TAGLIB_EXPORT ByteVector fromUInt(uint value); TAGLIB_EXPORT ByteVector fromUInt(unsigned int value);
/*! /*!
* Convert the data from unsynchronized data to its original format. * Convert the data from unsynchronized data to its original format.

View File

@ -23,21 +23,19 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifdef HAVE_CONFIG_H #include <algorithm>
#include "config.h"
#endif
#include "tfile.h" #include <tfile.h>
#include <tbytevector.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include "id3v2tag.h" #include "id3v2tag.h"
#include "id3v2header.h" #include "id3v2header.h"
#include "id3v2extendedheader.h" #include "id3v2extendedheader.h"
#include "id3v2footer.h" #include "id3v2footer.h"
#include "id3v2synchdata.h" #include "id3v2synchdata.h"
#include "tbytevector.h"
#include "id3v1genres.h" #include "id3v1genres.h"
#include "tpropertymap.h"
#include "tdebug.h"
#include "frames/textidentificationframe.h" #include "frames/textidentificationframe.h"
#include "frames/commentsframe.h" #include "frames/commentsframe.h"
@ -49,43 +47,46 @@
using namespace TagLib; using namespace TagLib;
using namespace ID3v2; using namespace ID3v2;
namespace
{
const ID3v2::Latin1StringHandler defaultStringHandler;
const ID3v2::Latin1StringHandler *stringHandler = &defaultStringHandler;
const long MinPaddingSize = 1024;
const long MaxPaddingSize = 1024 * 1024;
}
class ID3v2::Tag::TagPrivate class ID3v2::Tag::TagPrivate
{ {
public: public:
TagPrivate() : file(0), tagOffset(-1), extendedHeader(0), footer(0), paddingSize(0) TagPrivate() :
file(0),
tagOffset(0),
extendedHeader(0),
footer(0)
{ {
frameList.setAutoDelete(true); frameList.setAutoDelete(true);
} }
~TagPrivate() ~TagPrivate()
{ {
delete extendedHeader; delete extendedHeader;
delete footer; delete footer;
} }
const FrameFactory *factory;
File *file; File *file;
long tagOffset; long tagOffset;
const FrameFactory *factory;
Header header; Header header;
ExtendedHeader *extendedHeader; ExtendedHeader *extendedHeader;
Footer *footer; Footer *footer;
int paddingSize;
FrameListMap frameListMap; FrameListMap frameListMap;
FrameList frameList; FrameList frameList;
static const Latin1StringHandler *stringHandler;
}; };
static const Latin1StringHandler defaultStringHandler;
const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler;
namespace
{
const TagLib::uint DefaultPaddingSize = 1024;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// StringHandler implementation // StringHandler implementation
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -107,20 +108,20 @@ String Latin1StringHandler::parse(const ByteVector &data) const
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ID3v2::Tag::Tag() : TagLib::Tag() ID3v2::Tag::Tag() :
TagLib::Tag(),
d(new TagPrivate())
{ {
d = new TagPrivate;
d->factory = FrameFactory::instance(); d->factory = FrameFactory::instance();
} }
ID3v2::Tag::Tag(File *file, long tagOffset, const FrameFactory *factory) : ID3v2::Tag::Tag(File *file, long tagOffset, const FrameFactory *factory) :
TagLib::Tag() TagLib::Tag(),
d(new TagPrivate())
{ {
d = new TagPrivate; d->factory = factory;
d->file = file; d->file = file;
d->tagOffset = tagOffset; d->tagOffset = tagOffset;
d->factory = factory;
read(); read();
} }
@ -130,26 +131,25 @@ ID3v2::Tag::~Tag()
delete d; delete d;
} }
String ID3v2::Tag::title() const String ID3v2::Tag::title() const
{ {
if(!d->frameListMap["TIT2"].isEmpty()) if(!d->frameListMap["TIT2"].isEmpty())
return d->frameListMap["TIT2"].front()->toString(); return d->frameListMap["TIT2"].front()->toString();
return String::null; return String();
} }
String ID3v2::Tag::artist() const String ID3v2::Tag::artist() const
{ {
if(!d->frameListMap["TPE1"].isEmpty()) if(!d->frameListMap["TPE1"].isEmpty())
return d->frameListMap["TPE1"].front()->toString(); return d->frameListMap["TPE1"].front()->toString();
return String::null; return String();
} }
String ID3v2::Tag::album() const String ID3v2::Tag::album() const
{ {
if(!d->frameListMap["TALB"].isEmpty()) if(!d->frameListMap["TALB"].isEmpty())
return d->frameListMap["TALB"].front()->toString(); return d->frameListMap["TALB"].front()->toString();
return String::null; return String();
} }
String ID3v2::Tag::comment() const String ID3v2::Tag::comment() const
@ -157,7 +157,7 @@ String ID3v2::Tag::comment() const
const FrameList &comments = d->frameListMap["COMM"]; const FrameList &comments = d->frameListMap["COMM"];
if(comments.isEmpty()) if(comments.isEmpty())
return String::null; return String();
for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it)
{ {
@ -179,7 +179,7 @@ String ID3v2::Tag::genre() const
if(d->frameListMap["TCON"].isEmpty() || if(d->frameListMap["TCON"].isEmpty() ||
!dynamic_cast<TextIdentificationFrame *>(d->frameListMap["TCON"].front())) !dynamic_cast<TextIdentificationFrame *>(d->frameListMap["TCON"].front()))
{ {
return String::null; return String();
} }
// ID3v2.4 lists genres as the fields in its frames field list. If the field // ID3v2.4 lists genres as the fields in its frames field list. If the field
@ -213,14 +213,14 @@ String ID3v2::Tag::genre() const
return genres.toString(); return genres.toString();
} }
TagLib::uint ID3v2::Tag::year() const unsigned int ID3v2::Tag::year() const
{ {
if(!d->frameListMap["TDRC"].isEmpty()) if(!d->frameListMap["TDRC"].isEmpty())
return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt(); return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt();
return 0; return 0;
} }
TagLib::uint ID3v2::Tag::track() const unsigned int ID3v2::Tag::track() const
{ {
if(!d->frameListMap["TRCK"].isEmpty()) if(!d->frameListMap["TRCK"].isEmpty())
return d->frameListMap["TRCK"].front()->toString().toInt(); return d->frameListMap["TRCK"].front()->toString().toInt();
@ -284,7 +284,7 @@ void ID3v2::Tag::setGenre(const String &s)
#endif #endif
} }
void ID3v2::Tag::setYear(uint i) void ID3v2::Tag::setYear(unsigned int i)
{ {
if(i <= 0) { if(i <= 0) {
removeFrames("TDRC"); removeFrames("TDRC");
@ -293,7 +293,7 @@ void ID3v2::Tag::setYear(uint i)
setTextFrame("TDRC", String::number(i)); setTextFrame("TDRC", String::number(i));
} }
void ID3v2::Tag::setTrack(uint i) void ID3v2::Tag::setTrack(unsigned int i)
{ {
if(i <= 0) { if(i <= 0) {
removeFrames("TRCK"); removeFrames("TRCK");
@ -466,10 +466,18 @@ ByteVector ID3v2::Tag::render() const
void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
{ {
#ifdef NO_ITUNES_HACKS
const char *unsupportedFrames[] = { const char *unsupportedFrames[] = {
"ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG", "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
"TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0 "TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0
}; };
#else
// iTunes writes and reads TSOA, TSOT, TSOP to ID3v2.3.
const char *unsupportedFrames[] = {
"ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
"TMOO", "TPRO", "TSST", 0
};
#endif
ID3v2::TextIdentificationFrame *frameTDOR = 0; ID3v2::TextIdentificationFrame *frameTDOR = 0;
ID3v2::TextIdentificationFrame *frameTDRC = 0; ID3v2::TextIdentificationFrame *frameTDRC = 0;
ID3v2::TextIdentificationFrame *frameTIPL = 0; ID3v2::TextIdentificationFrame *frameTIPL = 0;
@ -540,14 +548,14 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
StringList people; StringList people;
if(frameTMCL) { if(frameTMCL) {
StringList v24People = frameTMCL->fieldList(); StringList v24People = frameTMCL->fieldList();
for(uint i = 0; i + 1 < v24People.size(); i += 2) { for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) {
people.append(v24People[i]); people.append(v24People[i]);
people.append(v24People[i+1]); people.append(v24People[i+1]);
} }
} }
if(frameTIPL) { if(frameTIPL) {
StringList v24People = frameTIPL->fieldList(); StringList v24People = frameTIPL->fieldList();
for(uint i = 0; i + 1 < v24People.size(); i += 2) { for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) {
people.append(v24People[i]); people.append(v24People[i]);
people.append(v24People[i+1]); people.append(v24People[i+1]);
} }
@ -558,7 +566,6 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
} }
} }
ByteVector ID3v2::Tag::render(int version) const ByteVector ID3v2::Tag::render(int version) const
{ {
// We need to render the "tag data" first so that we have to correct size to // We need to render the "tag data" first so that we have to correct size to
@ -566,8 +573,6 @@ ByteVector ID3v2::Tag::render(int version) const
// in ID3v2::Header::tagSize() -- includes the extended header, frames and // in ID3v2::Header::tagSize() -- includes the extended header, frames and
// padding, but does not include the tag's header or footer. // padding, but does not include the tag's header or footer.
ByteVector tagData;
if(version != 3 && version != 4) { if(version != 3 && version != 4) {
debug("Unknown ID3v2 version, using ID3v2.4"); debug("Unknown ID3v2 version, using ID3v2.4");
version = 4; version = 4;
@ -575,7 +580,7 @@ ByteVector ID3v2::Tag::render(int version) const
// TODO: Render the extended header. // TODO: Render the extended header.
// Loop through the frames rendering them and adding them to the tagData. // Downgrade the frames that ID3v2.3 doesn't support.
FrameList newFrames; FrameList newFrames;
newFrames.setAutoDelete(true); newFrames.setAutoDelete(true);
@ -588,6 +593,12 @@ ByteVector ID3v2::Tag::render(int version) const
downgradeFrames(&frameList, &newFrames); downgradeFrames(&frameList, &newFrames);
} }
// Reserve a 10-byte blank space for an ID3v2 tag header.
ByteVector tagData(Header::size(), '\0');
// Loop through the frames rendering them and adding them to the tagData.
for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) { for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) {
(*it)->header()->setVersion(version); (*it)->header()->setVersion(version);
if((*it)->header()->frameID().size() != 4) { if((*it)->header()->frameID().size() != 4) {
@ -607,42 +618,49 @@ ByteVector ID3v2::Tag::render(int version) const
} }
// Compute the amount of padding, and append that to tagData. // Compute the amount of padding, and append that to tagData.
// TODO: Should be calculated in long long in taglib2.
uint paddingSize = DefaultPaddingSize; long originalSize = d->header.tagSize();
long paddingSize = originalSize - (tagData.size() - Header::size());
if(d->file && tagData.size() < d->header.tagSize()) { if(paddingSize <= 0) {
paddingSize = d->header.tagSize() - tagData.size(); paddingSize = MinPaddingSize;
}
else {
// Padding won't increase beyond 1% of the file size or 1MB.
// Padding won't increase beyond 1% of the file size. long threshold = d->file ? d->file->length() / 100 : 0;
threshold = std::max(threshold, MinPaddingSize);
threshold = std::min(threshold, MaxPaddingSize);
if(paddingSize > DefaultPaddingSize) {
const uint threshold = d->file->length() / 100; // should be ulonglong in taglib2.
if(paddingSize > threshold) if(paddingSize > threshold)
paddingSize = DefaultPaddingSize; paddingSize = MinPaddingSize;
}
} }
tagData.append(ByteVector(paddingSize, '\0')); tagData.resize(static_cast<unsigned int>(tagData.size() + paddingSize), '\0');
// Set the version and data size. // Set the version and data size.
d->header.setMajorVersion(version); d->header.setMajorVersion(version);
d->header.setTagSize(tagData.size()); d->header.setTagSize(tagData.size() - Header::size());
// TODO: This should eventually include d->footer->render(). // TODO: This should eventually include d->footer->render().
return d->header.render() + tagData; const ByteVector headerData = d->header.render();
std::copy(headerData.begin(), headerData.end(), tagData.begin());
return tagData;
} }
Latin1StringHandler const *ID3v2::Tag::latin1StringHandler() Latin1StringHandler const *ID3v2::Tag::latin1StringHandler()
{ {
return TagPrivate::stringHandler; return stringHandler;
} }
void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler) void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
{ {
if(handler) if(handler)
TagPrivate::stringHandler = handler; stringHandler = handler;
else else
TagPrivate::stringHandler = &defaultStringHandler; stringHandler = &defaultStringHandler;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -651,18 +669,43 @@ void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
void ID3v2::Tag::read() void ID3v2::Tag::read()
{ {
if(d->file && d->file->isOpen()) { if(!d->file)
return;
if(!d->file->isOpen())
return;
d->file->seek(d->tagOffset); d->file->seek(d->tagOffset);
d->header.setData(d->file->readBlock(Header::size())); d->header.setData(d->file->readBlock(Header::size()));
// if the tag size is 0, then this is an invalid tag (tags must contain at // If the tag size is 0, then this is an invalid tag (tags must contain at
// least one frame) // least one frame)
if(d->header.tagSize() == 0) if(d->header.tagSize() != 0)
return;
parse(d->file->readBlock(d->header.tagSize())); parse(d->file->readBlock(d->header.tagSize()));
// Look for duplicate ID3v2 tags and treat them as an extra blank of this one.
// It leads to overwriting them with zero when saving the tag.
// This is a workaround for some faulty files that have duplicate ID3v2 tags.
// Unfortunately, TagLib itself may write such duplicate tags until v1.10.
unsigned int extraSize = 0;
while(true) {
d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize);
const ByteVector data = d->file->readBlock(Header::size());
if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier()))
break;
extraSize += Header(data).completeTagSize();
}
if(extraSize != 0) {
debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found.");
d->header.setTagSize(d->header.tagSize() + extraSize);
} }
} }
@ -673,8 +716,8 @@ void ID3v2::Tag::parse(const ByteVector &origData)
if(d->header.unsynchronisation() && d->header.majorVersion() <= 3) if(d->header.unsynchronisation() && d->header.majorVersion() <= 3)
data = SynchData::decode(data); data = SynchData::decode(data);
uint frameDataPosition = 0; unsigned int frameDataPosition = 0;
uint frameDataLength = data.size(); unsigned int frameDataLength = data.size();
// check for extended header // check for extended header
@ -710,7 +753,6 @@ void ID3v2::Tag::parse(const ByteVector &origData)
debug("Padding *and* a footer found. This is not allowed by the spec."); debug("Padding *and* a footer found. This is not allowed by the spec.");
} }
d->paddingSize = frameDataLength - frameDataPosition;
break; break;
} }

View File

@ -169,16 +169,16 @@ namespace TagLib {
virtual String album() const; virtual String album() const;
virtual String comment() const; virtual String comment() const;
virtual String genre() const; virtual String genre() const;
virtual uint year() const; virtual unsigned int year() const;
virtual uint track() const; virtual unsigned int track() const;
virtual void setTitle(const String &s); virtual void setTitle(const String &s);
virtual void setArtist(const String &s); virtual void setArtist(const String &s);
virtual void setAlbum(const String &s); virtual void setAlbum(const String &s);
virtual void setComment(const String &s); virtual void setComment(const String &s);
virtual void setGenre(const String &s); virtual void setGenre(const String &s);
virtual void setYear(uint i); virtual void setYear(unsigned int i);
virtual void setTrack(uint i); virtual void setTrack(unsigned int i);
virtual bool isEmpty() const; virtual bool isEmpty() const;

View File

@ -24,6 +24,7 @@
***************************************************************************/ ***************************************************************************/
#include <tagunion.h> #include <tagunion.h>
#include <tagutils.h>
#include <id3v2tag.h> #include <id3v2tag.h>
#include <id3v2header.h> #include <id3v2header.h>
#include <id3v1tag.h> #include <id3v1tag.h>
@ -33,29 +34,11 @@
#include "mpegfile.h" #include "mpegfile.h"
#include "mpegheader.h" #include "mpegheader.h"
#include "mpegutils.h"
#include "tpropertymap.h" #include "tpropertymap.h"
using namespace TagLib; using namespace TagLib;
namespace
{
/*!
* MPEG frames can be recognized by the bit pattern 11111111 111, so the
* first byte is easy to check for, however checking to see if the second byte
* starts with \e 111 is a bit more tricky, hence these functions.
*/
inline bool firstSyncByte(uchar byte)
{
return (byte == 0xFF);
}
inline bool secondSynchByte(uchar byte)
{
return ((byte & 0xE0) == 0xE0);
}
}
namespace namespace
{ {
enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 }; enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
@ -64,20 +47,14 @@ namespace
class MPEG::File::FilePrivate class MPEG::File::FilePrivate
{ {
public: public:
FilePrivate(ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) : FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
ID3v2FrameFactory(frameFactory), ID3v2FrameFactory(frameFactory),
ID3v2Location(-1), ID3v2Location(-1),
ID3v2OriginalSize(0), ID3v2OriginalSize(0),
APELocation(-1), APELocation(-1),
APEFooterLocation(-1),
APEOriginalSize(0), APEOriginalSize(0),
ID3v1Location(-1), ID3v1Location(-1),
hasID3v2(false), properties(0) {}
hasID3v1(false),
hasAPE(false),
properties(0)
{
}
~FilePrivate() ~FilePrivate()
{ {
@ -87,23 +64,15 @@ public:
const ID3v2::FrameFactory *ID3v2FrameFactory; const ID3v2::FrameFactory *ID3v2FrameFactory;
long ID3v2Location; long ID3v2Location;
uint ID3v2OriginalSize; long ID3v2OriginalSize;
long APELocation; long APELocation;
long APEFooterLocation; long APEOriginalSize;
uint APEOriginalSize;
long ID3v1Location; long ID3v1Location;
TagUnion tag; TagUnion tag;
// These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets.
bool hasID3v2;
bool hasID3v1;
bool hasAPE;
Properties *properties; Properties *properties;
}; };
@ -149,33 +118,22 @@ TagLib::Tag *MPEG::File::tag() const
PropertyMap MPEG::File::properties() const PropertyMap MPEG::File::properties() const
{ {
// once Tag::properties() is virtual, this case distinction could actually be done return d->tag.properties();
// within TagUnion.
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
if(d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
return PropertyMap();
} }
void MPEG::File::removeUnsupportedProperties(const StringList &properties) void MPEG::File::removeUnsupportedProperties(const StringList &properties)
{ {
if(d->hasID3v2) d->tag.removeUnsupportedProperties(properties);
d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(properties);
else if(d->hasAPE)
d->tag.access<APE::Tag>(APEIndex, false)->removeUnsupportedProperties(properties);
else if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->removeUnsupportedProperties(properties);
} }
PropertyMap MPEG::File::setProperties(const PropertyMap &properties) PropertyMap MPEG::File::setProperties(const PropertyMap &properties)
{ {
if(d->hasID3v1)
// update ID3v1 tag if it exists, but ignore the return value // update ID3v1 tag if it exists, but ignore the return value
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties); if(ID3v1Tag())
ID3v1Tag()->setProperties(properties);
return ID3v2Tag(true)->setProperties(properties);
} }
MPEG::Properties *MPEG::File::audioProperties() const MPEG::Properties *MPEG::File::audioProperties() const
@ -205,17 +163,6 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags) bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
{ {
if(tags == NoTags && stripOthers)
return strip(AllTags);
if(!ID3v2Tag() && !ID3v1Tag() && !APETag()) {
if((d->hasID3v1 || d->hasID3v2 || d->hasAPE) && stripOthers)
return strip(AllTags);
return true;
}
if(readOnly()) { if(readOnly()) {
debug("MPEG::File::save() -- File is read only."); debug("MPEG::File::save() -- File is read only.");
return false; return false;
@ -223,7 +170,7 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
// Create the tags if we've been asked to. // Create the tags if we've been asked to.
if (duplicateTags) { if(duplicateTags) {
// Copy the values from the tag that does exist into the new tag, // Copy the values from the tag that does exist into the new tag,
// except if the existing tag is to be stripped. // except if the existing tag is to be stripped.
@ -235,79 +182,93 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false); Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
} }
bool success = true; // Remove all the tags not going to be saved.
if(stripOthers)
strip(~tags, false);
if(ID3v2 & tags) { if(ID3v2 & tags) {
if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) { if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
if(!d->hasID3v2) // ID3v2 tag is not empty. Update the old one or create a new one.
if(d->ID3v2Location < 0)
d->ID3v2Location = 0; d->ID3v2Location = 0;
insert(ID3v2Tag()->render(id3v2Version), d->ID3v2Location, d->ID3v2OriginalSize); const ByteVector data = ID3v2Tag()->render(id3v2Version);
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
d->hasID3v2 = true; if(d->APELocation >= 0)
d->APELocation += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
// v1 tag location has changed, update if it exists if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
if(ID3v1Tag()) d->ID3v2OriginalSize = data.size();
d->ID3v1Location = findID3v1(); }
else {
// APE tag location has changed, update if it exists
// ID3v2 tag is empty. Remove the old one.
if(APETag())
findAPE(); strip(ID3v2, false);
} }
else if(stripOthers)
success = strip(ID3v2, false) && success;
} }
else if(d->hasID3v2 && stripOthers)
success = strip(ID3v2) && success;
if(ID3v1 & tags) { if(ID3v1 & tags) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
int offset = d->hasID3v1 ? -128 : 0;
seek(offset, End);
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
d->ID3v1Location = findID3v1();
}
else if(stripOthers)
success = strip(ID3v1) && success;
}
else if(d->hasID3v1 && stripOthers)
success = strip(ID3v1, false) && success;
// Dont save an APE-tag unless one has been created // ID3v1 tag is not empty. Update the old one or create a new one.
if((APE & tags) && APETag()) { if(d->ID3v1Location >= 0) {
if(d->hasAPE) seek(d->ID3v1Location);
insert(APETag()->render(), d->APELocation, d->APEOriginalSize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->APELocation = d->ID3v1Location;
d->ID3v1Location += d->APEOriginalSize;
} }
else { else {
seek(0, End); seek(0, End);
d->APELocation = tell(); d->ID3v1Location = tell();
APE::Tag *apeTag = d->tag.access<APE::Tag>(APEIndex, false);
d->APEFooterLocation = d->APELocation
+ apeTag->footer()->completeTagSize()
- APE::Footer::size();
writeBlock(APETag()->render());
d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
} }
}
}
else if(d->hasAPE && stripOthers)
success = strip(APE, false) && success;
return success; writeBlock(ID3v1Tag()->render());
}
else {
// ID3v1 tag is empty. Remove the old one.
strip(ID3v1, false);
}
}
if(APE & tags) {
if(APETag() && !APETag()->isEmpty()) {
// APE tag is not empty. Update the old one or create a new one.
if(d->APELocation < 0) {
if(d->ID3v1Location >= 0)
d->APELocation = d->ID3v1Location;
else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, d->APEOriginalSize);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APEOriginalSize);
d->APEOriginalSize = data.size();
}
else {
// APE tag is empty. Remove the old one.
strip(APE, false);
}
}
return true;
} }
ID3v2::Tag *MPEG::File::ID3v2Tag(bool create) ID3v2::Tag *MPEG::File::ID3v2Tag(bool create)
@ -337,44 +298,39 @@ bool MPEG::File::strip(int tags, bool freeMemory)
return false; return false;
} }
if((tags & ID3v2) && d->hasID3v2) { if((tags & ID3v2) && d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
if(d->APELocation >= 0)
d->APELocation -= d->ID3v2OriginalSize;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2OriginalSize;
d->ID3v2Location = -1; d->ID3v2Location = -1;
d->ID3v2OriginalSize = 0; d->ID3v2OriginalSize = 0;
d->hasID3v2 = false;
if(freeMemory) if(freeMemory)
d->tag.set(ID3v2Index, 0); d->tag.set(ID3v2Index, 0);
// v1 tag location has changed, update if it exists
if(ID3v1Tag())
d->ID3v1Location = findID3v1();
// APE tag location has changed, update if it exists
if(APETag())
findAPE();
} }
if((tags & ID3v1) && d->hasID3v1) { if((tags & ID3v1) && d->ID3v1Location >= 0) {
truncate(d->ID3v1Location); truncate(d->ID3v1Location);
d->ID3v1Location = -1; d->ID3v1Location = -1;
d->hasID3v1 = false;
if(freeMemory) if(freeMemory)
d->tag.set(ID3v1Index, 0); d->tag.set(ID3v1Index, 0);
} }
if((tags & APE) && d->hasAPE) { if((tags & APE) && d->APELocation >= 0) {
removeBlock(d->APELocation, d->APEOriginalSize); removeBlock(d->APELocation, d->APEOriginalSize);
d->APELocation = -1;
d->APEFooterLocation = -1; if(d->ID3v1Location >= 0)
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation)
d->ID3v1Location -= d->APEOriginalSize; d->ID3v1Location -= d->APEOriginalSize;
}
d->APELocation = -1;
d->APEOriginalSize = 0;
if(freeMemory) if(freeMemory)
d->tag.set(APEIndex, 0); d->tag.set(APEIndex, 0);
@ -404,7 +360,7 @@ long MPEG::File::nextFrameOffset(long position)
if(foundLastSyncPattern && secondSynchByte(buffer[0])) if(foundLastSyncPattern && secondSynchByte(buffer[0]))
return position - 1; return position - 1;
for(uint i = 0; i < buffer.size() - 1; i++) { for(unsigned int i = 0; i < buffer.size() - 1; i++) {
if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1]))
return position + i; return position + i;
} }
@ -420,7 +376,7 @@ long MPEG::File::previousFrameOffset(long position)
ByteVector buffer; ByteVector buffer;
while (position > 0) { while (position > 0) {
long size = ulong(position) < bufferSize() ? position : bufferSize(); long size = std::min<long>(position, bufferSize());
position -= size; position -= size;
seek(position); seek(position);
@ -446,45 +402,39 @@ long MPEG::File::firstFrameOffset()
{ {
long position = 0; long position = 0;
if(hasID3v2Tag()) { if(hasID3v2Tag())
position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
// Skip duplicate ID3v2 tags.
// Workaround for some faulty files that have duplicate ID3v2 tags.
// Combination of EAC and LAME creates such files when configured incorrectly.
long location;
while((location = findID3v2(position)) >= 0) {
seek(location);
const ID3v2::Header header(readBlock(ID3v2::Header::size()));
position = location + header.completeTagSize();
debug("MPEG::File::firstFrameOffset() - Duplicate ID3v2 tag found.");
}
}
return nextFrameOffset(position); return nextFrameOffset(position);
} }
long MPEG::File::lastFrameOffset() long MPEG::File::lastFrameOffset()
{ {
return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length()); long position;
if(hasAPETag())
position = d->APELocation - 1;
else if(hasID3v1Tag())
position = d->ID3v1Location - 1;
else
position = length();
return previousFrameOffset(position);
} }
bool MPEG::File::hasID3v1Tag() const bool MPEG::File::hasID3v1Tag() const
{ {
return d->hasID3v1; return (d->ID3v1Location >= 0);
} }
bool MPEG::File::hasID3v2Tag() const bool MPEG::File::hasID3v2Tag() const
{ {
return d->hasID3v2; return (d->ID3v2Location >= 0);
} }
bool MPEG::File::hasAPETag() const bool MPEG::File::hasAPETag() const
{ {
return d->hasAPE; return (d->APELocation >= 0);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -495,38 +445,28 @@ void MPEG::File::read(bool readProperties)
{ {
// Look for an ID3v2 tag // Look for an ID3v2 tag
d->ID3v2Location = findID3v2(0); d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) { if(d->ID3v2Location >= 0) {
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(ID3v2Index, 0);
else
d->hasID3v2 = true;
} }
// Look for an ID3v1 tag // Look for an ID3v1 tag
d->ID3v1Location = findID3v1(); d->ID3v1Location = Utils::findID3v1(this);
if(d->ID3v1Location >= 0) { if(d->ID3v1Location >= 0)
d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for an APE tag // Look for an APE tag
findAPE(); d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) { if(d->APELocation >= 0) {
d->tag.set(APEIndex, new APE::Tag(this, d->APELocation));
d->tag.set(APEIndex, new APE::Tag(this, d->APEFooterLocation));
d->APEOriginalSize = APETag()->footer()->completeTagSize(); d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true; d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize;
} }
if(readProperties) if(readProperties)
@ -538,146 +478,38 @@ void MPEG::File::read(bool readProperties)
ID3v1Tag(true); ID3v1Tag(true);
} }
long MPEG::File::findID3v2(long offset) long MPEG::File::findID3v2()
{ {
// This method is based on the contents of TagLib::File::find(), but because if(!isValid())
// of some subtleties -- specifically the need to look for the bit pattern of
// an MPEG sync, it has been modified for use here.
if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) {
// The position in the file that the current buffer starts at.
long bufferOffset = 0;
ByteVector buffer;
// These variables are used to keep track of a partial match that happens at
// the end of a buffer.
int previousPartialMatch = -1;
bool previousPartialSynchMatch = false;
// Save the location of the current read pointer. We will restore the
// position using seek() before all returns.
long originalPosition = tell();
// Start the search at the offset.
seek(offset);
// This loop is the crux of the find method. There are three cases that we
// want to account for:
// (1) The previously searched buffer contained a partial match of the search
// pattern and we want to see if the next one starts with the remainder of
// that pattern.
//
// (2) The search pattern is wholly contained within the current buffer.
//
// (3) The current buffer ends with a partial match of the pattern. We will
// note this for use in the next iteration, where we will check for the rest
// of the pattern.
for(buffer = readBlock(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) {
// (1) previous partial match
if(previousPartialSynchMatch && secondSynchByte(buffer[0]))
return -1; return -1;
if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) { // An ID3v2 tag or MPEG frame is most likely be at the beginning of the file.
const int patternOffset = (bufferSize() - previousPartialMatch);
if(buffer.containsAt(ID3v2::Header::fileIdentifier(), 0, patternOffset)) {
seek(originalPosition);
return offset + bufferOffset - bufferSize() + previousPartialMatch;
}
}
// (2) pattern contained in current buffer const ByteVector headerID = ID3v2::Header::fileIdentifier();
long location = buffer.find(ID3v2::Header::fileIdentifier()); seek(0);
if(location >= 0) {
seek(originalPosition);
return offset + bufferOffset + location;
}
int firstSynchByte = buffer.find(char(uchar(255))); const ByteVector data = readBlock(headerID.size());
if(data.size() < headerID.size())
// Here we have to loop because there could be several of the first
// (11111111) byte, and we want to check all such instances until we find
// a full match (11111111 111) or hit the end of the buffer.
while(firstSynchByte >= 0) {
// if this *is not* at the end of the buffer
if(firstSynchByte < int(buffer.size()) - 1) {
if(secondSynchByte(buffer[firstSynchByte + 1])) {
// We've found the frame synch pattern.
seek(originalPosition);
return -1; return -1;
}
else {
// We found 11111111 at the end of the current buffer indicating a if(data == headerID)
// partial match of the synch pattern. The find() below should return 0;
// return -1 and break out of the loop.
previousPartialSynchMatch = true;
}
}
// Check in the rest of the buffer.
firstSynchByte = buffer.find(char(uchar(255)), firstSynchByte + 1);
}
// (3) partial match
previousPartialMatch = buffer.endsWithPartialMatch(ID3v2::Header::fileIdentifier());
bufferOffset += bufferSize();
}
// Since we hit the end of the file, reset the status before continuing.
clear();
seek(originalPosition);
}
if(firstSyncByte(data[0]) && secondSynchByte(data[1]))
return -1; return -1;
}
// Look for the entire file, if neither an MEPG frame or ID3v2 tag was found
long MPEG::File::findID3v1() // at the beginning of the file.
{ // We don't care about the inefficiency of the code, since this is a seldom case.
if(isValid()) {
seek(-128, End); const long tagOffset = find(headerID);
long p = tell(); if(tagOffset < 0)
return -1;
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p; const long frameOffset = firstFrameOffset();
} if(frameOffset < tagOffset)
return -1; return -1;
}
return tagOffset;
void MPEG::File::findAPE()
{
if(isValid()) {
seek(d->hasID3v1 ? -160 : -32, End);
long p = tell();
if(readBlock(8) == APE::Tag::fileIdentifier()) {
d->APEFooterLocation = p;
seek(d->APEFooterLocation);
APE::Footer footer(readBlock(APE::Footer::size()));
d->APELocation = d->APEFooterLocation - footer.completeTagSize()
+ APE::Footer::size();
return;
}
}
d->APELocation = -1;
d->APEFooterLocation = -1;
} }

View File

@ -175,9 +175,6 @@ namespace TagLib {
* If you would like more granular control over the content of the tags, * If you would like more granular control over the content of the tags,
* with the concession of generality, use parameterized save call below. * with the concession of generality, use parameterized save call below.
* *
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*
* \see save(int tags) * \see save(int tags)
*/ */
virtual bool save(); virtual bool save();
@ -190,9 +187,6 @@ namespace TagLib {
* This strips all tags not included in the mask, but does not modify them * This strips all tags not included in the mask, but does not modify them
* in memory, so later calls to save() which make use of these tags will * in memory, so later calls to save() which make use of these tags will
* remain valid. This also strips empty tags. * remain valid. This also strips empty tags.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
bool save(int tags); bool save(int tags);
@ -204,9 +198,6 @@ namespace TagLib {
* If \a stripOthers is true this strips all tags not included in the mask, * If \a stripOthers is true this strips all tags not included in the mask,
* but does not modify them in memory, so later calls to save() which make * but does not modify them in memory, so later calls to save() which make
* use of these tags will remain valid. This also strips empty tags. * use of these tags will remain valid. This also strips empty tags.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
// BIC: combine with the above method // BIC: combine with the above method
bool save(int tags, bool stripOthers); bool save(int tags, bool stripOthers);
@ -222,9 +213,6 @@ namespace TagLib {
* *
* The \a id3v2Version parameter specifies the version of the saved * The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3. * ID3v2 tag. It can be either 4 or 3.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
// BIC: combine with the above method // BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version); bool save(int tags, bool stripOthers, int id3v2Version);
@ -243,9 +231,6 @@ namespace TagLib {
* *
* If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 -- * If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 --
* exists this will duplicate its content into the other tag. * exists this will duplicate its content into the other tag.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
// BIC: combine with the above method // BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags); bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags);
@ -390,9 +375,7 @@ namespace TagLib {
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties); void read(bool readProperties);
long findID3v2(long offset); long findID3v2();
long findID3v1();
void findAPE();
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -23,14 +23,14 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include <bitset>
#include <tbytevector.h> #include <tbytevector.h>
#include <tstring.h> #include <tstring.h>
#include <tfile.h>
#include <tdebug.h> #include <tdebug.h>
#include "trefcounter.h" #include <trefcounter.h>
#include "mpegheader.h" #include "mpegheader.h"
#include "mpegutils.h"
using namespace TagLib; using namespace TagLib;
@ -42,6 +42,7 @@ public:
version(Version1), version(Version1),
layer(0), layer(0),
protectionEnabled(false), protectionEnabled(false),
bitrate(0),
sampleRate(0), sampleRate(0),
isPadded(false), isPadded(false),
channelMode(Stereo), channelMode(Stereo),
@ -68,20 +69,27 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
MPEG::Header::Header(const ByteVector &data) MPEG::Header::Header(const ByteVector &data) :
d(new HeaderPrivate())
{ {
d = new HeaderPrivate; debug("MPEG::Header::Header() - This constructor is no longer used.");
parse(data);
} }
MPEG::Header::Header(const Header &h) : d(h.d) MPEG::Header::Header(File *file, long offset, bool checkLength) :
d(new HeaderPrivate())
{
parse(file, offset, checkLength);
}
MPEG::Header::Header(const Header &h) :
d(h.d)
{ {
d->ref(); d->ref();
} }
MPEG::Header::~Header() MPEG::Header::~Header()
{ {
if (d->deref()) if(d->deref())
delete d; delete d;
} }
@ -162,41 +170,54 @@ MPEG::Header &MPEG::Header::operator=(const Header &h)
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void MPEG::Header::parse(const ByteVector &data) void MPEG::Header::parse(File *file, long offset, bool checkLength)
{ {
if(data.size() < 4 || uchar(data[0]) != 0xff) { file->seek(offset);
debug("MPEG::Header::parse() -- First byte did not match MPEG synch."); const ByteVector data = file->readBlock(4);
if(data.size() < 4) {
debug("MPEG::Header::parse() -- data is too short for an MPEG frame header.");
return; return;
} }
std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt())); // Check for the MPEG synch bytes.
// Check for the second byte's part of the MPEG synch if(!firstSyncByte(data[0]) || !secondSynchByte(data[1])) {
debug("MPEG::Header::parse() -- MPEG header did not match MPEG synch.");
if(!flags[23] || !flags[22] || !flags[21]) {
debug("MPEG::Header::parse() -- Second byte did not match MPEG synch.");
return; return;
} }
// Set the MPEG version // Set the MPEG version
if(!flags[20] && !flags[19]) const int versionBits = (static_cast<unsigned char>(data[1]) >> 3) & 0x03;
if(versionBits == 0)
d->version = Version2_5; d->version = Version2_5;
else if(flags[20] && !flags[19]) else if(versionBits == 2)
d->version = Version2; d->version = Version2;
else if(flags[20] && flags[19]) else if(versionBits == 3)
d->version = Version1; d->version = Version1;
else {
debug("MPEG::Header::parse() -- Invalid MPEG version bits.");
return;
}
// Set the MPEG layer // Set the MPEG layer
if(!flags[18] && flags[17]) const int layerBits = (static_cast<unsigned char>(data[1]) >> 1) & 0x03;
d->layer = 3;
else if(flags[18] && !flags[17])
d->layer = 2;
else if(flags[18] && flags[17])
d->layer = 1;
d->protectionEnabled = !flags[16]; if(layerBits == 1)
d->layer = 3;
else if(layerBits == 2)
d->layer = 2;
else if(layerBits == 3)
d->layer = 1;
else {
debug("MPEG::Header::parse() -- Invalid MPEG layer bits.");
return;
}
d->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0);
// Set the bitrate // Set the bitrate
@ -219,9 +240,14 @@ void MPEG::Header::parse(const ByteVector &data)
// The bitrate index is encoded as the first 4 bits of the 3rd byte, // The bitrate index is encoded as the first 4 bits of the 3rd byte,
// i.e. 1111xxxx // i.e. 1111xxxx
int i = uchar(data[2]) >> 4; const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F;
d->bitrate = bitrates[versionIndex][layerIndex][i]; d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];
if(d->bitrate == 0) {
debug("MPEG::Header::parse() -- Invalid bit rate.");
return;
}
// Set the sample rate // Set the sample rate
@ -233,9 +259,9 @@ void MPEG::Header::parse(const ByteVector &data)
// The sample rate index is encoded as two bits in the 3nd byte, i.e. xxxx11xx // The sample rate index is encoded as two bits in the 3nd byte, i.e. xxxx11xx
i = uchar(data[2]) >> 2 & 0x03; const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03;
d->sampleRate = sampleRates[d->version][i]; d->sampleRate = sampleRates[d->version][samplerateIndex];
if(d->sampleRate == 0) { if(d->sampleRate == 0) {
debug("MPEG::Header::parse() -- Invalid sample rate."); debug("MPEG::Header::parse() -- Invalid sample rate.");
@ -245,13 +271,13 @@ void MPEG::Header::parse(const ByteVector &data)
// The channel mode is encoded as a 2 bit value at the end of the 3nd byte, // The channel mode is encoded as a 2 bit value at the end of the 3nd byte,
// i.e. xxxxxx11 // i.e. xxxxxx11
d->channelMode = ChannelMode((uchar(data[3]) & 0xC0) >> 6); d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03);
// TODO: Add mode extension for completeness // TODO: Add mode extension for completeness
d->isOriginal = flags[2]; d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0);
d->isCopyrighted = flags[3]; d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0);
d->isPadded = flags[9]; d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0);
// Samples per frame // Samples per frame
@ -273,6 +299,34 @@ void MPEG::Header::parse(const ByteVector &data)
if(d->isPadded) if(d->isPadded)
d->frameLength += paddingSize[layerIndex]; d->frameLength += paddingSize[layerIndex];
if(checkLength) {
// Check if the frame length has been calculated correctly, or the next frame
// header is right next to the end of this frame.
// The MPEG versions, layers and sample rates of the two frames should be
// consistent. Otherwise, we assume that either or both of the frames are
// broken.
file->seek(offset + d->frameLength);
const ByteVector nextData = file->readBlock(4);
if(nextData.size() < 4) {
debug("MPEG::Header::parse() -- Could not read the next frame header.");
return;
}
const unsigned int HeaderMask = 0xfffe0c00;
const unsigned int header = data.toUInt(0, true) & HeaderMask;
const unsigned int nextHeader = nextData.toUInt(0, true) & HeaderMask;
if(header != nextHeader) {
debug("MPEG::Header::parse() -- The next frame was not consistent with this frame.");
return;
}
}
// Now that we're done parsing, set this to be a valid frame. // Now that we're done parsing, set this to be a valid frame.
d->isValid = true; d->isValid = true;

View File

@ -31,6 +31,7 @@
namespace TagLib { namespace TagLib {
class ByteVector; class ByteVector;
class File;
namespace MPEG { namespace MPEG {
@ -48,9 +49,20 @@ namespace TagLib {
public: public:
/*! /*!
* Parses an MPEG header based on \a data. * Parses an MPEG header based on \a data.
*
* \deprecated
*/ */
Header(const ByteVector &data); Header(const ByteVector &data);
/*!
* Parses an MPEG header based on \a file and \a offset.
*
* \note If \a checkLength is true, this requires the next MPEG frame to
* check if the frame length is parsed and calculated correctly. So it's
* suitable for seeking for the first valid frame.
*/
Header(File *file, long offset, bool checkLength = true);
/*! /*!
* Does a shallow copy of \a h. * Does a shallow copy of \a h.
*/ */
@ -155,7 +167,7 @@ namespace TagLib {
Header &operator=(const Header &h); Header &operator=(const Header &h);
private: private:
void parse(const ByteVector &data); void parse(File *file, long offset, bool checkLength);
class HeaderPrivate; class HeaderPrivate;
HeaderPrivate *d; HeaderPrivate *d;

View File

@ -155,27 +155,31 @@ bool MPEG::Properties::isOriginal() const
void MPEG::Properties::read(File *file) void MPEG::Properties::read(File *file)
{ {
// Only the first frame is required if we have a VBR header. // Only the first valid frame is required if we have a VBR header.
const long first = file->firstFrameOffset(); long firstFrameOffset = file->firstFrameOffset();
if(first < 0) { if(firstFrameOffset < 0) {
debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream.");
return;
}
Header firstHeader(file, firstFrameOffset);
while(!firstHeader.isValid()) {
firstFrameOffset = file->nextFrameOffset(firstFrameOffset + 1);
if(firstFrameOffset < 0) {
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream."); debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
return; return;
} }
file->seek(first); firstHeader = Header(file, firstFrameOffset);
const Header firstHeader(file->readBlock(4));
if(!firstHeader.isValid()) {
debug("MPEG::Properties::read() -- The first page header is invalid.");
return;
} }
// Check for a VBR header that will help us in gathering information about a // Check for a VBR header that will help us in gathering information about a
// VBR stream. // VBR stream.
file->seek(first + 4); file->seek(firstFrameOffset);
d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4)); d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength()));
if(!d->xingHeader->isValid()) { if(!d->xingHeader->isValid()) {
delete d->xingHeader; delete d->xingHeader;
d->xingHeader = 0; d->xingHeader = 0;
@ -201,14 +205,27 @@ void MPEG::Properties::read(File *file)
d->bitrate = firstHeader.bitrate(); d->bitrate = firstHeader.bitrate();
long streamLength = file->length() - first; // Look for the last MPEG audio frame to calculate the stream length.
if(file->hasID3v1Tag()) long lastFrameOffset = file->lastFrameOffset();
streamLength -= 128; if(lastFrameOffset < 0) {
debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream.");
return;
}
if(file->hasAPETag()) Header lastHeader(file, lastFrameOffset, false);
streamLength -= file->APETag()->footer()->completeTagSize();
while(!lastHeader.isValid()) {
lastFrameOffset = file->previousFrameOffset(lastFrameOffset);
if(lastFrameOffset < 0) {
debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream.");
return;
}
lastHeader = Header(file, lastFrameOffset, false);
}
const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength();
if(streamLength > 0) if(streamLength > 0)
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5); d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
} }

63
3rdparty/taglib/mpeg/mpegutils.h vendored Normal file
View File

@ -0,0 +1,63 @@
/***************************************************************************
copyright : (C) 2015 by Tsuda Kageyu
email : tsuda.kageyu@gmail.com
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MPEGUTILS_H
#define TAGLIB_MPEGUTILS_H
// THIS FILE IS NOT A PART OF THE TAGLIB API
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
namespace TagLib
{
namespace MPEG
{
namespace
{
/*!
* MPEG frames can be recognized by the bit pattern 11111111 111, so the
* first byte is easy to check for, however checking to see if the second byte
* starts with \e 111 is a bit more tricky, hence these functions.
*/
inline bool firstSyncByte(unsigned char byte)
{
return (byte == 0xFF);
}
inline bool secondSynchByte(unsigned char byte)
{
// 0xFF is possible in theory, but it's very unlikely be a header.
return (byte != 0xFF && ((byte & 0xE0) == 0xE0));
}
}
}
}
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More