From eba99c3a701688a6e7b5bb2a22d29ceff24f8f34 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 30 Mar 2014 09:26:03 +0200 Subject: [PATCH 1/2] 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 2/2] 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);