31 Commits

Author SHA1 Message Date
Lukáš Lalinský
aa34afda79 Merge remote-tracking branch 'TsudaKageyu/substr-bug' 2012-09-06 20:03:15 +02:00
Lukáš Lalinský
942ec58de5 Add tests for String::substr 2012-09-06 20:03:08 +02:00
Lukáš Lalinský
082a36147b Add change log, update SOVERSION 2012-09-06 19:57:10 +02:00
Lukáš Lalinský
f11b206fe8 Do not delete the IOStream object in TagLib::File 2012-09-06 19:43:52 +02:00
Lukáš Lalinský
e37f6ed752 Update CMakeLists.txt 2012-09-06 19:30:45 +02:00
Lukáš Lalinský
d2f20e8d2a Merge branch 'master' of https://github.com/moeeka/taglib 2012-09-06 19:24:22 +02:00
Rupert Daniel
f194a55c0f Updated OWNE implementaion with minor changes after pull review 2012-09-06 12:11:20 +01:00
Rupert Daniel
719187794e Implementation of the ID3v2.4 OWNE frame. 2012-09-05 16:37:46 +01:00
Tsuda Kageyu
74b94613a0 Bug fix in String::substr() 2012-09-05 19:46:52 +09:00
Lukáš Lalinský
33d0be110b Fix ambiguous reference to uint in tests 2012-09-03 19:55:55 +02:00
Tsuda Kageyu
df12b4ffc6 Refectored the FileStream constructor. 2012-09-03 01:45:28 +09:00
Tsuda Kageyu
d16c24ae21 Merge branch 'master' of https://github.com/taglib/taglib into security-warnings 2012-09-02 23:40:11 +09:00
Lukáš Lalinský
1c35918834 Merge remote-tracking branch 'TsudaKageyu/warnings' 2012-09-02 15:25:41 +02:00
Tsuda Kageyu
d163f36d35 Fix Visual C++ specific warnings about security 2012-08-23 21:57:12 +09:00
Tsuda Kageyu
590cd4c9f6 Fix warnings with VS2010 2012-08-23 20:54:18 +09:00
Tsuda Kageyu
6c0227ee13 Fix compilation errors with Visual Studio 2010 2012-08-23 19:58:21 +09:00
Lukáš Lalinský
9bb57fe0a7 Merge remote-tracking branch 'TsudaKageyu/id3v2-comment-update' 2012-08-23 11:00:32 +02:00
tsuda.kageyu@gmail.com
3fecdbf428 Comment update for ID3v2::Tag::setLatin1StringHandler() 2012-08-23 17:57:00 +09:00
Lukáš Lalinský
356c7a5d6e Merge remote-tracking branch 'TsudaKageyu/cross-border-delete' 2012-08-23 10:51:47 +02:00
tsuda.kageyu@gmail.com
4b4f70253b Comment update for ID3v1::Tag::setStringHandler() 2012-08-23 17:45:25 +09:00
Lukáš Lalinský
8b61a06fda Merge remote-tracking branch 'TsudaKageyu/id3v2-brokenlatin1-patch' 2012-08-23 10:22:43 +02:00
Lukáš Lalinský
6801ac2515 Merge remote-tracking branch 'poiru/master' 2012-08-23 10:19:08 +02:00
Lukáš Lalinský
29d17bb8e9 Merge remote-tracking branch 'supermihi/master' 2012-08-23 10:17:05 +02:00
tsuda.kageyu@gmail.com
fe8053c7d5 Support broken Latin-1 encodings in ID3V2 2012-08-23 12:19:51 +09:00
tsuda.kageyu@gmail.com
eb63ee8ec6 Remove possible cross binary boundary delete 2012-08-23 11:09:22 +09:00
Lukáš Lalinský
e86e5f906b ID3 frame IDs with 0 should be recognized as valid
http://article.gmane.org/gmane.comp.kde.devel.taglib/2275
2012-08-17 07:50:48 +02:00
Lukáš Lalinský
60e82e6694 Ignore additional files 2012-08-17 07:34:44 +02:00
Birunthan Mohanathas
fc6e02da35 Update header comments to reflect c4163a2 2012-08-02 17:25:20 +03:00
Michael Helmling
4140c5f2eb Check PropertyMap keys format-specifically instead of globally.
Instead of statically forbidding certain keys in PropertyMap, now the
setProperties() implementations of the different formats check if the
keys are valid for that particular specification and include them in
the returned PropertyMap otherwise.
This should remove an unneccessary complification for programmers since
now there's only one step, namely calling setProperties(), where
problems might occur.
Also the previous implementation leads to problems with invalid keys:
because taglib doesn't use exceptions, something like

  map.insert("FORBIDDEN KEY", "some value");

would lead to the value being inserted under String::null, which
smells like the source of strange bugs.
2012-07-30 20:52:30 +02:00
Michael Helmling
fc3fc10f60 add id3v2 frame delete test 2012-07-23 20:53:25 +02:00
BSDKaffee
3bc123aed6 - Disambiguate uint and ushort references 2012-07-15 01:57:53 -04:00
68 changed files with 895 additions and 182 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,7 @@
cmake_install.cmake
cmake_uninstall.cmake
Makefile
CTestTestfile.cmake
CMakeFiles/
*.so
*.so.*
@@ -16,6 +18,7 @@ CMakeFiles/
/config.h
/taglib.pc
/tests/test_runner
/tests/Testing
/taglib_config.h
/taglib-config
/bindings/c/taglib_c.pc

View File

@@ -56,9 +56,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE
# 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0.
# 3. If any interfaces have been added since the last public release, then increment age.
# 4. If any interfaces have been removed since the last public release, then set age to 0.
set(TAGLIB_SOVERSION_CURRENT 12)
set(TAGLIB_SOVERSION_CURRENT 13)
set(TAGLIB_SOVERSION_REVISION 0)
set(TAGLIB_SOVERSION_AGE 11)
set(TAGLIB_SOVERSION_AGE 12)
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")

17
NEWS
View File

@@ -1,6 +1,21 @@
TagLib 1.8 BETA (Jul 14, 2012)
TagLib 1.8 (Sep 6, 2012)
==============================
1.8:
* Added support for OWNE ID3 frames.
* Changed key validation in the new PropertyMap API.
* ID3v1::Tag::setStringHandler will no londer delete the previous handler,
the caller is responsible for this.
* File objects will also no longer delete the passed IOStream objects. It
should be done in the caller code after the File object is no longer
used.
* Added ID3v2::Tag::setLatin1StringHandler for custom handling of
latin1-encoded text in ID3v2 frames.
* Fixed validation of ID3v2 frame IDs (IDs with '0' were ignored).
1.8 BETA:
* New API for accessing tags by name.
* New abstract I/O stream layer to allow custom I/O handlers.
* Support for writing ID3v2.3 tags.

View File

