xref: /MusicPlayer2/MusicPlayer2/AudioCommon.cpp (revision b02ea78914f3f3cea834525e3ae2052cd91d1e6d)
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