Compare commits

...

60 Commits

Author SHA1 Message Date
real-zony
e08d1c7f16 ci: Publish new version.(4.0.2)
Some checks failed
.NET / build (push) Has been cancelled
.NET / release (push) Has been cancelled
2024-12-15 15:29:32 +08:00
real-zony
c3d98d60b5 fix: Fixed the garbled text issue caused by the CSV file encoding. (#156) 2024-12-15 15:20:33 +08:00
real-zony
8aac9e16a6 ci: Release new version.
Some checks failed
.NET / build (push) Has been cancelled
.NET / release (push) Has been cancelled
2024-11-29 23:20:04 +08:00
real-zony
21f25ee500 Merge remote-tracking branch 'origin/dev' into dev 2024-11-29 23:12:45 +08:00
real-zony
9a979942e3 test: Removed irrelevant tests. 2024-11-29 23:12:26 +08:00
real-zony
32b6c4052b build: Upgraded package version. 2024-11-29 23:11:40 +08:00
real-zony
7732ab52d3 refactor: Removed avalonia code. 2024-11-29 22:54:38 +08:00
Zony
1fdbaef7a8
Set English as the default language for project README. 2024-07-10 10:08:29 +08:00
Zony
36ef2d2c3a
Update English documentation. 2024-07-10 10:07:08 +08:00
Zony
fa4620c37d
Add separate Chinese documentation. 2024-07-10 10:00:57 +08:00
real-zony
9ab9cd50e2 feat: Mostly completed bidirectional binding for settings page and configuration. 2024-07-03 23:38:33 +08:00
real-zony
e66ef89e7a build: Upgrade the YamlDotNet version. 2024-07-02 21:51:55 +08:00
real-zony
8b5d5c64b0 refactor: Adjusted the structure of the configuration file.
BREAKING CHANGE: Adjusted the internal structure of the config.yaml file, removed the top-level hierarchy.
2024-07-02 21:22:40 +08:00
real-zony
2dca5239f5 fix: Fixed exception throw when NumericUpDown is empty. 2024-07-02 21:00:03 +08:00
real-zony
d5689164b5 fix(Fixed some issues in the settings page.): 2024-07-02 18:27:52 +08:00
real-zony
b57739f543 feat: Completed the adjustments and bindings of global settings. 2024-07-02 10:43:23 +08:00
real-zony
a2c44901fd refactor: Fixed incorrect naming. 2024-06-30 22:02:33 +08:00
real-zony
6ad2992144 feat: Added some UI animation effects. 2024-06-30 21:58:41 +08:00
real-zony
3b0c55ac00 feat: Completed the download lyrics feature. 2024-06-30 17:52:12 +08:00
real-zony
81bf6ebe3f refactor: Adjusted the view model to support reactive programming. 2024-06-30 17:51:53 +08:00
real-zony
aa3c45101b feat: Added a comparator for MusicInfo. 2024-06-30 17:50:49 +08:00
real-zony
d00d735bf0 feat: When downloading lyrics, a callback was added to facilitate UI updates. 2024-06-30 17:50:26 +08:00
real-zony
7fa595a71b refactor: Removed unused styles. 2024-06-28 15:13:00 +08:00
real-zony
4f15d06e63 feat: Added a settings page. 2024-06-28 15:07:06 +08:00
real-zony
5ccd8a7c53 feat: Complete the basic GUI layout. 2024-06-28 12:33:14 +08:00
real-zony
14812bceb7 refactor: Removed the local server project. 2024-06-28 12:29:31 +08:00
real-zony
be538eb650 feat: Upgraded the versions of the packages that the project depends on. 2024-06-28 12:27:24 +08:00
real-zony
888c7f511b refactor: Removed unused files. 2024-06-28 12:23:48 +08:00
Zony
7bd1915f11
Merge pull request #150 from w568w/patch-1
在 README 中添加 AUR 安装方式介绍
2024-06-19 17:37:47 +08:00
w568w
aa78ea0713
Update README.md 2024-06-08 07:16:29 +00:00
real-zony
1342ca936c build: Upgrade dependency packages. 2024-06-03 00:41:47 +08:00
Zony
46ac2d57a2
Merge pull request #149 from ZaneYork/dev
修复网易云歌曲ID溢出问题
2024-05-31 16:20:48 +08:00
ZaneYork
444f4e75a6 修复网易云歌曲ID溢出问题 2024-05-21 17:41:40 +08:00
Zony
61aa31cc6f
Removed alipay and wechatpay QR code image. 2024-04-12 16:09:06 +08:00
Zony
46d11a86ea
Update README.md 2024-04-12 16:08:19 +08:00
Zony
8aaae90ed8
Upgraded MusicDecrypto.Library version to 2.4.0. 2024-04-12 15:34:38 +08:00
real-zony
d91cc68a60 Merge remote-tracking branch 'origin/dev' into dev 2023-12-22 15:06:32 +08:00
real-zony
b6e3cb6038 chore: Upgrade version. 2023-12-22 14:59:48 +08:00
Zony
093e2879ad
Update release.md 2023-12-22 13:54:26 +08:00
real-zony
fea5e1124f fix: Fixed the issue where Kuwo Music could not download lyrics. 2023-12-22 13:52:28 +08:00
real-zony
2ce7b00d14 ci: Release new version. 2023-12-12 22:47:19 +08:00
real-zony
ad6ea732f8 Merge remote-tracking branch 'origin/dev' into dev 2023-12-12 22:46:20 +08:00
real-zony
06bf733ae9 fix: Fixed the issue where logs were not displaying. 2023-12-12 22:45:37 +08:00
Zony
558cca3c0e
Update release.md 2023-12-12 22:07:20 +08:00
Zony
93cc31b1e2
Update release.md 2023-12-12 22:06:56 +08:00
real-zony
dfe18d8541 ci: Fixed the script error. 2023-12-12 22:05:12 +08:00
real-zony
40b73c3dfa chore: Triggered the release of a new version. 2023-12-12 22:02:45 +08:00
real-zony
243a0e2559 fix: Fixed the issue where NetEase Cloud Music could not download lyrics. 2023-12-12 21:56:49 +08:00
real-zony
d83bff1516 build: Upgrade all packages. 2023-12-12 21:55:35 +08:00
real-zony
8d946ebbd5 build: Upgraded the dependency framework to .NET 8.0. 2023-12-12 19:22:50 +08:00
Zony
4c3d554885
Merge pull request #147 from travislee89/dev
Support for more file extensions; Building arm64 binaries
2023-12-11 20:45:28 +08:00
Travis Lee
14c78f9a83 feat: build arm64 binaries 2023-12-11 15:36:12 +08:00
Travis Lee
b9d6da5f72 feat: support file extensions m4a, ogg and opus 2023-12-11 15:35:55 +08:00
real-zony
c8ffbc55af fix: Fixed the issue mentioned in #145.
Added a new line break.('\n')
2023-09-11 09:49:02 +08:00
real-zony
b0835dcf01 docs: Adjusted README.md . 2023-08-13 22:31:35 +08:00
real-zony
4e1c4d1519 chore: Corrected description information. 2023-08-13 21:25:43 +08:00
real-zony
3d96b96edf build: Fixed NuGet package reference issue. 2023-08-13 21:24:11 +08:00
real-zony
7f81a3edea refactor: Managed dependencies with unified package management. 2023-07-14 21:20:01 +08:00
real-zony
24624a6d21 feat: Added Avalonia UI project. 2023-07-14 19:01:53 +08:00
real-zony
d860632327 chore: Removed the frontend web page code. 2023-07-14 19:01:25 +08:00
77 changed files with 447 additions and 7096 deletions

View File

@ -20,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Publish

11
Directory.Build.props Normal file
View File

@ -0,0 +1,11 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<Version>4.0.2</Version>
<Authors>Zony(real-zony)</Authors>
<RepositoryUrl>https://github.com/real-zony/ZonyLrcToolsX</RepositoryUrl>
</PropertyGroup>
</Project>

45
Directory.Packages.props Normal file
View File

@ -0,0 +1,45 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="Polly" Version="8.5.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageVersion Include="MusicDecrypto.Library" Version="2.4.1" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="AutoMapper" Version="13.0.1" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.6" />
<PackageVersion Include="SuperSocket.WebSocket" Version="2.0.0-beta.11" />
<PackageVersion Include="SuperSocket.WebSocket.Server" Version="2.0.0-beta.11" />
<!-- Testing Projects -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="xunit" Version="2.8.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<!-- Avalonia -->
<PackageVersion Include="Ude.NetStandard" Version="1.2.0" />
<PackageVersion Include="YamlDotNet" Version="16.2.0" />
</ItemGroup>
</Project>

222
README.md
View File

@ -1,219 +1,39 @@
简体中文 | [English](./docs/en_US.md)
English | [简体中文](./zh_CN.md)
# 免责声明
# Disclaimer
- 本工具仅作个人学习研究使用,可运行的二进制文件仅用于演示功能,不得将源码及其产物用于商业用途,否则由此造成的相关法律问题,[本人](https://github.com/real-zony) 不承担任何法律责任。
- 任何单位或个人因下载使用软件所产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等)[本人](https://github.com/real-zony) 不承担任何法律责任。
- 用户明确并同意本声明条款列举的全部内容,对使用本工具可能存在的风险和相关后果将完全由用户自行承担,[本人](https://github.com/real-zony) 不承担任何法律责任。
- This tool is for personal learning and research purposes only. The executable binary files are for demonstration purposes only and the source code and its products must not be used for commercial purposes. Otherwise, I [here](https://github.com/real-zony) will not be responsible for any related legal issues.
- Any unit or individual that downloads and uses the software resulting in any accidents, negligence, contract breaches, defamation, copyright or intellectual property infringement, and their resulting losses (including but not limited to direct, indirect, incidental or derivative losses), I [here](https://github.com/real-zony) will not bear any legal responsibility.
- Users clearly agree to all the terms listed in this statement and fully assume the risks and consequences of using this tool. I [here](https://github.com/real-zony) will not bear any legal responsibility.
# 简介
# Introduction
ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具,**QQ 群:337656932**,群文件里面有详细的视频教程。
ZonyLrcToolX 4 is a cross-platform lyrics download tool based on CEF. **QQ Group: 337656932**. Detailed video tutorials are available in the group files.
🚧 当前版本正在开发当中。
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
🚧 The current version is under development.
🚧 If you want to see working code, please switch to the dev branch.
# 下载
# Download
工具会执行每日构建动作,请访问 **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** 页面进行下载。
To get the latest version, please visit the **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** page for download.
# 用法
## Arch Linux User Repository
Windows 用户请在软件目录当中,按住 Shift + 右键呼出菜单,然后选择 PowerShell/命令提示符/Windows 终端,根据下述说明执行命令即可。
This software has been packaged into [AUR](https://aur.archlinux.org/packages/zonylrctoolsx-bin). Arch Linux and its derivative distribution users can install it with the following command:
macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命令即可。
```bash
# yay or other AUR Helpers
yay -S zonylrctoolsx-bin
## 子命令
### 下载功能
子命令为 `download`,可用于下载歌词数据和专辑图像,支持多个下载器进行下载。
```shell
.\ZonyLrcTools.Cli.exe download -d|dir <WAIT_SCAN_DIRECTORY> [-l|--lyric] [-a|--album] [-n|--number]
.\ZonyLrcTools.Cli.exe download -h|--help
ZonyLrcTools.Cli --help
```
**例子**
# Usage
Detailed usage instructions have been moved to a new documentation site. Please visit [https://docs.myzony.com](https://docs.myzony.com) for complete documentation.
```shell
# 下载歌词
.\ZonyLrcTools.Cli.exe download -d "C:\歌曲目录" -l -n 2
# 下载专辑封面
.\ZonyLrcTools.Cli.exe download -d "C:\歌曲目录" -a -n 2
```
#### 指定歌曲文件源
目前程序支持从本地目录、CSV 文件、网易云歌单获取歌曲数据,然后下载 LRC 歌词数据。
指定歌词源的参数是 `-sc|--scanner`,可以通过这个参数指定歌曲信息的来源。它拥有以下选值,默认情况下是 `local`
```shell
-sc local # 从目录获取歌曲信息。
-sc netease # 从网易云歌单获取歌曲信息。
-sc csv # 从 CSV 文件获取歌曲信息。
```
**从网易云歌单获取歌曲数据**
完整的命令如下:
```shell
.\ZonyLrcTools.Cli.exe download -sc netease -o "D:\TempFiles" -s "7224428149" -l
```
其中 `-sc` 参数用于指定歌词信息的来源是网易云歌单; `-o` 参数指定的是歌词文件的输出目录,请尽量使用绝对路径; `-s` 参数指定的的是歌单的 ID该 ID 可以从网页版的网易云音乐获得。
例如获取地址 [https://music.163.com/#/playlist?id=158010361](https://music.163.com/#/playlist?id=158010361) 的歌单信息,那么歌单 ID 就应该传递 158010361。
如果你需要获取多个歌单的歌曲数据,请在传递歌单 ID 时使用分号 `;` 指定多个 ID例如下面的命令就是下载两个歌单的歌曲数据。
```shell
.\ZonyLrcTools.Cli.exe download -sc netease -o "D:\TempFiles" -s "7224428149;158010361" -l
```
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
![image-20230328223155280](./README.assets/image-20230328223155280.png)
**从 CSV 文件获取歌曲数据**
应 [Issue 126](https://github.com/real-zony/ZonyLrcToolsX/issues/126) 的请求,增加了从 CSV 获取歌曲信息的方式。这样可以在没有原始歌曲的情况下载歌词数据。
示例命令:
```shell
.\ZonyLrcTools.Cli.exe download -sc csv -o "D:\TempFiles" -f "D:\TempFiles\demo.csv" -l
```
其中 `-f` 参数用于指定 csv 文件的路径csv 文件的格式应该如下所示,保证第一行是列信息,一共要包含两列。
```csv
Song,Artist
刀马旦,李玟
发如雪,周杰伦
说书人,寅子
爱的供养,张国荣
七里香,周杰伦
```
### 加密格式转换
子命令为 `util`,可用于转换部分加密歌曲,**仅供个人研究学习使用,思路与源码都来自于网络**。
具体支持的格式请参考项目 [MusicDecrypto](https://github.com/davidxuang/MusicDecrypto/blob/master/MusicDecrypto.Library/DecryptoFactory.cs#L23),本工具仅做一个集成,替换掉原本自己的一些实现。现在不需要指定对应的类型参数,程序会自动根据文件后缀选择适合的解密算法。
命令只需要一个参数 `-s`,指定需要转换的文件夹或者是文件路径。
```shell
.\ZonyLrcTools.Cli.exe util -s D:\CloudMusic
```
## 配置文件
程序的所有的配置信息,都在 `config.yaml` 进行更改,下面标注了各个配置的说明。
其中是否开启的可选项为 `true` 或者 `false`,等同于中文的是和否。
```yaml
globalOption:
# 允许扫描的歌曲文件后缀名。
supportFileExtensions:
- '*.mp3'
- '*.flac'
- '*.wav'
# 网络代理服务设置,仅支持 HTTP 代理。
networkOptions:
isEnable: false # 是否启用代理。
ip: 127.0.0.1 # 代理服务 IP 地址。
port: 4780 # 代理服务端口号。
updateUrl: https://api.myzony.com/lrc-tools/update # 更新检查地址。
# 下载器的相关参数配置。
provider:
# 标签扫描器的相关参数配置。
tag:
# 支持的标签扫描器。
plugin:
- name: Taglib # 基于 Taglib 库的标签扫描器。
priority: 1 # 优先级,升序排列。
- name: FileName # 基于文件名的标签扫描器。
priority: 2
# 基于文件名扫描器的扩展参数。
extensions:
# 正则表达式,用于匹配文件名中的作者信息和歌曲信息,可根据
# 自己的需求进行调整。
regularExpressions: "(?'artist'.+)\\s-\\s(?'name'.+)"
# 歌曲标签屏蔽字典替换功能。
blockWord:
isEnable: false # 是否启用屏蔽字典。
filePath: 'BlockWords.json' # 屏蔽字典的路径。
# 歌词下载器的相关参数配置。
lyric:
# 支持的歌词下载器。
plugin:
- name: NetEase # 基于网易云音乐的歌词下载器。
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
depth: 10 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
- name: QQ # 基于 QQ 音乐的歌词下载器。
priority: 2
# depth: 10 # 暂时不支持。
- name: KuGou # 基于酷狗音乐的歌词下载器。
priority: 3
depth: 10
- name: KuWo # 基于酷我音乐的歌词下载器。
priority: 4
depth: 10
# 歌词下载的一些共有配置参数。
config:
isOneLine: true # 双语歌词是否合并为一行展示。
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
isEnableTranslation: true # 是否启用翻译歌词。
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
fileEncoding: 'utf-8' # 歌词文件的编码格式。
```
### 支持的编码格式
详细信息请参考: [MSDN Encoding 列表](https://learn.microsoft.com/en-us/dotnet/api/System.Text.Encoding.GetEncodings?view=net-6.0#examples),使用 `identifier and name` 作为参数值填入 `config.yaml` 文件当中的 `fileEncoding`
> 针对 UTF-8 BOM 格式,程序进行了特殊处理,请在 `fileEncoding` 里面填写 "utf-8-bom" 以支持。
### 支持的歌词源
| 歌词源 | 默认优先级 |
| ---------- | ---------- |
| 网易云音乐 | 1 |
| QQ 音乐 | 2 |
| 酷狗音乐 | 3 |
| 酷我音乐 | 4 |
## 屏蔽字典
屏蔽字典适用于网易云音乐歌词下载,针对某些单词,网易云音乐使用了 * 号进行屏蔽,这个时候可以使用屏蔽字典,设置歌曲名的关键词替换。例如有一首歌曲叫做 *Fucking ABC* ,这个时候网易云实际的名字是 *Fu****ing* ,用户只需要在屏蔽字典加入替换逻辑即可,例如:
```json
{
"Fuckking": "Fu****ing"
}
```
屏蔽字典默认路径为程序所在目录的 *BlockWords.json* 文件,用户可以在 *appsettings.json* 文件中配置其他路径。
# 捐赠
<img src="./docs/img/alipay.jpg" width="200"/><img src="./docs/img/wechat.jpg" width="200"/>
# Donation
[爱发电](https://afdian.net/a/zony-lrc-tools)
# Star History
[![Star History Chart](https://api.star-history.com/svg?repos=real-zony/ZonyLrcToolsX&type=Timeline)](https://star-history.com/#real-zony/ZonyLrcToolsX&Timeline)
# 路线图
- [x] 支持跨平台的 CLI 工具。
- [x] 基于 Web GUI 的操作站点。
- [ ] 支持插件系统(Lua 引擎)。

View File

@ -11,8 +11,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Cli", "src\Zon
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Tests", "tests\ZonyLrcTools.Tests\ZonyLrcTools.Tests.csproj", "{FFBD3200-568F-455B-8390-5E76C51D522C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.LocalServer", "src\ZonyLrcTools.LocalServer\ZonyLrcTools.LocalServer.csproj", "{2875A08A-FFD6-4863-B815-5384DCFE88FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Common", "src\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj", "{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}"
EndProject
Global
@ -29,10 +27,6 @@ Global
{FFBD3200-568F-455B-8390-5E76C51D522C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFBD3200-568F-455B-8390-5E76C51D522C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFBD3200-568F-455B-8390-5E76C51D522C}.Release|Any CPU.Build.0 = Release|Any CPU
{2875A08A-FFD6-4863-B815-5384DCFE88FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2875A08A-FFD6-4863-B815-5384DCFE88FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2875A08A-FFD6-4863-B815-5384DCFE88FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2875A08A-FFD6-4863-B815-5384DCFE88FC}.Release|Any CPU.Build.0 = Release|Any CPU
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -47,7 +41,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{55D74323-ABFA-4A73-A3BF-F3E8D5DE6DE8} = {C29FB05C-54B1-4020-94D2-87E192EB2F98}
{FFBD3200-568F-455B-8390-5E76C51D522C} = {AF8ADB2F-E46C-4DEE-8316-652D9FE1A69B}
{2875A08A-FFD6-4863-B815-5384DCFE88FC} = {C29FB05C-54B1-4020-94D2-87E192EB2F98}
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67} = {C29FB05C-54B1-4020-94D2-87E192EB2F98}
EndGlobalSection
EndGlobal

View File

@ -1,18 +1,39 @@
English | [简体中文](./zh_CN.md)
## Overview
# Disclaimer
ZonyLrcToolX 2.0 is a cross-platform lyric downlaod tool based on CEF.
- This tool is for personal learning and research purposes only. The executable binary files are for demonstration purposes only and the source code and its products must not be used for commercial purposes. Otherwise, I [here](https://github.com/real-zony) will not be responsible for any related legal issues.
- Any unit or individual that downloads and uses the software resulting in any accidents, negligence, contract breaches, defamation, copyright or intellectual property infringement, and their resulting losses (including but not limited to direct, indirect, incidental or derivative losses), I [here](https://github.com/real-zony) will not bear any legal responsibility.
- Users clearly agree to all the terms listed in this statement and fully assume the risks and consequences of using this tool. I [here](https://github.com/real-zony) will not bear any legal responsibility.
🚧 The current version is under development.
🚧 If you want to see the working code, please switch to the 1.0 branch.
# Introduction
## Usage
ZonyLrcToolX 4 is a cross-platform lyrics download tool based on CEF. **QQ Group: 337656932**. Detailed video tutorials are available in the group files.
## Donation
🚧 The current version is under development.
🚧 If you want to see working code, please switch to the dev branch.
## Roadmap
# Download
- [ ] Supports cross-platform CLI tools.
- [ ] Web GUI based site (local).
- [ ] Support plug-in system (Lua Engine).
To get the latest version, please visit the **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** page for download.
## Arch Linux User Repository
This software has been packaged into [AUR](https://aur.archlinux.org/packages/zonylrctoolsx-bin). Arch Linux and its derivative distribution users can install it with the following command:
```bash
# yay or other AUR Helpers
yay -S zonylrctoolsx-bin
ZonyLrcTools.Cli --help
```
# Usage
Detailed usage instructions have been moved to a new documentation site. Please visit [https://docs.myzony.com](https://docs.myzony.com) for complete documentation.
# Donation
[爱发电](https://afdian.net/a/zony-lrc-tools)
# Star History
[![Star History Chart](https://api.star-history.com/svg?repos=real-zony/ZonyLrcToolsX&type=Timeline)](https://star-history.com/#real-zony/ZonyLrcToolsX&Timeline)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

40
docs/zh_CN.md Normal file
View File

@ -0,0 +1,40 @@
简体中文 | [English](./docs/en_US.md)
# 免责声明
- 本工具仅作个人学习研究使用,可运行的二进制文件仅用于演示功能,不得将源码及其产物用于商业用途,否则由此造成的相关法律问题,[本人](https://github.com/real-zony) 不承担任何法律责任。
- 任何单位或个人因下载使用软件所产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等)[本人](https://github.com/real-zony) 不承担任何法律责任。
- 用户明确并同意本声明条款列举的全部内容,对使用本工具可能存在的风险和相关后果将完全由用户自行承担,[本人](https://github.com/real-zony) 不承担任何法律责任。
# 简介
ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具,**QQ 群:337656932**,群文件里面有详细的视频教程。
🚧 当前版本正在开发当中。
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
# 下载
如果你要获取最新版本,请访问 **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** 页面进行下载。
## Arch Linux 用户仓库
本软件已打包到 [AUR](https://aur.archlinux.org/packages/zonylrctoolsx-bin)。Arch Linux 及其衍生发行版用户可用如下命令安装:
```bash
# yay 或其他 AUR Helper
yay -S zonylrctoolsx-bin
ZonyLrcTools.Cli --help
```
# 用法
软件的具体使用方法已经迁移到了全新的文档站点,请跳转到 [https://docs.myzony.com](https://docs.myzony.com),里面包含完整的说明文档。
# 捐赠
[爱发电](https://afdian.net/a/zony-lrc-tools)
# Star History
[![Star History Chart](https://api.star-history.com/svg?repos=real-zony/ZonyLrcToolsX&type=Timeline)](https://star-history.com/#real-zony/ZonyLrcToolsX&Timeline)

View File

@ -46,7 +46,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
[Option("-a|--album", CommandOptionType.NoValue, Description = "指定程序需要下载专辑图像。")]
public bool DownloadAlbum { get; set; }
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 1)")]
public int ParallelNumber { get; set; } = 1;
#endregion

View File

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils;
@ -79,11 +78,10 @@ namespace ZonyLrcTools.Cli
private static Task<int> BuildHostedService(string[] args)
{
return new HostBuilder()
.ConfigureLogging(builder => builder.AddSerilog())
.ConfigureLogging(l => l.AddSerilog())
.ConfigureHostConfiguration(builder =>
{
builder
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
builder.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddYamlFile("config.yaml");
})
.ConfigureServices((_, services) =>

View File

@ -1,46 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<AssemblyVersion>0.0.0.1</AssemblyVersion>
<FileVersion>0.0.0.1</FileVersion>
<PackageVersion>0.0.0.1</PackageVersion>
<Version>0.0.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.2" />
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils"/>
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine"/>
<PackageReference Include="Microsoft.Extensions.Hosting"/>
<PackageReference Include="Serilog.Extensions.Hosting"/>
<PackageReference Include="Serilog.Sinks.Async"/>
<PackageReference Include="Serilog.Sinks.Console"/>
<PackageReference Include="Serilog.Sinks.File"/>
<PackageReference Include="System.Text.Encoding.CodePages"/>
<PackageReference Include="Ude.NetStandard"/>
</ItemGroup>
<ItemGroup>
<None Remove="appsettings.json" />
<None Remove="Resources\error_msg.json" />
<None Remove="appsettings.json"/>
<None Remove="Resources\error_msg.json"/>
<Content Include="Resources\error_msg.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Remove="BlockWords.json" />
<None Remove="BlockWords.json"/>
<Content Include="BlockWords.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Remove="config.yaml" />
<None Remove="config.yaml"/>
<Content Include="config.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj" />
<ProjectReference Include="..\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj"/>
</ItemGroup>
</Project>

View File

@ -1,58 +1,61 @@
globalOption:
# 允许扫描的歌曲文件后缀名。
supportFileExtensions:
- '*.mp3'
- '*.flac'
- '*.wav'
# 网络代理服务设置,仅支持 HTTP 代理。
networkOptions:
isEnable: false # 是否启用代理。
ip: 127.0.0.1 # 代理服务 IP 地址。
port: 4780 # 代理服务端口号。
updateUrl: https://api.myzony.com/lrc-tools/update # 更新检查地址。
# 下载器的相关参数配置。
provider:
# 标签扫描器的相关参数配置。
tag:
# 支持的标签扫描器。
plugin:
- name: Taglib # 基于 Taglib 库的标签扫描器。
priority: 1 # 优先级,升序排列。
- name: FileName # 基于文件名的标签扫描器。
priority: 2
# 基于文件名扫描器的扩展参数。
extensions:
# 正则表达式,用于匹配文件名中的作者信息和歌曲信息,可根据
# 自己的需求进行调整。
regularExpressions: "(?'artist'.+)\\s-\\s(?'name'.+)"
# 歌曲标签屏蔽字典替换功能。
blockWord:
isEnable: false # 是否启用屏蔽字典。
filePath: 'BlockWords.json' # 屏蔽字典的路径。
# 歌词下载器的相关参数配置。
lyric:
# 支持的歌词下载器。
plugin:
- name: NetEase # 基于网易云音乐的歌词下载器。
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
depth: 10 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
additional:
isEnableRomanOutput: false # 是否启用罗马音输出,本参数仅当对应歌曲有罗马音歌词时有效。
- name: QQ # 基于 QQ 音乐的歌词下载器。
priority: 2
# depth: 10 # 暂时不支持。
- name: KuGou # 基于酷狗音乐的歌词下载器。
priority: 3
depth: 10
- name: KuWo # 基于酷我音乐的歌词下载器。
priority: 4
depth: 10
# 歌词下载的一些共有配置参数。
config:
isOneLine: true # 双语歌词是否合并为一行展示。
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
isEnableTranslation: true # 是否启用翻译歌词。
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
fileEncoding: 'utf-8' # 歌词文件的编码格式。
# 允许扫描的歌曲文件后缀名。
supportFileExtensions:
- '*.mp3'
- '*.flac'
- '*.wav'
- '*.m4a'
- '*.ogg'
- '*.opus'
# 网络代理服务设置,仅支持 HTTP 代理。
networkOptions:
isEnable: false # 是否启用代理。
ip: 127.0.0.1 # 代理服务 IP 地址。
port: 4780 # 代理服务端口号。
updateUrl: https://api.myzony.com/lrc-tools/update # 更新检查地址。
# 下载器的相关参数配置。
provider:
# 标签扫描器的相关参数配置。
tag:
# 支持的标签扫描器。
plugin:
- name: Taglib # 基于 Taglib 库的标签扫描器。
priority: 1 # 优先级,升序排列。
- name: FileName # 基于文件名的标签扫描器。
priority: 2
# 基于文件名扫描器的扩展参数。
extensions:
# 正则表达式,用于匹配文件名中的作者信息和歌曲信息,可根据
# 自己的需求进行调整。
regularExpressions: "(?'artist'.+)\\s-\\s(?'name'.+)"
# 歌曲标签屏蔽字典替换功能。
blockWord:
isEnable: false # 是否启用屏蔽字典。
filePath: 'BlockWords.json' # 屏蔽字典的路径。
# 歌词下载器的相关参数配置。
lyric:
# 支持的歌词下载器。
plugin:
- name: NetEase # 基于网易云音乐的歌词下载器。
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
depth: 10 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
additional:
isEnableRomanOutput: false # 是否启用罗马音输出,本参数仅当对应歌曲有罗马音歌词时有效。
- name: QQ # 基于 QQ 音乐的歌词下载器。
priority: 2
# depth: 10 # 暂时不支持。
- name: KuGou # 基于酷狗音乐的歌词下载器。
priority: 3
depth: 10
- name: KuWo # 基于酷我音乐的歌词下载器。
priority: 4
depth: 10
# 歌词下载的一些共有配置参数。
config:
isOneLine: true # 双语歌词是否合并为一行展示。
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
isEnableTranslation: true # 是否启用翻译歌词。
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
fileEncoding: 'utf-8' # 歌词文件的编码格式。

View File

@ -1,4 +1,4 @@
$Platforms = @('win-x64', 'linux-x64', 'osx-x64')
$Platforms = @('win-x64', 'linux-x64', 'osx-x64', 'win-arm64', 'linux-arm64', 'osx-arm64')
if (-not (Test-Path ./TempFiles)) {
New-Item -ItemType Directory -Path ./TempFiles | Out-Null

View File

@ -1,5 +1,5 @@
#!/bin/bash
Platforms=('win-x64' 'linux-x64' 'osx-x64')
Platforms=('win-x64' 'linux-x64' 'osx-x64' 'win-arm64' 'linux-arm64' 'osx-arm64')
if ! [ -d './TempFiles' ];
then
@ -12,9 +12,9 @@ for platform in "${Platforms[@]}"
do
dotnet publish -r "$platform" -c Release -p:PublishSingleFile=true -p:DebugType=none --self-contained true || exit 1
cd ./bin/Release/net7.0/"$platform"/publish/ || exit 1
cd ./bin/Release/net8.0/"$platform"/publish/ || exit 1
zip -r ./ZonyLrcTools_"$platform"_"${PUBLISH_VERSION}".zip ./ || exit 1
cd ../../../../../ || exit 1
mv ./bin/Release/net7.0/"$platform"/publish/ZonyLrcTools_"$platform"_"$PUBLISH_VERSION".zip ./TempFiles
mv ./bin/Release/net8.0/"$platform"/publish/ZonyLrcTools_"$platform"_"$PUBLISH_VERSION".zip ./TempFiles
done

View File

@ -16,5 +16,10 @@
/// 搜索深度,值越大搜索结果越多,但搜索时间越长。
/// </summary>
public int Depth { get; set; }
/// <summary>
/// 歌词下载器的扩展属性。
/// </summary>
public Dictionary<string, string>? Additional { get; set; }
}
}

View File

@ -19,5 +19,10 @@ namespace ZonyLrcTools.Common.Configuration
/// 代理服务器的 端口。
/// </summary>
public int Port { get; set; }
/// <summary>
/// 更新检查的 Url。
/// </summary>
public string UpdateUrl { get; set; } = default!;
}
}

View File

@ -8,4 +8,9 @@ public class TagInfoOptions
/// 屏蔽词功能相关配置。
/// </summary>
public BlockWordOptions BlockWord { get; set; } = null!;
public TagInfoProviderOptions GetTagProviderOption(string name)
{
return Plugin.FirstOrDefault(x => x.Name == name)!;
}
}

View File

@ -6,6 +6,6 @@ namespace ZonyLrcTools.Common.Configuration
public int Priority { get; set; }
public Dictionary<string, string> Extensions { get; set; } = null!;
public Dictionary<string, string>? Extensions { get; set; } = null!;
}
}

View File

@ -47,7 +47,7 @@ namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
.AddYamlFile("config.yaml")
.Build();
services.Configure<GlobalOptions>(configuration.GetSection("globalOption"));
services.Configure<GlobalOptions>(configuration);
return services;
}

View File

@ -1,10 +1,24 @@
namespace ZonyLrcTools.Common.Lyrics;
/// <summary>
/// 歌词下载核心逻辑的接口定义。
/// </summary>
public interface ILyricsDownloader
{
/// <summary>
/// 使用给定的歌词信息下载歌词,并输出文件到指定的路径。
/// </summary>
/// <param name="needDownloadMusicInfos">需要下载的歌词信息。</param>
/// <param name="parallelCount">下载线程/并发量。</param>
/// <param name="callback">任务完成之后的回调方法。</param>
/// <param name="cancellationToken">任务取消标记。</param>
Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
int parallelCount = 2,
int parallelCount = 1,
Func<MusicInfo, Task>? callback = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取目前可用的歌词下载器。
/// </summary>
IEnumerable<ILyricsProvider> AvailableProviders { get; }
}

View File

@ -10,6 +10,7 @@ using ZonyLrcTools.Common.Infrastructure.Threading;
namespace ZonyLrcTools.Common.Lyrics;
/// <inheritdoc cref="ZonyLrcTools.Common.Lyrics.ILyricsDownloader" />
public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
{
private readonly IEnumerable<ILyricsProvider> _lyricsProviders;
@ -38,6 +39,7 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
public async Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
int parallelCount = 1,
Func<MusicInfo, Task>? callback = null,
CancellationToken cancellationToken = default)
{
await _logger.InfoAsync("开始下载歌词文件数据...");
@ -57,12 +59,12 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
{
await DownloadAndWriteLyricsAsync(lyricsProvider, info);
if (info.IsSuccessful)
{
_logger.LogSuccessful(info);
return;
}
if (!info.IsSuccessful) continue;
_logger.LogSuccessful(info);
break;
}
if (callback != null) await callback(info);
}, cancellationToken), cancellationToken));
await Task.WhenAll(downloadTasks);

View File

@ -8,7 +8,7 @@ using ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel;
namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
{
public class KuGourLyricsProvider : LyricsProvider
public class KuGouLyricsProvider : LyricsProvider
{
public override string DownloaderName => InternalLyricsProviderNames.KuGou;
@ -20,7 +20,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
private const string KuGouGetLyricAccessKeyUrl = @"http://lyrics.kugou.com/search";
private const string KuGouGetLyricUrl = @"http://lyrics.kugou.com/download";
public KuGourLyricsProvider(IWarpHttpClient warpHttpClient,
public KuGouLyricsProvider(IWarpHttpClient warpHttpClient,
ILyricsItemCollectionFactory lyricsItemCollectionFactory,
IOptions<GlobalOptions> options)
{

View File

@ -4,13 +4,49 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
public class SongSearchRequest
{
[JsonProperty("key")] public string Keyword { get; set; }
[JsonProperty("all")] public string Keyword { get; set; }
[JsonProperty("pn")] public int PageNumber { get; }
[JsonProperty("rn")] public int PageSize { get; }
public SongSearchRequest(string name, string artist, int pageNumber = 1, int pageSize = 20)
[JsonProperty("ft")] public string Unknown1 { get; } = "music";
[JsonProperty("newsearch")] public string Unknown2 { get; } = "1";
[JsonProperty("alflac")] public string Unknown3 { get; } = "1";
[JsonProperty("itemset")] public string Unknown4 { get; } = "web_2013";
[JsonProperty("client")] public string Unknown5 { get; } = "kt";
[JsonProperty("cluster")] public string Unknown6 { get; } = "0";
[JsonProperty("vermerge")] public string Unknown7 { get; } = "1";
[JsonProperty("rformat")] public string Unknown8 { get; } = "json";
[JsonProperty("encoding")] public string Unknown9 { get; } = "utf8";
[JsonProperty("show_copyright_off")] public string Unknown10 { get; } = "1";
[JsonProperty("pcmp4")] public string Unknown11 { get; } = "1";
[JsonProperty("ver")] public string Unknown12 { get; } = "mbox";
[JsonProperty("plat")] public string Unknown13 { get; } = "pc";
[JsonProperty("vipver")] public string Unknown14 { get; } = "MUSIC_9.2.0.0_W6";
[JsonProperty("devid")] public string Unknown15 { get; } = "11404450";
[JsonProperty("newver")] public string Unknown16 { get; } = "1";
[JsonProperty("issubtitle")] public string Unknown17 { get; } = "1";
[JsonProperty("pcjson")] public string Unknown18 { get; } = "1";
public SongSearchRequest(string name, string artist, int pageNumber = 0, int pageSize = 20)
{
Keyword = $"{name} {artist}";
PageNumber = pageNumber;

View File

@ -4,15 +4,14 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
public class SongSearchResponse
{
[JsonProperty("code")] public int Code { get; set; }
[JsonProperty("TOTAL")] public int TotalCount { get; set; }
[JsonProperty("data")] public SongSearchResponseInnerData InnerData { get; set; } = null!;
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
[JsonProperty("abslist")]
public IList<SongSearchResponseSongDetail> SongList { get; set; }
public long GetMatchedMusicId(string musicName, string artistName, long? duration)
{
var prefectMatch = InnerData.SongItems.FirstOrDefault(x => x.Name == musicName && x.Artist == artistName);
var prefectMatch = SongList.FirstOrDefault(x => x.Name == musicName && x.Artist == artistName);
if (prefectMatch != null)
{
return prefectMatch.MusicId;
@ -20,27 +19,42 @@ public class SongSearchResponse
if (duration is null or 0)
{
return InnerData.SongItems.First().MusicId;
return SongList.First().MusicId;
}
return InnerData.SongItems.OrderBy(t => Math.Abs(t.Duration - duration.Value)).First().MusicId;
return SongList.OrderBy(t => Math.Abs(t.Duration - duration.Value)).First().MusicId;
}
}
public class SongSearchResponseInnerData
public class SongSearchResponseSongDetail
{
[JsonProperty("total")] public string? Total { get; set; }
/// <summary>
/// 专辑名称。
/// </summary>
[JsonProperty("ALBUM")]
public string Album { get; set; }
[JsonProperty("list")] public ICollection<SongSearchResponseDetail> SongItems { get; set; } = null!;
}
/// <summary>
/// 歌手名称。
/// </summary>
[JsonProperty("ARTIST")]
public string Artist { get; set; }
public class SongSearchResponseDetail
{
[JsonProperty("artist")] public string? Artist { get; set; }
/// <summary>
/// 歌曲名称。
/// </summary>
[JsonProperty("SONGNAME")]
public string Name { get; set; }
[JsonProperty("name")] public string? Name { get; set; }
/// <summary>
/// 歌曲的 ID。
/// </summary>
[JsonProperty("DC_TARGETID")]
public long MusicId { get; set; }
[JsonProperty("rid")] public long MusicId { get; set; }
[JsonProperty("duration")] public long Duration { get; set; }
/// <summary>
/// 歌曲的时间长度。
/// </summary>
[JsonProperty("DURATION")]
public long Duration { get; set; }
}

View File

@ -13,29 +13,27 @@ public class KuWoLyricsProvider : LyricsProvider
{
public override string DownloaderName => InternalLyricsProviderNames.KuWo;
private const string KuWoSearchMusicUrl = @"https://www.kuwo.cn/api/www/search/searchMusicBykeyWord";
private const string KuWoSearchMusicUrl = @"https://search.kuwo.cn/r.s";
private const string KuWoSearchLyricsUrl = @"https://m.kuwo.cn/newh5/singles/songinfoandlrc";
private const string KuWoDefaultToken = "ABCDE12345";
private readonly IWarpHttpClient _warpHttpClient;
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
private readonly GlobalOptions _options;
private static readonly ProductInfoHeaderValue UserAgent = new("Chrome", "81.0.4044.138");
public KuWoLyricsProvider(IWarpHttpClient warpHttpClient,
ILyricsItemCollectionFactory lyricsItemCollectionFactory,
IOptions<GlobalOptions> options)
{
_warpHttpClient = warpHttpClient;
_lyricsItemCollectionFactory = lyricsItemCollectionFactory;
_options = options.Value;
}
protected override async ValueTask<object> DownloadDataAsync(LyricsProviderArgs args)
{
var songSearchResponse = await _warpHttpClient.GetAsync<SongSearchResponse>(KuWoSearchMusicUrl,
new SongSearchRequest(args.SongName, args.Artist, pageSize: _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
new SongSearchRequest(args.SongName, args.Artist,
pageSize: _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
op =>
{
op.Headers.UserAgent.Add(UserAgent);
@ -55,7 +53,8 @@ public class KuWoLyricsProvider : LyricsProvider
});
}
protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(object lyricsObject, LyricsProviderArgs args)
protected override async ValueTask<LyricsItemCollection> GenerateLyricAsync(object lyricsObject,
LyricsProviderArgs args)
{
await ValueTask.CompletedTask;
@ -69,7 +68,8 @@ public class KuWoLyricsProvider : LyricsProvider
{
var position = double.Parse(l.Position);
var positionSpan = TimeSpan.FromSeconds(position);
return new LyricsItem(positionSpan.Minutes, double.Parse($"{positionSpan.Seconds}.{positionSpan.Milliseconds}"), l.Text);
return new LyricsItem(positionSpan.Minutes,
double.Parse($"{positionSpan.Seconds}.{positionSpan.Milliseconds}"), l.Text);
});
var lyricsItemCollection = new LyricsItemCollection(_options.Provider.Lyric.Config);
@ -79,12 +79,7 @@ public class KuWoLyricsProvider : LyricsProvider
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
{
if (response.Code != 200)
{
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, response.ErrorMessage, args);
}
if (response.InnerData.SongItems.Count == 0)
if (response.TotalCount == 0)
{
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
}

View File

@ -4,13 +4,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
{
public class GetSongDetailsRequest
{
public GetSongDetailsRequest(int songId)
public GetSongDetailsRequest(long songId)
{
SongId = songId;
SongIds = $"%5B{songId}%5D";
}
[JsonProperty("id")] public int SongId { get; }
[JsonProperty("id")] public long SongId { get; }
[JsonProperty("ids")] public string SongIds { get; }
}

View File

@ -8,7 +8,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
[JsonProperty("code")] public int StatusCode { get; set; }
public int GetFirstMatchSongId(string songName, long? duration)
public long GetFirstMatchSongId(string songName, long? duration)
{
var perfectMatch = Items.SongItems.FirstOrDefault(x => x.Name == songName);
if (perfectMatch != null)
@ -44,7 +44,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
/// 歌曲的 Sid (Song Id)。
/// </summary>
[JsonProperty("id")]
public int Id { get; set; }
public long Id { get; set; }
/// <summary>
/// 歌曲的演唱者。

View File

@ -17,7 +17,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
private readonly GlobalOptions _options;
private const string NetEaseSearchMusicUrl = @"https://music.163.com/weapi/cloudsearch/get/web";
private const string NetEaseSearchMusicUrl = @"https://music.163.com/weapi/search/get";
private const string NetEaseGetLyricUrl = @"https://music.163.com/weapi/song/lyric?csrf_token=";
private const string NetEaseRequestReferer = @"https://music.163.com";

View File

@ -57,5 +57,25 @@ namespace ZonyLrcTools.Common
[GeneratedRegex(@"[<>:""/\\|?*]")]
private static partial Regex InvalidFilePathRegex();
public static bool operator ==(MusicInfo? left, MusicInfo? right)
{
if (left is null && right is null)
{
return true;
}
if (left is null || right is null)
{
return false;
}
return left.FilePath == right.FilePath;
}
public static bool operator !=(MusicInfo? left, MusicInfo? right)
{
return !(left == right);
}
}
}

View File

@ -1,3 +1,5 @@
using System.Text;
using Ude;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
namespace ZonyLrcTools.Common.MusicScanner;
@ -13,11 +15,15 @@ public class CsvFileMusicScanner : ITransientDependency
/// <param name="csvFilePath">CSV 文件的路径。</param>
/// <param name="outputDirectory">歌词文件的输出目录。</param>
/// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</param>
public async Task<List<MusicInfo>> GetMusicInfoFromCsvFileAsync(string csvFilePath, string outputDirectory, string pattern)
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, outputDirectory, pattern)).ToList();
var encoding = DetectFileEncoding(csvFilePath);
var csvFileContent = await File.ReadAllTextAsync(csvFilePath, encoding);
var csvFileLines = csvFileContent.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
return csvFileLines.Skip(1).Select(line => GetMusicInfoFromCsvFileLine(line, outputDirectory, pattern))
.ToList();
}
private MusicInfo GetMusicInfoFromCsvFileLine(string csvFileLine, string outputDirectory, string pattern)
@ -29,4 +35,19 @@ public class CsvFileMusicScanner : ITransientDependency
var musicInfo = new MusicInfo(fakeFilePath, name, artist);
return musicInfo;
}
private Encoding DetectFileEncoding(string filePath)
{
using var fileStream = File.OpenRead(filePath);
var detector = new CharsetDetector();
detector.Feed(fileStream);
detector.DataEnd();
if (detector.Charset != null)
{
return Encoding.GetEncoding(detector.Charset);
}
return Encoding.Default;
}
}

View File

@ -139,7 +139,7 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
var asciiQrCodeString = qrCode.GetGraphic(1, drawQuietZones: false);
_logger.LogInformation("请使用网易云 APP 扫码登录:");
_logger.LogInformation(asciiQrCodeString);
_logger.LogInformation("\n{AsciiQrCodeString}", asciiQrCodeString);
// Wait for login success.
var isLogin = false;

View File

@ -1,5 +0,0 @@
// namespace ZonyLrcTools.Common.MusicScanner;
//
// public class QQMusicSongListMusicScanner : IMusicScanner
// {
// }

View File

@ -1,26 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>4.0.0.55</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="MusicDecrypto.Library" Version="2.2.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Http"/>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions"/>
<PackageReference Include="MusicDecrypto.Library"/>
<PackageReference Include="Newtonsoft.Json"/>
<PackageReference Include="NetEscapades.Configuration.Yaml"/>
<PackageReference Include="Polly"/>
<PackageReference Include="QRCoder"/>
<PackageReference Include="TagLibSharp"/>
<PackageReference Include="Ude.NetStandard" />
<PackageReference Include="YamlDotNet" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Lyrics\Providers\Kugeci\KugeciDownloader.cs" />
<Compile Remove="Lyrics\Providers\Kugeci\KugeciDownloader.cs"/>
</ItemGroup>
</Project>

View File

@ -1,7 +0,0 @@
namespace ZonyLrcTools.LocalServer.Contract.Dtos;
public class PagedListRequestDto
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
}

View File

@ -1,9 +0,0 @@
namespace ZonyLrcTools.LocalServer.Contract.Dtos;
public class PagedListResultDto<T>
{
public int TotalCount { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; }
public List<T> Items { get; set; } = new List<T>();
}

View File

@ -1,24 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.LocalServer.Contract.Dtos;
using ZonyLrcTools.LocalServer.Services.MusicInfo;
using ZonyLrcTools.LocalServer.Services.MusicInfo.Dtos;
namespace ZonyLrcTools.LocalServer.Controllers;
[Route("api/music-infos")]
public class MusicInfoController : Controller, IMusicInfoService, ITransientDependency
{
private readonly IMusicInfoService _musicInfoService;
public MusicInfoController(IMusicInfoService musicInfoService)
{
_musicInfoService = musicInfoService;
}
[HttpGet]
public Task<PagedListResultDto<MusicInfoListItemDto>> GetMusicInfoListAsync(MusicInfoListInput input)
{
return _musicInfoService.GetMusicInfoListAsync(input);
}
}

View File

@ -1,15 +0,0 @@
using SuperSocket.WebSocket.Server;
namespace ZonyLrcTools.LocalServer.EventBus;
public class SuperSocketListener
{
public async Task ListenAsync()
{
var host = WebSocketHostBuilder.Create()
.UseWebSocketMessageHandler(async (session, message) => { await session.SendAsync(message); })
.Build();
await host.StartAsync();
}
}

View File

@ -1,66 +0,0 @@
using System.Diagnostics;
using System.Reflection;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.LocalServer.EventBus;
#region Main Flow
var app = RegisterAndConfigureServices();
await ListenServices();
#endregion
#region Configure Services
async Task ListenServices()
{
await new SuperSocketListener().ListenAsync();
await app?.RunAsync()!;
}
WebApplication? RegisterAndConfigureServices()
{
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(k => k.ListenAnyIP(50002));
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.BeginAutoDependencyInject<Program>();
var insideApp = builder.Build();
insideApp.UseSpaStaticFiles(new StaticFileOptions
{
RequestPath = "",
FileProvider = new Microsoft.Extensions.FileProviders
.ManifestEmbeddedFileProvider(
Assembly.GetExecutingAssembly(), "UiStaticResources"
)
});
insideApp.MapControllers();
#if !DEBUG
insideApp.Lifetime.ApplicationStarted.Register(OpenBrowser);
#endif
return insideApp;
}
void OpenBrowser()
{
const string url = "http://localhost:50002/index.html";
if (OperatingSystem.IsWindows())
{
Process.Start("explorer.exe", url);
}
else if (OperatingSystem.IsMacOS())
{
Process.Start("open", url);
}
else if (OperatingSystem.IsLinux())
{
Process.Start("xdg-open", url);
}
}
#endregion

View File

@ -1,14 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"ZonyLrcTools.LocalServer": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:50002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -1,16 +0,0 @@
using ZonyLrcTools.LocalServer.Contract.Dtos;
namespace ZonyLrcTools.LocalServer.Services.MusicInfo.Dtos;
public class MusicInfoListItemDto
{
public string Name { get; set; }
public int Size { get; set; }
public int Status { get; set; }
}
public class MusicInfoListInput : PagedListRequestDto
{
}

View File

@ -1,9 +0,0 @@
using ZonyLrcTools.LocalServer.Contract.Dtos;
using ZonyLrcTools.LocalServer.Services.MusicInfo.Dtos;
namespace ZonyLrcTools.LocalServer.Services.MusicInfo;
public interface IMusicInfoService
{
Task<PagedListResultDto<MusicInfoListItemDto>> GetMusicInfoListAsync(MusicInfoListInput input);
}

View File

@ -1,38 +0,0 @@
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.LocalServer.Contract.Dtos;
using ZonyLrcTools.LocalServer.Services.MusicInfo.Dtos;
namespace ZonyLrcTools.LocalServer.Services.MusicInfo;
public class MusicInfoService : ITransientDependency, IMusicInfoService
{
public async Task<PagedListResultDto<MusicInfoListItemDto>> GetMusicInfoListAsync(MusicInfoListInput input)
{
await Task.CompletedTask;
return new PagedListResultDto<MusicInfoListItemDto>
{
Items = new List<MusicInfoListItemDto>
{
new MusicInfoListItemDto
{
Name = "测试歌曲",
Size = 1024,
Status = 1
},
new MusicInfoListItemDto
{
Name = "测试歌曲2",
Size = 1024,
Status = 1
},
new MusicInfoListItemDto
{
Name = "测试歌曲3",
Size = 1024,
Status = 1
},
}
};
}
}

View File

@ -1,37 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SuperSocket.WebSocket" Version="2.0.0-beta.11" />
<PackageReference Include="SuperSocket.WebSocket.Server" Version="2.0.0-beta.11" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UiStaticResources\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj" />
</ItemGroup>
<!-- <ItemGroup>-->
<!-- <ProjectReference Include="..\ZonyLrcTools.Cli\ZonyLrcTools.Cli.csproj" />-->
<!-- </ItemGroup>-->
</Project>

View File

@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -1,18 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"serverOptions": {
"name": "ZonyLRcToolsServer",
"listeners": [
{
"ip": "Any",
"port": 50001
}
]
}
}

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

213
src/ui/.gitignore vendored
View File

@ -1,213 +0,0 @@
### VisualStudioCode template
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Vue template
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# TODO: where does this rule come from?
docs/_book
# TODO: where does this rule come from?
test/

View File

@ -1,19 +0,0 @@
# 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/).

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

View File

@ -1,28 +0,0 @@
{
"name": "ui",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^1.1.3",
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vue-router": "^3.5.1",
"vuetify": "^2.6.0",
"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",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"vue-cli-plugin-vuetify": "~2.5.8",
"vue-template-compiler": "^2.6.14",
"vuetify-loader": "^1.7.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<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>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</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>

View File

@ -1,80 +0,0 @@
<template>
<v-app id="inspire">
<v-navigation-drawer
v-model="drawer"
app
>
<v-sheet
color="grey lighten-4"
class="pa-4"
>
<div>ZonyLrcTools-X</div>
</v-sheet>
<v-divider></v-divider>
<v-list>
<v-list-item
v-for="[icon, text, to] in links"
:key="icon"
:to="to"
link
>
<v-list-item-icon>
<v-icon>{{ icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ text }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar app>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>ZonyLrcToolsX</v-toolbar-title>
</v-app-bar>
<v-main>
<v-container
class="py-8 px-6"
fluid
>
<router-view></router-view>
</v-container>
</v-main>
</v-app>
</template>
<script>
import Socket from "@/communication/socket";
import {mapMutations} from "vuex"
export default {
data: () => ({
drawer: null,
links: [
['mdi-play-circle', '开始', '/'],
['mdi-multiplication-box', '设置', '/setting'],
['mdi-information', '关于', '/about'],
],
}),
created() {
Socket.$on("message", this.handleGetMessage);
},
beforeDestroy() {
Socket.$off("message", this.handleGetMessage);
},
methods: {
...mapMutations({
setWsRes: "ws/setWsRes",
}),
handleGetMessage(msg) {
this.setWsRes(JSON.parse(msg));
}
}
}
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

Before

Width:  |  Height:  |  Size: 539 B

View File

@ -1,5 +0,0 @@
import Vue from "vue";
const eventBus = new Vue();
export default eventBus;

View File

@ -1,31 +0,0 @@
import Vue from 'vue'
import eventBus from "@/communication/eventbus";
// const wsUrl = process.env.VUE_APP_WS_URL;
const wsUrl = "ws://127.0.0.1:50001"
let socket = new WebSocket(wsUrl);
const emitter = new Vue({
methods: {
send(message) {
if (socket.readyState === 1) {
socket.send(JSON.stringify(message));
}
}, connect() {
socket = new WebSocket(wsUrl);
socket.onmessage = (msg) => {
let event = JSON.parse(msg.data);
eventBus.$emit(event.action, event);
};
socket.onerror = (err) => {
eventBus.$emit('error', err);
};
socket.onclose = () => {
emitter.connect()
};
}
}
})
emitter.connect();
export default emitter;

View File

@ -1,13 +0,0 @@
<template>
</template>
<script>
export default {
name: "setting"
}
</script>
<style scoped>
</style>

View File

@ -1,14 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
Vue.config.productionTip = false
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')

View File

@ -1,21 +0,0 @@
import Vue from 'vue';
import Vuetify from 'vuetify/lib/framework';
Vue.use(Vuetify);
export default new Vuetify({
theme: {
dark: false,
themes: {
light: {
primary: '#03a9f4',
secondary: '#8bc34a',
accent: '#cddc39',
error: '#f44336',
warning: '#ffc107',
info: '#607d8b',
success: '#4caf50'
}
},
}
});

View File

@ -1,33 +0,0 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
import SettingView from "@/views/SettingView"
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
},
{
path: '/setting',
name: 'setting',
component: SettingView
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router

View File

@ -1,12 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
})

View File

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -1,90 +0,0 @@
<template>
<div class="container">
<!-- Operation Buttons Group -->
<div class="button-group">
<v-btn @click="openDir">
扫描文件
</v-btn>
<input type="file" class="d-none">
<v-btn class="ml-5" disabled>开始下载</v-btn>
</div>
<div class="file-list mt-5">
<v-data-table
:headers="headers"
:items="items"
class="elevation-1"
>
<template v-slot:item.status="{ item }">
<v-icon v-if="item.status === 'success'" color="success">mdi-check</v-icon>
<v-icon v-else-if="item.status === 'error'" color="error">mdi-close</v-icon>
<v-icon v-else>mdi-minus</v-icon>
</template>
</v-data-table>
<div class="output mt-8">
<v-textarea disabled outlined label="日志信息" :value='outputText'/>
</div>
</div>
</div>
</template>
<script>
import Socket from '@/communication/socket'
import eventBus from "@/communication/eventbus";
export default {
name: 'Home',
data() {
return {
headers: [
{
text: '文件名',
align: 'start',
sortable: false,
value: 'name',
},
{text: '大小', value: 'size'},
{text: '状态', value: 'status'},
{text: '操作', value: 'action'},
],
items: [],
outputText: ''
}
},
methods: {
openDir() {
Socket.send({
type: 'openDir',
data: {},
})
},
loadItems() {
this.items = []
axios.get()
}
},
computed: {},
mounted() {
// eventBus.$on('getFile', (msgData) => {
// console.log(msgData);
// });
// eventBus.$on('getFileInfo', (msgData) => {
// this.items = [...this.items, ...msgData.data.map(item => {
// return {
// name: item.name,
// size: item.size,
// status: 'success',
// action: 4.0,
// }
// })]
// });
//
eventBus.$on('output', (msgData) => {
this.outputText = this.outputText.concat(msgData.data.text).concat('\n');
});
}
}
</script>

View File

@ -1,148 +0,0 @@
<template>
<v-form>
<v-list subheader flat two-line>
<v-subheader>网络代理</v-subheader>
<v-list-item>
<v-switch label="启用代理" @change="proxyEnabledChange" v-model="isProxyEnabled"/>
</v-list-item>
<v-list-item>
<v-text-field :disabled="proxyEnabled" v-model="proxyAddress" label="代理地址"
placeholder="请输入代理服务器的地址,例如 127.0.0.1:1080。"/>
</v-list-item>
<v-list-item>
<v-text-field :disabled="proxyEnabled" v-model="updateAddress" label="更新检查地址"
placeholder="请输入更新检查地址,默认情况下无需变更。"/>
</v-list-item>
<v-divider></v-divider>
<v-subheader>全局下载设置</v-subheader>
<v-list-item>
<v-switch label="是否启用翻译歌词"/>
</v-list-item>
<v-list-item>
<v-switch label="仅输出翻译歌词"/>
</v-list-item>
<v-list-item>
<v-switch label="跳过歌词文件已存在的歌曲"/>
</v-list-item>
<v-list-item>
<v-switch label="双语歌词合并展示" v-model="mergeLrc"/>
</v-list-item>
<v-list-item>
<!--文件编码-->
<v-select v-model="lrcEncoding" :items="lrcEncodings" label="歌词文件编码" placeholder="请选择歌词文件编码"/>
</v-list-item>
<v-list-item>
<!--换行符下拉选择-->
<v-row>
<v-col>
<v-select label="换行符" v-model="lineBreak" :items="lineBreakOptions" item-text="label"
item-value="value"/>
</v-col>
</v-row>
</v-list-item>
<v-divider></v-divider>
<v-list-item>
<v-row>
<v-col cols="6">
<v-subheader>歌词下载器设置</v-subheader>
<v-list-item>
<v-data-table hide-default-footer :headers="lyricDownloaderHeaders" :items="lyricDownloader"
class="elevation-1" disable-sort>
<template v-slot:item.enabled="{ item }">
<v-simple-checkbox v-model="item.enabled" color="primary"/>
</template>
</v-data-table>
</v-list-item>
</v-col>
<v-col cols="6">
<v-subheader>标签解析器设置</v-subheader>
<v-list-item>
<v-data-table hide-default-footer :headers="tagParserHeaders" :items="tagParser" class="elevation-1"
disable-sort>
<template v-slot:item.enabled="{ item }">
<v-simple-checkbox v-model="item.enabled" color="primary"></v-simple-checkbox>
</template>
</v-data-table>
</v-list-item>
</v-col>
</v-row>
</v-list-item>
<v-divider class="mt-5"></v-divider>
<v-subheader>屏蔽字典</v-subheader>
<v-list-item>
<v-switch label="启用屏蔽字典" v-model="isBlockDictEnabled" @change="blockDictEnabledChange"/>
</v-list-item>
<v-list-item>
<v-text-field :disabled="blockDictEnabled" v-model="blockDictPath" label="屏蔽字典路径"
placeholder="请选择屏蔽字典的文件路径。"/>
<v-btn :disabled="blockDictEnabled" icon @click="openBlockDict">
<v-icon>mdi-folder</v-icon>
</v-btn>
</v-list-item>
<v-divider></v-divider>
<v-subheader>扫描设置</v-subheader>
<v-list-item>
<v-text-field v-model="fileSearchSuffix" label="文件搜索后缀"
placeholder="请输入文件搜索后缀,多个后缀以英文逗号分隔。"/>
</v-list-item>
</v-list>
</v-form>
</template>
<script>
export default {
name: "Setting",
data: () => {
return {
isProxyEnabled: true,
proxyEnabled: false,
isBlockDictEnabled: true,
blockDictEnabled: false,
blockDictPath: "",
proxyAddress: "",
updateAddress: "",
mergeLrc: false,
lineBreak: "lf",
lineBreakOptions: [
{label: "LF", value: "lf"},
{label: "CRLF", value: "crlf"},
],
lrcEncoding: "utf-8",
lyricDownloaderHeaders: [
{text: "启用", value: "enabled"},
{text: "歌词源", value: "name"},
{text: "搜索深度", value: "searchDepth"},
{text: "优先级", value: "priority"},
],
lyricDownloader: [
{enabled: true, name: "网易云音乐", searchDepth: 10, priority: 1},
],
tagParserHeaders: [
{text: "启用", value: "enabled", sortable: false, width: "10%"},
{text: "标签解析器", value: "name", sortable: false, width: "90%"},
],
tagParser: [
{name: '正则表达式解析器', enabled: true},
],
}
},
computed: {},
methods: {
proxyEnabledChange() {
this.proxyEnabled = !this.proxyEnabled;
},
blockDictEnabledChange() {
this.blockDictEnabled = !this.blockDictEnabled;
}
},
};
</script>

View File

@ -1,6 +0,0 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: [
'vuetify'
]
})

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,14 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
using ZonyLrcTools.Common.Album;
namespace ZonyLrcTools.Tests.Infrastructure.Album
{
public class QQMusicAlbumDownloaderTests : TestBase
{
[Fact]
public async Task DownloadDataAsync_Test()
{
var downloader = ServiceProvider.GetRequiredService<IEnumerable<IAlbumProvider>>()

View File

@ -33,7 +33,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
await Should.ThrowAsync<ErrorCodeException>(_lyricsProvider.DownloadAsync("天ノ弱", "漆柚").AsTask);
}
[Fact]
[Fact(Skip = "This music already exists KuGou's database.")]
public async Task DownloadAsync_Index_Exception_Test()
{
await Should.ThrowAsync<ErrorCodeException>(async () => await _lyricsProvider.DownloadAsync("40'z", "ZOOLY"));

View File

@ -113,17 +113,11 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
lyric.IsPruneMusic.ShouldBeTrue();
}
[Fact]
public async Task DownloadAsync_NullException_Test()
{
var result = await Should.ThrowAsync<ErrorCodeException>(_lyricsProvider.DownloadAsync("創世記", "りりィ").AsTask);
result.ErrorCode.ShouldBe(ErrorCodes.NoMatchingSong);
}
[Fact]
public async Task DownloadAsync_Source_Null_Test()
{
var lyric = await _lyricsProvider.DownloadAsync("Concerto for Piano and Orchestra No. 12 in A major, K414 - 1. Allegro",
var lyric = await _lyricsProvider.DownloadAsync(
"Concerto for Piano and Orchestra No. 12 in A major, K414 - 1. Allegro",
"Wolfgang Amadeus Mozart");
lyric.IsPruneMusic.ShouldBeTrue();
@ -150,5 +144,12 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyrics
var lyric = await _lyricsProvider.DownloadAsync("Your Heart Is A Muscle (2012)", "Carly Rae Jepsen");
lyric.IsPruneMusic.ShouldBeFalse();
}
[Fact]
public async Task DownloadAsync_Issue_156_Test()
{
var lyric = await _lyricsProvider.DownloadAsync("你说demo", "枯木逢春");
lyric.IsPruneMusic.ShouldBeFalse();
}
}
}

View File

@ -5,6 +5,7 @@ using Shouldly;
using Xunit;
using ZonyLrcTools.Common.Configuration;
using ZonyLrcTools.Common.Infrastructure.Network;
using ZonyLrcTools.Common.Updater;
namespace ZonyLrcTools.Tests.Infrastructure.Network
{
@ -25,9 +26,9 @@ namespace ZonyLrcTools.Tests.Infrastructure.Network
{
var client = ServiceProvider.GetRequiredService<IWarpHttpClient>();
var response = await client.GetAsync(@"https://www.baidu.com");
var response = await client.GetAsync(DefaultUpdater.UpdateUrl);
response.ShouldNotBeNull();
response.ShouldContain("百度");
response.ShouldContain("NewVersion");
}
[Fact]
@ -39,10 +40,10 @@ namespace ZonyLrcTools.Tests.Infrastructure.Network
var client = ServiceProvider.GetRequiredService<IWarpHttpClient>();
var response = await client.GetAsync(@"https://www.baidu.com");
var response = await client.GetAsync(DefaultUpdater.UpdateUrl);
response.ShouldNotBeNull();
response.ShouldContain("百度");
response.ShouldContain("NewVersion");
}
}
}

View File

@ -1,4 +1,5 @@
using Shouldly;
using System.IO;
using Shouldly;
using Xunit;
using ZonyLrcTools.Common;
@ -9,7 +10,10 @@ public class MusicInfoTests
[Fact]
public void InvalidFilePathTest()
{
var musicInfo = new MusicInfo("C:\\Users\\Zony\\Music\\[ZonyLrcTools]:? - 01. 你好.mp3", "你好", "Zony");
musicInfo.FilePath.ShouldBe(@"C:\Users\Zony\Music\[ZonyLrcTools] - 01. 你好.mp3");
var tempFilePath = Path.GetTempFileName();
var errorFilePath = $"{tempFilePath}" + "?:";
var musicInfo = new MusicInfo(errorFilePath, "你好", "Zony");
musicInfo.FilePath.ShouldBe(tempFilePath);
}
}

View File

@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="Shouldly"/>
<PackageReference Include="xunit"/>
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -1,8 +0,0 @@
New Features: NONE
Breaking Changes: None
Enhancement:
- 支持 UTF-8 BOM 编码,以解决部分设备乱码的问题。
Fixed Bugs:
- None

View File

@ -1,13 +1,7 @@
New Features: None
New Features: None
Breaking Changes: None
Enhancement: :
- 网易云歌单下载歌词时,支持使用 `;` 传递多个歌单 ID。(`15445646;468435123;4131357`) - [`41cba02`](https://github.com/real-zony/ZonyLrcToolsX/commit/41cba028333d0cf65f7dc3ee466660cc20e558fc)
- 优化了结果提示信息,更加明确。- [`1e5c418`](https://github.com/real-zony/ZonyLrcToolsX/commit/1e5c41852f70df4b824d2795f175e57c82a0d104)
- 优化了程序的启动时间,不会每次启动都去检测更新了。- [`b240564`](https://github.com/real-zony/ZonyLrcToolsX/commit/b240564cf7ac3432715dd54c280d73f9f985c4fc)
Enhancement: None
Fixed Bugs:
- 更新了依赖的 NuGet 程序包版本。- [`1f312c7`](https://github.com/real-zony/ZonyLrcToolsX/commit/1f312c749d8e7784e7670ec3821db0b4b430ce2e)
- 过滤了包含特殊符号的文件路径,避免报错。 - [`62d08df`](https://github.com/real-zony/ZonyLrcToolsX/commit/62d08df7353343711741b23a6ecb9c3908754075)
- 修复了从网易云歌单下载歌曲时,将专辑名称当作歌手名称进行取值。 - [`98c935e`](https://github.com/real-zony/ZonyLrcToolsX/commit/98c935ed93f9278e8606c056868824ea8335ee5d)
- 修复了 #156 的问题。