[video] Fix the refresh of movies with additional versions or extras
[xbmc.git] / xbmc / playlists / PlayListPLS.cpp
blob369804dba28a0bd7ff8b633d52dd1df4f63f7929
1 /*
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.
7 */
9 #include "PlayListPLS.h"
11 #include "FileItem.h"
12 #include "PlayListFactory.h"
13 #include "Util.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"
24 #include <iostream>
25 #include <memory>
26 #include <string>
27 #include <vector>
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 /*----------------------------------------------------------------------
36 [playlist]
37 PlaylistName=Playlist 001
38 File1=E:\Program Files\Winamp3\demo.mp3
39 Title1=demo
40 Length1=5
41 File2=E:\Program Files\Winamp3\demo.mp3
42 Title2=demo
43 Length2=5
44 NumberOfEntries=2
45 Version=2
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);
57 Clear();
59 bool bShoutCast = false;
60 if( StringUtils::StartsWithNoCase(strFileName, "shout://") )
62 strFileName.replace(0, 8, "http://");
63 m_strBasePath = "";
64 bShoutCast = true;
66 else
67 URIUtils::GetParentPath(strFileName, m_strBasePath);
69 CFile file;
70 if (!file.Open(strFileName) )
72 file.Close();
73 return false;
76 if (file.GetLength() > 1024*1024)
78 CLog::Log(LOGWARNING, "{} - File is larger than 1 MB, most likely not a playlist",
79 __FUNCTION__);
80 return false;
83 char szLine[4096];
84 std::string strLine;
86 // run through looking for the [playlist] marker.
87 // if we find another http stream, then load it.
88 while (true)
90 if ( !file.ReadString(szLine, sizeof(szLine) ) )
92 file.Close();
93 return size() > 0;
95 strLine = szLine;
96 StringUtils::Trim(strLine);
97 if(StringUtils::EqualsNoCase(strLine, START_PLAYLIST_MARKER))
98 break;
100 // if there is something else before playlist marker, this isn't a pls file
101 if(!strLine.empty())
102 return false;
105 bool bFailed = false;
106 while (file.ReadString(szLine, sizeof(szLine) ) )
108 strLine = 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);
114 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);
126 if (!Resize(idx))
128 bFailed = true;
129 break;
132 // Skip self - do not load playlist recursively
133 if (StringUtils::EqualsNoCase(URIUtils::GetFileName(strValue),
134 URIUtils::GetFileName(strFileName)))
135 continue;
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);
151 if (!Resize(idx))
153 bFailed = true;
154 break;
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);
162 if (!Resize(idx))
164 bFailed = true;
165 break;
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);
176 file.Close();
178 if (bFailed)
180 CLog::Log(LOGERROR,
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));
184 return false;
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);
195 else
197 ++p;
201 return true;
204 void CPlayListPLS::Save(const std::string& strFileName) const
206 if (!m_vecItems.size()) return ;
207 std::string strPlaylist = CUtil::MakeLegalPath(strFileName);
208 CFile file;
209 if (!file.OpenForWrite(strPlaylist, true))
211 CLog::Log(LOGERROR, "Could not save PLS playlist: [{}]", strPlaylist);
212 return;
214 std::string write;
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());
229 write +=
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());
236 file.Close();
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())
249 stream.get();
251 if(stream.peek() == '[')
253 // this is an [section] part, just ignore it
254 while(stream.good() && stream.peek() != '\r' && stream.peek() != '\n')
255 stream.get();
256 continue;
258 name = "";
259 value = "";
260 // consume name
261 while(stream.peek() != '\r' && stream.peek() != '\n' && stream.peek() != '=' && stream.good())
262 name += stream.get();
264 // consume =
265 if(stream.get() != '=')
266 continue;
268 // consume value
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
277 Add(newItem);
280 return true;
283 bool CPlayListASX::LoadData(std::istream& stream)
285 CLog::Log(LOGINFO, "Parsing ASX");
287 if(stream.peek() == '[')
289 return LoadAsxIniInfo(stream);
291 else
293 std::string asxstream(std::istreambuf_iterator<char>(stream), {});
294 CXBMCTinyXML xmlDoc;
295 xmlDoc.Parse(asxstream, TIXML_DEFAULT_ENCODING);
297 if (xmlDoc.Error())
299 CLog::Log(LOGERROR, "Unable to parse ASX info Error: {}", xmlDoc.ErrorDesc());
300 return false;
303 TiXmlElement *pRootElement = xmlDoc.RootElement();
305 if (!pRootElement)
306 return false;
308 // lowercase every element
309 TiXmlNode *pNode = pRootElement;
310 TiXmlNode *pChild = NULL;
311 std::string value;
312 value = pNode->Value();
313 StringUtils::ToLower(value);
314 pNode->SetValue(value);
315 while(pNode)
317 pChild = pNode->IterateChildren(pChild);
318 if(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();
327 while(pAttr)
329 value = pAttr->Name();
330 StringUtils::ToLower(value);
331 pAttr->SetName(value);
332 pAttr = pAttr->Next();
336 pNode = pChild;
337 pChild = NULL;
338 continue;
341 pChild = pNode;
342 pNode = pNode->Parent();
344 std::string roottitle;
345 TiXmlElement *pElement = pRootElement->FirstChildElement();
346 while (pElement)
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();
363 while (pRef)
364 { // multiple references may appear for one entry
365 // duration may exist on this level too
366 value = XMLUtils::GetAttribute(pRef, "href");
367 if (!value.empty())
369 if(title.empty())
370 title = value;
372 CLog::Log(LOGINFO, "Adding element {}, {}", title, value);
373 CFileItemPtr newItem(new CFileItem(title));
374 newItem->SetPath(value);
375 Add(newItem);
377 pRef = pRef->NextSiblingElement("ref");
380 else if (value == "entryref")
382 value = XMLUtils::GetAttribute(pElement, "href");
383 if (!value.empty())
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))
388 Add(*playlist);
391 pElement = pElement->NextSiblingElement();
395 return true;
399 bool CPlayListRAM::LoadData(std::istream& stream)
401 CLog::Log(LOGINFO, "Parsing RAM");
403 std::string strMMS;
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);
410 Add(newItem);
411 return true;
414 bool CPlayListPLS::Resize(std::vector <int>::size_type newSize)
416 if (newSize == 0)
417 return false;
419 while (m_vecItems.size() < newSize)
421 CFileItemPtr fileItem(new CFileItem());
422 m_vecItems.push_back(fileItem);
424 return true;