1 #include "stdafx.h"
2 #include "Playlist.h"
3 #include "Common.h"
4 #include "FilePathHelper.h"
5 #include "SongDataManager.h"
6
7 const vector<wstring> CPlaylistFile::m_surpported_playlist{ PLAYLIST_EXTENSION_2, L"m3u", L"m3u8" };
8
9 /*
10 播放列表文件格式说明
11 每行一个曲目,每一行的格式为:
12 文件路径|是否为cue音轨|cue音轨起始时间|cue音轨结束时间|标题|艺术家|唱片集|曲目序号|比特率|流派|年份|注释|cue文件路径
13 播放列表至少要保存能够在song_data.dat清空时原样恢复特定歌曲的项目
14 目前除了cue音轨外,其他曲目只保存文件路径
15 列表cue条目必要保存的项目有“文件路径”、“音轨号”、“cue文件路径”,但出于向后兼容考虑仍然保留其他项目(实际上不以这些项目为准,外部编辑会被忽略)
16 */
17
CPlaylistFile()18 CPlaylistFile::CPlaylistFile()
19 {
20 }
21
22
~CPlaylistFile()23 CPlaylistFile::~CPlaylistFile()
24 {
25 }
26
DeleteInvalidCh(const wstring & str)27 wstring DeleteInvalidCh(const wstring& str)
28 {
29 wstring result = str;
30 CCommon::StringCharacterReplace(result, L'|', L'_');
31 return result;
32 }
33
LoadFromFile(const wstring & file_path)34 void CPlaylistFile::LoadFromFile(const wstring & file_path)
35 {
36 m_path = file_path;
37 ifstream stream{ file_path };
38 if (stream.fail())
39 return;
40
41 //判断文件编码
42 bool utf8{};
43 wstring file_extension = CFilePathHelper(file_path).GetFileExtension();
44 utf8 = (file_extension != L"m3u");
45
46 string current_line;
47 while (!stream.eof())
48 {
49 std::getline(stream, current_line);
50 DisposePlaylistFileLine(current_line, utf8);
51 }
52 stream.close();
53 }
54
SaveToFile(const wstring & file_path,Type type) const55 void CPlaylistFile::SaveToFile(const wstring& file_path, Type type) const
56 {
57 SavePlaylistToFile(m_playlist, file_path, type);
58 }
59
SavePlaylistToFile(const vector<SongInfo> & song_list,const wstring & file_path,Type type)60 void CPlaylistFile::SavePlaylistToFile(const vector<SongInfo>& song_list, const wstring& file_path, Type type)
61 {
62 ofstream stream{ file_path };
63 if (!stream.is_open())
64 return;
65 if (type == PL_PLAYLIST)
66 {
67 for (const auto& item : song_list)
68 {
69 if (item.file_path.empty()) continue; // 不保存没有音频路径的项目
70 stream << CCommon::UnicodeToStr(item.file_path, CodeType::UTF8_NO_BOM);
71 if (item.is_cue)
72 {
73 // 出于向后兼容考虑必要这行代码,当song_list来自LoadFromFile加载的不记录cue_file_path的播放列表时item需要从媒体库加载cue_file_path
74 SongInfo song = CSongDataManager::GetInstance().GetSongInfo3(item); // 从媒体库载入数据,媒体库不存在的话会原样返回item
75 CString buff;
76 buff.Format(L"|%d|%d|%d|%s|%s|%s|%d|%d|%s|%s|%s|%s", song.is_cue, song.start_pos.toInt(), song.end_pos.toInt(),
77 DeleteInvalidCh(song.title).c_str(), DeleteInvalidCh(song.artist).c_str(), DeleteInvalidCh(song.album).c_str(),
78 song.track, song.bitrate,
79 DeleteInvalidCh(song.genre).c_str(), DeleteInvalidCh(song.get_year()).c_str(), DeleteInvalidCh(song.comment).c_str(),
80 song.cue_file_path.c_str()
81 );
82 stream << CCommon::UnicodeToStr(buff.GetString(), CodeType::UTF8_NO_BOM);
83 }
84 stream << "\n"; // 使用std::endl会触发flush影响效率
85 }
86 }
87 else if (type == PL_M3U || type == PL_M3U8)
88 {
89 CodeType code_type{ CodeType::ANSI };
90 if (type == PL_M3U8)
91 code_type = CodeType::UTF8_NO_BOM;
92
93 stream << "#EXTM3U" << '\n';
94 std::set<std::wstring> saved_cue_path; //已经保存过的cue文件的路径
95 for (const auto& item : song_list)
96 {
97 if (item.file_path.empty()) continue; // 不保存没有音频路径的项目
98 // song_list可能来自LoadFromFile含有信息不足,此处先从媒体库载入最新数据,媒体库不存在的话会原样返回item
99 SongInfo song = CSongDataManager::GetInstance().GetSongInfo3(item);
100 if (song.is_cue)
101 {
102 //如果播放列表中的项目是cue,且该cue文件没有保存过,则将其保存
103 if (!song.cue_file_path.empty() && saved_cue_path.find(song.cue_file_path) == saved_cue_path.end())
104 {
105 stream << "#" << '\n';
106 stream << CCommon::UnicodeToStr(song.cue_file_path, code_type) << '\n';
107 saved_cue_path.insert(song.cue_file_path);
108 }
109 }
110 else
111 {
112 CString buff;
113 buff.Format(_T("#EXTINF:%d,%s - %s"), song.length().toInt() / 1000, song.GetArtist().c_str(), song.GetTitle().c_str());
114 stream << CCommon::UnicodeToStr(buff.GetString(), code_type) << '\n';
115 stream << CCommon::UnicodeToStr(song.file_path, code_type) << '\n';
116 }
117 }
118 }
119 stream.close();
120 }
121
GetPlaylist() const122 const vector<SongInfo>& CPlaylistFile::GetPlaylist() const
123 {
124 return m_playlist;
125 }
126
AddSongsToPlaylist(const vector<SongInfo> & songs,bool insert_begin)127 int CPlaylistFile::AddSongsToPlaylist(const vector<SongInfo>& songs, bool insert_begin)
128 {
129 int added{};
130 for (const auto& file : songs)
131 {
132 if (CCommon::IsItemInVector(m_playlist, [&](const SongInfo& song) { return song.IsSameSong(file); }))
133 continue;
134 m_playlist.push_back(file);
135 ++added;
136 }
137 if (insert_begin) // 使用循环旋转将新增条目移动到开头而不是直接插入到开头,可对参数songs去重
138 std::rotate(m_playlist.rbegin(), m_playlist.rbegin() + added, m_playlist.rend());
139 return added;
140 }
141
MoveToSongList(vector<SongInfo> & song_list)142 void CPlaylistFile::MoveToSongList(vector<SongInfo>& song_list)
143 {
144 song_list = std::move(m_playlist);
145 }
146
IsSongInPlaylist(const SongInfo & song)147 bool CPlaylistFile::IsSongInPlaylist(const SongInfo& song)
148 {
149 return GetSongIndexInPlaylist(song) != -1;
150 }
151
GetSongIndexInPlaylist(const SongInfo & song)152 int CPlaylistFile::GetSongIndexInPlaylist(const SongInfo& song)
153 {
154 auto iter = std::find_if(m_playlist.begin(), m_playlist.end(), [&song](const SongInfo& item)
155 {
156 return song.IsSameSong(item);
157 });
158 if (iter != m_playlist.end())
159 return iter - m_playlist.begin();
160 else
161 return -1;
162 }
163
RemoveSong(const SongInfo & song)164 void CPlaylistFile::RemoveSong(const SongInfo& song)
165 {
166 std::erase_if(m_playlist, [&](const SongInfo& item) { return song.IsSameSong(item); });
167 }
168
IsPlaylistFile(const wstring & file_path)169 bool CPlaylistFile::IsPlaylistFile(const wstring& file_path)
170 {
171 wstring file_extension = CFilePathHelper(file_path).GetFileExtension();
172 return CCommon::IsItemInVector(m_surpported_playlist, file_extension);
173 }
174
IsPlaylistExt(wstring ext)175 bool CPlaylistFile::IsPlaylistExt(wstring ext)
176 {
177 if (ext.empty())
178 return false;
179 if (ext.front() == L'.')
180 ext = ext.substr(1);
181 return CCommon::IsItemInVector(m_surpported_playlist, ext);
182 }
183
DisposePlaylistFileLine(const string & str_current_line,bool utf8)184 void CPlaylistFile::DisposePlaylistFileLine(const string& str_current_line, bool utf8)
185 {
186 if (str_current_line.substr(0, 7) == "#EXTM3U" || str_current_line.substr(0, 7) == "#EXTINF")
187 return;
188
189 string current_line = str_current_line;
190 CCommon::DeleteStringBom(current_line);
191 if (!current_line.empty() && current_line.front() == '\"')
192 current_line = current_line.substr(1);
193 if (!current_line.empty() && current_line.back() == '\"')
194 current_line.pop_back();
195
196 if (current_line.size() > 3)
197 {
198 SongInfo item;
199 wstring current_line_wcs = CCommon::StrToUnicode(current_line, utf8 ? CodeType::UTF8 : CodeType::ANSI);
200 size_t index = current_line_wcs.find(L'|');
201 item.file_path = current_line_wcs.substr(0, index);
202
203 //如果是URL,则直接添加
204 if (CCommon::IsURL(item.file_path))
205 {
206 m_playlist.push_back(item);
207 }
208 else
209 {
210 //如果是相对路径,则转换成绝对路径
211 item.file_path = CCommon::RelativePathToAbsolutePath(item.file_path, CFilePathHelper(m_path).GetDir());
212
213 if (index < current_line_wcs.size() - 1)
214 {
215 vector<wstring> result;
216 CCommon::StringSplit(current_line_wcs, L'|', result, false);
217 if (result.size() >= 2)
218 item.is_cue = (_wtoi(result[1].c_str()) != 0);
219 if (result.size() >= 3)
220 item.start_pos.fromInt(_wtoi(result[2].c_str()));
221 if (result.size() >= 4)
222 item.end_pos.fromInt(_wtoi(result[3].c_str()));
223 //item.lengh = item.end_pos - item.start_pos;
224 if (result.size() >= 5)
225 item.title = result[4];
226 if (result.size() >= 6)
227 item.artist = result[5];
228 if (result.size() >= 7)
229 item.album = result[6];
230 if (result.size() >= 8)
231 item.track = _wtoi(result[7].c_str());
232 if (result.size() >= 9)
233 item.bitrate = _wtoi(result[8].c_str());
234 if (result.size() >= 10)
235 item.genre = result[9];
236 if (result.size() >= 11)
237 item.SetYear(result[10].c_str());
238 if (result.size() >= 12)
239 item.comment = result[11];
240 if (result.size() >= 13)
241 item.cue_file_path = result[12];
242 }
243 if (CCommon::IsPath(item.file_path)) // 绝对路径的语法检查
244 {
245 m_playlist.push_back(item);
246 }
247 }
248 }
249 }
250