aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/installdata/test-reference-data.txt1
-rw-r--r--cmake/treedata/common/tests.txt1
-rw-r--r--xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp2
-rw-r--r--xbmc/platform/darwin/ios/Info.plist.in2
-rw-r--r--xbmc/playlists/CMakeLists.txt2
-rw-r--r--xbmc/playlists/PlayListFactory.cpp11
-rw-r--r--xbmc/playlists/PlayListXSPF.cpp127
-rw-r--r--xbmc/playlists/PlayListXSPF.h24
-rw-r--r--xbmc/playlists/test/CMakeLists.txt4
-rw-r--r--xbmc/playlists/test/TestPlayListFactory.cpp39
-rw-r--r--xbmc/playlists/test/TestPlayListXSPF.cpp80
-rw-r--r--xbmc/playlists/test/test.xspf54
-rw-r--r--xbmc/settings/AdvancedSettings.cpp4
-rw-r--r--xbmc/utils/Mime.cpp1
14 files changed, 347 insertions, 5 deletions
diff --git a/cmake/installdata/test-reference-data.txt b/cmake/installdata/test-reference-data.txt
index 895079a05a..b6c94a7f7d 100644
--- a/cmake/installdata/test-reference-data.txt
+++ b/cmake/installdata/test-reference-data.txt
@@ -8,3 +8,4 @@ xbmc/filesystem/test/refRARstored.rar
xbmc/network/test/data/test.html
xbmc/network/test/data/test.png
xbmc/network/test/data/test-ranges.txt
+xbmc/playlists/test/test.xspf
diff --git a/cmake/treedata/common/tests.txt b/cmake/treedata/common/tests.txt
index 358bc4d546..6889cc1d74 100644
--- a/cmake/treedata/common/tests.txt
+++ b/cmake/treedata/common/tests.txt
@@ -4,6 +4,7 @@ xbmc/filesystem/test test/filesystem
xbmc/interfaces/python/test test/python
xbmc/music/tags/test test/music_tags
xbmc/network/test test/network
+xbmc/playlists/test test/playlists
xbmc/threads/test test/threads
xbmc/utils/test test/utils
xbmc/video/test test/video
diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp
index 8da3a7af47..6b9434e066 100644
--- a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp
+++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp
@@ -340,7 +340,7 @@ void CGUIWindowMusicPlaylistEditor::OnLoadPlaylist()
share.strPath = "special://musicplaylists/";
if (find(shares.begin(), shares.end(), share) == shares.end())
shares.push_back(share);
- if (CGUIDialogFileBrowser::ShowAndGetFile(shares, ".m3u|.pls|.b4s|.wpl", g_localizeStrings.Get(656), playlist))
+ if (CGUIDialogFileBrowser::ShowAndGetFile(shares, ".m3u|.pls|.b4s|.wpl|.xspf", g_localizeStrings.Get(656), playlist))
LoadPlaylist(playlist);
}
diff --git a/xbmc/platform/darwin/ios/Info.plist.in b/xbmc/platform/darwin/ios/Info.plist.in
index ea79a0aee6..9408ec57bc 100644
--- a/xbmc/platform/darwin/ios/Info.plist.in
+++ b/xbmc/platform/darwin/ios/Info.plist.in
@@ -320,6 +320,7 @@
<string>aif</string>
<string>aiff</string>
<string>wpl</string>
+ <string>xspf</string>
<string>ape</string>
<string>mac</string>
<string>mpc</string>
@@ -431,6 +432,7 @@
<string>rar</string>
<string>001</string>
<string>wpl</string>
+ <string>xspf</string>
<string>zip</string>
<string>vdr</string>
<string>dvr-ms</string>
diff --git a/xbmc/playlists/CMakeLists.txt b/xbmc/playlists/CMakeLists.txt
index a2eb0c253c..2c65ad8135 100644
--- a/xbmc/playlists/CMakeLists.txt
+++ b/xbmc/playlists/CMakeLists.txt
@@ -6,6 +6,7 @@ set(SOURCES PlayListB4S.cpp
PlayListURL.cpp
PlayListWPL.cpp
PlayListXML.cpp
+ PlayListXSPF.cpp
SmartPlayList.cpp
SmartPlaylistFileItemListModifier.cpp)
@@ -17,6 +18,7 @@ set(HEADERS PlayList.h
PlayListURL.h
PlayListWPL.h
PlayListXML.h
+ PlayListXSPF.h
SmartPlayList.h
SmartPlaylistFileItemListModifier.h)
diff --git a/xbmc/playlists/PlayListFactory.cpp b/xbmc/playlists/PlayListFactory.cpp
index 9213d37015..f98c230342 100644
--- a/xbmc/playlists/PlayListFactory.cpp
+++ b/xbmc/playlists/PlayListFactory.cpp
@@ -13,6 +13,7 @@
#include "PlayListWPL.h"
#include "PlayListURL.h"
#include "PlayListXML.h"
+#include "PlayListXSPF.h"
#include "utils/URIUtils.h"
#include "utils/StringUtils.h"
@@ -58,6 +59,9 @@ CPlayList* CPlayListFactory::Create(const CFileItem& item)
if (strMimeType == "application/vnd.ms-wpl")
return new CPlayListWPL();
+
+ if (strMimeType == "application/xspf+xml")
+ return new CPlayListXSPF();
}
std::string path = item.GetDynPath();
@@ -89,6 +93,9 @@ CPlayList* CPlayListFactory::Create(const CFileItem& item)
if (extension == ".pxml")
return new CPlayListXML();
+ if (extension == ".xspf")
+ return new CPlayListXSPF();
+
return NULL;
}
@@ -125,12 +132,12 @@ bool CPlayListFactory::IsPlaylist(const CFileItem& item)
bool CPlayListFactory::IsPlaylist(const CURL& url)
{
return URIUtils::HasExtension(url,
- ".m3u|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml");
+ ".m3u|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml|.xspf");
}
bool CPlayListFactory::IsPlaylist(const std::string& filename)
{
return URIUtils::HasExtension(filename,
- ".m3u|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml");
+ ".m3u|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml|.xspf");
}
diff --git a/xbmc/playlists/PlayListXSPF.cpp b/xbmc/playlists/PlayListXSPF.cpp
new file mode 100644
index 0000000000..b7ab7f646b
--- /dev/null
+++ b/xbmc/playlists/PlayListXSPF.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 Tyler Szabo
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PlayListXSPF.h"
+
+#include "utils/log.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "URL.h"
+
+using namespace PLAYLIST;
+
+namespace
+{
+
+constexpr char const* LOCATION_TAGNAME = "location";
+constexpr char const* PLAYLIST_TAGNAME = "playlist";
+constexpr char const* TITLE_TAGNAME = "title";
+constexpr char const* TRACK_TAGNAME = "track";
+constexpr char const* TRACKLIST_TAGNAME = "trackList";
+
+std::string GetXMLText(const TiXmlElement* pXmlElement)
+{
+ std::string result;
+ if (pXmlElement)
+ {
+ const char* const innerText = pXmlElement->GetText();
+ if (innerText)
+ result = innerText;
+ }
+ return result;
+}
+
+}
+
+CPlayListXSPF::CPlayListXSPF(void) = default;
+
+CPlayListXSPF::~CPlayListXSPF(void) = default;
+
+bool CPlayListXSPF::Load(const std::string& strFileName)
+{
+ CXBMCTinyXML xmlDoc;
+
+ if (!xmlDoc.LoadFile(strFileName))
+ {
+ CLog::Log(LOGERROR, "Error parsing XML file %s (%d, %d): %s", strFileName.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorCol(), xmlDoc.ErrorDesc());
+ return false;
+ }
+
+ TiXmlElement* pPlaylist = xmlDoc.FirstChildElement(PLAYLIST_TAGNAME);
+ if (!pPlaylist)
+ {
+ CLog::Log(LOGERROR, "Error parsing XML file %s: missing root element %s", strFileName.c_str(), PLAYLIST_TAGNAME);
+ return false;
+ }
+
+ TiXmlElement* pTracklist = pPlaylist->FirstChildElement(TRACKLIST_TAGNAME);
+ if (!pTracklist)
+ {
+ CLog::Log(LOGERROR, "Error parsing XML file %s: missing element %s", strFileName.c_str(), TRACKLIST_TAGNAME);
+ return false;
+ }
+
+ Clear();
+ URIUtils::GetParentPath(strFileName, m_strBasePath);
+
+ m_strPlayListName = GetXMLText(pPlaylist->FirstChildElement(TITLE_TAGNAME));
+
+ TiXmlElement* pCurTrack = pTracklist->FirstChildElement(TRACK_TAGNAME);
+ while (pCurTrack)
+ {
+ std::string location = GetXMLText(pCurTrack->FirstChildElement(LOCATION_TAGNAME));
+ if (!location.empty())
+ {
+ std::string label = GetXMLText(pCurTrack->FirstChildElement(TITLE_TAGNAME));
+
+ CFileItemPtr newItem(new CFileItem(label));
+
+ CURL uri(location);
+
+ // at the time of writing CURL doesn't handle file:// URI scheme the way
+ // it's presented in this format, parse to local path instead
+ std::string localpath;
+ if (StringUtils::StartsWith(location, "file:///"))
+ {
+#ifndef TARGET_WINDOWS
+ // Linux absolute path must start with root
+ localpath = "/";
+#endif
+ // Path starts after "file:///"
+ localpath += CURL::Decode(location.substr(8));
+ }
+ else if (uri.GetProtocol().empty())
+ {
+ localpath = URIUtils::AppendSlash(m_strBasePath) + CURL::Decode(location);
+ }
+
+ if (!localpath.empty())
+ {
+#ifdef TARGET_WINDOWS
+ StringUtils::Replace(localpath, "/", "\\");
+ localpath = URIUtils::CanonicalizePath(localpath, '\\');
+#else
+ localpath = URIUtils::CanonicalizePath(localpath, '/');
+#endif
+
+ newItem->SetPath(localpath);
+ }
+ else
+ {
+ newItem->SetURL(uri);
+ }
+
+ Add(newItem);
+ }
+
+ pCurTrack = pCurTrack->NextSiblingElement(TRACK_TAGNAME);
+ }
+
+ return true;
+}
diff --git a/xbmc/playlists/PlayListXSPF.h b/xbmc/playlists/PlayListXSPF.h
new file mode 100644
index 0000000000..8960460f78
--- /dev/null
+++ b/xbmc/playlists/PlayListXSPF.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PlayList.h"
+
+namespace PLAYLIST
+{
+class CPlayListXSPF : public CPlayList
+{
+public:
+ CPlayListXSPF(void);
+ ~CPlayListXSPF(void) override;
+
+ // Implementation of CPlayList
+ bool Load(const std::string& strFileName) override;
+};
+}
diff --git a/xbmc/playlists/test/CMakeLists.txt b/xbmc/playlists/test/CMakeLists.txt
new file mode 100644
index 0000000000..f421fac108
--- /dev/null
+++ b/xbmc/playlists/test/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(SOURCES TestPlayListFactory.cpp
+ TestPlayListXSPF.cpp)
+
+core_add_test_library(playlists_test)
diff --git a/xbmc/playlists/test/TestPlayListFactory.cpp b/xbmc/playlists/test/TestPlayListFactory.cpp
new file mode 100644
index 0000000000..18764fb778
--- /dev/null
+++ b/xbmc/playlists/test/TestPlayListFactory.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 Tyler Szabo
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "playlists/PlayListFactory.h"
+
+#include "playlists/PlayList.h"
+#include "playlists/PlayListXSPF.h"
+#include "test/TestUtils.h"
+#include "URL.h"
+
+#include "gtest/gtest.h"
+
+using namespace PLAYLIST;
+
+
+TEST(TestPlayListFactory, XSPF)
+{
+ std::string filename = XBMC_REF_FILE_PATH("/xbmc/playlists/test/newfile.xspf");
+ CURL url("http://example.com/playlists/playlist.xspf");
+ CPlayList* playlist = nullptr;
+
+ EXPECT_TRUE(CPlayListFactory::IsPlaylist(url));
+ EXPECT_TRUE(CPlayListFactory::IsPlaylist(filename));
+
+ playlist = CPlayListFactory::Create(filename);
+ EXPECT_NE(playlist, nullptr);
+
+ if (playlist)
+ {
+ EXPECT_NE(dynamic_cast<CPlayListXSPF*>(playlist), nullptr);
+ delete playlist;
+ }
+}
diff --git a/xbmc/playlists/test/TestPlayListXSPF.cpp b/xbmc/playlists/test/TestPlayListXSPF.cpp
new file mode 100644
index 0000000000..85737037b7
--- /dev/null
+++ b/xbmc/playlists/test/TestPlayListXSPF.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 Tyler Szabo
+ * Copyright (C) 2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "playlists/PlayListXSPF.h"
+
+#include "test/TestUtils.h"
+#include "utils/URIUtils.h"
+#include "FileItem.h"
+#include "URL.h"
+
+#include "gtest/gtest.h"
+
+using namespace PLAYLIST;
+
+
+TEST(TestPlayListXSPF, Load)
+{
+ std::string filename = XBMC_REF_FILE_PATH("/xbmc/playlists/test/test.xspf");
+ CPlayListXSPF playlist;
+ std::vector<std::string> pathparts;
+ std::vector<std::string>::reverse_iterator it;
+
+ EXPECT_TRUE(playlist.Load(filename));
+
+ EXPECT_EQ(playlist.size(), 5);
+ EXPECT_STREQ(playlist.GetName().c_str(), "Various Music");
+
+
+ ASSERT_GT(playlist.size(), 0);
+ EXPECT_STREQ(playlist[0]->GetLabel().c_str(), "");
+ EXPECT_STREQ(playlist[0]->GetURL().Get().c_str(), "http://example.com/song_1.mp3");
+
+
+ ASSERT_GT(playlist.size(), 1);
+ EXPECT_STREQ(playlist[1]->GetLabel().c_str(), "Relative local file");
+ pathparts = URIUtils::SplitPath(playlist[1]->GetPath());
+ it = pathparts.rbegin();
+ EXPECT_STREQ((*it++).c_str(), "song_2.mp3");
+ EXPECT_STREQ((*it++).c_str(), "path_to");
+ EXPECT_STREQ((*it++).c_str(), "relative");
+ EXPECT_STREQ((*it++).c_str(), "test");
+ EXPECT_STREQ((*it++).c_str(), "playlists");
+ EXPECT_STREQ((*it++).c_str(), "xbmc");
+
+
+ ASSERT_GT(playlist.size(), 2);
+ EXPECT_STREQ(playlist[2]->GetLabel().c_str(), "Don\xC2\x92t Worry, We\xC2\x92ll Be Watching You");
+ pathparts = URIUtils::SplitPath(playlist[2]->GetPath());
+ it = pathparts.rbegin();
+ EXPECT_STREQ((*it++).c_str(), "09 - Don't Worry, We'll Be Watching You.mp3");
+ EXPECT_STREQ((*it++).c_str(), "Making Mirrors");
+ EXPECT_STREQ((*it++).c_str(), "Gotye");
+ EXPECT_STREQ((*it++).c_str(), "Music");
+ EXPECT_STREQ((*it++).c_str(), "jane");
+ EXPECT_STREQ((*it++).c_str(), "Users");
+ EXPECT_STREQ((*it++).c_str(), "C:");
+
+
+ ASSERT_GT(playlist.size(), 3);
+ EXPECT_STREQ(playlist[3]->GetLabel().c_str(), "Rollin' & Scratchin'");
+ pathparts = URIUtils::SplitPath(playlist[3]->GetPath());
+ it = pathparts.rbegin();
+ EXPECT_STREQ((*it++).c_str(), "08 - Rollin' & Scratchin'.mp3");
+ EXPECT_STREQ((*it++).c_str(), "Homework");
+ EXPECT_STREQ((*it++).c_str(), "Daft Punk");
+ EXPECT_STREQ((*it++).c_str(), "Music");
+ EXPECT_STREQ((*it++).c_str(), "jane");
+ EXPECT_STREQ((*it++).c_str(), "home");
+
+
+ ASSERT_GT(playlist.size(), 4);
+ EXPECT_STREQ(playlist[4]->GetLabel().c_str(), "");
+ EXPECT_STREQ(playlist[4]->GetURL().Get().c_str(), "http://example.com/song_2.mp3");
+}
diff --git a/xbmc/playlists/test/test.xspf b/xbmc/playlists/test/test.xspf
new file mode 100644
index 0000000000..cf98d640db
--- /dev/null
+++ b/xbmc/playlists/test/test.xspf
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<playlist version="1" xmlns="http://xspf.org/ns/0/">
+
+ <!-- title of the playlist -->
+ <title>Various Music</title>
+
+ <!-- name of the author -->
+ <creator>Jane Doe</creator>
+
+ <!-- homepage of the author -->
+ <info>http://example.com/~jane</info>
+
+ <trackList>
+ <track>
+ <location>http://example.com/song_1.mp3</location>
+ </track>
+
+ <track>
+ <title>Relative local file</title>
+ <location>relative/path_to/song_2.mp3</location>
+ </track>
+
+ <track>
+ <!--Absolute Windows path -->
+ <location>file:///C:/Users/jane/Music/Gotye/Making%20Mirrors/09%20-%20Don%27t%20Worry%2C%20We%27ll%20Be%20Watching%20You.mp3</location>
+ <title>Don&#146;t Worry, We&#146;ll Be Watching You</title>
+ <creator>Gotye</creator>
+ <album>Making Mirrors</album>
+ <trackNum>9</trackNum>
+ </track>
+
+ <track>
+ <!--Absolute Linux path -->
+ <location>file:///home/jane/Music/Daft%20Punk/Homework/08%20-%20Rollin%27%20%26%20Scratchin%27.mp3</location>
+ <title>Rollin&#39; &amp; Scratchin&#39;</title>
+ <creator>Daft Punk</creator>
+ <album>Homework</album>
+ <trackNum>8</trackNum>
+ </track>
+
+ <track>
+ <title></title>
+ <location></location>
+ </track>
+
+ <track>
+ <title>Nothing to add for this entry</title>
+ </track>
+
+ <track>
+ <location>http://example.com/song_2.mp3</location>
+ </track>
+ </trackList>
+</playlist>
diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp
index 5fa7169ce3..4e66fc55e9 100644
--- a/xbmc/settings/AdvancedSettings.cpp
+++ b/xbmc/settings/AdvancedSettings.cpp
@@ -410,8 +410,8 @@ void CAdvancedSettings::Initialize()
m_databaseVideo.Reset();
m_pictureExtensions = ".png|.jpg|.jpeg|.bmp|.gif|.ico|.tif|.tiff|.tga|.pcx|.cbz|.zip|.rss|.webp|.jp2|.apng";
- m_musicExtensions = ".nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.gdm|.imf|.m15|.sfx|.uni|.ac3|.dts|.cue|.aif|.aiff|.wpl|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.wv|.dsp|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.wtv|.mka|.tak|.opus|.dff|.dsf|.m4b";
- m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.mpd|.m3u|.m3u8|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.udf|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.mk3d|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.001|.wpl|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.mpls|.webm|.bdmv|.wtv|.trp|.f4v";
+ m_musicExtensions = ".nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.gdm|.imf|.m15|.sfx|.uni|.ac3|.dts|.cue|.aif|.aiff|.wpl|.xspf|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.wv|.dsp|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.wtv|.mka|.tak|.opus|.dff|.dsf|.m4b";
+ m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.mpd|.m3u|.m3u8|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.udf|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.mk3d|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.001|.wpl|.xspf|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.mpls|.webm|.bdmv|.wtv|.trp|.f4v";
m_subtitlesExtensions = ".utf|.utf8|.utf-8|.sub|.srt|.smi|.rt|.txt|.ssa|.text|.ssa|.aqt|.jss|.ass|.idx|.ifo|.zip";
m_discStubExtensions = ".disc";
// internal music extensions
diff --git a/xbmc/utils/Mime.cpp b/xbmc/utils/Mime.cpp
index 5229ed658e..826dccb95b 100644
--- a/xbmc/utils/Mime.cpp
+++ b/xbmc/utils/Mime.cpp
@@ -490,6 +490,7 @@ static std::map<std::string, std::string> fillMimeTypes()
mimeTypes.insert(std::pair<std::string, std::string>("xpix", "application/x-vnd.ls-xpix"));
mimeTypes.insert(std::pair<std::string, std::string>("xpm", "image/xpm"));
mimeTypes.insert(std::pair<std::string, std::string>("x-png", "image/png"));
+ mimeTypes.insert(std::pair<std::string, std::string>("xspf", "application/xspf+xml"));
mimeTypes.insert(std::pair<std::string, std::string>("xsr", "video/x-amt-showrun"));
mimeTypes.insert(std::pair<std::string, std::string>("xvid", "video/x-msvideo"));
mimeTypes.insert(std::pair<std::string, std::string>("xwd", "image/x-xwd"));