2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "PlayListPLS.h"
12 #include "PlayListFactory.h"
14 #include "filesystem/File.h"
15 #include "music/tags/MusicInfoTag.h"
16 #include "utils/CharsetConverter.h"
17 #include "utils/StringUtils.h"
18 #include "utils/URIUtils.h"
19 #include "utils/XBMCTinyXML.h"
20 #include "utils/XMLUtils.h"
21 #include "utils/log.h"
22 #include "video/VideoInfoTag.h"
29 using namespace XFILE
;
30 using namespace PLAYLIST
;
32 #define START_PLAYLIST_MARKER "[playlist]" // may be case-insensitive (equivalent to .ini file on win32)
33 #define PLAYLIST_NAME "PlaylistName"
35 /*----------------------------------------------------------------------
37 PlaylistName=Playlist 001
38 File1=E:\Program Files\Winamp3\demo.mp3
41 File2=E:\Program Files\Winamp3\demo.mp3
46 ----------------------------------------------------------------------*/
47 CPlayListPLS::CPlayListPLS(void) = default;
49 CPlayListPLS::~CPlayListPLS(void) = default;
51 bool CPlayListPLS::Load(const std::string
&strFile
)
53 //read it from the file
54 std::string
strFileName(strFile
);
55 m_strPlayListName
= URIUtils::GetFileName(strFileName
);
59 bool bShoutCast
= false;
60 if( StringUtils::StartsWithNoCase(strFileName
, "shout://") )
62 strFileName
.replace(0, 8, "http://");
67 URIUtils::GetParentPath(strFileName
, m_strBasePath
);
70 if (!file
.Open(strFileName
) )
76 if (file
.GetLength() > 1024*1024)
78 CLog::Log(LOGWARNING
, "{} - File is larger than 1 MB, most likely not a playlist",
86 // run through looking for the [playlist] marker.
87 // if we find another http stream, then load it.
90 if ( !file
.ReadString(szLine
, sizeof(szLine
) ) )
96 StringUtils::Trim(strLine
);
97 if(StringUtils::EqualsNoCase(strLine
, START_PLAYLIST_MARKER
))
100 // if there is something else before playlist marker, this isn't a pls file
105 bool bFailed
= false;
106 while (file
.ReadString(szLine
, sizeof(szLine
) ) )
109 StringUtils::RemoveCRLF(strLine
);
110 size_t iPosEqual
= strLine
.find('=');
111 if (iPosEqual
!= std::string::npos
)
113 std::string strLeft
= strLine
.substr(0, iPosEqual
);
115 std::string strValue
= strLine
.substr(iPosEqual
);
116 StringUtils::ToLower(strLeft
);
117 StringUtils::TrimLeft(strLeft
);
119 if (strLeft
== "numberofentries")
121 m_vecItems
.reserve(atoi(strValue
.c_str()));
123 else if (StringUtils::StartsWith(strLeft
, "file"))
125 std::vector
<int>::size_type idx
= atoi(strLeft
.c_str() + 4);
132 // Skip self - do not load playlist recursively
133 if (StringUtils::EqualsNoCase(URIUtils::GetFileName(strValue
),
134 URIUtils::GetFileName(strFileName
)))
137 if (m_vecItems
[idx
- 1]->GetLabel().empty())
138 m_vecItems
[idx
- 1]->SetLabel(URIUtils::GetFileName(strValue
));
139 CFileItem
item(strValue
, false);
140 if (bShoutCast
&& !item
.IsAudio())
141 strValue
.replace(0, 7, "shout://");
143 strValue
= URIUtils::SubstitutePath(strValue
);
144 CUtil::GetQualifiedFilename(m_strBasePath
, strValue
);
145 g_charsetConverter
.unknownToUTF8(strValue
);
146 m_vecItems
[idx
- 1]->SetPath(strValue
);
148 else if (StringUtils::StartsWith(strLeft
, "title"))
150 std::vector
<int>::size_type idx
= atoi(strLeft
.c_str() + 5);
156 g_charsetConverter
.unknownToUTF8(strValue
);
157 m_vecItems
[idx
- 1]->SetLabel(strValue
);
159 else if (StringUtils::StartsWith(strLeft
, "length"))
161 std::vector
<int>::size_type idx
= atoi(strLeft
.c_str() + 6);
167 m_vecItems
[idx
- 1]->GetMusicInfoTag()->SetDuration(atol(strValue
.c_str()));
169 else if (strLeft
== "playlistname")
171 m_strPlayListName
= strValue
;
172 g_charsetConverter
.unknownToUTF8(m_strPlayListName
);
181 "File {} is not a valid PLS playlist. Location of first file,title or length is not "
182 "permitted (eg. File0 should be File1)",
183 URIUtils::GetFileName(strFileName
));
187 // check for missing entries
188 ivecItems p
= m_vecItems
.begin();
189 while ( p
!= m_vecItems
.end())
191 if ((*p
)->GetPath().empty())
193 p
= m_vecItems
.erase(p
);
204 void CPlayListPLS::Save(const std::string
& strFileName
) const
206 if (!m_vecItems
.size()) return ;
207 std::string strPlaylist
= CUtil::MakeLegalPath(strFileName
);
209 if (!file
.OpenForWrite(strPlaylist
, true))
211 CLog::Log(LOGERROR
, "Could not save PLS playlist: [{}]", strPlaylist
);
215 write
+= StringUtils::Format("{}\n", START_PLAYLIST_MARKER
);
216 std::string strPlayListName
=m_strPlayListName
;
217 g_charsetConverter
.utf8ToStringCharset(strPlayListName
);
218 write
+= StringUtils::Format("PlaylistName={}\n", strPlayListName
);
220 for (int i
= 0; i
< (int)m_vecItems
.size(); ++i
)
222 CFileItemPtr item
= m_vecItems
[i
];
223 std::string strFileName
=item
->GetPath();
224 g_charsetConverter
.utf8ToStringCharset(strFileName
);
225 std::string strDescription
=item
->GetLabel();
226 g_charsetConverter
.utf8ToStringCharset(strDescription
);
227 write
+= StringUtils::Format("File{}={}\n", i
+ 1, strFileName
);
228 write
+= StringUtils::Format("Title{}={}\n", i
+ 1, strDescription
.c_str());
230 StringUtils::Format("Length{}={}\n", i
+ 1, item
->GetMusicInfoTag()->GetDuration() / 1000);
233 write
+= StringUtils::Format("NumberOfEntries={0}\n", m_vecItems
.size());
234 write
+= StringUtils::Format("Version=2\n");
235 file
.Write(write
.c_str(), write
.size());
239 bool CPlayListASX::LoadAsxIniInfo(std::istream
&stream
)
241 CLog::Log(LOGINFO
, "Parsing INI style ASX");
243 std::string name
, value
;
245 while( stream
.good() )
247 // consume blank rows, and blanks
248 while((stream
.peek() == '\r' || stream
.peek() == '\n' || stream
.peek() == ' ') && stream
.good())
251 if(stream
.peek() == '[')
253 // this is an [section] part, just ignore it
254 while(stream
.good() && stream
.peek() != '\r' && stream
.peek() != '\n')
261 while(stream
.peek() != '\r' && stream
.peek() != '\n' && stream
.peek() != '=' && stream
.good())
262 name
+= stream
.get();
265 if(stream
.get() != '=')
269 while(stream
.peek() != '\r' && stream
.peek() != '\n' && stream
.good())
270 value
+= stream
.get();
272 CLog::Log(LOGINFO
, "Adding element {}={}", name
, value
);
273 CFileItemPtr
newItem(new CFileItem(value
));
274 newItem
->SetPath(value
);
275 if (newItem
->IsVideo() && !newItem
->HasVideoInfoTag()) // File is a video and needs a VideoInfoTag
276 newItem
->GetVideoInfoTag()->Reset(); // Force VideoInfoTag creation
283 bool CPlayListASX::LoadData(std::istream
& stream
)
285 CLog::Log(LOGINFO
, "Parsing ASX");
287 if(stream
.peek() == '[')
289 return LoadAsxIniInfo(stream
);
293 std::string
asxstream(std::istreambuf_iterator
<char>(stream
), {});
295 xmlDoc
.Parse(asxstream
, TIXML_DEFAULT_ENCODING
);
299 CLog::Log(LOGERROR
, "Unable to parse ASX info Error: {}", xmlDoc
.ErrorDesc());
303 TiXmlElement
*pRootElement
= xmlDoc
.RootElement();
308 // lowercase every element
309 TiXmlNode
*pNode
= pRootElement
;
310 TiXmlNode
*pChild
= NULL
;
312 value
= pNode
->Value();
313 StringUtils::ToLower(value
);
314 pNode
->SetValue(value
);
317 pChild
= pNode
->IterateChildren(pChild
);
320 if (pChild
->Type() == TiXmlNode::TINYXML_ELEMENT
)
322 value
= pChild
->Value();
323 StringUtils::ToLower(value
);
324 pChild
->SetValue(value
);
326 TiXmlAttribute
* pAttr
= pChild
->ToElement()->FirstAttribute();
329 value
= pAttr
->Name();
330 StringUtils::ToLower(value
);
331 pAttr
->SetName(value
);
332 pAttr
= pAttr
->Next();
342 pNode
= pNode
->Parent();
344 std::string roottitle
;
345 TiXmlElement
*pElement
= pRootElement
->FirstChildElement();
348 value
= pElement
->Value();
349 if (value
== "title" && !pElement
->NoChildren())
351 roottitle
= pElement
->FirstChild()->ValueStr();
353 else if (value
== "entry")
355 std::string
title(roottitle
);
357 TiXmlElement
*pRef
= pElement
->FirstChildElement("ref");
358 TiXmlElement
*pTitle
= pElement
->FirstChildElement("title");
360 if(pTitle
&& !pTitle
->NoChildren())
361 title
= pTitle
->FirstChild()->ValueStr();
364 { // multiple references may appear for one entry
365 // duration may exist on this level too
366 value
= XMLUtils::GetAttribute(pRef
, "href");
372 CLog::Log(LOGINFO
, "Adding element {}, {}", title
, value
);
373 CFileItemPtr
newItem(new CFileItem(title
));
374 newItem
->SetPath(value
);
377 pRef
= pRef
->NextSiblingElement("ref");
380 else if (value
== "entryref")
382 value
= XMLUtils::GetAttribute(pElement
, "href");
384 { // found an entryref, let's try loading that url
385 std::unique_ptr
<CPlayList
> playlist(CPlayListFactory::Create(value
));
386 if (nullptr != playlist
)
387 if (playlist
->Load(value
))
391 pElement
= pElement
->NextSiblingElement();
399 bool CPlayListRAM::LoadData(std::istream
& stream
)
401 CLog::Log(LOGINFO
, "Parsing RAM");
404 while( stream
.peek() != '\n' && stream
.peek() != '\r' )
405 strMMS
+= stream
.get();
407 CLog::Log(LOGINFO
, "Adding element {}", strMMS
);
408 CFileItemPtr
newItem(new CFileItem(strMMS
));
409 newItem
->SetPath(strMMS
);
414 bool CPlayListPLS::Resize(std::vector
<int>::size_type newSize
)
419 while (m_vecItems
.size() < newSize
)
421 CFileItemPtr
fileItem(new CFileItem());
422 m_vecItems
.push_back(fileItem
);