mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-06 05:36:53 +00:00
feat: Reinitialize the Repository.
重新初始化仓库。
This commit is contained in:
196
src/ZonyLrcTools.Cli/Commands/DownloadCommand.cs
Normal file
196
src/ZonyLrcTools.Cli/Commands/DownloadCommand.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Album;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Extensions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.IO;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Threading;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
[Command("download", Description = "下载歌词文件或专辑图像。")]
|
||||
public class DownloadCommand : ToolCommandBase
|
||||
{
|
||||
private readonly ILogger<DownloadCommand> _logger;
|
||||
private readonly IFileScanner _fileScanner;
|
||||
private readonly ITagLoader _tagLoader;
|
||||
private readonly IEnumerable<ILyricDownloader> _lyricDownloaderList;
|
||||
private readonly IEnumerable<IAlbumDownloader> _albumDownloaderList;
|
||||
|
||||
private readonly ToolOptions _options;
|
||||
|
||||
public DownloadCommand(ILogger<DownloadCommand> logger,
|
||||
IFileScanner fileScanner,
|
||||
IOptions<ToolOptions> options,
|
||||
ITagLoader tagLoader,
|
||||
IEnumerable<ILyricDownloader> lyricDownloaderList,
|
||||
IEnumerable<IAlbumDownloader> albumDownloaderList)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileScanner = fileScanner;
|
||||
_tagLoader = tagLoader;
|
||||
_lyricDownloaderList = lyricDownloaderList;
|
||||
_albumDownloaderList = albumDownloaderList;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
#region > Options <
|
||||
|
||||
[Option("-d|--dir", CommandOptionType.SingleValue, Description = "指定需要扫描的目录。")]
|
||||
[DirectoryExists]
|
||||
public string Directory { get; set; }
|
||||
|
||||
[Option("-l|--lyric", CommandOptionType.NoValue, Description = "指定程序需要下载歌词文件。")]
|
||||
public bool DownloadLyric { get; set; }
|
||||
|
||||
[Option("-a|--album", CommandOptionType.NoValue, Description = "指定程序需要下载专辑图像。")]
|
||||
public bool DownloadAlbum { get; set; }
|
||||
|
||||
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
|
||||
public int ParallelNumber { get; set; } = 2;
|
||||
|
||||
#endregion
|
||||
|
||||
public override List<string> CreateArgs() => new();
|
||||
|
||||
protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
var files = await ScanMusicFilesAsync();
|
||||
var musicInfos = await LoadMusicInfoAsync(files);
|
||||
|
||||
if (DownloadLyric)
|
||||
{
|
||||
await DownloadLyricFilesAsync(musicInfos);
|
||||
}
|
||||
|
||||
if (DownloadAlbum)
|
||||
{
|
||||
await DownloadAlbumAsync(musicInfos);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ScanMusicFilesAsync()
|
||||
{
|
||||
var files = (await _fileScanner.ScanAsync(Directory, _options.SupportFileExtensions.Split(';')))
|
||||
.SelectMany(t => t.FilePaths)
|
||||
.ToList();
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
_logger.LogError("没有找到任何音乐文件。");
|
||||
throw new ErrorCodeException(ErrorCodes.NoFilesWereScanned);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"已经扫描到了 {files.Count} 个音乐文件。");
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private async Task<ImmutableList<MusicInfo>> LoadMusicInfoAsync(IReadOnlyCollection<string> files)
|
||||
{
|
||||
_logger.LogInformation("开始加载音乐文件的标签信息...");
|
||||
|
||||
var warpTask = new WarpTask(ParallelNumber);
|
||||
var warpTaskList = files.Select(file => warpTask.RunAsync(() => Task.Run(async () => await _tagLoader.LoadTagAsync(file))));
|
||||
var result = await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"已成功加载 {files.Count} 个音乐文件的标签信息。");
|
||||
|
||||
return result.ToImmutableList();
|
||||
}
|
||||
|
||||
#region > 歌词下载逻辑 <
|
||||
|
||||
private async ValueTask DownloadLyricFilesAsync(ImmutableList<MusicInfo> musicInfos)
|
||||
{
|
||||
_logger.LogInformation("开始下载歌词文件数据...");
|
||||
|
||||
var downloader = _lyricDownloaderList.FirstOrDefault(d => d.DownloaderName == InternalLyricDownloaderNames.NetEase);
|
||||
var warpTask = new WarpTask(ParallelNumber);
|
||||
var warpTaskList = musicInfos.Select(info =>
|
||||
warpTask.RunAsync(() => Task.Run(async () => await DownloadLyricTaskLogicAsync(downloader, info))));
|
||||
|
||||
await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"歌词数据下载完成,成功: {musicInfos.Count} 失败{0}。");
|
||||
}
|
||||
|
||||
private async Task DownloadLyricTaskLogicAsync(ILyricDownloader downloader, MusicInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lyric = await downloader.DownloadAsync(info.Name, info.Artist);
|
||||
var filePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!, $"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (lyric.IsPruneMusic)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using var stream = new FileStream(filePath, FileMode.Create);
|
||||
await using var sw = new StreamWriter(stream);
|
||||
await sw.WriteAsync(lyric.ToString());
|
||||
await sw.FlushAsync();
|
||||
}
|
||||
catch (ErrorCodeException ex)
|
||||
{
|
||||
_logger.LogWarningInfo(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region > 专辑图像下载逻辑 <
|
||||
|
||||
private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos)
|
||||
{
|
||||
_logger.LogInformation("开始下载专辑图像数据...");
|
||||
|
||||
var downloader = _albumDownloaderList.FirstOrDefault(d => d.DownloaderName == InternalAlbumDownloaderNames.NetEase);
|
||||
var warpTask = new WarpTask(ParallelNumber);
|
||||
var warpTaskList = musicInfos.Select(info =>
|
||||
warpTask.RunAsync(() => Task.Run(async () => await DownloadAlbumTaskLogicAsync(downloader, info))));
|
||||
|
||||
await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"专辑数据下载完成,成功: {musicInfos.Count} 失败{0}。");
|
||||
}
|
||||
|
||||
private async Task DownloadAlbumTaskLogicAsync(IAlbumDownloader downloader, MusicInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
var album = await downloader.DownloadAsync(info.Name, info.Artist);
|
||||
var filePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!, $"{Path.GetFileNameWithoutExtension(info.FilePath)}.png");
|
||||
if (File.Exists(filePath) || album.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await new FileStream(filePath, FileMode.Create).WriteBytesToFileAsync(album, 1024);
|
||||
}
|
||||
catch (ErrorCodeException ex)
|
||||
{
|
||||
_logger.LogWarningInfo(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
47
src/ZonyLrcTools.Cli/Commands/ScanCommand.cs
Normal file
47
src/ZonyLrcTools.Cli/Commands/ScanCommand.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.IO;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
[Command("scan", Description = "扫描指定目录下符合条件的音乐文件数量。")]
|
||||
public class ScanCommand : ToolCommandBase
|
||||
{
|
||||
private readonly IFileScanner _fileScanner;
|
||||
private readonly ToolOptions _options;
|
||||
private readonly ILogger<ScanCommand> _logger;
|
||||
|
||||
public ScanCommand(IFileScanner fileScanner,
|
||||
IOptions<ToolOptions> options,
|
||||
ILogger<ScanCommand> logger)
|
||||
{
|
||||
_fileScanner = fileScanner;
|
||||
_logger = logger;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
[Option("-d|--dir", CommandOptionType.SingleValue, Description = "指定需要扫描的目录。")]
|
||||
[DirectoryExists]
|
||||
public string DirectoryPath { get; set; }
|
||||
|
||||
protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
var result = await _fileScanner.ScanAsync(
|
||||
DirectoryPath,
|
||||
_options.SupportFileExtensions.Split(';'));
|
||||
|
||||
_logger.LogInformation($"目录扫描完成,共扫描到 {result.Sum(f => f.FilePaths.Count)} 个音乐文件。");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override List<string> CreateArgs()
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
113
src/ZonyLrcTools.Cli/Commands/ToolCommand.cs
Normal file
113
src/ZonyLrcTools.Cli/Commands/ToolCommand.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Extensions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
[Command("lyric-tool")]
|
||||
[Subcommand(typeof(ScanCommand), typeof(DownloadCommand))]
|
||||
public class ToolCommand : ToolCommandBase
|
||||
{
|
||||
public override List<string> CreateArgs()
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
ConfigureLogger();
|
||||
ConfigureErrorMessage();
|
||||
|
||||
try
|
||||
{
|
||||
return await BuildHostedService(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return HandleException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
#region > 程序初始化配置 <
|
||||
|
||||
private static void ConfigureErrorMessage() => ErrorCodeHelper.LoadErrorMessage();
|
||||
|
||||
private static void ConfigureLogger()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
#if DEBUG
|
||||
.MinimumLevel.Debug()
|
||||
#else
|
||||
.MinimumLevel.Information()
|
||||
#endif
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Async(c => c.Console())
|
||||
.WriteTo.Logger(lc =>
|
||||
{
|
||||
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Warning)
|
||||
.WriteTo.Async(c => c.File("Logs/warnings.txt"));
|
||||
})
|
||||
.WriteTo.Logger(lc =>
|
||||
{
|
||||
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Error)
|
||||
.WriteTo.Async(c => c.File("Logs/errors.txt"));
|
||||
})
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
private static Task<int> BuildHostedService(string[] args)
|
||||
{
|
||||
return new HostBuilder()
|
||||
.ConfigureLogging(builder => builder.AddSerilog())
|
||||
.ConfigureHostConfiguration(builder =>
|
||||
{
|
||||
builder
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json");
|
||||
})
|
||||
.ConfigureServices((_, services) =>
|
||||
{
|
||||
services.AddSingleton(PhysicalConsole.Singleton);
|
||||
services.BeginAutoDependencyInject<ToolCommand>();
|
||||
services.ConfigureConfiguration();
|
||||
services.ConfigureToolService();
|
||||
})
|
||||
.RunCommandLineApplicationAsync<ToolCommand>(args);
|
||||
}
|
||||
|
||||
private static int HandleException(Exception ex)
|
||||
{
|
||||
switch (ex)
|
||||
{
|
||||
case ErrorCodeException exception:
|
||||
Log.Logger.Error($"出现了未处理的异常,错误代码: {exception.ErrorCode},错误信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}\n调用栈:\n{exception.StackTrace}");
|
||||
Environment.Exit(exception.ErrorCode);
|
||||
return exception.ErrorCode;
|
||||
case Exception unknownException:
|
||||
Log.Logger.Error($"出现了未处理的异常: {unknownException.Message}\n调用栈:\n{unknownException.StackTrace}");
|
||||
Environment.Exit(-1);
|
||||
return 1;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
17
src/ZonyLrcTools.Cli/Commands/ToolCommandBase.cs
Normal file
17
src/ZonyLrcTools.Cli/Commands/ToolCommandBase.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
[HelpOption("--help|-h", Description = "欢迎使用 ZonyLrcToolsX Command Line Interface。")]
|
||||
public abstract class ToolCommandBase
|
||||
{
|
||||
public abstract List<string> CreateArgs();
|
||||
|
||||
protected virtual Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
20
src/ZonyLrcTools.Cli/Commands/UtilityCommand.cs
Normal file
20
src/ZonyLrcTools.Cli/Commands/UtilityCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具类相关命令。
|
||||
/// </summary>
|
||||
[Command("util", Description = "提供常用的工具类功能。")]
|
||||
public class UtilityCommand : ToolCommandBase
|
||||
{
|
||||
public override List<string> CreateArgs() => new();
|
||||
|
||||
protected override Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
return base.OnExecuteAsync(app);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user