@@ -65,6 +65,7 @@ set(tag_HDRS
mpeg/id3v2/frames/attachedpictureframe.h
mpeg/id3v2/frames/commentsframe.h
mpeg/id3v2/frames/generalencapsulatedobjectframe.h
mpeg/id3v2/frames/ownershipframe.h
mpeg/id3v2/frames/popularimeterframe.h
mpeg/id3v2/frames/privateframe.h
mpeg/id3v2/frames/relativevolumeframe.h
@@ -151,6 +152,7 @@ set(frames_SRCS
mpeg/id3v2/frames/attachedpictureframe.cpp
mpeg/id3v2/frames/commentsframe.cpp
mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp
mpeg/id3v2/frames/ownershipframe.cpp
mpeg/id3v2/frames/popularimeterframe.cpp
mpeg/id3v2/frames/privateframe.cpp
mpeg/id3v2/frames/relativevolumeframe.cpp

View File

@@ -95,6 +95,9 @@ namespace TagLib {
* Contructs an WavPack file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -189,7 +189,7 @@ PropertyMap APE::Tag::properties() const
PropertyMap properties;
ItemListMap::ConstIterator it = itemListMap().begin();
for(; it != itemListMap().end(); ++it) {
String tagName = PropertyMap::prepareKey(it->first);
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())
@@ -227,7 +227,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
StringList toRemove;
ItemListMap::ConstIterator remIt = itemListMap().begin();
for(; remIt != itemListMap().end(); ++remIt) {
String key = PropertyMap::prepareKey(remIt->first);
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))
toRemove.append(remIt->first);
@@ -238,9 +238,12 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
// now sync in the "forward direction"
PropertyMap::ConstIterator it = properties.begin();
PropertyMap invalid;
for(; it != properties.end(); ++it) {
const String &tagName = it->first;
if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) {
if(!checkKey(tagName))
invalid.insert(it->first, it->second);
else if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) {
if(it->second.size() == 0)
removeItem(tagName);
else {
@@ -252,7 +255,21 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
}
}
}
return PropertyMap();
return invalid;
}
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;
}
APE::Footer *APE::Tag::footer() const

View File

@@ -123,10 +123,17 @@ namespace TagLib {
/*!
* Implements the unified tag dictionary interface -- import function. The same
* comments as for the export function apply.
* comments as for the export function apply; additionally note that the APE tag
* specification requires keys to have between 2 and 16 printable ASCII characters
* with the exception of the fixed strings "ID3", "TAG", "OGGS", and "MP+".
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Check if the given String is a valid APE tag key.
*/
static bool checkKey(const String&);
/*!
* Returns a pointer to the tag's footer.
*/

View File

@@ -348,7 +348,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
else {
obj = new UnknownObject(guid);
}
obj->parse(file, size);
obj->parse(file, (unsigned int)size);
objects.append(obj);
dataPos += size;
}
@@ -535,7 +535,7 @@ bool ASF::File::save()
data.append(d->objects[i]->render(this));
}
data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data;
insert(data, 0, d->size);
insert(data, 0, (TagLib::ulong)d->size);
return true;
}

View File

@@ -54,6 +54,9 @@ namespace TagLib {
*
* \note In the current implementation, both \a readProperties and
* \a propertiesStyle are ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average);
@@ -64,6 +67,9 @@ namespace TagLib {
*
* \note In the current implementation, both \a readProperties and
* \a propertiesStyle are ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -243,7 +243,7 @@ bool FLAC::File::save()
}
ByteVector padding = ByteVector::fromUInt(paddingLength);
padding.resize(paddingLength + 4);
padding[0] = FLAC::MetadataBlock::Padding | LastBlockFlag;
padding[0] = (char)(FLAC::MetadataBlock::Padding | LastBlockFlag);
data.append(padding);
// Write the data to the file

View File

