refactor: Refactor lyrics download logic and abstract a new logger.

This commit is contained in:
real-zony
2022-10-23 13:38:51 +08:00
parent 3e27e18098
commit 7d17fc0b97
18 changed files with 245 additions and 163 deletions

View File

@@ -0,0 +1,127 @@
using System.Text;
using Microsoft.Extensions.Options;
using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Extensions;
using ZonyLrcTools.Common.Infrastructure.Logging;
using ZonyLrcTools.Common.Infrastructure.Threading;
namespace ZonyLrcTools.Common.Lyrics;
public class DefaultLyricsDownloader : ILyricsDownloader, ISingletonDependency
{
private readonly IEnumerable<ILyricsProvider> _lyricDownloaderList;
private readonly GlobalOptions _options;
private readonly IWarpLogger _logger;
public IEnumerable<ILyricsProvider> AvailableProviders => new Lazy<IEnumerable<ILyricsProvider>>(() =>
{
return _options.Provider.Lyric.Plugin
.Where(op => op.Priority != -1)
.OrderBy(op => op.Priority)
.Join(_lyricDownloaderList,
op => op.Name,
loader => loader.DownloaderName,
(op, loader) => loader);
}).Value;
public DefaultLyricsDownloader(IEnumerable<ILyricsProvider> lyricDownloaderList,
IOptions<GlobalOptions> options,
IWarpLogger logger)
{
_lyricDownloaderList = lyricDownloaderList;
_logger = logger;
_options = options.Value;
}
public async Task<List<MusicInfo>> DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
int parallelCount = 2,
CancellationToken cancellationToken = default)
{
await _logger.InfoAsync("开始下载歌词文件数据...");
if (parallelCount <= 0)
{
parallelCount = 1;
}
var warpTask = new WarpTask(parallelCount);
var downloadTasks = needDownloadMusicInfos.Select(info =>
warpTask.RunAsync(() =>
Task.Run(async () =>
{
// Try to download lyrics from all available providers.
foreach (var lyricsProvider in AvailableProviders)
{
await DownloadAndWriteLyricsAsync(lyricsProvider, info);
if (info.IsSuccessful)
{
_logger.LogSuccessful(info);
return;
}
}
}, cancellationToken), cancellationToken));
await Task.WhenAll(downloadTasks);
await _logger.InfoAsync($"歌词数据下载完成,成功: {needDownloadMusicInfos.Count(m => m.IsSuccessful)} 失败{needDownloadMusicInfos.Count(m => m.IsSuccessful == false)}。");
return needDownloadMusicInfos;
}
private async Task DownloadAndWriteLyricsAsync(ILyricsProvider provider, MusicInfo info)
{
try
{
var lyrics = await provider.DownloadAsync(info.Name, info.Artist);
if (lyrics.IsPruneMusic)
{
info.IsSuccessful = true;
return;
}
var newLyricsFilePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!,
$"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc");
if (File.Exists(newLyricsFilePath))
{
File.Delete(newLyricsFilePath);
}
// Write lyrics to file.
await using (var fileStream = new FileStream(newLyricsFilePath, FileMode.CreateNew, FileAccess.Write))
{
await using (var binaryWriter = new BinaryWriter(fileStream, Encoding.UTF8))
{
binaryWriter.Write(Utf8ToSelectedEncoding(lyrics));
binaryWriter.Flush();
}
}
info.IsSuccessful = true;
}
catch (ErrorCodeException ex)
{
_logger.LogWarningInfo(ex);
info.IsSuccessful = false;
}
catch (Exception ex)
{
await _logger.ErrorAsync($"下载歌词文件时发生错误:{ex.Message},歌曲名: {info.Name},歌手: {info.Artist}。");
info.IsSuccessful = false;
}
}
private byte[] Utf8ToSelectedEncoding(LyricsItemCollection lyrics)
{
var supportEncodings = Encoding.GetEncodings();
if (supportEncodings.All(x => x.Name != _options.Provider.Lyric.Config.FileEncoding))
{
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
}
return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(_options.Provider.Lyric.Config.FileEncoding), lyrics.GetUtf8Bytes());
}
}

View File

