Merge remote-tracking branch 'upstream/master' into qt5

This commit is contained in:
Chocobozzz 2016-10-07 13:29:50 +02:00
commit 18a89f78a9
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(TAGLIB_SOVERSION_CURRENT 16)
set(TAGLIB_SOVERSION_REVISION 1)
set(TAGLIB_SOVERSION_AGE 15)
set(TAGLIB_SOVERSION_CURRENT 17)
set(TAGLIB_SOVERSION_REVISION 0)
set(TAGLIB_SOVERSION_AGE 16)
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
@ -52,6 +53,10 @@ elseif(HAVE_ZLIB_SOURCE)
include_directories(${ZLIB_SOURCE})
endif()
if(HAVE_BOOST_BYTESWAP OR HAVE_BOOST_ATOMIC OR HAVE_BOOST_ZLIB)
include_directories(${Boost_INCLUDE_DIR})
endif()
set(tag_HDRS
tag.h
fileref.h
@ -103,6 +108,7 @@ set(tag_HDRS
mpeg/id3v2/frames/urllinkframe.h
mpeg/id3v2/frames/chapterframe.h
mpeg/id3v2/frames/tableofcontentsframe.h
mpeg/id3v2/frames/podcastframe.h
ogg/oggfile.h
ogg/oggpage.h
ogg/oggpageheader.h
@ -197,6 +203,7 @@ set(frames_SRCS
mpeg/id3v2/frames/urllinkframe.cpp
mpeg/id3v2/frames/chapterframe.cpp
mpeg/id3v2/frames/tableofcontentsframe.cpp
mpeg/id3v2/frames/podcastframe.cpp
)
set(ogg_SRCS
@ -323,6 +330,7 @@ set(toolkit_SRCS
toolkit/tpropertymap.cpp
toolkit/trefcounter.cpp
toolkit/tdebuglistener.cpp
toolkit/tzlib.cpp
)
if(NOT WIN32)
@ -352,6 +360,7 @@ set(tag_LIB_SRCS
tagunion.cpp
fileref.cpp
audioproperties.cpp
tagutils.cpp
)
add_library(tag STATIC ${tag_LIB_SRCS} ${tag_HDRS})
@ -360,6 +369,14 @@ if(ZLIB_FOUND)
target_link_libraries(tag ${ZLIB_LIBRARIES})
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
VERSION ${TAGLIB_SOVERSION_MAJOR}.${TAGLIB_SOVERSION_MINOR}.${TAGLIB_SOVERSION_PATCH}
SOVERSION ${TAGLIB_SOVERSION_MAJOR}

View File

@ -38,9 +38,9 @@
#include <id3v1tag.h>
#include <id3v2header.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "apefile.h"
#include "apetag.h"
#include "apefooter.h"
@ -61,10 +61,7 @@ public:
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
properties(0),
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
properties(0) {}
~FilePrivate()
{
@ -73,24 +70,17 @@ public:
}
long APELocation;
uint APESize;
long APESize;
long ID3v1Location;
ID3v2::Header *ID3v2Header;
long ID3v2Location;
uint ID3v2Size;
long ID3v2Size;
TagUnion tag;
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
{
if(d->hasAPE)
return d->tag.access<APE::Tag>(ApeAPEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->properties();
return PropertyMap();
return d->tag.properties();
}
void APE::File::removeUnsupportedProperties(const StringList &properties)
{
if(d->hasAPE)
d->tag.access<APE::Tag>(ApeAPEIndex, false)->removeUnsupportedProperties(properties);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->removeUnsupportedProperties(properties);
d->tag.removeUnsupportedProperties(properties);
}
PropertyMap APE::File::setProperties(const PropertyMap &properties)
{
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ApeID3v1Index, false)->setProperties(properties);
return d->tag.access<APE::Tag>(ApeAPEIndex, true)->setProperties(properties);
if(ID3v1Tag())
ID3v1Tag()->setProperties(properties);
return APETag(true)->setProperties(properties);
}
APE::Properties *APE::File::audioProperties() const
@ -161,64 +145,67 @@ bool APE::File::save()
// Update ID3v1 tag
if(ID3v1Tag()) {
if(d->hasID3v1) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
}
else {
seek(0, End);
d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
writeBlock(ID3v1Tag()->render());
}
else {
if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128);
d->hasID3v1 = false;
if(d->hasAPE) {
if(d->APELocation > d->ID3v1Location)
d->APELocation -= 128;
}
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
}
// Update APE tag
if(APETag()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
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;
d->ID3v1Location += d->APESize;
}
else {
seek(0, End);
d->APELocation = tell();
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
}
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 {
if(d->hasAPE) {
// APE tag is empty. Remove the old one.
if(d->APELocation >= 0) {
removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation) {
d->ID3v1Location -= d->APESize;
}
}
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APESize;
d->APELocation = -1;
d->APESize = 0;
}
}
return true;
return true;
}
ID3v1::Tag *APE::File::ID3v1Tag(bool create)
@ -233,27 +220,24 @@ APE::Tag *APE::File::APETag(bool create)
void APE::File::strip(int tags)
{
if(tags & ID3v1) {
if(tags & ID3v1)
d->tag.set(ApeID3v1Index, 0);
APETag(true);
}
if(tags & APE) {
if(tags & APE)
d->tag.set(ApeAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
if(!ID3v1Tag())
APETag(true);
}
bool APE::File::hasAPETag() const
{
return d->hasAPE;
return (d->APELocation >= 0);
}
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
d->ID3v2Location = findID3v2();
d->ID3v2Location = Utils::findID3v2(this);
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
}
// 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->hasID3v1 = true;
}
// Look for an APE tag
d->APELocation = findAPE();
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) {
d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation));
d->APESize = APETag()->footer()->completeTagSize();
d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize;
d->hasAPE = true;
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
}
if(!d->hasID3v1)
if(d->ID3v1Location < 0)
APETag(true);
// Look for APE audio properties
@ -302,14 +282,14 @@ void APE::File::read(bool readProperties)
long streamLength;
if(d->hasAPE)
if(d->APELocation >= 0)
streamLength = d->APELocation;
else if(d->hasID3v1)
else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
if(d->ID3v2Location >= 0) {
seek(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);
}
}
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
* 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();
@ -219,9 +216,6 @@ namespace TagLib {
File &operator=(const File &);
void read(bool readProperties);
long findAPE();
long findID3v1();
long findID3v2();
class FilePrivate;
FilePrivate *d;

View File

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

View File

@ -64,7 +64,7 @@ namespace TagLib {
/*!
* 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.
@ -89,13 +89,13 @@ namespace TagLib {
/*!
* Returns the number of items in the tag.
*/
uint itemCount() const;
unsigned int itemCount() const;
/*!
* Set the item count to \a s.
* \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.
@ -103,7 +103,7 @@ namespace TagLib {
*
* \see completeTagSize()
*/
uint tagSize() const;
unsigned int tagSize() const;
/*!
* Returns the tag size, including if present, the header
@ -111,18 +111,18 @@ namespace TagLib {
*
* \see tagSize()
*/
uint completeTagSize() const;
unsigned int completeTagSize() const;
/*!
* Set the tag size to \a s.
* \see tagSize()
*/
void setTagSize(uint s);
void setTagSize(unsigned int s);
/*!
* 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.

View File

@ -34,7 +34,9 @@ using namespace APE;
class APE::Item::ItemPrivate
{
public:
ItemPrivate() : type(Text), readOnly(false) {}
ItemPrivate() :
type(Text),
readOnly(false) {}
Item::ItemTypes type;
String key;
@ -43,40 +45,45 @@ public:
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->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->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;
if(binary) {
d->type = Binary;
d->value = value;
}
else
else {
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()
@ -86,13 +93,17 @@ APE::Item::~Item()
Item &APE::Item::operator=(const Item &item)
{
if(&item != this) {
delete d;
d = new ItemPrivate(*item.d);
}
Item(item).swap(*this);
return *this;
}
void APE::Item::swap(Item &item)
{
using std::swap;
swap(d, item.d);
}
void APE::Item::setReadOnly(bool readOnly)
{
d->readOnly = readOnly;
@ -173,11 +184,10 @@ void APE::Item::appendValues(const StringList &values)
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() /* d->key.data(String::UTF8).size() */ + 1;
switch (d->type) {
int result = 8 + d->key.size() + 1;
switch(d->type) {
case Text:
if(d->text.size()) {
if(!d->text.isEmpty()) {
StringList::ConstIterator it = d->text.begin();
result += it->data(String::UTF8).size();
@ -210,7 +220,7 @@ String APE::Item::toString() const
if(d->type == Text && !isEmpty())
return d->text.front();
else
return String::null;
return String();
}
bool APE::Item::isEmpty() const
@ -239,17 +249,20 @@ void APE::Item::parse(const ByteVector &data)
return;
}
const uint valueLength = data.toUInt(0, false);
const uint flags = data.toUInt(4, false);
const unsigned int valueLength = data.toUInt(0, 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);
setReadOnly(flags & 1);
setType(ItemTypes((flags >> 1) & 3));
if(Text == d->type)
if(Text == d->type)
d->text = StringList(ByteVectorList::split(value, '\0'), String::UTF8);
else
d->value = value;
@ -258,7 +271,7 @@ void APE::Item::parse(const ByteVector &data)
ByteVector APE::Item::render() const
{
ByteVector data;
TagLib::uint flags = ((d->readOnly) ? 1 : 0) | (d->type << 1);
unsigned int flags = ((d->readOnly) ? 1 : 0) | (d->type << 1);
ByteVector value;
if(isEmpty())
@ -280,7 +293,7 @@ ByteVector APE::Item::render() const
data.append(ByteVector::fromUInt(value.size(), 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(value);

View File

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

View File

@ -56,7 +56,7 @@ public:
int channels;
int version;
int bitsPerSample;
uint sampleFrames;
unsigned int sampleFrames;
};
////////////////////////////////////////////////////////////////////////////////
@ -122,7 +122,7 @@ int APE::Properties::bitsPerSample() const
return d->bitsPerSample;
}
TagLib::uint APE::Properties::sampleFrames() const
unsigned int APE::Properties::sampleFrames() const
{
return d->sampleFrames;
}
@ -133,7 +133,7 @@ TagLib::uint APE::Properties::sampleFrames() const
namespace
{
inline int headerVersion(const ByteVector &header)
int headerVersion(const ByteVector &header)
{
if(header.size() < 6 || !header.startsWith("MAC "))
return -1;
@ -184,7 +184,7 @@ void APE::Properties::analyzeCurrent(File *file)
return;
}
const uint descriptorBytes = descriptor.toUInt(0, false);
const unsigned int descriptorBytes = descriptor.toUInt(0, false);
if((descriptorBytes - 52) > 0)
file->seek(descriptorBytes - 52, File::Current);
@ -201,12 +201,12 @@ void APE::Properties::analyzeCurrent(File *file)
d->sampleRate = header.toUInt(20, 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)
return;
const uint blocksPerFrame = header.toUInt(4, false);
const uint finalFrameBlocks = header.toUInt(8, false);
const unsigned int blocksPerFrame = header.toUInt(4, false);
const unsigned int finalFrameBlocks = header.toUInt(8, false);
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
}
@ -218,14 +218,14 @@ void APE::Properties::analyzeOld(File *file)
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)
if(totalFrames == 0)
return;
const short compressionLevel = header.toShort(0, false);
uint blocksPerFrame;
unsigned int blocksPerFrame;
if(d->version >= 3950)
blocksPerFrame = 73728 * 4;
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->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;
// 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.
*/
uint sampleFrames() const;
unsigned int sampleFrames() const;
/*!
* Returns APE version.

View File

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

View File

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

View File

@ -58,84 +58,86 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
ASF::Attribute::Attribute()
ASF::Attribute::Attribute() :
d(new AttributePrivate())
{
d = new AttributePrivate;
d->type = UnicodeType;
}
ASF::Attribute::Attribute(const ASF::Attribute &other)
: d(other.d)
ASF::Attribute::Attribute(const ASF::Attribute &other) :
d(other.d)
{
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)
{
if(&other != this) {
if(d->deref())
delete d;
d = other.d;
d->ref();
}
Attribute(other).swap(*this);
return *this;
}
void ASF::Attribute::swap(Attribute &other)
{
using std::swap;
swap(d, other.d);
}
ASF::Attribute::~Attribute()
{
if(d->deref())
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
{
return d->type;
@ -180,7 +182,7 @@ ASF::Picture ASF::Attribute::toPicture() const
String ASF::Attribute::parse(ASF::File &f, int kind)
{
uint size, nameLength;
unsigned int size, nameLength;
String name;
d->pictureValue = Picture::fromInvalid();
// extended content descriptor
@ -351,4 +353,3 @@ void ASF::Attribute::setStream(int value)
{
d->stream = value;
}

View File

@ -115,6 +115,11 @@ namespace TagLib
*/
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.
*/

View File

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

View File

@ -112,9 +112,6 @@ namespace TagLib {
* Save the file.
*
* 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();

View File

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

View File

@ -117,6 +117,11 @@ namespace TagLib
*/
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
*/

View File

@ -64,7 +64,7 @@ String ASF::Tag::album() const
{
if(d->attributeListMap.contains("WM/AlbumTitle"))
return d->attributeListMap["WM/AlbumTitle"][0].toString();
return String::null;
return String();
}
String ASF::Tag::copyright() const
@ -107,7 +107,7 @@ String ASF::Tag::genre() const
{
if(d->attributeListMap.contains("WM/Genre"))
return d->attributeListMap["WM/Genre"][0].toString();
return String::null;
return String();
}
void ASF::Tag::setTitle(const String &value)
@ -145,12 +145,12 @@ void ASF::Tag::setGenre(const String &value)
setAttribute("WM/Genre", value);
}
void ASF::Tag::setYear(uint value)
void ASF::Tag::setYear(unsigned int 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));
}
@ -210,58 +210,64 @@ bool ASF::Tag::isEmpty() const
d->attributeListMap.isEmpty();
}
static const char *keyTranslation[][2] = {
{ "WM/AlbumTitle", "ALBUM" },
{ "WM/AlbumArtist", "ALBUMARTIST" },
{ "WM/Composer", "COMPOSER" },
{ "WM/Writer", "WRITER" },
{ "WM/Conductor", "CONDUCTOR" },
{ "WM/ModifiedBy", "REMIXER" },
{ "WM/Year", "DATE" },
{ "WM/OriginalReleaseYear", "ORIGINALDATE" },
{ "WM/Producer", "PRODUCER" },
{ "WM/ContentGroupDescription", "GROUPING" },
{ "WM/SubTitle", "SUBTITLE" },
{ "WM/SetSubTitle", "DISCSUBTITLE" },
{ "WM/TrackNumber", "TRACKNUMBER" },
{ "WM/PartOfSet", "DISCNUMBER" },
{ "WM/Genre", "GENRE" },
{ "WM/BeatsPerMinute", "BPM" },
{ "WM/Mood", "MOOD" },
{ "WM/ISRC", "ISRC" },
{ "WM/Lyrics", "LYRICS" },
{ "WM/Media", "MEDIA" },
{ "WM/Publisher", "LABEL" },
{ "WM/CatalogNo", "CATALOGNUMBER" },
{ "WM/Barcode", "BARCODE" },
{ "WM/EncodedBy", "ENCODEDBY" },
{ "WM/AlbumSortOrder", "ALBUMSORT" },
{ "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" },
{ "WM/ArtistSortOrder", "ARTISTSORT" },
{ "WM/TitleSortOrder", "TITLESORT" },
{ "WM/Script", "SCRIPT" },
{ "WM/Language", "LANGUAGE" },
{ "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" },
{ "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" },
{ "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" },
{ "MusicIP/PUID", "MUSICIP_PUID" },
{ "Acoustid/Id", "ACOUSTID_ID" },
{ "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" },
};
namespace
{
const char *keyTranslation[][2] = {
{ "WM/AlbumTitle", "ALBUM" },
{ "WM/AlbumArtist", "ALBUMARTIST" },
{ "WM/Composer", "COMPOSER" },
{ "WM/Writer", "WRITER" },
{ "WM/Conductor", "CONDUCTOR" },
{ "WM/ModifiedBy", "REMIXER" },
{ "WM/Year", "DATE" },
{ "WM/OriginalReleaseYear", "ORIGINALDATE" },
{ "WM/Producer", "PRODUCER" },
{ "WM/ContentGroupDescription", "GROUPING" },
{ "WM/SubTitle", "SUBTITLE" },
{ "WM/SetSubTitle", "DISCSUBTITLE" },
{ "WM/TrackNumber", "TRACKNUMBER" },
{ "WM/PartOfSet", "DISCNUMBER" },
{ "WM/Genre", "GENRE" },
{ "WM/BeatsPerMinute", "BPM" },
{ "WM/Mood", "MOOD" },
{ "WM/ISRC", "ISRC" },
{ "WM/Lyrics", "LYRICS" },
{ "WM/Media", "MEDIA" },
{ "WM/Publisher", "LABEL" },
{ "WM/CatalogNo", "CATALOGNUMBER" },
{ "WM/Barcode", "BARCODE" },
{ "WM/EncodedBy", "ENCODEDBY" },
{ "WM/AlbumSortOrder", "ALBUMSORT" },
{ "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" },
{ "WM/ArtistSortOrder", "ARTISTSORT" },
{ "WM/TitleSortOrder", "TITLESORT" },
{ "WM/Script", "SCRIPT" },
{ "WM/Language", "LANGUAGE" },
{ "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" },
{ "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" },
{ "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" },
{ "MusicIP/PUID", "MUSICIP_PUID" },
{ "Acoustid/Id", "ACOUSTID_ID" },
{ "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
{
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;
if(!d->title.isEmpty()) {
@ -279,8 +285,8 @@ PropertyMap ASF::Tag::properties() const
ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin();
for(; it != d->attributeListMap.end(); ++it) {
if(keyMap.contains(it->first)) {
String key = keyMap[it->first];
const String key = translateKey(it->first);
if(!key.isEmpty()) {
AttributeList::ConstIterator it2 = it->second.begin();
for(; it2 != it->second.end(); ++it2) {
if(key == "TRACKNUMBER") {
@ -323,16 +329,16 @@ PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
for(; it != origProps.end(); ++it) {
if(!props.contains(it->first) || props[it->first].isEmpty()) {
if(it->first == "TITLE") {
d->title = String::null;
d->title.clear();
}
else if(it->first == "ARTIST") {
d->artist = String::null;
d->artist.clear();
}
else if(it->first == "COMMENT") {
d->comment = String::null;
d->comment.clear();
}
else if(it->first == "COPYRIGHT") {
d->copyright = String::null;
d->copyright.clear();
}
else {
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.
*/
virtual uint year() const;
virtual unsigned int year() const;
/*!
* Returns the track number; if there is no track number set, this will
* return 0.
*/
virtual uint track() const;
virtual unsigned int track() const;
/*!
* 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.
*/
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.
*/
virtual void setTrack(uint i);
virtual void setTrack(unsigned int i);
/*!
* Returns true if the tag does not contain any data. This should be

View File

@ -34,65 +34,68 @@ namespace TagLib
{
namespace ASF
{
inline ushort readWORD(File *file, bool *ok = 0)
namespace
{
const ByteVector v = file->readBlock(2);
if(v.size() != 2) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUShort(false);
}
inline uint readDWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(4);
if(v.size() != 4) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt(false);
}
inline long long readQWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(8);
if(v.size() != 8) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toLongLong(false);
}
inline String readString(File *file, int length)
{
ByteVector data = file->readBlock(length);
unsigned int size = data.size();
while (size >= 2) {
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
break;
inline unsigned short readWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(2);
if(v.size() != 2) {
if(ok) *ok = false;
return 0;
}
size -= 2;
if(ok) *ok = true;
return v.toUShort(false);
}
if(size != data.size()) {
data.resize(size);
}
return String(data, String::UTF16LE);
}
inline ByteVector renderString(const String &str, bool includeLength = false)
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
if(includeLength) {
data = ByteVector::fromShort(data.size(), false) + data;
inline unsigned int readDWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(4);
if(v.size() != 4) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt(false);
}
return data;
}
inline long long readQWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(8);
if(v.size() != 8) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toLongLong(false);
}
inline String readString(File *file, int length)
{
ByteVector data = file->readBlock(length);
unsigned int size = data.size();
while (size >= 2) {
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
break;
}
size -= 2;
}
if(size != data.size()) {
data.resize(size);
}
return String(data, String::UTF16LE);
}
inline ByteVector renderString(const String &str, bool includeLength = false)
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
if(includeLength) {
data = ByteVector::fromShort(data.size(), false) + data;
}
return data;
}
}
}
}

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

View File

@ -30,7 +30,7 @@
#include <tfile.h>
#include <tstring.h>
#include <tdebug.h>
#include "trefcounter.h"
#include <trefcounter.h>
#include "fileref.h"
#include "asffile.h"
@ -54,41 +54,176 @@
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
{
public:
FileRefPrivate(File *f) : RefCounter(), file(f) {}
FileRefPrivate(File *f) :
RefCounter(),
file(f) {}
~FileRefPrivate() {
delete file;
}
File *file;
static List<const FileTypeResolver *> fileTypeResolvers;
};
List<const FileRef::FileTypeResolver *> FileRef::FileRefPrivate::fileTypeResolvers;
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
FileRef::FileRef()
FileRef::FileRef() :
d(new FileRefPrivate(0))
{
d = new FileRefPrivate(0);
}
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();
}
@ -133,7 +268,7 @@ bool FileRef::save()
const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static
{
FileRefPrivate::fileTypeResolvers.prepend(resolver);
fileTypeResolvers.prepend(resolver);
return resolver;
}
@ -155,6 +290,7 @@ StringList FileRef::defaultFileExtensions()
l.append("m4p");
l.append("3g2");
l.append("mp4");
l.append("m4v");
l.append("wma");
l.append("asf");
l.append("aif");
@ -174,113 +310,34 @@ StringList FileRef::defaultFileExtensions()
bool FileRef::isNull() const
{
return !d->file || !d->file->isValid();
return (!d->file || !d->file->isValid());
}
FileRef &FileRef::operator=(const FileRef &ref)
{
if(&ref == this)
return *this;
if(d->deref())
delete d;
d = ref.d;
d->ref();
FileRef(ref).swap(*this);
return *this;
}
void FileRef::swap(FileRef &ref)
{
using std::swap;
swap(d, ref.d);
}
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
{
return ref.d->file != d->file;
return (ref.d->file != d->file);
}
File *FileRef::create(FileName fileName, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle) // static
{
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;
return createInternal(fileName, readAudioProperties, audioPropertiesStyle);
}

View File

@ -127,6 +127,23 @@ namespace TagLib {
AudioProperties::ReadStyle
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
* pointer and will delete the File when it passes out of scope.
@ -226,6 +243,11 @@ namespace TagLib {
*/
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.
*/

View File

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

View File

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

View File

@ -135,7 +135,7 @@ void FLAC::Properties::read(const ByteVector &data, long streamLength)
return;
}
uint pos = 0;
unsigned int pos = 0;
// Minimum block size (in samples)
pos += 2;
@ -149,7 +149,7 @@ void FLAC::Properties::read(const ByteVector &data, long streamLength)
// Maximum frame size (in bytes)
pos += 3;
const uint flags = data.toUInt(pos, true);
const unsigned int flags = data.toUInt(pos, true);
pos += 4;
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
// stream length in samples. (Audio files measured in days)
const ulonglong hi = flags & 0xf;
const ulonglong lo = data.toUInt(pos, true);
const unsigned long long hi = flags & 0xf;
const unsigned long long lo = data.toUInt(pos, true);
pos += 4;
d->sampleFrames = (hi << 32) | lo;

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,10 +15,15 @@
* *
* 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 *
* 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 "tstringlist.h"
#include "itfile.h"
#include "tdebug.h"
@ -96,9 +101,9 @@ bool IT::File::save()
seek(2, Current);
ushort length = 0;
ushort instrumentCount = 0;
ushort sampleCount = 0;
unsigned short length = 0;
unsigned short instrumentCount = 0;
unsigned short sampleCount = 0;
if(!readU16L(length) || !readU16L(instrumentCount) || !readU16L(sampleCount))
return false;
@ -107,9 +112,9 @@ bool IT::File::save()
// write comment as instrument and sample names:
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));
ulong instrumentOffset = 0;
unsigned long instrumentOffset = 0;
if(!readU32L(instrumentOffset))
return false;
@ -118,28 +123,28 @@ bool IT::File::save()
if(i < lines.size())
writeString(lines[i], 25);
else
writeString(String::null, 25);
writeString(String(), 25);
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));
ulong sampleOffset = 0;
unsigned long sampleOffset = 0;
if(!readU32L(sampleOffset))
return false;
seek(sampleOffset + 20);
if((TagLib::uint)(i + instrumentCount) < lines.size())
if((unsigned int)(i + instrumentCount) < lines.size())
writeString(lines[i + instrumentCount], 25);
else
writeString(String::null, 25);
writeString(String(), 25);
writeByte(0);
}
// write rest as message:
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]);
ByteVector message = messageLines.toString("\r").data(String::Latin1);
@ -149,15 +154,15 @@ bool IT::File::save()
message.resize(7999);
message.append((char)0);
ushort special = 0;
ushort messageLength = 0;
ulong messageOffset = 0;
unsigned short special = 0;
unsigned short messageLength = 0;
unsigned long messageOffset = 0;
seek(46);
if(!readU16L(special))
return false;
ulong fileSize = File::length();
unsigned long fileSize = File::length();
if(special & Properties::MessageAttached) {
seek(54);
if(!readU16L(messageLength) || !readU32L(messageOffset))
@ -259,8 +264,8 @@ void IT::File::read(bool)
d->properties.setChannels(channels);
// real length might be shorter because of skips and terminator
ushort realLength = 0;
for(ushort i = 0; i < length; ++ i) {
unsigned short realLength = 0;
for(unsigned short i = 0; i < length; ++ i) {
READ_BYTE_AS(order);
if(order == 255) break;
if(order != 254) ++ realLength;
@ -274,7 +279,7 @@ void IT::File::read(bool)
// Currently I just discard anything after a nil, but
// e.g. VLC seems to interprete a nil as a space. I
// 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));
READ_U32L_AS(instrumentOffset);
seek(instrumentOffset);
@ -290,7 +295,7 @@ void IT::File::read(bool)
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));
READ_U32L_AS(sampleOffset);

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,10 +15,15 @@
* *
* 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 *
* 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 "itproperties.h"
using namespace TagLib;
@ -46,21 +51,21 @@ public:
{
}
int channels;
ushort lengthInPatterns;
ushort instrumentCount;
ushort sampleCount;
ushort patternCount;
ushort version;
ushort compatibleVersion;
ushort flags;
ushort special;
uchar globalVolume;
uchar mixVolume;
uchar tempo;
uchar bpmSpeed;
uchar panningSeparation;
uchar pitchWheelDepth;
int channels;
unsigned short lengthInPatterns;
unsigned short instrumentCount;
unsigned short sampleCount;
unsigned short patternCount;
unsigned short version;
unsigned short compatibleVersion;
unsigned short flags;
unsigned short special;
unsigned char globalVolume;
unsigned char mixVolume;
unsigned char tempo;
unsigned char bpmSpeed;
unsigned char panningSeparation;
unsigned char pitchWheelDepth;
};
IT::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) :
@ -104,7 +109,7 @@ int IT::Properties::channels() const
return d->channels;
}
TagLib::ushort IT::Properties::lengthInPatterns() const
unsigned short IT::Properties::lengthInPatterns() const
{
return d->lengthInPatterns;
}
@ -114,67 +119,67 @@ bool IT::Properties::stereo() const
return d->flags & Stereo;
}
TagLib::ushort IT::Properties::instrumentCount() const
unsigned short IT::Properties::instrumentCount() const
{
return d->instrumentCount;
}
TagLib::ushort IT::Properties::sampleCount() const
unsigned short IT::Properties::sampleCount() const
{
return d->sampleCount;
}
TagLib::ushort IT::Properties::patternCount() const
unsigned short IT::Properties::patternCount() const
{
return d->patternCount;
}
TagLib::ushort IT::Properties::version() const
unsigned short IT::Properties::version() const
{
return d->version;
}
TagLib::ushort IT::Properties::compatibleVersion() const
unsigned short IT::Properties::compatibleVersion() const
{
return d->compatibleVersion;
}
TagLib::ushort IT::Properties::flags() const
unsigned short IT::Properties::flags() const
{
return d->flags;
}
TagLib::ushort IT::Properties::special() const
unsigned short IT::Properties::special() const
{
return d->special;
}
uchar IT::Properties::globalVolume() const
unsigned char IT::Properties::globalVolume() const
{
return d->globalVolume;
}
uchar IT::Properties::mixVolume() const
unsigned char IT::Properties::mixVolume() const
{
return d->mixVolume;
}
uchar IT::Properties::tempo() const
unsigned char IT::Properties::tempo() const
{
return d->tempo;
}
uchar IT::Properties::bpmSpeed() const
unsigned char IT::Properties::bpmSpeed() const
{
return d->bpmSpeed;
}
uchar IT::Properties::panningSeparation() const
unsigned char IT::Properties::panningSeparation() const
{
return d->panningSeparation;
}
uchar IT::Properties::pitchWheelDepth() const
unsigned char IT::Properties::pitchWheelDepth() const
{
return d->pitchWheelDepth;
}
@ -184,72 +189,72 @@ void IT::Properties::setChannels(int channels)
d->channels = channels;
}
void IT::Properties::setLengthInPatterns(ushort lengthInPatterns)
void IT::Properties::setLengthInPatterns(unsigned short lengthInPatterns)
{
d->lengthInPatterns = lengthInPatterns;
}
void IT::Properties::setInstrumentCount(ushort instrumentCount)
void IT::Properties::setInstrumentCount(unsigned short instrumentCount)
{
d->instrumentCount = instrumentCount;
}
void IT::Properties::setSampleCount(ushort sampleCount)
void IT::Properties::setSampleCount(unsigned short sampleCount)
{
d->sampleCount = sampleCount;
}
void IT::Properties::setPatternCount(ushort patternCount)
void IT::Properties::setPatternCount(unsigned short patternCount)
{
d->patternCount = patternCount;
}
void IT::Properties::setFlags(ushort flags)
void IT::Properties::setFlags(unsigned short flags)
{
d->flags = flags;
}
void IT::Properties::setSpecial(ushort special)
void IT::Properties::setSpecial(unsigned short special)
{
d->special = special;
}
void IT::Properties::setCompatibleVersion(ushort compatibleVersion)
void IT::Properties::setCompatibleVersion(unsigned short compatibleVersion)
{
d->compatibleVersion = compatibleVersion;
}
void IT::Properties::setVersion(ushort version)
void IT::Properties::setVersion(unsigned short version)
{
d->version = version;
}
void IT::Properties::setGlobalVolume(uchar globalVolume)
void IT::Properties::setGlobalVolume(unsigned char globalVolume)
{
d->globalVolume = globalVolume;
}
void IT::Properties::setMixVolume(uchar mixVolume)
void IT::Properties::setMixVolume(unsigned char mixVolume)
{
d->mixVolume = mixVolume;
}
void IT::Properties::setTempo(uchar tempo)
void IT::Properties::setTempo(unsigned char tempo)
{
d->tempo = tempo;
}
void IT::Properties::setBpmSpeed(uchar bpmSpeed)
void IT::Properties::setBpmSpeed(unsigned char bpmSpeed)
{
d->bpmSpeed = bpmSpeed;
}
void IT::Properties::setPanningSeparation(uchar panningSeparation)
void IT::Properties::setPanningSeparation(unsigned char panningSeparation)
{
d->panningSeparation = panningSeparation;
}
void IT::Properties::setPitchWheelDepth(uchar pitchWheelDepth)
void IT::Properties::setPitchWheelDepth(unsigned char pitchWheelDepth)
{
d->pitchWheelDepth = pitchWheelDepth;
}

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,8 +15,12 @@
* *
* 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 *
* 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_ITPROPERTIES_H
@ -58,37 +62,37 @@ namespace TagLib {
int sampleRate() const;
int channels() const;
ushort lengthInPatterns() const;
bool stereo() const;
ushort instrumentCount() const;
ushort sampleCount() const;
ushort patternCount() const;
ushort version() const;
ushort compatibleVersion() const;
ushort flags() const;
ushort special() const;
uchar globalVolume() const;
uchar mixVolume() const;
uchar tempo() const;
uchar bpmSpeed() const;
uchar panningSeparation() const;
uchar pitchWheelDepth() const;
unsigned short lengthInPatterns() const;
bool stereo() const;
unsigned short instrumentCount() const;
unsigned short sampleCount() const;
unsigned short patternCount() const;
unsigned short version() const;
unsigned short compatibleVersion() const;
unsigned short flags() const;
unsigned short special() const;
unsigned char globalVolume() const;
unsigned char mixVolume() const;
unsigned char tempo() const;
unsigned char bpmSpeed() const;
unsigned char panningSeparation() const;
unsigned char pitchWheelDepth() const;
void setChannels(int channels);
void setLengthInPatterns(ushort lengthInPatterns);
void setInstrumentCount(ushort instrumentCount);
void setSampleCount (ushort sampleCount);
void setPatternCount(ushort patternCount);
void setVersion (ushort version);
void setCompatibleVersion(ushort compatibleVersion);
void setFlags (ushort flags);
void setSpecial (ushort special);
void setGlobalVolume(uchar globalVolume);
void setMixVolume (uchar mixVolume);
void setTempo (uchar tempo);
void setBpmSpeed (uchar bpmSpeed);
void setPanningSeparation(uchar panningSeparation);
void setPitchWheelDepth (uchar pitchWheelDepth);
void setLengthInPatterns(unsigned short lengthInPatterns);
void setInstrumentCount(unsigned short instrumentCount);
void setSampleCount (unsigned short sampleCount);
void setPatternCount(unsigned short patternCount);
void setVersion (unsigned short version);
void setCompatibleVersion(unsigned short compatibleVersion);
void setFlags (unsigned short flags);
void setSpecial (unsigned short special);
void setGlobalVolume(unsigned char globalVolume);
void setMixVolume (unsigned char mixVolume);
void setTempo (unsigned char tempo);
void setBpmSpeed (unsigned char bpmSpeed);
void setPanningSeparation(unsigned char panningSeparation);
void setPitchWheelDepth (unsigned char pitchWheelDepth);
private:
Properties(const Properties&);

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,10 +15,15 @@
* *
* 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 *
* 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 "modfile.h"
#include "tstringlist.h"
#include "tdebug.h"
@ -92,14 +97,14 @@ bool Mod::File::save()
seek(0);
writeString(d->tag.title(), 20);
StringList lines = d->tag.comment().split("\n");
uint n = std::min(lines.size(), d->properties.instrumentCount());
for(uint i = 0; i < n; ++ i) {
unsigned int n = std::min(lines.size(), d->properties.instrumentCount());
for(unsigned int i = 0; i < n; ++ i) {
writeString(lines[i], 22);
seek(8, Current);
}
for(uint i = n; i < d->properties.instrumentCount(); ++ i) {
writeString(String::null, 22);
for(unsigned int i = n; i < d->properties.instrumentCount(); ++ i) {
writeString(String(), 22);
seek(8, Current);
}
return true;
@ -114,8 +119,8 @@ void Mod::File::read(bool)
ByteVector modId = readBlock(4);
READ_ASSERT(modId.size() == 4);
int channels = 4;
uint instruments = 31;
int channels = 4;
unsigned int instruments = 31;
if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") {
d->tag.setTrackerName("ProTracker");
channels = 4;
@ -159,7 +164,7 @@ void Mod::File::read(bool)
READ_STRING(d->tag.setTitle, 20);
StringList comment;
for(uint i = 0; i < instruments; ++ i) {
for(unsigned int i = 0; i < instruments; ++ i) {
READ_STRING_AS(instrumentName, 22);
// value in words, * 2 (<< 1) for bytes:
READ_U16B_AS(sampleLength);

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,8 +15,12 @@
* *
* 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 *
* 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_MODFILE_H

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,10 +15,15 @@
* *
* 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 *
* 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 "tdebug.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));
data.resize(size, padding);
writeBlock(data);
}
bool Mod::FileBase::readString(String &s, ulong size)
bool Mod::FileBase::readString(String &s, unsigned long size)
{
ByteVector data(readBlock(size));
if(data.size() < size) return false;
@ -49,39 +54,39 @@ bool Mod::FileBase::readString(String &s, ulong size)
{
data.resize(index);
}
data.replace((char) 0xff, ' ');
data.replace('\xff', ' ');
s = data;
return true;
}
void Mod::FileBase::writeByte(uchar byte)
void Mod::FileBase::writeByte(unsigned char byte)
{
ByteVector data(1, byte);
writeBlock(data);
}
void Mod::FileBase::writeU16L(ushort number)
void Mod::FileBase::writeU16L(unsigned short number)
{
writeBlock(ByteVector::fromShort(number, false));
}
void Mod::FileBase::writeU32L(ulong number)
void Mod::FileBase::writeU32L(unsigned long number)
{
writeBlock(ByteVector::fromUInt(number, false));
}
void Mod::FileBase::writeU16B(ushort number)
void Mod::FileBase::writeU16B(unsigned short number)
{
writeBlock(ByteVector::fromShort(number, true));
}
void Mod::FileBase::writeU32B(ulong number)
void Mod::FileBase::writeU32B(unsigned long number)
{
writeBlock(ByteVector::fromUInt(number, true));
}
bool Mod::FileBase::readByte(uchar &byte)
bool Mod::FileBase::readByte(unsigned char &byte)
{
ByteVector data(readBlock(1));
if(data.size() < 1) return false;
@ -89,7 +94,7 @@ bool Mod::FileBase::readByte(uchar &byte)
return true;
}
bool Mod::FileBase::readU16L(ushort &number)
bool Mod::FileBase::readU16L(unsigned short &number)
{
ByteVector data(readBlock(2));
if(data.size() < 2) return false;
@ -97,14 +102,14 @@ bool Mod::FileBase::readU16L(ushort &number)
return true;
}
bool Mod::FileBase::readU32L(ulong &number) {
bool Mod::FileBase::readU32L(unsigned long &number) {
ByteVector data(readBlock(4));
if(data.size() < 4) return false;
number = data.toUInt(false);
return true;
}
bool Mod::FileBase::readU16B(ushort &number)
bool Mod::FileBase::readU16B(unsigned short &number)
{
ByteVector data(readBlock(2));
if(data.size() < 2) return false;
@ -112,7 +117,7 @@ bool Mod::FileBase::readU16B(ushort &number)
return true;
}
bool Mod::FileBase::readU32B(ulong &number) {
bool Mod::FileBase::readU32B(unsigned long &number) {
ByteVector data(readBlock(4));
if(data.size() < 4) return false;
number = data.toUInt(true);

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,8 +15,12 @@
* *
* 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 *
* 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_MODFILEBASE_H
@ -40,19 +44,19 @@ namespace TagLib {
FileBase(FileName file);
FileBase(IOStream *stream);
void writeString(const String &s, ulong size, char padding = 0);
void writeByte(uchar byte);
void writeU16L(ushort number);
void writeU32L(ulong number);
void writeU16B(ushort number);
void writeU32B(ulong number);
void writeString(const String &s, unsigned long size, char padding = 0);
void writeByte(unsigned char byte);
void writeU16L(unsigned short number);
void writeU32L(unsigned long number);
void writeU16B(unsigned short number);
void writeU32B(unsigned long number);
bool readString(String &s, ulong size);
bool readByte(uchar &byte);
bool readU16L(ushort &number);
bool readU32L(ulong &number);
bool readU16B(ushort &number);
bool readU32B(ulong &number);
bool readString(String &s, unsigned long size);
bool readByte(unsigned char &byte);
bool readU16L(unsigned short &number);
bool readU32L(unsigned long &number);
bool readU16B(unsigned short &number);
bool readU32B(unsigned long &number);
};
}

View File

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

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,10 +15,15 @@
* *
* 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 *
* 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 "modproperties.h"
using namespace TagLib;
@ -34,9 +39,9 @@ public:
{
}
int channels;
uint instrumentCount;
uchar lengthInPatterns;
int channels;
unsigned int instrumentCount;
unsigned char lengthInPatterns;
};
Mod::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) :
@ -80,12 +85,12 @@ int Mod::Properties::channels() const
return d->channels;
}
TagLib::uint Mod::Properties::instrumentCount() const
unsigned int Mod::Properties::instrumentCount() const
{
return d->instrumentCount;
}
uchar Mod::Properties::lengthInPatterns() const
unsigned char Mod::Properties::lengthInPatterns() const
{
return d->lengthInPatterns;
}
@ -95,12 +100,12 @@ void Mod::Properties::setChannels(int channels)
d->channels = channels;
}
void Mod::Properties::setInstrumentCount(uint instrumentCount)
void Mod::Properties::setInstrumentCount(unsigned int instrumentCount)
{
d->instrumentCount = instrumentCount;
}
void Mod::Properties::setLengthInPatterns(uchar lengthInPatterns)
void Mod::Properties::setLengthInPatterns(unsigned char lengthInPatterns)
{
d->lengthInPatterns = lengthInPatterns;
}

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,8 +15,12 @@
* *
* 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 *
* 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_MODPROPERTIES_H
@ -42,13 +46,13 @@ namespace TagLib {
int sampleRate() const;
int channels() const;
uint instrumentCount() const;
uchar lengthInPatterns() const;
unsigned int instrumentCount() const;
unsigned char lengthInPatterns() const;
void setChannels(int channels);
void setInstrumentCount(uint sampleCount);
void setLengthInPatterns(uchar lengthInPatterns);
void setInstrumentCount(unsigned int sampleCount);
void setLengthInPatterns(unsigned char lengthInPatterns);
private:
friend class File;

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,10 +15,15 @@
* *
* 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 *
* 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 "modtag.h"
#include "tstringlist.h"
#include "tpropertymap.h"
@ -55,12 +60,12 @@ String Mod::Tag::title() const
String Mod::Tag::artist() const
{
return String::null;
return String();
}
String Mod::Tag::album() const
{
return String::null;
return String();
}
String Mod::Tag::comment() const
@ -70,15 +75,15 @@ String Mod::Tag::comment() 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;
}
TagLib::uint Mod::Tag::track() const
unsigned int Mod::Tag::track() const
{
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;
properties["TITLE"] = d->title;
properties["COMMENT"] = d->comment;
if(!(d->trackerName.isNull()))
if(!(d->trackerName.isEmpty()))
properties["TRACKERNAME"] = d->trackerName;
return properties;
}
@ -142,19 +147,19 @@ PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps)
d->title = properties["TITLE"].front();
oneValueSet.append("TITLE");
} else
d->title = String::null;
d->title.clear();
if(properties.contains("COMMENT")) {
d->comment = properties["COMMENT"].front();
oneValueSet.append("COMMENT");
} else
d->comment = String::null;
d->comment.clear();
if(properties.contains("TRACKERNAME")) {
d->trackerName = properties["TRACKERNAME"].front();
oneValueSet.append("TRACKERNAME");
} else
d->trackerName = String::null;
d->trackerName.clear();
// 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.

View File

@ -5,7 +5,7 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 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 *
@ -15,8 +15,12 @@
* *
* 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 *
* 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_MODTAG_H
@ -50,39 +54,39 @@ namespace TagLib {
* Returns the track name; if no track name is present in the tag
* String::null will be returned.
*/
String title() const;
virtual String title() const;
/*!
* 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.
*/
String album() const;
virtual String album() const;
/*!
* Returns the track comment derived from the instrument/sample/pattern
* names; if no comment is present in the tag String::null will be
* returned.
*/
String comment() const;
virtual String comment() const;
/*!
* 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.
*/
uint year() const;
virtual unsigned int year() const;
/*!
* 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.
@ -101,17 +105,17 @@ namespace TagLib {
* Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20
* characters.
*/
void setTitle(const String &title);
virtual void setTitle(const String &title);
/*!
* 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.
*/
void setAlbum(const String &album);
virtual void setAlbum(const String &album);
/*!
* 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
* characters.
*/
void setComment(const String &comment);
virtual void setComment(const String &comment);
/*!
* 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.
*/
void setYear(uint year);
virtual void setYear(unsigned int year);
/*!
* 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

View File

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

View File

@ -77,29 +77,29 @@ namespace TagLib {
class Atom
{
public:
Atom(File *file);
~Atom();
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0);
AtomList findall(const char *name, bool recursive = false);
long offset;
long length;
TagLib::ByteVector name;
AtomList children;
Atom(File *file);
~Atom();
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0);
AtomList findall(const char *name, bool recursive = false);
long offset;
long length;
TagLib::ByteVector name;
AtomList children;
private:
static const int numContainers = 11;
static const char *containers[11];
static const int numContainers = 11;
static const char *containers[11];
};
//! Root-level atoms
class Atoms
{
public:
Atoms(File *file);
~Atoms();
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
AtomList atoms;
Atoms(File *file);
~Atoms();
Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0);
AtomList atoms;
};
}

View File

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

View File

@ -53,8 +53,17 @@ namespace TagLib {
~CoverArt();
CoverArt(const CoverArt &item);
/*!
* Copies the contents of \a item into this CoverArt.
*/
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 format() const;

View File

@ -130,8 +130,7 @@ MP4::File::read(bool readProperties)
}
// must have a moov atom, otherwise consider it invalid
MP4::Atom *moov = d->atoms->find("moov");
if(!moov) {
if(!d->atoms->find("moov")) {
setValid(false);
return;
}
@ -158,3 +157,8 @@ MP4::File::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.
*
* 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();
/*!
* 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:
void read(bool readProperties);

View File

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

View File

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

View File

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

View File

@ -35,21 +35,23 @@ using namespace TagLib;
class MP4::Tag::TagPrivate
{
public:
TagPrivate() : file(0), atoms(0) {}
~TagPrivate() {}
TagPrivate() :
file(0),
atoms(0) {}
TagLib::File *file;
Atoms *atoms;
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->atoms = atoms;
@ -59,8 +61,8 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
return;
}
for(unsigned int i = 0; i < ilst->children.size(); i++) {
MP4::Atom *atom = ilst->children[i];
for(AtomList::ConstIterator it = ilst->children.begin(); it != ilst->children.end(); ++it) {
MP4::Atom *atom = *it;
file->seek(atom->offset + 8);
if(atom->name == "----") {
parseFreeForm(atom);
@ -149,8 +151,8 @@ MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm)
{
AtomDataList data = parseData2(atom, expectedFlags, freeForm);
ByteVectorList result;
for(uint i = 0; i < data.size(); i++) {
result.append(data[i].data);
for(AtomDataList::ConstIterator it = data.begin(); it != data.end(); ++it) {
result.append(it->data);
}
return result;
}
@ -159,7 +161,7 @@ void
MP4::Tag::parseInt(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
if(!data.isEmpty()) {
addItem(atom->name, (int)data[0].toShort());
}
}
@ -168,7 +170,7 @@ void
MP4::Tag::parseUInt(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
if(!data.isEmpty()) {
addItem(atom->name, data[0].toUInt());
}
}
@ -177,7 +179,7 @@ void
MP4::Tag::parseLongLong(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
if(!data.isEmpty()) {
addItem(atom->name, data[0].toLongLong());
}
}
@ -186,8 +188,8 @@ void
MP4::Tag::parseByte(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
addItem(atom->name, (uchar)data[0].at(0));
if(!data.isEmpty()) {
addItem(atom->name, static_cast<unsigned char>(data[0].at(0)));
}
}
@ -195,7 +197,7 @@ void
MP4::Tag::parseGnre(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
if(!data.isEmpty()) {
int idx = (int)data[0].toShort();
if(idx > 0) {
addItem("\251gen", StringList(ID3v1::genre(idx - 1)));
@ -207,7 +209,7 @@ void
MP4::Tag::parseIntPair(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
if(!data.isEmpty()) {
const int a = data[0].toShort(2U);
const int b = data[0].toShort(4U);
addItem(atom->name, MP4::Item(a, b));
@ -218,7 +220,7 @@ void
MP4::Tag::parseBool(const MP4::Atom *atom)
{
ByteVectorList data = parseData(atom);
if(data.size()) {
if(!data.isEmpty()) {
bool value = data[0].size() ? data[0][0] != '\0' : false;
addItem(atom->name, value);
}
@ -228,10 +230,10 @@ void
MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags)
{
ByteVectorList data = parseData(atom, expectedFlags);
if(data.size()) {
if(!data.isEmpty()) {
StringList value;
for(unsigned int i = 0; i < data.size(); i++) {
value.append(String(data[i], String::UTF8));
for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
value.append(String(*it, String::UTF8));
}
addItem(atom->name, value);
}
@ -242,18 +244,25 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom)
{
AtomDataList data = parseData2(atom, -1, true);
if(data.size() > 2) {
String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8);
AtomDataType type = data[2].type;
for(uint i = 2; i < data.size(); i++) {
if(data[i].type != type) {
AtomDataList::ConstIterator itBegin = data.begin();
String name = "----:";
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");
break;
}
}
if(type == TypeUTF8) {
StringList value;
for(uint i = 2; i < data.size(); i++) {
value.append(String(data[i].data, String::UTF8));
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
value.append(String(it->data, String::UTF8));
}
Item item(value);
item.setAtomDataType(type);
@ -261,8 +270,8 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom)
}
else {
ByteVectorList value;
for(uint i = 2; i < data.size(); i++) {
value.append(data[i].data);
for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) {
value.append(it->data);
}
Item item(value);
item.setAtomDataType(type);
@ -300,7 +309,7 @@ MP4::Tag::parseCovr(const MP4::Atom *atom)
}
pos += length;
}
if(value.size() > 0)
if(!value.isEmpty())
addItem(atom->name, value);
}
@ -323,8 +332,8 @@ ByteVector
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const
{
ByteVector result;
for(unsigned int i = 0; i < data.size(); i++) {
result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + data[i]));
for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) {
result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + *it));
}
return renderAtom(name, result);
}
@ -395,8 +404,8 @@ MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) c
{
ByteVectorList data;
StringList value = item.toStringList();
for(unsigned int i = 0; i < value.size(); i++) {
data.append(value[i].data(String::UTF8));
for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(it->data(String::UTF8));
}
return renderData(name, flags, data);
}
@ -406,9 +415,9 @@ MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
{
ByteVector data;
MP4::CoverArtList value = item.toCoverArtList();
for(unsigned int i = 0; i < value.size(); i++) {
data.append(renderAtom("data", ByteVector::fromUInt(value[i].format()) +
ByteVector(4, '\0') + value[i].data()));
for(MP4::CoverArtList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(renderAtom("data", ByteVector::fromUInt(it->format()) +
ByteVector(4, '\0') + it->data()));
}
return renderAtom(name, data);
}
@ -417,9 +426,9 @@ ByteVector
MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
{
StringList header = StringList::split(name, ":");
if (header.size() != 3) {
if(header.size() != 3) {
debug("MP4: Invalid free-form item name \"" + name + "\"");
return ByteVector::null;
return ByteVector();
}
ByteVector data;
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) {
StringList value = item.toStringList();
for(unsigned int i = 0; i < value.size(); i++) {
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i].data(String::UTF8)));
for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + it->data(String::UTF8)));
}
}
else {
ByteVectorList value = item.toByteVectorList();
for(unsigned int i = 0; i < value.size(); i++) {
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i]));
for(ByteVectorList::ConstIterator it = value.begin(); it != value.end(); ++it) {
data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + *it));
}
}
return renderAtom("----", data);
@ -505,20 +514,26 @@ MP4::Tag::save()
void
MP4::Tag::updateParents(const AtomList &path, long delta, int ignore)
{
for(unsigned int i = 0; i < path.size() - ignore; i++) {
d->file->seek(path[i]->offset);
if(static_cast<int>(path.size()) <= ignore)
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();
// 64-bit
if (size == 1) {
d->file->seek(4, File::Current); // Skip name
long long longSize = d->file->readBlock(8).toLongLong();
// 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));
}
// 32-bit
else {
d->file->seek(path[i]->offset);
d->file->seek((*it)->offset);
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");
if(moov) {
MP4::AtomList stco = moov->findall("stco", true);
for(unsigned int i = 0; i < stco.size(); i++) {
MP4::Atom *atom = stco[i];
for(MP4::AtomList::ConstIterator it = stco.begin(); it != stco.end(); ++it) {
MP4::Atom *atom = *it;
if(atom->offset > offset) {
atom->offset += delta;
}
@ -539,7 +554,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
ByteVector data = d->file->readBlock(atom->length - 12);
unsigned int count = data.toUInt();
d->file->seek(atom->offset + 16);
uint pos = 4;
unsigned int pos = 4;
while(count--) {
long o = static_cast<long>(data.toUInt(pos));
if(o > offset) {
@ -551,8 +566,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
}
MP4::AtomList co64 = moov->findall("co64", true);
for(unsigned int i = 0; i < co64.size(); i++) {
MP4::Atom *atom = co64[i];
for(MP4::AtomList::ConstIterator it = co64.begin(); it != co64.end(); ++it) {
MP4::Atom *atom = *it;
if(atom->offset > offset) {
atom->offset += delta;
}
@ -560,7 +575,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
ByteVector data = d->file->readBlock(atom->length - 12);
unsigned int count = data.toUInt();
d->file->seek(atom->offset + 16);
uint pos = 4;
unsigned int pos = 4;
while(count--) {
long long o = data.toLongLong(pos);
if(o > offset) {
@ -575,8 +590,8 @@ MP4::Tag::updateOffsets(long delta, long offset)
MP4::Atom *moof = d->atoms->find("moof");
if(moof) {
MP4::AtomList tfhd = moof->findall("tfhd", true);
for(unsigned int i = 0; i < tfhd.size(); i++) {
MP4::Atom *atom = tfhd[i];
for(MP4::AtomList::ConstIterator it = tfhd.begin(); it != tfhd.end(); ++it) {
MP4::Atom *atom = *it;
if(atom->offset > offset) {
atom->offset += delta;
}
@ -609,21 +624,28 @@ MP4::Tag::saveNew(ByteVector 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);
updateParents(path, data.size());
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
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 length = ilst->length;
MP4::Atom *meta = path[path.size() - 2];
MP4::Atom *meta = *(--it);
AtomList::ConstIterator index = meta->children.find(ilst);
// 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"))
return d->items["\251nam"].toStringList().toString(", ");
return String::null;
return String();
}
String
@ -677,7 +699,7 @@ MP4::Tag::artist() const
{
if(d->items.contains("\251ART"))
return d->items["\251ART"].toStringList().toString(", ");
return String::null;
return String();
}
String
@ -685,7 +707,7 @@ MP4::Tag::album() const
{
if(d->items.contains("\251alb"))
return d->items["\251alb"].toStringList().toString(", ");
return String::null;
return String();
}
String
@ -693,7 +715,7 @@ MP4::Tag::comment() const
{
if(d->items.contains("\251cmt"))
return d->items["\251cmt"].toStringList().toString(", ");
return String::null;
return String();
}
String
@ -701,7 +723,7 @@ MP4::Tag::genre() const
{
if(d->items.contains("\251gen"))
return d->items["\251gen"].toStringList().toString(", ");
return String::null;
return String();
}
unsigned int
@ -751,13 +773,13 @@ MP4::Tag::setGenre(const String &value)
}
void
MP4::Tag::setYear(uint value)
MP4::Tag::setYear(unsigned int value)
{
d->items["\251day"] = StringList(String::number(value));
}
void
MP4::Tag::setTrack(uint value)
MP4::Tag::setTrack(unsigned int value)
{
d->items["trkn"] = MP4::Item(value, 0);
}
@ -797,71 +819,76 @@ bool MP4::Tag::contains(const String &key) const
return d->items.contains(key);
}
static const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" },
{ "\251ART", "ARTIST" },
{ "\251alb", "ALBUM" },
{ "\251cmt", "COMMENT" },
{ "\251gen", "GENRE" },
{ "\251day", "DATE" },
{ "\251wrt", "COMPOSER" },
{ "\251grp", "GROUPING" },
{ "aART", "ALBUMARTIST" },
{ "trkn", "TRACKNUMBER" },
{ "disk", "DISCNUMBER" },
{ "cpil", "COMPILATION" },
{ "tmpo", "BPM" },
{ "cprt", "COPYRIGHT" },
{ "\251lyr", "LYRICS" },
{ "\251too", "ENCODEDBY" },
{ "soal", "ALBUMSORT" },
{ "soaa", "ALBUMARTISTSORT" },
{ "soar", "ARTISTSORT" },
{ "sonm", "TITLESORT" },
{ "soco", "COMPOSERSORT" },
{ "sosn", "SHOWSORT" },
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
{ "----:com.apple.iTunes:ASIN", "ASIN" },
{ "----:com.apple.iTunes:LABEL", "LABEL" },
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
{ "----:com.apple.iTunes:MIXER", "MIXER" },
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
{ "----:com.apple.iTunes:MOOD", "MOOD" },
{ "----:com.apple.iTunes:ISRC", "ISRC" },
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
};
namespace
{
const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" },
{ "\251ART", "ARTIST" },
{ "\251alb", "ALBUM" },
{ "\251cmt", "COMMENT" },
{ "\251gen", "GENRE" },
{ "\251day", "DATE" },
{ "\251wrt", "COMPOSER" },
{ "\251grp", "GROUPING" },
{ "aART", "ALBUMARTIST" },
{ "trkn", "TRACKNUMBER" },
{ "disk", "DISCNUMBER" },
{ "cpil", "COMPILATION" },
{ "tmpo", "BPM" },
{ "cprt", "COPYRIGHT" },
{ "\251lyr", "LYRICS" },
{ "\251too", "ENCODEDBY" },
{ "soal", "ALBUMSORT" },
{ "soaa", "ALBUMARTISTSORT" },
{ "soar", "ARTISTSORT" },
{ "sonm", "TITLESORT" },
{ "soco", "COMPOSERSORT" },
{ "sosn", "SHOWSORT" },
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
{ "----:com.apple.iTunes:ASIN", "ASIN" },
{ "----:com.apple.iTunes:LABEL", "LABEL" },
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
{ "----:com.apple.iTunes:MIXER", "MIXER" },
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
{ "----:com.apple.iTunes:MOOD", "MOOD" },
{ "----:com.apple.iTunes:ISRC", "ISRC" },
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
{ "----: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
{
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;
MP4::ItemMap::ConstIterator it = d->items.begin();
for(; it != d->items.end(); ++it) {
if(keyMap.contains(it->first)) {
String key = keyMap[it->first];
for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
const String key = translateKey(it->first);
if(!key.isEmpty()) {
if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
MP4::Item::IntPair ip = it->second.toIntPair();
String value = String::number(ip.first);
@ -889,8 +916,7 @@ PropertyMap MP4::Tag::properties() const
void MP4::Tag::removeUnsupportedProperties(const StringList &props)
{
StringList::ConstIterator it = props.begin();
for(; it != props.end(); ++it)
for(StringList::ConstIterator it = props.begin(); it != props.end(); ++it)
d->items.erase(*it);
}
@ -905,22 +931,20 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
}
PropertyMap origProps = properties();
PropertyMap::ConstIterator it = origProps.begin();
for(; it != origProps.end(); ++it) {
for(PropertyMap::ConstIterator it = origProps.begin(); it != origProps.end(); ++it) {
if(!props.contains(it->first) || props[it->first].isEmpty()) {
d->items.erase(reverseKeyMap[it->first]);
}
}
PropertyMap ignoredProps;
it = props.begin();
for(; it != props.end(); ++it) {
for(PropertyMap::ConstIterator it = props.begin(); it != props.end(); ++it) {
if(reverseKeyMap.contains(it->first)) {
String name = reverseKeyMap[it->first];
if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) {
int first = 0, second = 0;
StringList parts = StringList::split(it->second.front(), "/");
if(parts.size() > 0) {
if(!parts.isEmpty()) {
first = parts[0].toInt();
if(parts.size() > 1) {
second = parts[1].toInt();

View File

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

View File

@ -28,6 +28,7 @@
#include <tagunion.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "mpcfile.h"
#include "id3v1tag.h"
@ -52,10 +53,7 @@ public:
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
properties(0),
hasAPE(false),
hasID3v1(false),
hasID3v2(false) {}
properties(0) {}
~FilePrivate()
{
@ -64,24 +62,17 @@ public:
}
long APELocation;
uint APESize;
long APESize;
long ID3v1Location;
ID3v2::Header *ID3v2Header;
long ID3v2Location;
uint ID3v2Size;
long ID3v2Size;
TagUnion tag;
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
{
if(d->hasAPE)
return d->tag.access<APE::Tag>(MPCAPEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(MPCID3v1Index, false)->properties();
return PropertyMap();
return d->tag.properties();
}
void MPC::File::removeUnsupportedProperties(const StringList &properties)
{
if(d->hasAPE)
d->tag.access<APE::Tag>(MPCAPEIndex, false)->removeUnsupportedProperties(properties);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(MPCID3v1Index, false)->removeUnsupportedProperties(properties);
d->tag.removeUnsupportedProperties(properties);
}
PropertyMap MPC::File::setProperties(const PropertyMap &properties)
{
if(d->hasID3v1)
d->tag.access<APE::Tag>(MPCID3v1Index, false)->setProperties(properties);
return d->tag.access<APE::Tag>(MPCAPEIndex, true)->setProperties(properties);
if(ID3v1Tag())
ID3v1Tag()->setProperties(properties);
return APETag(true)->setProperties(properties);
}
MPC::Properties *MPC::File::audioProperties() const
@ -152,69 +137,80 @@ bool MPC::File::save()
// Possibly strip ID3v2 tag
if(d->hasID3v2 && !d->ID3v2Header) {
if(!d->ID3v2Header && d->ID3v2Location >= 0) {
removeBlock(d->ID3v2Location, d->ID3v2Size);
d->hasID3v2 = false;
if(d->hasID3v1)
d->ID3v1Location -= d->ID3v2Size;
if(d->hasAPE)
if(d->APELocation >= 0)
d->APELocation -= d->ID3v2Size;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2Size;
d->ID3v2Location = -1;
d->ID3v2Size = 0;
}
// Update ID3v1 tag
if(ID3v1Tag()) {
if(d->hasID3v1) {
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
// ID3v1 tag is not empty. Update the old one or create a new one.
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
writeBlock(ID3v1Tag()->render());
}
else {
seek(0, End);
d->ID3v1Location = tell();
writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
}
} else
if(d->hasID3v1) {
removeBlock(d->ID3v1Location, 128);
d->hasID3v1 = false;
if(d->hasAPE) {
if(d->APELocation > d->ID3v1Location)
d->APELocation -= 128;
}
writeBlock(ID3v1Tag()->render());
}
else {
// ID3v1 tag is empty. Remove the old one.
if(d->ID3v1Location >= 0) {
truncate(d->ID3v1Location);
d->ID3v1Location = -1;
}
}
// Update APE tag
if(APETag()) {
if(d->hasAPE)
insert(APETag()->render(), d->APELocation, d->APESize);
else {
if(d->hasID3v1) {
insert(APETag()->render(), d->ID3v1Location, 0);
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
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;
d->ID3v1Location += d->APESize;
}
else {
seek(0, End);
d->APELocation = tell();
writeBlock(APETag()->render());
d->APESize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
}
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 {
// APE tag is empty. Remove the old one.
if(d->APELocation >= 0) {
removeBlock(d->APELocation, d->APESize);
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APESize;
d->APELocation = -1;
d->APESize = 0;
}
}
else
if(d->hasAPE) {
removeBlock(d->APELocation, d->APESize);
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation)
d->ID3v1Location -= d->APESize;
}
}
return true;
}
@ -231,22 +227,19 @@ APE::Tag *MPC::File::APETag(bool create)
void MPC::File::strip(int tags)
{
if(tags & ID3v1) {
if(tags & ID3v1)
d->tag.set(MPCID3v1Index, 0);
if(tags & APE)
d->tag.set(MPCAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
if(tags & ID3v2) {
delete d->ID3v2Header;
d->ID3v2Header = 0;
}
if(tags & APE) {
d->tag.set(MPCAPEIndex, 0);
if(!ID3v1Tag())
APETag(true);
}
}
void MPC::File::remove(int tags)
@ -256,12 +249,12 @@ void MPC::File::remove(int tags)
bool MPC::File::hasID3v1Tag() const
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
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)
{
// 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
d->ID3v2Location = findID3v2();
d->ID3v2Location = Utils::findID3v2(this);
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
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
if(readProperties) {
long streamLength;
if(d->hasAPE)
if(d->APELocation >= 0)
streamLength = d->APELocation;
else if(d->hasID3v1)
else if(d->ID3v1Location >= 0)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
if(d->ID3v2Location >= 0) {
seek(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);
}
}
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.
*
* 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();
@ -222,9 +219,6 @@ namespace TagLib {
File &operator=(const File &);
void read(bool readProperties);
long findAPE();
long findID3v1();
long findID3v2();
class FilePrivate;
FilePrivate *d;

View File

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

View File

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

View File

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

View File

@ -32,10 +32,20 @@
using namespace TagLib;
using namespace ID3v1;
namespace
{
const ID3v1::StringHandler defaultStringHandler;
const ID3v1::StringHandler *stringHandler = &defaultStringHandler;
}
class ID3v1::Tag::TagPrivate
{
public:
TagPrivate() : file(0), tagOffset(-1), track(0), genre(255) {}
TagPrivate() :
file(0),
tagOffset(0),
track(0),
genre(255) {}
File *file;
long tagOffset;
@ -45,15 +55,10 @@ public:
String album;
String year;
String comment;
uchar track;
uchar genre;
static const StringHandler *stringHandler;
unsigned char track;
unsigned char genre;
};
static const StringHandler defaultStringHandler;
const ID3v1::StringHandler *ID3v1::Tag::TagPrivate::stringHandler = &defaultStringHandler;
////////////////////////////////////////////////////////////////////////////////
// StringHandler implementation
////////////////////////////////////////////////////////////////////////////////
@ -69,26 +74,26 @@ String ID3v1::StringHandler::parse(const ByteVector &data) const
ByteVector ID3v1::StringHandler::render(const String &s) const
{
if(!s.isLatin1())
{
if(s.isLatin1())
return s.data(String::Latin1);
else
return ByteVector();
}
return s.data(String::Latin1);
}
////////////////////////////////////////////////////////////////////////////////
// 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->tagOffset = tagOffset;
@ -105,11 +110,11 @@ ByteVector ID3v1::Tag::render() const
ByteVector data;
data.append(fileIdentifier());
data.append(TagPrivate::stringHandler->render(d->title).resize(30));
data.append(TagPrivate::stringHandler->render(d->artist).resize(30));
data.append(TagPrivate::stringHandler->render(d->album).resize(30));
data.append(TagPrivate::stringHandler->render(d->year).resize(4));
data.append(TagPrivate::stringHandler->render(d->comment).resize(28));
data.append(stringHandler->render(d->title).resize(30));
data.append(stringHandler->render(d->artist).resize(30));
data.append(stringHandler->render(d->album).resize(30));
data.append(stringHandler->render(d->year).resize(4));
data.append(stringHandler->render(d->comment).resize(28));
data.append(char(0));
data.append(char(d->track));
data.append(char(d->genre));
@ -147,12 +152,12 @@ String ID3v1::Tag::genre() const
return ID3v1::genre(d->genre);
}
TagLib::uint ID3v1::Tag::year() const
unsigned int ID3v1::Tag::year() const
{
return d->year.toInt();
}
TagLib::uint ID3v1::Tag::track() const
unsigned int ID3v1::Tag::track() const
{
return d->track;
}
@ -182,32 +187,32 @@ void ID3v1::Tag::setGenre(const String &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;
}
TagLib::uint ID3v1::Tag::genreNumber() const
unsigned int ID3v1::Tag::genreNumber() const
{
return d->genre;
}
void ID3v1::Tag::setGenreNumber(TagLib::uint i)
void ID3v1::Tag::setGenreNumber(unsigned int i)
{
d->genre = i < 256 ? i : 255;
}
void ID3v1::Tag::setStringHandler(const StringHandler *handler)
{
if (handler)
TagPrivate::stringHandler = handler;
if(handler)
stringHandler = handler;
else
TagPrivate::stringHandler = &defaultStringHandler;
stringHandler = &defaultStringHandler;
}
////////////////////////////////////////////////////////////////////////////////
@ -219,7 +224,7 @@ void ID3v1::Tag::read()
if(d->file && d->file->isValid()) {
d->file->seek(d->tagOffset);
// read the tag -- always 128 bytes
ByteVector data = d->file->readBlock(128);
const ByteVector data = d->file->readBlock(128);
// some initial sanity checking
if(data.size() == 128 && data.startsWith("TAG"))
@ -233,16 +238,16 @@ void ID3v1::Tag::parse(const ByteVector &data)
{
int offset = 3;
d->title = TagPrivate::stringHandler->parse(data.mid(offset, 30));
d->title = stringHandler->parse(data.mid(offset, 30));
offset += 30;
d->artist = TagPrivate::stringHandler->parse(data.mid(offset, 30));
d->artist = stringHandler->parse(data.mid(offset, 30));
offset += 30;
d->album = TagPrivate::stringHandler->parse(data.mid(offset, 30));
d->album = stringHandler->parse(data.mid(offset, 30));
offset += 30;
d->year = TagPrivate::stringHandler->parse(data.mid(offset, 4));
d->year = stringHandler->parse(data.mid(offset, 4));
offset += 4;
// 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) {
// ID3v1.1 detected
d->comment = TagPrivate::stringHandler->parse(data.mid(offset, 28));
d->track = uchar(data[offset + 29]);
d->comment = stringHandler->parse(data.mid(offset, 28));
d->track = static_cast<unsigned char>(data[offset + 29]);
}
else
d->comment = data.mid(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 comment() const;
virtual String genre() const;
virtual TagLib::uint year() const;
virtual TagLib::uint track() const;
virtual unsigned int year() const;
virtual unsigned int track() const;
virtual void setTitle(const String &s);
virtual void setArtist(const String &s);
virtual void setAlbum(const String &s);
virtual void setComment(const String &s);
virtual void setGenre(const String &s);
virtual void setYear(TagLib::uint i);
virtual void setTrack(TagLib::uint i);
virtual void setYear(unsigned int i);
virtual void setTrack(unsigned int i);
/*!
* Returns the genre in number.
*
* \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.
@ -164,7 +164,7 @@ namespace TagLib {
* \note Valid value is from 0 up to 255. Normally 255 indicates that
* 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

View File

@ -137,7 +137,7 @@ void AttachedPictureFrame::parseFields(const ByteVector &data)
d->mimeType = readStringField(data, String::Latin1, &pos);
/* 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.");
return;
}

View File

@ -44,10 +44,10 @@ public:
const ID3v2::Header *tagHeader;
ByteVector elementID;
TagLib::uint startTime;
TagLib::uint endTime;
TagLib::uint startOffset;
TagLib::uint endOffset;
unsigned int startTime;
unsigned int endTime;
unsigned int startOffset;
unsigned int endOffset;
FrameListMap embeddedFrameListMap;
FrameList embeddedFrameList;
};
@ -65,8 +65,8 @@ ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &dat
}
ChapterFrame::ChapterFrame(const ByteVector &elementID,
TagLib::uint startTime, TagLib::uint endTime,
TagLib::uint startOffset, TagLib::uint endOffset,
unsigned int startTime, unsigned int endTime,
unsigned int startOffset, unsigned int endOffset,
const FrameList &embeddedFrames) :
ID3v2::Frame("CHAP")
{
@ -97,22 +97,22 @@ ByteVector ChapterFrame::elementID() const
return d->elementID;
}
TagLib::uint ChapterFrame::startTime() const
unsigned int ChapterFrame::startTime() const
{
return d->startTime;
}
TagLib::uint ChapterFrame::endTime() const
unsigned int ChapterFrame::endTime() const
{
return d->endTime;
}
TagLib::uint ChapterFrame::startOffset() const
unsigned int ChapterFrame::startOffset() const
{
return d->startOffset;
}
TagLib::uint ChapterFrame::endOffset() const
unsigned int ChapterFrame::endOffset() const
{
return d->endOffset;
}
@ -125,22 +125,22 @@ void ChapterFrame::setElementID(const ByteVector &eID)
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;
}
void ChapterFrame::setEndTime(const TagLib::uint &eT)
void ChapterFrame::setEndTime(const unsigned int &eT)
{
d->endTime = eT;
}
void ChapterFrame::setStartOffset(const TagLib::uint &sO)
void ChapterFrame::setStartOffset(const unsigned int &sO)
{
d->startOffset = sO;
}
void ChapterFrame::setEndOffset(const TagLib::uint &eO)
void ChapterFrame::setEndOffset(const unsigned int &eO)
{
d->endOffset = eO;
}
@ -238,7 +238,7 @@ ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVec
void ChapterFrame::parseFields(const ByteVector &data)
{
TagLib::uint size = data.size();
unsigned int size = data.size();
if(size < 18) {
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).");
@ -246,7 +246,7 @@ void ChapterFrame::parseFields(const ByteVector &data)
}
int pos = 0;
TagLib::uint embPos = 0;
unsigned int embPos = 0;
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->startTime = data.toUInt(pos, true);
pos += 4;

View File

@ -62,8 +62,8 @@ namespace TagLib {
* All times are in milliseconds.
*/
ChapterFrame(const ByteVector &elementID,
uint startTime, uint endTime,
uint startOffset, uint endOffset,
unsigned int startTime, unsigned int endTime,
unsigned int startOffset, unsigned int endOffset,
const FrameList &embeddedFrames = FrameList());
/*!
@ -84,14 +84,14 @@ namespace TagLib {
*
* \see setStartTime()
*/
uint startTime() const;
unsigned int startTime() const;
/*!
* Returns time of chapter's end (in milliseconds).
*
* \see setEndTime()
*/
uint endTime() const;
unsigned int endTime() const;
/*!
* 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.
* \see setStartOffset()
*/
uint startOffset() const;
unsigned int startOffset() const;
/*!
* 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.
* \see setEndOffset()
*/
uint endOffset() const;
unsigned int endOffset() const;
/*!
* Sets the element ID of the frame to \a eID. If \a eID isn't
@ -124,14 +124,14 @@ namespace TagLib {
*
* \see startTime()
*/
void setStartTime(const uint &sT);
void setStartTime(const unsigned int &sT);
/*!
* Sets time of chapter's end (in milliseconds) to \a eT.
*
* \see endTime()
*/
void setEndTime(const uint &eT);
void setEndTime(const unsigned int &eT);
/*!
* Sets zero based byte offset (count of bytes from the beginning
@ -139,7 +139,7 @@ namespace TagLib {
*
* \see startOffset()
*/
void setStartOffset(const uint &sO);
void setStartOffset(const unsigned int &sO);
/*!
* Sets zero based byte offset (count of bytes from the beginning
@ -147,7 +147,7 @@ namespace TagLib {
*
* \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

View File

@ -116,8 +116,6 @@ PropertyMap CommentsFrame::asProperties() const
PropertyMap map;
if(key.isEmpty() || key == "COMMENT")
map.insert("COMMENT", text());
else if(key.isNull())
map.unsupportedData().append(L"COMM/" + description());
else
map.insert("COMMENT:" + key, text());
return map;
@ -164,7 +162,7 @@ void CommentsFrame::parseFields(const ByteVector &data)
} else {
d->description = String(l.front(), d->textEncoding);
d->text = String(l.back(), d->textEncoding);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -24,9 +24,10 @@
***************************************************************************/
#include <tdebug.h>
#include <tstringlist.h>
#include <id3v2tag.h>
#include "ownershipframe.h"
#include <id3v2tag.h>
using namespace TagLib;
using namespace ID3v2;
@ -113,24 +114,24 @@ void OwnershipFrame::setTextEncoding(String::Type encoding)
void OwnershipFrame::parseFields(const ByteVector &data)
{
int pos = 0;
// Get the text encoding
d->textEncoding = String::Type(data[0]);
pos += 1;
// Read the price paid this is a null terminate string
d->pricePaid = readStringField(data, String::Latin1, &pos);
// If we don't have at least 8 bytes left then don't parse the rest of the
// data
if(data.size() - pos < 8) {
return;
}
// Read the date purchased YYYYMMDD
d->datePurchased = String(data.mid(pos, 8));
pos += 8;
// Read the seller
if(d->textEncoding == String::Latin1)
d->seller = Tag::latin1StringHandler()->parse(data.mid(pos));
@ -140,14 +141,19 @@ void OwnershipFrame::parseFields(const ByteVector &data)
ByteVector OwnershipFrame::renderFields() const
{
StringList sl;
sl.append(d->seller);
const String::Type encoding = checkTextEncoding(sl, d->textEncoding);
ByteVector v;
v.append(char(d->textEncoding));
v.append(char(encoding));
v.append(d->pricePaid.data(String::Latin1));
v.append(textDelimiter(String::Latin1));
v.append(d->datePurchased.data(String::Latin1));
v.append(d->seller.data(d->textEncoding));
v.append(d->seller.data(encoding));
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) {}
String email;
int rating;
TagLib::uint counter;
unsigned int counter;
};
////////////////////////////////////////////////////////////////////////////////
@ -84,12 +84,12 @@ void PopularimeterFrame::setRating(int s)
d->rating = s;
}
TagLib::uint PopularimeterFrame::counter() const
unsigned int PopularimeterFrame::counter() const
{
return d->counter;
}
void PopularimeterFrame::setCounter(TagLib::uint s)
void PopularimeterFrame::setCounter(unsigned int s)
{
d->counter = s;
}
@ -109,7 +109,7 @@ void PopularimeterFrame::parseFields(const ByteVector &data)
if(pos < size) {
d->rating = (unsigned char)(data[pos++]);
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()
*/
uint counter() const;
unsigned int counter() const;
/*!
* Set the counter.
*
* \see counter()
*/
void setCounter(uint counter);
void setCounter(unsigned int counter);
protected:
// Reimplementations.

View File

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

View File

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

View File

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

View File

@ -121,7 +121,7 @@ bool TableOfContentsFrame::isOrdered() const
return d->isOrdered;
}
TagLib::uint TableOfContentsFrame::entryCount() const
unsigned int TableOfContentsFrame::entryCount() const
{
return d->childElements.size();
}
@ -214,7 +214,7 @@ void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
String TableOfContentsFrame::toString() const
{
return String::null;
return String();
}
PropertyMap TableOfContentsFrame::asProperties() const
@ -261,7 +261,7 @@ TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag)
void TableOfContentsFrame::parseFields(const ByteVector &data)
{
TagLib::uint size = data.size();
unsigned int size = data.size();
if(size < 6) {
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 "
@ -270,12 +270,12 @@ void TableOfContentsFrame::parseFields(const ByteVector &data)
}
int pos = 0;
TagLib::uint embPos = 0;
unsigned int embPos = 0;
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->isTopLevel = (data.at(pos) & 2) > 0;
d->isOrdered = (data.at(pos++) & 1) > 0;
TagLib::uint entryCount = data.at(pos++);
for(TagLib::uint i = 0; i < entryCount; i++) {
unsigned int entryCount = data.at(pos++);
for(unsigned int i = 0; i < entryCount; i++) {
ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->childElements.append(childElementID);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,30 +31,27 @@ using namespace ID3v2;
class Footer::FooterPrivate
{
public:
static const uint size = 10;
};
Footer::Footer()
Footer::Footer() :
d(0)
{
}
Footer::~Footer()
{
}
TagLib::uint Footer::size()
unsigned int Footer::size()
{
return FooterPrivate::size;
return 10;
}
ByteVector Footer::render(const Header *header) const
{
ByteVector headerData = header->render();
headerData[0] = '3';
headerData[1] = 'D';
headerData[2] = 'I';
return headerData;
ByteVector headerData = header->render();
headerData[0] = '3';
headerData[1] = 'D';
headerData[2] = 'I';
return headerData;
}

View File

@ -62,7 +62,7 @@ namespace TagLib {
/*!
* 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.

View File

@ -23,22 +23,16 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if HAVE_ZLIB
#include <zlib.h>
#endif
#include <bitset>
#include <tdebug.h>
#include <tstringlist.h>
#include <tzlib.h>
#include "id3v2tag.h"
#include "id3v2frame.h"
#include "id3v2synchdata.h"
#include "tpropertymap.h"
#include "frames/textidentificationframe.h"
#include "frames/urllinkframe.h"
@ -85,22 +79,22 @@ namespace
// static methods
////////////////////////////////////////////////////////////////////////////////
TagLib::uint Frame::headerSize()
unsigned int Frame::headerSize()
{
return Header::size();
}
TagLib::uint Frame::headerSize(uint version)
unsigned int Frame::headerSize(unsigned int version)
{
return Header::size(version);
}
ByteVector Frame::textDelimiter(String::Type t)
{
ByteVector d = char(0);
if(t == String::UTF16 || t == String::UTF16BE || t == String::UTF16LE)
d.append(char(0));
return d;
return ByteVector(2, '\0');
else
return ByteVector(1, '\0');
}
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
ByteVector frameID = keyToFrameID(key);
if(!frameID.isNull()) {
if(frameID[0] == 'T'){ // text frame
if(!frameID.isEmpty()) {
// 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);
frame->setText(values);
return frame;
@ -169,10 +164,10 @@ ByteVector Frame::frameID() const
if(d->header)
return d->header->frameID();
else
return ByteVector::null;
return ByteVector();
}
TagLib::uint Frame::size() const
unsigned int Frame::size() const
{
if(d->header)
return d->header->frameSize();
@ -240,68 +235,31 @@ void Frame::parse(const ByteVector &data)
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;
uint frameDataLength = size();
unsigned int frameDataOffset = headerSize;
unsigned int frameDataLength = size();
if(d->header->compression() || d->header->dataLengthIndicator()) {
frameDataLength = SynchData::toUInt(frameData.mid(headerSize, 4));
frameDataOffset += 4;
}
#if HAVE_ZLIB
if(d->header->compression() &&
!d->header->encryption())
{
if(zlib::isAvailable() && d->header->compression() && !d->header->encryption()) {
if(frameData.size() <= frameDataOffset) {
debug("Compressed frame doesn't have enough data to decode");
return ByteVector();
}
z_stream stream = {};
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())
const ByteVector outData = zlib::decompress(frameData.mid(frameDataOffset));
if(!outData.isEmpty() && frameDataLength != outData.size()) {
debug("frameDataLength does not match the data length returned by zlib");
}
return data;
return outData;
}
else
#endif
return frameData.mid(frameDataOffset, frameDataLength);
return frameData.mid(frameDataOffset, frameDataLength);
}
String Frame::readStringField(const ByteVector &data, String::Type encoding, int *position)
@ -316,7 +274,7 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int
int end = data.find(delimiter, *position, delimiter.size());
if(end < *position)
return String::null;
return String();
String str;
if(encoding == String::Latin1)
@ -334,7 +292,7 @@ String::Type Frame::checkEncoding(const StringList &fields, String::Type encodin
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)
return String::UTF16;
@ -363,161 +321,145 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
return checkEncoding(fields, encoding, header()->version());
}
static const TagLib::uint frameTranslationSize = 51;
static const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
{ "TBPM", "BPM" },
{ "TCOM", "COMPOSER" },
{ "TCON", "GENRE" },
{ "TCOP", "COPYRIGHT" },
{ "TDEN", "ENCODINGTIME" },
{ "TDLY", "PLAYLISTDELAY" },
{ "TDOR", "ORIGINALDATE" },
{ "TDRC", "DATE" },
// { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
{ "TDRL", "RELEASEDATE" },
{ "TDTG", "TAGGINGDATE" },
{ "TENC", "ENCODEDBY" },
{ "TEXT", "LYRICIST" },
{ "TFLT", "FILETYPE" },
//{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
{ "TIT1", "CONTENTGROUP" },
{ "TIT2", "TITLE"},
{ "TIT3", "SUBTITLE" },
{ "TKEY", "INITIALKEY" },
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
{ "TMED", "MEDIA" },
{ "TMOO", "MOOD" },
{ "TOAL", "ORIGINALALBUM" },
{ "TOFN", "ORIGINALFILENAME" },
{ "TOLY", "ORIGINALLYRICIST" },
{ "TOPE", "ORIGINALARTIST" },
{ "TOWN", "OWNER" },
{ "TPE1", "ARTIST"},
{ "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
{ "TPE3", "CONDUCTOR" },
{ "TPE4", "REMIXER" }, // could also be ARRANGER
{ "TPOS", "DISCNUMBER" },
{ "TPRO", "PRODUCEDNOTICE" },
{ "TPUB", "LABEL" },
{ "TRCK", "TRACKNUMBER" },
{ "TRSN", "RADIOSTATION" },
{ "TRSO", "RADIOSTATIONOWNER" },
{ "TSOA", "ALBUMSORT" },
{ "TSOP", "ARTISTSORT" },
{ "TSOT", "TITLESORT" },
{ "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes
{ "TSRC", "ISRC" },
{ "TSSE", "ENCODING" },
// URL frames
{ "WCOP", "COPYRIGHTURL" },
{ "WOAF", "FILEWEBPAGE" },
{ "WOAR", "ARTISTWEBPAGE" },
{ "WOAS", "AUDIOSOURCEWEBPAGE" },
{ "WORS", "RADIOSTATIONWEBPAGE" },
{ "WPAY", "PAYMENTWEBPAGE" },
{ "WPUB", "PUBLISHERWEBPAGE" },
//{ "WXXX", "URL"}, handled specially
// Other frames
{ "COMM", "COMMENT" },
//{ "USLT", "LYRICS" }, handled specially
};
static const TagLib::uint txxxFrameTranslationSize = 8;
static const char *txxxFrameTranslation[][2] = {
{ "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
{ "Acoustid Id", "ACOUSTID_ID" },
{ "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" },
{ "MusicIP PUID", "MUSICIP_PUID" },
};
Map<ByteVector, String> &idMap()
namespace
{
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;
}
const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
{ "TBPM", "BPM" },
{ "TCOM", "COMPOSER" },
{ "TCON", "GENRE" },
{ "TCOP", "COPYRIGHT" },
{ "TDEN", "ENCODINGTIME" },
{ "TDLY", "PLAYLISTDELAY" },
{ "TDOR", "ORIGINALDATE" },
{ "TDRC", "DATE" },
// { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
{ "TDRL", "RELEASEDATE" },
{ "TDTG", "TAGGINGDATE" },
{ "TENC", "ENCODEDBY" },
{ "TEXT", "LYRICIST" },
{ "TFLT", "FILETYPE" },
//{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
{ "TIT1", "CONTENTGROUP" },
{ "TIT2", "TITLE"},
{ "TIT3", "SUBTITLE" },
{ "TKEY", "INITIALKEY" },
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
{ "TMED", "MEDIA" },
{ "TMOO", "MOOD" },
{ "TOAL", "ORIGINALALBUM" },
{ "TOFN", "ORIGINALFILENAME" },
{ "TOLY", "ORIGINALLYRICIST" },
{ "TOPE", "ORIGINALARTIST" },
{ "TOWN", "OWNER" },
{ "TPE1", "ARTIST"},
{ "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
{ "TPE3", "CONDUCTOR" },
{ "TPE4", "REMIXER" }, // could also be ARRANGER
{ "TPOS", "DISCNUMBER" },
{ "TPRO", "PRODUCEDNOTICE" },
{ "TPUB", "LABEL" },
{ "TRCK", "TRACKNUMBER" },
{ "TRSN", "RADIOSTATION" },
{ "TRSO", "RADIOSTATIONOWNER" },
{ "TSOA", "ALBUMSORT" },
{ "TSOP", "ARTISTSORT" },
{ "TSOT", "TITLESORT" },
{ "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes
{ "TSRC", "ISRC" },
{ "TSSE", "ENCODING" },
// URL frames
{ "WCOP", "COPYRIGHTURL" },
{ "WOAF", "FILEWEBPAGE" },
{ "WOAR", "ARTISTWEBPAGE" },
{ "WOAS", "AUDIOSOURCEWEBPAGE" },
{ "WORS", "RADIOSTATIONWEBPAGE" },
{ "WPAY", "PAYMENTWEBPAGE" },
{ "WPUB", "PUBLISHERWEBPAGE" },
//{ "WXXX", "URL"}, handled specially
// Other frames
{ "COMM", "COMMENT" },
//{ "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]);
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;
}
const char *txxxFrameTranslation[][2] = {
{ "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" },
{ "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" },
{ "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" },
{ "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" },
{ "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" },
{ "ACOUSTID ID", "ACOUSTID_ID" },
{ "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" },
{ "MUSICIP PUID", "MUSICIP_PUID" },
};
const size_t txxxFrameTranslationSize = sizeof(txxxFrameTranslation) / sizeof(txxxFrameTranslation[0]);
// 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)
{"TDAT", "TDRC"}, // 2.3 -> 2.4
{"TYER", "TDRC"}, // 2.3 -> 2.4
{"TIME", "TDRC"}, // 2.3 -> 2.4
};
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;
// list of deprecated frames and their successors
const char *deprecatedFrames[][2] = {
{"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
{"TDAT", "TDRC"}, // 2.3 -> 2.4
{"TYER", "TDRC"}, // 2.3 -> 2.4
{"TIME", "TDRC"}, // 2.3 -> 2.4
};
const size_t deprecatedFramesSize = sizeof(deprecatedFrames) / sizeof(deprecatedFrames[0]);;
}
String Frame::frameIDToKey(const ByteVector &id)
{
Map<ByteVector, String> &m = idMap();
if(m.contains(id))
return m[id];
if(deprecationMap().contains(id))
return m[deprecationMap()[id]];
return String::null;
ByteVector id24 = id;
for(size_t i = 0; i < deprecatedFramesSize; ++i) {
if(id24 == deprecatedFrames[i][0]) {
id24 = deprecatedFrames[i][1];
break;
}
}
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)
{
static Map<String, ByteVector> m;
if(m.isEmpty())
for(size_t i = 0; i < frameTranslationSize; ++i)
m[frameTranslation[i][1]] = frameTranslation[i][0];
if(m.contains(s.upper()))
return m[s];
return ByteVector::null;
const String key = s.upper();
for(size_t i = 0; i < frameTranslationSize; ++i) {
if(key == frameTranslation[i][1])
return frameTranslation[i][0];
}
return ByteVector();
}
String Frame::txxxToKey(const String &description)
{
Map<String, String> &m = txxxMap();
String d = description.upper();
if(m.contains(d))
return m[d];
const String d = description.upper();
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
if(d == txxxFrameTranslation[i][0])
return txxxFrameTranslation[i][1];
}
return d;
}
String Frame::keyToTXXX(const String &s)
{
static Map<String, String> m;
if(m.isEmpty())
for(size_t i = 0; i < txxxFrameTranslationSize; ++i)
m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0];
if(m.contains(s.upper()))
return m[s];
const String key = s.upper();
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
if(key == txxxFrameTranslation[i][1])
return txxxFrameTranslation[i][0];
}
return s;
}
@ -532,7 +474,8 @@ PropertyMap Frame::asProperties() const
// workaround until this function is virtual
if(id == "TXXX")
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();
else if(id == "WXXX")
return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties();
@ -552,7 +495,6 @@ PropertyMap Frame::asProperties() const
void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
PropertyMap &tiplProperties, PropertyMap &tmclProperties)
{
singleFrameProperties.clear();
tiplProperties.clear();
tmclProperties.clear();
@ -587,8 +529,8 @@ public:
{}
ByteVector frameID;
uint frameSize;
uint version;
unsigned int frameSize;
unsigned int version;
// flags
@ -606,12 +548,12 @@ public:
// static members (Frame::Header)
////////////////////////////////////////////////////////////////////////////////
TagLib::uint Frame::Header::size()
unsigned int Frame::Header::size()
{
return size(4);
}
TagLib::uint Frame::Header::size(uint version)
unsigned int Frame::Header::size(unsigned int version)
{
switch(version) {
case 0:
@ -635,7 +577,7 @@ Frame::Header::Header(const ByteVector &data, bool synchSafeInts)
setData(data, synchSafeInts);
}
Frame::Header::Header(const ByteVector &data, uint version)
Frame::Header::Header(const ByteVector &data, unsigned int version)
{
d = new HeaderPrivate;
setData(data, version);
@ -648,10 +590,10 @@ Frame::Header::~Header()
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;
@ -792,22 +734,22 @@ void Frame::Header::setFrameID(const ByteVector &id)
d->frameID = id.mid(0, 4);
}
TagLib::uint Frame::Header::frameSize() const
unsigned int Frame::Header::frameSize() const
{
return d->frameSize;
}
void Frame::Header::setFrameSize(uint size)
void Frame::Header::setFrameSize(unsigned int size)
{
d->frameSize = size;
}
TagLib::uint Frame::Header::version() const
unsigned int Frame::Header::version() const
{
return d->version;
}
void Frame::Header::setVersion(TagLib::uint version)
void Frame::Header::setVersion(unsigned int version)
{
d->version = version;
}

View File

@ -79,7 +79,7 @@ namespace TagLib {
/*!
* Returns the size of the frame.
*/
uint size() const;
unsigned int size() const;
/*!
* 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
* 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.
*
* \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
@ -242,7 +242,7 @@ namespace TagLib {
*/
// BIC: remove and make non-static
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
@ -264,13 +264,13 @@ namespace TagLib {
/*!
* 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 &);
/*!
* 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 &);
@ -343,7 +343,7 @@ namespace TagLib {
*
* \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.
@ -362,7 +362,7 @@ namespace TagLib {
* Sets the data for the Header. \a version should indicate the ID3v2
* 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>)
@ -384,24 +384,24 @@ namespace TagLib {
* Returns the size of the frame data portion, as set when setData() was
* called or set explicitly via setFrameSize().
*/
uint frameSize() const;
unsigned int frameSize() const;
/*!
* 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
* 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
* correct parsing/rendering of frame data.
*/
void setVersion(uint version);
void setVersion(unsigned int version);
/*!
* 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
* 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
@ -419,7 +419,7 @@ namespace TagLib {
*
* \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.

View File

@ -23,11 +23,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <tdebug.h>
#include <tzlib.h>
#include "id3v2framefactory.h"
#include "id3v2synchdata.h"
@ -49,10 +46,45 @@
#include "frames/eventtimingcodesframe.h"
#include "frames/chapterframe.h"
#include "frames/tableofcontentsframe.h"
#include "frames/podcastframe.h"
using namespace TagLib;
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
{
public:
@ -83,10 +115,10 @@ FrameFactory *FrameFactory::instance()
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;
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
{
ByteVector data = origData;
uint version = tagHeader->majorVersion();
unsigned int version = tagHeader->majorVersion();
Frame::Header *header = new Frame::Header(data, version);
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.
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())
{
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
// as unknown frames.
#if !defined(HAVE_ZLIB) || HAVE_ZLIB == 0
if(header->compression()) {
if(!zlib::isAvailable() && header->compression()) {
debug("Compressed frames are currently not supported.");
return new UnknownFrame(data, header);
}
#endif
if(header->encryption()) {
debug("Encrypted frames are currently not supported.");
return new UnknownFrame(data, header);
@ -167,7 +198,8 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
// 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"
? new TextIdentificationFrame(data, header)
@ -287,6 +319,11 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
if(frameID == "CTOC")
return new TableOfContentsFrame(tagHeader, data, header);
// Apple proprietary PCST (Podcast)
if(frameID == "PCST")
return new PodcastFrame(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("TDAT").size() == 1)
{
TextIdentificationFrame *trdc =
TextIdentificationFrame *tdrc =
static_cast<TextIdentificationFrame *>(tag->frameList("TDRC").front());
UnknownFrame *tdat =
static_cast<UnknownFrame *>(tag->frameList("TDAT").front());
UnknownFrame *tdat = static_cast<UnknownFrame *>(tag->frameList("TDAT").front());
if(trdc->fieldList().size() == 1 &&
trdc->fieldList().front().size() == 4 &&
if(tdrc->fieldList().size() == 1 &&
tdrc->fieldList().front().size() == 4 &&
tdat->data().size() >= 5)
{
String date(tdat->data().mid(1), String::Type(tdat->data()[0]));
if(date.length() == 4)
trdc->setText(trdc->toString() + '-' + date.substr(2, 2) + '-' + date.substr(0, 2));
if(date.length() == 4) {
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
////////////////////////////////////////////////////////////////////////////////
FrameFactory::FrameFactory()
FrameFactory::FrameFactory() :
d(new FrameFactoryPrivate())
{
d = new FrameFactoryPrivate;
}
FrameFactory::~FrameFactory()
@ -337,9 +383,94 @@ FrameFactory::~FrameFactory()
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
{
TagLib::ByteVector frameID = header->frameID();
const ByteVector frameID = header->frameID();
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
// the frames to their 4 byte ID3v2.4 equivalent.
convertFrame("BUF", "RBUF", header);
convertFrame("CNT", "PCNT", header);
convertFrame("COM", "COMM", header);
convertFrame("CRA", "AENC", header);
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);
for(size_t i = 0; i < frameConversion2Size; ++i) {
if(frameID == frameConversion2[i][0]) {
header->setFrameID(frameConversion2[i][1]);
break;
}
}
break;
}
@ -440,9 +516,12 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
return false;
}
convertFrame("TORY", "TDOR", header);
convertFrame("TYER", "TDRC", header);
convertFrame("IPLS", "TIPL", header);
for(size_t i = 0; i < frameConversion3Size; ++i) {
if(frameID == frameConversion3[i][0]) {
header->setFrameID(frameConversion3[i][1]);
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
// version 1.1 where TRDC was used for the year rather than TDRC.
convertFrame("TRDC", "TDRC", header);
if(frameID == "TRDC")
header->setFrameID("TDRC");
break;
}
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
* not directly supported by TagLib to your application. To do so you would
* subclass this factory reimplement createFrame(). Then by setting your
* factory to be the default factory in ID3v2::Tag constructor or with
* MPEG::File::setID3v2FrameFactory() you can implement behavior that will
* allow for new ID3v2::Frame subclasses (also provided by you) to be used.
* factory to be the default factory in ID3v2::Tag constructor you can
* implement behavior that will allow for new ID3v2::Frame subclasses (also
* provided by you) to be used.
*
* 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
@ -84,7 +84,7 @@ namespace TagLib {
* \deprecated Please use the method below that accepts a ID3v2::Header
* 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
@ -152,16 +152,6 @@ namespace TagLib {
FrameFactory(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;
class FrameFactoryPrivate;

View File

@ -39,36 +39,33 @@ using namespace ID3v2;
class Header::HeaderPrivate
{
public:
HeaderPrivate() : majorVersion(4),
revisionNumber(0),
unsynchronisation(false),
extendedHeader(false),
experimentalIndicator(false),
footerPresent(false),
tagSize(0) {}
HeaderPrivate() :
majorVersion(4),
revisionNumber(0),
unsynchronisation(false),
extendedHeader(false),
experimentalIndicator(false),
footerPresent(false),
tagSize(0) {}
~HeaderPrivate() {}
uint majorVersion;
uint revisionNumber;
unsigned int majorVersion;
unsigned int revisionNumber;
bool unsynchronisation;
bool extendedHeader;
bool experimentalIndicator;
bool footerPresent;
uint tagSize;
static const uint size = 10;
unsigned int tagSize;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
TagLib::uint Header::size()
unsigned int Header::size()
{
return HeaderPrivate::size;
return 10;
}
ByteVector Header::fileIdentifier()
@ -80,14 +77,14 @@ ByteVector Header::fileIdentifier()
// 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);
}
@ -96,17 +93,17 @@ Header::~Header()
delete d;
}
TagLib::uint Header::majorVersion() const
unsigned int Header::majorVersion() const
{
return d->majorVersion;
}
void Header::setMajorVersion(TagLib::uint version)
void Header::setMajorVersion(unsigned int version)
{
d->majorVersion = version;
}
TagLib::uint Header::revisionNumber() const
unsigned int Header::revisionNumber() const
{
return d->revisionNumber;
}
@ -131,20 +128,20 @@ bool Header::footerPresent() const
return d->footerPresent;
}
TagLib::uint Header::tagSize() const
unsigned int Header::tagSize() const
{
return d->tagSize;
}
TagLib::uint Header::completeTagSize() const
unsigned int Header::completeTagSize() const
{
if(d->footerPresent)
return d->tagSize + d->size + Footer::size();
return d->tagSize + size() + Footer::size();
else
return d->tagSize + d->size;
return d->tagSize + size();
}
void Header::setTagSize(uint s)
void Header::setTagSize(unsigned int s)
{
d->tagSize = s;
}
@ -199,7 +196,6 @@ void Header::parse(const ByteVector &data)
if(data.size() < size())
return;
// 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
// 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++) {
if(uchar(*it) >= 128) {
if(static_cast<unsigned char>(*it) >= 128) {
d->tagSize = 0;
debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128.");
return;

View File

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

View File

@ -30,9 +30,9 @@
using namespace TagLib;
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;
int last = data.size() > 4 ? 3 : data.size() - 1;
@ -62,23 +62,37 @@ TagLib::uint SynchData::toUInt(const ByteVector &data)
return sum;
}
ByteVector SynchData::fromUInt(uint value)
ByteVector SynchData::fromUInt(unsigned int value)
{
ByteVector v(4, 0);
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;
}
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));
pattern[0] = '\xFF';
pattern[1] = '\x00';
ByteVector result(data.size());
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
* 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.
*/
TAGLIB_EXPORT ByteVector fromUInt(uint value);
TAGLIB_EXPORT ByteVector fromUInt(unsigned int value);
/*!
* Convert the data from unsynchronized data to its original format.

View File

@ -23,21 +23,19 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <algorithm>
#include "tfile.h"
#include <tfile.h>
#include <tbytevector.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include "id3v2tag.h"
#include "id3v2header.h"
#include "id3v2extendedheader.h"
#include "id3v2footer.h"
#include "id3v2synchdata.h"
#include "tbytevector.h"
#include "id3v1genres.h"
#include "tpropertymap.h"
#include "tdebug.h"
#include "frames/textidentificationframe.h"
#include "frames/commentsframe.h"
@ -49,43 +47,46 @@
using namespace TagLib;
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
{
public:
TagPrivate() : file(0), tagOffset(-1), extendedHeader(0), footer(0), paddingSize(0)
TagPrivate() :
file(0),
tagOffset(0),
extendedHeader(0),
footer(0)
{
frameList.setAutoDelete(true);
}
~TagPrivate()
{
delete extendedHeader;
delete footer;
}
const FrameFactory *factory;
File *file;
long tagOffset;
const FrameFactory *factory;
Header header;
ExtendedHeader *extendedHeader;
Footer *footer;
int paddingSize;
FrameListMap frameListMap;
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
////////////////////////////////////////////////////////////////////////////////
@ -107,20 +108,20 @@ String Latin1StringHandler::parse(const ByteVector &data) const
// public members
////////////////////////////////////////////////////////////////////////////////
ID3v2::Tag::Tag() : TagLib::Tag()
ID3v2::Tag::Tag() :
TagLib::Tag(),
d(new TagPrivate())
{
d = new TagPrivate;
d->factory = FrameFactory::instance();
}
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->tagOffset = tagOffset;
d->factory = factory;
read();
}
@ -130,26 +131,25 @@ ID3v2::Tag::~Tag()
delete d;
}
String ID3v2::Tag::title() const
{
if(!d->frameListMap["TIT2"].isEmpty())
return d->frameListMap["TIT2"].front()->toString();
return String::null;
return String();
}
String ID3v2::Tag::artist() const
{
if(!d->frameListMap["TPE1"].isEmpty())
return d->frameListMap["TPE1"].front()->toString();
return String::null;
return String();
}
String ID3v2::Tag::album() const
{
if(!d->frameListMap["TALB"].isEmpty())
return d->frameListMap["TALB"].front()->toString();
return String::null;
return String();
}
String ID3v2::Tag::comment() const
@ -157,7 +157,7 @@ String ID3v2::Tag::comment() const
const FrameList &comments = d->frameListMap["COMM"];
if(comments.isEmpty())
return String::null;
return String();
for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it)
{
@ -179,7 +179,7 @@ String ID3v2::Tag::genre() const
if(d->frameListMap["TCON"].isEmpty() ||
!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
@ -213,14 +213,14 @@ String ID3v2::Tag::genre() const
return genres.toString();
}
TagLib::uint ID3v2::Tag::year() const
unsigned int ID3v2::Tag::year() const
{
if(!d->frameListMap["TDRC"].isEmpty())
return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt();
return 0;
}
TagLib::uint ID3v2::Tag::track() const
unsigned int ID3v2::Tag::track() const
{
if(!d->frameListMap["TRCK"].isEmpty())
return d->frameListMap["TRCK"].front()->toString().toInt();
@ -284,7 +284,7 @@ void ID3v2::Tag::setGenre(const String &s)
#endif
}
void ID3v2::Tag::setYear(uint i)
void ID3v2::Tag::setYear(unsigned int i)
{
if(i <= 0) {
removeFrames("TDRC");
@ -293,7 +293,7 @@ void ID3v2::Tag::setYear(uint i)
setTextFrame("TDRC", String::number(i));
}
void ID3v2::Tag::setTrack(uint i)
void ID3v2::Tag::setTrack(unsigned int i)
{
if(i <= 0) {
removeFrames("TRCK");
@ -466,10 +466,18 @@ ByteVector ID3v2::Tag::render() const
void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
{
#ifdef NO_ITUNES_HACKS
const char *unsupportedFrames[] = {
"ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
"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 *frameTDRC = 0;
ID3v2::TextIdentificationFrame *frameTIPL = 0;
@ -540,14 +548,14 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
StringList people;
if(frameTMCL) {
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+1]);
}
}
if(frameTIPL) {
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+1]);
}
@ -558,7 +566,6 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
}
}
ByteVector ID3v2::Tag::render(int version) const
{
// 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
// padding, but does not include the tag's header or footer.
ByteVector tagData;
if(version != 3 && version != 4) {
debug("Unknown ID3v2 version, using ID3v2.4");
version = 4;
@ -575,7 +580,7 @@ ByteVector ID3v2::Tag::render(int version) const
// 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;
newFrames.setAutoDelete(true);
@ -588,6 +593,12 @@ ByteVector ID3v2::Tag::render(int version) const
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++) {
(*it)->header()->setVersion(version);
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.
// 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()) {
paddingSize = d->header.tagSize() - tagData.size();
if(paddingSize <= 0) {
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)
paddingSize = DefaultPaddingSize;
}
if(paddingSize > threshold)
paddingSize = MinPaddingSize;
}
tagData.append(ByteVector(paddingSize, '\0'));
tagData.resize(static_cast<unsigned int>(tagData.size() + paddingSize), '\0');
// Set the version and data size.
d->header.setMajorVersion(version);
d->header.setTagSize(tagData.size());
d->header.setTagSize(tagData.size() - Header::size());
// 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()
{
return TagPrivate::stringHandler;
return stringHandler;
}
void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
{
if(handler)
TagPrivate::stringHandler = handler;
stringHandler = handler;
else
TagPrivate::stringHandler = &defaultStringHandler;
stringHandler = &defaultStringHandler;
}
////////////////////////////////////////////////////////////////////////////////
@ -651,18 +669,43 @@ void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
void ID3v2::Tag::read()
{
if(d->file && d->file->isOpen()) {
if(!d->file)
return;
d->file->seek(d->tagOffset);
d->header.setData(d->file->readBlock(Header::size()));
if(!d->file->isOpen())
return;
// if the tag size is 0, then this is an invalid tag (tags must contain at
// least one frame)
d->file->seek(d->tagOffset);
d->header.setData(d->file->readBlock(Header::size()));
if(d->header.tagSize() == 0)
return;
// If the tag size is 0, then this is an invalid tag (tags must contain at
// least one frame)
if(d->header.tagSize() != 0)
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)
data = SynchData::decode(data);
uint frameDataPosition = 0;
uint frameDataLength = data.size();
unsigned int frameDataPosition = 0;
unsigned int frameDataLength = data.size();
// 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.");
}
d->paddingSize = frameDataLength - frameDataPosition;
break;
}

View File

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

View File

@ -24,6 +24,7 @@
***************************************************************************/
#include <tagunion.h>
#include <tagutils.h>
#include <id3v2tag.h>
#include <id3v2header.h>
#include <id3v1tag.h>
@ -33,29 +34,11 @@
#include "mpegfile.h"
#include "mpegheader.h"
#include "mpegutils.h"
#include "tpropertymap.h"
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
{
enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
@ -64,20 +47,14 @@ namespace
class MPEG::File::FilePrivate
{
public:
FilePrivate(ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
ID3v2FrameFactory(frameFactory),
ID3v2Location(-1),
ID3v2OriginalSize(0),
APELocation(-1),
APEFooterLocation(-1),
APEOriginalSize(0),
ID3v1Location(-1),
hasID3v2(false),
hasID3v1(false),
hasAPE(false),
properties(0)
{
}
properties(0) {}
~FilePrivate()
{
@ -87,23 +64,15 @@ public:
const ID3v2::FrameFactory *ID3v2FrameFactory;
long ID3v2Location;
uint ID3v2OriginalSize;
long ID3v2OriginalSize;
long APELocation;
long APEFooterLocation;
uint APEOriginalSize;
long APEOriginalSize;
long ID3v1Location;
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;
};
@ -149,33 +118,22 @@ TagLib::Tag *MPEG::File::tag() const
PropertyMap MPEG::File::properties() const
{
// once Tag::properties() is virtual, this case distinction could actually be done
// 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();
return d->tag.properties();
}
void MPEG::File::removeUnsupportedProperties(const StringList &properties)
{
if(d->hasID3v2)
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);
d->tag.removeUnsupportedProperties(properties);
}
PropertyMap MPEG::File::setProperties(const PropertyMap &properties)
{
if(d->hasID3v1)
// 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);
// update ID3v1 tag if it exists, but ignore the return value
if(ID3v1Tag())
ID3v1Tag()->setProperties(properties);
return ID3v2Tag(true)->setProperties(properties);
}
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)
{
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()) {
debug("MPEG::File::save() -- File is read only.");
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.
if (duplicateTags) {
if(duplicateTags) {
// Copy the values from the tag that does exist into the new tag,
// 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);
}
bool success = true;
// Remove all the tags not going to be saved.
if(stripOthers)
strip(~tags, false);
if(ID3v2 & tags) {
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;
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->ID3v1Location = findID3v1();
// APE tag location has changed, update if it exists
if(APETag())
findAPE();
d->ID3v2OriginalSize = data.size();
}
else {
// ID3v2 tag is empty. Remove the old one.
strip(ID3v2, false);
}
else if(stripOthers)
success = strip(ID3v2, false) && success;
}
else if(d->hasID3v2 && stripOthers)
success = strip(ID3v2) && success;
if(ID3v1 & tags) {
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->hasAPE)
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;
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
}
else {
seek(0, End);
d->APELocation = 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;
d->ID3v1Location = tell();
}
writeBlock(ID3v1Tag()->render());
}
else {
// ID3v1 tag is empty. Remove the old one.
strip(ID3v1, false);
}
}
else if(d->hasAPE && stripOthers)
success = strip(APE, false) && success;
return success;
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)
@ -337,44 +298,39 @@ bool MPEG::File::strip(int tags, bool freeMemory)
return false;
}
if((tags & ID3v2) && d->hasID3v2) {
if((tags & ID3v2) && d->ID3v2Location >= 0) {
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->ID3v2OriginalSize = 0;
d->hasID3v2 = false;
if(freeMemory)
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);
d->ID3v1Location = -1;
d->hasID3v1 = false;
if(freeMemory)
d->tag.set(ID3v1Index, 0);
}
if((tags & APE) && d->hasAPE) {
if((tags & APE) && d->APELocation >= 0) {
removeBlock(d->APELocation, d->APEOriginalSize);
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APEOriginalSize;
d->APELocation = -1;
d->APEFooterLocation = -1;
d->hasAPE = false;
if(d->hasID3v1) {
if(d->ID3v1Location > d->APELocation)
d->ID3v1Location -= d->APEOriginalSize;
}
d->APEOriginalSize = 0;
if(freeMemory)
d->tag.set(APEIndex, 0);
@ -404,7 +360,7 @@ long MPEG::File::nextFrameOffset(long position)
if(foundLastSyncPattern && secondSynchByte(buffer[0]))
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]))
return position + i;
}
@ -420,7 +376,7 @@ long MPEG::File::previousFrameOffset(long position)
ByteVector buffer;
while (position > 0) {
long size = ulong(position) < bufferSize() ? position : bufferSize();
long size = std::min<long>(position, bufferSize());
position -= size;
seek(position);
@ -446,45 +402,39 @@ long MPEG::File::firstFrameOffset()
{
long position = 0;
if(hasID3v2Tag()) {
if(hasID3v2Tag())
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);
}
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
{
return d->hasID3v1;
return (d->ID3v1Location >= 0);
}
bool MPEG::File::hasID3v2Tag() const
{
return d->hasID3v2;
return (d->ID3v2Location >= 0);
}
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
d->ID3v2Location = findID3v2(0);
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(ID3v2Index, 0);
else
d->hasID3v2 = true;
}
// 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->hasID3v1 = true;
}
// Look for an APE tag
findAPE();
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
if(d->APELocation >= 0) {
d->tag.set(APEIndex, new APE::Tag(this, d->APEFooterLocation));
d->tag.set(APEIndex, new APE::Tag(this, d->APELocation));
d->APEOriginalSize = APETag()->footer()->completeTagSize();
d->hasAPE = true;
d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize;
}
if(readProperties)
@ -538,146 +478,38 @@ void MPEG::File::read(bool readProperties)
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
// 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())
return -1;
if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) {
// An ID3v2 tag or MPEG frame is most likely be at the beginning of the file.
// The position in the file that the current buffer starts at.
const ByteVector headerID = ID3v2::Header::fileIdentifier();
long bufferOffset = 0;
ByteVector buffer;
seek(0);
// These variables are used to keep track of a partial match that happens at
// the end of a buffer.
const ByteVector data = readBlock(headerID.size());
if(data.size() < headerID.size())
return -1;
int previousPartialMatch = -1;
bool previousPartialSynchMatch = false;
if(data == headerID)
return 0;
// Save the location of the current read pointer. We will restore the
// position using seek() before all returns.
if(firstSyncByte(data[0]) && secondSynchByte(data[1]))
return -1;
long originalPosition = tell();
// Look for the entire file, if neither an MEPG frame or ID3v2 tag was found
// at the beginning of the file.
// We don't care about the inefficiency of the code, since this is a seldom case.
// Start the search at the offset.
const long tagOffset = find(headerID);
if(tagOffset < 0)
return -1;
seek(offset);
const long frameOffset = firstFrameOffset();
if(frameOffset < tagOffset)
return -1;
// 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;
if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) {
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
long location = buffer.find(ID3v2::Header::fileIdentifier());
if(location >= 0) {
seek(originalPosition);
return offset + bufferOffset + location;
}
int firstSynchByte = buffer.find(char(uchar(255)));
// 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;
}
else {
// We found 11111111 at the end of the current buffer indicating a
// partial match of the synch pattern. The find() below should
// 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);
}
return -1;
}
long MPEG::File::findID3v1()
{
if(isValid()) {
seek(-128, End);
long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
}
return -1;
}
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;
return tagOffset;
}