@@ -97,6 +97,9 @@ namespace TagLib {
*
* If this file contains and ID3v2 tag the frames will be created using
* \a frameFactory.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
// BIC: merge with the above constructor
File(IOStream *stream, ID3v2::FrameFactory *frameFactory,

View File

@@ -128,7 +128,7 @@ bool IT::File::save()
seek(sampleOffset + 20);
if((i + instrumentCount) < lines.size())
if((TagLib::uint)(i + instrumentCount) < lines.size())
writeString(lines[i + instrumentCount], 25);
else
writeString(String::null, 25);

View File

@@ -48,6 +48,9 @@ namespace TagLib {
* Contructs a Impulse Tracker file from \a stream. If \a readProperties
* is true the file's audio properties will also be read using
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stram, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle =

View File

@@ -94,7 +94,7 @@ int IT::Properties::channels() const
return d->channels;
}
ushort IT::Properties::lengthInPatterns() const
TagLib::ushort IT::Properties::lengthInPatterns() const
{
return d->lengthInPatterns;
}
@@ -104,37 +104,37 @@ bool IT::Properties::stereo() const
return d->flags & Stereo;
}
ushort IT::Properties::instrumentCount() const
TagLib::ushort IT::Properties::instrumentCount() const
{
return d->instrumentCount;
}
ushort IT::Properties::sampleCount() const
TagLib::ushort IT::Properties::sampleCount() const
{
return d->sampleCount;
}
ushort IT::Properties::patternCount() const
TagLib::ushort IT::Properties::patternCount() const
{
return d->patternCount;
}
ushort IT::Properties::version() const
TagLib::ushort IT::Properties::version() const
{
return d->version;
}
ushort IT::Properties::compatibleVersion() const
TagLib::ushort IT::Properties::compatibleVersion() const
{
return d->compatibleVersion;
}
ushort IT::Properties::flags() const
TagLib::ushort IT::Properties::flags() const
{
return d->flags;
}
ushort IT::Properties::special() const
TagLib::ushort IT::Properties::special() const
{
return d->special;
}

View File

@@ -49,6 +49,9 @@ namespace TagLib {
* Contructs a Protracker file from \a stream. If \a readProperties
* is true the file's audio properties will also be read using
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle =

View File

@@ -70,7 +70,7 @@ int Mod::Properties::channels() const
return d->channels;
}
uint Mod::Properties::instrumentCount() const
TagLib::uint Mod::Properties::instrumentCount() const
{
return d->instrumentCount;
}

View File

@@ -65,6 +65,9 @@ namespace TagLib {
*
* \note In the current implementation, both \a readProperties and
* \a propertiesStyle are ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average);

View File

@@ -95,6 +95,9 @@ namespace TagLib {
* Contructs an MPC file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -214,7 +214,7 @@ void MPC::Properties::readSV8(File *file)
d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1;
if((d->sampleFrames - begSilence) != 0)
d->bitrate = d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence);
d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence));
d->bitrate = d->bitrate / 1000;
d->length = (d->sampleFrames - begSilence) / d->sampleRate;
@@ -279,10 +279,10 @@ void MPC::Properties::readSV7(const ByteVector &data)
}
if (d->trackPeak != 0)
d->trackPeak = (int)(log10(d->trackPeak) * 20 * 256 + .5);
d->trackPeak = (int)(log10((double)d->trackPeak) * 20 * 256 + .5);
if (d->albumPeak != 0)
d->albumPeak = (int)(log10(d->albumPeak) * 20 * 256 + .5);
d->albumPeak = (int)(log10((double)d->albumPeak) * 20 * 256 + .5);
bool trueGapless = (gapless >> 31) & 0x0001;
if(trueGapless) {

View File

@@ -194,10 +194,10 @@ void ID3v1::Tag::setTrack(uint i)
void ID3v1::Tag::setStringHandler(const StringHandler *handler)
{
if(TagPrivate::stringHandler != &defaultStringHandler)
delete TagPrivate::stringHandler;
TagPrivate::stringHandler = handler;
if (handler)
TagPrivate::stringHandler = handler;
else
TagPrivate::stringHandler = &defaultStringHandler;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -154,6 +154,11 @@ namespace TagLib {
/*!
* Sets the string handler that decides how the ID3v1 data will be
* converted to and from binary data.
* If the parameter \a handler is null, the previous handler is
* released and default ISO-8859-1 handler is restored.
*
* \note The caller is responsible for deleting the previous handler
* as needed after it is released.
*
* \see StringHandler
*/

View File

@@ -112,7 +112,7 @@ void CommentsFrame::setTextEncoding(String::Type encoding)
PropertyMap CommentsFrame::asProperties() const
{
String key = PropertyMap::prepareKey(description());
String key = description().upper();
PropertyMap map;
if(key.isEmpty() || key == "COMMENT")
map.insert("COMMENT", text());
@@ -158,8 +158,13 @@ void CommentsFrame::parseFields(const ByteVector &data)
ByteVectorList l = ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2);
if(l.size() == 2) {
d->description = String(l.front(), d->textEncoding);
d->text = String(l.back(), d->textEncoding);
if(d->textEncoding == String::Latin1) {
d->description = Tag::latin1StringHandler()->parse(l.front());
d->text = Tag::latin1StringHandler()->parse(l.back());
} else {
d->description = String(l.front(), d->textEncoding);
d->text = String(l.back(), d->textEncoding);
}
}
}

View File

@@ -0,0 +1,162 @@
/***************************************************************************
copyright : (C) 2012 by Rupert Daniel
email : rupert@cancelmonday.com
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <tdebug.h>
#include "ownershipframe.h"
#include <id3v2tag.h>
using namespace TagLib;
using namespace ID3v2;
class OwnershipFrame::OwnershipFramePrivate
{
public:
String pricePaid;
String datePurchased;
String seller;
String::Type textEncoding;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
OwnershipFrame::OwnershipFrame(String::Type encoding) : Frame("OWNE")
{
d = new OwnershipFramePrivate;
d->textEncoding = encoding;
}
OwnershipFrame::OwnershipFrame(const ByteVector &data) : Frame(data)
{
d = new OwnershipFramePrivate;
setData(data);
}
OwnershipFrame::~OwnershipFrame()
{
delete d;
}
String OwnershipFrame::toString() const
{
return "pricePaid=" + d->pricePaid + " datePurchased=" + d->datePurchased + " seller=" + d->seller;
}
String OwnershipFrame::pricePaid() const
{
return d->pricePaid;
}
void OwnershipFrame::setPricePaid(const String &s)
{
d->pricePaid = s;
}
String OwnershipFrame::datePurchased() const
{
return d->datePurchased;
}
void OwnershipFrame::setDatePurchased(const String &s)
{
d->datePurchased = s;
}
String OwnershipFrame::seller() const
{
return d->seller;
}
void OwnershipFrame::setSeller(const String &s)
{
d->seller = s;
}
String::Type OwnershipFrame::textEncoding() const
{
return d->textEncoding;
}
void OwnershipFrame::setTextEncoding(String::Type encoding)
{
d->textEncoding = encoding;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
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));
else
d->seller = String(data.mid(pos), d->textEncoding);
}
ByteVector OwnershipFrame::renderFields() const
{
ByteVector v;
v.append(char(d->textEncoding));
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));
return v;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : Frame(h)
{
d = new OwnershipFramePrivate;
parseFields(fieldData(data));
}

View File

@@ -0,0 +1,151 @@
/***************************************************************************
copyright : (C) 2012 by Rupert Daniel
email : rupert@cancelmonday.com
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_OWNERSHIPFRAME_H
#define TAGLIB_OWNERSHIPFRAME_H
#include "id3v2frame.h"
#include "taglib_export.h"
namespace TagLib {
namespace ID3v2 {
//! An implementation of ID3v2 "ownership"
/*!
* This implements the ID3v2 ownership (OWNE frame). It consists of
* a price paid, a date purchased (YYYYMMDD) and the name of the seller.
*/
class TAGLIB_EXPORT OwnershipFrame : public Frame
{
friend class FrameFactory;
public:
/*!
* Construct an empty ownership frame.
*/
explicit OwnershipFrame(String::Type encoding = String::Latin1);
/*!
* Construct a ownership based on the data in \a data.
*/
explicit OwnershipFrame(const ByteVector &data);
/*!
* Destroys this OwnershipFrame instance.
*/
virtual ~OwnershipFrame();
/*!
* Returns the text of this popularimeter.
*
* \see text()
*/
virtual String toString() const;
/*!
* Returns the date purchased.
*
* \see setDatePurchased()
*/
String datePurchased() const;
/*!
* Set the date purchased.
*
* \see datePurchased()
*/
void setDatePurchased(const String &datePurchased);
/*!
* Returns the price paid.
*
* \see setPricePaid()
*/
String pricePaid() const;
/*!
* Set the price paid.
*
* \see pricePaid()
*/
void setPricePaid(const String &pricePaid);
/*!
* Returns the seller.
*
* \see setSeller()
*/
String seller() const;
/*!
* Set the seller.
*
* \see seller()
*/
void setSeller(const String &seller);
/*!
* Returns the text encoding that will be used in rendering this frame.
* This defaults to the type that was either specified in the constructor
* or read from the frame when parsed.
*
* \see setTextEncoding()
* \see render()
*/
String::Type textEncoding() const;
/*!
* Sets the text encoding to be used when rendering this frame to
* \a encoding.
*
* \see textEncoding()
* \see render()
*/
void setTextEncoding(String::Type encoding);
protected:
// Reimplementations.
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
private:
/*!
* The constructor used by the FrameFactory.
*/
OwnershipFrame(const ByteVector &data, Header *h);
OwnershipFrame(const OwnershipFrame &);
OwnershipFrame &operator=(const OwnershipFrame &);
class OwnershipFramePrivate;
OwnershipFramePrivate *d;
};
}
}
#endif

View File

