diff options
-rw-r--r-- | cmake/installdata/test-reference-data.txt | 1 | ||||
-rw-r--r-- | cmake/treedata/common/tests.txt | 1 | ||||
-rw-r--r-- | xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp | 2 | ||||
-rw-r--r-- | xbmc/platform/darwin/ios/Info.plist.in | 2 | ||||
-rw-r--r-- | xbmc/playlists/CMakeLists.txt | 2 | ||||
-rw-r--r-- | xbmc/playlists/PlayListFactory.cpp | 11 | ||||
-rw-r--r-- | xbmc/playlists/PlayListXSPF.cpp | 127 | ||||
-rw-r--r-- | xbmc/playlists/PlayListXSPF.h | 24 | ||||
-rw-r--r-- | xbmc/playlists/test/CMakeLists.txt | 4 | ||||
-rw-r--r-- | xbmc/playlists/test/TestPlayListFactory.cpp | 39 | ||||
-rw-r--r-- | xbmc/playlists/test/TestPlayListXSPF.cpp | 80 | ||||
-rw-r--r-- | xbmc/playlists/test/test.xspf | 54 | ||||
-rw-r--r-- | xbmc/settings/AdvancedSettings.cpp | 4 | ||||
-rw-r--r-- | xbmc/utils/Mime.cpp | 1 |
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’t Worry, We’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' & Scratchin'</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")); |