1 #include "stdafx.h"
2 #include "AudioCommon.h"
3 #include "CueFile.h"
4 #include "MusicPlayer2.h"
5 #include "SongDataManager.h"
6 #include "taglib/id3v1genres.h"
7 #include "SongInfoHelper.h"
8 #include "Lyric.h"
9 #include "AudioTag.h"
10 #include "FilePathHelper.h"
11 #include "COSUPlayerHelper.h"
12 #include "Player.h"
13
14
CreateExtensionsList()15 void SupportedFormat::CreateExtensionsList()
16 {
17 for (const auto& ext : extensions)
18 {
19 extensions_list += L"*.";
20 extensions_list += ext;
21 extensions_list += L';';
22 }
23 if (!extensions.empty())
24 extensions_list.pop_back();
25 }
26
27
28 //////////////////////////////////////////////////////////////////////////////
29 //////////////////////////////////////////////////////////////////////////////
30 //////////////////////////////////////////////////////////////////////////////
31 vector<SupportedFormat> CAudioCommon::m_surpported_format;
32 vector<wstring> CAudioCommon::m_all_surpported_extensions;
33
CAudioCommon()34 CAudioCommon::CAudioCommon()
35 {
36 }
37
38
~CAudioCommon()39 CAudioCommon::~CAudioCommon()
40 {
41 }
42
FileIsAudio(const wstring & file_name)43 bool CAudioCommon::FileIsAudio(const wstring & file_name)
44 {
45 CFilePathHelper file_path(file_name);
46 wstring extension{ file_path.GetFileExtension() }; //获取文件扩展名
47 for (const auto& ext : m_all_surpported_extensions) //判断文件扩展是否在支持的扩展名列表里
48 {
49 if (ext == extension)
50 return true;
51 }
52 return false;
53 }
54
GetAudioTypeByFileExtension(const wstring & type)55 AudioType CAudioCommon::GetAudioTypeByFileExtension(const wstring& type)
56 {
57 if (type == L"mp3" || type == L"mp2" || type == L"mp1")
58 return AU_MP3;
59 else if (type == L"wma" || type == L"asf")
60 return AU_WMA_ASF;
61 else if (type == L"ogg" || type == L"oga")
62 return AU_OGG;
63 else if (type == L"m4a" || type == L"mp4")
64 return AU_MP4;
65 else if (type == L"aac")
66 return AU_AAC;
67 else if (type == L"flac" || type == L"fla")
68 return AU_FLAC;
69 else if (type == L"cue")
70 return AU_CUE;
71 else if (type == L"ape" || type == L"mac")
72 return AU_APE;
73 else if (type == L"mid" || type == L"midi" || type == L"rmi" || type == L"kar")
74 return AU_MIDI;
75 else if (type == L"aif" || type == L"aiff")
76 return AU_AIFF;
77 else if (type == L"wav")
78 return AU_WAV;
79 else if (type == L"mpc" || type == L"mp+" || type == L"mpp")
80 return AU_MPC;
81 else if (type == L"dff" || type == L"dsf")
82 return AU_DSD;
83 else if (type == L"opus")
84 return AU_OPUS;
85 else if (type == L"wv")
86 return AU_WV;
87 else if (type == L"spx")
88 return AU_SPX;
89 else if (type == L"tta")
90 return AU_TTA;
91 else
92 return AU_OTHER;
93 }
94
GetAudioTypeByFileName(const wstring & file_name)95 AudioType CAudioCommon::GetAudioTypeByFileName(const wstring & file_name)
96 {
97 CFilePathHelper file_path(file_name);
98 wstring type{ file_path.GetFileExtension() }; //获取文件扩展名
99 return GetAudioTypeByFileExtension(type);
100 }
101
GetAudioDescriptionByExtension(wstring extension)102 wstring CAudioCommon::GetAudioDescriptionByExtension(wstring extension)
103 {
104 if (extension.empty())
105 return theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_UNKNOWN");
106
107 CCommon::StringTransform(extension, false);
108
109 for (const auto& item : m_surpported_format)
110 {
111 if (item.description != theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_BASE"))
112 {
113 for (const auto& ext : item.extensions)
114 {
115 if (ext == extension)
116 return item.description;
117 }
118 }
119 }
120 wstring audio_str;
121 if (extension == L"mp3")
122 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_MP3");
123 else if (extension == L"mp1" || extension == L"mp2")
124 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_MP1_MP2");
125 else if (extension == L"wma")
126 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_WMA");
127 else if (extension == L"asf")
128 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_ASF");
129 else if (extension == L"wav")
130 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_WAV");
131 else if (extension == L"ogg" || extension == L"oga")
132 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_OGG_OGA");
133 else if (extension == L"m4a" || extension == L"mp4")
134 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_M4A_MP4");
135 else if (extension == L"ape")
136 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_APE");
137 else if (extension == L"aac")
138 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_ACC");
139 else if (extension == L"aif" || extension == L"aiff")
140 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_AIF_AIFF");
141 else if (extension == L"cda")
142 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_CDA");
143 else if (extension == L"playlist")
144 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_PLAYLIST");
145 else if (extension == L"m3u")
146 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_M3U");
147 else if (extension == L"m3u8")
148 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_M3U8");
149 else if (extension == L"cue")
150 audio_str = theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_CUE");
151 else
152 audio_str = theApp.m_str_table.LoadTextFormat(L"TXT_FILE_TYPE_OTHER", { extension });
153 return audio_str;
154 }
155
GetAudioFiles(wstring path,vector<SongInfo> & files,size_t max_file,bool include_sub_dir)156 void CAudioCommon::GetAudioFiles(wstring path, vector<SongInfo>& files, size_t max_file, bool include_sub_dir)
157 {
158 if (path.empty())
159 return;
160 if (COSUPlayerHelper::IsOsuFolder(path))
161 {
162 COSUPlayerHelper::GetOSUAudioFiles(path, files);
163 return;
164 }
165 //文件句柄
166 intptr_t hFile = 0;
167 //文件信息(用Unicode保存使用_wfinddata_t,多字节字符集使用_finddata_t)
168 _wfinddata_t fileinfo;
169 SongInfo song_info;
170 if (path.back() != '\\' && path.back() != '/')
171 path.push_back('\\');
172 if ((hFile = _wfindfirst((path + L"\\*.*").c_str(), &fileinfo)) != -1)
173 {
174 do
175 {
176 if (files.size() >= max_file) break;
177 wstring file_name = fileinfo.name;
178 if (file_name == L"." || file_name == L"..")
179 continue;
180
181 if (CCommon::IsFolder(path + file_name)) //当前是文件夹,则递归调用
182 {
183 if (include_sub_dir)
184 GetAudioFiles(path + file_name, files, max_file, true);
185 }
186 else if (FileIsAudio(wstring(fileinfo.name))) //如果找到的文件是音频文件,则保存到容器中
187 {
188 //song_info.file_name = fileinfo.name;
189 song_info.file_path = path + fileinfo.name;
190 files.push_back(song_info);
191 }
192 }
193 while (_wfindnext(hFile, &fileinfo) == 0);
194 }
195 _findclose(hFile);
196 }
197
GetAudioFiles(wstring path,std::vector<std::wstring> & files,size_t max_file,bool include_sub_dir)198 void CAudioCommon::GetAudioFiles(wstring path, std::vector<std::wstring>& files, size_t max_file, bool include_sub_dir)
199 {
200 if (path.empty())
201 return;
202 if (COSUPlayerHelper::IsOsuFolder(path))
203 {
204 COSUPlayerHelper::GetOSUAudioFiles(path, files);
205 return;
206 }
207 //文件句柄
208 intptr_t hFile = 0;
209 //文件信息
210 _wfinddata_t fileinfo;
211 if (path.back() != '\\' && path.back() != '/')
212 path.push_back('\\');
213 if ((hFile = _wfindfirst((path + L"*").c_str(), &fileinfo)) != -1)
214 {
215 do
216 {
217 if (files.size() >= max_file) break;
218
219 wstring file_name = fileinfo.name;
220 if(file_name == L"." || file_name == L"..")
221 continue;
222
223 if (CCommon::IsFolder(path + file_name)) //当前是文件夹,则递归调用
224 {
225 if (include_sub_dir)
226 GetAudioFiles(path + file_name, files, max_file, true);
227 }
228 else
229 {
230 if (FileIsAudio(file_name)) //如果找到的文件是音频文件,则保存到容器中
231 files.push_back(path + file_name);
232 }
233 } while (_wfindnext(hFile, &fileinfo) == 0);
234 }
235 _findclose(hFile);
236 }
237
IsPathContainsAudioFile(std::wstring path,bool include_sub_dir)238 bool CAudioCommon::IsPathContainsAudioFile(std::wstring path, bool include_sub_dir /*= false*/)
239 {
240 if (COSUPlayerHelper::IsOsuFolder(path))
241 return true;
242 //文件句柄
243 intptr_t hFile = 0;
244 //文件信息
245 _wfinddata_t fileinfo;
246 if (path.back() != '\\' && path.back() != '/')
247 path.push_back('\\');
248 if ((hFile = _wfindfirst((path + L"*").c_str(), &fileinfo)) != -1)
249 {
250 do
251 {
252 //if (files.size() >= max_file) break;
253
254 wstring file_name = fileinfo.name;
255 if (file_name == L"." || file_name == L"..")
256 continue;
257
258 if (CCommon::IsFolder(path + file_name)) //当前是文件夹,则递归调用
259 {
260 if (include_sub_dir)
261 {
262 if (IsPathContainsAudioFile(path + file_name, include_sub_dir))
263 {
264 _findclose(hFile);
265 return true;
266 }
267 }
268 }
269 else
270 {
271 if (FileIsAudio(file_name)) //如果找到了音频文件,返回true
272 {
273 _findclose(hFile);
274 return true;
275 }
276 }
277 } while (_wfindnext(hFile, &fileinfo) == 0);
278 }
279 _findclose(hFile);
280 return false;
281 }
282
GetLyricFiles(wstring path,vector<wstring> & files)283 void CAudioCommon::GetLyricFiles(wstring path, vector<wstring>& files)
284 {
285 //文件句柄
286 intptr_t hFile = 0;
287 //文件信息(用Unicode保存使用_wfinddata_t,多字节字符集使用_finddata_t)
288 _wfinddata_t fileinfo;
289 //wstring file_path;
290 if ((hFile = _wfindfirst((path + L"*").c_str(), &fileinfo)) != -1)
291 {
292 do
293 {
294 wstring file_name = fileinfo.name;
295 if (CLyrics::FileIsLyric(file_name) && !CCommon::IsFolder(path + file_name)) // 如果找到的文件是歌词文件,则保存到容器中
296 files.push_back(file_name);
297 }
298 while (_wfindnext(hFile, &fileinfo) == 0);
299 }
300 _findclose(hFile);
301 }
302
303
CheckCueAudioPath(vector<SongInfo> & track_from_text)304 void CAudioCommon::CheckCueAudioPath(vector<SongInfo>& track_from_text)
305 {
306 // file_path如果不存在那么试着模糊匹配一个音频文件,没能成功找到时清空file_path
307 wstring audio_path; // 存储上一个存在的song的音频文件(正常情况下用来避免反复CCommon::FileExist)
308 for (SongInfo& song : track_from_text) // track_from_text是CCueFile给出的文本解析结果
309 {
310 // 如果循环开始时audio_path不为空说明上次已确认此文件存在
311 if (!audio_path.empty() && audio_path == song.file_path)
312 continue;
313 // if (CCommon::FileExist(song.file_path) // 当cue内FILE与实际音频文件名大小写不符时以cue为准
314 if (CCommon::CheckAndFixFile(song.file_path)) // 严格检查大小写,如果不正确则修正(以音频文件为准)
315 {
316 audio_path = song.file_path; // 更新audio_path
317 continue;
318 }
319 // 试着修正文件不存在的file_path(这是可选的,但如果切换是否启用那些涉及此功能的现有条目会出问题)
320 if (!song.file_path.empty() && true)
321 {
322 // 文件不存在,以下开始模糊匹配
323 auto GetFirstMatchAudioAndFix = [&](const wstring& path_mode, const wstring& dir)->bool {
324 bool succeed = false;
325 intptr_t hFile = 0; // 文件句柄
326 _wfinddata_t fileinfo; //文件信息(用Unicode保存使用_wfinddata_t,多字节字符集使用_finddata_t)
327 if ((hFile = _wfindfirst(path_mode.c_str(), &fileinfo)) != -1)
328 {
329 do
330 {
331 wstring name{ fileinfo.name };
332 if (CFilePathHelper(name).GetFileExtension() != L"cue" && FileIsAudio(name))
333 {
334 song.file_path = dir + name;
335 succeed = true;
336 break;
337 }
338 } while (_wfindnext(hFile, &fileinfo) == 0);
339 }
340 _findclose(hFile);
341 return succeed;
342 };
343 // 匹配任意格式的音频文件
344 CFilePathHelper path(song.file_path);
345 if (GetFirstMatchAudioAndFix(path.ReplaceFileExtension(L"*"), path.GetDir()))
346 continue;
347 // 匹配与cue同名的任意格式音频文件
348 path.SetFilePath(song.cue_file_path);
349 if (GetFirstMatchAudioAndFix(path.ReplaceFileExtension(L"*"), path.GetDir()))
350 continue;
351 // 处理cue有表示语言的双重后缀的情况,例如“filename.jp.cue”匹配“filename.*”的音频文件
352 path.SetFilePath(path.GetDir() + path.GetFileNameWithoutExtension()); // 将path设置为不含“.cue”的cue路径
353 if (GetFirstMatchAudioAndFix(path.ReplaceFileExtension(L"*"), path.GetDir()))
354 continue;
355 // 再进行一次
356 path.SetFilePath(path.GetDir() + path.GetFileNameWithoutExtension());
357 if (GetFirstMatchAudioAndFix(path.ReplaceFileExtension(L"*"), path.GetDir()))
358 continue;
359 }
360 // 标记此cue解析得到的SongInfo没有音频文件
361 song.file_path.clear();
362 }
363 }
364
GetCueTracks(vector<SongInfo> & files,int & update_cnt,bool & exit_flag,MediaLibRefreshMode refresh_mode)365 void CAudioCommon::GetCueTracks(vector<SongInfo>& files, int& update_cnt, bool& exit_flag, MediaLibRefreshMode refresh_mode)
366 {
367 // 收集files内所有与cue相关的项目信息,wstring为cue路径,vector为此cue中存在于files的下辖曲目
368 map<wstring, vector<SongInfo>> cue_file_tmp;
369 for (const SongInfo& song : files)
370 {
371 if (exit_flag) return;
372 // ASSERT(!song.file_path.empty()); // 等到m_playlist可空以后加回来
373 if (song.file_path.empty()) continue;
374 if (song.is_cue)
375 {
376 // 设置了合适“媒体库目录”之后“强制重新加载”可以使得媒体库能够转换旧播放列表到新格式
377 SongInfo song_info{ CSongDataManager::GetInstance().GetSongInfo3(song) };
378 if (!song_info.cue_file_path.empty()) // 如果媒体库内仍然没有cue_file_path那么什么也不会做(和之前一样)
379 cue_file_tmp[song_info.cue_file_path].push_back(std::move(song_info));
380 }
381 else if (CFilePathHelper(song.file_path).GetFileExtension() == L"cue")
382 {
383 auto& a = cue_file_tmp[song.file_path]; // 此处file_path为cue路径
384 a.insert(a.begin(), song); // is_cue为false的此项存在于开头说明添加此cue全部track
385 }
386 /* 不再支持内嵌cue,主要是架构问题现有很多代码都不适于内嵌cue需要加写特殊处理 (CCueFile也没有准备好支持内嵌cue等等等)
387 else
388 {
389 // 支持内嵌cue影响文件夹模式的快速启动(当文件夹中有大量ape时),我认为只能二选一
390 // 与refresh_mode的MR_MIN_REQUIRED有冲突 属性编辑等功能也没有支持内嵌cue
391 CAudioTag audio_tag(song.file_path);
392 wstring song_cue_text{ audio_tag.GetAudioCue() };// 对于ape文件即使是固态硬盘这步也非常慢(不适合对所有音频遍历执行)
393 if (!song_cue_text.empty()) // 如果音频有内嵌cue那么将其加入cue_file_tmp
394 {
395 auto& a = cue_file_tmp[song.file_path]; // 此处file_path为cue路径
396 a.insert(a.begin(), song); // is_cue为false的此项存在于开头说明添加此cue全部track
397 }
398 }
399 */
400 }
401
402 struct A_Cue_File
403 {
404 wstring cue_path;
405 unsigned __int64 modified_time{};
406 vector<SongInfo> tracks_in_cue;
407 vector<SongInfo> tracks_in_files;
408 };
409 vector<A_Cue_File> cue_file;
410 map<wstring, map<int, SongInfo>> audio_file;
411 for (auto& [cue_path, tracks_in_files] : cue_file_tmp)
412 {
413 if (exit_flag) return;
414 audio_file[cue_path]; // 在audio_file中插入cue_path键,以免文件不存在或解析结果为空导致最后的时候cue原始文件没有从files中移除
415 unsigned __int64 modified_time{};
416 // 确认cue文件是否存在(同时获取修改时间)
417 if (!CCommon::GetFileLastModified(cue_path, modified_time) || tracks_in_files.empty())
418 continue;
419 ASSERT(CFilePathHelper(cue_path).GetFileExtension() == L"cue"); // CCueFile只接受cue文件
420 // CCueFile暂时不支持内嵌cue有待修改,(这里需要内嵌cue的GetAnalysisResult返回SongInfo中cue_file_path项为音频路径)
421 CCueFile cue_analysis(cue_path);
422 vector<SongInfo> tracks_in_cue;
423 cue_analysis.MoveToSongList(tracks_in_cue);
424 // 试着检查&更正音频文件路径,将文件不存在的song.file_path清空,参数是一个cue的文件解析结果
425 CheckCueAudioPath(tracks_in_cue);
426 // 移除结果中file_path为空的项目
427 std::erase_if(tracks_in_cue, [&](const SongInfo& song_info) { return song_info.file_path.empty(); });
428 if (!tracks_in_cue.empty())
429 cue_file.emplace_back(A_Cue_File{ cue_path, modified_time, std::move(tracks_in_cue), std::move(tracks_in_files) });
430 }
431 std::stable_sort(cue_file.begin(), cue_file.end(),
432 [](const A_Cue_File& a, const A_Cue_File& b) { return a.modified_time < b.modified_time; });
433 // 此处是按修改时间升序遍历,也就是每个SongKey曲目若同时被多个cue描述则最后留在媒体库的版本会是修改时间最大的那个cue
434 // 这使得不修改任何PT文件直接建立自己的新cue重新描述音轨是可能的
435 for (A_Cue_File& item : cue_file)
436 {
437 if (exit_flag) return;
438 for (auto it = files.begin(); it != files.end();)
439 {
440 bool in_files = std::find_if(item.tracks_in_files.begin(), item.tracks_in_files.end(),
441 [&](const SongInfo& song_info) { return song_info.IsSameSong(*it); }) != item.tracks_in_files.end();
442 bool in_cue = std::find_if(item.tracks_in_cue.begin(), item.tracks_in_cue.end(),
443 [&](const SongInfo& song_info) { return song_info.IsSameSong(*it); }) != item.tracks_in_cue.end();
444 bool remove_it{}, add_all{};
445 if (!item.tracks_in_files.front().is_cue) // 如果存在原始cue
446 {
447 remove_it = (in_cue || in_files); // 删除files内所有与此cue相关条目(存在多个cue时这也会移除之前cue的同SongKey条目)
448 add_all = (in_files && !it->is_cue); // 特别的,对于cue原始文件将其代换为tracks_in_cue
449 }
450 else if (in_files && !in_cue) // 没有原始cue,此时一般来说不用处理
451 { // 除了这个特殊情况,it存在于files却不存在于tracks_in_cue
452 auto fixed_track = std::find_if(item.tracks_in_cue.begin(), item.tracks_in_cue.end(),
453 [&](const SongInfo& song_info) { return song_info.track == it->track; });
454 if (fixed_track != item.tracks_in_cue.end()) // 找到了同trackSongInfo说明cue有修改,其更换了音频文件
455 it->file_path = fixed_track->file_path; // 更新音频文件路径
456 else // 或此cue已不再有此track
457 remove_it = true; // 移除it
458 }
459 if (remove_it)
460 it = files.erase(it); // erase返回指向下一个元素的迭代器
461 if (add_all) // 这里为insert返回迭代器加 tracks_in_cue.size() 使其指向下一个元素
462 it = files.insert(it, item.tracks_in_cue.begin(), item.tracks_in_cue.end()) + item.tracks_in_cue.size();
463 if (!remove_it && !add_all) // 没有erase/insert时直接自增使其指向下一个元素
464 ++it;
465 }
466 for (SongInfo& new_track : item.tracks_in_cue)
467 {
468 new_track.modified_time = item.modified_time;
469 audio_file[new_track.file_path][new_track.track] = new_track;
470 }
471 }
472 // audio_file结构上已对SongInfo去重,audio_file的第一层key即cue文件的FILE标签,第二层key是此FILE下包含哪些track
473 for (auto& [audio_path, item] : audio_file)
474 {
475 if (exit_flag) return;
476 bool need_get_info{ refresh_mode == MR_FOECE_FULL };
477 for (auto& [track, song] : item)
478 {
479 if (need_get_info) break;
480 const SongInfo& song_info = CSongDataManager::GetInstance().GetSongInfo(song); // 这个song_info仅用来确认是否必须刷新
481 need_get_info |= (song_info.modified_time == 0 || !song_info.info_acquired || !song_info.ChannelInfoAcquired());
482 // 对cue即使是MR_MIN_REQUIRED也确认修改时间,因为此时cue已获取修改时间,这不会增加额外耗时
483 // if (refresh_mode == MR_MIN_REQUIRED) continue;
484 // 使用cue修改时间作为SongInfo修改时间(无法发现音频文件需要更新)(但按修改时间排序时比较合适,如需音频修改时间再另加变量)
485 need_get_info |= (song.modified_time != song_info.modified_time);
486 }
487 if (!need_get_info || item.empty())
488 continue;
489
490 SongInfo& last_song = item.rbegin()->second;
491 Time file_length{};
492 std::swap(last_song.end_pos, file_length);
493 // IPlayerCore::GetAudioInfo只能用于每个FILE的最后一个音轨(会把音频时长直接写入end_pos)(这是预定行为,一个cue可以含有多个FILE)
494 int flag = AF_LENGTH | AF_BITRATE | AF_CHANNEL_INFO;
495 CPlayer::GetInstance().GetPlayerCore()->GetAudioInfo(audio_path.c_str(), last_song, flag);
496 std::swap(last_song.end_pos, file_length);
497
498 for (auto& [track, song] : item) // 这个遍历也包括last_song自身
499 {
500 // FILE可能在此次更新中由支持的文件变为不支持的文件所以即使获取时长失败以下项目也总是更新
501 song.bitrate = last_song.bitrate;
502 song.freq = last_song.freq;
503 song.bits = last_song.bits;
504 song.channels = last_song.channels;
505 // 获取时长失败则标记此FILE下所有音轨(包括last_song)为无效条目
506 if (file_length.isZero())
507 song.end_pos = song.start_pos;
508 else
509 {
510 if (song.end_pos > file_length || song.end_pos.isZero())
511 song.end_pos = file_length;
512 if (song.start_pos > song.end_pos)
513 song.end_pos = song.start_pos;
514 }
515 }
516 // 更新item中的信息到媒体库
517 vector<SongInfo> tmp;
518 std::transform(item.begin(), item.end(), std::back_inserter(tmp), [](auto& it) { return std::move(it.second); });
519 CSongDataManager::GetInstance().SaveCueSongInfo(tmp);
520 update_cnt += item.size();
521 }
522 // 移除files中的cue关联原始音频文件条目(在这之后才能进行普通音频的处理以避免cue关联音轨进入媒体库)
523 if (!audio_file.empty())
524 std::erase_if(files, [&](const SongInfo& song_info) { return !song_info.is_cue && audio_file.contains(song_info.file_path); });
525 }
526
GetAudioInfo(vector<SongInfo> & files,int & update_cnt,bool & exit_flag,int & process_percent,MediaLibRefreshMode refresh_mode,bool ignore_short)527 void CAudioCommon::GetAudioInfo(vector<SongInfo>& files, int& update_cnt, bool& exit_flag, int& process_percent, MediaLibRefreshMode refresh_mode, bool ignore_short)
528 {
529 // GetCueTracks计算进度太难,直接为其分配5%进度
530 process_percent = 5;
531 GetCueTracks(files, update_cnt, exit_flag, refresh_mode);
532 int file_too_short_ms{ theApp.m_media_lib_setting_data.file_too_short_sec * 1000 };
533 unsigned int process_cnt{}, process_all = max(files.size(), 1); // 防止除0
534 std::set<wstring> too_short_remove;
535 for (const SongInfo& song : files)
536 {
537 if (exit_flag) return;
538 process_percent = ++process_cnt * 95 / process_all + 5;
539 // ASSERT(!song.file_path.empty()); // 等到m_playlist可空以后加回来
540 if (song.is_cue || song.file_path.empty())
541 continue;
542 SongInfo song_info{ CSongDataManager::GetInstance().GetSongInfo3(song) };
543 // 这里的info_acquired和ChannelInfoAcquired是旧版兼容,当时程序不读取这些信息故设置标志位触发更新
544 bool need_get_info{ song_info.modified_time == 0 || !song_info.info_acquired || !song_info.ChannelInfoAcquired() };
545 if (refresh_mode == MR_MIN_REQUIRED && !need_get_info)
546 continue;
547 unsigned __int64 modified_time{};
548 if (!CCommon::GetFileLastModified(song_info.file_path, modified_time)) // 跳过当前不存在的文件
549 continue;
550 if (refresh_mode != MR_FOECE_FULL && song_info.modified_time == modified_time && !need_get_info)
551 continue;
552 song_info.modified_time = modified_time;
553
554 int flag = AF_LENGTH | AF_BITRATE | AF_CHANNEL_INFO;
555 if (COSUPlayerHelper::IsOsuFile(song_info.file_path))
556 COSUPlayerHelper::GetOSUAudioTitleArtist(song_info);
557 else
558 flag |= AF_TAG_INFO; // 原来在这里获取“分级rating”,更改到AF_TAG_INFO中(条件一致)
559 CPlayer::GetInstance().GetPlayerCore()->GetAudioInfo(song_info.file_path.c_str(), song_info, flag);
560
561 if (ignore_short && song_info.length().toInt() < file_too_short_ms)
562 too_short_remove.insert(song_info.file_path);
563 else
564 {
565 song_info.info_acquired = true;
566 song_info.SetChannelInfoAcquired(true);
567 CSongDataManager::GetInstance().AddItem(song_info);
568 ++update_cnt;
569 }
570 }
571 // 移除files中过短的音频文件
572 if (!too_short_remove.empty())
573 {
574 auto new_end = std::remove_if(files.begin(), files.end(),
575 [&](const SongInfo& song_info) { return !song_info.is_cue && too_short_remove.find(song_info.file_path) != too_short_remove.end(); });
576 files.erase(new_end, files.end());
577 }
578 }
579
580
GetGenre(BYTE genre)581 wstring CAudioCommon::GetGenre(BYTE genre)
582 {
583 //if (genre < GENRE_MAX)
584 // return GENRE_TABLE[genre];
585 //else
586 // return wstring();
587 return TagLib::ID3v1::genre(genre).toWString();
588 }
589
EmulateGenre(std::function<void (const wstring &)> fun,bool sort)590 void CAudioCommon::EmulateGenre(std::function<void(const wstring&)> fun, bool sort)
591 {
592 if (sort)
593 {
594 auto genre_map = TagLib::ID3v1::genreMap();
595 for (const auto& item : genre_map)
596 {
597 wstring genre_str = item.first.toWString();
598 fun(genre_str);
599 }
600 }
601 else
602 {
603 wstring genre_str{};
604 for (int i{}; i < 256; i++)
605 {
606 genre_str = GetGenre(i);
607 if (genre_str.empty())
608 break;
609 fun(genre_str);
610 }
611
612 }
613 }
614
GenreIndex(const wstring & genre)615 int CAudioCommon::GenreIndex(const wstring& genre)
616 {
617 return TagLib::ID3v1::genreIndex(genre);
618 }
619
GenreConvert(wstring genre)620 wstring CAudioCommon::GenreConvert(wstring genre)
621 {
622 if(genre.empty())
623 return wstring();
624 if (genre[0] == L'(') //如果前后有括号,则删除括号
625 genre = genre.substr(1);
626 if (genre.back() == L')')
627 genre.pop_back();
628 if (CCommon::StrIsNumber(genre)) //如果流派信息是数字,则转换为标准流派信息
629 {
630 int n_genre = _wtoi(genre.c_str());
631 if(n_genre < 256)
632 return GetGenre(static_cast<BYTE>(n_genre));
633 }
634 return genre;
635 }
636
637
TagStrNormalize(wstring & str)638 void CAudioCommon::TagStrNormalize(wstring & str)
639 {
640 str.erase(std::remove(str.begin(), str.end(), '\r'), str.end());
641 str.erase(std::remove(str.begin(), str.end(), '\n'), str.end());
642
643 for (size_t i{}; i < str.size(); i++)
644 {
645 if (str[i] < 32 || str[i] >= static_cast<wchar_t>(0xfff0))
646 {
647 str = str.substr(0, i);
648 return;
649 }
650 }
651 }
652
GetBASSChannelDescription(DWORD ctype)653 wstring CAudioCommon::GetBASSChannelDescription(DWORD ctype)
654 {
655 switch (ctype)
656 {
657 case 0:
658 return theApp.m_str_table.LoadText(L"TXT_FILE_TYPE_UNKNOWN"); // 这里使用的是文件类型描述(File type description)的 “未知”的字符串
659 case 1:
660 return L"SAMPLE";
661 case 2:
662 return L"RECORD";
663 case 0x10000:
664 return L"STREAM";
665 case 0x10002:
666 return L"OGG";
667 case 0x10003:
668 return L"MP1";
669 case 0x10004:
670 return L"MP2";
671 case 0x10005:
672 return L"MP3";
673 case 0x10006:
674 return L"AIFF";
675 case 0x10007:
676 return L"CA";
677 case 0x10008:
678 return L"MF";
679 case 0x10009:
680 return L"AM";
681 case 0x18000:
682 return L"DUMMY";
683 case 0x18001:
684 return L"DEVICE";
685 case 0x40000:
686 return L"WAV";
687 case 0x50001:
688 return L"WAV_PCM";
689 case 0x50003:
690 return L"WAV_FLOAT";
691 case 0x20000:
692 return L"MOD";
693 case 0x20001:
694 return L"MTM";
695 case 0x20002:
696 return L"S3M";
697 case 0x20003:
698 return L"XM";
699 case 0x20004:
700 return L"IT";
701 case 0x00100:
702 return L"MO3";
703 case 0x10e00:
704 return L"ALAC";
705 case 0x10200:
706 return L"CD";
707 case 0x10900:
708 return L"FLAC";
709 case 0x10901:
710 return L"FLAC_OGG";
711 case 0x10d00:
712 return L"MIDI";
713 case 0x10300:
714 return L"WMA";
715 case 0x10301:
716 return L"WMA_MP3";
717 case 0x10500:
718 return L"WV";
719 case 0x10b00:
720 return L"AAC";
721 case 0x10b01:
722 return L"MP4";
723 case 0x10700:
724 return L"APE";
725 case 0x10a00:
726 return L"MPC";
727 case 0x11700:
728 return L"DSD";
729 case 0x11200:
730 return L"OPUS";
731 case 0x10c00:
732 return L"SPX";
733 case 0x10f00:
734 return L"TTA";
735 default:
736 return wstring();
737 }
738 }
739
GetAudioTypeByBassChannel(DWORD ctype)740 AudioType CAudioCommon::GetAudioTypeByBassChannel(DWORD ctype)
741 {
742 AudioType type;
743 switch (ctype)
744 {
745 case BASS_CTYPE_STREAM_MP1:
746 case BASS_CTYPE_STREAM_MP2:
747 case BASS_CTYPE_STREAM_MP3:
748 type = AudioType::AU_MP3;
749 break;
750 case BASS_CTYPE_STREAM_WAV:
751 case BASS_CTYPE_STREAM_WAV_PCM:
752 case BASS_CTYPE_STREAM_WAV_FLOAT:
753 type = AudioType::AU_WAV;
754 break;
755 case 0x10300:
756 case 0x10301:
757 type = AudioType::AU_WMA_ASF;
758 break;
759 case BASS_CTYPE_STREAM_AIFF:
760 type = AudioType::AU_AIFF;
761 break;
762 case BASS_CTYPE_STREAM_OGG:
763 type = AudioType::AU_OGG;
764 break;
765 case 0x10b01:
766 type = AudioType::AU_MP4;
767 break;
768 case 0x10b00:
769 type = AudioType::AU_AAC;
770 break;
771 case 0x10900:
772 case 0x10901:
773 type = AudioType::AU_FLAC;
774 break;
775 case 0x10d00:
776 type = AudioType::AU_MIDI;
777 break;
778 case 0x10700:
779 type = AudioType::AU_APE;
780 break;
781 case 0x10a00:
782 type = AudioType::AU_MPC;
783 break;
784 case 0x11700:
785 type = AudioType::AU_DSD;
786 break;
787 case 0x11200:
788 type = AudioType::AU_OPUS;
789 break;
790 case 0x10500:
791 type = AudioType::AU_WV;
792 break;
793 case 0x10c00:
794 type = AudioType::AU_SPX;
795 break;
796 case 0x10f00:
797 type = AudioType::AU_TTA;
798 break;
799 default:
800 type = AudioType::AU_OTHER;
801 break;
802 }
803 return type;
804 }
805
TrackToString(BYTE track)806 CString CAudioCommon::TrackToString(BYTE track)
807 {
808 CString str;
809 if (track != 0)
810 {
811 str.Format(_T("%d"), track);
812 return str;
813 }
814 else
815 {
816 return CString();
817 }
818 }
819
CreateSupportedFormat(const wchar_t * exts,const wchar_t * description,const wchar_t * file_name)820 SupportedFormat CAudioCommon::CreateSupportedFormat(const wchar_t* exts, const wchar_t* description, const wchar_t* file_name /*= L""*/)
821 {
822 SupportedFormat format;
823 format.description = description;
824 format.file_name = file_name;
825 CCommon::StringSplit(std::wstring(exts), L' ', format.extensions);
826 format.CreateExtensionsList();
827 return format;
828 }
829
CreateSupportedFormat(const std::vector<std::wstring> & exts,const wchar_t * description,const wchar_t * file_name)830 SupportedFormat CAudioCommon::CreateSupportedFormat(const std::vector<std::wstring>& exts, const wchar_t* description, const wchar_t* file_name /*= L""*/)
831 {
832 SupportedFormat format;
833 format.description = description;
834 format.file_name = file_name;
835 format.extensions = exts;
836 format.CreateExtensionsList();
837 return format;
838
839 }
840