Merge branch 'master' of github.com:taglib/taglib

This commit is contained in:
Lukáš Lalinský
2011-04-09 19:19:49 +02:00
15 changed files with 354 additions and 14 deletions

2
NEWS
View File

@ -1,7 +1,9 @@
TagLib 1.8 (In Development)
===========================
* Support for writing ID3v2.3 tags.
* Added methods for checking if WMA and MP4 files are DRM-protected.
* Started using atomic int operations for reference counting.
TagLib 1.7 (Mar 11, 2011)
=========================

View File

@ -152,7 +152,7 @@ ByteVector AttachedPictureFrame::renderFields() const
{
ByteVector data;
String::Type encoding = checkEncoding(d->description, d->textEncoding);
String::Type encoding = checkTextEncoding(d->description, d->textEncoding);
data.append(char(encoding));
data.append(d->mimeType.data(String::Latin1));

View File

@ -155,8 +155,8 @@ ByteVector CommentsFrame::renderFields() const
String::Type encoding = d->textEncoding;
encoding = checkEncoding(d->description, encoding);
encoding = checkEncoding(d->text, encoding);
encoding = checkTextEncoding(d->description, encoding);
encoding = checkTextEncoding(d->text, encoding);
v.append(char(encoding));
v.append(d->language.size() == 3 ? d->language : "XXX");

View File

@ -139,7 +139,7 @@ void TextIdentificationFrame::parseFields(const ByteVector &data)
ByteVector TextIdentificationFrame::renderFields() const
{
String::Type encoding = checkEncoding(d->fieldList, d->textEncoding);
String::Type encoding = checkTextEncoding(d->fieldList, d->textEncoding);
ByteVector v;

View File

@ -175,7 +175,7 @@ ByteVector UserUrlLinkFrame::renderFields() const
{
ByteVector v;
String::Type encoding = checkEncoding(d->description, d->textEncoding);
String::Type encoding = checkTextEncoding(d->description, d->textEncoding);
v.append(char(encoding));
v.append(d->description.data(encoding));

View File

@ -230,19 +230,38 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int
String::Type Frame::checkEncoding(const StringList &fields, String::Type encoding) // static
{
return checkEncoding(fields, encoding, 4);
}
String::Type Frame::checkEncoding(const StringList &fields, String::Type encoding, uint version) // static
{
if((encoding == String::UTF8 || encoding == String::UTF16BE) && version != 4)
return String::UTF16;
if(encoding != String::Latin1)
return encoding;
for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
if(!(*it).isLatin1()) {
debug("Frame::checkEncoding() -- Rendering using UTF8.");
return String::UTF8;
if(version == 4) {
debug("Frame::checkEncoding() -- Rendering using UTF8.");
return String::UTF8;
}
else {
debug("Frame::checkEncoding() -- Rendering using UTF16.");
return String::UTF16;
}
}
}
return String::Latin1;
}
String::Type Frame::checkTextEncoding(const StringList &fields, String::Type encoding) const
{
return checkEncoding(fields, encoding, header()->version());
}
////////////////////////////////////////////////////////////////////////////////
// Frame::Header class
////////////////////////////////////////////////////////////////////////////////
@ -484,6 +503,11 @@ TagLib::uint Frame::Header::version() const
return d->version;
}
void Frame::Header::setVersion(TagLib::uint version)
{
d->version = version;
}
bool Frame::Header::tagAlterPreservation() const
{
return d->tagAlterPreservation;
@ -538,7 +562,11 @@ ByteVector Frame::Header::render() const
{
ByteVector flags(2, char(0)); // just blank for the moment
ByteVector v = d->frameID + SynchData::fromUInt(d->frameSize) + flags;
ByteVector v = d->frameID +
(d->version == 3
? ByteVector::fromUInt(d->frameSize)
: SynchData::fromUInt(d->frameSize)) +
flags;
return v;
}

View File

@ -199,9 +199,29 @@ namespace TagLib {
* Checks a the list of string values to see if they can be used with the
* specified encoding and returns the recommended encoding.
*/
// BIC: remove and make non-static
static String::Type checkEncoding(const StringList &fields,
String::Type encoding);
/*!
* Checks a the list of string values to see if they can be used with the
* specified encoding and returns the recommended encoding. This method
* also checks the ID3v2 version and makes sure the encoding can be used
* in the specified version.
*/
// BIC: remove and make non-static
static String::Type checkEncoding(const StringList &fields,
String::Type encoding, uint version);
/*!
* Checks a the list of string values to see if they can be used with the
* specified encoding and returns the recommended encoding. This method
* also checks the ID3v2 version and makes sure the encoding can be used
* in the version specified by the frame's header.
*/
String::Type checkTextEncoding(const StringList &fields,
String::Type encoding) const;
private:
Frame(const Frame &);
Frame &operator=(const Frame &);
@ -294,11 +314,17 @@ namespace TagLib {
void setFrameSize(uint size);
/*!
* Returns the ID3v2 version of the header (as passed in from the
* construction of the header).
* Returns the ID3v2 version of the header, as passed in from the
* construction of the header or set via setVersion().
*/
uint version() const;
/*!
* Sets the ID3v2 version of the header, changing has impact on the
* correct parsing/rendering of frame data.
*/
void setVersion(uint version);
/*!
* Returns the size of the frame header in bytes.
*

View File

@ -368,6 +368,7 @@ bool FrameFactory::updateFrame(Frame::Header *header) const
convertFrame("TORY", "TDOR", header);
convertFrame("TYER", "TDRC", header);
convertFrame("IPLS", "TIPL", header);
break;
}

View File

@ -164,7 +164,7 @@ ByteVector Header::render() const
// add the version number -- we always render a 2.4.0 tag regardless of what
// the tag originally was.
v.append(char(4));
v.append(char(majorVersion()));
v.append(char(0));
// Currently we don't actually support writing extended headers, footers or

View File

@ -330,6 +330,106 @@ void ID3v2::Tag::removeFrames(const ByteVector &id)
}
ByteVector ID3v2::Tag::render() const
{
return render(4);
}
void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
{
const char *unsupportedFrames[] = {
"ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
"TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0
};
ID3v2::TextIdentificationFrame *frameTDOR = 0;
ID3v2::TextIdentificationFrame *frameTDRC = 0;
ID3v2::TextIdentificationFrame *frameTIPL = 0;
ID3v2::TextIdentificationFrame *frameTMCL = 0;
for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) {
ID3v2::Frame *frame = *it;
ByteVector frameID = frame->header()->frameID();
for(int i = 0; unsupportedFrames[i]; i++) {
if(frameID == unsupportedFrames[i]) {
debug("A frame that is not supported in ID3v2.3 \'"
+ String(frameID) + "\' has been discarded");
frame = 0;
break;
}
}
if(frame && frameID == "TDOR") {
frameTDOR = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
frame = 0;
}
if(frame && frameID == "TDRC") {
frameTDRC = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
frame = 0;
}
if(frame && frameID == "TIPL") {
frameTIPL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
frame = 0;
}
if(frame && frameID == "TMCL") {
frameTMCL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
frame = 0;
}
if(frame) {
frames->append(frame);
}
}
if(frameTDOR) {
String content = frameTDOR->toString();
if(content.size() >= 4) {
ID3v2::TextIdentificationFrame *frameTORY = new ID3v2::TextIdentificationFrame("TORY", String::Latin1);
frameTORY->setText(content.substr(0, 4));
frames->append(frameTORY);
newFrames->append(frameTORY);
}
}
if(frameTDRC) {
String content = frameTDRC->toString();
if(content.size() >= 4) {
ID3v2::TextIdentificationFrame *frameTYER = new ID3v2::TextIdentificationFrame("TYER", String::Latin1);
frameTYER->setText(content.substr(0, 4));
frames->append(frameTYER);
newFrames->append(frameTYER);
if(content.size() >= 10 && content[4] == '-' && content[7] == '-') {
ID3v2::TextIdentificationFrame *frameTDAT = new ID3v2::TextIdentificationFrame("TDAT", String::Latin1);
frameTDAT->setText(content.substr(8, 2) + content.substr(5, 2));
frames->append(frameTDAT);
newFrames->append(frameTDAT);
if(content.size() >= 16 && content[10] == 'T' && content[13] == ':') {
ID3v2::TextIdentificationFrame *frameTIME = new ID3v2::TextIdentificationFrame("TIME", String::Latin1);
frameTIME->setText(content.substr(11, 2) + content.substr(14, 2));
frames->append(frameTIME);
newFrames->append(frameTIME);
}
}
}
}
if(frameTIPL || frameTMCL) {
ID3v2::TextIdentificationFrame *frameIPLS = new ID3v2::TextIdentificationFrame("IPLS", String::Latin1);
StringList people;
if(frameTMCL) {
StringList v24People = frameTMCL->fieldList();
for(uint 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) {
people.append(v24People[i]);
people.append(v24People[i+1]);
}
}
frameIPLS->setText(people);
frames->append(frameIPLS);
newFrames->append(frameIPLS);
}
}
ByteVector ID3v2::Tag::render(int version) const
{
// We need to render the "tag data" first so that we have to correct size to
// render in the tag's header. The "tag data" -- everything that is included
@ -338,11 +438,28 @@ ByteVector ID3v2::Tag::render() const
ByteVector tagData;
if(version != 3 && version != 4) {
debug("Unknown ID3v2 version, using ID3v2.4");
version = 4;
}
// TODO: Render the extended header.
// Loop through the frames rendering them and adding them to the tagData.
for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) {
FrameList newFrames;
newFrames.setAutoDelete(true);
FrameList frameList;
if(version == 4) {
frameList = d->frameList;
}
else {
downgradeFrames(&frameList, &newFrames);
}
for(FrameList::Iterator it = frameList.begin(); it != frameList.end(); it++) {
(*it)->header()->setVersion(version);
if((*it)->header()->frameID().size() != 4) {
debug("A frame of unsupported or unknown type \'"
+ String((*it)->header()->frameID()) + "\' has been discarded");
@ -364,7 +481,8 @@ ByteVector ID3v2::Tag::render() const
tagData.append(ByteVector(paddingSize, char(0)));
// Set the tag size.
// Set the version and data size.
d->header.setMajorVersion(version);
d->header.setTagSize(tagData.size());
// TODO: This should eventually include d->footer->render().

View File

@ -265,6 +265,15 @@ namespace TagLib {
*/
ByteVector render() const;
/*!
* Render the tag back to binary data, suitable to be written to disk.
*
* The \a version parameter specifies the version of the rendered
* ID3v2 tag. It can be either 4 or 3.
*/
// BIC: combine with the above method
ByteVector render(int version) const;
protected:
/*!
* Reads data from the file specified in the constructor. It does basic
@ -286,6 +295,8 @@ namespace TagLib {
*/
void setTextFrame(const ByteVector &id, const String &value);
void downgradeFrames(FrameList *existingFrames, FrameList *newFrames) const;
private:
Tag(const Tag &);
Tag &operator=(const Tag &);

View File

@ -139,6 +139,11 @@ bool MPEG::File::save(int tags)
}
bool MPEG::File::save(int tags, bool stripOthers)
{
return save(tags, stripOthers, 4);
}
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
{
if(tags == NoTags && stripOthers)
return strip(AllTags);
@ -174,7 +179,7 @@ bool MPEG::File::save(int tags, bool stripOthers)
if(!d->hasID3v2)
d->ID3v2Location = 0;
insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize);
insert(ID3v2Tag()->render(id3v2Version), d->ID3v2Location, d->ID3v2OriginalSize);
d->hasID3v2 = true;

View File

@ -161,6 +161,21 @@ namespace TagLib {
// BIC: combine with the above method
bool save(int tags, bool stripOthers);
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
*
* 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.
*
* The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3.
*/
// BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version);
/*!
* Returns a pointer to the ID3v2 tag of the file.
*

View File

@ -1,9 +1,12 @@
#include <cppunit/extensions/HelperMacros.h>
#include <string>
#include <stdio.h>
// so evil :(
#define protected public
#include <id3v2tag.h>
#include <mpegfile.h>
#include <id3v2frame.h>
#undef protected
#include <uniquefileidentifierframe.h>
#include <textidentificationframe.h>
#include <attachedpictureframe.h>
@ -33,6 +36,7 @@ class TestID3v2 : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestID3v2);
CPPUNIT_TEST(testUnsynchDecode);
CPPUNIT_TEST(testDowngradeUTF8ForID3v23);
CPPUNIT_TEST(testUTF16BEDelimiter);
CPPUNIT_TEST(testUTF16Delimiter);
CPPUNIT_TEST(testReadStringField);
@ -60,6 +64,7 @@ class TestID3v2 : public CppUnit::TestFixture
CPPUNIT_TEST(testUpdateGenre23_2);
CPPUNIT_TEST(testUpdateGenre24);
CPPUNIT_TEST(testUpdateDate22);
CPPUNIT_TEST(testDowngradeTo23);
// CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together
CPPUNIT_TEST(testCompressedFrameWithBrokenLength);
CPPUNIT_TEST_SUITE_END();
@ -73,6 +78,20 @@ public:
CPPUNIT_ASSERT_EQUAL(String("My babe just cares for me"), f.tag()->title());
}
void testDowngradeUTF8ForID3v23()
{
ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF8);
StringList sl;
sl.append("Foo");
f.setText(sl);
f.header()->setVersion(3);
ByteVector data = f.render();
CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+6+2), data.size());
ID3v2::TextIdentificationFrame f2(data);
CPPUNIT_ASSERT_EQUAL(sl, f2.fieldList());
CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding());
}
void testUTF16BEDelimiter()
{
ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF16BE);
@ -456,6 +475,65 @@ public:
CPPUNIT_ASSERT_EQUAL(String("2010-04-03"), f.ID3v2Tag()->frameListMap()["TDRC"].front()->toString());
}
void testDowngradeTo23()
{
ScopedFileCopy copy("xing", ".mp3");
string newname = copy.fileName();
ID3v2::TextIdentificationFrame *tf;
MPEG::File foo(newname.c_str());
tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1);
tf->setText("2011-03-16");
foo.ID3v2Tag()->addFrame(tf);
tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1);
tf->setText("2012-04-17T12:01");
foo.ID3v2Tag()->addFrame(tf);
tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1);
tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2"));
foo.ID3v2Tag()->addFrame(tf);
tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1);
tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4"));
foo.ID3v2Tag()->addFrame(tf);
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1));
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1));
foo.save(MPEG::File::AllTags, true, 3);
MPEG::File bar(newname.c_str());
tf = static_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TDOR").front());
CPPUNIT_ASSERT(tf);
CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size());
CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front());
tf = static_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TDRC").front());
CPPUNIT_ASSERT(tf);
CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), tf->fieldList().size());
CPPUNIT_ASSERT_EQUAL(String("2012"), tf->fieldList().front());
tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TIPL").front());
CPPUNIT_ASSERT(tf);
CPPUNIT_ASSERT_EQUAL(TagLib::uint(8), tf->fieldList().size());
CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]);
CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]);
CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]);
CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]);
CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]);
CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]);
CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]);
CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]);
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST"));
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP"));
}
void testCompressedFrameWithBrokenLength()
{
MPEG::File f("data/compressed_id3_frame.mp3", false);

View File

@ -2,6 +2,8 @@
#include <string>
#include <stdio.h>
#include <mpegfile.h>
#include <id3v2tag.h>
#include "utils.h"
using namespace std;
using namespace TagLib;
@ -10,6 +12,9 @@ class TestMPEG : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestMPEG);
CPPUNIT_TEST(testVersion2DurationWithXingHeader);
CPPUNIT_TEST(testSaveID3v24);
CPPUNIT_TEST(testSaveID3v24WrongParam);
CPPUNIT_TEST(testSaveID3v23);
CPPUNIT_TEST_SUITE_END();
public:
@ -20,6 +25,57 @@ public:
CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->length());
}
void testSaveID3v24()
{
ScopedFileCopy copy("xing", ".mp3");
string newname = copy.fileName();
String xxx = ByteVector(254, 'X');
MPEG::File f(newname.c_str());
f.tag()->setTitle(xxx);
f.tag()->setArtist("Artist A");
f.save(MPEG::File::AllTags, true, 4);
MPEG::File f2(newname.c_str());
CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion());
CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist());
CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title());
}
void testSaveID3v24WrongParam()
{
ScopedFileCopy copy("xing", ".mp3");
string newname = copy.fileName();
String xxx = ByteVector(254, 'X');
MPEG::File f(newname.c_str());
f.tag()->setTitle(xxx);
f.tag()->setArtist("Artist A");
f.save(MPEG::File::AllTags, true, 8);
MPEG::File f2(newname.c_str());
CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f2.ID3v2Tag()->header()->majorVersion());
CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist());
CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title());
}
void testSaveID3v23()
{
ScopedFileCopy copy("xing", ".mp3");
string newname = copy.fileName();
String xxx = ByteVector(254, 'X');
MPEG::File f(newname.c_str());
f.tag()->setTitle(xxx);
f.tag()->setArtist("Artist A");
f.save(MPEG::File::AllTags, true, 3);
MPEG::File f2(newname.c_str());
CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f2.ID3v2Tag()->header()->majorVersion());
CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist());
CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title());
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG);