Merge pull request #4857 from t-nelson/Gotham_13.2_backports
[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, 4095))
90   {
91     strLine = szLine;
92     StringUtils::Trim(strLine);
93
94     if (StringUtils::StartsWith(strLine, M3U_INFO_MARKER))
95     {
96       // start of info
97       size_t iColon = strLine.find(":");
98       size_t iComma = strLine.find(",");
99       if (iColon != std::string::npos &&
100           iComma != std::string::npos &&
101           iComma > iColon)
102       {
103         // Read the info and duration
104         iColon++;
105         CStdString strLength = strLine.substr(iColon, iComma - iColon);
106         lDuration = atoi(strLength.c_str());
107         iComma++;
108         strInfo = strLine.substr(iComma);
109         g_charsetConverter.unknownToUTF8(strInfo);
110       }
111     }
112     else if (strLine != M3U_START_MARKER &&
113              !StringUtils::StartsWith(strLine, M3U_ARTIST_MARKER) &&
114              !StringUtils::StartsWith(strLine, M3U_ALBUM_MARKER))
115     {
116       CStdString strFileName = strLine;
117
118       if (strFileName.size() > 0 && strFileName[0] == '#')
119         continue; // assume a comment or something else we don't support
120
121       // Skip self - do not load playlist recursively
122       if (URIUtils::GetFileName(strFileName).Equals(m_strPlayListName))
123         continue;
124
125       if (strFileName.length() > 0)
126       {
127         g_charsetConverter.unknownToUTF8(strFileName);
128
129         // If no info was read from from the extended tag information, use the file name
130         if (strInfo.length() == 0)
131         {
132           strInfo = URIUtils::GetFileName(strFileName);
133         }
134
135         // should substitition occur befor or after charset conversion??
136         strFileName = URIUtils::SubstitutePath(strFileName);
137
138         // Get the full path file name and add it to the the play list
139         CUtil::GetQualifiedFilename(m_strBasePath, strFileName);
140         CFileItemPtr newItem(new CFileItem(strInfo));
141         newItem->SetPath(strFileName);
142         if (lDuration && newItem->IsAudio())
143           newItem->GetMusicInfoTag()->SetDuration(lDuration);
144         Add(newItem);
145
146         // Reset the values just in case there part of the file have the extended marker
147         // and part don't
148         strInfo = "";
149         lDuration = 0;
150       }
151     }
152   }
153
154   file.Close();
155   return true;
156 }
157
158 void CPlayListM3U::Save(const CStdString& strFileName) const
159 {
160   if (!m_vecItems.size())
161     return;
162   CStdString strPlaylist = CUtil::MakeLegalPath(strFileName);
163   CFile file;
164   if (!file.OpenForWrite(strPlaylist,true))
165   {
166     CLog::Log(LOGERROR, "Could not save M3U playlist: [%s]", strPlaylist.c_str());
167     return;
168   }
169   CStdString strLine = StringUtils::Format("%s\n",M3U_START_MARKER);
170   file.Write(strLine.c_str(),strLine.size());
171   for (int i = 0; i < (int)m_vecItems.size(); ++i)
172   {
173     CFileItemPtr item = m_vecItems[i];
174     CStdString strDescription=item->GetLabel();
175     g_charsetConverter.utf8ToStringCharset(strDescription);
176     strLine = StringUtils::Format( "%s:%i,%s\n", M3U_INFO_MARKER, item->GetMusicInfoTag()->GetDuration() / 1000, strDescription.c_str() );
177     file.Write(strLine.c_str(),strLine.size());
178     CStdString strFileName = ResolveURL(item);
179     g_charsetConverter.utf8ToStringCharset(strFileName);
180     strLine = StringUtils::Format("%s\n",strFileName.c_str());
181     file.Write(strLine.c_str(),strLine.size());
182   }
183   file.Close();
184 }
185
186 CStdString CPlayListM3U::GetBestBandwidthStream(const CStdString &strFileName, size_t bandwidth)
187 {
188   // we may be passed a playlist that does not contain playlists of different
189   // bitrates (eg: this playlist is really the HLS video). So, default the
190   // return to the filename so it can be played
191   char szLine[4096];
192   CStdString strLine;
193   size_t maxBandwidth = 0;
194
195   // open the file, and if it fails, return
196   CFile file;
197   if (!file.Open(strFileName) )
198   {
199     file.Close();
200     return strFileName;
201   }
202
203   // get protocol options if they were set, so we can restore them again at the end
204   CURL playlistUrl(strFileName);
205   
206   // and set the fallback value
207   CURL subStreamUrl = CURL(strFileName);
208   
209   // determine the base
210   CURL basePlaylistUrl(URIUtils::GetParentPath(strFileName));
211   basePlaylistUrl.SetOptions("");
212   basePlaylistUrl.SetProtocolOptions("");
213   CStdString basePart = basePlaylistUrl.Get();
214
215   // convert bandwidth specified in kbps to bps used by the m3u8
216   bandwidth *= 1000;
217
218   while (file.ReadString(szLine, 1024))
219   {
220     // read and trim a line
221     strLine = szLine;
222     StringUtils::Trim(strLine);
223
224     // skip the first line
225     if (strLine == M3U_START_MARKER)
226         continue;
227     else if (StringUtils::StartsWith(strLine, 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           StringUtils::Trim(strLine);
244
245           // this line was empty
246           if (strLine.empty())
247             continue;
248
249           // store the max bandwidth
250           maxBandwidth = streamBandwidth;
251
252           // if the path is absolute just use it
253           if (CURL::IsFullPath(strLine))
254             subStreamUrl = CURL(strLine);
255           else
256             subStreamUrl = CURL(basePart + strLine);
257         }
258       }
259     }
260   }
261
262   // if any protocol options were set, restore them
263   subStreamUrl.SetProtocolOptions(playlistUrl.GetProtocolOptions());
264   return subStreamUrl.Get();
265 }
266
267 std::map< CStdString, CStdString > CPlayListM3U::ParseStreamLine(const CStdString &streamLine)
268 {
269   std::map< CStdString, CStdString > params;
270
271   // ensure the line has something beyond the stream marker and ':'
272   if (strlen(streamLine) < strlen(M3U_STREAM_MARKER) + 2)
273     return params;
274
275   // get the actual params following the :
276   CStdString strParams(streamLine.substr(strlen(M3U_STREAM_MARKER) + 1));
277
278   // separate the parameters
279   CStdStringArray vecParams = StringUtils::SplitString(strParams, ",");
280   for (size_t i = 0; i < vecParams.size(); i++)
281   {
282     // split the param, ensure there was an =
283     StringUtils::Trim(vecParams[i]);
284     CStdStringArray vecTuple = StringUtils::SplitString(vecParams[i], "=");
285     if (vecTuple.size() < 2)
286       continue;
287
288     // remove white space from name and value and store it in the dictionary
289     StringUtils::Trim(vecTuple[0]);
290     StringUtils::Trim(vecTuple[1]);
291     params[vecTuple[0]] = vecTuple[1];
292   }
293
294   return params;
295 }
296