[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / playlists / PlayListM3U.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://www.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;
167   strLine.Format("%s\n",M3U_START_MARKER);
168   file.Write(strLine.c_str(),strLine.size());
169   for (int i = 0; i < (int)m_vecItems.size(); ++i)
170   {
171     CFileItemPtr item = m_vecItems[i];
172     CStdString strDescription=item->GetLabel();
173     g_charsetConverter.utf8ToStringCharset(strDescription);
174     strLine.Format( "%s:%i,%s\n", M3U_INFO_MARKER, item->GetMusicInfoTag()->GetDuration() / 1000, strDescription.c_str() );
175     file.Write(strLine.c_str(),strLine.size());
176     CStdString strFileName = ResolveURL(item);
177     g_charsetConverter.utf8ToStringCharset(strFileName);
178     strLine.Format("%s\n",strFileName.c_str());
179     file.Write(strLine.c_str(),strLine.size());
180   }
181   file.Close();
182 }
183
184 CStdString CPlayListM3U::GetBestBandwidthStream(const CStdString &strFileName, size_t bandwidth)
185 {
186   // we may be passed a playlist that does not contain playlists of different
187   // bitrates (eg: this playlist is really the HLS video). So, default the
188   // return to the filename so it can be played
189   char szLine[4096];
190   CStdString strLine;
191   CStdString strPlaylist = strFileName;
192   size_t maxBandwidth = 0;
193
194   // if we cannot get the last / we wont be able to determine the sub-playlists
195   size_t baseEnd = strPlaylist.rfind('/');
196   if (baseEnd == std::string::npos)
197     return strPlaylist;
198
199   // store the base path (the path without the filename)
200   CStdString basePath = strPlaylist.substr(0, baseEnd + 1);
201
202   // open the file, and if it fails, return
203   CFile file;
204   if (!file.Open(strFileName) )
205   {
206     file.Close();
207     return strPlaylist;
208   }
209
210   // convert bandwidth specified in kbps to bps used by the m3u8
211   bandwidth *= 1000;
212
213   while (file.ReadString(szLine, 1024))
214   {
215     // read and trim a line
216     strLine = szLine;
217     strLine.TrimRight(" \t\r\n");
218     strLine.TrimLeft(" \t");
219
220     // skip the first line
221     if (strLine == M3U_START_MARKER)
222         continue;
223     else if (strLine.Left(strlen(M3U_STREAM_MARKER)) == M3U_STREAM_MARKER)
224     {
225       // parse the line so we can pull out the bandwidth
226       std::map< CStdString, CStdString > params = ParseStreamLine(strLine);
227       std::map< CStdString, CStdString >::iterator it = params.find(M3U_BANDWIDTH_MARKER);
228
229       if (it != params.end())
230       {
231         size_t streamBandwidth = atoi(it->second.c_str());
232         if ((maxBandwidth < streamBandwidth) && (streamBandwidth <= bandwidth))
233         {
234           // read the next line
235           if (!file.ReadString(szLine, 1024))
236             continue;
237
238           strLine = szLine;
239           strLine.TrimRight(" \t\r\n");
240           strLine.TrimLeft(" \t");
241
242           // this line was empty
243           if (strLine.empty())
244             continue;
245
246           // store the max bandwidth
247           maxBandwidth = streamBandwidth;
248
249           // if the path is absolute just use it
250           if (CURL::IsFullPath(strLine))
251             strPlaylist = strLine;
252           else
253             strPlaylist = basePath + strLine;
254         }
255       }
256     }
257   }
258
259   CLog::Log(LOGINFO, "Auto-selecting %s based on configured bandwidth.", strPlaylist.c_str());
260
261   return strPlaylist;
262 }
263
264 std::map< CStdString, CStdString > CPlayListM3U::ParseStreamLine(const CStdString &streamLine)
265 {
266   std::map< CStdString, CStdString > params;
267
268   // ensure the line has something beyond the stream marker and ':'
269   if (strlen(streamLine) < strlen(M3U_STREAM_MARKER) + 2)
270     return params;
271
272   // get the actual params following the :
273   CStdString strParams(streamLine.substr(strlen(M3U_STREAM_MARKER) + 1));
274
275   // separate the parameters
276   CStdStringArray vecParams = StringUtils::SplitString(strParams, ",");
277   for (size_t i = 0; i < vecParams.size(); i++)
278   {
279     // split the param, ensure there was an =
280     CStdStringArray vecTuple = StringUtils::SplitString(vecParams[i].Trim(), "=");
281     if (vecTuple.size() < 2)
282       continue;
283
284     // remove white space from name and value and store it in the dictionary
285     params[vecTuple[0].Trim()] = vecTuple[1].Trim();
286   }
287
288   return params;
289 }
290