@@ -213,8 +213,10 @@ void TextIdentificationFrame::parseFields(const ByteVector &data)
for(ByteVectorList::Iterator it = l.begin(); it != l.end(); it++) {
if(!(*it).isEmpty()) {
String s(*it, d->textEncoding);
d->fieldList.append(s);
if(d->textEncoding == String::Latin1)
d->fieldList.append(Tag::latin1StringHandler()->parse(*it));
else
d->fieldList.append(String(*it, d->textEncoding));
}
}
}
@@ -289,7 +291,7 @@ PropertyMap TextIdentificationFrame::makeTMCLProperties() const
}
StringList l = fieldList();
for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
String instrument = PropertyMap::prepareKey(*it);
String instrument = it->upper();
if(instrument.isNull()) {
// instrument is not a valid key -> frame unsupported
map.clear();
@@ -382,7 +384,7 @@ PropertyMap UserTextIdentificationFrame::asProperties() const
String tagName = description();
PropertyMap map;
String key = map.prepareKey(tagName);
String key = tagName.upper();
if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list
map.unsupportedData().append(L"TXXX/" + description());
else {

View File

@@ -116,7 +116,7 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding)
PropertyMap UnsynchronizedLyricsFrame::asProperties() const
{
PropertyMap map;
String key = PropertyMap::prepareKey(description());
String key = description().upper();
if(key.isEmpty() || key.upper() == "LYRICS")
map.insert("LYRICS", text());
else if(key.isNull())
@@ -158,8 +158,13 @@ void UnsynchronizedLyricsFrame::parseFields(const ByteVector &data)
ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2);
if(l.size() == 2) {
d->description = String(l.front(), d->textEncoding);
d->text = String(l.back(), d->textEncoding);
if(d->textEncoding == String::Latin1) {
d->description = Tag::latin1StringHandler()->parse(l.front());
d->text = Tag::latin1StringHandler()->parse(l.back());
} else {
d->description = String(l.front(), d->textEncoding);
d->text = String(l.back(), d->textEncoding);
}
}
}

View File

@@ -156,7 +156,7 @@ void UserUrlLinkFrame::setDescription(const String &s)
PropertyMap UserUrlLinkFrame::asProperties() const
{
PropertyMap map;
String key = PropertyMap::prepareKey(description());
String key = description().upper();
if(key.isEmpty() || key.upper() == "URL")
map.insert("URL", url());
else if(key.isNull())

View File

@@ -36,6 +36,7 @@
#include <tdebug.h>
#include <tstringlist.h>
#include "id3v2tag.h"
#include "id3v2frame.h"
#include "id3v2synchdata.h"
#include "tpropertymap.h"
@@ -71,7 +72,7 @@ namespace
return false;
for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) {
return false;
}
}
@@ -273,7 +274,11 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int
if(end < *position)
return String::null;
String str = String(data.mid(*position, end - *position), encoding);
String str;
if(encoding == String::Latin1)
str = Tag::latin1StringHandler()->parse(data.mid(*position, end - *position));
else
str = String(data.mid(*position, end - *position), encoding);
*position = end + delimiter.size();

View File

@@ -44,6 +44,7 @@
#include "frames/unsynchronizedlyricsframe.h"
#include "frames/popularimeterframe.h"
#include "frames/privateframe.h"
#include "frames/ownershipframe.h"
using namespace TagLib;
using namespace ID3v2;
@@ -107,7 +108,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
}
for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) {
delete header;
return 0;
}
@@ -238,6 +239,14 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
if(frameID == "PRIV")
return new PrivateFrame(data, header);
// Ownership (frames 4.22)
if(frameID == "OWNE") {
OwnershipFrame *f = new OwnershipFrame(data, header);
d->setTextEncoding(f);
return f;
}
return new UnknownFrame(data, header);
}

View File

@@ -123,8 +123,7 @@ namespace TagLib {
FrameFactory();
/*!
* Destroys the frame factory. In most cases this will never be called (as
* is typical of singletons).
* Destroys the frame factory.
*/
virtual ~FrameFactory();

View File

@@ -70,8 +70,30 @@ public:
FrameListMap frameListMap;
FrameList frameList;
static const Latin1StringHandler *stringHandler;
};
static const Latin1StringHandler defaultStringHandler;
const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler;
////////////////////////////////////////////////////////////////////////////////
// StringHandler implementation
////////////////////////////////////////////////////////////////////////////////
Latin1StringHandler::Latin1StringHandler()
{
}
Latin1StringHandler::~Latin1StringHandler()
{
}
String Latin1StringHandler::parse(const ByteVector &data) const
{
return String(data, String::Latin1);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
@@ -584,6 +606,19 @@ ByteVector ID3v2::Tag::render(int version) const
return d->header.render() + tagData;
}
Latin1StringHandler const *ID3v2::Tag::latin1StringHandler()
{
return TagPrivate::stringHandler;
}
void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
{
if(handler)
TagPrivate::stringHandler = handler;
else
TagPrivate::stringHandler = &defaultStringHandler;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
@@ -645,8 +680,9 @@ void ID3v2::Tag::parse(const ByteVector &origData)
// portion of the frame data.
if(data.at(frameDataPosition) == 0) {
if(d->header.footerPresent())
if(d->header.footerPresent()) {
debug("Padding *and* a footer found. This is not allowed by the spec.");
}
d->paddingSize = frameDataLength - frameDataPosition;
return;

View File

@@ -57,6 +57,36 @@ namespace TagLib {
typedef List<Frame *> FrameList;
typedef Map<ByteVector, FrameList> FrameListMap;
//! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags.
/*!
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
* supports genuine ISO-8859-1 by default. However, in practice, non
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
* Windows-1252 for western languages, Shift_JIS for Japanese and so on.
*
* Here is an option to read such tags by subclassing this class,
* reimplementing parse() and setting your reimplementation as the default
* with ID3v2::Tag::setStringHandler().
*
* \note Writing non-ISO-8859-1 tags is not implemented intentionally.
* Use UTF-16 or UTF-8 instead.
*
* \see ID3v2::Tag::setStringHandler()
*/
class TAGLIB_EXPORT Latin1StringHandler
{
public:
Latin1StringHandler();
virtual ~Latin1StringHandler();
/*!
* Decode a string from \a data. The default implementation assumes that
* \a data is an ISO-8859-1 (Latin1) character array.
*/
virtual String parse(const ByteVector &data) const;
};
//! The main class in the ID3v2 implementation
/*!
@@ -323,6 +353,27 @@ namespace TagLib {
*/
// BIC: combine with the above method
ByteVector render(int version) const;
/*!
* Gets the current string handler that decides how the "Latin-1" data
* will be converted to and from binary data.
*
* \see Latin1StringHandler
*/
static Latin1StringHandler const *latin1StringHandler();
/*!
* Sets the string handler that decides how the "Latin-1" data will be
* converted to and from binary data.
* If the parameter \a handler is null, the previous handler is
* released and default ISO-8859-1 handler is restored.
*
* \note The caller is responsible for deleting the previous handler
* as needed after it is released.
*
* \see Latin1StringHandler
*/
static void setLatin1StringHandler(const Latin1StringHandler *handler);
protected:
/*!

View File

@@ -99,6 +99,9 @@ namespace TagLib {
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored. The frames will be created using
* \a frameFactory.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
// BIC: merge with the above constructor
File(IOStream *stream, ID3v2::FrameFactory *frameFactory,

View File

@@ -221,7 +221,7 @@ void MPEG::Properties::read()
double length = timePerFrame * d->xingHeader->totalFrames();
d->length = int(length);
d->bitrate = d->length > 0 ? d->xingHeader->totalSize() * 8 / length / 1000 : 0;
d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0;
}
else {
// Since there was no valid Xing header found, we hope that we're in a constant

View File

@@ -263,9 +263,9 @@ void Ogg::FLAC::File::scan()
d->hasXiphComment = true;
d->commentPacket = ipacket;
}
else if(blockType > 5)
else if(blockType > 5) {
debug("Ogg::FLAC::File::scan() -- Unknown metadata block");
}
}
// End of metadata, now comes the datastream

View File

@@ -75,6 +75,9 @@ namespace TagLib {
* Contructs an Ogg/FLAC file from \a file. If \a readProperties is true
* the file's audio properties will also be read using \a propertiesStyle.
* If false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -100,6 +100,9 @@ namespace TagLib {
* \note This constructor is protected since Ogg::File shouldn't be
* instantiated directly but rather should be used through the codec
* specific subclasses.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream);

View File

@@ -67,6 +67,9 @@ namespace TagLib {
* Contructs a Speex file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -74,6 +74,9 @@ namespace TagLib {
* Contructs a Vorbis file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -173,7 +173,7 @@ void Vorbis::Properties::read()
long long end = last->absoluteGranularPosition();
if(start >= 0 && end >= 0 && d->sampleRate > 0)
d->length = (end - start) / (long long) d->sampleRate;
d->length = (int)((end - start) / (long long) d->sampleRate);
else
debug("Vorbis::Properties::read() -- Either the PCM values for the start or "
"end of this file was incorrect or the sample rate is zero.");

View File

@@ -205,11 +205,14 @@ PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties)
for(StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it)
removeField(*it);
// now go through keys in \a properties and check that the values match those in the xiph comment */
// now go through keys in \a properties and check that the values match those in the xiph comment
PropertyMap invalid;
PropertyMap::ConstIterator it = properties.begin();
for(; it != properties.end(); ++it)
{
if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) {
if(!checkKey(it->first))
invalid.insert(it->first, it->second);
else if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) {
const StringList &sl = it->second;
if(sl.size() == 0)
// zero size string list -> remove the tag with all values
@@ -224,7 +227,18 @@ PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties)
}
}
}
return PropertyMap();
return invalid;
}
bool Ogg::XiphComment::checkKey(const String &key)
{
if(key.size() < 1)
return false;
for(String::ConstIterator it = key.begin(); it != key.end(); it++)
// forbid non-printable, non-ascii, '=' (#61) and '~' (#126)
if (*it < 32 || *it >= 128 || *it == 61 || *it == 126)
return false;
return true;
}
String Ogg::XiphComment::vendorID() const