@@ -2,5 +2,9 @@
public interface ILyricsDownloader
{
Task<List<MusicInfo>> DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
int parallelCount = 2,
CancellationToken cancellationToken = default);
IEnumerable<ILyricsProvider> AvailableProviders { get; }
}

View File

@@ -1,23 +1,23 @@
namespace ZonyLrcTools.Common.Lyrics
{
/// <summary>
/// 构建 <see cref="LyricItemCollection"/> 对象的工厂。
/// 构建 <see cref="LyricsItemCollection"/> 对象的工厂。
/// </summary>
public interface ILyricsItemCollectionFactory
{
/// <summary>
/// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。
/// 根据指定的歌曲数据构建新的 <see cref="LyricsItemCollection"/> 实例。
/// </summary>
/// <param name="sourceLyric">原始歌词数据。</param>
/// <returns>构建完成的 <see cref="LyricItemCollection"/> 对象。</returns>
LyricItemCollection Build(string sourceLyric);
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
LyricsItemCollection Build(string sourceLyric);
/// <summary>
/// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。
/// 根据指定的歌曲数据构建新的 <see cref="LyricsItemCollection"/> 实例。
/// </summary>
/// <param name="sourceLyric">原始歌词数据。</param>
/// <param name="translationLyric">翻译歌词数据。</param>
/// <returns>构建完成的 <see cref="LyricItemCollection"/> 对象。</returns>
LyricItemCollection Build(string sourceLyric, string translationLyric);
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
LyricsItemCollection Build(string sourceLyric, string translationLyric);
}
}

View File

@@ -12,7 +12,7 @@ namespace ZonyLrcTools.Common.Lyrics
/// <param name="artist">歌曲的作者。</param>
/// <param name="duration">歌曲的时长。</param>
/// <returns>歌曲的歌词数据对象。</returns>
ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist, long? duration = null);
ValueTask<LyricsItemCollection> DownloadAsync(string songName, string artist, long? duration = null);
/// <summary>
/// 下载器的名称。

View File

@@ -2,6 +2,6 @@ namespace ZonyLrcTools.Common.Lyrics
{
public interface ILyricsTextResolver
{
LyricItemCollection Resolve(string lyricText);
LyricsItemCollection Resolve(string lyricText);
}
}

View File

@@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
namespace ZonyLrcTools.Common.Lyrics
{
/// <summary>
/// 歌词的行对象,是 <see cref="LyricItemCollection"/> 的最小单位。。
/// 歌词的行对象,是 <see cref="LyricsItemCollection"/> 的最小单位。。
/// </summary>
public class LyricsItem : IComparable<LyricsItem>
{

View File

@@ -7,7 +7,7 @@ namespace ZonyLrcTools.Common.Lyrics
/// <summary>
/// 歌词数据,包含多条歌词行对象(<see cref="LyricsItem"/>)。
/// </summary>
public class LyricItemCollection : List<LyricsItem>
public class LyricsItemCollection : List<LyricsItem>
{
/// <summary>
/// 是否为纯音乐,当没有任何歌词数据的时候,属性值为 True。
@@ -16,12 +16,12 @@ namespace ZonyLrcTools.Common.Lyrics
public GlobalLyricsConfigOptions Options { get; private set; }
public LyricItemCollection(GlobalLyricsConfigOptions options)
public LyricsItemCollection(GlobalLyricsConfigOptions options)
{
Options = options;
}
public static LyricItemCollection operator +(LyricItemCollection left, LyricItemCollection right)
public static LyricsItemCollection operator +(LyricsItemCollection left, LyricsItemCollection right)
{
if (right.IsPruneMusic)
{
@@ -29,7 +29,7 @@ namespace ZonyLrcTools.Common.Lyrics
}
var option = left.Options;
var newCollection = new LyricItemCollection(option);
var newCollection = new LyricsItemCollection(option);
var indexDiff = left.Count - right.Count;
if (!option.IsOneLine)
{
@@ -91,7 +91,7 @@ namespace ZonyLrcTools.Common.Lyrics
/// 这个索引字典用于标识每个索引的歌词是否被处理,为 True 则为已处理,为 False 为未处理。
/// </remarks>
/// <param name="items">等待构建的歌词集合实例。</param>
private static Dictionary<int, bool> BuildMarkDictionary(LyricItemCollection items)
private static Dictionary<int, bool> BuildMarkDictionary(LyricsItemCollection items)
{
return items
.Select((item, index) => new { index, item })

View File

@@ -17,9 +17,9 @@ namespace ZonyLrcTools.Common.Lyrics
_options = options.Value;
}
public LyricItemCollection Build(string sourceLyric)
public LyricsItemCollection Build(string sourceLyric)
{
var lyric = new LyricItemCollection(_options.Provider.Lyric.Config);
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
if (string.IsNullOrEmpty(sourceLyric))
{
return lyric;
@@ -30,9 +30,9 @@ namespace ZonyLrcTools.Common.Lyrics
return lyric;
}
public LyricItemCollection Build(string sourceLyric, string translationLyric)
public LyricsItemCollection Build(string sourceLyric, string translationLyric)
{
var lyric = new LyricItemCollection(_options.Provider.Lyric.Config);
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
if (string.IsNullOrEmpty(sourceLyric))
{
return lyric;
@@ -42,7 +42,7 @@ namespace ZonyLrcTools.Common.Lyrics
if (_options.Provider.Lyric.Config.IsEnableTranslation && !string.IsNullOrEmpty(translationLyric))
{
var translatedLyric = InternalBuildLyricObject(new LyricItemCollection(_options.Provider.Lyric.Config), translationLyric);
var translatedLyric = InternalBuildLyricObject(new LyricsItemCollection(_options.Provider.Lyric.Config), translationLyric);
if (_options.Provider.Lyric.Config.IsOnlyOutputTranslation)
{
return translatedLyric;
@@ -54,15 +54,15 @@ namespace ZonyLrcTools.Common.Lyrics
return lyric;
}
private LyricItemCollection InternalBuildLyricObject(LyricItemCollection lyric, string sourceText)
private LyricsItemCollection InternalBuildLyricObject(LyricsItemCollection lyrics, string sourceText)
{
var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?");
foreach (Match match in regex.Matches(sourceText))
{
lyric.Add(new LyricsItem(match.Value));
lyrics.Add(new LyricsItem(match.Value));
}
return lyric;
return lyrics;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace ZonyLrcTools.Common.Lyrics
/// <param name="artist">歌曲作者/艺术家。</param>
/// <param name="duration">歌曲的时长。</param>
/// <returns>下载完成的歌曲数据。</returns>
public virtual async ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist, long? duration = null)
public virtual async ValueTask<LyricsItemCollection> DownloadAsync(string songName, string artist, long? duration = null)
{
var args = new LyricsProviderArgs(songName, artist, duration ?? 0);
await ValidateAsync(args);
@@ -53,6 +53,6 @@ namespace ZonyLrcTools.Common.Lyrics
/// 根据指定的歌词二进制数据,生成歌词数据。
/// </summary>
/// <param name="data">歌词的原始二进制数据。</param>
protected abstract ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args);
protected abstract ValueTask<LyricsItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args);
}
}

View File

@@ -47,7 +47,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
return Encoding.UTF8.GetBytes(lyricResponse);
}
protected override async ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
{
await ValueTask.CompletedTask;
var lyricJsonObj = JObject.Parse(Encoding.UTF8.GetString(data));

View File

@@ -57,19 +57,19 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
return Encoding.UTF8.GetBytes(lyricResponse);
}
protected override async ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
{
await ValueTask.CompletedTask;
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data));
if (json?.OriginalLyric == null || string.IsNullOrEmpty(json.OriginalLyric.Text))
{
return new LyricItemCollection(null);
return new LyricsItemCollection(null);
}
if (json.OriginalLyric.Text.Contains("纯音乐,请欣赏"))
{
return new LyricItemCollection(null);
return new LyricsItemCollection(null);
}
return _lyricsItemCollectionFactory.Build(

View File

@@ -42,7 +42,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic
return Encoding.UTF8.GetBytes(lyricJsonString);
}
protected override async ValueTask<LyricItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(byte[] data, LyricsProviderArgs args)
{
await ValueTask.CompletedTask;