mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-05 13:07:26 +00:00
Compare commits
75 Commits
Alpha.2022
...
ZonyLrcToo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ba91108ca4 | ||
![]() |
f597e24ff7 | ||
![]() |
d453d520d4 | ||
![]() |
9f254aae8e | ||
![]() |
c9fa0de95f | ||
![]() |
6d7ee04b74 | ||
![]() |
f519eb1251 | ||
![]() |
85f325b300 | ||
![]() |
140043db79 | ||
![]() |
c583a9c278 | ||
![]() |
8385f6f118 | ||
![]() |
be4380c744 | ||
![]() |
87f6a98668 | ||
![]() |
83fcc91fb8 | ||
![]() |
b4c229ccca | ||
![]() |
71302c7262 | ||
![]() |
1bec50e409 | ||
![]() |
f7cae8062c | ||
![]() |
c73f0d8eea | ||
![]() |
fe7c9115d5 | ||
![]() |
e13fb47aeb | ||
![]() |
32891d4208 | ||
![]() |
770d246787 | ||
![]() |
589151a77d | ||
![]() |
65af048f72 | ||
![]() |
99fd3b5021 | ||
![]() |
e4420e749a | ||
![]() |
5f22dafb1e | ||
![]() |
c2d8ee4341 | ||
![]() |
6a4435eb0d | ||
![]() |
346e07e9e7 | ||
![]() |
efdb3e8441 | ||
![]() |
0438433284 | ||
![]() |
d34a830660 | ||
![]() |
975f61a8e7 | ||
![]() |
8ba78c2681 | ||
![]() |
019bdb571c | ||
![]() |
a2aeed021d | ||
![]() |
0e8d361ed9 | ||
![]() |
c3b381a5f7 | ||
![]() |
8acba586b0 | ||
![]() |
5905d4b384 | ||
![]() |
2ad771e62e | ||
![]() |
403d7b151d | ||
![]() |
5c8c9af9ab | ||
![]() |
bfd23fc14e | ||
![]() |
81271a5aa5 | ||
![]() |
80a0bcdcee | ||
![]() |
172174d9e3 | ||
![]() |
d4a6a46078 | ||
![]() |
0772f5888f | ||
![]() |
950652c040 | ||
![]() |
55f720c1a1 | ||
![]() |
7463709a39 | ||
![]() |
07e660e13b | ||
![]() |
a51b399a6c | ||
![]() |
279eba48f8 | ||
![]() |
f3b1dacb0c | ||
![]() |
559efff928 | ||
![]() |
8370172aef | ||
![]() |
cb8572e599 | ||
![]() |
3b0f9fa89b | ||
![]() |
c655ac4cbb | ||
![]() |
893b1e7918 | ||
![]() |
e900a92f37 | ||
![]() |
0f84621919 | ||
![]() |
61ca863770 | ||
![]() |
2044a0b8fa | ||
![]() |
ffd76f5f2b | ||
![]() |
3705c6c8b5 | ||
![]() |
f8531e82ff | ||
![]() |
969d5e9e19 | ||
![]() |
88ed247430 | ||
![]() |
be68b3474c | ||
![]() |
363d104dbe |
61
.github/workflows/dotnet.yml
vendored
Normal file
61
.github/workflows/dotnet.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: .NET
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
# paths:
|
||||
# - "versions/**"
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get build version
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y%m%d')${{github.run_number}}"
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Publish
|
||||
working-directory: ./src/ZonyLrcTools.Cli
|
||||
run: |
|
||||
ls -a
|
||||
chmod +x ./publish.sh
|
||||
./publish.sh
|
||||
mv ./TempFiles ../../
|
||||
shell: bash
|
||||
env:
|
||||
PUBLISH_VERSION: ${{ steps.date.outputs.date }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release-files
|
||||
path: |
|
||||
./TempFiles
|
||||
./versions/release.md
|
||||
outputs:
|
||||
version: ${{ steps.date.outputs.date }}
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: release-files
|
||||
path: .
|
||||
- name: Upload Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "./TempFiles/*.zip"
|
||||
token: ${{ secrets.GITHUBACTIONS }}
|
||||
tag: ZonyLrcToolsX_Alpha.${{ needs.build.outputs.version }}
|
||||
commit: dev
|
||||
bodyFile: ./versions/release.md
|
18
README.md
18
README.md
@@ -1,5 +1,10 @@
|
||||
简体中文 | [English](./docs/en_US.md)
|
||||
|
||||
## 免责声明
|
||||
- 本工具仅作个人学习研究使用,可运行的二进制文件仅用于演示功能,不得将源码及其产物用于商业用途,否则由此造成的相关法律问题,[本人](https://github.com/real-zony) 不承担任何法律责任。
|
||||
- 任何单位或个人因下载使用软件所产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),[本人](https://github.com/real-zony) 不承担任何法律责任。
|
||||
- 用户明确并同意本声明条款列举的全部内容,对使用本工具可能存在的风险和相关后果将完全由用户自行承担,[本人](https://github.com/real-zony) 不承担任何法律责任。
|
||||
|
||||
## 简介
|
||||
|
||||
ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具。
|
||||
@@ -7,6 +12,10 @@ ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具。
|
||||
🚧 当前版本正在开发当中。
|
||||
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
|
||||
|
||||
## 下载
|
||||
|
||||
工具会执行每日构建动作,请访问 **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** 页面进行下载。
|
||||
|
||||
## 用法
|
||||
|
||||
Windows 用户请在软件目录当中,按住 Shift + 右键呼出菜单,然后选择 PowerShell/命令提示符/Windows 终端,根据下述说明执行命令即可。
|
||||
@@ -85,16 +94,17 @@ globalOption:
|
||||
# 支持的歌词下载器。
|
||||
plugin:
|
||||
- name: NetEase # 基于网易云音乐的歌词下载器。
|
||||
priority: 1 # 优先级,升序排列。
|
||||
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
|
||||
- name: QQ # 基于 QQ 音乐的歌词下载器。
|
||||
priority: 2
|
||||
- name: KuGou # 基于酷狗音乐的歌词下载器。
|
||||
priority: 3
|
||||
# 歌词下载的一些共有配置参数。
|
||||
config:
|
||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||
lineBreak: '\n' # 换行符的类型。
|
||||
isEnableTranslation: false # 是否启用翻译歌词。
|
||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
||||
isEnableTranslation: true # 是否启用翻译歌词。
|
||||
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
|
||||
```
|
||||
|
||||
### 屏蔽字典
|
||||
|
@@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C29FB05C-54B
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AF8ADB2F-E46C-4DEE-8316-652D9FE1A69B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ui", "ui", "{D6E0DAF5-8171-44C0-817E-2FF9CF574E4F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Cli", "src\ZonyLrcTools.Cli\ZonyLrcTools.Cli.csproj", "{55D74323-ABFA-4A73-A3BF-F3E8D5DE6DE8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Tests", "tests\ZonyLrcTools.Tests\ZonyLrcTools.Tests.csproj", "{FFBD3200-568F-455B-8390-5E76C51D522C}"
|
||||
|
@@ -3,10 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NAudio.Wave;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Album;
|
||||
@@ -16,6 +18,7 @@ using ZonyLrcTools.Cli.Infrastructure.IO;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Threading;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
{
|
||||
@@ -49,7 +52,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
|
||||
[Option("-d|--dir", CommandOptionType.SingleValue, Description = "指定需要扫描的目录。")]
|
||||
[DirectoryExists]
|
||||
public string Directory { get; set; }
|
||||
public string SongsDirectory { get; set; }
|
||||
|
||||
[Option("-l|--lyric", CommandOptionType.NoValue, Description = "指定程序需要下载歌词文件。")]
|
||||
public bool DownloadLyric { get; set; }
|
||||
@@ -60,21 +63,25 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
|
||||
public int ParallelNumber { get; set; } = 2;
|
||||
|
||||
[Option] public string ErrorMessage { get; set; } = Path.Combine(Directory.GetCurrentDirectory(), "error.log");
|
||||
|
||||
#endregion
|
||||
|
||||
protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
{
|
||||
var files = await ScanMusicFilesAsync();
|
||||
var musicInfos = await LoadMusicInfoAsync(files);
|
||||
|
||||
if (DownloadLyric)
|
||||
{
|
||||
await DownloadLyricFilesAsync(musicInfos);
|
||||
await DownloadLyricFilesAsync(
|
||||
await LoadMusicInfoAsync(
|
||||
RemoveExistLyricFiles(
|
||||
await ScanMusicFilesAsync())));
|
||||
}
|
||||
|
||||
if (DownloadAlbum)
|
||||
{
|
||||
await DownloadAlbumAsync(musicInfos);
|
||||
await DownloadAlbumAsync(
|
||||
await LoadMusicInfoAsync(
|
||||
await ScanMusicFilesAsync()));
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -82,7 +89,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
|
||||
private async Task<List<string>> ScanMusicFilesAsync()
|
||||
{
|
||||
var files = (await _fileScanner.ScanAsync(Directory, _options.SupportFileExtensions))
|
||||
var files = (await _fileScanner.ScanAsync(SongsDirectory, _options.SupportFileExtensions))
|
||||
.SelectMany(t => t.FilePaths)
|
||||
.ToList();
|
||||
|
||||
@@ -97,6 +104,27 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
return files;
|
||||
}
|
||||
|
||||
private List<string> RemoveExistLyricFiles(List<string> filePaths)
|
||||
{
|
||||
if (!_options.Provider.Lyric.Config.IsSkipExistLyricFiles)
|
||||
{
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
return filePaths
|
||||
.Where(path =>
|
||||
{
|
||||
if (!File.Exists(Path.ChangeExtension(path, ".lrc")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.LogWarning($"已经存在歌词文件 {path},跳过。");
|
||||
return false;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<ImmutableList<MusicInfo>> LoadMusicInfoAsync(IReadOnlyCollection<string> files)
|
||||
{
|
||||
_logger.LogInformation("开始加载音乐文件的标签信息...");
|
||||
@@ -105,7 +133,11 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
var warpTaskList = files.Select(file => warpTask.RunAsync(() => Task.Run(async () => await _tagLoader.LoadTagAsync(file))));
|
||||
var result = (await Task.WhenAll(warpTaskList))
|
||||
.Where(m => m != null)
|
||||
.Where(m => !string.IsNullOrEmpty(m.Name) || !string.IsNullOrEmpty(m.Artist));
|
||||
.Where(m => !string.IsNullOrEmpty(m.Name) || !string.IsNullOrEmpty(m.Artist))
|
||||
.ToList();
|
||||
|
||||
// Load music total time info.
|
||||
result.Foreach(m => { m.TotalTime = (long?)new AudioFileReader(m.FilePath).TotalTime.TotalMilliseconds; });
|
||||
|
||||
_logger.LogInformation($"已成功加载 {files.Count} 个音乐文件的标签信息。");
|
||||
|
||||
@@ -125,7 +157,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
return downloader;
|
||||
}
|
||||
|
||||
#region > 歌词下载逻辑 <
|
||||
#region > Lyric download logic <
|
||||
|
||||
private async ValueTask DownloadLyricFilesAsync(ImmutableList<MusicInfo> musicInfos)
|
||||
{
|
||||
@@ -138,63 +170,76 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
|
||||
await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"歌词数据下载完成,成功: {musicInfos.Count} 失败{0}。");
|
||||
_logger.LogInformation($"歌词数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。");
|
||||
}
|
||||
|
||||
private async Task DownloadLyricTaskLogicAsync(IEnumerable<ILyricDownloader> downloaderList, MusicInfo info)
|
||||
{
|
||||
async Task<bool> InternalDownloadLogicAsync(ILyricDownloader downloader)
|
||||
async Task InternalDownloadLogicAsync(ILyricDownloader downloader)
|
||||
{
|
||||
_logger.LogMusicInfoWithInformation(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))
|
||||
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))
|
||||
{
|
||||
return true;
|
||||
File.Delete(lyricFilePath);
|
||||
}
|
||||
|
||||
info.IsSuccessful = true;
|
||||
|
||||
if (lyric.IsPruneMusic)
|
||||
{
|
||||
return true;
|
||||
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();
|
||||
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)
|
||||
{
|
||||
if (ex.ErrorCode == ErrorCodes.NoMatchingSong)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
info.IsSuccessful = ex.ErrorCode == ErrorCodes.NoMatchingSong;
|
||||
|
||||
_logger.LogWarningInfo(ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"下载歌词文件时发生错误:{ex.Message},歌曲名: {info.Name},歌手: {info.Artist}。");
|
||||
info.IsSuccessful = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var downloader in downloaderList)
|
||||
{
|
||||
if (await InternalDownloadLogicAsync(downloader))
|
||||
await InternalDownloadLogicAsync(downloader);
|
||||
|
||||
if (info.IsSuccessful)
|
||||
{
|
||||
break;
|
||||
_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 > 专辑图像下载逻辑 <
|
||||
#region > Ablum image download logic <
|
||||
|
||||
private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos)
|
||||
{
|
||||
@@ -207,12 +252,12 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
|
||||
await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"专辑数据下载完成,成功: {musicInfos.Count} 失败{0}。");
|
||||
_logger.LogInformation($"专辑数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。");
|
||||
}
|
||||
|
||||
private async Task DownloadAlbumTaskLogicAsync(IAlbumDownloader downloader, MusicInfo info)
|
||||
{
|
||||
_logger.LogMusicInfoWithInformation(info);
|
||||
_logger.LogSuccessful(info);
|
||||
|
||||
try
|
||||
{
|
||||
|
@@ -1,4 +1,4 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
namespace ZonyLrcTools.Cli.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 屏蔽词选项类。
|
50
src/ZonyLrcTools.Cli/Config/LyricOption.cs
Normal file
50
src/ZonyLrcTools.Cli/Config/LyricOption.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Config;
|
||||
|
||||
public class LyricOption
|
||||
{
|
||||
public IEnumerable<LyricProviderOption> Plugin { get; set; }
|
||||
|
||||
public LyricConfigOption Config { get; set; }
|
||||
|
||||
public LyricProviderOption GetLyricProviderOption(string name)
|
||||
{
|
||||
return Plugin.FirstOrDefault(x => x.Name == name);
|
||||
}
|
||||
}
|
||||
|
||||
public class LyricConfigOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 双语歌词是否合并为一行。
|
||||
/// </summary>
|
||||
public bool IsOneLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 换行符格式,取值来自 <see cref="LineBreakType"/> 常量类。
|
||||
/// </summary>
|
||||
public string LineBreak { get; set; } = LineBreakType.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用歌词翻译功能。
|
||||
/// </summary>
|
||||
public bool IsEnableTranslation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 如果歌词文件已经存在,是否跳过这些文件
|
||||
/// </summary>
|
||||
public bool IsSkipExistLyricFiles { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 歌词文件的编码格式。
|
||||
/// </summary>
|
||||
public string FileEncoding { get; set; } = "utf-8";
|
||||
|
||||
/// <summary>
|
||||
/// 是否只输出翻译歌词。
|
||||
/// </summary>
|
||||
public bool IsOnlyOutputTranslation { get; set; } = false;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
namespace ZonyLrcTools.Cli.Config
|
||||
{
|
||||
public class LyricProviderOption
|
||||
{
|
||||
@@ -11,5 +11,10 @@
|
||||
/// 歌词下载时的优先级,当值为 -1 时是禁用。
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 搜索深度,值越大搜索结果越多,但搜索时间越长。
|
||||
/// </summary>
|
||||
public int Depth { get; set; }
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Config;
|
||||
|
||||
|
@@ -51,7 +51,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Album.NetEase
|
||||
|
||||
var songDetailJsonStr = await _warpHttpClient.GetAsync(
|
||||
GetMusicInfoApi,
|
||||
new GetSongDetailsRequest(searchResult.GetFirstMatchSongId(songName)),
|
||||
new GetSongDetailsRequest(searchResult.GetFirstMatchSongId(songName, null)),
|
||||
_defaultOption);
|
||||
|
||||
var url = JObject.Parse(songDetailJsonStr).SelectToken("$.songs[0].album.picUrl")?.Value<string>();
|
||||
|
@@ -27,6 +27,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Exceptions
|
||||
/// </summary>
|
||||
public const int NoFilesWereScanned = 10004;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 指定的编码不受支持,请检查配置,所有受支持的编码名称。
|
||||
/// </summary>
|
||||
public const int NotSupportedFileEncoding = 10005;
|
||||
|
||||
#endregion
|
||||
|
||||
#region > 警告信息 <
|
||||
|
@@ -41,13 +41,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲信息。
|
||||
/// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲下载成功信息。
|
||||
/// </summary>
|
||||
/// <param name="logger">日志记录器的实例。</param>
|
||||
/// <param name="musicInfo">需要打印的歌曲信息。</param>
|
||||
public static void LogMusicInfoWithInformation(this ILogger logger, MusicInfo musicInfo)
|
||||
public static void LogSuccessful(this ILogger logger, MusicInfo musicInfo)
|
||||
{
|
||||
logger.LogInformation($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 歌曲路径: {musicInfo.FilePath}");
|
||||
logger.LogInformation($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 下载成功.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Logging;
|
||||
|
||||
public static class CustomConsoleTheme
|
||||
{
|
||||
public static AnsiConsoleTheme Code { get; } = new AnsiConsoleTheme(
|
||||
new Dictionary<ConsoleThemeStyle, string>
|
||||
{
|
||||
[ConsoleThemeStyle.Text] = "\x1b[38;5;0253m",
|
||||
[ConsoleThemeStyle.SecondaryText] = "\x1b[38;5;0246m",
|
||||
[ConsoleThemeStyle.TertiaryText] = "\x1b[38;5;0242m",
|
||||
[ConsoleThemeStyle.Invalid] = "\x1b[33;1m",
|
||||
[ConsoleThemeStyle.Null] = "\x1b[38;5;0038m",
|
||||
[ConsoleThemeStyle.Name] = "\x1b[38;5;0081m",
|
||||
[ConsoleThemeStyle.String] = "\x1b[38;5;0216m",
|
||||
[ConsoleThemeStyle.Number] = "\x1b[38;5;151m",
|
||||
[ConsoleThemeStyle.Boolean] = "\x1b[38;5;0038m",
|
||||
[ConsoleThemeStyle.Scalar] = "\x1b[38;5;0079m",
|
||||
[ConsoleThemeStyle.LevelVerbose] = "\x1b[37m",
|
||||
[ConsoleThemeStyle.LevelDebug] = "\x1b[37m",
|
||||
[ConsoleThemeStyle.LevelInformation] = "\x1b[32m\x1b[48;5;0238m",
|
||||
[ConsoleThemeStyle.LevelWarning] = "\x1b[38;5;0229m",
|
||||
[ConsoleThemeStyle.LevelError] = "\x1b[38;5;0197m\x1b[48;5;0238m",
|
||||
[ConsoleThemeStyle.LevelFatal] = "\x1b[38;5;0197m\x1b[48;5;0238m",
|
||||
});
|
||||
}
|
@@ -12,8 +12,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
/// </summary>
|
||||
/// <param name="songName">歌曲的名称。</param>
|
||||
/// <param name="artist">歌曲的作者。</param>
|
||||
/// <param name="duration">歌曲的时长。</param>
|
||||
/// <returns>歌曲的歌词数据对象。</returns>
|
||||
ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist);
|
||||
ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist, long? duration = null);
|
||||
|
||||
/// <summary>
|
||||
/// 下载器的名称。
|
||||
|
@@ -12,11 +12,17 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
|
||||
|
||||
[JsonProperty("keyword")] public string Keyword { get; }
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName)
|
||||
[JsonProperty("pagesize")] public int PageSize { get; }
|
||||
|
||||
[JsonProperty("page")] public int Page { get; }
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName, int pageSize = 30)
|
||||
{
|
||||
Filter = 2;
|
||||
Platform = "WebFilter";
|
||||
Keyword = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
|
||||
PageSize = pageSize;
|
||||
Page = 1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Network;
|
||||
@@ -14,22 +16,25 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou
|
||||
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
private readonly ToolOptions _options;
|
||||
|
||||
private const string KuGouSearchMusicUrl = @"https://songsearch.kugou.com/song_search_v2";
|
||||
private const string KuGouGetLyricAccessKeyUrl = @"http://lyrics.kugou.com/search";
|
||||
private const string KuGouGetLyricUrl = @"http://lyrics.kugou.com/download";
|
||||
|
||||
public KuGourLyricDownloader(IWarpHttpClient warpHttpClient,
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory)
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory,
|
||||
IOptions<ToolOptions> options)
|
||||
{
|
||||
_warpHttpClient = warpHttpClient;
|
||||
_lyricItemCollectionFactory = lyricItemCollectionFactory;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
|
||||
{
|
||||
var searchResult = await _warpHttpClient.GetAsync<SongSearchResponse>(KuGouSearchMusicUrl,
|
||||
new SongSearchRequest(args.SongName, args.Artist));
|
||||
new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth));
|
||||
|
||||
ValidateSongSearchResponse(searchResult, args);
|
||||
|
||||
|
@@ -16,10 +16,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
/// </summary>
|
||||
/// <param name="songName">歌曲名称。</param>
|
||||
/// <param name="artist">歌曲作者/艺术家。</param>
|
||||
/// <param name="duration">歌曲的时长。</param>
|
||||
/// <returns>下载完成的歌曲数据。</returns>
|
||||
public virtual async ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist)
|
||||
public virtual async ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist, long? duration = null)
|
||||
{
|
||||
var args = new LyricDownloaderArgs(songName, artist);
|
||||
var args = new LyricDownloaderArgs(songName, artist, duration ?? 0);
|
||||
await ValidateAsync(args);
|
||||
var downloadDataBytes = await DownloadDataAsync(args);
|
||||
return await GenerateLyricAsync(downloadDataBytes, args);
|
||||
|
@@ -6,10 +6,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
|
||||
public string Artist { get; set; }
|
||||
|
||||
public LyricDownloaderArgs(string songName, string artist)
|
||||
public long Duration { get; set; }
|
||||
|
||||
public LyricDownloaderArgs(string songName, string artist, long duration)
|
||||
{
|
||||
SongName = songName;
|
||||
Artist = artist;
|
||||
Duration = duration;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Extensions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
@@ -106,5 +107,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
ForEach(lyric => lyricBuilder.Append(lyric).Append(Option.LineBreak));
|
||||
return lyricBuilder.ToString().TrimEnd(Option.LineBreak);
|
||||
}
|
||||
|
||||
public byte[] GetUtf8Bytes()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -43,6 +43,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
if (_options.Provider.Lyric.Config.IsEnableTranslation && !string.IsNullOrEmpty(translationLyric))
|
||||
{
|
||||
var translatedLyric = InternalBuildLyricObject(new LyricItemCollection(_options.Provider.Lyric.Config), translationLyric);
|
||||
if (_options.Provider.Lyric.Config.IsOnlyOutputTranslation)
|
||||
{
|
||||
return translatedLyric;
|
||||
}
|
||||
|
||||
return lyric + translatedLyric;
|
||||
}
|
||||
|
||||
|
@@ -1,28 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
|
||||
public class LyricOption
|
||||
{
|
||||
public IEnumerable<LyricProviderOption> Plugin { get; set; }
|
||||
|
||||
public LyricConfigOption Config { get; set; }
|
||||
}
|
||||
|
||||
public class LyricConfigOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 双语歌词是否合并为一行。
|
||||
/// </summary>
|
||||
public bool IsOneLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 换行符格式,取值来自 <see cref="LineBreakType"/> 常量类。
|
||||
/// </summary>
|
||||
public string LineBreak { get; set; } = LineBreakType.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用歌词翻译功能。
|
||||
/// </summary>
|
||||
public bool IsEnableTranslation { get; set; } = false;
|
||||
}
|
@@ -8,9 +8,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
{
|
||||
public GetLyricRequest(long songId)
|
||||
{
|
||||
OS = "osx";
|
||||
OS = "ios";
|
||||
Id = songId;
|
||||
Lv = Kv = Tv = -1;
|
||||
Lv = Kv = Tv = Rv = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,5 +30,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
[JsonProperty("kv")] public int Kv { get; }
|
||||
|
||||
[JsonProperty("tv")] public int Tv { get; }
|
||||
|
||||
[JsonProperty("rv")] public int Rv { get; set; }
|
||||
|
||||
[JsonProperty("crypto")] public string Protocol { get; set; } = "api";
|
||||
}
|
||||
}
|
@@ -22,6 +22,12 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
[JsonProperty("tlyric")]
|
||||
public InnerLyric TranslationLyric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。
|
||||
/// </summary>
|
||||
[JsonProperty("romalrc")]
|
||||
public InnerLyric RomaLyric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态码。
|
||||
/// </summary>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -42,18 +43,25 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
[JsonProperty("limit")]
|
||||
public int Limit { get; set; }
|
||||
|
||||
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
|
||||
|
||||
public SongSearchRequest()
|
||||
{
|
||||
CsrfToken = string.Empty;
|
||||
Type = 1;
|
||||
Offset = 0;
|
||||
IsTotal = true;
|
||||
Limit = 5;
|
||||
Limit = 10;
|
||||
}
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName) : this()
|
||||
public SongSearchRequest(string musicName, string artistName, int limit = 10) : this()
|
||||
{
|
||||
// Remove all the brackets and the content inside them.
|
||||
var regex = new Regex(@"\([^)]*\)");
|
||||
musicName = regex.Replace(musicName, string.Empty);
|
||||
|
||||
SearchKey = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
|
||||
Limit = limit;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
@@ -10,10 +11,20 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
|
||||
[JsonProperty("code")] public int StatusCode { get; set; }
|
||||
|
||||
public int GetFirstMatchSongId(string songName)
|
||||
public int GetFirstMatchSongId(string songName, long? duration)
|
||||
{
|
||||
var item = Items.SongItems.FirstOrDefault(x => x.Name == songName);
|
||||
return item?.Id ?? Items.SongItems[0].Id;
|
||||
var perfectMatch = Items.SongItems.FirstOrDefault(x => x.Name == songName);
|
||||
if (perfectMatch != null)
|
||||
{
|
||||
return perfectMatch.Id;
|
||||
}
|
||||
|
||||
if (duration is null or 0)
|
||||
{
|
||||
return Items.SongItems.First().Id;
|
||||
}
|
||||
|
||||
return Items.SongItems.OrderBy(t => Math.Abs(t.Duration - duration.Value)).First().Id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +60,12 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
|
||||
/// </summary>
|
||||
[JsonProperty("album")]
|
||||
public SongAlbumModel Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的实际长度。
|
||||
/// </summary>
|
||||
[JsonProperty("duration")]
|
||||
public long Duration { get; set; }
|
||||
}
|
||||
|
||||
public class SongArtistModel
|
||||
|
@@ -2,7 +2,9 @@ using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Network;
|
||||
@@ -15,6 +17,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
private readonly ToolOptions _options;
|
||||
|
||||
private const string NetEaseSearchMusicUrl = @"https://music.163.com/api/search/get/web";
|
||||
private const string NetEaseGetLyricUrl = @"https://music.163.com/api/song/lyric";
|
||||
@@ -23,17 +26,19 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
private const string NetEaseRequestContentType = @"application/x-www-form-urlencoded";
|
||||
|
||||
public NetEaseLyricDownloader(IWarpHttpClient warpHttpClient,
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory)
|
||||
ILyricItemCollectionFactory lyricItemCollectionFactory,
|
||||
IOptions<ToolOptions> options)
|
||||
{
|
||||
_warpHttpClient = warpHttpClient;
|
||||
_lyricItemCollectionFactory = lyricItemCollectionFactory;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
|
||||
{
|
||||
var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>(
|
||||
NetEaseSearchMusicUrl,
|
||||
new SongSearchRequest(args.SongName, args.Artist),
|
||||
new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
|
||||
true,
|
||||
msg =>
|
||||
{
|
||||
@@ -48,7 +53,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
|
||||
var lyricResponse = await _warpHttpClient.GetAsync(
|
||||
NetEaseGetLyricUrl,
|
||||
new GetLyricRequest(searchResult.GetFirstMatchSongId(args.SongName)),
|
||||
new GetLyricRequest(searchResult.GetFirstMatchSongId(args.SongName, args.Duration)),
|
||||
msg => msg.Headers.Referrer = new Uri(NetEaseRequestReferer));
|
||||
|
||||
return Encoding.UTF8.GetBytes(lyricResponse);
|
||||
@@ -59,7 +64,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
await ValueTask.CompletedTask;
|
||||
|
||||
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data));
|
||||
if (json?.OriginalLyric == null)
|
||||
if (json?.OriginalLyric == null || string.IsNullOrEmpty(json.OriginalLyric.Text))
|
||||
{
|
||||
return new LyricItemCollection(null);
|
||||
}
|
||||
@@ -70,8 +75,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
}
|
||||
|
||||
return _lyricItemCollectionFactory.Build(
|
||||
json.OriginalLyric.Text,
|
||||
json.TranslationLyric.Text);
|
||||
json.OriginalLyric?.Text,
|
||||
json.TranslationLyric?.Text);
|
||||
}
|
||||
|
||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricDownloaderArgs args)
|
||||
|
@@ -6,32 +6,26 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
|
||||
{
|
||||
public class SongSearchRequest
|
||||
{
|
||||
[JsonProperty("remoteplace")] public string RemotePlace { get; set; }
|
||||
[JsonProperty("format")]
|
||||
public string Format { get; protected set; }
|
||||
|
||||
[JsonProperty("p")] public int Page { get; set; }
|
||||
[JsonProperty("inCharset")]
|
||||
public string InCharset { get; protected set; }
|
||||
|
||||
[JsonProperty("n")] public int Limit { get; set; }
|
||||
[JsonProperty("outCharset")]
|
||||
public string OutCharset { get; protected set; }
|
||||
|
||||
[JsonProperty("w")] public string Keyword { get; set; }
|
||||
|
||||
[JsonProperty("format")] public string ResultFormat { get; set; }
|
||||
|
||||
[JsonProperty("inCharset")] public string InCharset { get; set; }
|
||||
|
||||
[JsonProperty("outCharset")] public string OutCharset { get; set; }
|
||||
|
||||
[JsonProperty("platform")] public string Platform { get; set; }
|
||||
[JsonProperty("platform")]
|
||||
public string Platform { get; protected set; }
|
||||
|
||||
[JsonProperty("key")]
|
||||
public string Keyword { get; protected set; }
|
||||
|
||||
protected SongSearchRequest()
|
||||
{
|
||||
RemotePlace = "txt.yqq.song";
|
||||
Page = 1;
|
||||
Limit = 5;
|
||||
ResultFormat = "json";
|
||||
InCharset = "utf8";
|
||||
OutCharset = "utf8";
|
||||
Platform = "yqq";
|
||||
Format = "json";
|
||||
InCharset = OutCharset = "utf-8";
|
||||
Platform = "yqq.json";
|
||||
}
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName) : this()
|
||||
|
@@ -17,11 +17,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
|
||||
|
||||
public class QQMusicInnerSongModel
|
||||
{
|
||||
[JsonProperty("list")] public List<QQMusicInnerSongItem> SongItems { get; set; }
|
||||
[JsonProperty("itemlist")] public List<QQMusicInnerSongItem> SongItems { get; set; }
|
||||
}
|
||||
|
||||
public class QQMusicInnerSongItem
|
||||
{
|
||||
[JsonProperty("songmid")] public string SongId { get; set; }
|
||||
[JsonProperty("mid")] public string SongId { get; set; }
|
||||
}
|
||||
}
|
@@ -17,7 +17,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
|
||||
private const string QQSearchMusicUrl = @"https://c.y.qq.com/soso/fcgi-bin/client_search_cp";
|
||||
// private const string QQSearchMusicUrl = @"https://c.y.qq.com/soso/fcgi-bin/client_search_cp";
|
||||
private const string QQSearchMusicUrl = @"https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg";
|
||||
private const string QQGetLyricUrl = @"https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg";
|
||||
|
||||
private const string QQMusicRequestReferer = @"https://y.qq.com/";
|
||||
|
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
@@ -10,6 +12,11 @@ namespace ZonyLrcTools.Cli.Infrastructure
|
||||
/// </summary>
|
||||
public string FilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的实际歌曲长度。
|
||||
/// </summary>
|
||||
public long? TotalTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌曲的名称。
|
||||
/// </summary>
|
||||
@@ -20,6 +27,11 @@ namespace ZonyLrcTools.Cli.Infrastructure
|
||||
/// </summary>
|
||||
public string Artist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否下载成功?
|
||||
/// </summary>
|
||||
public bool IsSuccessful { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 构建一个新的 <see cref="MusicInfo"/> 对象。
|
||||
/// </summary>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -16,10 +17,12 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
protected readonly IEnumerable<ITagInfoProvider> TagInfoProviders;
|
||||
protected readonly IBlockWordDictionary BlockWordDictionary;
|
||||
protected readonly ILogger<DefaultTagLoader> _logger;
|
||||
protected readonly ILogger<DefaultTagLoader> Logger;
|
||||
|
||||
protected ToolOptions Options;
|
||||
|
||||
private readonly IEnumerable<ITagInfoProvider> _sortedTagInfoProviders;
|
||||
|
||||
public DefaultTagLoader(IEnumerable<ITagInfoProvider> tagInfoProviders,
|
||||
IBlockWordDictionary blockWordDictionary,
|
||||
IOptions<ToolOptions> options,
|
||||
@@ -27,30 +30,46 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
TagInfoProviders = tagInfoProviders;
|
||||
BlockWordDictionary = blockWordDictionary;
|
||||
_logger = logger;
|
||||
Logger = logger;
|
||||
Options = options.Value;
|
||||
|
||||
_sortedTagInfoProviders = GetTagInfoProviders();
|
||||
}
|
||||
|
||||
public virtual async ValueTask<MusicInfo> LoadTagAsync(string filePath)
|
||||
{
|
||||
foreach (var provider in _sortedTagInfoProviders)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = await provider.LoadAsync(filePath);
|
||||
if (info != null && !string.IsNullOrEmpty(info.Name))
|
||||
{
|
||||
HandleBlockWord(info);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogWarning($"{filePath} 没有找到正确的标签信息,请考虑调整正则表达式。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<ITagInfoProvider> GetTagInfoProviders()
|
||||
{
|
||||
if (!TagInfoProviders.Any())
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.LoadTagInfoProviderError);
|
||||
}
|
||||
|
||||
foreach (var provider in TagInfoProviders)
|
||||
{
|
||||
var info = await provider.LoadAsync(filePath);
|
||||
if (info != null)
|
||||
{
|
||||
HandleBlockWord(info);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning($"{filePath} 没有找到正确的标签信息,请考虑调整正则表达式。");
|
||||
|
||||
return null;
|
||||
return Options.Provider.Tag.Plugin
|
||||
.Where(x => x.Priority != -1)
|
||||
.OrderBy(x => x.Priority)
|
||||
.Join(TagInfoProviders, x => x.Name, y => y.Name, (x, y) => y);
|
||||
}
|
||||
|
||||
protected void HandleBlockWord(MusicInfo info)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using YamlDotNet.Serialization;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -7,10 +8,12 @@ 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.DependencyInject;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Logging;
|
||||
|
||||
namespace ZonyLrcTools.Cli
|
||||
{
|
||||
@@ -21,6 +24,8 @@ namespace ZonyLrcTools.Cli
|
||||
{
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
ConfigureLogger();
|
||||
ConfigureErrorMessage();
|
||||
|
||||
@@ -53,7 +58,7 @@ namespace ZonyLrcTools.Cli
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Async(c => c.Console())
|
||||
.WriteTo.Async(c => c.Console(theme: CustomConsoleTheme.Code))
|
||||
.WriteTo.Logger(lc =>
|
||||
{
|
||||
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Warning)
|
||||
@@ -92,7 +97,8 @@ namespace ZonyLrcTools.Cli
|
||||
switch (ex)
|
||||
{
|
||||
case ErrorCodeException exception:
|
||||
Log.Logger.Error($"出现了未处理的异常,错误代码: {exception.ErrorCode},错误信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}\n调用栈:\n{exception.StackTrace}");
|
||||
Log.Logger.Error(
|
||||
$"出现了未处理的异常。\n错误代码: {exception.ErrorCode}\n错误信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}\n原始信息:{exception.Message}\n调用栈:{exception.StackTrace}");
|
||||
Environment.Exit(exception.ErrorCode);
|
||||
return exception.ErrorCode;
|
||||
case { } unknownException:
|
||||
|
@@ -3,7 +3,8 @@
|
||||
"10001": "待搜索的后缀不能为空。",
|
||||
"10002": "需要扫描的目录不存在,请确认路径是否正确。",
|
||||
"10003": "不能获取文件的后缀信息。",
|
||||
"10004": "没有扫描到任何音乐文件。"
|
||||
"10004": "没有扫描到任何音乐文件。",
|
||||
"10005": "指定的编码不受支持,请检查配置,所有受支持的编码名称,请参考: https://docs.microsoft.com/en-us/dotnet/api/system.text.encodinginfo.codepage?view=net-6.0#system-text-encodinginfo-codepage。"
|
||||
},
|
||||
"Warning": {
|
||||
"50001": "扫描文件时出现了错误。",
|
||||
|
@@ -10,16 +10,18 @@
|
||||
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Refit" Version="6.3.2" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />
|
||||
<PackageReference Include="Refit.Newtonsoft.Json" Version="6.3.2" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -34,13 +34,19 @@ globalOption:
|
||||
# 支持的歌词下载器。
|
||||
plugin:
|
||||
- name: NetEase # 基于网易云音乐的歌词下载器。
|
||||
priority: 1 # 优先级,升序排列。
|
||||
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
|
||||
depth: 30 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
|
||||
- name: QQ # 基于 QQ 音乐的歌词下载器。
|
||||
priority: 2
|
||||
# depth: 10 # 暂时不支持。
|
||||
- name: KuGou # 基于酷狗音乐的歌词下载器。
|
||||
priority: 3
|
||||
depth: 10
|
||||
# 歌词下载的一些共有配置参数。
|
||||
config:
|
||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
||||
isEnableTranslation: true # 是否启用翻译歌词。
|
||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
||||
isEnableTranslation: true # 是否启用翻译歌词。
|
||||
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
|
||||
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
|
||||
fileEncoding: 'utf-8' # 歌词文件的编码格式。
|
@@ -1,5 +1,4 @@
|
||||
#!/bin/bash
|
||||
read -r -p "请输入版本号:" Version
|
||||
Platforms=('win-x64' 'linux-x64' 'osx-x64')
|
||||
|
||||
if ! [ -d './TempFiles' ];
|
||||
@@ -11,11 +10,11 @@ rm -rf ./TempFiles/*
|
||||
|
||||
for platform in "${Platforms[@]}"
|
||||
do
|
||||
dotnet publish -r "$platform" -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true
|
||||
dotnet publish -r "$platform" -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true || exit 1
|
||||
|
||||
cd ./bin/Release/net5.0/"$platform"/publish/
|
||||
zip -r ./ZonyLrcTools_"$platform"_"$Version".zip ./
|
||||
cd ../../../../../
|
||||
cd ./bin/Release/net6.0/"$platform"/publish/ || exit 1
|
||||
zip -r ./ZonyLrcTools_"$platform"_"${PUBLISH_VERSION}".zip ./ || exit 1
|
||||
cd ../../../../../ || exit 1
|
||||
|
||||
mv ./bin/Release/net5.0/"$platform"/publish/ZonyLrcTools_"$platform"_"$Version".zip ./TempFiles
|
||||
mv ./bin/Release/net6.0/"$platform"/publish/ZonyLrcTools_"$platform"_"$PUBLISH_VERSION".zip ./TempFiles
|
||||
done
|
3
src/ui/.browserslistrc
Normal file
3
src/ui/.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
23
src/ui/.gitignore
vendored
Normal file
23
src/ui/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
19
src/ui/README.md
Normal file
19
src/ui/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# ui
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
src/ui/babel.config.js
Normal file
5
src/ui/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
19
src/ui/jsconfig.json
Normal file
19
src/ui/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
23
src/ui/package.json
Normal file
23
src/ui/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"buefy": "^0.9.20",
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
}
|
||||
}
|
BIN
src/ui/public/favicon.ico
Normal file
BIN
src/ui/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
17
src/ui/public/index.html
Normal file
17
src/ui/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
5
src/ui/src/App.vue
Normal file
5
src/ui/src/App.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
BIN
src/ui/src/assets/logo.png
Normal file
BIN
src/ui/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
12
src/ui/src/main.js
Normal file
12
src/ui/src/main.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
15
src/ui/src/router/index.js
Normal file
15
src/ui/src/router/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [{
|
||||
path: '/', name: 'home', component: HomeView
|
||||
}]
|
||||
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
17
src/ui/src/store/index.js
Normal file
17
src/ui/src/store/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
}
|
||||
})
|
12
src/ui/src/views/HomeView.vue
Normal file
12
src/ui/src/views/HomeView.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
|
||||
export default {
|
||||
name: 'HomeView'
|
||||
}
|
||||
</script>
|
4
src/ui/vue.config.js
Normal file
4
src/ui/vue.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true
|
||||
})
|
5518
src/ui/yarn.lock
Normal file
5518
src/ui/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
|
||||
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
|
@@ -1,8 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
|
||||
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
@@ -39,7 +42,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("シンデレラ (Giga First Night Remix)", "DECO 27 ギガP");
|
||||
lyric.ShouldNotBeNull();
|
||||
lyric.IsPruneMusic.ShouldBe(true);
|
||||
lyric.IsPruneMusic.ShouldBe(false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -69,5 +72,30 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
|
||||
lyric.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownIssue_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("主題歌Arrietty's Song", "Cécile Corbel");
|
||||
|
||||
lyric.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloaderAsync_Issue101_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("君への嘘", "VALSHE");
|
||||
lyric.ShouldNotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadAsync_Issue114_Test()
|
||||
{
|
||||
var options = ServiceProvider.GetRequiredService<IOptions<ToolOptions>>();
|
||||
options.Value.Provider.Lyric.Config.IsOnlyOutputTranslation = true;
|
||||
|
||||
var lyric = await _lyricDownloader.DownloadAsync("Bones", "Image Dragons");
|
||||
lyric.ToString().ShouldNotContain("Gimme, gimme, gimme some time to think");
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,10 +7,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="Shouldly" Version="4.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
16
versions/release.md
Normal file
16
versions/release.md
Normal file
@@ -0,0 +1,16 @@
|
||||
New Features:
|
||||
|
||||
- [[#114](https://github.com/real-zony/ZonyLrcToolsX/issues/114)] 下载双语歌词的时候,可以控制是否仅输出翻译歌词。
|
||||
- [[6d7ee04](https://github.com/real-zony/ZonyLrcToolsX/commit/6d7ee04b741191b5526075cdd3c3fc2c5737880f)] 提供深度搜索选项,以便更加准确地匹配歌曲歌词,值越大结果越准确,但是搜索速度越慢。
|
||||
|
||||
Breaking Changes: None
|
||||
|
||||
Enhancement:
|
||||
|
||||
- 关于网易云歌词下载器在搜索歌曲名带括号的歌曲时,会搜索不到的情况,现在默认会移除掉歌曲名中的括号进行搜索以增强准确性。
|
||||
- [[#100](https://github.com/real-zony/ZonyLrcToolsX/issues/100)] 优化歌曲查找逻辑,优先匹配长度符合实际歌曲长度的歌词。
|
||||
|
||||
Fixed Bugs:
|
||||
|
||||
- [[#106](https://github.com/real-zony/ZonyLrcToolsX/issues/106)] 跳过已存在歌词的选项存在逻辑问题,会导致下载专辑图像的时候也会跳过带歌词的歌曲文件。
|
||||
- [[85f325b](https://github.com/real-zony/ZonyLrcToolsX/commit/85f325b300dd4e6f54c783b2f9065ab4c566e3ad)] 修复某些极端情况下网易云歌词下载器报错的问题。
|
Reference in New Issue
Block a user