View File

@@ -151,10 +151,18 @@ namespace TagLib {
/*!
* Implements the unified property interface -- import function.
* The tags from the given map will be stored one-to-one in the file.
* The tags from the given map will be stored one-to-one in the file,
* except for invalid keys (less than one character, non-ASCII, or
* containing '=' or '~') in which case the according values will
* be contained in the returned PropertyMap.
*/
PropertyMap setProperties(const PropertyMap&);
/*!
* Check if the given String is a valid Xiph comment key.
*/
static bool checkKey(const String&);
/*!
* Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the
* most common case always returns "Xiph.Org libVorbis I 20020717".

View File

@@ -69,6 +69,9 @@ namespace TagLib {
* Contructs an AIFF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -139,7 +139,7 @@ int RIFF::AIFF::Properties::sampleWidth() const
return d->sampleWidth;
}
uint RIFF::AIFF::Properties::sampleFrames() const
TagLib::uint RIFF::AIFF::Properties::sampleFrames() const
{
return d->sampleFrames;
}
@@ -154,7 +154,7 @@ void RIFF::AIFF::Properties::read(const ByteVector &data)
d->sampleFrames = data.mid(2, 4).toUInt();
d->sampleWidth = data.mid(6, 2).toShort();
double sampleRate = ConvertFromIeeeExtended(reinterpret_cast<unsigned char *>(data.mid(8, 10).data()));
d->sampleRate = sampleRate;
d->bitrate = (sampleRate * d->sampleWidth * d->channels) / 1000.0;
d->sampleRate = (int)sampleRate;
d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0);
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
}

View File

@@ -69,6 +69,9 @@ namespace TagLib {
* Contructs an WAV file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -104,7 +104,7 @@ int RIFF::WAV::Properties::sampleWidth() const
return d->sampleWidth;
}
uint RIFF::WAV::Properties::sampleFrames() const
TagLib::uint RIFF::WAV::Properties::sampleFrames() const
{
return d->sampleFrames;
}

View File

@@ -48,6 +48,9 @@ namespace TagLib {
* Contructs a ScreamTracker III file from \a stream. If \a readProperties
* is true the file's audio properties will also be read using
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle =

View File

@@ -88,7 +88,7 @@ int S3M::Properties::channels() const
return d->channels;
}
ushort S3M::Properties::lengthInPatterns() const
TagLib::ushort S3M::Properties::lengthInPatterns() const
{
return d->lengthInPatterns;
}
@@ -98,27 +98,27 @@ bool S3M::Properties::stereo() const
return d->stereo;
}
ushort S3M::Properties::sampleCount() const
TagLib::ushort S3M::Properties::sampleCount() const
{
return d->sampleCount;
}
ushort S3M::Properties::patternCount() const
TagLib::ushort S3M::Properties::patternCount() const
{
return d->patternCount;
}
ushort S3M::Properties::flags() const
TagLib::ushort S3M::Properties::flags() const
{
return d->flags;
}
ushort S3M::Properties::trackerVersion() const
TagLib::ushort S3M::Properties::trackerVersion() const
{
return d->trackerVersion;
}
ushort S3M::Properties::fileFormatVersion() const
TagLib::ushort S3M::Properties::fileFormatVersion() const
{
return d->fileFormatVersion;
}

View File

@@ -74,15 +74,17 @@ using namespace TagLib;
class File::FilePrivate
{
public:
FilePrivate(IOStream *stream);
FilePrivate(IOStream *stream, bool owner);
IOStream *stream;
bool streamOwner;
bool valid;
static const uint bufferSize = 1024;
};
File::FilePrivate::FilePrivate(IOStream *stream) :
File::FilePrivate::FilePrivate(IOStream *stream, bool owner) :
stream(stream),
streamOwner(owner),
valid(true)
{
}
@@ -94,17 +96,17 @@ File::FilePrivate::FilePrivate(IOStream *stream) :
File::File(FileName fileName)
{
IOStream *stream = new FileStream(fileName);
d = new FilePrivate(stream);
d = new FilePrivate(stream, true);
}
File::File(IOStream *stream)
{
d = new FilePrivate(stream);
d = new FilePrivate(stream, false);
}
File::~File()
{
if(d->stream)
if(d->stream && d->streamOwner)
delete d->stream;
delete d;
}
@@ -452,12 +454,32 @@ long File::length()
bool File::isReadable(const char *file)
{
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
return _access_s(file, R_OK) == 0;
#else
return access(file, R_OK) == 0;
#endif
}
bool File::isWritable(const char *file)
{
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
return _access_s(file, W_OK) == 0;
#else
return access(file, W_OK) == 0;
#endif
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -260,6 +260,9 @@ namespace TagLib {
/*!
* Construct a File object and use the \a stream instance.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*
* \note Constructor is protected since this class should only be
* instantiated through subclasses.
*/

View File

@@ -65,6 +65,51 @@ struct FileNameHandle : public std::string
#endif
namespace {
FILE *openFile(const FileName &path, bool readOnly)
{
// Calls a proper variation of fopen() depending on the compiling environment.
#if defined(_WIN32)
# if defined(_MSC_VER) && (_MSC_VER >= 1400)
// Visual C++ 2005 or later.
FILE *file;
errno_t err;
if(wcslen(path) > 0)
err = _wfopen_s(&file, path, readOnly ? L"rb" : L"rb+");
else
err = fopen_s(&file, path, readOnly ? "rb" : "rb+");
if(err == 0)
return file;
else
return NULL;
# else // defined(_MSC_VER) && (_MSC_VER >= 1400)
// Visual C++.NET 2003 or earlier.
if(wcslen(path) > 0)
return _wfopen(path, readOnly ? L"rb" : L"rb+");
else
return fopen(path, readOnly ? "rb" : "rb+");
# endif // defined(_MSC_VER) && (_MSC_VER >= 1400)
#else // defined(_WIN32)
// Non-Win32
return fopen(path, readOnly ? "rb" : "rb+");
#endif // defined(_WIN32)
}
}
class FileStream::FileStreamPrivate
{
public:
@@ -87,41 +132,15 @@ FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName, bool openRea
{
// First try with read / write mode, if that fails, fall back to read only.
#ifdef _WIN32
if(!openReadOnly)
file = openFile(name, false);
if(wcslen((const wchar_t *) fileName) > 0) {
if(file)
readOnly = false;
else
file = openFile(name, true);
if(openReadOnly)
file = _wfopen(name, L"rb");
else {
file = _wfopen(name, L"rb+");
if(file)
readOnly = false;
else
file = _wfopen(name, L"rb");
}
if(file)
return;
}
#endif
if(openReadOnly)
file = fopen(name, "rb");
else {
file = fopen(name, "rb+");
if(file)
readOnly = false;
else
file = fopen(name, "rb");
}
if(!file)
{
if(!file) {
debug("Could not open file " + String((const char *) name));
}
}
@@ -379,7 +398,17 @@ long FileStream::length()
void FileStream::truncate(long length)
{
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
ftruncate(_fileno(d->file), length);
#else
ftruncate(fileno(d->file), length);
#endif
}
TagLib::uint FileStream::bufferSize()

View File

@@ -34,7 +34,7 @@ PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m), unsupport
PropertyMap::PropertyMap(const SimplePropertyMap &m)
{
for(SimplePropertyMap::ConstIterator it = m.begin(); it != m.end(); ++it){
String key = prepareKey(it->first);
String key = it->first.upper();
if(!key.isNull())
insert(it->first, it->second);
else
@@ -48,10 +48,7 @@ PropertyMap::~PropertyMap()
bool PropertyMap::insert(const String &key, const StringList &values)
{
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
String realKey = key.upper();
Iterator result = SimplePropertyMap::find(realKey);
if(result == end())
SimplePropertyMap::insert(realKey, values);
@@ -62,9 +59,7 @@ bool PropertyMap::insert(const String &key, const StringList &values)
bool PropertyMap::replace(const String &key, const StringList &values)
{
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
String realKey = key.upper();
SimplePropertyMap::erase(realKey);
SimplePropertyMap::insert(realKey, values);
return true;
@@ -72,26 +67,17 @@ bool PropertyMap::replace(const String &key, const StringList &values)
PropertyMap::Iterator PropertyMap::find(const String &key)
{
String realKey = prepareKey(key);
if(realKey.isNull())
return end();
return SimplePropertyMap::find(realKey);
return SimplePropertyMap::find(key.upper());
}
PropertyMap::ConstIterator PropertyMap::find(const String &key) const
{
String realKey = prepareKey(key);
if(realKey.isNull())
return end();
return SimplePropertyMap::find(realKey);
return SimplePropertyMap::find(key.upper());
}
bool PropertyMap::contains(const String &key) const
{
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
return SimplePropertyMap::contains(realKey);
return SimplePropertyMap::contains(key.upper());
}
bool PropertyMap::contains(const PropertyMap &other) const
@@ -107,9 +93,7 @@ bool PropertyMap::contains(const PropertyMap &other) const
PropertyMap &PropertyMap::erase(const String &key)
{
String realKey = prepareKey(key);
if(!realKey.isNull())
SimplePropertyMap::erase(realKey);
SimplePropertyMap::erase(key.upper());
return *this;
}
@@ -122,23 +106,20 @@ PropertyMap &PropertyMap::erase(const PropertyMap &other)
PropertyMap &PropertyMap::merge(const PropertyMap &other)
{
for(PropertyMap::ConstIterator it = other.begin(); it != other.end(); ++it) {
for(PropertyMap::ConstIterator it = other.begin(); it != other.end(); ++it)
insert(it->first, it->second);
}
unsupported.append(other.unsupported);
return *this;
}
const StringList &PropertyMap::operator[](const String &key) const
{
String realKey = prepareKey(key);
return SimplePropertyMap::operator[](realKey);
return SimplePropertyMap::operator[](key.upper());
}
StringList &PropertyMap::operator[](const String &key)
{
String realKey = prepareKey(key);
return SimplePropertyMap::operator[](realKey);
return SimplePropertyMap::operator[](key.upper());
}
bool PropertyMap::operator==(const PropertyMap &other) const
@@ -190,13 +171,3 @@ const StringList &PropertyMap::unsupportedData() const
{
return unsupported;
}
String PropertyMap::prepareKey(const String &proposed) {
if(proposed.isEmpty())
return String::null;
for (String::ConstIterator it = proposed.begin(); it != proposed.end(); it++)
// forbid non-printable, non-ascii, '=' (#61) and '~' (#126)
if (*it < 32 || *it >= 128 || *it == 61 || *it == 126)
return String::null;
return proposed.upper();
}

View File

@@ -36,15 +36,10 @@ namespace TagLib {
* ("tags") realized as pairs of a case-insensitive key
* and a nonempty list of corresponding values, each value being an an arbitrary
* unicode String.
* The key has the same restrictions as in the vorbis comment specification,
* i.e. it must contain at least one character; all printable ASCII characters
* except '=' and '~' are allowed.
*
* In order to be safe with other formats, keep these additional restrictions in mind:
*
* - APE only allows keys from 2 to 16 printable ASCII characters (including space),
* with the exception of these strings: ID3, TAG, OggS, MP+
*
* Note that most metadata formats pose additional conditions on the tag keys. The
* most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of
* length between 2 and 16.
*/
class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap
@@ -126,16 +121,17 @@ namespace TagLib {
/*!
* Returns a reference to the value associated with \a key.
*
* \note: This has undefined behavior if the key is not valid or not
* present in the map.
* \note: If \a key is not contained in the map, an empty
* StringList is returned without error.
*/
const StringList &operator[](const String &key) const;
/*!
* Returns a reference to the value associated with \a key.
*
* \note: This has undefined behavior if the key is not valid or not
* present in the map.
* \note: If \a key is not contained in the map, an empty
* StringList is returned. You can also directly add entries
* by using this function as an lvalue.
*/
StringList &operator[](const String &key);
@@ -168,12 +164,6 @@ namespace TagLib {
String toString() const;
/*!
* Converts \a proposed into another String suitable to be used as
* a key, or returns String::null if this is not possible.
*/
static String prepareKey(const String &proposed);
private:

View File

@@ -233,8 +233,9 @@ std::string String::to8Bit(bool unicode) const
&target, targetBuffer + outputBufferSize,
Unicode::lenientConversion);
if(result != Unicode::conversionOK)
if(result != Unicode::conversionOK) {
debug("String::to8Bit() - Unicode conversion error.");
}
int newSize = target - targetBuffer;
s.resize(newSize);
@@ -259,8 +260,17 @@ const char *String::toCString(bool unicode) const
std::string buffer = to8Bit(unicode);
d->CString = new char[buffer.size() + 1];
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
strcpy_s(d->CString, buffer.size() + 1, buffer.c_str());
#else
strcpy(d->CString, buffer.c_str());
#endif
return d->CString;
}
@@ -335,9 +345,6 @@ bool String::startsWith(const String &s) const
String String::substr(uint position, uint n) const
{
if(n > position + d->data.size())
n = d->data.size() - position;
String s;
s.d->data = d->data.substr(position, n);
return s;
@@ -779,9 +786,9 @@ void String::prepare(Type t)
&target, targetBuffer + bufferSize,
Unicode::lenientConversion);
if(result != Unicode::conversionOK)
if(result != Unicode::conversionOK) {
debug("String::prepare() - Unicode conversion error.");
}
int newSize = target != targetBuffer ? target - targetBuffer - 1 : 0;
d->data.resize(newSize);

View File

@@ -155,7 +155,7 @@ ConversionResult ConvertUTF16toUTF8 (
case 4: *--target = (ch | byteMark) & byteMask; ch >>= 6;
case 3: *--target = (ch | byteMark) & byteMask; ch >>= 6;
case 2: *--target = (ch | byteMark) & byteMask; ch >>= 6;
case 1: *--target = ch | firstByteMark[bytesToWrite];
case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]);
}
target += bytesToWrite;
}
@@ -253,7 +253,7 @@ ConversionResult ConvertUTF8toUTF16 (
result = sourceIllegal;
break;
} else {
*target++ = ch; /* normal case */
*target++ = (UTF16)ch; /* normal case */
}
} else if (ch > UNI_MAX_UTF16) {
if (flags == strictConversion) {
@@ -270,7 +270,7 @@ ConversionResult ConvertUTF8toUTF16 (
result = targetExhausted; break;
}
ch -= halfBase;
*target++ = (ch >> halfShift) + UNI_SUR_HIGH_START;
*target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
*target++ = (ch & halfMask) + UNI_SUR_LOW_START;
}
}

View File

@@ -100,6 +100,9 @@ namespace TagLib {
* Contructs an TrueAudio file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
@@ -109,6 +112,9 @@ namespace TagLib {
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored. The frames will be created using
* \a frameFactory.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
bool readProperties = true,

View File

@@ -103,7 +103,7 @@ int TrueAudio::Properties::channels() const
return d->channels;
}
uint TrueAudio::Properties::sampleFrames() const
TagLib::uint TrueAudio::Properties::sampleFrames() const
{
return d->sampleFrames;
}

View File

@@ -91,6 +91,9 @@ namespace TagLib {
* Contructs an WavPack file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);

View File

@@ -117,7 +117,7 @@ int WavPack::Properties::bitsPerSample() const
return d->bitsPerSample;
}
uint WavPack::Properties::sampleFrames() const
TagLib::uint WavPack::Properties::sampleFrames() const
{
return d->sampleFrames;
}

View File

@@ -48,6 +48,9 @@ namespace TagLib {
* Contructs a Extended Module file from \a stream. If \a readProperties
* is true the file's audio properties will also be read using
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*/
File(IOStream *stream, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle =

View File

@@ -84,47 +84,47 @@ int XM::Properties::channels() const
return d->channels;
}
ushort XM::Properties::lengthInPatterns() const
TagLib::ushort XM::Properties::lengthInPatterns() const
{
return d->lengthInPatterns;
}
ushort XM::Properties::version() const
TagLib::ushort XM::Properties::version() const
{
return d->version;
}
ushort XM::Properties::restartPosition() const
TagLib::ushort XM::Properties::restartPosition() const
{
return d->restartPosition;
}
ushort XM::Properties::patternCount() const
TagLib::ushort XM::Properties::patternCount() const
{
return d->patternCount;
}
ushort XM::Properties::instrumentCount() const
TagLib::ushort XM::Properties::instrumentCount() const
{
return d->instrumentCount;
}
uint XM::Properties::sampleCount() const
TagLib::uint XM::Properties::sampleCount() const
{
return d->sampleCount;
}
ushort XM::Properties::flags() const
TagLib::ushort XM::Properties::flags() const
{
return d->flags;
}
ushort XM::Properties::tempo() const
TagLib::ushort XM::Properties::tempo() const
{
return d->tempo;
}
ushort XM::Properties::bpmSpeed() const
TagLib::ushort XM::Properties::bpmSpeed() const
{
return d->bpmSpeed;
}

View File

@@ -35,6 +35,7 @@ SET(test_runner_SRCS
test_bytevectorlist.cpp
test_bytevectorstream.cpp
test_string.cpp
test_propertymap.cpp
test_fileref.cpp
test_id3v1.cpp
test_id3v2.cpp

BIN
tests/data/w000.mp3 Normal file

Binary file not shown.

View File

@@ -19,6 +19,7 @@ class TestAPETag : public CppUnit::TestFixture
CPPUNIT_TEST(testIsEmpty2);
CPPUNIT_TEST(testPropertyInterface1);
CPPUNIT_TEST(testPropertyInterface2);
CPPUNIT_TEST(testInvalidKeys);
CPPUNIT_TEST_SUITE_END();
public:
@@ -76,12 +77,27 @@ public:
APE::Item item3 = APE::Item("TRACKNUMBER", "29");
tag.setItem("TRACKNUMBER", item3);
properties = tag.properties();
CPPUNIT_ASSERT_EQUAL(2u, properties["TRACKNUMBER"].size());
CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), properties["TRACKNUMBER"].size());
CPPUNIT_ASSERT_EQUAL(String("17"), properties["TRACKNUMBER"][0]);
CPPUNIT_ASSERT_EQUAL(String("29"), properties["TRACKNUMBER"][1]);
}
void testInvalidKeys()
{
PropertyMap properties;
properties["A"] = String("invalid key: one character");
properties["MP+"] = String("invalid key: forbidden string");
properties["A B~C"] = String("valid key: space and tilde");
properties["ARTIST"] = String("valid key: normal one");
APE::Tag tag;
PropertyMap unsuccessful = tag.setProperties(properties);
CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), unsuccessful.size());
CPPUNIT_ASSERT(unsuccessful.contains("A"));
CPPUNIT_ASSERT(unsuccessful.contains("MP+"));
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestAPETag);

