[tags] Add new TagLoaderTagLib & associated VFSStream
authorChris Roberts <croberts@bongle.co.uk>
Thu, 5 Jul 2012 15:54:59 +0000 (23:54 +0800)
committerChris Roberts <croberts@bongle.co.uk>
Mon, 10 Sep 2012 11:26:19 +0000 (19:26 +0800)
XBMC.xcodeproj/project.pbxproj
project/VS2010Express/XBMC.vcxproj
project/VS2010Express/XBMC.vcxproj.filters
xbmc/music/tags/Makefile
xbmc/music/tags/TagLibVFSStream.cpp [new file with mode: 0644]
xbmc/music/tags/TagLibVFSStream.h [new file with mode: 0644]
xbmc/music/tags/TagLoaderTagLib.cpp [new file with mode: 0644]
xbmc/music/tags/TagLoaderTagLib.h [new file with mode: 0644]

index f2bc349..c732db9 100644 (file)
                889B4D8E0E0EF86C00FAD25E /* RSSDirectory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 889B4D8C0E0EF86C00FAD25E /* RSSDirectory.cpp */; };
                88ACB01B0DCF40800083CFDF /* ASAPFileDirectory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 88ACB0190DCF40800083CFDF /* ASAPFileDirectory.cpp */; };
                88ACB01F0DCF409E0083CFDF /* ASAPCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 88ACB01C0DCF409E0083CFDF /* ASAPCodec.cpp */; };
+               AE84CB5A15A5B8A600A3810E /* TagLibVFSStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE84CB5915A5B8A600A3810E /* TagLibVFSStream.cpp */; };
+               AEC0083115ACAC6E0099888C /* TagLoaderTagLib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEC0083015ACAC6E0099888C /* TagLoaderTagLib.cpp */; };
                C807114D135DB5CC002F601B /* InputOperations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C807114B135DB5CC002F601B /* InputOperations.cpp */; };
                C84828C0156CFCD8005A996F /* PVRClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C8482874156CFCD8005A996F /* PVRClient.cpp */; };
                C84828C1156CFCD8005A996F /* PVRClients.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C8482876156CFCD8005A996F /* PVRClients.cpp */; };
                88ACB01E0DCF409E0083CFDF /* DllASAP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DllASAP.h; sourceTree = "<group>"; };
                88ECB6580DE013C4003396A7 /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = /System/Library/Frameworks/DiskArbitration.framework; sourceTree = "<absolute>"; };
                8DD76F7E0486A8DE00D96B5E /* XBMC */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = XBMC; sourceTree = BUILT_PRODUCTS_DIR; };
+               AE84CB5915A5B8A600A3810E /* TagLibVFSStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TagLibVFSStream.cpp; sourceTree = "<group>"; };
+               AE84CB5C15A5B8BA00A3810E /* TagLibVFSStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagLibVFSStream.h; sourceTree = "<group>"; };
+               AEC0083015ACAC6E0099888C /* TagLoaderTagLib.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TagLoaderTagLib.cpp; sourceTree = "<group>"; };
+               AEC0083315ACAC7C0099888C /* TagLoaderTagLib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagLoaderTagLib.h; sourceTree = "<group>"; };
                C807114B135DB5CC002F601B /* InputOperations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputOperations.cpp; sourceTree = "<group>"; };
                C807114C135DB5CC002F601B /* InputOperations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputOperations.h; sourceTree = "<group>"; };
                C8482874156CFCD8005A996F /* PVRClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PVRClient.cpp; sourceTree = "<group>"; };
                                18B7C94712942AD7009E7A26 /* Tag.h */,
                                18B7C88A129423A7009E7A26 /* VorbisTag.cpp */,
                                18B7C88B129423A7009E7A26 /* VorbisTag.h */,
+                               AE84CB5915A5B8A600A3810E /* TagLibVFSStream.cpp */,
+                               AE84CB5C15A5B8BA00A3810E /* TagLibVFSStream.h */,
+                               AEC0083015ACAC6E0099888C /* TagLoaderTagLib.cpp */,
+                               AEC0083315ACAC7C0099888C /* TagLoaderTagLib.h */,
                        );
                        path = tags;
                        sourceTree = "<group>";
                                DF2345E215FA639500A934F6 /* UPnPInternal.cpp in Sources */,
                                DF2345E315FA639500A934F6 /* UPnPRenderer.cpp in Sources */,
                                DF2345E415FA639500A934F6 /* UPnPServer.cpp in Sources */,
