[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / playlists / PlayListM3U.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "PlayListM3U.h"
22 #include "filesystem/File.h"
23 #include "URL.h"
24 #include "Util.h"
25 #include "utils/StringUtils.h"
26 #include "utils/CharsetConverter.h"
27 #include "utils/RegExp.h"
28 #include "utils/log.h"
29 #include "utils/URIUtils.h"
30 #include "settings/AdvancedSettings.h"
31 #include "music/tags/MusicInfoTag.h"
32
33 using namespace PLAYLIST;
34 using namespace XFILE;
35
36 #define M3U_START_MARKER "#EXTM3U"
37 #define M3U_INFO_MARKER  "#EXTINF"
38 #define M3U_ARTIST_MARKER  "#EXTART"
39 #define M3U_ALBUM_MARKER  "#EXTALB"
40 #define M3U_STREAM_MARKER  "#EXT-X-STREAM-INF"
41 #define M3U_BANDWIDTH_MARKER  "BANDWIDTH"
42
43 // example m3u file:
44 //   #EXTM3U
45 //   #EXTART:Demo Artist
46 //   #EXTALB:Demo Album
47 //   #EXTINF:5,demo
48 //   E:\Program Files\Winamp3\demo.mp3
49 //   #EXTINF:5,demo
50 //   E:\Program Files\Winamp3\demo.mp3
51
52
53 // example m3u8 containing streams of different bitrates
54 //   #EXTM3U
55 //   #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1600000
56 //   playlist_1600.m3u8
57 //   #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=3000000
58 //   playlist_3000.m3u8
59 //   #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=800000
60 //   playlist_800.m3u8
61
62
63 CPlayListM3U::CPlayListM3U(void)
64 {}
65
66 CPlayListM3U::~CPlayListM3U(void)
67 {}
68
69
70 bool CPlayListM3U::Load(const CStdString& strFileName)
71 {
72   char szLine[4096];
73   CStdString strLine;
74   CStdString strInfo = "";
75   long lDuration = 0;
76
77   Clear();
78
79   m_strPlayListName = URIUtils::GetFileName(strFileName);
80   URIUtils::GetParentPath(strFileName, m_strBasePath);
81
82   CFile file;
83   if (!file.Open(strFileName) )
84   {
85     file.Close();
86     return false;
87   }
88
89   while (file.ReadString(szLine, 1024))
90   {
91     strLine = szLine;
92     strLine.TrimRight(" \t\r\n");
93     strLine.TrimLeft(" \t");
94
95     if (strLine.Left( (int)strlen(M3U_INFO_MARKER) ) == M3U_INFO_MARKER)
96     {
97       // start of info
98       int iColon = (int)strLine.find(":");
99       int iComma = (int)strLine.find(",");
100       if (iColon >= 0 && iComma >= 0 && iComma > iColon)
101       {
102         // Read the info and duration
103         iColon++;
104         CStdString strLength = strLine.Mid(iColon, iComma - iColon);
105         lDuration = atoi(strLength.c_str());
106         iComma++;
107         strInfo = strLine.Right((int)strLine.size() - iComma);
108         g_charsetConverter.unknownToUTF8(strInfo);
109       }
110     }
111     else if (strLine != M3U_START_MARKER && strLine.Left(strlen(M3U_ARTIST_MARKER)) != M3U_ARTIST_MARKER && strLine.Left(strlen(M3U_ALBUM_MARKER)) != M3U_ALBUM_MARKER )
112     {
113       CStdString strFileName = strLine;
114
115       if (strFileName.size() > 0 && strFileName[0] == '#')
116         continue; // assume a comment or something else we don't support
117
118       // Skip self - do not load playlist recursively
119       if (URIUtils::GetFileName(strFileName).Equals(m_strPlayListName))
120         continue;
121
122       if (strFileName.length() > 0)
123       {
124         g_charsetConverter.unknownToUTF8(strFileName);
125
126         // If no info was read from from the extended tag information, use the file name
127         if (strInfo.length() == 0)
128         {
129           strInfo = URIUtils::GetFileName(strFileName);
130         }
131
132         // should substitition occur befor or after charset conversion??
133         strFileName = URIUtils::SubstitutePath(strFileName);
134
135         // Get the full path file name and add it to the the play list
136         CUtil::GetQualifiedFilename(m_strBasePath, strFileName);
137         CFileItemPtr newItem(new CFileItem(strInfo));
138         newItem->SetPath(strFileName);
139         if (lDuration && newItem->IsAudio())
140           newItem->GetMusicInfoTag()->SetDuration(lDuration);
141         Add(newItem);
142
143         // Reset the values just in case there part of the file have the extended marker
144         // and part don't
145         strInfo = "";
146         lDuration = 0;
147       }
148     }
149   }
150
151   file.Close();
152   return true;
153 }
154
155 void CPlayListM3U::Save(const CStdString& strFileName) const
156 {
157   if (!m_vecItems.size())
158     return;
159   CStdString strPlaylist = CUtil::MakeLegalPath(strFileName);
160   CFile file;
161   if (!file.OpenForWrite(strPlaylist,true))
162   {
163     CLog::Log(LOGERROR, "Could not save M3U playlist: [%s]", strPlaylist.c_str());
164     return;
165   }
166   CStdString strLine = StringUtils::Format("%s\n",M3U_START_MARKER);
167   file.Write(strLine.c_str(),strLine.size());
168   for (int i = 0; i < (int)m_vecItems.size(); ++i)
169   {
170     CFileItemPtr item = m_vecItems[i];
171     CStdString strDescription=item->GetLabel();
172     g_charsetConverter.utf8ToStringCharset(strDescription);
173     strLine = StringUtils::Format( "%s:%i,%s\n", M3U_INFO_MARKER, item->GetMusicInfoTag()->GetDuration() / 1000, strDescription.c_str() );
174     file.Write(strLine.c_str(),strLine.size());
175     CStdString strFileName = ResolveURL(item);
176     g_charsetConverter.utf8ToStringCharset(strFileName);
177     strLine = StringUtils::Format("%s\n",strFileName.c_str());
178     file.Write(strLine.c_str(),strLine.size());
179   }
180   file.Close();
181 }
182
183 CStdString CPlayListM3U::GetBestBandwidthStream(const CStdString &strFileName, size_t bandwidth)
184 {
185   // we may be passed a playlist that does not contain playlists of different
186   // bitrates (eg: this playlist is really the HLS video). So, default the
187   // return to the filename so it can be played
188   char szLine[4096];
189   CStdString strLine;
190   CStdString strPlaylist = strFileName;
191   size_t maxBandwidth = 0;
192
193   // first strip off any query string
194   size_t baseEnd = strPlaylist.find('?');
195   if (baseEnd != std::string::npos)
196     strPlaylist = strPlaylist.substr(0, baseEnd);
197
198   // if we cannot get the last / we wont be able to determine the sub-playlists
199   baseEnd = strPlaylist.rfind('/');
200   if (baseEnd == std::string::npos)
201     return strPlaylist;
202
203   // store the base path (the path without the filename)
204   CStdString basePath = strPlaylist.substr(0, baseEnd + 1);
205
206   // open the file, and if it fails, return
207   CFile file;
208   if (!file.Open(strFileName) )
209   {
210     file.Close();
211     return strPlaylist;
212   }
213
214   // convert bandwidth specified in kbps to bps used by the m3u8
215   bandwidth *= 1000;
216
217   while (file.ReadString(szLine, 1024))
218   {
219     // read and trim a line
220     strLine = szLine;
221     strLine.TrimRight(" \t\r\n");
222     strLine.TrimLeft(" \t");
223
224     // skip the first line
225     if (strLine == M3U_START_MARKER)
226         continue;
227     else if (strLine.Left(strlen(M3U_STREAM_MARKER)) == M3U_STREAM_MARKER)
228     {
229       // parse the line so we can pull out the bandwidth
230       std::map< CStdString, CStdString > params = ParseStreamLine(strLine);
231       std::map< CStdString, CStdString >::iterator it = params.find(M3U_BANDWIDTH_MARKER);
232
233       if (it != params.end())
234       {
235         size_t streamBandwidth = atoi(it->second.c_str());
236         if ((maxBandwidth < streamBandwidth) && (streamBandwidth <= bandwidth))
237         {
238           // read the next line
239           if (!file.ReadString(szLine, 1024))
240             continue;
241
242           strLine = szLine;
243           strLine.TrimRight(" \t\r\n");
244           strLine.TrimLeft(" \t");
245
246           // this line was empty
247           if (strLine.empty())
248             continue;
249
250           // store the max bandwidth
251           maxBandwidth = streamBandwidth;
252
253           // if the path is absolute just use it
254           if (CURL::IsFullPath(strLine))
255             strPlaylist = strLine;
256           else
257             strPlaylist = basePath + strLine;
258         }
259       }
260     }
261   }
262
263   CLog::Log(LOGINFO, "Auto-selecting %s based on configured bandwidth.", strPlaylist.c_str());
264
265   return strPlaylist;
266 }
267
268 std::map< CStdString, CStdString > CPlayListM3U::ParseStreamLine(const CStdString &streamLine)
269 {
270   std::map< CStdString, CStdString > params;
271
272   // ensure the line has something beyond the stream marker and ':'
273   if (strlen(streamLine) < strlen(M3U_STREAM_MARKER) + 2)
274     return params;
275
276   // get the actual params following the :
277   CStdString strParams(streamLine.substr(strlen(M3U_STREAM_MARKER) + 1));
278
279   // separate the parameters
280   CStdStringArray vecParams = StringUtils::SplitString(strParams, ",");
281   for (size_t i = 0; i < vecParams.size(); i++)
282   {
283     // split the param, ensure there was an =
284     CStdStringArray vecTuple = StringUtils::SplitString(vecParams[i].Trim(), "=");
285     if (vecTuple.size() < 2)
286       continue;
287
288     // remove white space from name and value and store it in the dictionary
289     params[vecTuple[0].Trim()] = vecTuple[1].Trim();
290   }
291
292   return params;
293 }
294