View File

@@ -15,6 +15,7 @@
#include <relativevolumeframe.h>
#include <popularimeterframe.h>
#include <urllinkframe.h>
#include <ownershipframe.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include "utils.h"
@@ -61,6 +62,8 @@ class TestID3v2 : public CppUnit::TestFixture
CPPUNIT_TEST(testRenderUrlLinkFrame);
CPPUNIT_TEST(testParseUserUrlLinkFrame);
CPPUNIT_TEST(testRenderUserUrlLinkFrame);
CPPUNIT_TEST(testParseOwnershipFrame);
CPPUNIT_TEST(testRenderOwnershipFrame);
CPPUNIT_TEST(testSaveUTF16Comment);
CPPUNIT_TEST(testUpdateGenre23_1);
CPPUNIT_TEST(testUpdateGenre23_2);
@@ -69,8 +72,10 @@ class TestID3v2 : public CppUnit::TestFixture
CPPUNIT_TEST(testDowngradeTo23);
// CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together
CPPUNIT_TEST(testCompressedFrameWithBrokenLength);
CPPUNIT_TEST(testW000);
CPPUNIT_TEST(testPropertyInterface);
CPPUNIT_TEST(testPropertyInterface2);
CPPUNIT_TEST(testDeleteFrame);
CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2);
CPPUNIT_TEST_SUITE_END();
@@ -384,6 +389,38 @@ public:
"http://example.com", 33), // URL
f.render());
}
void testParseOwnershipFrame()
{
ID3v2::OwnershipFrame f(
ByteVector("OWNE" // Frame ID
"\x00\x00\x00\x19" // Frame size
"\x00\x00" // Frame flags
"\x00" // Text encoding
"GBP1.99\x00" // Price paid
"20120905" // Date of purchase
"Beatport", 35)); // Seller
CPPUNIT_ASSERT_EQUAL(String("GBP1.99"), f.pricePaid());
CPPUNIT_ASSERT_EQUAL(String("20120905"), f.datePurchased());
CPPUNIT_ASSERT_EQUAL(String("Beatport"), f.seller());
}
void testRenderOwnershipFrame()
{
ID3v2::OwnershipFrame f;
f.setPricePaid("GBP1.99");
f.setDatePurchased("20120905");
f.setSeller("Beatport");
CPPUNIT_ASSERT_EQUAL(
ByteVector("OWNE" // Frame ID
"\x00\x00\x00\x19" // Frame size
"\x00\x00" // Frame flags
"\x00" // Text encoding
"GBP1.99\x00" // Price paid
"20120905" // Date of purchase
"Beatport", 35), // URL
f.render());
}
void testItunes24FrameSize()
{
@@ -551,6 +588,16 @@ public:
CPPUNIT_ASSERT_EQUAL(String(""), frame->description());
CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size());
}
void testW000()
{
MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false);
CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000"));
ID3v2::UrlLinkFrame *frame =
dynamic_cast<TagLib::ID3v2::UrlLinkFrame*>(f.ID3v2Tag()->frameListMap()["W000"].front());
CPPUNIT_ASSERT(frame);
CPPUNIT_ASSERT_EQUAL(String("lukas.lalinsky@example.com____"), frame->url());
}
void testPropertyInterface()
{
@@ -629,24 +676,40 @@ public:
CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty());
}
void testDeleteFrame()
{
ScopedFileCopy copy("rare_frames", ".mp3");
string newname = copy.fileName();
MPEG::File f(newname.c_str());
ID3v2::Tag *t = f.ID3v2Tag();
ID3v2::Frame *frame = t->frameList("TCON")[0];
CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size());
t->removeFrame(frame, true);
f.save(MPEG::File::ID3v2);
MPEG::File f2(newname.c_str());
t = f2.ID3v2Tag();
CPPUNIT_ASSERT(t->frameList("TCON").isEmpty());
}
void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2()
{
ScopedFileCopy copy("xing", ".mp3");
string newname = copy.fileName();
{
MPEG::File foo(newname.c_str());
foo.tag()->setArtist("Artist");
foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2);
}
{
MPEG::File bar(newname.c_str());
bar.ID3v2Tag()->removeFrames("TPE1");
// Should strip ID3v1 here and not add old values to ID3v2 again
bar.save(MPEG::File::ID3v2, true);
}
MPEG::File f(newname.c_str());
CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1"));
}

