summaryrefslogtreecommitdiffstats
path: root/xbmc/playlists/PlayListPLS.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/playlists/PlayListPLS.cpp')
-rw-r--r--xbmc/playlists/PlayListPLS.cpp425
1 files changed, 425 insertions, 0 deletions
diff --git a/xbmc/playlists/PlayListPLS.cpp b/xbmc/playlists/PlayListPLS.cpp
new file mode 100644
index 0000000..369804d
--- /dev/null
+++ b/xbmc/playlists/PlayListPLS.cpp
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2005-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 "PlayListPLS.h"
+
+#include "FileItem.h"
+#include "PlayListFactory.h"
+#include "Util.h"
+#include "filesystem/File.h"
+#include "music/tags/MusicInfoTag.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace XFILE;
+using namespace PLAYLIST;
+
+#define START_PLAYLIST_MARKER "[playlist]" // may be case-insensitive (equivalent to .ini file on win32)
+#define PLAYLIST_NAME "PlaylistName"
+
+/*----------------------------------------------------------------------
+[playlist]
+PlaylistName=Playlist 001
+File1=E:\Program Files\Winamp3\demo.mp3
+Title1=demo
+Length1=5
+File2=E:\Program Files\Winamp3\demo.mp3
+Title2=demo
+Length2=5
+NumberOfEntries=2
+Version=2
+----------------------------------------------------------------------*/
+CPlayListPLS::CPlayListPLS(void) = default;
+
+CPlayListPLS::~CPlayListPLS(void) = default;
+
+bool CPlayListPLS::Load(const std::string &strFile)
+{
+ //read it from the file
+ std::string strFileName(strFile);
+ m_strPlayListName = URIUtils::GetFileName(strFileName);
+
+ Clear();
+
+ bool bShoutCast = false;
+ if( StringUtils::StartsWithNoCase(strFileName, "shout://") )
+ {
+ strFileName.replace(0, 8, "http://");
+ m_strBasePath = "";
+ bShoutCast = true;
+ }
+ else
+ URIUtils::GetParentPath(strFileName, m_strBasePath);
+
+ CFile file;
+ if (!file.Open(strFileName) )
+ {
+ file.Close();
+ return false;
+ }
+
+ if (file.GetLength() > 1024*1024)
+ {
+ CLog::Log(LOGWARNING, "{} - File is larger than 1 MB, most likely not a playlist",
+ __FUNCTION__);
+ return false;
+ }
+
+ char szLine[4096];
+ std::string strLine;
+
+ // run through looking for the [playlist] marker.
+ // if we find another http stream, then load it.
+ while (true)
+ {
+ if ( !file.ReadString(szLine, sizeof(szLine) ) )
+ {
+ file.Close();
+ return size() > 0;
+ }
+ strLine = szLine;
+ StringUtils::Trim(strLine);
+ if(StringUtils::EqualsNoCase(strLine, START_PLAYLIST_MARKER))
+ break;
+
+ // if there is something else before playlist marker, this isn't a pls file
+ if(!strLine.empty())
+ return false;
+ }
+
+ bool bFailed = false;
+ while (file.ReadString(szLine, sizeof(szLine) ) )
+ {
+ strLine = szLine;
+ StringUtils::RemoveCRLF(strLine);
+ size_t iPosEqual = strLine.find('=');
+ if (iPosEqual != std::string::npos)
+ {
+ std::string strLeft = strLine.substr(0, iPosEqual);
+ iPosEqual++;
+ std::string strValue = strLine.substr(iPosEqual);
+ StringUtils::ToLower(strLeft);
+ StringUtils::TrimLeft(strLeft);
+
+ if (strLeft == "numberofentries")
+ {
+ m_vecItems.reserve(atoi(strValue.c_str()));
+ }
+ else if (StringUtils::StartsWith(strLeft, "file"))
+ {
+ std::vector <int>::size_type idx = atoi(strLeft.c_str() + 4);
+ if (!Resize(idx))
+ {
+ bFailed = true;
+ break;
+ }
+
+ // Skip self - do not load playlist recursively
+ if (StringUtils::EqualsNoCase(URIUtils::GetFileName(strValue),
+ URIUtils::GetFileName(strFileName)))
+ continue;
+
+ if (m_vecItems[idx - 1]->GetLabel().empty())
+ m_vecItems[idx - 1]->SetLabel(URIUtils::GetFileName(strValue));
+ CFileItem item(strValue, false);
+ if (bShoutCast && !item.IsAudio())
+ strValue.replace(0, 7, "shout://");
+
+ strValue = URIUtils::SubstitutePath(strValue);
+ CUtil::GetQualifiedFilename(m_strBasePath, strValue);
+ g_charsetConverter.unknownToUTF8(strValue);
+ m_vecItems[idx - 1]->SetPath(strValue);
+ }
+ else if (StringUtils::StartsWith(strLeft, "title"))
+ {
+ std::vector <int>::size_type idx = atoi(strLeft.c_str() + 5);
+ if (!Resize(idx))
+ {
+ bFailed = true;
+ break;
+ }
+ g_charsetConverter.unknownToUTF8(strValue);
+ m_vecItems[idx - 1]->SetLabel(strValue);
+ }
+ else if (StringUtils::StartsWith(strLeft, "length"))
+ {
+ std::vector <int>::size_type idx = atoi(strLeft.c_str() + 6);
+ if (!Resize(idx))
+ {
+ bFailed = true;
+ break;
+ }
+ m_vecItems[idx - 1]->GetMusicInfoTag()->SetDuration(atol(strValue.c_str()));
+ }
+ else if (strLeft == "playlistname")
+ {
+ m_strPlayListName = strValue;
+ g_charsetConverter.unknownToUTF8(m_strPlayListName);
+ }
+ }
+ }
+ file.Close();
+
+ if (bFailed)
+ {
+ CLog::Log(LOGERROR,
+ "File {} is not a valid PLS playlist. Location of first file,title or length is not "
+ "permitted (eg. File0 should be File1)",
+ URIUtils::GetFileName(strFileName));
+ return false;
+ }
+
+ // check for missing entries
+ ivecItems p = m_vecItems.begin();
+ while ( p != m_vecItems.end())
+ {
+ if ((*p)->GetPath().empty())
+ {
+ p = m_vecItems.erase(p);
+ }
+ else
+ {
+ ++p;
+ }
+ }
+
+ return true;
+}
+
+void CPlayListPLS::Save(const std::string& strFileName) const
+{
+ if (!m_vecItems.size()) return ;
+ std::string strPlaylist = CUtil::MakeLegalPath(strFileName);
+ CFile file;
+ if (!file.OpenForWrite(strPlaylist, true))
+ {
+ CLog::Log(LOGERROR, "Could not save PLS playlist: [{}]", strPlaylist);
+ return;
+ }
+ std::string write;
+ write += StringUtils::Format("{}\n", START_PLAYLIST_MARKER);
+ std::string strPlayListName=m_strPlayListName;
+ g_charsetConverter.utf8ToStringCharset(strPlayListName);
+ write += StringUtils::Format("PlaylistName={}\n", strPlayListName);
+
+ for (int i = 0; i < (int)m_vecItems.size(); ++i)
+ {
+ CFileItemPtr item = m_vecItems[i];
+ std::string strFileName=item->GetPath();
+ g_charsetConverter.utf8ToStringCharset(strFileName);
+ std::string strDescription=item->GetLabel();
+ g_charsetConverter.utf8ToStringCharset(strDescription);
+ write += StringUtils::Format("File{}={}\n", i + 1, strFileName);
+ write += StringUtils::Format("Title{}={}\n", i + 1, strDescription.c_str());
+ write +=
+ StringUtils::Format("Length{}={}\n", i + 1, item->GetMusicInfoTag()->GetDuration() / 1000);
+ }
+
+ write += StringUtils::Format("NumberOfEntries={0}\n", m_vecItems.size());
+ write += StringUtils::Format("Version=2\n");
+ file.Write(write.c_str(), write.size());
+ file.Close();
+}
+
+bool CPlayListASX::LoadAsxIniInfo(std::istream &stream)
+{
+ CLog::Log(LOGINFO, "Parsing INI style ASX");
+
+ std::string name, value;
+
+ while( stream.good() )
+ {
+ // consume blank rows, and blanks
+ while((stream.peek() == '\r' || stream.peek() == '\n' || stream.peek() == ' ') && stream.good())
+ stream.get();
+
+ if(stream.peek() == '[')
+ {
+ // this is an [section] part, just ignore it
+ while(stream.good() && stream.peek() != '\r' && stream.peek() != '\n')
+ stream.get();
+ continue;
+ }
+ name = "";
+ value = "";
+ // consume name
+ while(stream.peek() != '\r' && stream.peek() != '\n' && stream.peek() != '=' && stream.good())
+ name += stream.get();
+
+ // consume =
+ if(stream.get() != '=')
+ continue;
+
+ // consume value
+ while(stream.peek() != '\r' && stream.peek() != '\n' && stream.good())
+ value += stream.get();
+
+ CLog::Log(LOGINFO, "Adding element {}={}", name, value);
+ CFileItemPtr newItem(new CFileItem(value));
+ newItem->SetPath(value);
+ if (newItem->IsVideo() && !newItem->HasVideoInfoTag()) // File is a video and needs a VideoInfoTag
+ newItem->GetVideoInfoTag()->Reset(); // Force VideoInfoTag creation
+ Add(newItem);
+ }
+
+ return true;
+}
+
+bool CPlayListASX::LoadData(std::istream& stream)
+{
+ CLog::Log(LOGINFO, "Parsing ASX");
+
+ if(stream.peek() == '[')
+ {
+ return LoadAsxIniInfo(stream);
+ }
+ else
+ {
+ std::string asxstream(std::istreambuf_iterator<char>(stream), {});
+ CXBMCTinyXML xmlDoc;
+ xmlDoc.Parse(asxstream, TIXML_DEFAULT_ENCODING);
+
+ if (xmlDoc.Error())
+ {
+ CLog::Log(LOGERROR, "Unable to parse ASX info Error: {}", xmlDoc.ErrorDesc());
+ return false;
+ }
+
+ TiXmlElement *pRootElement = xmlDoc.RootElement();
+
+ if (!pRootElement)
+ return false;
+
+ // lowercase every element
+ TiXmlNode *pNode = pRootElement;
+ TiXmlNode *pChild = NULL;
+ std::string value;
+ value = pNode->Value();
+ StringUtils::ToLower(value);
+ pNode->SetValue(value);
+ while(pNode)
+ {
+ pChild = pNode->IterateChildren(pChild);
+ if(pChild)
+ {
+ if (pChild->Type() == TiXmlNode::TINYXML_ELEMENT)
+ {
+ value = pChild->Value();
+ StringUtils::ToLower(value);
+ pChild->SetValue(value);
+
+ TiXmlAttribute* pAttr = pChild->ToElement()->FirstAttribute();
+ while(pAttr)
+ {
+ value = pAttr->Name();
+ StringUtils::ToLower(value);
+ pAttr->SetName(value);
+ pAttr = pAttr->Next();
+ }
+ }
+
+ pNode = pChild;
+ pChild = NULL;
+ continue;
+ }
+
+ pChild = pNode;
+ pNode = pNode->Parent();
+ }
+ std::string roottitle;
+ TiXmlElement *pElement = pRootElement->FirstChildElement();
+ while (pElement)
+ {
+ value = pElement->Value();
+ if (value == "title" && !pElement->NoChildren())
+ {
+ roottitle = pElement->FirstChild()->ValueStr();
+ }
+ else if (value == "entry")
+ {
+ std::string title(roottitle);
+
+ TiXmlElement *pRef = pElement->FirstChildElement("ref");
+ TiXmlElement *pTitle = pElement->FirstChildElement("title");
+
+ if(pTitle && !pTitle->NoChildren())
+ title = pTitle->FirstChild()->ValueStr();
+
+ while (pRef)
+ { // multiple references may appear for one entry
+ // duration may exist on this level too
+ value = XMLUtils::GetAttribute(pRef, "href");
+ if (!value.empty())
+ {
+ if(title.empty())
+ title = value;
+
+ CLog::Log(LOGINFO, "Adding element {}, {}", title, value);
+ CFileItemPtr newItem(new CFileItem(title));
+ newItem->SetPath(value);
+ Add(newItem);
+ }
+ pRef = pRef->NextSiblingElement("ref");
+ }
+ }
+ else if (value == "entryref")
+ {
+ value = XMLUtils::GetAttribute(pElement, "href");
+ if (!value.empty())
+ { // found an entryref, let's try loading that url
+ std::unique_ptr<CPlayList> playlist(CPlayListFactory::Create(value));
+ if (nullptr != playlist)
+ if (playlist->Load(value))
+ Add(*playlist);
+ }
+ }
+ pElement = pElement->NextSiblingElement();
+ }
+ }
+
+ return true;
+}
+
+
+bool CPlayListRAM::LoadData(std::istream& stream)
+{
+ CLog::Log(LOGINFO, "Parsing RAM");
+
+ std::string strMMS;
+ while( stream.peek() != '\n' && stream.peek() != '\r' )
+ strMMS += stream.get();
+
+ CLog::Log(LOGINFO, "Adding element {}", strMMS);
+ CFileItemPtr newItem(new CFileItem(strMMS));
+ newItem->SetPath(strMMS);
+ Add(newItem);
+ return true;
+}
+
+bool CPlayListPLS::Resize(std::vector <int>::size_type newSize)
+{
+ if (newSize == 0)
+ return false;
+
+ while (m_vecItems.size() < newSize)
+ {
+ CFileItemPtr fileItem(new CFileItem());
+ m_vecItems.push_back(fileItem);
+ }
+ return true;
+}