View File

@ -175,9 +175,6 @@ namespace TagLib {
* If you would like more granular control over the content of the tags,
* 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)
*/
virtual bool save();
@ -190,9 +187,6 @@ namespace TagLib {
* 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
* 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);
@ -204,9 +198,6 @@ namespace TagLib {
* 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
* 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
bool save(int tags, bool stripOthers);
@ -222,9 +213,6 @@ namespace TagLib {
*
* The \a id3v2Version parameter specifies the version of the saved
* 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
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 --
* 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
bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags);
@ -390,9 +375,7 @@ namespace TagLib {
File &operator=(const File &);
void read(bool readProperties);
long findID3v2(long offset);
long findID3v1();
void findAPE();
long findID3v2();
class FilePrivate;
FilePrivate *d;

View File

@ -23,14 +23,14 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <bitset>
#include <tbytevector.h>
#include <tstring.h>
#include <tfile.h>
#include <tdebug.h>
#include "trefcounter.h"
#include <trefcounter.h>
#include "mpegheader.h"
#include "mpegutils.h"
using namespace TagLib;
@ -42,6 +42,7 @@ public:
version(Version1),
layer(0),
protectionEnabled(false),
bitrate(0),
sampleRate(0),
isPadded(false),
channelMode(Stereo),
@ -68,20 +69,27 @@ public:
// public members
////////////////////////////////////////////////////////////////////////////////
MPEG::Header::Header(const ByteVector &data)
MPEG::Header::Header(const ByteVector &data) :
d(new HeaderPrivate())
{
d = new HeaderPrivate;
parse(data);
debug("MPEG::Header::Header() - This constructor is no longer used.");
}
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();
}
MPEG::Header::~Header()
{
if (d->deref())
if(d->deref())
delete d;
}
@ -162,41 +170,54 @@ MPEG::Header &MPEG::Header::operator=(const Header &h)
// 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) {
debug("MPEG::Header::parse() -- First byte did not match MPEG synch.");
file->seek(offset);
const ByteVector data = file->readBlock(4);
if(data.size() < 4) {
debug("MPEG::Header::parse() -- data is too short for an MPEG frame header.");
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(!flags[23] || !flags[22] || !flags[21]) {
debug("MPEG::Header::parse() -- Second byte did not match MPEG synch.");
if(!firstSyncByte(data[0]) || !secondSynchByte(data[1])) {
debug("MPEG::Header::parse() -- MPEG header did not match MPEG synch.");
return;
}
// 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;
else if(flags[20] && !flags[19])
else if(versionBits == 2)
d->version = Version2;
else if(flags[20] && flags[19])
else if(versionBits == 3)
d->version = Version1;
else {
debug("MPEG::Header::parse() -- Invalid MPEG version bits.");
return;
}
// Set the MPEG layer
if(!flags[18] && flags[17])
d->layer = 3;
else if(flags[18] && !flags[17])
d->layer = 2;
else if(flags[18] && flags[17])
d->layer = 1;
const int layerBits = (static_cast<unsigned char>(data[1]) >> 1) & 0x03;
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
@ -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,
// 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
@ -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
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) {
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,
// 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
d->isOriginal = flags[2];
d->isCopyrighted = flags[3];
d->isPadded = flags[9];
d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0);
d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0);
d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0);
// Samples per frame
@ -273,6 +299,34 @@ void MPEG::Header::parse(const ByteVector &data)
if(d->isPadded)
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.
d->isValid = true;

View File

@ -31,6 +31,7 @@
namespace TagLib {
class ByteVector;
class File;
namespace MPEG {
@ -48,9 +49,20 @@ namespace TagLib {
public:
/*!
* Parses an MPEG header based on \a data.
*
* \deprecated
*/
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.
*/
@ -155,7 +167,7 @@ namespace TagLib {
Header &operator=(const Header &h);
private:
void parse(const ByteVector &data);
void parse(File *file, long offset, bool checkLength);
class HeaderPrivate;
HeaderPrivate *d;

View File

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

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