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 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<int> 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;
}
/// <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 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)

View File

@ -4,19 +4,19 @@ namespace ZonyLrcTools.Common.MusicScanner;
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 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;
}

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

View File

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

View File

@ -11,7 +11,7 @@ public class NetEaseMusicSongListMusicScannerTests : TestBase
public async Task GetMusicInfoFromNetEaseMusicSongListAsync_Test()
{
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.Count.ShouldBeGreaterThan(10);