From fb1f743365bc534e3f4fbe01ee26af0daea295ed Mon Sep 17 00:00:00 2001 From: real-zony Date: Sun, 12 Mar 2023 15:20:55 +0800 Subject: [PATCH] feat: Added support for reading song information from CSV files and NetEase Cloud Music playlists. --- .../Commands/SubCommand/DownloadCommand.cs | 78 ++++++++++++++++++- .../MusicScannerOptions/MusicScannerConsts.cs | 10 +++ src/ZonyLrcTools.Cli/Program.cs | 2 +- .../MusicScanner/CsvFileMusicScanner.cs | 8 +- .../MusicScanner/ManualDownloadOptions.cs | 7 -- .../NetEaseMusicSongListMusicScanner.cs | 4 +- .../MusicScanner/CsvFileMusicScannerTests.cs | 2 +- .../NetEaseMusicSongListMusicScannerTests.cs | 2 +- 8 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 src/ZonyLrcTools.Cli/Infrastructure/MusicScannerOptions/MusicScannerConsts.cs delete mode 100644 src/ZonyLrcTools.Common/MusicScanner/ManualDownloadOptions.cs diff --git a/src/ZonyLrcTools.Cli/Commands/SubCommand/DownloadCommand.cs b/src/ZonyLrcTools.Cli/Commands/SubCommand/DownloadCommand.cs index 7b30270..9576214 100644 --- a/src/ZonyLrcTools.Cli/Commands/SubCommand/DownloadCommand.cs +++ b/src/ZonyLrcTools.Cli/Commands/SubCommand/DownloadCommand.cs @@ -1,8 +1,14 @@ +using System; +using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.DependencyInjection; +using ZonyLrcTools.Cli.Infrastructure.MusicScannerOptions; using ZonyLrcTools.Common; using ZonyLrcTools.Common.Album; using ZonyLrcTools.Common.Lyrics; +using ZonyLrcTools.Common.MusicScanner; // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable MemberCanBePrivate.Global @@ -15,14 +21,17 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand private readonly ILyricsDownloader _lyricsDownloader; private readonly IAlbumDownloader _albumDownloader; private readonly IMusicInfoLoader _musicInfoLoader; + private readonly IServiceProvider _serviceProvider; public DownloadCommand(ILyricsDownloader lyricsDownloader, IMusicInfoLoader musicInfoLoader, - IAlbumDownloader albumDownloader) + IAlbumDownloader albumDownloader, + IServiceProvider serviceProvider) { _lyricsDownloader = lyricsDownloader; _musicInfoLoader = musicInfoLoader; _albumDownloader = albumDownloader; + _serviceProvider = serviceProvider; } #region > Options < @@ -42,19 +51,82 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand #endregion + #region > Scanner Options < + + [Option("-sc|--scanner", CommandOptionType.SingleValue, Description = "指定歌词文件扫描器,目前支持本地文件(local),网易云音乐(netease),csv 文件(csv),默认值为 local。")] + public string Scanner { get; set; } = "local"; + + [Option("-o|--output", Description = "指定歌词文件的输出路径。")] + public string OutputDirectory { get; set; } = "DownloadedLrc"; + + [Option("-p|--pattern", Description = "指定歌词文件的输出文件名模式。")] + public string OutputFileNamePattern { get; set; } = "{Artist} - {Name}.lrc"; + + [Option("-f|--file", Description = "指定 CSV 文件的路径。")] + public string CsvFilePath { get; set; } + + [Option("-s|--song-list-id", Description = "指定网易云音乐歌单的 ID。")] + public string SongListId { get; set; } + + #endregion + protected override async Task OnExecuteAsync(CommandLineApplication app) { if (DownloadLyric) { - await _lyricsDownloader.DownloadAsync(await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber), ParallelNumber); + await _lyricsDownloader.DownloadAsync(await GetMusicInfosAsync(Scanner), ParallelNumber); } if (DownloadAlbum) { - await _albumDownloader.DownloadAsync(await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber), ParallelNumber); + await _albumDownloader.DownloadAsync(await GetMusicInfosAsync(Scanner), ParallelNumber); } return 0; } + + /// + /// Get the music infos by the scanner. + /// + private async Task> GetMusicInfosAsync(string scanner) + { + ValidateScannerOptions(scanner); + + return scanner switch + { + MusicScannerConsts.LocalScanner => await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber), + MusicScannerConsts.CsvScanner => await _serviceProvider.GetService() + .GetMusicInfoFromCsvFileAsync(CsvFilePath, OutputDirectory, OutputFileNamePattern), + MusicScannerConsts.NeteaseScanner => await _serviceProvider.GetService() + .GetMusicInfoFromNetEaseMusicSongListAsync(SongListId, OutputDirectory, OutputFileNamePattern), + _ => await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber) + }; + } + + /// + /// Manually validate the options. + /// + /// Scanner Name. + /// If the options are invalid. + private void ValidateScannerOptions(string scanner) + { + if (scanner != MusicScannerConsts.LocalScanner && string.IsNullOrEmpty(OutputDirectory)) + { + throw new ArgumentException("当使用非本地文件扫描器时,必须指定歌词文件的输出路径。"); + } + + if (scanner != MusicScannerConsts.LocalScanner && !Directory.Exists(OutputDirectory)) + { + throw new ArgumentException("指定的歌词文件输出路径不存在。"); + } + + switch (scanner) + { + case MusicScannerConsts.CsvScanner when string.IsNullOrWhiteSpace(CsvFilePath): + throw new ArgumentException("当使用 CSV 文件扫描器时,必须指定 CSV 文件的路径。"); + case MusicScannerConsts.NeteaseScanner when string.IsNullOrWhiteSpace(SongListId): + throw new ArgumentException("当使用网易云音乐扫描器时,必须指定歌单的 ID。"); + } + } } } \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Infrastructure/MusicScannerOptions/MusicScannerConsts.cs b/src/ZonyLrcTools.Cli/Infrastructure/MusicScannerOptions/MusicScannerConsts.cs new file mode 100644 index 0000000..c352390 --- /dev/null +++ b/src/ZonyLrcTools.Cli/Infrastructure/MusicScannerOptions/MusicScannerConsts.cs @@ -0,0 +1,10 @@ +// ReSharper disable IdentifierTypo + +namespace ZonyLrcTools.Cli.Infrastructure.MusicScannerOptions; + +public class MusicScannerConsts +{ + public const string LocalScanner = "local"; + public const string NeteaseScanner = "netease"; + public const string CsvScanner = "csv"; +} \ No newline at end of file diff --git a/src/ZonyLrcTools.Cli/Program.cs b/src/ZonyLrcTools.Cli/Program.cs index bb87d41..8ca0fc3 100644 --- a/src/ZonyLrcTools.Cli/Program.cs +++ b/src/ZonyLrcTools.Cli/Program.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; using Serilog.Events; -using Serilog.Sinks.SystemConsole.Themes; using ZonyLrcTools.Cli.Commands; using ZonyLrcTools.Cli.Commands.SubCommand; using ZonyLrcTools.Cli.Infrastructure; @@ -33,6 +32,7 @@ namespace ZonyLrcTools.Cli try { + Log.Logger.Information("Starting..."); return await BuildHostedService(args); } catch (Exception ex) diff --git a/src/ZonyLrcTools.Common/MusicScanner/CsvFileMusicScanner.cs b/src/ZonyLrcTools.Common/MusicScanner/CsvFileMusicScanner.cs index c222567..8c6e3f4 100644 --- a/src/ZonyLrcTools.Common/MusicScanner/CsvFileMusicScanner.cs +++ b/src/ZonyLrcTools.Common/MusicScanner/CsvFileMusicScanner.cs @@ -4,19 +4,19 @@ namespace ZonyLrcTools.Common.MusicScanner; public class CsvFileMusicScanner : ITransientDependency { - public async Task> GetMusicInfoFromCsvFileAsync(string csvFilePath, ManualDownloadOptions options) + public async Task> GetMusicInfoFromCsvFileAsync(string csvFilePath, string outputDirectory, string pattern) { var csvFileContent = await File.ReadAllTextAsync(csvFilePath); var csvFileLines = csvFileContent.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - return csvFileLines.Skip(1).Select(line => GetMusicInfoFromCsvFileLine(line, options)).ToList(); + return csvFileLines.Skip(1).Select(line => GetMusicInfoFromCsvFileLine(line, outputDirectory, pattern)).ToList(); } - private MusicInfo GetMusicInfoFromCsvFileLine(string csvFileLine, ManualDownloadOptions options) + private MusicInfo GetMusicInfoFromCsvFileLine(string csvFileLine, string outputDirectory, string pattern) { var csvFileLineItems = csvFileLine.Split(','); var name = csvFileLineItems[0]; var artist = csvFileLineItems[1]; - var fakeFilePath = Path.Combine(options.OutputDirectory, options.OutputFileNamePattern.Replace("{Name}", name).Replace("{Artist}", artist)); + var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", name).Replace("{Artist}", artist)); var musicInfo = new MusicInfo(fakeFilePath, name, artist); return musicInfo; } diff --git a/src/ZonyLrcTools.Common/MusicScanner/ManualDownloadOptions.cs b/src/ZonyLrcTools.Common/MusicScanner/ManualDownloadOptions.cs deleted file mode 100644 index 113ede0..0000000 --- a/src/ZonyLrcTools.Common/MusicScanner/ManualDownloadOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ZonyLrcTools.Common.MusicScanner; - -public class ManualDownloadOptions -{ - public string OutputFileNamePattern { get; set; } = "{Artist} - {Name}.lrc"; - public string OutputDirectory { get; set; } = "DownloadedLrc"; -} \ No newline at end of file diff --git a/src/ZonyLrcTools.Common/MusicScanner/NetEaseMusicSongListMusicScanner.cs b/src/ZonyLrcTools.Common/MusicScanner/NetEaseMusicSongListMusicScanner.cs index f88e438..44cf038 100644 --- a/src/ZonyLrcTools.Common/MusicScanner/NetEaseMusicSongListMusicScanner.cs +++ b/src/ZonyLrcTools.Common/MusicScanner/NetEaseMusicSongListMusicScanner.cs @@ -17,7 +17,7 @@ public class NetEaseMusicSongListMusicScanner : ITransientDependency _warpHttpClient = warpHttpClient; } - public async Task> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, ManualDownloadOptions options) + public async Task> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, string outputDirectory, string pattern) { var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16); var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey); @@ -47,7 +47,7 @@ public class NetEaseMusicSongListMusicScanner : ITransientDependency .Select(song => { var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty; - var fakeFilePath = Path.Combine(options.OutputDirectory, options.OutputFileNamePattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName)); + var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName)); var info = new MusicInfo(fakeFilePath, song.Name!, artistName); return info; diff --git a/tests/ZonyLrcTools.Tests/MusicScanner/CsvFileMusicScannerTests.cs b/tests/ZonyLrcTools.Tests/MusicScanner/CsvFileMusicScannerTests.cs index 778411b..6ef8063 100644 --- a/tests/ZonyLrcTools.Tests/MusicScanner/CsvFileMusicScannerTests.cs +++ b/tests/ZonyLrcTools.Tests/MusicScanner/CsvFileMusicScannerTests.cs @@ -12,7 +12,7 @@ public class CsvFileMusicScannerTests : TestBase public async Task GetMusicInfoFromCsvFileAsync_Test() { var musicScanner = GetService(); - var musicInfo = await musicScanner.GetMusicInfoFromCsvFileAsync(Path.Combine("TestData", "test.csv"), new ManualDownloadOptions()); + var musicInfo = await musicScanner.GetMusicInfoFromCsvFileAsync(Path.Combine("TestData", "test.csv"), "DownloadedLrc", "{Artist} - {Name}.lrc"); musicInfo.ShouldNotBeNull(); musicInfo.Count.ShouldBeGreaterThan(0); diff --git a/tests/ZonyLrcTools.Tests/MusicScanner/NetEaseMusicSongListMusicScannerTests.cs b/tests/ZonyLrcTools.Tests/MusicScanner/NetEaseMusicSongListMusicScannerTests.cs index 59e4b93..62c52cd 100644 --- a/tests/ZonyLrcTools.Tests/MusicScanner/NetEaseMusicSongListMusicScannerTests.cs +++ b/tests/ZonyLrcTools.Tests/MusicScanner/NetEaseMusicSongListMusicScannerTests.cs @@ -11,7 +11,7 @@ public class NetEaseMusicSongListMusicScannerTests : TestBase public async Task GetMusicInfoFromNetEaseMusicSongListAsync_Test() { var musicScanner = GetService(); - var musicInfo = await musicScanner.GetMusicInfoFromNetEaseMusicSongListAsync("7224428149", new ManualDownloadOptions()); + var musicInfo = await musicScanner.GetMusicInfoFromNetEaseMusicSongListAsync("7224428149", "DownloadedLrc", "{Artist} - {Name}.lrc"); musicInfo.ShouldNotBeNull(); musicInfo.Count.ShouldBeGreaterThan(10);