From c357d293a14bdd5b6e332cf9b14b4bb7bedf3687 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 13 Mar 2014 14:38:03 +0900 Subject: [PATCH 1/9] Added some comment about UTF-16 encoding to String::data(). --- taglib/toolkit/tstring.cpp | 3 +-- taglib/toolkit/tstring.h | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 0151f784..603455a1 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -457,8 +457,7 @@ ByteVector String::data(Type t) const ByteVector v(2 + size() * 2, 0); char *p = v.data(); - // Assume that if we're doing UTF16 and not UTF16BE that we want little - // endian encoding. (Byte Order Mark) + // We use little-endian encoding here and need a BOM. *p++ = '\xff'; *p++ = '\xfe'; diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index 2cff5a4b..795e913f 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -334,6 +334,9 @@ namespace TagLib { * UTF8, this will return a vector of 8 bit characters, otherwise it will use * 16 bit characters. * + * \note If \a t is UTF16, the returned data is encoded in little-endian + * format and has a BOM. + * * \note The returned data is not null terminated. */ ByteVector data(Type t) const; From 6bcd422afacd5bbf5a64fc18a8e1dd3fe865c793 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 14 Mar 2014 15:12:03 +0900 Subject: [PATCH 2/9] Small fix of a comment --- taglib/toolkit/tstring.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index 795e913f..76e4c32f 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -163,7 +163,6 @@ namespace TagLib { */ String(wchar_t c, Type t = Latin1); - /*! * Makes a deep copy of the data in \a s. * @@ -173,7 +172,7 @@ namespace TagLib { String(const char *s, Type t = Latin1); /*! - * Makes a deep copy of the data in \a s. + * Makes a deep copy of the data in \a v. * * \note This should only be used with the 8-bit codecs Latin1 and UTF8, when * used with other codecs it will simply print a warning and exit. From eba99c3a701688a6e7b5bb2a22d29ceff24f8f34 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 30 Mar 2014 09:26:03 +0200 Subject: [PATCH 3/9] Add support for ID3v2 SYLT frames (synchronized lyrics). --- taglib/CMakeLists.txt | 2 + .../id3v2/frames/synchronizedlyricsframe.cpp | 243 ++++++++++++++++++ .../id3v2/frames/synchronizedlyricsframe.h | 231 +++++++++++++++++ taglib/mpeg/id3v2/id3v2framefactory.cpp | 10 + tests/test_id3v2.cpp | 60 +++++ 5 files changed, 546 insertions(+) create mode 100644 taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 23fa60df..e61bba01 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -72,6 +72,7 @@ set(tag_HDRS mpeg/id3v2/frames/popularimeterframe.h mpeg/id3v2/frames/privateframe.h mpeg/id3v2/frames/relativevolumeframe.h + mpeg/id3v2/frames/synchronizedlyricsframe.h mpeg/id3v2/frames/textidentificationframe.h mpeg/id3v2/frames/uniquefileidentifierframe.h mpeg/id3v2/frames/unknownframe.h @@ -162,6 +163,7 @@ set(frames_SRCS mpeg/id3v2/frames/popularimeterframe.cpp mpeg/id3v2/frames/privateframe.cpp mpeg/id3v2/frames/relativevolumeframe.cpp + mpeg/id3v2/frames/synchronizedlyricsframe.cpp mpeg/id3v2/frames/textidentificationframe.cpp mpeg/id3v2/frames/uniquefileidentifierframe.cpp mpeg/id3v2/frames/unknownframe.cpp diff --git a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp new file mode 100644 index 00000000..878af9bd --- /dev/null +++ b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp @@ -0,0 +1,243 @@ +/*************************************************************************** + copyright : (C) 2014 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 "synchronizedlyricsframe.h" +#include +#include +#include +#include + +using namespace TagLib; +using namespace ID3v2; + +class SynchronizedLyricsFrame::SynchronizedLyricsFramePrivate +{ +public: + SynchronizedLyricsFramePrivate() : + textEncoding(String::Latin1), + timestampFormat(SynchronizedLyricsFrame::AbsoluteMilliseconds), + type(SynchronizedLyricsFrame::Lyrics) {} + String::Type textEncoding; + ByteVector language; + SynchronizedLyricsFrame::TimestampFormat timestampFormat; + SynchronizedLyricsFrame::Type type; + String description; + SynchronizedLyricsFrame::SynchedTextList synchedText; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +SynchronizedLyricsFrame::SynchronizedLyricsFrame(String::Type encoding) : + Frame("SYLT") +{ + d = new SynchronizedLyricsFramePrivate; + d->textEncoding = encoding; +} + +SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data) : + Frame(data) +{ + d = new SynchronizedLyricsFramePrivate; + setData(data); +} + +SynchronizedLyricsFrame::~SynchronizedLyricsFrame() +{ + delete d; +} + +String SynchronizedLyricsFrame::toString() const +{ + return d->description; +} + +String::Type SynchronizedLyricsFrame::textEncoding() const +{ + return d->textEncoding; +} + +ByteVector SynchronizedLyricsFrame::language() const +{ + return d->language; +} + +SynchronizedLyricsFrame::TimestampFormat +SynchronizedLyricsFrame::timestampFormat() const +{ + return d->timestampFormat; +} + +SynchronizedLyricsFrame::Type SynchronizedLyricsFrame::type() const +{ + return d->type; +} + +String SynchronizedLyricsFrame::description() const +{ + return d->description; +} + +SynchronizedLyricsFrame::SynchedTextList +SynchronizedLyricsFrame::synchedText() const +{ + return d->synchedText; +} + +void SynchronizedLyricsFrame::setTextEncoding(String::Type encoding) +{ + d->textEncoding = encoding; +} + +void SynchronizedLyricsFrame::setLanguage(const ByteVector &languageEncoding) +{ + d->language = languageEncoding.mid(0, 3); +} + +void SynchronizedLyricsFrame::setTimestampFormat( + SynchronizedLyricsFrame::TimestampFormat f) +{ + d->timestampFormat = f; +} + +void SynchronizedLyricsFrame::setType(SynchronizedLyricsFrame::Type t) +{ + d->type = t; +} + +void SynchronizedLyricsFrame::setDescription(const String &s) +{ + d->description = s; +} + +void SynchronizedLyricsFrame::setSynchedText( + const SynchronizedLyricsFrame::SynchedTextList &t) +{ + d->synchedText = t; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void SynchronizedLyricsFrame::parseFields(const ByteVector &data) +{ + const int end = data.size(); + if(end < 7) { + debug("A synchronized lyrics frame must contain at least 7 bytes."); + return; + } + + d->textEncoding = String::Type(data[0]); + d->language = data.mid(1, 3); + d->timestampFormat = TimestampFormat(data[4]); + d->type = Type(data[5]); + + int pos = 6; + + d->description = readStringField(data, d->textEncoding, &pos); + if(d->description.isNull()) + return; + + /* + * If UTF16 strings are found in SYLT frames, a BOM may only be + * present in the first string (content descriptor), and the strings of + * the synchronized text have no BOM. Here the BOM is read from + * the first string to have a specific encoding with endianness for the + * case of strings without BOM so that readStringField() will work. + */ + String::Type encWithEndianness = d->textEncoding; + if(d->textEncoding == String::UTF16) { + ushort bom = data.toUShort(6, true); + if(bom == 0xfffe) { + encWithEndianness = String::UTF16LE; + } else if(bom == 0xfeff) { + encWithEndianness = String::UTF16BE; + } + } + + d->synchedText.clear(); + while(pos < end) { + 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); + if(bom != 0xfffe && bom != 0xfeff) { + enc = encWithEndianness; + } + } + String text = readStringField(data, enc, &pos); + if(text.isNull() || pos + 4 > end) + return; + + uint time = data.mid(pos, 4).toUInt(true); + pos += 4; + + d->synchedText.append(SynchedText(time, text)); + } +} + +ByteVector SynchronizedLyricsFrame::renderFields() const +{ + ByteVector v; + + String::Type encoding = d->textEncoding; + + encoding = checkTextEncoding(d->description, encoding); + for(SynchedTextList::ConstIterator it = d->synchedText.begin(); + it != d->synchedText.end(); + ++it) { + encoding = checkTextEncoding(it->text, encoding); + } + + v.append(char(encoding)); + v.append(d->language.size() == 3 ? d->language : "XXX"); + v.append(char(d->timestampFormat)); + v.append(char(d->type)); + v.append(d->description.data(encoding)); + v.append(textDelimiter(encoding)); + for(SynchedTextList::ConstIterator it = d->synchedText.begin(); + it != d->synchedText.end(); + ++it) { + const SynchedText &entry = *it; + v.append(entry.text.data(encoding)); + v.append(textDelimiter(encoding)); + v.append(ByteVector::fromUInt(entry.time)); + } + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data, Header *h) + : Frame(h) +{ + d = new SynchronizedLyricsFramePrivate(); + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h new file mode 100644 index 00000000..6e81b51b --- /dev/null +++ b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h @@ -0,0 +1,231 @@ +/*************************************************************************** + copyright : (C) 2014 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_SYNCHRONIZEDLYRICSFRAME_H +#define TAGLIB_SYNCHRONIZEDLYRICSFRAME_H + +#include "id3v2frame.h" +#include "tlist.h" + +namespace TagLib { + + namespace ID3v2 { + + //! ID3v2 synchronized lyrics frame + /*! + * An implementation of ID3v2 synchronized lyrics. + */ + class TAGLIB_EXPORT SynchronizedLyricsFrame : public Frame + { + friend class FrameFactory; + + public: + + /*! + * Specifies the timestamp format used. + */ + enum TimestampFormat { + //! The timestamp is of unknown format. + Unknown = 0x00, + //! The timestamp represents the number of MPEG frames since + //! the beginning of the audio stream. + AbsoluteMpegFrames = 0x01, + //! The timestamp represents the number of milliseconds since + //! the beginning of the audio stream. + AbsoluteMilliseconds = 0x02 + }; + + /*! + * Specifies the type of text contained. + */ + enum Type { + //! The text is some other type of text. + Other = 0x00, + //! The text contains lyrical data. + Lyrics = 0x01, + //! The text contains a transcription. + TextTranscription = 0x02, + //! The text lists the movements in the piece. + Movement = 0x03, + //! The text describes events that occur. + Events = 0x04, + //! The text contains chord changes that occur in the music. + Chord = 0x05, + //! The text contains trivia or "pop up" information about the media. + Trivia = 0x06, + //! The text contains URLs for relevant webpages. + WebpageUrls = 0x07, + //! The text contains URLs for relevant images. + ImageUrls = 0x08 + }; + + /*! + * Single entry of time stamp and lyrics text. + */ + struct SynchedText { + SynchedText(uint ms, String str) : time(ms), text(str) {} + uint time; + String text; + }; + + /*! + * List of synchronized lyrics. + */ + typedef TagLib::List SynchedTextList; + + /*! + * Construct an empty synchronized lyrics frame that will use the text + * encoding \a encoding. + */ + explicit SynchronizedLyricsFrame(String::Type encoding = String::Latin1); + + /*! + * Construct a synchronized lyrics frame based on the data in \a data. + */ + explicit SynchronizedLyricsFrame(const ByteVector &data); + + /*! + * Destroys this SynchronizedLyricsFrame instance. + */ + virtual ~SynchronizedLyricsFrame(); + + /*! + * Returns the description of this synchronized lyrics frame. + * + * \see description() + */ + virtual String toString() const; + + /*! + * 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; + + /*! + * Returns the language encoding as a 3 byte encoding as specified by + * ISO-639-2. + * + * \note Most taggers simply ignore this value. + * + * \see setLanguage() + */ + ByteVector language() const; + + /*! + * Returns the timestamp format. + */ + TimestampFormat timestampFormat() const; + + /*! + * Returns the type of text contained. + */ + Type type() const; + + /*! + * Returns the description of this synchronized lyrics frame. + * + * \note Most taggers simply ignore this value. + * + * \see setDescription() + */ + String description() const; + + /*! + * Returns the text with the time stamps. + */ + SynchedTextList synchedText() const; + + /*! + * Sets the text encoding to be used when rendering this frame to + * \a encoding. + * + * \see textEncoding() + * \see render() + */ + void setTextEncoding(String::Type encoding); + + /*! + * Set the language using the 3 byte language code from + * ISO-639-2 to + * \a languageCode. + * + * \see language() + */ + void setLanguage(const ByteVector &languageCode); + + /*! + * Set the timestamp format. + * + * \see timestampFormat() + */ + void setTimestampFormat(TimestampFormat f); + + /*! + * Set the type of text contained. + * + * \see type() + */ + void setType(Type t); + + /*! + * Sets the description of the synchronized lyrics frame to \a s. + * + * \see decription() + */ + void setDescription(const String &s); + + /*! + * Sets the text with the time stamps. + * + * \see text() + */ + void setSynchedText(const SynchedTextList &t); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + SynchronizedLyricsFrame(const ByteVector &data, Header *h); + SynchronizedLyricsFrame(const SynchronizedLyricsFrame &); + SynchronizedLyricsFrame &operator=(const SynchronizedLyricsFrame &); + + class SynchronizedLyricsFramePrivate; + SynchronizedLyricsFramePrivate *d; + }; + + } +} +#endif diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 3371ca7d..74d4f382 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -45,6 +45,7 @@ #include "frames/popularimeterframe.h" #include "frames/privateframe.h" #include "frames/ownershipframe.h" +#include "frames/synchronizedlyricsframe.h" using namespace TagLib; using namespace ID3v2; @@ -241,6 +242,15 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) return f; } + // Synchronised lyrics/text (frames 4.9) + + if(frameID == "SYLT") { + SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header); + if(d->useDefaultEncoding) + f->setTextEncoding(d->defaultEncoding); + return f; + } + // Popularimeter (frames 4.17) if(frameID == "POPM") diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 9f5ffe01..da74b960 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderUserUrlLinkFrame); CPPUNIT_TEST(testParseOwnershipFrame); CPPUNIT_TEST(testRenderOwnershipFrame); + CPPUNIT_TEST(testParseSynchronizedLyricsFrame); + CPPUNIT_TEST(testRenderSynchronizedLyricsFrame); CPPUNIT_TEST(testSaveUTF16Comment); CPPUNIT_TEST(testUpdateGenre23_1); CPPUNIT_TEST(testUpdateGenre23_2); @@ -427,6 +430,63 @@ public: f.render()); } + void testParseSynchronizedLyricsFrame() + { + ID3v2::SynchronizedLyricsFrame f( + ByteVector("SYLT" // Frame ID + "\x00\x00\x00\x21" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "eng" // Language + "\x02" // Time stamp format + "\x01" // Content type + "foo\x00" // Content descriptor + "Example\x00" // 1st text + "\x00\x00\x04\xd2" // 1st time stamp + "Lyrics\x00" // 2nd text + "\x00\x00\x11\xd7", 43)); // 2nd time stamp + CPPUNIT_ASSERT_EQUAL(String::Latin1, f.textEncoding()); + CPPUNIT_ASSERT_EQUAL(ByteVector("eng", 3), f.language()); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds, + f.timestampFormat()); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::Lyrics, f.type()); + CPPUNIT_ASSERT_EQUAL(String("foo"), f.description()); + ID3v2::SynchronizedLyricsFrame::SynchedTextList stl = f.synchedText(); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), stl.size()); + CPPUNIT_ASSERT_EQUAL(String("Example"), stl[0].text); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(1234), stl[0].time); + CPPUNIT_ASSERT_EQUAL(String("Lyrics"), stl[1].text); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(4567), stl[1].time); + } + + void testRenderSynchronizedLyricsFrame() + { + ID3v2::SynchronizedLyricsFrame f; + f.setTextEncoding(String::Latin1); + f.setLanguage(ByteVector("eng", 3)); + f.setTimestampFormat(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds); + f.setType(ID3v2::SynchronizedLyricsFrame::Lyrics); + f.setDescription("foo"); + ID3v2::SynchronizedLyricsFrame::SynchedTextList stl; + stl.append(ID3v2::SynchronizedLyricsFrame::SynchedText(1234, "Example")); + stl.append(ID3v2::SynchronizedLyricsFrame::SynchedText(4567, "Lyrics")); + f.setSynchedText(stl); + CPPUNIT_ASSERT_EQUAL( + ByteVector("SYLT" // Frame ID + "\x00\x00\x00\x21" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "eng" // Language + "\x02" // Time stamp format + "\x01" // Content type + "foo\x00" // Content descriptor + "Example\x00" // 1st text + "\x00\x00\x04\xd2" // 1st time stamp + "Lyrics\x00" // 2nd text + "\x00\x00\x11\xd7", 43), // 2nd time stamp + f.render()); + } + void testItunes24FrameSize() { MPEG::File f(TEST_FILE_PATH_C("005411.id3"), false); From 3b60af2c0b051fe8227a689a8c33bf3b321accdd Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 30 Mar 2014 09:27:23 +0200 Subject: [PATCH 4/9] Add support for ID3v2 ETCO frames (event timing codes). --- taglib/CMakeLists.txt | 2 + .../id3v2/frames/eventtimingcodesframe.cpp | 144 ++++++++++++++ .../mpeg/id3v2/frames/eventtimingcodesframe.h | 185 ++++++++++++++++++ taglib/mpeg/id3v2/id3v2framefactory.cpp | 6 + tests/test_id3v2.cpp | 44 +++++ 5 files changed, 381 insertions(+) create mode 100644 taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp create mode 100644 taglib/mpeg/id3v2/frames/eventtimingcodesframe.h diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index e61bba01..89cb8e12 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -67,6 +67,7 @@ set(tag_HDRS mpeg/id3v2/id3v2tag.h mpeg/id3v2/frames/attachedpictureframe.h mpeg/id3v2/frames/commentsframe.h + mpeg/id3v2/frames/eventtimingcodesframe.h mpeg/id3v2/frames/generalencapsulatedobjectframe.h mpeg/id3v2/frames/ownershipframe.h mpeg/id3v2/frames/popularimeterframe.h @@ -158,6 +159,7 @@ set(id3v2_SRCS set(frames_SRCS mpeg/id3v2/frames/attachedpictureframe.cpp mpeg/id3v2/frames/commentsframe.cpp + mpeg/id3v2/frames/eventtimingcodesframe.cpp mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp mpeg/id3v2/frames/ownershipframe.cpp mpeg/id3v2/frames/popularimeterframe.cpp diff --git a/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp b/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp new file mode 100644 index 00000000..70214bad --- /dev/null +++ b/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp @@ -0,0 +1,144 @@ +/*************************************************************************** + copyright : (C) 2014 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 "eventtimingcodesframe.h" +#include +#include +#include +#include + +using namespace TagLib; +using namespace ID3v2; + +class EventTimingCodesFrame::EventTimingCodesFramePrivate +{ +public: + EventTimingCodesFramePrivate() : + timestampFormat(EventTimingCodesFrame::AbsoluteMilliseconds) {} + EventTimingCodesFrame::TimestampFormat timestampFormat; + EventTimingCodesFrame::SynchedEventList synchedEvents; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EventTimingCodesFrame::EventTimingCodesFrame() : + Frame("ETCO") +{ + d = new EventTimingCodesFramePrivate; +} + +EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data) : + Frame(data) +{ + d = new EventTimingCodesFramePrivate; + setData(data); +} + +EventTimingCodesFrame::~EventTimingCodesFrame() +{ + delete d; +} + +String EventTimingCodesFrame::toString() const +{ + return String(); +} + +EventTimingCodesFrame::TimestampFormat +EventTimingCodesFrame::timestampFormat() const +{ + return d->timestampFormat; +} + +EventTimingCodesFrame::SynchedEventList +EventTimingCodesFrame::synchedEvents() const +{ + return d->synchedEvents; +} + +void EventTimingCodesFrame::setTimestampFormat( + EventTimingCodesFrame::TimestampFormat f) +{ + d->timestampFormat = f; +} + +void EventTimingCodesFrame::setSynchedEvents( + const EventTimingCodesFrame::SynchedEventList &e) +{ + d->synchedEvents = e; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void EventTimingCodesFrame::parseFields(const ByteVector &data) +{ + const int end = data.size(); + if(end < 1) { + debug("An event timing codes frame must contain at least 1 byte."); + return; + } + + d->timestampFormat = TimestampFormat(data[0]); + + int pos = 1; + d->synchedEvents.clear(); + while(pos + 4 < end) { + EventType type = EventType(uchar(data[pos++])); + uint time = data.toUInt(pos, true); + pos += 4; + d->synchedEvents.append(SynchedEvent(time, type)); + } +} + +ByteVector EventTimingCodesFrame::renderFields() const +{ + ByteVector v; + + v.append(char(d->timestampFormat)); + for(SynchedEventList::ConstIterator it = d->synchedEvents.begin(); + it != d->synchedEvents.end(); + ++it) { + const SynchedEvent &entry = *it; + v.append(char(entry.type)); + v.append(ByteVector::fromUInt(entry.time)); + } + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data, Header *h) + : Frame(h) +{ + d = new EventTimingCodesFramePrivate(); + parseFields(fieldData(data)); +} diff --git a/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h b/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h new file mode 100644 index 00000000..0719f51f --- /dev/null +++ b/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h @@ -0,0 +1,185 @@ +/*************************************************************************** + copyright : (C) 2014 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_EVENTTIMINGCODESFRAME_H +#define TAGLIB_EVENTTIMINGCODESFRAME_H + +#include "id3v2frame.h" +#include "tlist.h" + +namespace TagLib { + + namespace ID3v2 { + + //! ID3v2 event timing codes frame + /*! + * An implementation of ID3v2 event timing codes. + */ + class TAGLIB_EXPORT EventTimingCodesFrame : public Frame + { + friend class FrameFactory; + + public: + + /*! + * Specifies the timestamp format used. + */ + enum TimestampFormat { + //! The timestamp is of unknown format. + Unknown = 0x00, + //! The timestamp represents the number of MPEG frames since + //! the beginning of the audio stream. + AbsoluteMpegFrames = 0x01, + //! The timestamp represents the number of milliseconds since + //! the beginning of the audio stream. + AbsoluteMilliseconds = 0x02 + }; + + /*! + * Event types defined in id3v2.4.0-frames.txt 4.5. Event timing codes. + */ + enum EventType { + Padding = 0x00, + EndOfInitialSilence = 0x01, + IntroStart = 0x02, + MainPartStart = 0x03, + OutroStart = 0x04, + OutroEnd = 0x05, + VerseStart = 0x06, + RefrainStart = 0x07, + InterludeStart = 0x08, + ThemeStart = 0x09, + VariationStart = 0x0a, + KeyChange = 0x0b, + TimeChange = 0x0c, + MomentaryUnwantedNoise = 0x0d, + SustainedNoise = 0x0e, + SustainedNoiseEnd = 0x0f, + IntroEnd = 0x10, + MainPartEnd = 0x11, + VerseEnd = 0x12, + RefrainEnd = 0x13, + ThemeEnd = 0x14, + Profanity = 0x15, + ProfanityEnd = 0x16, + NotPredefinedSynch0 = 0xe0, + NotPredefinedSynch1 = 0xe1, + NotPredefinedSynch2 = 0xe2, + NotPredefinedSynch3 = 0xe3, + NotPredefinedSynch4 = 0xe4, + NotPredefinedSynch5 = 0xe5, + NotPredefinedSynch6 = 0xe6, + NotPredefinedSynch7 = 0xe7, + NotPredefinedSynch8 = 0xe8, + NotPredefinedSynch9 = 0xe9, + NotPredefinedSynchA = 0xea, + NotPredefinedSynchB = 0xeb, + NotPredefinedSynchC = 0xec, + NotPredefinedSynchD = 0xed, + NotPredefinedSynchE = 0xee, + NotPredefinedSynchF = 0xef, + AudioEnd = 0xfd, + AudioFileEnds = 0xfe + }; + + /*! + * Single entry of time stamp and event. + */ + struct SynchedEvent { + SynchedEvent(uint ms, EventType t) : time(ms), type(t) {} + uint time; + EventType type; + }; + + /*! + * List of synchronized events. + */ + typedef TagLib::List SynchedEventList; + + /*! + * Construct an empty event timing codes frame. + */ + explicit EventTimingCodesFrame(); + + /*! + * Construct a event timing codes frame based on the data in \a data. + */ + explicit EventTimingCodesFrame(const ByteVector &data); + + /*! + * Destroys this EventTimingCodesFrame instance. + */ + virtual ~EventTimingCodesFrame(); + + /*! + * Returns a null string. + */ + virtual String toString() const; + + /*! + * Returns the timestamp format. + */ + TimestampFormat timestampFormat() const; + + /*! + * Returns the events with the time stamps. + */ + SynchedEventList synchedEvents() const; + + /*! + * Set the timestamp format. + * + * \see timestampFormat() + */ + void setTimestampFormat(TimestampFormat f); + + /*! + * Sets the text with the time stamps. + * + * \see text() + */ + void setSynchedEvents(const SynchedEventList &e); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + EventTimingCodesFrame(const ByteVector &data, Header *h); + EventTimingCodesFrame(const EventTimingCodesFrame &); + EventTimingCodesFrame &operator=(const EventTimingCodesFrame &); + + class EventTimingCodesFramePrivate; + EventTimingCodesFramePrivate *d; + }; + + } +} +#endif diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 74d4f382..b2c32ce4 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -46,6 +46,7 @@ #include "frames/privateframe.h" #include "frames/ownershipframe.h" #include "frames/synchronizedlyricsframe.h" +#include "frames/eventtimingcodesframe.h" using namespace TagLib; using namespace ID3v2; @@ -251,6 +252,11 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) return f; } + // Event timing codes (frames 4.5) + + if(frameID == "ETCO") + return new EventTimingCodesFrame(data, header); + // Popularimeter (frames 4.17) if(frameID == "POPM") diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index da74b960..fadcae2f 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderOwnershipFrame); CPPUNIT_TEST(testParseSynchronizedLyricsFrame); CPPUNIT_TEST(testRenderSynchronizedLyricsFrame); + CPPUNIT_TEST(testParseEventTimingCodesFrame); + CPPUNIT_TEST(testRenderEventTimingCodesFrame); CPPUNIT_TEST(testSaveUTF16Comment); CPPUNIT_TEST(testUpdateGenre23_1); CPPUNIT_TEST(testUpdateGenre23_2); @@ -487,6 +490,47 @@ public: f.render()); } + void testParseEventTimingCodesFrame() + { + ID3v2::EventTimingCodesFrame f( + ByteVector("ETCO" // Frame ID + "\x00\x00\x00\x0b" // Frame size + "\x00\x00" // Frame flags + "\x02" // Time stamp format + "\x02" // 1st event + "\x00\x00\xf3\x5c" // 1st time stamp + "\xfe" // 2nd event + "\x00\x36\xee\x80", 21)); // 2nd time stamp + CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::AbsoluteMilliseconds, + f.timestampFormat()); + ID3v2::EventTimingCodesFrame::SynchedEventList sel = f.synchedEvents(); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), sel.size()); + CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::IntroStart, sel[0].type); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(62300), sel[0].time); + CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::AudioFileEnds, sel[1].type); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(3600000), sel[1].time); + } + + void testRenderEventTimingCodesFrame() + { + ID3v2::EventTimingCodesFrame f; + f.setTimestampFormat(ID3v2::EventTimingCodesFrame::AbsoluteMilliseconds); + ID3v2::EventTimingCodesFrame::SynchedEventList sel; + sel.append(ID3v2::EventTimingCodesFrame::SynchedEvent(62300, ID3v2::EventTimingCodesFrame::IntroStart)); + sel.append(ID3v2::EventTimingCodesFrame::SynchedEvent(3600000, ID3v2::EventTimingCodesFrame::AudioFileEnds)); + f.setSynchedEvents(sel); + CPPUNIT_ASSERT_EQUAL( + ByteVector("ETCO" // Frame ID + "\x00\x00\x00\x0b" // Frame size + "\x00\x00" // Frame flags + "\x02" // Time stamp format + "\x02" // 1st event + "\x00\x00\xf3\x5c" // 1st time stamp + "\xfe" // 2nd event + "\x00\x36\xee\x80", 21), // 2nd time stamp + f.render()); + } + void testItunes24FrameSize() { MPEG::File f(TEST_FILE_PATH_C("005411.id3"), false); From a93ee10134bdfae5545e01fe98ceb14a48b158bc Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 5 Apr 2014 19:44:47 +0900 Subject: [PATCH 5/9] Changed cmake_minimum_required from 2.6.0 to 2.8.0. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 317ffa16..7a675024 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ project(taglib) -cmake_minimum_required(VERSION 2.6.0 FATAL_ERROR) +cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR) option(ENABLE_STATIC "Make static version of libtag" OFF) if(ENABLE_STATIC) From 3f557be6080028f0766e7f739dde12087b8e5b2c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 5 Apr 2014 20:51:40 +0900 Subject: [PATCH 6/9] Suppressed the warning "Policy CMP0022 is not set" on CMake 2.8.12. --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a675024..a27b2f6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ project(taglib) cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR) +if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12) + cmake_policy(SET CMP0022 OLD) +endif() + option(ENABLE_STATIC "Make static version of libtag" OFF) if(ENABLE_STATIC) add_definitions(-DTAGLIB_STATIC) From 95a59c416b45b7e1d111d976307cda4740ca78f8 Mon Sep 17 00:00:00 2001 From: David King Date: Thu, 10 Apr 2014 17:33:29 +0100 Subject: [PATCH 7/9] Add ALBUMARTIST property mapping for MP4 https://code.google.com/p/mp4v2/wiki/iTunesMetadata --- taglib/mp4/mp4tag.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index bbe3301d..75a16cd4 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -765,6 +765,7 @@ static const char *keyTranslation[][2] = { { "\251day", "DATE" }, { "\251wrt", "COMPOSER" }, { "\251grp", "GROUPING" }, + { "aART", "ALBUMARTIST" }, { "trkn", "TRACKNUMBER" }, { "disk", "DISCNUMBER" }, { "cpil", "COMPILATION" }, From c65753c766391aa1579f6a773832b15d9043e87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Thu, 10 Apr 2014 20:03:31 +0200 Subject: [PATCH 8/9] Add ALBUMARTIST property mapping for WMA --- taglib/asf/asftag.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index c07abe94..9317bc6a 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -195,6 +195,7 @@ bool ASF::Tag::isEmpty() const static const char *keyTranslation[][2] = { { "WM/AlbumTitle", "ALBUM" }, + { "WM/AlbumArtist", "ALBUMARTIST" }, { "WM/Composer", "COMPOSER" }, { "WM/Writer", "WRITER" }, { "WM/Conductor", "CONDUCTOR" }, From fabc411cb9e9ab95d67f63e157807dd91d94ceac Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 11 Apr 2014 17:16:55 +0900 Subject: [PATCH 9/9] Removed a wrong note from a comment in tstring.h. --- taglib/toolkit/tstring.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index 76e4c32f..6d3257eb 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -173,9 +173,6 @@ namespace TagLib { /*! * Makes a deep copy of the data in \a v. - * - * \note This should only be used with the 8-bit codecs Latin1 and UTF8, when - * used with other codecs it will simply print a warning and exit. */ String(const ByteVector &v, Type t = Latin1);