+                               AE84CB5A15A5B8A600A3810E /* TagLibVFSStream.cpp in Sources */,
+                               AEC0083115ACAC6E0099888C /* TagLoaderTagLib.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 560d9a1..09df83f 100644 (file)
     <ClCompile Include="..\..\xbmc\music\tags\MusicInfoTagLoaderWavPack.cpp" />
     <ClCompile Include="..\..\xbmc\music\tags\MusicInfoTagLoaderWMA.cpp" />
     <ClCompile Include="..\..\xbmc\music\tags\MusicInfoTagLoaderYM.cpp" />
+    <ClCompile Include="..\..\xbmc\music\tags\TagLibVFSStream.cpp" />
+    <ClCompile Include="..\..\xbmc\music\tags\TagLoaderTagLib.cpp" />
     <ClCompile Include="..\..\xbmc\music\tags\OggTag.cpp" />
     <ClCompile Include="..\..\xbmc\music\tags\VorbisTag.cpp" />
     <ClCompile Include="..\..\xbmc\music\windows\GUIWindowMusicBase.cpp" />
     <ClInclude Include="..\..\xbmc\interfaces\python\XBPython.h" />
     <ClInclude Include="..\..\xbmc\interfaces\python\XBPyThread.h" />
     <ClInclude Include="..\..\xbmc\music\MusicDbUrl.h" />
+    <ClInclude Include="..\..\xbmc\music\tags\MusicInfoTagLoaderWav.h" />
+    <ClInclude Include="..\..\xbmc\music\tags\TagLibVFSStream.h" />
+    <ClInclude Include="..\..\xbmc\music\tags\TagLoaderTagLib.h" />
     <ClInclude Include="..\..\xbmc\network\httprequesthandler\HTTPImageHandler.h" />
     <ClInclude Include="..\..\xbmc\network\AirTunesServer.h" />
     <ClInclude Include="..\..\xbmc\network\DllLibShairplay.h" />
index aca6fe6..0b23411 100644 (file)
     <ClCompile Include="..\..\xbmc\interfaces\python\generated\AddonModuleXbmcvfs.cpp">
       <Filter>interfaces\python\generated</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\xbmc\music\tags\TagLibVFSStream.cpp">
+      <Filter>music\tags</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\xbmc\music\tags\TagLoaderTagLib.cpp">
+      <Filter>music\tags</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\xbmc\music\tags\MusicInfoTagLoaderWav.cpp">
+      <Filter>music\tags</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\xbmc\win32\pch.h">
     <ClInclude Include="..\..\xbmc\interfaces\python\XBPyThread.h">
       <Filter>interfaces\python</Filter>
     </ClInclude>
+   <ClInclude Include="..\..\xbmc\music\tags\TagLibVFSStream.h">
+      <Filter>music\tags</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\xbmc\music\tags\TagLoaderTagLib.h">
+      <Filter>music\tags</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\xbmc\music\tags\MusicInfoTagLoaderWav.h">
+      <Filter>music\tags</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\xbmc\win32\XBMC_PC.rc">
       <Filter>interfaces\python\generated</Filter>
     </None>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index d1e84aa..57754ab 100644 (file)
@@ -1,4 +1,5 @@
-SRCS=APEv2Tag.cpp \
+SRCS=\
+     APEv2Tag.cpp \
      FlacTag.cpp \
      Id3Tag.cpp \
      MusicInfoTag.cpp \
@@ -24,6 +25,8 @@ SRCS=APEv2Tag.cpp \
      MusicInfoTagLoaderYM.cpp \
      OggTag.cpp \
      VorbisTag.cpp \
+     TagLoaderTagLib.cpp \
+     TagLibVFSStream.cpp \
 
 LIB=musictags.a
 
diff --git a/xbmc/music/tags/TagLibVFSStream.cpp b/xbmc/music/tags/TagLibVFSStream.cpp
new file mode 100644 (file)
index 0000000..f910956
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ *      Copyright (C) 2005-2008 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+#include "limits.h"
+#include "TagLibVFSStream.h"
+#include "filesystem/File.h"
+#include "utils/StdString.h"
+#include "utils/log.h"
+#include <taglib/tiostream.h>
+
+using namespace XFILE;
+using namespace TagLib;
+using namespace MUSIC_INFO;
+using namespace std;
+
+#ifdef TARGET_WINDOWS
+#pragma comment(lib, "tag.lib")
+#endif
+
+/*!
+ * Construct a File object and opens the \a file.  \a file should be a
+ * be an XBMC Vfile.
+ */
+TagLibVFSStream::TagLibVFSStream(const string& strFileName, bool readOnly)
+{
+  m_bIsOpen = true;
+  if (readOnly)
+  {
+    if (!m_file.Open(strFileName))
+      m_bIsOpen = false;
+  }
+  else
+  {
+    if (!m_file.OpenForWrite(strFileName))
+      m_bIsOpen = false;
+  }
+  m_strFileName = strFileName;
+}
+
+/*!
+ * Destroys this ByteVectorStream instance.
+ */
+TagLibVFSStream::~TagLibVFSStream()
+{
+  m_file.Close();
+}
+
+/*!
+ * Returns the file name in the local file system encoding.
+ */
+FileName TagLibVFSStream::name() const
+{
+  return m_strFileName.c_str();
+}
+
+/*!
+ * Reads a block of size \a length at the current get pointer.
+ */
+ByteVector TagLibVFSStream::readBlock(ulong length)
+{
+  ByteVector byteVector(static_cast<TagLib::uint>(length));
+  byteVector.resize(m_file.Read(byteVector.data(), length));
+  return byteVector;
+}
+
+/*!
+ * Attempts to write the block \a data at the current get pointer.  If the
+ * file is currently only opened read only -- i.e. readOnly() returns true --
+ * this attempts to reopen the file in read/write mode.
+ *
+ * \note This should be used instead of using the streaming output operator
+ * for a ByteVector.  And even this function is significantly slower than
+ * doing output with a char[].
+ */
+void TagLibVFSStream::writeBlock(const ByteVector &data)
+{
+  m_file.Write(data.data(), data.size());
+}
+
+/*!
+ * Insert \a data at position \a start in the file overwriting \a replace
+ * bytes of the original content.
+ *
+ * \note This method is slow since it requires rewriting all of the file
+ * after the insertion point.
+ */
+void TagLibVFSStream::insert(const ByteVector &data, ulong start, ulong replace)
+{
+  if (data.size() == replace)
+  {
+    seek(start);
+    writeBlock(data);
+    return;
+  }
+  else if (data.size() < replace)
+  {
+    seek(start);
+    writeBlock(data);
+    removeBlock(start + data.size(), replace - data.size());
+  }
+
+  // Woohoo!  Faster (about 20%) than id3lib at last.  I had to get hardcore
+  // and avoid TagLib's high level API for rendering just copying parts of
+  // the file that don't contain tag data.
+  //
+  // Now I'll explain the steps in this ugliness:
+
+  // First, make sure that we're working with a buffer that is longer than
+  // the *differnce* in the tag sizes.  We want to avoid overwriting parts
+  // that aren't yet in memory, so this is necessary.
+  ulong bufferLength = bufferSize();
+
+  while (data.size() - replace > bufferLength)
+    bufferLength += bufferSize();
+
+  // Set where to start the reading and writing.
+  long readPosition = start + replace;
+  long writePosition = start;
+  ByteVector buffer;
+  ByteVector aboutToOverwrite(static_cast<TagLib::uint>(bufferLength));
+
+  // This is basically a special case of the loop below.  Here we're just
+  // doing the same steps as below, but since we aren't using the same buffer
+  // size -- instead we're using the tag size -- this has to be handled as a
+  // special case.  We're also using File::writeBlock() just for the tag.
+  // That's a bit slower than using char *'s so, we're only doing it here.
+  seek(readPosition);
+  int bytesRead = m_file.Read(aboutToOverwrite.data(), bufferLength);
+  readPosition += bufferLength;
+
+  seek(writePosition);
+  writeBlock(data);
+  writePosition += data.size();
+
+  buffer = aboutToOverwrite;
+  buffer.resize(bytesRead);
+
+  // Ok, here's the main loop.  We want to loop until the read fails, which
+  // means that we hit the end of the file.
+  while (!buffer.isEmpty())
+  {
+    // Seek to the current read position and read the data that we're about
+    // to overwrite.  Appropriately increment the readPosition.
+    seek(readPosition);
+    bytesRead = m_file.Read(aboutToOverwrite.data(), bufferLength);
+    aboutToOverwrite.resize(bytesRead);
+    readPosition += bufferLength;
+
+    // Check to see if we just read the last block.  We need to call clear()
+    // if we did so that the last write succeeds.
+    if (ulong(bytesRead) < bufferLength)
+      clear();
+
+    // Seek to the write position and write our buffer.  Increment the
+    // writePosition.
+    seek(writePosition);
+    m_file.Write(buffer.data(), buffer.size());
+    writePosition += buffer.size();
+
+    buffer = aboutToOverwrite;
+    bufferLength = bytesRead;
+  }
+}
+
+/*!
+ * Removes a block of the file starting a \a start and continuing for
+ * \a length bytes.
+ *
+ * \note This method is slow since it involves rewriting all of the file
+ * after the removed portion.
+ */
+void TagLibVFSStream::removeBlock(ulong start, ulong length)
+{
+  ulong bufferLength = bufferSize();
+
+  long readPosition = start + length;
+  long writePosition = start;
+
+  ByteVector buffer(static_cast<TagLib::uint>(bufferLength));
+
+  ulong bytesRead = 1;
+
+  while(bytesRead != 0)
+  {
+    seek(readPosition);
+    bytesRead = m_file.Read(buffer.data(), bufferLength);
+    readPosition += bytesRead;
+
+    // Check to see if we just read the last block.  We need to call clear()
+    // if we did so that the last write succeeds.
+    if(bytesRead < bufferLength)
+      clear();
+
+    seek(writePosition);
+    m_file.Write(buffer.data(), bytesRead);
+    writePosition += bytesRead;
+  }
+  truncate(writePosition);
+}
+
+/*!
+ * Returns true if the file is read only (or if the file can not be opened).
+ */
+bool TagLibVFSStream::readOnly() const
+{
+  return m_bIsReadOnly;
+}
+
+/*!
+ * Since the file can currently only be opened as an argument to the
+ * constructor (sort-of by design), this returns if that open succeeded.
+ */
+bool TagLibVFSStream::isOpen() const
+{
+  return m_bIsOpen;
+}
+
+/*!
+ * Move the I/O pointer to \a offset in the file from position \a p.  This
+ * defaults to seeking from the beginning of the file.
+ *
+ * \see Position
+ */
+void TagLibVFSStream::seek(long offset, Position p)
+{
+  switch(p)
+  {
+    case Beginning:
+      m_file.Seek(offset, SEEK_SET);
+      break;
+    case Current:
+      m_file.Seek(offset, SEEK_CUR);
+      break;
+    case End:
+      m_file.Seek(offset, SEEK_END);
+      break;
+  }
+}
+
+/*!
+ * Reset the end-of-file and error flags on the file.
+ */
+void TagLibVFSStream::clear()
+{
+}
+
+/*!
+ * Returns the current offset within the file.
+ */
+long TagLibVFSStream::tell() const
+{
+  int64_t pos = m_file.GetPosition();
+  if(pos > LONG_MAX)
+    return -1;
+  else
+    return (long)pos;
+}
+
+/*!
+ * Returns the length of the file.
+ */
+long TagLibVFSStream::length()
+{
+  return m_file.GetLength();
+}
+
+/*!
+ * Truncates the file to a \a length.
+ */
+void TagLibVFSStream::truncate(long length)
+{
+  m_file.Truncate(length);
+}
diff --git a/xbmc/music/tags/TagLibVFSStream.h b/xbmc/music/tags/TagLibVFSStream.h
new file mode 100644 (file)
index 0000000..a2b23c9
--- /dev/null
@@ -0,0 +1,137 @@
+#pragma once
+/*
+ *      Copyright (C) 2005-2008 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+#include "filesystem/File.h"
+#include "utils/StdString.h"
+#include <taglib/tiostream.h>
+
+using namespace XFILE;
+using namespace TagLib;
+
+namespace MUSIC_INFO
+{
+  class TagLibVFSStream : public IOStream
+  {
+  public:
+    /*!
+     * Construct a File object and opens the \a file.  \a file should be a
+     * be an XBMC Vfile.
+     */
+    TagLibVFSStream(const std::string& strFileName, bool readOnly);
+
+    /*!
+     * Destroys this ByteVectorStream instance.
+     */
+    virtual ~TagLibVFSStream();
+    
+    /*!
+     * Returns the file name in the local file system encoding.
+     */
+    FileName name() const;
+
+    /*!
+     * Reads a block of size \a length at the current get pointer.
+     */
+    ByteVector readBlock(ulong length);
+
+    /*!
+     * Attempts to write the block \a data at the current get pointer.  If the
+     * file is currently only opened read only -- i.e. readOnly() returns true --
+     * this attempts to reopen the file in read/write mode.
+     *
+     * \note This should be used instead of using the streaming output operator
+     * for a ByteVector.  And even this function is significantly slower than
+     * doing output with a char[].
+     */
+    void writeBlock(const ByteVector &data);
+
+    /*!
+     * Insert \a data at position \a start in the file overwriting \a replace
+     * bytes of the original content.
+     *
+     * \note This method is slow since it requires rewriting all of the file
+     * after the insertion point.
+     */
+    void insert(const ByteVector &data, ulong start = 0, ulong replace = 0);
+
+    /*!
+     * Removes a block of the file starting a \a start and continuing for
+     * \a length bytes.
+     *
+     * \note This method is slow since it involves rewriting all of the file
+     * after the removed portion.
+     */
+    void removeBlock(ulong start = 0, ulong length = 0);
+
+    /*!
+     * Returns true if the file is read only (or if the file can not be opened).
+     */
+    bool readOnly() const;
+
+    /*!
+     * Since the file can currently only be opened as an argument to the
+     * constructor (sort-of by design), this returns if that open succeeded.
+     */
+    bool isOpen() const;
+
+    /*!
+     * Move the I/O pointer to \a offset in the file from position \a p.  This
+     * defaults to seeking from the beginning of the file.
+     *
+     * \see Position
+     */
+    void seek(long offset, Position p = Beginning);
+
+    /*!
+     * Reset the end-of-file and error flags on the file.
+     */
+    void clear();
+
+    /*!
+     * Returns the current offset within the file.
+     */
+    long tell() const;
+
+    /*!
+     * Returns the length of the file.
+     */
+    long length();
+
+    /*!
+     * Truncates the file to a \a length.
+     */
+    void truncate(long length);
+
+  protected:
+    /*!
+     * Returns the buffer size that is used for internal buffering.
+     */
+    static TagLib::uint bufferSize() { return 1024; };
+
+  private:
+    std::string m_strFileName;
+    CFile       m_file;
+    bool        m_bIsReadOnly;
+    bool        m_bIsOpen;
+    int         m_bufferSize;
+  };
+}
+
diff --git a/xbmc/music/tags/TagLoaderTagLib.cpp b/xbmc/music/tags/TagLoaderTagLib.cpp
new file mode 100644 (file)
index 0000000..0d591b7
--- /dev/null
@@ -0,0 +1,538 @@
+/*
+ *      Copyright (C) 2005-2012 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include "TagLoaderTagLib.h"
+
+#include <vector>
+
+#include <taglib/id3v1tag.h>
+#include <taglib/id3v2tag.h>
+#include <taglib/apetag.h>
+#include <taglib/xiphcomment.h>
+
+#include <taglib/textidentificationframe.h>
+#include <taglib/uniquefileidentifierframe.h>
+#include <taglib/popularimeterframe.h>
+#include <taglib/commentsframe.h>
+#include <taglib/unsynchronizedlyricsframe.h>
+#include <taglib/attachedpictureframe.h>
+
+#undef byte
+#include <taglib/tstring.h>
+#include <taglib/tpropertymap.h>
+
+#include "TagLibVFSStream.h"
+#include "MusicInfoTag.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "utils/StringUtils.h"
+#include "settings/AdvancedSettings.h"
+
+using namespace std;
+using namespace TagLib;
+using namespace MUSIC_INFO;
+
+CTagLoaderTagLib::CTagLoaderTagLib(const std::string& strFileName) : m_tagLibVFSStream(strFileName, true)
+{
+}
+
+CTagLoaderTagLib::~CTagLoaderTagLib()
+{
+  
+}
+
+static const vector<string> StringListToVectorString(const StringList& stringList)
+{
+  vector<string> values;
+  for (StringList::ConstIterator it = stringList.begin(); it != stringList.end(); ++it)
+    values.push_back(it->toCString(true));
+  return values;
+}
+
+bool CTagLoaderTagLib::Load(const string& strFileName, CMusicInfoTag& tag, EmbeddedArt *art /* = NULL */)
+{  
+  CStdString strExtension;
+  URIUtils::GetExtension(strFileName, strExtension);
+  strExtension.ToLower();
+  strExtension.TrimLeft('.');
+
+  if (strExtension.IsEmpty())
+    return false;
+
+  TagLib::File*              file = NULL;
+  TagLib::APE::File*         apeFile = NULL;
+  TagLib::ASF::File*         asfFile = NULL;
+  TagLib::FLAC::File*        flacFile = NULL;
+  TagLib::IT::File*          itFile = NULL;
+  TagLib::Mod::File*         modFile = NULL;
+  TagLib::MP4::File*         mp4File = NULL;
+  TagLib::MPC::File*         mpcFile = NULL;
+  TagLib::MPEG::File*        mpegFile = NULL;
+  TagLib::Ogg::Vorbis::File* oggVorbisFile = NULL;
+  TagLib::Ogg::FLAC::File*   oggFlacFile = NULL;
+  TagLib::RIFF::File*        riffFile = NULL;
+  TagLib::S3M::File*         s3mFile = NULL;
+  TagLib::TrueAudio::File*   ttaFile = NULL;
+  TagLib::WavPack::File*     wvFile = NULL;
+  TagLib::XM::File*          xmFile = NULL;
+
+  if (strExtension == "ape")
+    file = apeFile = new APE::File(&m_tagLibVFSStream);
+  else if (strExtension == "asf" || strExtension == "wmv" || strExtension == "wma")
+    file = asfFile = new ASF::File(&m_tagLibVFSStream);
+  else if (strExtension == "flac")
+    file = flacFile = new FLAC::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+  else if (strExtension == "it")
+    file = itFile = new IT::File(&m_tagLibVFSStream);
+  else if (strExtension == "mod" || strExtension == "module" || strExtension == "nst" || strExtension == "wow")
+    file = modFile = new Mod::File(&m_tagLibVFSStream);
+  else if (strExtension == "mp4" || strExtension == "m4a" || 
+           strExtension == "m4r" || strExtension == "m4b" || 
+           strExtension == "m4p" || strExtension == "3g2")
+    file = mp4File = new MP4::File(&m_tagLibVFSStream);
+  else if (strExtension == "mpc")
+    file = mpcFile = new MPC::File(&m_tagLibVFSStream);
+  else if (strExtension == "mp3" || strExtension == "aac")
+    file = mpegFile = new MPEG::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+  else if (strExtension == "s3m")
+    file = s3mFile = new S3M::File(&m_tagLibVFSStream);
+  else if (strExtension == "tta")
+    file = ttaFile = new TrueAudio::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+  else if (strExtension == "wv")
+    file = wvFile = new WavPack::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+  else if (strExtension == "xm")
+    file = xmFile = new XM::File(&m_tagLibVFSStream);
+  else if (strExtension == "ogg")
+    file = oggVorbisFile = new Ogg::Vorbis::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+  else if (strExtension == "oga") // Leave this madness until last - oga container can have Vorbis or FLAC
+  {
+    file = oggFlacFile = new Ogg::FLAC::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+    if (!file || !file->isValid())
+    {
+      delete file;
+      file = oggVorbisFile = new Ogg::Vorbis::File(&m_tagLibVFSStream, ID3v2::FrameFactory::instance());
+    }
+  }
+
+  if (!file || !file->isOpen())
+  {
+    if (file)
+      delete file;
+    CLog::Log(LOGDEBUG, "file could not be opened for tag reading");
+    return false;
+  }
+
+  APE::Tag *ape = NULL;
+  ASF::Tag *asf = NULL;
+  MP4::Tag *mp4 = NULL;
+  ID3v2::Tag *id3v2 = NULL;
+  Ogg::XiphComment *xiph = NULL;
+  Tag *generic = NULL;
+
+  if (apeFile)
+    ape = apeFile->APETag(false);
+  else if (asfFile)
+    asf = asfFile->tag();
+  else if (flacFile)
+  {
+    xiph = flacFile->xiphComment(false);
+    id3v2 = flacFile->ID3v2Tag(false);
+  }
+  else if (mp4File)
+    mp4 = mp4File->tag();
+  else if (mpegFile)
+  {
+    id3v2 = mpegFile->ID3v2Tag(false);
+    ape = mpegFile->APETag(false);
+  }
+  else if (oggFlacFile)
+    xiph = dynamic_cast<Ogg::XiphComment *>(oggFlacFile->tag());
+  else if (oggVorbisFile)
+    xiph = dynamic_cast<Ogg::XiphComment *>(oggVorbisFile->tag());
+  else if (ttaFile)
+    id3v2 = ttaFile->ID3v2Tag(false);
+  else if (wvFile)
+    ape = wvFile->APETag(false);
+  else    // This is a catch all to get generic information for other files types (s3m, xm, it, mod, etc)
+    generic = file->tag();
+
+  if (file->audioProperties())
+    tag.SetDuration(file->audioProperties()->length());
+
+  if (ape && !g_advancedSettings.m_prioritiseAPEv2tags)
+    ParseAPETag(ape, art, tag);
+  else if (asf)
+    ParseASF(asf, art, tag);
+  else if (id3v2)
+    ParseID3v2Tag(id3v2, art, tag);
+  else if (generic)
+    ParseGenericTag(generic, art, tag);
+  else if (mp4)
+    ParseMP4Tag(mp4, art, tag);
+  else if (xiph)
+    ParseXiphComment(xiph, art, tag);
+
+  // Add APE tags over the top of ID3 tags if we want to prioritize them
+  if (ape && g_advancedSettings.m_prioritiseAPEv2tags)
+    ParseAPETag(ape, art, tag);
+
+  if (!tag.GetTitle().IsEmpty() || !tag.GetArtist().empty() || !tag.GetAlbum().IsEmpty())
+    tag.SetLoaded();
+  tag.SetURL(strFileName);
+
+  delete file;
+
+  return true;
+}
+
+bool CTagLoaderTagLib::ParseASF(ASF::Tag *asf, EmbeddedArt *art, CMusicInfoTag& tag)
+{
+  if (!asf)
+    return false;
+
+  const ASF::AttributeListMap& attributeListMap = asf->attributeListMap();
+  for (ASF::AttributeListMap::ConstIterator it = attributeListMap.begin(); it != attributeListMap.end(); ++it)
+  {
+    if (it->first == "Author")                           tag.SetArtist(GetASFStringList(it->second));
+    else if (it->first == "WM/AlbumArtist")              tag.SetAlbumArtist(GetASFStringList(it->second));
+    else if (it->first == "WM/AlbumTitle")               tag.SetAlbum(it->second.front().toString().toCString());
+    else if (it->first == "WM/TrackNumber")              tag.SetTrackNumber(it->second.front().toUInt());
+    else if (it->first == "WM/PartOfSet")                tag.SetPartOfSet(it->second.front().toUInt());
+    else if (it->first == "WM/Genre")                    tag.SetGenre(GetASFStringList(it->second));
+    else if (it->first == "WM/AlbumArtistSortOrder")     {} // Known unsupported, supress warnings
+    else if (it->first == "WM/ArtistSortOrder")          {} // Known unsupported, supress warnings
+    else if (it->first == "WM/Script")                   {} // Known unsupported, supress warnings
+    else if (it->first == "MusicBrainz/Artist Id")       tag.SetMusicBrainzArtistID(it->second.front().toString().toCString());
+    else if (it->first == "MusicBrainz/Album Id")        tag.SetMusicBrainzAlbumID(it->second.front().toString().toCString());
+    else if (it->first == "MusicBrainz/Album Artist Id") tag.SetMusicBrainzAlbumArtistID(it->second.front().toString().toCString());
+    else if (it->first == "MusicBrainz/Track Id")        tag.SetMusicBrainzTrackID(it->second.front().toString().toCString());
+    else if (it->first == "MusicBrainz/Album Status")    {}
+    else if (it->first == "MusicBrainz/Album Type")      {}
+    else if (it->first == "MusicIP/PUID")                {}
+    else
+      CLog::Log(LOGDEBUG, "unrecognized ASF tag name: %s", it->first.toCString());
+  }
+  tag.SetLoaded(true);
+  return true;
+}
+
+bool CTagLoaderTagLib::ParseID3v2Tag(ID3v2::Tag *id3v2, EmbeddedArt *art, CMusicInfoTag& tag)
+{
+  // Notes:
+  // Ratings:
+  // FROM: http://thiagoarrais.com/repos/banshee/src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs
+  // The following schemes are used by the other POPM-compatible players:
+  // WMP/Vista: "Windows Media Player 9 Series" ratings:
+  //   1 = 1, 2 = 64, 3=128, 4=196 (not 192), 5=255
+  // MediaMonkey: "no@email" ratings:
+  //   0.5=26, 1=51, 1.5=76, 2=102, 2.5=128,
+  //   3=153, 3.5=178, 4=204, 4.5=230, 5=255
+  // Quod Libet: "quodlibet@lists.sacredchao.net" ratings
+  //   (but that email can be changed):
+  //   arbitrary scale from 0-255
+
+  //  tag.SetURL(strFile);
+  if (!id3v2) return false;
+
+  ID3v2::AttachedPictureFrame *pictures[3] = {};
+  const ID3v2::FrameListMap& frameListMap = id3v2->frameListMap();
+  for (ID3v2::FrameListMap::ConstIterator it = frameListMap.begin(); it != frameListMap.end(); ++it)
+  {
+    if      (it->first == "TPE1")   tag.SetArtist(GetID3v2StringList(it->second));
+    else if (it->first == "TALB")   tag.SetAlbum(it->second.front()->toString().toCString(true));
+    else if (it->first == "TPE2")   tag.SetAlbumArtist(GetID3v2StringList(it->second));
+    else if (it->first == "TIT2")   tag.SetTitle(it->second.front()->toString().toCString(true));
+    else if (it->first == "TCON")   tag.SetGenre(it->second.front()->toString().toCString(true));
+    else if (it->first == "TRCK")   tag.SetTrackNumber(strtol(it->second.front()->toString().toCString(true), NULL, 10));
+    else if (it->first == "TPOS")   tag.SetPartOfSet(strtol(it->second.front()->toString().toCString(true), NULL, 10));
+    else if (it->first == "TYER")   tag.SetYear(strtol(it->second.front()->toString().toCString(true), NULL, 10));
+    else if (it->first == "TCMP")   tag.SetCompilation(strtol(it->second.front()->toString().toCString(true), NULL, 10));
+    else if (it->first == "TENC")   {} // EncodedBy
+    else if (it->first == "USLT")
+      // Loop through any lyrics frames. Could there be multiple frames, how to choose?
+      for (ID3v2::FrameList::ConstIterator lt = it->second.begin(); lt != it->second.end(); ++lt)
+      {
+        ID3v2::UnsynchronizedLyricsFrame *lyricsFrame = dynamic_cast<ID3v2::UnsynchronizedLyricsFrame *> (*lt);
+        if (lyricsFrame)           
+          tag.SetLyrics(lyricsFrame->text().toCString());
+      }
+    else if (it->first == "COMM")
+      // Loop through and look for the main (no description) comment
+      for (ID3v2::FrameList::ConstIterator ct = it->second.begin(); ct != it->second.end(); ++ct)
+      {
+        ID3v2::CommentsFrame *commentsFrame = dynamic_cast<ID3v2::CommentsFrame *> (*ct);
+        if (commentsFrame && commentsFrame->description().isEmpty())
+          tag.SetComment(commentsFrame->text().toCString());
+      }
+    else if (it->first == "TXXX")
+      // Loop through and process the UserTextIdentificationFrames
+      for (ID3v2::FrameList::ConstIterator ut = it->second.begin(); ut != it->second.end(); ++ut)
+      {
+        ID3v2::UserTextIdentificationFrame *frame = dynamic_cast<ID3v2::UserTextIdentificationFrame *> (*ut);
+        if (!frame) continue;
+        
+        // First field is the same as the description
+        StringList stringList = frame->fieldList(); 
+        stringList.erase(stringList.begin());
+        if      (frame->description() == "MusicBrainz Artist Id")      tag.SetMusicBrainzArtistID(stringList.front().toCString());
+        else if (frame->description() == "MusicBrainz Album Id")       tag.SetMusicBrainzAlbumID(stringList.front().toCString());
+        else if (frame->description() == "MusicBrainz Album Artist Id") tag.SetMusicBrainzAlbumArtistID(stringList.front().toCString());
+        else if (frame->description() == "replaygain_track_gain")    m_rg.SetTrackGain(atof(stringList.front().toCString()) * 100 + 0.5);
+        else if (frame->description() == "replaygain_album_gain")    m_rg.SetAlbumGain(atof(stringList.front().toCString()) * 100 + 0.5);
+        else if (frame->description() == "replaygain_track_peak")    m_rg.SetTrackPeak(atof(stringList.front().toCString()));
+        else if (frame->description() == "replaygain_album_peak")    m_rg.SetAlbumPeak(atof(stringList.front().toCString()));
+        else
+          CLog::Log(LOGDEBUG, "unrecognized user text tag detected: TXXX:%s", frame->description().toCString());
+      }
+    else if (it->first == "UFID")
+      // Loop through any UFID frames and set them
+      for (ID3v2::FrameList::ConstIterator ut = it->second.begin(); ut != it->second.end(); ++ut)
+      {
+        ID3v2::UniqueFileIdentifierFrame *ufid = reinterpret_cast<ID3v2::UniqueFileIdentifierFrame*> (*ut);
+        if (ufid->owner() == "http://musicbrainz.org")
+        {
+          // MusicBrainz pads with a \0, but the spec requires binary, be cautious
+          char cUfid[64];
+          int max_size = std::min((int)ufid->identifier().size(), 63);
+          strncpy(cUfid, ufid->identifier().data(), max_size);
+          cUfid[max_size] = '\0';
+          tag.SetMusicBrainzTrackID(cUfid);
+        }
+      }
+    else if (it->first == "APIC")
+      // Loop through all pictures and store the frame pointers for the picture types we want
+      for (ID3v2::FrameList::ConstIterator pi = it->second.begin(); pi != it->second.end(); ++pi)
+      {
+        ID3v2::AttachedPictureFrame *pictureFrame = dynamic_cast<ID3v2::AttachedPictureFrame *> (*pi);
+        if (!pictureFrame) continue;
+        
+        if      (pictureFrame->type() == ID3v2::AttachedPictureFrame::FrontCover) pictures[0] = pictureFrame;
+        else if (pictureFrame->type() == ID3v2::AttachedPictureFrame::Other)      pictures[1] = pictureFrame;
+        else if (pi == it->second.begin())                                        pictures[2] = pictureFrame;
+      }
+    else if (it->first == "POPM")
+      // Loop through and process ratings
+      for (ID3v2::FrameList::ConstIterator ct = it->second.begin(); ct != it->second.end(); ++ct)
+      {
+        ID3v2::PopularimeterFrame *popFrame = dynamic_cast<ID3v2::PopularimeterFrame *> (*ct);
+        if (!popFrame) continue;
+        
+        // @xbmc.org ratings trump others (of course)
+        if      (popFrame->email() == "ratings@xbmc.org")
+          tag.SetRating(popFrame->rating() / 51 + '0');
+        else if (popFrame->email() == "Windows Media Player 9 Series" && tag.GetRating() == '0')  
+          tag.SetRating(popFrame->rating() / 51 + '0');
+        else if (popFrame->email() == "no@email" && tag.GetRating() == '0')                       
+          tag.SetRating(popFrame->rating() / 51 + '0');
+        else if (popFrame->email() == "quodlibet@lists.sacredchao.net" && tag.GetRating() == '0') 
+          tag.SetRating(popFrame->rating() / 51 + '0');
+        else
+          CLog::Log(LOGDEBUG, "unrecognized ratings schema detected: %s", popFrame->email().toCString());
+      }
+    else
+      CLog::Log(LOGDEBUG, "unrecognized ID3 frame detected: %c%c%c%c", it->first[0], it->first[1], it->first[2], it->first[3]);
+  } // for
+
+  // Process the extracted picture frames; 0 = CoverArt, 1 = Other, 2 = First Found picture
+  for (int i = 0; i < 3; ++i)
+    if (pictures[i])
+    {
+      string      mime =             pictures[i]->mimeType().toCString();
+      TagLib::uint size =            pictures[i]->picture().size();
+      uint8_t*    data  = (uint8_t*) pictures[i]->picture().data();
+      tag.SetCoverArtInfo(size, mime);
+      if (art)
+        art->set(data, size, mime);
+      
+      // Stop after we find the first picture for now.
+      break;
+    }
+  return true;
+}
+
+bool CTagLoaderTagLib::ParseAPETag(APE::Tag *ape, EmbeddedArt *art, CMusicInfoTag& tag)
+{
+  if (!ape)
+    return false;
+
+  const APE::ItemListMap itemListMap = ape->itemListMap();
+  for (APE::ItemListMap::ConstIterator it = itemListMap.begin(); it != itemListMap.end(); ++it)
+  {
+    if (it->first == "ARTIST")                         tag.SetArtist(StringListToVectorString(it->second.toStringList()));
+    else if (it->first == "ALBUM ARTIST")              tag.SetAlbumArtist(StringListToVectorString(it->second.toStringList()));
+    else if (it->first == "ALBUM")                     tag.SetAlbum(it->second.toString().toCString());
+    else if (it->first == "TITLE")                     tag.SetTitle(it->second.toString().toCString());
+    else if (it->first == "TRACKNUMBER")               tag.SetTrackNumber(it->second.toString().toInt());
+    else if (it->first == "DISCNUMBER")                tag.SetPartOfSet(it->second.toString().toInt());
+    else if (it->first == "YEAR")                      tag.SetYear(it->second.toString().toInt());
+    else if (it->first == "GENRE")                     tag.SetGenre(StringListToVectorString(it->second.toStringList()));
+    else if (it->first == "COMMENT")                   tag.SetComment(it->second.toString().toCString());
+    else if (it->first == "ENCODEDBY")                 {}
+    else if (it->first == "COMPILATION")               tag.SetCompilation(it->second.toString().toInt() == 1);
+    else if (it->first == "LYRICS")                    tag.SetLyrics(it->second.toString().toCString());
+    else if (it->first == "REPLAYGAIN_TRACK_GAIN")     m_rg.SetTrackGain((int)(atof(it->second.toString().toCString()) * 100 + 0.5));
+    else if (it->first == "REPLAYGAIN_ALBUM_GAIN")     m_rg.SetAlbumGain((int)(atof(it->second.toString().toCString()) * 100 + 0.5));
+    else if (it->first == "REPLAYGAIN_TRACK_PEAK")     m_rg.SetTrackPeak((float)atof(it->second.toString().toCString()));
+    else if (it->first == "REPLAYGAIN_ALBUM_PEAK")     m_rg.SetAlbumPeak((float)atof(it->second.toString().toCString()));
+    else if (it->first == "MUSICBRAINZ_ARTISTID")      tag.SetMusicBrainzArtistID(it->second.toString().toCString());
+    else if (it->first == "MUSICBRAINZ_ALBUMARTISTID") tag.SetMusicBrainzAlbumArtistID(it->second.toString().toCString());
+    else if (it->first == "MUSICBRAINZ_ALBUMID")       tag.SetMusicBrainzAlbumID(it->second.toString().toCString());
+    else if (it->first == "MUSICBRAINZ_TRACKID")       tag.SetMusicBrainzTrackID(it->second.toString().toCString());
+    else
+      CLog::Log(LOGDEBUG, "unrecognized APE tag: %s", it->first.toCString());
+  }
+
+  return true;
+}
+
+bool CTagLoaderTagLib::ParseXiphComment(Ogg::XiphComment *xiph, EmbeddedArt *art, CMusicInfoTag& tag)
+{
+  if (!xiph)
+    return false;
+
+  const Ogg::FieldListMap& fieldListMap = xiph->fieldListMap();
+  for (Ogg::FieldListMap::ConstIterator it = fieldListMap.begin(); it != fieldListMap.end(); ++it)
+  {
+    if (it->first == "ARTIST")                         tag.SetArtist(StringListToVectorString(it->second));
+    else if (it->first == "ALBUMARTIST")               tag.SetAlbumArtist(StringListToVectorString(it->second));
+    else if (it->first == "ALBUM")                     tag.SetAlbum(it->second.front().toCString());
+    else if (it->first == "TITLE")                     tag.SetTitle(it->second.front().toCString());
+    else if (it->first == "TRACKNUMBER")               tag.SetTrackNumber(it->second.front().toInt());
+    else if (it->first == "DISCNUMBER")                tag.SetPartOfSet(it->second.front().toInt());
+    else if (it->first == "YEAR")                      tag.SetYear(it->second.front().toInt());
+    else if (it->first == "GENRE")                     tag.SetGenre(StringListToVectorString(it->second));
+    else if (it->first == "COMMENT")                   tag.SetComment(it->second.front().toCString());
+    else if (it->first == "ENCODEDBY")                 {}
+    else if (it->first == "COMPILATION")               tag.SetCompilation(it->second.front().toInt() == 1);
+    else if (it->first == "LYRICS")                    tag.SetLyrics(it->second.front().toCString());
+    else if (it->first == "REPLAYGAIN_TRACK_GAIN")     m_rg.SetTrackGain((int)(atof(it->second.front().toCString()) * 100 + 0.5));
+    else if (it->first == "REPLAYGAIN_ALBUM_GAIN")     m_rg.SetAlbumGain((int)(atof(it->second.front().toCString()) * 100 + 0.5));
+    else if (it->first == "REPLAYGAIN_TRACK_PEAK")     m_rg.SetTrackPeak((float)atof(it->second.front().toCString()));
+    else if (it->first == "REPLAYGAIN_ALBUM_PEAK")     m_rg.SetAlbumPeak((float)atof(it->second.front().toCString()));
+    else if (it->first == "MUSICBRAINZ_ARTISTID")      tag.SetMusicBrainzArtistID(it->second.front().toCString());
+    else if (it->first == "MUSICBRAINZ_ALBUMARTISTID") tag.SetMusicBrainzAlbumArtistID(it->second.front().toCString());
+    else if (it->first == "MUSICBRAINZ_ALBUMID")       tag.SetMusicBrainzAlbumID(it->second.front().toCString());
+    else if (it->first == "MUSICBRAINZ_TRACKID")       tag.SetMusicBrainzTrackID(it->second.front().toCString());
+    else if (it->first == "RATING")
+    {
+      // Vorbis ratings are a mess because the standard forgot to mention anything about them.
+      // If you want to see how emotive the issue is and the varying standards, check here:
+      // http://forums.winamp.com/showthread.php?t=324512
+      // The most common standard in that thread seems to be a 0-100 scale for 1-5 stars.
+      // So, that's what we'll support for now.
+      int iRating = it->second.front().toInt();
+      if (iRating > 0 && iRating <= 100)
+        tag.SetRating((iRating / 20) + '0');
+    }
+    else
+      CLog::Log(LOGDEBUG, "unrecognized XipComment name: %s", it->first.toCString());
+  }
+
+  return true;
+}
+
+bool CTagLoaderTagLib::ParseMP4Tag(MP4::Tag *mp4, EmbeddedArt *art, CMusicInfoTag& tag)
+{
+  if (!mp4)
+    return false;
+
+  MP4::ItemListMap& itemListMap = mp4->itemListMap();
+  for (MP4::ItemListMap::ConstIterator it = itemListMap.begin(); it != itemListMap.end(); ++it)
+  {
+    if (it->first == "\251nam")      tag.SetTitle(it->second.toStringList().front().toCString());
+    else if (it->first == "\251ART") tag.SetArtist(StringListToVectorString(it->second.toStringList()));
+    else if (it->first == "\251alb") tag.SetAlbum(it->second.toStringList().front().toCString());
+    else if (it->first == "aART")    tag.SetAlbumArtist(StringListToVectorString(it->second.toStringList()));
+    else if (it->first == "\251gen") tag.SetGenre(StringListToVectorString(it->second.toStringList()));
+    else if (it->first == "\251cmt") tag.SetComment(it->second.toStringList().front().toCString());
+    else if (it->first == "cpil")    tag.SetCompilation(it->second.toBool());
+    else if (it->first == "trkn")    tag.SetTrackNumber(it->second.toIntPair().first);
+    else if (it->first == "disk")    tag.SetPartOfSet(it->second.toIntPair().first);
+    else if (it->first == "\251day") tag.SetYear(it->second.toStringList().front().toInt());
+    else if (it->first == "----:com.apple.iTunes:MusicBrainz Artist Id")
+      tag.SetMusicBrainzArtistID(it->second.toStringList().front().toCString());
+    else if (it->first == "----:com.apple.iTunes:MusicBrainz Album Artist Id")
+      tag.SetMusicBrainzAlbumArtistID(it->second.toStringList().front().toCString());
+    else if (it->first == "----:com.apple.iTunes:MusicBrainz Album Id")
+      tag.SetMusicBrainzAlbumID(it->second.toStringList().front().toCString());
+    else if (it->first == "----:com.apple.iTunes:MusicBrainz Track Id")
+      tag.SetMusicBrainzTrackID(it->second.toStringList().front().toCString());
+    else if (it->first == "covr")
+    {
+      MP4::CoverArtList coverArtList = it->second.toCoverArtList();
+      for (MP4::CoverArtList::ConstIterator pt = coverArtList.begin(); pt != coverArtList.end(); ++pt)
+      {
+        string   mime =             pt->format() == MP4::CoverArt::PNG ? "image/png" : "image/jpeg";
+        size_t   size =             pt->data().size();
+        uint8_t* data = (uint8_t *) pt->data().data();
+        tag.SetCoverArtInfo(size, mime);
+        if (art)
+          art->set(data, size, mime);
+      }
+    }
+  }
+
+  return true;
+}
+
+bool CTagLoaderTagLib::ParseGenericTag(Tag *generic, EmbeddedArt *art, CMusicInfoTag& tag)
+{
+  if (!generic)
+    return false;
+
+  PropertyMap properties = generic->properties();
+  for (PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it)
+  {
+    if (it->first == "ARTIST")                         tag.SetArtist(StringListToVectorString(it->second));
+    else if (it->first == "ALBUM")                     tag.SetArtist(it->second.front().toCString());
+    else if (it->first == "TITLE")                     tag.SetTitle(it->second.front().toCString());
+    else if (it->first == "TRACKNUMBER")               tag.SetTrackNumber(it->second.front().toInt());
+    else if (it->first == "YEAR")                      tag.SetYear(it->second.front().toInt());
+    else if (it->first == "GENRE")                     tag.SetGenre(StringListToVectorString(it->second));
+    else if (it->first == "COMMENT")                   tag.SetGenre(it->second.front().toCString());
+  }
+
+  return true;
+}
+
+const vector<string> CTagLoaderTagLib::GetASFStringList(const List<ASF::Attribute>& list)
+{
+  vector<string> values;
+  for (List<ASF::Attribute>::ConstIterator at = list.begin(); at != list.end(); ++at)
+    values.push_back(at->toString().toCString());
+  return values;
+}
+
+const vector<string> CTagLoaderTagLib::GetID3v2StringList(const ID3v2::FrameList& frameList) const
+{
+  const ID3v2::TextIdentificationFrame *frame = dynamic_cast<ID3v2::TextIdentificationFrame *>(frameList.front());
+  if (frame)
+    return StringListToVectorString(frame->fieldList());
+  return vector<string>();
+}
+
+bool CTagLoaderTagLib::GetReplayGain(CReplayGain &info) const
+{
+  if (!m_rg.iHasGainInfo)
+    return false;
+  info = m_rg;
+  return true;
+}
diff --git a/xbmc/music/tags/TagLoaderTagLib.h b/xbmc/music/tags/TagLoaderTagLib.h
new file mode 100644 (file)
index 0000000..547a85e
--- /dev/null
@@ -0,0 +1,76 @@
+#pragma once
+/*
+ *      Copyright (C) 2005-2012 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#undef byte
+#include <taglib/aifffile.h>
+#include <taglib/apefile.h>
+#include <taglib/asffile.h>
+#include <taglib/flacfile.h>
+#include <taglib/itfile.h>
+#include <taglib/modfile.h>
+#include <taglib/mpcfile.h>
+#include <taglib/mp4file.h>
+#include <taglib/mpegfile.h>
+#include <taglib/oggfile.h>
+#include <taglib/oggflacfile.h>
+#include <taglib/rifffile.h>
+#include <taglib/speexfile.h>
+#include <taglib/s3mfile.h>
+#include <taglib/trueaudiofile.h>
+#include <taglib/vorbisfile.h>
+#include <taglib/wavpackfile.h>
+#include <taglib/xmfile.h>
+
+#include <taglib/id3v2tag.h>
+#include <taglib/xiphcomment.h>
+#include <taglib/mp4tag.h>
+#include "TagLibVFSStream.h"
+#include "cores/paplayer/ReplayGain.h"
+
+namespace MUSIC_INFO
+{
+  class CMusicInfoTag;
+  class EmbeddedArt;
+};
+
+class CTagLoaderTagLib
+{
+public:
+  CTagLoaderTagLib(const std::string& strFileName);
+  virtual ~CTagLoaderTagLib();
+  virtual bool                   Load(const std::string& strFileName, MUSIC_INFO::CMusicInfoTag& tag, MUSIC_INFO::EmbeddedArt *art = NULL);
+  bool                           GetReplayGain(CReplayGain &info) const;
+private:
+  bool                           Open(const std::string& strFileName, bool readOnly);
+  const std::vector<std::string> GetASFStringList(const TagLib::List<TagLib::ASF::Attribute>& list);
+  const std::vector<std::string> GetID3v2StringList(const TagLib::ID3v2::FrameList& frameList) const;
+
+  bool                           ParseAPETag(TagLib::APE::Tag *ape, MUSIC_INFO::EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& tag);
+  bool                           ParseASF(TagLib::ASF::Tag *asf, MUSIC_INFO::EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& tag);
+  bool                           ParseID3v2Tag(TagLib::ID3v2::Tag *id3v2, MUSIC_INFO::EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& tag);
+  bool                           ParseXiphComment(TagLib::Ogg::XiphComment *id3v2, MUSIC_INFO::EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& tag);
+  bool                           ParseMP4Tag(TagLib::MP4::Tag *mp4, MUSIC_INFO::EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& tag);
+  bool                           ParseGenericTag(TagLib::Tag *generic, MUSIC_INFO::EmbeddedArt *art, MUSIC_INFO::CMusicInfoTag& tag);
+
+  CReplayGain      m_rg;
+  MUSIC_INFO::TagLibVFSStream    m_tagLibVFSStream;
+};
\ No newline at end of file