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 "PlayListM3U.h"
14 #include "filesystem/File.h"
15 #include "music/MusicFileItemClassify.h"
16 #include "music/tags/MusicInfoTag.h"
17 #include "utils/CharsetConverter.h"
18 #include "utils/URIUtils.h"
19 #include "utils/log.h"
20 #include "video/VideoFileItemClassify.h"
21 #include "video/VideoInfoTag.h"
25 using namespace XFILE
;
27 namespace KODI::PLAYLIST
30 const char* CPlayListM3U::StartMarker
= "#EXTCPlayListM3U::M3U";
31 const char* CPlayListM3U::InfoMarker
= "#EXTINF";
32 const char* CPlayListM3U::ArtistMarker
= "#EXTART";
33 const char* CPlayListM3U::AlbumMarker
= "#EXTALB";
34 const char* CPlayListM3U::PropertyMarker
= "#KODIPROP";
35 const char* CPlayListM3U::VLCOptMarker
= "#EXTVLCOPT";
36 const char* CPlayListM3U::StreamMarker
= "#EXT-X-STREAM-INF";
37 const char* CPlayListM3U::BandwidthMarker
= "BANDWIDTH";
38 const char* CPlayListM3U::OffsetMarker
= "#EXT-KX-OFFSET";
42 // #EXTART:Demo Artist
44 // #KODIPROP:name=value
46 // E:\Program Files\Winamp3\demo.mp3
50 // example m3u8 containing streams of different bitrates
52 // #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1600000
54 // #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=3000000
56 // #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=800000
60 CPlayListM3U::CPlayListM3U(void) = default;
62 CPlayListM3U::~CPlayListM3U(void) = default;
65 bool CPlayListM3U::Load(const std::string
& strFileName
)
70 std::vector
<std::pair
<std::string
, std::string
> > properties
;
77 if (URIUtils::GetExtension(strFileName
) == ".m3u8")
82 m_strPlayListName
= URIUtils::GetFileName(strFileName
);
83 URIUtils::GetParentPath(strFileName
, m_strBasePath
);
86 if (!file
.Open(strFileName
) )
92 while (file
.ReadString(szLine
, 4095))
95 StringUtils::Trim(strLine
);
97 if (StringUtils::StartsWith(strLine
, InfoMarker
))
100 size_t iColon
= strLine
.find(':');
101 size_t iComma
= strLine
.find(',');
102 if (iColon
!= std::string::npos
&&
103 iComma
!= std::string::npos
&&
106 // Read the info and duration
108 std::string strLength
= strLine
.substr(iColon
, iComma
- iColon
);
109 lDuration
= atoi(strLength
.c_str());
111 strInfo
= strLine
.substr(iComma
);
113 g_charsetConverter
.unknownToUTF8(strInfo
);
116 else if (StringUtils::StartsWith(strLine
, OffsetMarker
))
118 size_t iColon
= strLine
.find(':');
119 size_t iComma
= strLine
.find(',');
120 if (iColon
!= std::string::npos
&&
121 iComma
!= std::string::npos
&&
124 // Read the start and end offset
126 iStartOffset
= atoi(strLine
.substr(iColon
, iComma
- iColon
).c_str());
128 iEndOffset
= atoi(strLine
.substr(iComma
).c_str());
131 else if (StringUtils::StartsWith(strLine
, PropertyMarker
)
132 || StringUtils::StartsWith(strLine
, VLCOptMarker
))
134 size_t iColon
= strLine
.find(':');
135 size_t iEqualSign
= strLine
.find('=');
136 if (iColon
!= std::string::npos
&&
137 iEqualSign
!= std::string::npos
&&
140 std::string strFirst
, strSecond
;
141 properties
.emplace_back(
142 StringUtils::Trim((strFirst
= strLine
.substr(iColon
+ 1, iEqualSign
- iColon
- 1))),
143 StringUtils::Trim((strSecond
= strLine
.substr(iEqualSign
+ 1))));
146 else if (strLine
!= StartMarker
&&
147 !StringUtils::StartsWith(strLine
, ArtistMarker
) &&
148 !StringUtils::StartsWith(strLine
, AlbumMarker
))
150 std::string strFileName
= strLine
;
152 if (!strFileName
.empty() && strFileName
[0] == '#')
153 continue; // assume a comment or something else we don't support
155 // Skip self - do not load playlist recursively
156 // We compare case-less in case user has input incorrect case of the current playlist
157 if (StringUtils::EqualsNoCase(URIUtils::GetFileName(strFileName
), m_strPlayListName
))
160 if (strFileName
.length() > 0)
163 g_charsetConverter
.unknownToUTF8(strFileName
);
165 // If no info was read from from the extended tag information, use the file name
166 if (strInfo
.length() == 0)
168 strInfo
= URIUtils::GetFileName(strFileName
);
171 // should substitution occur before or after charset conversion??
172 strFileName
= URIUtils::SubstitutePath(strFileName
);
174 // Get the full path file name and add it to the the play list
175 CUtil::GetQualifiedFilename(m_strBasePath
, strFileName
);
176 CFileItemPtr
newItem(new CFileItem(strInfo
));
177 newItem
->SetPath(strFileName
);
178 if (iStartOffset
!= 0 || iEndOffset
!= 0)
180 newItem
->SetStartOffset(iStartOffset
);
181 newItem
->m_lStartPartNumber
= 1;
182 newItem
->SetProperty("item_start", iStartOffset
);
183 newItem
->SetEndOffset(iEndOffset
);
184 // Prevent load message from file and override offset set here
185 newItem
->GetMusicInfoTag()->SetLoaded();
186 newItem
->GetMusicInfoTag()->SetTitle(strInfo
);
188 lDuration
= static_cast<int>(CUtil::ConvertMilliSecsToSecsIntRounded(iEndOffset
- iStartOffset
));
190 if (VIDEO::IsVideo(*newItem
) &&
191 !newItem
->HasVideoInfoTag()) // File is a video and needs a VideoInfoTag
192 newItem
->GetVideoInfoTag()->Reset(); // Force VideoInfoTag creation
193 if (lDuration
&& MUSIC::IsAudio(*newItem
))
194 newItem
->GetMusicInfoTag()->SetDuration(lDuration
);
195 for (auto &prop
: properties
)
197 newItem
->SetProperty(prop
.first
, prop
.second
);
200 newItem
->SetMimeType(newItem
->GetProperty("mimetype").asString());
201 if (!newItem
->GetMimeType().empty())
202 newItem
->SetContentLookup(false);
206 // Reset the values just in case there part of the file have the extended marker
221 void CPlayListM3U::Save(const std::string
& strFileName
) const
223 if (!m_vecItems
.size())
226 if (URIUtils::GetExtension(strFileName
) == ".m3u8")
228 std::string strPlaylist
= CUtil::MakeLegalPath(strFileName
);
230 if (!file
.OpenForWrite(strPlaylist
,true))
232 CLog::Log(LOGERROR
, "Could not save M3U playlist: [{}]", strPlaylist
);
235 std::string strLine
= StringUtils::Format("{}\n", StartMarker
);
236 if (file
.Write(strLine
.c_str(), strLine
.size()) != static_cast<ssize_t
>(strLine
.size()))
239 for (int i
= 0; i
< (int)m_vecItems
.size(); ++i
)
241 CFileItemPtr item
= m_vecItems
[i
];
242 std::string strDescription
=item
->GetLabel();
244 g_charsetConverter
.utf8ToStringCharset(strDescription
);
245 strLine
= StringUtils::Format("{}:{},{}\n", InfoMarker
,
246 item
->GetMusicInfoTag()->GetDuration(), strDescription
);
247 if (file
.Write(strLine
.c_str(), strLine
.size()) != static_cast<ssize_t
>(strLine
.size()))
249 if (item
->GetStartOffset() != 0 || item
->GetEndOffset() != 0)
251 strLine
= StringUtils::Format("{}:{},{}\n", OffsetMarker
, item
->GetStartOffset(),
252 item
->GetEndOffset());
253 file
.Write(strLine
.c_str(),strLine
.size());
255 std::string strFileName
= ResolveURL(item
);
257 g_charsetConverter
.utf8ToStringCharset(strFileName
);
258 strLine
= StringUtils::Format("{}\n", strFileName
);
259 if (file
.Write(strLine
.c_str(), strLine
.size()) != static_cast<ssize_t
>(strLine
.size()))
265 std::map
< std::string
, std::string
> CPlayListM3U::ParseStreamLine(const std::string
&streamLine
)
267 std::map
< std::string
, std::string
> params
;
269 // ensure the line has something beyond the stream marker and ':'
270 if (streamLine
.size() < strlen(StreamMarker
) + 2)
273 // get the actual params following the :
274 std::string
strParams(streamLine
.substr(strlen(StreamMarker
) + 1));
276 // separate the parameters
277 std::vector
<std::string
> vecParams
= StringUtils::Split(strParams
, ",");
278 for (std::vector
<std::string
>::iterator i
= vecParams
.begin(); i
!= vecParams
.end(); ++i
)
280 // split the param, ensure there was an =
281 StringUtils::Trim(*i
);
282 std::vector
<std::string
> vecTuple
= StringUtils::Split(*i
, "=");
283 if (vecTuple
.size() < 2)
286 // remove white space from name and value and store it in the dictionary
287 StringUtils::Trim(vecTuple
[0]);
288 StringUtils::Trim(vecTuple
[1]);
289 params
[vecTuple
[0]] = vecTuple
[1];
295 } // namespace KODI::PLAYLIST