]> granicus.if.org Git - taglib/commitdiff
Add support for ID3v2 SYLT frames (synchronized lyrics).
authorUrs Fleisch <ufleisch@users.sourceforge.net>
Sun, 30 Mar 2014 07:26:03 +0000 (09:26 +0200)
committerUrs Fleisch <ufleisch@users.sourceforge.net>
Sun, 30 Mar 2014 07:26:03 +0000 (09:26 +0200)
taglib/CMakeLists.txt
taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp [new file with mode: 0644]
taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h [new file with mode: 0644]
taglib/mpeg/id3v2/id3v2framefactory.cpp
tests/test_id3v2.cpp

index 23fa60df6c56458955f4d4f9ef4d199904da376d..e61bba0156cb283e386b22729600561ae59d2791 100644 (file)
@@ -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 (file)
index 0000000..878af9b
--- /dev/null
@@ -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 <tbytevectorlist.h>
+#include <id3v2tag.h>
+#include <tdebug.h>
+#include <tpropertymap.h>
+
+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 (file)
index 0000000..6e81b51
--- /dev/null
@@ -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<SynchedText> 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
+       * <a href="http://en.wikipedia.org/wiki/ISO_639">ISO-639-2</a>.
+       *
+       * \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
+       * <a href="http://en.wikipedia.org/wiki/ISO_639">ISO-639-2</a> 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
index 3371ca7ddb9797334ecd45b79ba911ab1405011b..74d4f382192a5a8d833934fae93a348a42af5b09 100644 (file)
@@ -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")
index 9f5ffe01ddeacb0969685f724b479314b66fd401..da74b96040ff82c5eabce9fbf0e9e59aa221f1f8 100644 (file)
@@ -14,6 +14,7 @@
 #include <textidentificationframe.h>
 #include <attachedpictureframe.h>
 #include <unsynchronizedlyricsframe.h>
+#include <synchronizedlyricsframe.h>
 #include <generalencapsulatedobjectframe.h>
 #include <relativevolumeframe.h>
 #include <popularimeterframe.h>
@@ -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);