diff --git a/NLyric/Audio/Album.cs b/NLyric/Audio/Album.cs index 3ceea1f..581af87 100644 --- a/NLyric/Audio/Album.cs +++ b/NLyric/Audio/Album.cs @@ -1,22 +1,27 @@ using System; +using System.Collections.Generic; using System.Linq; +using TagLib; namespace NLyric.Audio { + /// + /// 专辑 + /// public class Album : ITrackOrAlbum { private readonly string _name; private readonly string[] _artists; - private readonly int? _trackCount; - private readonly int? _year; + /// + /// 名称 + /// public string Name => _name; - public string[] Artists => _artists; - - public int? TrackCount => _trackCount; - - public int? Year => _year; + /// + /// 艺术家 + /// + public IReadOnlyList Artists => _artists; - public Album(string name, string[] artists, int? trackCount, int? year) { + public Album(string name, IEnumerable artists) { if (name is null) throw new ArgumentNullException(nameof(name)); if (artists is null) @@ -24,43 +29,44 @@ public Album(string name, string[] artists, int? trackCount, int? year) { _name = name; _artists = artists.Select(t => t.Trim()).ToArray(); - _trackCount = trackCount; - _year = year; + Array.Sort(_artists, StringHelper.OrdinalComparer); } /// /// 构造器 /// - /// + /// /// 为空时,是否从 获取艺术家 - public Album(ATL.Track track, bool getArtistsFromTrack) { - if (track is null) - throw new ArgumentNullException(nameof(track)); - if (!HasAlbumInfo(track)) - throw new ArgumentException(nameof(track) + " 中不存在专辑信息"); + public Album(Tag tag, bool getArtistsFromTrack) { + if (tag is null) + throw new ArgumentNullException(nameof(tag)); + if (!HasAlbumInfo(tag)) + throw new ArgumentException(nameof(tag) + " 中不存在专辑信息"); - string artists; + string[] artists; - _name = track.Album.GetSafeString(); - artists = track.AlbumArtist.GetSafeString(); + _name = tag.Album.GetSafeString(); + artists = tag.AlbumArtists.SelectMany(t => t.GetSafeString().SplitEx()).ToArray(); if (getArtistsFromTrack && artists.Length == 0) - artists = track.Artist.GetSafeString(); - _artists = artists.Length == 0 ? Array.Empty() : artists.SplitEx(); - if (track.TrackTotal != 0) - _trackCount = track.TrackTotal; - if (track.Year != 0) - _year = track.Year; + artists = tag.Performers.SelectMany(t => t.GetSafeString().SplitEx()).ToArray(); + Array.Sort(artists, StringHelper.OrdinalComparer); + _artists = artists; } - public static bool HasAlbumInfo(ATL.Track track) { - if (track is null) - throw new ArgumentNullException(nameof(track)); + /// + /// 是否存在专辑信息 + /// + /// + /// + public static bool HasAlbumInfo(Tag tag) { + if (tag is null) + throw new ArgumentNullException(nameof(tag)); - return !string.IsNullOrWhiteSpace(track.Album); + return !string.IsNullOrWhiteSpace(tag.Album); } public override string ToString() { - return "Name:" + _name + " | Artists:" + string.Join(",", _artists) + " | TrackCount:" + _trackCount.ToString() + " | Year:" + _year.ToString(); + return "Name:" + _name + " | Artists:" + string.Join(",", _artists); } } } diff --git a/NLyric/Audio/ITrackOrAlbum.cs b/NLyric/Audio/ITrackOrAlbum.cs index 75910c5..fa9a0ae 100644 --- a/NLyric/Audio/ITrackOrAlbum.cs +++ b/NLyric/Audio/ITrackOrAlbum.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; + namespace NLyric.Audio { public interface ITrackOrAlbum { string Name { get; } - string[] Artists { get; } + IReadOnlyList Artists { get; } } } diff --git a/NLyric/Audio/Track.cs b/NLyric/Audio/Track.cs index 1966730..b994825 100644 --- a/NLyric/Audio/Track.cs +++ b/NLyric/Audio/Track.cs @@ -1,16 +1,27 @@ using System; +using System.Collections.Generic; using System.Linq; +using TagLib; namespace NLyric.Audio { + /// + /// 单曲 + /// public class Track : ITrackOrAlbum { private readonly string _name; private readonly string[] _artists; + /// + /// 名称 + /// public string Name => _name; - public string[] Artists => _artists; + /// + /// 艺术家 + /// + public IReadOnlyList Artists => _artists; - public Track(string name, string[] artists) { + public Track(string name, IEnumerable artists) { if (name is null) throw new ArgumentNullException(nameof(name)); if (artists is null) @@ -18,14 +29,15 @@ public Track(string name, string[] artists) { _name = name; _artists = artists.Select(t => t.Trim()).ToArray(); + Array.Sort(_artists, StringHelper.OrdinalComparer); } - public Track(ATL.Track track) { - if (track is null) - throw new ArgumentNullException(nameof(track)); + public Track(Tag tag) { + if (tag is null) + throw new ArgumentNullException(nameof(tag)); - _name = track.Title.GetSafeString(); - _artists = track.Artist.GetSafeString().SplitEx(); + _name = tag.Title.GetSafeString(); + _artists = tag.Performers.SelectMany(s => s.GetSafeString().SplitEx()).ToArray(); Array.Sort(_artists, StringHelper.OrdinalComparer); } diff --git a/NLyric/Caches/AlbumCache.cs b/NLyric/Caches/AlbumCache.cs deleted file mode 100644 index 38055f4..0000000 --- a/NLyric/Caches/AlbumCache.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Newtonsoft.Json; -using NLyric.Audio; - -namespace NLyric.Caches { - public sealed class AlbumCache { - public string Name { get; set; } - - public int Id { get; set; } - - [JsonConstructor] - [Obsolete("Deserialization only", true)] - public AlbumCache() { - } - - public AlbumCache(Album album, int id) : this(album.Name, id) { - } - - public AlbumCache(string name, int id) { - if (name is null) - throw new ArgumentNullException(nameof(name)); - - Name = name; - Id = id; - } - } -} diff --git a/NLyric/Caches/AllCaches.cs b/NLyric/Caches/AllCaches.cs deleted file mode 100644 index 6511cdc..0000000 --- a/NLyric/Caches/AllCaches.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace NLyric.Caches { - public sealed class AllCaches { - public List AlbumCaches { get; set; } - - public List LyricCaches { get; set; } - - public List TrackCaches { get; set; } - } -} diff --git a/NLyric/Caches/Extensions.cs b/NLyric/Caches/Extensions.cs deleted file mode 100644 index db78a3a..0000000 --- a/NLyric/Caches/Extensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NLyric.Audio; - -namespace NLyric.Caches { - public static class Extensions { - public static AlbumCache Match(this IEnumerable caches, Album album) { - if (album is null) - throw new ArgumentNullException(nameof(album)); - - return caches.FirstOrDefault(t => IsMatched(t, album)); - } - - public static TrackCache Match(this IEnumerable caches, Track track, Album album) { - if (track is null) - throw new ArgumentNullException(nameof(track)); - if (album is null) - throw new ArgumentNullException(nameof(album)); - - return caches.FirstOrDefault(t => IsMatched(t, track, album)); - } - - public static TrackCache Match(this IEnumerable caches, Track track, string fileName) { - if (track is null) - throw new ArgumentNullException(nameof(track)); - if (fileName is null) - throw new ArgumentNullException(nameof(fileName)); - - return caches.FirstOrDefault(t => IsMatched(t, track, fileName)); - } - - public static LyricCache Match(this IEnumerable caches, int id) { - return caches.FirstOrDefault(t => IsMatched(t, id)); - } - - public static bool IsMatched(this AlbumCache cache, Album album) { - if (album is null) - throw new ArgumentNullException(nameof(album)); - - return cache.Name == album.Name; - } - - public static bool IsMatched(this TrackCache cache, Track track, Album album) { - if (track is null) - throw new ArgumentNullException(nameof(track)); - if (album is null) - throw new ArgumentNullException(nameof(album)); - - return cache.Name == track.Name && cache.AlbumName == album.Name && cache.Artists.SequenceEqual(track.Artists); - } - - public static bool IsMatched(this TrackCache cache, Track track, string fileName) { - if (track is null) - throw new ArgumentNullException(nameof(track)); - if (fileName is null) - throw new ArgumentNullException(nameof(fileName)); - - return cache.Name == track.Name && cache.FileName == fileName && cache.Artists.SequenceEqual(track.Artists); - } - - public static bool IsMatched(this LyricCache cache, int id) { - return cache.Id == id; - } - } -} diff --git a/NLyric/Caches/TrackCache.cs b/NLyric/Caches/TrackCache.cs deleted file mode 100644 index ffbd561..0000000 --- a/NLyric/Caches/TrackCache.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Newtonsoft.Json; -using NLyric.Audio; - -namespace NLyric.Caches { - public sealed class TrackCache { - public string Name { get; set; } - - public string[] Artists { get; set; } - - public string AlbumName { get; set; } - - public string FileName { get; set; } - - public int Id { get; set; } - - [JsonConstructor] - [Obsolete("Deserialization only", true)] - public TrackCache() { - } - - public TrackCache(Track track, Album album, int id) : this(track.Name, track.Artists, album.Name, null, id) { - } - - public TrackCache(Track track, string fileName, int id) : this(track.Name, track.Artists, null, fileName, id) { - } - - public TrackCache(string name, string[] artists, string albumName, string fileName, int id) { - if (name is null) - throw new ArgumentNullException(nameof(name)); - if (artists is null) - throw new ArgumentNullException(nameof(artists)); - if (albumName is null && fileName is null) - throw new ArgumentException($"{nameof(albumName)} 和 {nameof(fileName)} 不能同时为 null"); - - Name = name; - Artists = artists; - AlbumName = albumName; - FileName = fileName; - Id = id; - } - } -} diff --git a/NLyric/Database/AlbumInfo.cs b/NLyric/Database/AlbumInfo.cs new file mode 100644 index 0000000..bb7baba --- /dev/null +++ b/NLyric/Database/AlbumInfo.cs @@ -0,0 +1,36 @@ +using System; +using Newtonsoft.Json; +using NLyric.Audio; + +namespace NLyric.Database { + /// + /// 专辑信息 + /// + public sealed class AlbumInfo { + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 网易云音乐ID + /// + public int Id { get; set; } + + [JsonConstructor] + [Obsolete("Deserialization only", true)] + public AlbumInfo() { + } + + public AlbumInfo(Album album, int id) : this(album.Name, id) { + } + + public AlbumInfo(string name, int id) { + if (name is null) + throw new ArgumentNullException(nameof(name)); + + Name = name; + Id = id; + } + } +} diff --git a/NLyric/Database/Extensions.cs b/NLyric/Database/Extensions.cs new file mode 100644 index 0000000..111f820 --- /dev/null +++ b/NLyric/Database/Extensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLyric.Audio; + +namespace NLyric.Database { + public static class Extensions { + public static AlbumInfo Match(this IEnumerable caches, Album album) { + if (album is null) + throw new ArgumentNullException(nameof(album)); + + return caches.FirstOrDefault(t => IsMatched(t, album)); + } + + public static TrackInfo Match(this IEnumerable caches, Track track, Album album) { + if (track is null) + throw new ArgumentNullException(nameof(track)); + + return caches.FirstOrDefault(t => IsMatched(t, track, album)); + } + + public static bool IsMatched(this AlbumInfo cache, Album album) { + if (album is null) + throw new ArgumentNullException(nameof(album)); + + return cache.Name == album.Name; + } + + public static bool IsMatched(this TrackInfo cache, Track track, Album album) { + if (track is null) + throw new ArgumentNullException(nameof(track)); + + return cache.Name == track.Name && (album is null ? cache.AlbumName is null : cache.AlbumName == album.Name) && cache.Artists.SequenceEqual(track.Artists); + // 如果album为空,要求cache中AlbumName也为空,如果album不为空,要求cache中AlbumName匹配 + } + } +} diff --git a/NLyric/Caches/LyricCache.cs b/NLyric/Database/LyricInfo.cs similarity index 52% rename from NLyric/Caches/LyricCache.cs rename to NLyric/Database/LyricInfo.cs index 0b5e81b..b7b63e6 100644 --- a/NLyric/Caches/LyricCache.cs +++ b/NLyric/Database/LyricInfo.cs @@ -2,34 +2,40 @@ using Newtonsoft.Json; using NLyric.Ncm; -namespace NLyric.Caches { - public sealed class LyricCache { - public int Id { get; set; } - - public bool IsAbsoluteMusic { get; set; } - +namespace NLyric.Database { + /// + /// 歌词信息 + /// + public sealed class LyricInfo { + /// + /// 原始歌词版本 + /// public int RawVersion { get; set; } + /// + /// 翻译歌词版本(如果有) + /// public int TranslatedVersion { get; set; } + /// + /// 歌词校验值 + /// public string CheckSum { get; set; } [JsonConstructor] [Obsolete("Deserialization only", true)] - public LyricCache() { + public LyricInfo() { } - public LyricCache(NcmLyric lyric, string checkSum) : this(lyric.Id, lyric.IsAbsoluteMusic, lyric.RawVersion, lyric.TranslatedVersion, checkSum) { + public LyricInfo(NcmLyric lyric, string checkSum) : this(lyric.RawVersion, lyric.TranslatedVersion, checkSum) { if (!lyric.IsCollected) throw new ArgumentException("未收录的歌词不能添加到缓存", nameof(lyric)); } - public LyricCache(int id, bool isAbsoluteMusic, int rawVersion, int translatedVersion, string checkSum) { + public LyricInfo(int rawVersion, int translatedVersion, string checkSum) { if (checkSum is null) throw new ArgumentNullException(nameof(checkSum)); - Id = id; - IsAbsoluteMusic = isAbsoluteMusic; RawVersion = rawVersion; TranslatedVersion = translatedVersion; CheckSum = checkSum; diff --git a/NLyric/Database/NLyricDatabase.cs b/NLyric/Database/NLyricDatabase.cs new file mode 100644 index 0000000..d9daa6f --- /dev/null +++ b/NLyric/Database/NLyricDatabase.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; + +namespace NLyric.Database { + /// + /// NLyric数据库 + /// + public sealed class NLyricDatabase { + /// + /// 专辑信息 + /// + public List AlbumInfos { get; set; } + + /// + /// 单曲信息 + /// + public List TrackInfos { get; set; } + + /// + /// 数据库格式版本 + /// + public int FormatVersion { get; set; } + + /// + /// 检查 + /// + /// + public bool CheckFormatVersion() { + switch (FormatVersion) { + case 0: + case 1: + return true; + default: + return false; + } + } + + /// + /// 是否为老版本数据库 + /// + /// + public bool IsOldFormat() { + return FormatVersion < 1; + } + } +} diff --git a/NLyric/Database/TrackInfo.cs b/NLyric/Database/TrackInfo.cs new file mode 100644 index 0000000..e6e725b --- /dev/null +++ b/NLyric/Database/TrackInfo.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using NLyric.Audio; + +namespace NLyric.Database { + /// + /// 单曲信息 + /// + public sealed class TrackInfo { + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 艺术家 + /// + public IReadOnlyList Artists { get; set; } + + /// + /// 专辑名 + /// + public string AlbumName { get; set; } + + /// + /// 网易云音乐ID + /// + public int Id { get; set; } + + /// + /// 歌词缓存 + /// + public LyricInfo Lyric { get; set; } + + [JsonConstructor] + [Obsolete("Deserialization only", true)] + public TrackInfo() { + } + + public TrackInfo(Track track, Album album, int id) : this(track.Name, track.Artists, album?.Name, id) { + } + + public TrackInfo(string name, IEnumerable artists, string albumName, int id) { + if (name is null) + throw new ArgumentNullException(nameof(name)); + if (artists is null) + throw new ArgumentNullException(nameof(artists)); + + Name = name; + Artists = artists.Select(t => t.Trim()).ToArray(); + AlbumName = albumName; + Id = id; + } + } +} diff --git a/NLyric/NLyric.csproj b/NLyric/NLyric.csproj index 6c9be46..c736131 100644 --- a/NLyric/NLyric.csproj +++ b/NLyric/NLyric.csproj @@ -4,8 +4,8 @@ NLyric NLyric Copyright © 2019 Wwh - 2.2.6.4 - 2.2.6.4 + 2.5.0.0 + 2.5.0.0 ..\bin\$(Configuration) Exe netcoreapp2.1;net472 @@ -16,9 +16,9 @@ - - - + + + diff --git a/NLyric/NLyricImpl.cs b/NLyric/NLyricImpl.cs index 33a5476..bea9be0 100644 --- a/NLyric/NLyricImpl.cs +++ b/NLyric/NLyricImpl.cs @@ -6,10 +6,12 @@ using System.Threading.Tasks; using Newtonsoft.Json; using NLyric.Audio; -using NLyric.Caches; +using NLyric.Database; using NLyric.Lyrics; using NLyric.Ncm; using NLyric.Settings; +using TagLib; +using File = System.IO.File; namespace NLyric { internal static class NLyricImpl { @@ -17,37 +19,45 @@ internal static class NLyricImpl { private static readonly FuzzySettings _fuzzySettings = AllSettings.Default.Fuzzy; private static readonly MatchSettings _matchSettings = AllSettings.Default.Match; private static readonly LyricSettings _lyricSettings = AllSettings.Default.Lyric; - private static readonly Dictionary _cachedNcmAlbums = new Dictionary(); - // AlbumName -> Album + private static readonly HashSet _failMatchAlbums = new HashSet(); + // AlbumName private static readonly Dictionary _cachedNcmTrackses = new Dictionary(); // AlbumId -> Tracks private static readonly Dictionary _cachedNcmLyrics = new Dictionary(); // TrackId -> Lyric - private static string _allCachesPath; - private static AllCaches _allCaches; + private static NLyricDatabase _database; public static async Task ExecuteAsync(Arguments arguments) { + string databasePath; + await LoginIfNeedAsync(arguments); - _allCachesPath = Path.Combine(arguments.Directory, ".nlyric"); - LoadLocalCaches(); + databasePath = Path.Combine(arguments.Directory, ".nlyric"); + LoadDatabase(databasePath); foreach (string audioPath in Directory.EnumerateFiles(arguments.Directory, "*", SearchOption.AllDirectories)) { string lrcPath; - int? trackId; lrcPath = Path.ChangeExtension(audioPath, ".lrc"); if (CanSkip(audioPath, lrcPath)) continue; - Logger.Instance.LogInfo($"开始搜索文件\"{Path.GetFileName(audioPath)}\"的歌词。"); - trackId = await TryGetMusicId(audioPath); - // 同时尝试通过163Key和专辑获取歌曲信息 - if (trackId is null) - Logger.Instance.LogWarning($"无法找到文件\"{Path.GetFileName(audioPath)}\"的网易云音乐ID!"); - else - await WriteLrcAsync(trackId.Value, lrcPath); + using (TagLib.File audioFile = TagLib.File.Create(audioPath)) { + Tag tag; + TrackInfo trackInfo; + + Logger.Instance.LogInfo($"开始搜索文件\"{Path.GetFileName(audioPath)}\"的歌词。"); + tag = audioFile.Tag; + trackInfo = await SearchTrackAsync(tag); + if (trackInfo is null) + Logger.Instance.LogWarning($"无法找到文件\"{Path.GetFileName(audioPath)}\"的网易云音乐ID!"); + else { + Logger.Instance.LogInfo($"已获取文件\"{Path.GetFileName(audioPath)}\"的网易云音乐ID: {trackInfo.Id}。"); + await TryDownloadLyricAsync(trackInfo, lrcPath); + } + } + SaveDatabaseCore(databasePath); Logger.Instance.LogNewLine(); Logger.Instance.LogNewLine(); } - SaveLocalCaches(); + SaveDatabase(databasePath); } private static async Task LoginIfNeedAsync(Arguments arguments) { @@ -89,56 +99,165 @@ private static bool IsAudioFile(string extension) { return _searchSettings.AudioExtensions.Any(s => extension.Equals(s, StringComparison.OrdinalIgnoreCase)); } - private static async Task TryGetMusicId(string audioPath) { - int? trackId; - ATL.Track atlTrack; + /// + /// 同时根据专辑信息以及歌曲信息获取网易云音乐上的歌曲 + /// + /// + /// + private static async Task SearchTrackAsync(Tag tag) { Track track; Album album; - NcmTrack ncmTrack; - trackId = null; - atlTrack = new ATL.Track(audioPath); - track = new Track(atlTrack); - album = Album.HasAlbumInfo(atlTrack) ? new Album(atlTrack, true) : null; + track = new Track(tag); + album = Album.HasAlbumInfo(tag) ? new Album(tag, true) : null; + // 获取Tag信息 try { - // 歌曲无163Key,通过自己的算法匹配 - ncmTrack = await MapToAsync(track, album, audioPath); - if (!(ncmTrack is null)) { - trackId = ncmTrack.Id; - Logger.Instance.LogInfo($"已获取文件\"{Path.GetFileName(audioPath)}\"的网易云音乐ID: {trackId}。"); - } + return await SearchTrackAsync(tag, track, album); } catch (Exception ex) { Logger.Instance.LogException(ex); } - return trackId; + return null; } - private static async Task WriteLrcAsync(int trackId, string lrcPath) { - LyricCache lyricCache; + /// + /// 同时根据专辑信息以及歌曲信息获取网易云音乐上的歌曲 + /// + /// + /// + /// + /// + private static async Task SearchTrackAsync(Tag tag, Track track, Album album) { + TrackInfo trackInfo; + int trackId; + NcmTrack ncmTrack; + bool byUser; + + trackInfo = _database.TrackInfos.Match(track, album); + if (!(trackInfo is null)) + return trackInfo; + // 先尝试从数据库获取歌曲 + if (The163KeyHelper.TryGetTrackId(tag, out trackId)) { + // 尝试从163Key获取ID成功 + ncmTrack = new NcmTrack(track, trackId); + } + else { + // 不存在163Key + AlbumInfo albumInfo; + + albumInfo = album is null ? null : await SearchAlbumAsync(album); + // 尝试获取专辑信息 + if (!(albumInfo is null)) { + // 网易云音乐收录了歌曲所在专辑 + NcmTrack[] ncmTracks; + + ncmTracks = (await GetAlbumTracksAsync(albumInfo)).Where(t => ComputeSimilarity(t.Name, track.Name, false) != 0).ToArray(); + // 获取网易云音乐上专辑收录的歌曲 + ncmTrack = MatchByUser(ncmTracks, track); + } + else + ncmTrack = null; + if (ncmTrack is null) + // 没有对应的专辑信息,使用无专辑匹配,或者网易云音乐上的专辑可能没收录这个歌曲,不清楚为什么,但是确实存在这个情况,比如专辑id:3094396 + ncmTrack = await MapToAsync(track); + } + if (ncmTrack is null) + byUser = GetIdByUser("歌曲", out trackId); + else { + byUser = false; + trackId = 0; + } + if (ncmTrack is null && !byUser) + Logger.Instance.LogWarning("歌曲匹配失败!"); + else { + trackInfo = new TrackInfo(track, album, byUser ? trackId : ncmTrack.Id); + _database.TrackInfos.Add(trackInfo); + Logger.Instance.LogInfo("歌曲匹配成功!"); + } + return trackInfo; + } + + /// + /// 根据专辑信息获取网易云音乐上的专辑 + /// + /// + /// + private static async Task SearchAlbumAsync(Album album) { + AlbumInfo albumInfo; + string replacedAlbumName; + NcmAlbum ncmAlbum; + bool byUser; + int albumId; + + albumInfo = _database.AlbumInfos.Match(album); + if (!(albumInfo is null)) + return albumInfo; + // 先尝试从数据库获取专辑 + replacedAlbumName = album.Name.ReplaceEx(); + if (_failMatchAlbums.Contains(replacedAlbumName)) + return null; + // 防止不停重复匹配一个专辑 + ncmAlbum = await MapToAsync(album); + if (ncmAlbum is null) + byUser = GetIdByUser("专辑", out albumId); + else { + byUser = false; + albumId = 0; + } + if (ncmAlbum is null && !byUser) { + _failMatchAlbums.Add(replacedAlbumName); + Logger.Instance.LogWarning("专辑匹配失败!"); + } + else { + albumInfo = new AlbumInfo(album, byUser ? albumId : ncmAlbum.Id); + _database.AlbumInfos.Add(albumInfo); + Logger.Instance.LogInfo("专辑匹配成功!"); + } + return albumInfo; + } + + private static async Task GetAlbumTracksAsync(AlbumInfo albumInfo) { + NcmTrack[] ncmTracks; + + if (!_cachedNcmTrackses.TryGetValue(albumInfo.Id, out ncmTracks)) { + List list; + + list = new List(); + foreach (NcmTrack item in await CloudMusic.GetTracksAsync(albumInfo.Id)) + if ((await GetLyricAsync(item.Id)).IsCollected) + list.Add(item); + ncmTracks = list.ToArray(); + _cachedNcmTrackses[albumInfo.Id] = ncmTracks; + } + return ncmTracks; + } + + private static async Task TryDownloadLyricAsync(TrackInfo trackInfo, string lrcPath) { bool hasLrcFile; string lyricCheckSum; NcmLyric ncmLyric; Lrc lrc; - lyricCache = _allCaches.LyricCaches.Match(trackId); hasLrcFile = File.Exists(lrcPath); lyricCheckSum = hasLrcFile ? ComputeLyricCheckSum(File.ReadAllText(lrcPath)) : null; try { - ncmLyric = await GetLyricAsync(trackId); + ncmLyric = await GetLyricAsync(trackInfo.Id); } catch (Exception ex) { Logger.Instance.LogException(ex); - return; + return false; } if (hasLrcFile) { // 如果歌词存在,判断是否需要覆盖或更新 - if (!(lyricCache is null) && lyricCache.CheckSum == lyricCheckSum) { + LyricInfo lyricInfo; + + lyricInfo = trackInfo.Lyric; + if (!(lyricInfo is null) && lyricInfo.CheckSum == lyricCheckSum) { // 歌词由NLyric创建 - if (ncmLyric.RawVersion <= lyricCache.RawVersion && ncmLyric.TranslatedVersion <= lyricCache.TranslatedVersion) { + if (ncmLyric.RawVersion <= lyricInfo.RawVersion && ncmLyric.TranslatedVersion <= lyricInfo.TranslatedVersion) { // 是最新版本 Logger.Instance.LogInfo("本地歌词已是最新版本,正在跳过。", ConsoleColor.Yellow); - return; + return false; } else { // 不是最新版本 @@ -146,120 +265,40 @@ private static async Task WriteLrcAsync(int trackId, string lrcPath) { Logger.Instance.LogInfo("本地歌词不是最新版本,正在更新。", ConsoleColor.Green); else { Logger.Instance.LogInfo("本地歌词不是最新版本但是自动更新被禁止,正在跳过。", ConsoleColor.Yellow); - return; + return false; } } } else { // 歌词非NLyric创建 - if (!_lyricSettings.Overwriting) { - Logger.Instance.LogInfo("本地歌词已存在并且非NLyric创建,正在跳过。", ConsoleColor.Yellow); - return; + if (_lyricSettings.Overwriting) + Logger.Instance.LogInfo("本地歌词非NLyric创建,正在更新。", ConsoleColor.Yellow); + else { + Logger.Instance.LogInfo("本地歌词非NLyric创建但是覆盖被禁止,正在跳过。", ConsoleColor.Yellow); + return false; } } } lrc = ToLrc(ncmLyric); if (!(lrc is null)) { + // 歌词已收录,不是纯音乐 string lyric; lyric = lrc.ToString(); - UpdateCache(ncmLyric, ComputeLyricCheckSum(lyric)); - File.WriteAllText(lrcPath, lyric); - Logger.Instance.LogInfo("本地歌词下载完毕。", ConsoleColor.Magenta); - } - } - - #region mapping - /// - /// 同时根据专辑信息以及歌曲信息获取网易云音乐上的歌曲 - /// - /// - /// - /// - /// - private static async Task MapToAsync(Track track, Album album, string audioPath) { - if (track is null) - throw new ArgumentNullException(nameof(track)); - if (audioPath is null) - throw new ArgumentNullException(nameof(audioPath)); - - string fileName; - TrackCache trackCache; - int trackId; - NcmTrack ncmTrack; - - fileName = Path.GetFileName(audioPath); - trackCache = album is null ? _allCaches.TrackCaches.Match(track, fileName) : _allCaches.TrackCaches.Match(track, album); - // 有专辑信息就用专辑信息,没有专辑信息就用文件名 - if (!(trackCache is null)) - return new NcmTrack(track, trackCache.Id); - // 先尝试从缓存获取歌曲 - if (The163KeyHelper.TryGetMusicId(audioPath, out trackId)) { - // 尝试从163Key获取ID - ncmTrack = new NcmTrack(track, trackId); - } - else { - NcmAlbum ncmAlbum; - - ncmAlbum = null; - if (!(album is null)) { - // 存在专辑信息,尝试获取网易云音乐上对应的专辑 - AlbumCache albumCache; - - albumCache = _allCaches.AlbumCaches.Match(album); - if (!(albumCache is null)) - ncmAlbum = new NcmAlbum(album, albumCache.Id); - // 先尝试从缓存获取专辑 - if (ncmAlbum is null) { - ncmAlbum = await MapToAsync(album); - if (!(ncmAlbum is null)) - UpdateCache(album, ncmAlbum.Id); - } + try { + File.WriteAllText(lrcPath, lyric); } - if (ncmAlbum is null) { - // 没有对应的专辑信息,使用无专辑匹配 - ncmTrack = await MapToAsync(track); + catch (Exception ex) { + Logger.Instance.LogException(ex); + return false; } - else { - // 网易云音乐收录了歌曲所在专辑 - NcmTrack[] ncmTracks; - - ncmTracks = (await GetTracksAsync(ncmAlbum)).Where(t => ComputeSimilarity(t.Name, track.Name, false) != 0).ToArray(); - // 获取网易云音乐上专辑收录的歌曲 - ncmTrack = MatchByUser(ncmTracks, track); - if (ncmTrack is null) - // 网易云音乐上的专辑可能没收录这个歌曲,不清楚为什么,但是确实存在这个情况,比如专辑id:3094396 - ncmTrack = await MapToAsync(track); - } - } - if (ncmTrack is null) - Logger.Instance.LogWarning("歌曲匹配失败!"); - else { - Logger.Instance.LogInfo("歌曲匹配成功!"); - if (album is null) - UpdateCache(track, fileName, ncmTrack.Id); - else - UpdateCache(track, album, ncmTrack.Id); - } - return ncmTrack; - } - - private static async Task GetTracksAsync(NcmAlbum ncmAlbum) { - NcmTrack[] ncmTracks; - - if (!_cachedNcmTrackses.TryGetValue(ncmAlbum.Id, out ncmTracks)) { - List list; - - list = new List(); - foreach (NcmTrack item in await CloudMusic.GetTracksAsync(ncmAlbum.Id)) - if ((await GetLyricAsync(item.Id)).IsCollected) - list.Add(item); - ncmTracks = list.ToArray(); - _cachedNcmTrackses[ncmAlbum.Id] = ncmTracks; + trackInfo.Lyric = new LyricInfo(ncmLyric, ComputeLyricCheckSum(lyric)); + Logger.Instance.LogInfo("本地歌词下载完毕。", ConsoleColor.Magenta); } - return ncmTracks; + return true; } + #region map /// /// 获取网易云音乐上的歌曲,自动尝试带艺术家与不带艺术家搜索 /// @@ -290,12 +329,8 @@ private static async Task MapToAsync(Album album) { if (album is null) throw new ArgumentNullException(nameof(album)); - string replacedAlbumName; NcmAlbum ncmAlbum; - replacedAlbumName = album.Name.ReplaceEx(); - if (_cachedNcmAlbums.TryGetValue(replacedAlbumName, out ncmAlbum)) - return ncmAlbum; Logger.Instance.LogInfo($"开始搜索专辑\"{album}\"。"); Logger.Instance.LogWarning("正在尝试带艺术家搜索,结果可能将过少!"); ncmAlbum = await MapToAsync(album, true); @@ -303,13 +338,6 @@ private static async Task MapToAsync(Album album) { Logger.Instance.LogWarning("正在尝试忽略艺术家搜索,结果可能将不精确!"); ncmAlbum = await MapToAsync(album, false); } - if (ncmAlbum is null) { - Logger.Instance.LogWarning("专辑匹配失败!"); - _cachedNcmAlbums[replacedAlbumName] = null; - return null; - } - Logger.Instance.LogInfo("专辑匹配成功!"); - _cachedNcmAlbums[replacedAlbumName] = ncmAlbum; return ncmAlbum; } @@ -320,11 +348,22 @@ private static async Task MapToAsync(Album album) { /// 是否带艺术家搜索 /// private static async Task MapToAsync(Track track, bool withArtists) { - List list; NcmTrack[] ncmTracks; + List list; + try { + ncmTracks = await CloudMusic.SearchTrackAsync(track, _searchSettings.Limit, withArtists); + } + catch (KeywordForbiddenException ex1) { + Logger.Instance.LogError(ex1.Message); + return null; + } + catch (Exception ex2) { + Logger.Instance.LogException(ex2); + return null; + } list = new List(); - foreach (NcmTrack item in (await CloudMusic.SearchTrackAsync(track, _searchSettings.Limit, withArtists)).Where(t => ComputeSimilarity(t.Name, track.Name, false) != 0)) + foreach (NcmTrack item in ncmTracks.Where(t => ComputeSimilarity(t.Name, track.Name, false) != 0)) if ((await GetLyricAsync(item.Id)).IsCollected) list.Add(item); ncmTracks = list.ToArray(); @@ -340,92 +379,72 @@ private static async Task MapToAsync(Track track, bool withArtists) { private static async Task MapToAsync(Album album, bool withArtists) { NcmAlbum[] ncmAlbums; - ncmAlbums = (await CloudMusic.SearchAlbumAsync(album, _searchSettings.Limit, withArtists)).Where(t => ComputeSimilarity(t.Name, album.Name, false) != 0).ToArray(); - return MatchByUser(ncmAlbums, album); - } - #endregion - - #region local cache - private static void LoadLocalCaches() { - if (File.Exists(_allCachesPath)) { - _allCaches = JsonConvert.DeserializeObject(File.ReadAllText(_allCachesPath)); - NormalizeAllCaches(); - Logger.Instance.LogInfo($"搜索缓存\"{_allCachesPath}\"加载成功。"); + try { + ncmAlbums = await CloudMusic.SearchAlbumAsync(album, _searchSettings.Limit, withArtists); } - else { - _allCaches = new AllCaches() { - AlbumCaches = new List(), - LyricCaches = new List(), - TrackCaches = new List() - }; + catch (KeywordForbiddenException ex1) { + Logger.Instance.LogError(ex1.Message); + return null; } - } - - private static void SaveLocalCaches() { - NormalizeAllCaches(); - SaveLocalCachesCore(_allCachesPath); - Logger.Instance.LogInfo($"搜索缓存\"{_allCachesPath}\"已被保存。"); - } - - private static void NormalizeAllCaches() { - _allCaches.AlbumCaches.Sort((x, y) => string.CompareOrdinal(x.Name, y.Name)); - _allCaches.TrackCaches.Sort((x, y) => string.CompareOrdinal(x.Name, y.Name)); - _allCaches.LyricCaches.Sort((x, y) => x.Id.CompareTo(y.Id)); - foreach (TrackCache cache in _allCaches.TrackCaches) { - for (int i = 0; i < cache.Artists.Length; i++) - cache.Artists[i] = cache.Artists[i].Trim(); - Array.Sort(cache.Artists, StringHelper.OrdinalComparer); + catch (Exception ex2) { + Logger.Instance.LogException(ex2); + return null; } + ncmAlbums = ncmAlbums.Where(t => ComputeSimilarity(t.Name, album.Name, false) != 0).ToArray(); + return MatchByUser(ncmAlbums, album); } + #endregion - private static void SaveLocalCachesCore(string cachePath) { - File.SetAttributes(_allCachesPath, FileAttributes.Normal); - File.WriteAllText(cachePath, JsonConvert.SerializeObject(_allCaches)); - File.SetAttributes(_allCachesPath, FileAttributes.Hidden); - } - - private static void UpdateCache(Album album, int id) { - AlbumCache cache; - - cache = _allCaches.AlbumCaches.Match(album); - if (!(cache is null)) - return; - _allCaches.AlbumCaches.Add(new AlbumCache(album, id)); - OnCacheUpdated(); + #region database + private static void LoadDatabase(string databasePath) { + if (File.Exists(databasePath)) { + _database = JsonConvert.DeserializeObject(File.ReadAllText(databasePath)); + if (!_database.CheckFormatVersion()) + throw new InvalidOperationException("尝试加载新格式数据库。"); + if (_database.IsOldFormat()) + Logger.Instance.LogWarning("不兼容的老格式数据库,将被覆盖重建!"); + else { + SortDatabase(); + Logger.Instance.LogInfo($"搜索数据库\"{databasePath}\"加载成功。"); + return; + } + } + _database = new NLyricDatabase() { + AlbumInfos = new List(), + TrackInfos = new List(), + FormatVersion = 1 + }; + if (File.Exists(databasePath)) + File.Delete(databasePath); + SaveDatabaseCore(databasePath); + File.SetAttributes(databasePath, FileAttributes.Hidden); } - private static void UpdateCache(Track track, Album album, int id) { - TrackCache cache; - - cache = _allCaches.TrackCaches.Match(track, album); - if (!(cache is null)) - return; - _allCaches.TrackCaches.Add(new TrackCache(track, album, id)); - OnCacheUpdated(); + private static void SaveDatabase(string databasePath) { + SortDatabase(); + SaveDatabaseCore(databasePath); + Logger.Instance.LogInfo($"搜索数据库\"{databasePath}\"已被保存。"); } - private static void UpdateCache(Track track, string fileName, int id) { - TrackCache cache; - - cache = _allCaches.TrackCaches.Match(track, fileName); - if (!(cache is null)) - return; - _allCaches.TrackCaches.Add(new TrackCache(track, fileName, id)); - OnCacheUpdated(); + private static void SortDatabase() { + _database.AlbumInfos.Sort((x, y) => string.CompareOrdinal(x.Name, y.Name)); + _database.TrackInfos.Sort((x, y) => string.CompareOrdinal(x.Name, y.Name)); } - private static void UpdateCache(NcmLyric lyric, string checkSum) { - int index; - - index = _allCaches.LyricCaches.FindIndex(t => t.IsMatched(lyric.Id)); - if (index != -1) - _allCaches.LyricCaches.RemoveAt(index); - _allCaches.LyricCaches.Add(new LyricCache(lyric, checkSum)); - OnCacheUpdated(); + private static void SaveDatabaseCore(string databasePath) { + using (FileStream stream = new FileStream(databasePath, FileMode.OpenOrCreate)) + using (StreamWriter writer = new StreamWriter(stream)) + writer.Write(FormatJson(JsonConvert.SerializeObject(_database))); } - private static void OnCacheUpdated() { - SaveLocalCachesCore(_allCachesPath); + private static string FormatJson(string json) { + using (StringWriter writer = new StringWriter()) + using (JsonTextWriter jsonWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }) + using (StringReader reader = new StringReader(json)) + using (JsonTextReader jsonReader = new JsonTextReader(reader)) { + jsonWriter.WriteToken(jsonReader); + return writer.ToString(); + } } #endregion @@ -471,9 +490,9 @@ private static TSource MatchExactly(TSource[] sources, TTarget } if (x != y) goto not_equal; - if (source.Artists.Length != target.Artists.Length) + if (source.Artists.Count != target.Artists.Count) goto not_equal; - for (int i = 0; i < source.Artists.Length; i++) { + for (int i = 0; i < source.Artists.Count; i++) { x = source.Artists[i]; y = target.Artists[i]; if (fuzzy) { @@ -495,7 +514,7 @@ private static TSource Select(TSource[] sources, TTarget targe if (sources.Length == 0) return null; - Logger.Instance.LogInfo("请手动输入1,2,3...选择匹配的项,若不存在,请输入P。"); + Logger.Instance.LogInfo("请手动输入1,2,3...选择匹配的项,若不存在,请直接按下回车键。"); Logger.Instance.LogInfo("对比项:" + TrackOrAlbumToString(target)); for (int i = 0; i < sources.Length; i++) { double nameSimilarity; @@ -516,7 +535,7 @@ private static TSource Select(TSource[] sources, TTarget targe int index; userInput = Console.ReadLine().Trim(); - if (userInput.ToUpperInvariant() == "P") + if (userInput.Length == 0) break; if (int.TryParse(userInput, out index)) { index -= 1; @@ -532,7 +551,7 @@ private static TSource Select(TSource[] sources, TTarget targe return result; string TrackOrAlbumToString(ITrackOrAlbum trackOrAlbum) { - if (trackOrAlbum.Artists.Length == 0) + if (trackOrAlbum.Artists.Count == 0) return trackOrAlbum.Name; return trackOrAlbum.Name + " by " + string.Join(",", trackOrAlbum.Artists); } @@ -549,9 +568,25 @@ private static double ComputeSimilarity(string x, string y, bool fuzzy) { y = y.Trim(); return Levenshtein.Compute(x, y); } + + private static bool GetIdByUser(string s, out int id) { + Logger.Instance.LogInfo($"请输入{s}的网易云音乐ID,若不存在,请直接按下回车键。"); + do { + string userInput; + + userInput = Console.ReadLine().Trim(); + if (userInput.Length == 0) + break; + if (int.TryParse(userInput, out id)) + return true; + Logger.Instance.LogWarning("输入有误,请重新输入!"); + } while (true); + id = 0; + return false; + } #endregion - #region lyrics + #region lyric private static async Task GetLyricAsync(int trackId) { NcmLyric lyric; diff --git a/NLyric/Ncm/CloudMusic.cs b/NLyric/Ncm/CloudMusic.cs index e02f33c..c6cef04 100644 --- a/NLyric/Ncm/CloudMusic.cs +++ b/NLyric/Ncm/CloudMusic.cs @@ -57,7 +57,7 @@ public static async Task SearchTrackAsync(Track track, int limit, bo throw new ApplicationException(nameof(CloudMusicApiProviders.Search) + " API错误"); json = (JObject)json["result"]; if (json is null) - throw new ArgumentException($"\"{string.Join(" ", keywords)}\" 中有关键词被屏蔽"); + throw new KeywordForbiddenException(string.Join(" ", keywords)); songs = json["songs"] as JArray; if (songs is null) return Array.Empty(); @@ -94,7 +94,7 @@ public static async Task SearchAlbumAsync(Album album, int limit, bo throw new ApplicationException(nameof(CloudMusicApiProviders.Search) + " API错误"); json = (JObject)json["result"]; if (json is null) - throw new ArgumentException($"\"{string.Join(" ", keywords)}\" 中有关键词被屏蔽"); + throw new KeywordForbiddenException(string.Join(" ", keywords)); albums = json["albums"] as JArray; if (albums is null) return Array.Empty(); @@ -156,7 +156,7 @@ private static NcmAlbum ParseAlbum(JToken json) { Album album; NcmAlbum ncmAlbum; - album = new Album((string)json["name"], ParseNames(json["artists"]), (int)json["size"], TimeStampToDateTime((long)json["publishTime"]).Year); + album = new Album((string)json["name"], ParseNames(json["artists"])); ncmAlbum = new NcmAlbum(album, (int)json["id"]); return ncmAlbum; } @@ -185,10 +185,6 @@ private static (Lrc, int) ParseLyric(JToken json) { return (lrc, version); } - private static DateTime TimeStampToDateTime(long timeStamp) { - return new DateTime(1970, 1, 1).AddMilliseconds(timeStamp); - } - internal static class NormalApi { private const string SEARCH_URL = "http://music.163.com/api/search/pc"; private const string ALBUM_URL = "http://music.163.com/api/album"; diff --git a/NLyric/Ncm/KeywordForbiddenException.cs b/NLyric/Ncm/KeywordForbiddenException.cs new file mode 100644 index 0000000..1195756 --- /dev/null +++ b/NLyric/Ncm/KeywordForbiddenException.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.Serialization; + +namespace NLyric.Ncm { + /// + /// 关键词被禁止 + /// + [Serializable] + public sealed class KeywordForbiddenException : Exception { + public KeywordForbiddenException() { + } + + public KeywordForbiddenException(string text) : base($"\"{text}\" 中有关键词被屏蔽") { + } + + private KeywordForbiddenException(SerializationInfo info, StreamingContext context) : base(info, context) { + } + } +} diff --git a/NLyric/Ncm/NcmAlbum.cs b/NLyric/Ncm/NcmAlbum.cs index 4078781..499b83d 100644 --- a/NLyric/Ncm/NcmAlbum.cs +++ b/NLyric/Ncm/NcmAlbum.cs @@ -6,7 +6,7 @@ public sealed class NcmAlbum : Album { public int Id => _id; - public NcmAlbum(Album album, int id) : base(album.Name, album.Artists, album.TrackCount, album.Year) { + public NcmAlbum(Album album, int id) : base(album.Name, album.Artists) { _id = id; } diff --git a/NLyric/Settings.json b/NLyric/Settings.json index 623c947..8fba35f 100644 --- a/NLyric/Settings.json +++ b/NLyric/Settings.json @@ -70,6 +70,6 @@ ], // 歌词模式,依次尝试每一个模式直到成功,Merged表示混合未翻译和翻译后歌词,Raw表示未翻译的歌词,Translated表示翻译后的歌词 "SimplifyTranslated": true, // 部分翻译后的歌词是繁体的,这个选项可以简体化翻译后的歌词 "AutoUpdate": true, // 是否自动更新由NLyric创建的歌词 - "Overwriting": false // 是否覆盖非NLyric创建的歌词 + "Overwriting": true // 是否覆盖非NLyric创建的歌词 } } diff --git a/NLyric/StringHelper.cs b/NLyric/StringHelper.cs index 4aa59d0..1523007 100644 --- a/NLyric/StringHelper.cs +++ b/NLyric/StringHelper.cs @@ -16,7 +16,7 @@ internal static class StringHelper { public static IComparer OrdinalComparer => StringOrdinalComparer.Instance; /// - /// 获取非空字符串,并且清楚首尾空格 + /// 获取非空字符串,并且清除首尾空格 /// /// /// @@ -105,7 +105,7 @@ public static string[] SplitEx(this string value) { if (value is null) throw new ArgumentNullException(nameof(value)); - return value.Split(_searchSettings.Separators, StringSplitOptions.RemoveEmptyEntries); + return value.Split(_searchSettings.Separators, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); } /// diff --git a/NLyric/The163KeyHelper.cs b/NLyric/The163KeyHelper.cs index 864c255..ce79266 100644 --- a/NLyric/The163KeyHelper.cs +++ b/NLyric/The163KeyHelper.cs @@ -1,97 +1,62 @@ using System; -using System.IO; using System.Security.Cryptography; using System.Text; using Newtonsoft.Json.Linq; +using TagLib; namespace NLyric { /// /// 通过163Key直接获取歌曲ID /// internal static class The163KeyHelper { - private static readonly byte[] _163Start = Encoding.UTF8.GetBytes("163 key(Don't modify):"); - private static readonly byte[] _163EndMp3 = { 0x54, 0x41, 0x4C, 0x42 }; - private static readonly byte[] _163EndFlac = { 0x0, 0x0, 0x0, 0x45 }; - private static readonly Aes _aes; + private static readonly Aes _aes = Create163Aes(); - static The163KeyHelper() { - _aes = Aes.Create(); - _aes.BlockSize = 128; - _aes.Key = Encoding.UTF8.GetBytes(@"#14ljk_!\]&0U<'("); - _aes.Mode = CipherMode.ECB; - _aes.Padding = PaddingMode.PKCS7; - } + private static Aes Create163Aes() { + Aes aes; - public static bool TryGetMusicId(string filePath, out int trackId) { - string extension; - byte[] byt163Key; + aes = Aes.Create(); + aes.BlockSize = 128; + aes.Key = Encoding.UTF8.GetBytes(@"#14ljk_!\]&0U<'("); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.PKCS7; + return aes; + } - extension = Path.GetExtension(filePath); - switch (extension.ToUpperInvariant()) { - case ".FLAC": - byt163Key = Get163Key(filePath, false); - break; - case ".MP3": - byt163Key = Get163Key(filePath, true); - break; - default: - byt163Key = null; - break; + /// + /// 尝试获取网易云音乐ID + /// + /// + /// + /// + public static bool TryGetTrackId(Tag tag, out int trackId) { + if (tag is null) + throw new ArgumentNullException(nameof(tag)); + + string the163Key; + + trackId = 0; + the163Key = tag.Comment; + if (!Is163KeyCandidate(the163Key)) + the163Key = tag.Description; + if (!Is163KeyCandidate(the163Key)) + return false; + try { + byte[] byt163Key; + + the163Key = the163Key.Substring(22); + byt163Key = Convert.FromBase64String(the163Key); + using (ICryptoTransform cryptoTransform = _aes.CreateDecryptor()) + byt163Key = cryptoTransform.TransformFinalBlock(byt163Key, 0, byt163Key.Length); + trackId = (int)JObject.Parse(Encoding.UTF8.GetString(byt163Key).Substring(6))["musicId"]; } - if (byt163Key is null) { - trackId = 0; + catch { return false; } - trackId = GetMusicId(byt163Key); return true; } - private static byte[] Get163Key(string filePath, bool isMp3) { - byte[] bytFile; - int startIndex; - int endIndex; - byte[] byt163Key; - - bytFile = new byte[0x4000]; - using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) - stream.Read(bytFile, 0, bytFile.Length); - startIndex = GetIndex(bytFile, _163Start, 0); - if (startIndex == -1) - return null; - if (isMp3) - endIndex = GetIndex(bytFile, _163EndMp3, startIndex); - else - endIndex = GetIndex(bytFile, _163EndFlac, startIndex) - 1; - if (endIndex == -1) - return null; - byt163Key = new byte[endIndex - startIndex - _163Start.Length]; - Buffer.BlockCopy(bytFile, startIndex + _163Start.Length, byt163Key, 0, byt163Key.Length); - return byt163Key; - } - - private static int GetMusicId(byte[] byt163Key) { - byt163Key = Convert.FromBase64String(Encoding.UTF8.GetString(byt163Key)); - using (ICryptoTransform cryptoTransform = _aes.CreateDecryptor()) - byt163Key = cryptoTransform.TransformFinalBlock(byt163Key, 0, byt163Key.Length); - return (int)JObject.Parse(Encoding.UTF8.GetString(byt163Key).Substring(6))["musicId"]; - } - - private static int GetIndex(byte[] src, byte[] dest, int startIndex) { - return GetIndex(src, dest, startIndex, src.Length - dest.Length); - } - - private static int GetIndex(byte[] src, byte[] dest, int startIndex, int endIndex) { - int j; - - for (int i = startIndex; i < endIndex + 1; i++) - if (src[i] == dest[0]) { - for (j = 1; j < dest.Length; j++) - if (src[i + j] != dest[j]) - break; - if (j == dest.Length) - return i; - } - return -1; + private static bool Is163KeyCandidate(string s) { + return !string.IsNullOrEmpty(s) && s.StartsWith("163 key(Don't modify):", StringComparison.Ordinal); } } } diff --git a/README.md b/README.md index 14ce526..0d98975 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Windows用户专属GUI。 ], // 歌词模式,依次尝试每一个模式直到成功,Merged表示混合未翻译和翻译后歌词,Raw表示未翻译的歌词,Translated表示翻译后的歌词 "SimplifyTranslated": true, // 部分翻译后的歌词是繁体的,这个选项可以简体化翻译后的歌词 "AutoUpdate": true, // 是否自动更新由NLyric创建的歌词 - "Overwriting": false // 是否覆盖非NLyric创建的歌词 + "Overwriting": true // 是否覆盖非NLyric创建的歌词 } } ```