feat: Added support for reading song information from CSV files and NetEase Cloud Music playlists.

This commit is contained in:
real-zony 2023-03-12 15:20:55 +08:00
parent 0e5e48cd00
commit fb1f743365
8 changed files with 94 additions and 19 deletions

View File

@ -1,8 +1,14 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.DependencyInjection;
using ZonyLrcTools.Cli.Infrastructure.MusicScannerOptions;
using ZonyLrcTools.Common; using ZonyLrcTools.Common;
using ZonyLrcTools.Common.Album; using ZonyLrcTools.Common.Album;
using ZonyLrcTools.Common.Lyrics; using ZonyLrcTools.Common.Lyrics;
using ZonyLrcTools.Common.MusicScanner;
// ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
@ -15,14 +21,17 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
private readonly ILyricsDownloader _lyricsDownloader; private readonly ILyricsDownloader _lyricsDownloader;
private readonly IAlbumDownloader _albumDownloader; private readonly IAlbumDownloader _albumDownloader;
private readonly IMusicInfoLoader _musicInfoLoader; private readonly IMusicInfoLoader _musicInfoLoader;
private readonly IServiceProvider _serviceProvider;
public DownloadCommand(ILyricsDownloader lyricsDownloader, public DownloadCommand(ILyricsDownloader lyricsDownloader,
IMusicInfoLoader musicInfoLoader, IMusicInfoLoader musicInfoLoader,
IAlbumDownloader albumDownloader) IAlbumDownloader albumDownloader,
IServiceProvider serviceProvider)
{ {
_lyricsDownloader = lyricsDownloader; _lyricsDownloader = lyricsDownloader;
_musicInfoLoader = musicInfoLoader; _musicInfoLoader = musicInfoLoader;
_albumDownloader = albumDownloader; _albumDownloader = albumDownloader;
_serviceProvider = serviceProvider;
} }
#region > Options < #region > Options <
@ -42,19 +51,82 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
#endregion #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<int> OnExecuteAsync(CommandLineApplication app) protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
{ {
if (DownloadLyric) if (DownloadLyric)
{ {
await _lyricsDownloader.DownloadAsync(await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber), ParallelNumber); await _lyricsDownloader.DownloadAsync(await GetMusicInfosAsync(Scanner), ParallelNumber);
} }
if (DownloadAlbum) if (DownloadAlbum)
{ {
await _albumDownloader.DownloadAsync(await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber), ParallelNumber); await _albumDownloader.DownloadAsync(await GetMusicInfosAsync(Scanner), ParallelNumber);
} }
return 0; return 0;
} }
/// <summary>
/// Get the music infos by the scanner.
/// </summary>
private async Task<List<MusicInfo>> GetMusicInfosAsync(string scanner)
{
ValidateScannerOptions(scanner);
return scanner switch
{
MusicScannerConsts.LocalScanner => await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber),
MusicScannerConsts.CsvScanner => await _serviceProvider.GetService<CsvFileMusicScanner>()
.GetMusicInfoFromCsvFileAsync(CsvFilePath, OutputDirectory, OutputFileNamePattern),
MusicScannerConsts.NeteaseScanner => await _serviceProvider.GetService<NetEaseMusicSongListMusicScanner>()
.GetMusicInfoFromNetEaseMusicSongListAsync(SongListId, OutputDirectory, OutputFileNamePattern),
_ => await _musicInfoLoader.LoadAsync(SongsDirectory, ParallelNumber)
};
}
/// <summary>
/// Manually validate the options.
/// </summary>
/// <param name="scanner">Scanner Name.</param>
/// <exception cref="ArgumentException">If the options are invalid.</exception>
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。");
}
}
} }
} }

View File

@ -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";
}

View File

@ -8,7 +8,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
using ZonyLrcTools.Cli.Commands; using ZonyLrcTools.Cli.Commands;
using ZonyLrcTools.Cli.Commands.SubCommand; using ZonyLrcTools.Cli.Commands.SubCommand;
using ZonyLrcTools.Cli.Infrastructure; using ZonyLrcTools.Cli.Infrastructure;
@ -33,6 +32,7 @@ namespace ZonyLrcTools.Cli
try try
{ {
Log.Logger.Information("Starting...");
return await BuildHostedService(args); return await BuildHostedService(args);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -4,19 +4,19 @@ namespace ZonyLrcTools.Common.MusicScanner;
public class CsvFileMusicScanner : ITransientDependency public class CsvFileMusicScanner : ITransientDependency
{ {
public async Task<List<MusicInfo>> GetMusicInfoFromCsvFileAsync(string csvFilePath, ManualDownloadOptions options) public async Task<List<MusicInfo>> GetMusicInfoFromCsvFileAsync(string csvFilePath, string outputDirectory, string pattern)
{ {
var csvFileContent = await File.ReadAllTextAsync(csvFilePath); var csvFileContent = await File.ReadAllTextAsync(csvFilePath);
var csvFileLines = csvFileContent.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); 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 csvFileLineItems = csvFileLine.Split(',');
var name = csvFileLineItems[0]; var name = csvFileLineItems[0];
var artist = csvFileLineItems[1]; 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); var musicInfo = new MusicInfo(fakeFilePath, name, artist);
return musicInfo; return musicInfo;
} }

View File

@ -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";
}

View File

@ -17,7 +17,7 @@ public class NetEaseMusicSongListMusicScanner : ITransientDependency
_warpHttpClient = warpHttpClient; _warpHttpClient = warpHttpClient;
} }
public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, ManualDownloadOptions options) public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, string outputDirectory, string pattern)
{ {
var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16); var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey); var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
@ -47,7 +47,7 @@ public class NetEaseMusicSongListMusicScanner : ITransientDependency
.Select(song => .Select(song =>
{ {
var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty; 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); var info = new MusicInfo(fakeFilePath, song.Name!, artistName);
return info; return info;

View File

@ -12,7 +12,7 @@ public class CsvFileMusicScannerTests : TestBase
public async Task GetMusicInfoFromCsvFileAsync_Test() public async Task GetMusicInfoFromCsvFileAsync_Test()
{ {
var musicScanner = GetService<CsvFileMusicScanner>(); var musicScanner = GetService<CsvFileMusicScanner>();
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.ShouldNotBeNull();
musicInfo.Count.ShouldBeGreaterThan(0); musicInfo.Count.ShouldBeGreaterThan(0);

View File

@ -11,7 +11,7 @@ public class NetEaseMusicSongListMusicScannerTests : TestBase
public async Task GetMusicInfoFromNetEaseMusicSongListAsync_Test() public async Task GetMusicInfoFromNetEaseMusicSongListAsync_Test()
{ {
var musicScanner = GetService<NetEaseMusicSongListMusicScanner>(); var musicScanner = GetService<NetEaseMusicSongListMusicScanner>();
var musicInfo = await musicScanner.GetMusicInfoFromNetEaseMusicSongListAsync("7224428149", new ManualDownloadOptions()); var musicInfo = await musicScanner.GetMusicInfoFromNetEaseMusicSongListAsync("7224428149", "DownloadedLrc", "{Artist} - {Name}.lrc");
musicInfo.ShouldNotBeNull(); musicInfo.ShouldNotBeNull();
musicInfo.Count.ShouldBeGreaterThan(10); musicInfo.Count.ShouldBeGreaterThan(10);