View File

@@ -0,0 +1,32 @@
#include <cppunit/extensions/HelperMacros.h>
#include <tpropertymap.h>
class TestPropertyMap : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestPropertyMap);
CPPUNIT_TEST(testInvalidKeys);
CPPUNIT_TEST_SUITE_END();
public:
void testInvalidKeys()
{
TagLib::PropertyMap map1;
CPPUNIT_ASSERT(map1.isEmpty());
map1["ÄÖÜ"].append("test");
CPPUNIT_ASSERT_EQUAL(map1.size(), 1u);
TagLib::PropertyMap map2;
map2["ÄÖÜ"].append("test");
CPPUNIT_ASSERT(map1 == map2);
CPPUNIT_ASSERT(map1.contains(map2));
map2["ARTIST"] = TagLib::String("Test Artist");
CPPUNIT_ASSERT(map1 != map2);
CPPUNIT_ASSERT(map2.contains(map1));
map2["ÄÖÜ"].append("test 2");
CPPUNIT_ASSERT(!map2.contains(map1));
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestPropertyMap);

View File

@@ -41,6 +41,7 @@ class TestString : public CppUnit::TestFixture
CPPUNIT_TEST(testAppendCharDetach);
CPPUNIT_TEST(testAppendStringDetach);
CPPUNIT_TEST(testToInt);
CPPUNIT_TEST(testSubstr);
CPPUNIT_TEST_SUITE_END();
public:
@@ -193,6 +194,13 @@ public:
CPPUNIT_ASSERT_EQUAL(String("-123aa").toInt(), -123);
}
void testSubstr()
{
CPPUNIT_ASSERT_EQUAL(String("01"), String("0123456").substr(0, 2));
CPPUNIT_ASSERT_EQUAL(String("12"), String("0123456").substr(1, 2));
CPPUNIT_ASSERT_EQUAL(String("123456"), String("0123456").substr(1, 200));
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestString);

View File

@@ -2,6 +2,7 @@
#include <string>
#include <stdio.h>
#include <xiphcomment.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include "utils.h"
@@ -15,6 +16,7 @@ class TestXiphComment : public CppUnit::TestFixture
CPPUNIT_TEST(testSetYear);
CPPUNIT_TEST(testTrack);
CPPUNIT_TEST(testSetTrack);
CPPUNIT_TEST(testInvalidKeys);
CPPUNIT_TEST_SUITE_END();
public:
@@ -59,6 +61,19 @@ public:
CPPUNIT_ASSERT_EQUAL(String("3"), cmt.fieldListMap()["TRACKNUMBER"].front());
}
void testInvalidKeys()
{
PropertyMap map;
map[""] = String("invalid key: empty string");
map["A=B"] = String("invalid key: contains '='");
map["A~B"] = String("invalid key: contains '~'");
Ogg::XiphComment cmt;
PropertyMap unsuccessful = cmt.setProperties(map);
CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), unsuccessful.size());
CPPUNIT_ASSERT(cmt.properties().isEmpty());
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestXiphComment);