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

@ -1,12 +1,9 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using ZonyLrcTools.Cli.Infrastructure.Tag; using ZonyLrcTools.Cli.Infrastructure.Tag;
using ZonyLrcTools.Common; using ZonyLrcTools.Common;
@ -15,6 +12,7 @@ using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.Exceptions; using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Extensions; using ZonyLrcTools.Common.Infrastructure.Extensions;
using ZonyLrcTools.Common.Infrastructure.IO; using ZonyLrcTools.Common.Infrastructure.IO;
using ZonyLrcTools.Common.Infrastructure.Logging;
using ZonyLrcTools.Common.Infrastructure.Threading; using ZonyLrcTools.Common.Infrastructure.Threading;
using ZonyLrcTools.Common.Lyrics; using ZonyLrcTools.Common.Lyrics;
using File = System.IO.File; using File = System.IO.File;
@ -24,26 +22,25 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
[Command("download", Description = "下载歌词文件或专辑图像。")] [Command("download", Description = "下载歌词文件或专辑图像。")]
public class DownloadCommand : ToolCommandBase public class DownloadCommand : ToolCommandBase
{ {
private readonly ILogger<DownloadCommand> _logger; private readonly ILyricsDownloader _lyricsDownloader;
private readonly IFileScanner _fileScanner; private readonly IFileScanner _fileScanner;
private readonly ITagLoader _tagLoader;
private readonly IEnumerable<ILyricsProvider> _lyricDownloaderList;
private readonly IEnumerable<IAlbumDownloader> _albumDownloaderList; private readonly IEnumerable<IAlbumDownloader> _albumDownloaderList;
private readonly ITagLoader _tagLoader;
private readonly IWarpLogger _logger;
private readonly GlobalOptions _options; private readonly GlobalOptions _options;
public DownloadCommand(ILogger<DownloadCommand> logger, public DownloadCommand(IFileScanner fileScanner,
IFileScanner fileScanner,
IOptions<GlobalOptions> options, IOptions<GlobalOptions> options,
IEnumerable<IAlbumDownloader> albumDownloaderList,
ITagLoader tagLoader, ITagLoader tagLoader,
IEnumerable<ILyricsProvider> lyricDownloaderList, ILyricsDownloader lyricsDownloader, IWarpLogger logger)
IEnumerable<IAlbumDownloader> albumDownloaderList)
{ {
_logger = logger;
_fileScanner = fileScanner; _fileScanner = fileScanner;
_tagLoader = tagLoader;
_lyricDownloaderList = lyricDownloaderList;
_albumDownloaderList = albumDownloaderList; _albumDownloaderList = albumDownloaderList;
_tagLoader = tagLoader;
_lyricsDownloader = lyricsDownloader;
_logger = logger;
_options = options.Value; _options = options.Value;
} }
@ -62,18 +59,14 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")] [Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
public int ParallelNumber { get; set; } = 2; public int ParallelNumber { get; set; } = 2;
[Option] public string ErrorMessage { get; set; } = Path.Combine(Directory.GetCurrentDirectory(), "error.log");
#endregion #endregion
protected override async Task<int> OnExecuteAsync(CommandLineApplication app) protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
{ {
if (DownloadLyric) if (DownloadLyric)
{ {
await DownloadLyricFilesAsync( var musicInfos = await LoadMusicInfoAsync(RemoveExistLyricFiles(await ScanMusicFilesAsync()));
await LoadMusicInfoAsync( await _lyricsDownloader.DownloadAsync(musicInfos.ToList(), ParallelNumber);
RemoveExistLyricFiles(
await ScanMusicFilesAsync())));
} }
if (DownloadAlbum) if (DownloadAlbum)
@ -94,11 +87,11 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
if (files.Count == 0) if (files.Count == 0)
{ {
_logger.LogError("没有找到任何音乐文件。"); await _logger.ErrorAsync("没有找到任何音乐文件。");
throw new ErrorCodeException(ErrorCodes.NoFilesWereScanned); throw new ErrorCodeException(ErrorCodes.NoFilesWereScanned);
} }
_logger.LogInformation($"已经扫描到了 {files.Count} 个音乐文件。"); await _logger.InfoAsync($"已经扫描到了 {files.Count} 个音乐文件。");
return files; return files;
} }
@ -118,7 +111,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
return true; return true;
} }
_logger.LogWarning($"已经存在歌词文件 {path},跳过。"); _logger.WarnAsync($"已经存在歌词文件 {path},跳过。").GetAwaiter().GetResult();
return false; return false;
}) })
.ToList(); .ToList();
@ -126,7 +119,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
private async Task<ImmutableList<MusicInfo>> LoadMusicInfoAsync(IReadOnlyCollection<string> files) private async Task<ImmutableList<MusicInfo>> LoadMusicInfoAsync(IReadOnlyCollection<string> files)
{ {
_logger.LogInformation("开始加载音乐文件的标签信息..."); await _logger.InfoAsync("开始加载音乐文件的标签信息...");
var warpTask = new WarpTask(ParallelNumber); var warpTask = new WarpTask(ParallelNumber);
var warpTaskList = files.Select(file => warpTask.RunAsync(() => Task.Run(async () => await _tagLoader.LoadTagAsync(file)))); var warpTaskList = files.Select(file => warpTask.RunAsync(() => Task.Run(async () => await _tagLoader.LoadTagAsync(file))));
@ -138,111 +131,16 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
// Load music total time info. // Load music total time info.
// result.Foreach(m => { m.TotalTime = (long?)new AudioFileReader(m.FilePath).TotalTime.TotalMilliseconds; }); // result.Foreach(m => { m.TotalTime = (long?)new AudioFileReader(m.FilePath).TotalTime.TotalMilliseconds; });
_logger.LogInformation($"已成功加载 {files.Count} 个音乐文件的标签信息。"); await _logger.InfoAsync($"已成功加载 {files.Count} 个音乐文件的标签信息。");
return result.ToImmutableList(); return result.ToImmutableList();
} }
private IEnumerable<ILyricsProvider> GetLyricDownloaderList()
{
var downloader = _options.Provider.Lyric.Plugin
.Where(op => op.Priority != -1)
.OrderBy(op => op.Priority)
.Join(_lyricDownloaderList,
op => op.Name,
loader => loader.DownloaderName,
(op, loader) => loader);
return downloader;
}
#region > Lyric download logic <
private async ValueTask DownloadLyricFilesAsync(ImmutableList<MusicInfo> musicInfos)
{
_logger.LogInformation("开始下载歌词文件数据...");
var downloaderList = GetLyricDownloaderList();
var warpTask = new WarpTask(ParallelNumber);
var warpTaskList = musicInfos.Select(info =>
warpTask.RunAsync(() => Task.Run(async () => await DownloadLyricTaskLogicAsync(downloaderList, info))));
await Task.WhenAll(warpTaskList);
_logger.LogInformation($"歌词数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。");
}
private async Task DownloadLyricTaskLogicAsync(IEnumerable<ILyricsProvider> downloaderList, MusicInfo info)
{
async Task InternalDownloadLogicAsync(ILyricsProvider downloader)
{
try
{
var lyric = await downloader.DownloadAsync(info.Name, info.Artist, info.TotalTime);
var lyricFilePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!,
$"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc");
if (File.Exists(lyricFilePath))
{
File.Delete(lyricFilePath);
}
info.IsSuccessful = true;
if (lyric.IsPruneMusic)
{
return;
}
await using var stream = new FileStream(lyricFilePath, FileMode.Create);
await using var sw = new BinaryWriter(stream);
sw.Write(EncodingConvert(lyric));
await stream.FlushAsync();
}
catch (ErrorCodeException ex)
{
info.IsSuccessful = ex.ErrorCode == ErrorCodes.NoMatchingSong;
_logger.LogWarningInfo(ex);
}
catch (Exception ex)
{
_logger.LogError($"下载歌词文件时发生错误:{ex.Message},歌曲名: {info.Name},歌手: {info.Artist}。");
info.IsSuccessful = false;
}
}
foreach (var downloader in downloaderList)
{
await InternalDownloadLogicAsync(downloader);
if (info.IsSuccessful)
{
_logger.LogSuccessful(info);
return;
}
}
}
private byte[] EncodingConvert(LyricItemCollection lyric)
{
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), lyric.GetUtf8Bytes());
}
#endregion
#region > Ablum image download logic < #region > Ablum image download logic <
private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos) private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos)
{ {
_logger.LogInformation("开始下载专辑图像数据..."); await _logger.InfoAsync("开始下载专辑图像数据...");
var downloader = _albumDownloaderList.FirstOrDefault(d => d.DownloaderName == InternalAlbumDownloaderNames.NetEase); var downloader = _albumDownloaderList.FirstOrDefault(d => d.DownloaderName == InternalAlbumDownloaderNames.NetEase);
var warpTask = new WarpTask(ParallelNumber); var warpTask = new WarpTask(ParallelNumber);
@ -251,7 +149,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
await Task.WhenAll(warpTaskList); await Task.WhenAll(warpTaskList);
_logger.LogInformation($"专辑数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。"); await _logger.InfoAsync($"专辑数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。");
} }
private async Task DownloadAlbumTaskLogicAsync(IAlbumDownloader downloader, MusicInfo info) private async Task DownloadAlbumTaskLogicAsync(IAlbumDownloader downloader, MusicInfo info)

View File

@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.Common.Infrastructure.Logging;
namespace ZonyLrcTools.Cli.Infrastructure.Logging;
public class SerilogWarpLogger : IWarpLogger, ITransientDependency
{
private readonly ILogger<SerilogWarpLogger> _logger;
public SerilogWarpLogger(ILogger<SerilogWarpLogger> logger)
{
_logger = logger;
}
public Task DebugAsync(string message, Exception exception = null)
{
_logger.LogDebug(message, exception);
return Task.CompletedTask;
}
public Task InfoAsync(string message, Exception exception = null)
{
_logger.LogInformation(message, exception);
return Task.CompletedTask;
}
public Task WarnAsync(string message, Exception exception = null)
{
_logger.LogWarning(message, exception);
return Task.CompletedTask;
}
public Task ErrorAsync(string message, Exception exception = null)
{
_logger.LogError(message, exception);
return Task.CompletedTask;
}
}

View File

@ -2,6 +2,7 @@ using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using ZonyLrcTools.Common.Infrastructure.Exceptions; using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Logging;
namespace ZonyLrcTools.Common.Infrastructure.Extensions namespace ZonyLrcTools.Common.Infrastructure.Extensions
{ {
@ -16,9 +17,9 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
/// <param name="logger">日志记录器实例。</param> /// <param name="logger">日志记录器实例。</param>
/// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param> /// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param>
/// <param name="e">异常实例,可为空。</param> /// <param name="e">异常实例,可为空。</param>
public static void LogWarningWithErrorCode(this ILogger logger, int errorCode, Exception e = null) public static void LogWarningWithErrorCode(this IWarpLogger logger, int errorCode, Exception e = null)
{ {
logger.LogWarning($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}"); logger.WarnAsync($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}").GetAwaiter().GetResult();
} }
/// <summary> /// <summary>
@ -26,7 +27,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
/// </summary> /// </summary>
/// <param name="logger">日志记录器的实例。</param> /// <param name="logger">日志记录器的实例。</param>
/// <param name="exception">错误码异常实例。</param> /// <param name="exception">错误码异常实例。</param>
public static void LogWarningInfo(this ILogger logger, ErrorCodeException exception) public static void LogWarningInfo(this IWarpLogger logger, ErrorCodeException exception)
{ {
if (exception.ErrorCode < 50000) if (exception.ErrorCode < 50000)
{ {
@ -36,7 +37,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"错误代码: {exception.ErrorCode},信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}"); sb.Append($"错误代码: {exception.ErrorCode},信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}");
sb.Append($"\n附加信息:\n {JsonConvert.SerializeObject(exception.AttachObject)}"); sb.Append($"\n附加信息:\n {JsonConvert.SerializeObject(exception.AttachObject)}");
logger.LogWarning(sb.ToString()); logger.WarnAsync(sb.ToString()).GetAwaiter().GetResult();
} }
/// <summary> /// <summary>
@ -44,9 +45,9 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
/// </summary> /// </summary>
/// <param name="logger">日志记录器的实例。</param> /// <param name="logger">日志记录器的实例。</param>
/// <param name="musicInfo">需要打印的歌曲信息。</param> /// <param name="musicInfo">需要打印的歌曲信息。</param>
public static void LogSuccessful(this ILogger logger, MusicInfo musicInfo) public static void LogSuccessful(this IWarpLogger logger, MusicInfo musicInfo)
{ {
logger.LogInformation($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 下载成功."); logger.InfoAsync($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 下载成功.").GetAwaiter().GetResult();
} }
} }
} }

View File

@ -4,16 +4,17 @@ using Microsoft.Extensions.Logging.Abstractions;
using ZonyLrcTools.Common.Infrastructure.DependencyInject; using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.Common.Infrastructure.Exceptions; using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Extensions; using ZonyLrcTools.Common.Infrastructure.Extensions;
using ZonyLrcTools.Common.Infrastructure.Logging;
namespace ZonyLrcTools.Common.Infrastructure.IO namespace ZonyLrcTools.Common.Infrastructure.IO
{ {
public class FileScanner : IFileScanner, ITransientDependency public class FileScanner : IFileScanner, ITransientDependency
{ {
public ILogger<FileScanner> Logger { get; set; } private readonly IWarpLogger _logger;
public FileScanner() public FileScanner(IWarpLogger logger)
{ {
Logger = NullLogger<FileScanner>.Instance; _logger = logger;
} }
public Task<List<FileScannerResult>> ScanAsync(string path, IEnumerable<string> extensions) public Task<List<FileScannerResult>> ScanAsync(string path, IEnumerable<string> extensions)
@ -58,7 +59,7 @@ namespace ZonyLrcTools.Common.Infrastructure.IO
} }
catch (Exception e) catch (Exception e)
{ {
Logger.LogWarningWithErrorCode(ErrorCodes.ScanFileError, e); _logger.LogWarningWithErrorCode(ErrorCodes.ScanFileError, e);
} }
} }
} }

