xref: /MusicPlayer2/MusicPlayer2/Playlist.cpp (revision 965ce478a79b0d21e8a6e2ade0490efa175855dd)
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