View File

@ -0,0 +1,9 @@
namespace ZonyLrcTools.Common.Infrastructure.Logging;
public interface IWarpLogger
{
Task DebugAsync(string message, Exception? exception = null);
Task InfoAsync(string message, Exception? exception = null);
Task WarnAsync(string message, Exception? exception = null);
Task ErrorAsync(string message, Exception? exception = null);
}

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

View File

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

View File

@ -2,6 +2,6 @@ namespace ZonyLrcTools.Common.Lyrics
{ {
public interface ILyricsTextResolver 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 namespace ZonyLrcTools.Common.Lyrics
{ {
/// <summary> /// <summary>
/// 歌词的行对象,是 <see cref="LyricItemCollection"/> 的最小单位。。 /// 歌词的行对象,是 <see cref="LyricsItemCollection"/> 的最小单位。。
/// </summary> /// </summary>
public class LyricsItem : IComparable<LyricsItem> public class LyricsItem : IComparable<LyricsItem>
{ {

View File

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

View File

@ -17,9 +17,9 @@ namespace ZonyLrcTools.Common.Lyrics
_options = options.Value; _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)) if (string.IsNullOrEmpty(sourceLyric))
{ {
return lyric; return lyric;
@ -30,9 +30,9 @@ namespace ZonyLrcTools.Common.Lyrics
return lyric; 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)) if (string.IsNullOrEmpty(sourceLyric))
{ {
return lyric; return lyric;
@ -42,7 +42,7 @@ namespace ZonyLrcTools.Common.Lyrics
if (_options.Provider.Lyric.Config.IsEnableTranslation && !string.IsNullOrEmpty(translationLyric)) 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) if (_options.Provider.Lyric.Config.IsOnlyOutputTranslation)
{ {
return translatedLyric; return translatedLyric;
@ -54,15 +54,15 @@ namespace ZonyLrcTools.Common.Lyrics
return lyric; return lyric;
} }
private LyricItemCollection InternalBuildLyricObject(LyricItemCollection lyric, string sourceText) private LyricsItemCollection InternalBuildLyricObject(LyricsItemCollection lyrics, string sourceText)
{ {
var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?"); var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?");
foreach (Match match in regex.Matches(sourceText)) 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="artist">歌曲作者/艺术家。</param>
/// <param name="duration">歌曲的时长。</param> /// <param name="duration">歌曲的时长。</param>
/// <returns>下载完成的歌曲数据。</returns> /// <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); var args = new LyricsProviderArgs(songName, artist, duration ?? 0);
await ValidateAsync(args); await ValidateAsync(args);
@ -53,6 +53,6 @@ namespace ZonyLrcTools.Common.Lyrics
/// 根据指定的歌词二进制数据,生成歌词数据。 /// 根据指定的歌词二进制数据,生成歌词数据。
/// </summary> /// </summary>
/// <param name="data">歌词的原始二进制数据。</param> /// <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); 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; await ValueTask.CompletedTask;
var lyricJsonObj = JObject.Parse(Encoding.UTF8.GetString(data)); 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); 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; await ValueTask.CompletedTask;
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data)); var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data));
if (json?.OriginalLyric == null || string.IsNullOrEmpty(json.OriginalLyric.Text)) if (json?.OriginalLyric == null || string.IsNullOrEmpty(json.OriginalLyric.Text))
{ {
return new LyricItemCollection(null); return new LyricsItemCollection(null);
} }
if (json.OriginalLyric.Text.Contains("纯音乐,请欣赏")) if (json.OriginalLyric.Text.Contains("纯音乐,请欣赏"))
{ {
return new LyricItemCollection(null); return new LyricsItemCollection(null);
} }
return _lyricsItemCollectionFactory.Build( return _lyricsItemCollectionFactory.Build(

View File

@ -42,7 +42,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic
return Encoding.UTF8.GetBytes(lyricJsonString); 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; await ValueTask.CompletedTask;

View File

@ -10,7 +10,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
[Fact] [Fact]
public void LyricCollectionLineBreak_Test() public void LyricCollectionLineBreak_Test()
{ {
var lyricObject = new LyricItemCollection(new GlobalLyricsConfigOptions var lyricObject = new LyricsItemCollection(new GlobalLyricsConfigOptions
{ {
IsOneLine = false, IsOneLine = false,
LineBreak = LineBreakType.MacOs LineBreak = LineBreakType.MacOs