mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-07-01 20:30:41 +00:00
Compare commits
72 Commits
ZonyLrcToo
...
dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e08d1c7f16 | ||
![]() |
c3d98d60b5 | ||
![]() |
8aac9e16a6 | ||
![]() |
21f25ee500 | ||
![]() |
9a979942e3 | ||
![]() |
32b6c4052b | ||
![]() |
7732ab52d3 | ||
![]() |
1fdbaef7a8 | ||
![]() |
36ef2d2c3a | ||
![]() |
fa4620c37d | ||
![]() |
9ab9cd50e2 | ||
![]() |
e66ef89e7a | ||
![]() |
8b5d5c64b0 | ||
![]() |
2dca5239f5 | ||
![]() |
d5689164b5 | ||
![]() |
b57739f543 | ||
![]() |
a2c44901fd | ||
![]() |
6ad2992144 | ||
![]() |
3b0c55ac00 | ||
![]() |
81bf6ebe3f | ||
![]() |
aa3c45101b | ||
![]() |
d00d735bf0 | ||
![]() |
7fa595a71b | ||
![]() |
4f15d06e63 | ||
![]() |
5ccd8a7c53 | ||
![]() |
14812bceb7 | ||
![]() |
be538eb650 | ||
![]() |
888c7f511b | ||
![]() |
7bd1915f11 | ||
![]() |
aa78ea0713 | ||
![]() |
1342ca936c | ||
![]() |
46ac2d57a2 | ||
![]() |
444f4e75a6 | ||
![]() |
61aa31cc6f | ||
![]() |
46d11a86ea | ||
![]() |
8aaae90ed8 | ||
![]() |
d91cc68a60 | ||
![]() |
b6e3cb6038 | ||
![]() |
093e2879ad | ||
![]() |
fea5e1124f | ||
![]() |
2ce7b00d14 | ||
![]() |
ad6ea732f8 | ||
![]() |
06bf733ae9 | ||
![]() |
558cca3c0e | ||
![]() |
93cc31b1e2 | ||
![]() |
dfe18d8541 | ||
![]() |
40b73c3dfa | ||
![]() |
243a0e2559 | ||
![]() |
d83bff1516 | ||
![]() |
8d946ebbd5 | ||
![]() |
4c3d554885 | ||
![]() |
14c78f9a83 | ||
![]() |
b9d6da5f72 | ||
![]() |
c8ffbc55af | ||
![]() |
b0835dcf01 | ||
![]() |
4e1c4d1519 | ||
![]() |
3d96b96edf | ||
![]() |
7f81a3edea | ||
![]() |
24624a6d21 | ||
![]() |
d860632327 | ||
![]() |
1186369d16 | ||
![]() |
b240564cf7 | ||
![]() |
6e2848a9de | ||
![]() |
6361bf9265 | ||
![]() |
383e2c5939 | ||
![]() |
1e5c41852f | ||
![]() |
41cba02833 | ||
![]() |
98c935ed93 | ||
![]() |
62d08df735 | ||
![]() |
1f312c749d | ||
![]() |
30e10f4c01 | ||
![]() |
35468cfb71 |
2
.github/workflows/dotnet.yml
vendored
2
.github/workflows/dotnet.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: 7.0.x
|
dotnet-version: 8.0.x
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
11
Directory.Build.props
Normal file
11
Directory.Build.props
Normal 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
45
Directory.Packages.props
Normal 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>
|
216
README.md
216
README.md
@ -1,213 +1,39 @@
|
|||||||
简体中文 | [English](./docs/en_US.md)
|
English | [简体中文](./zh_CN.md)
|
||||||
|
|
||||||
# 免责声明
|
# Disclaimer
|
||||||
|
|
||||||
- 本工具仅作个人学习研究使用,可运行的二进制文件仅用于演示功能,不得将源码及其产物用于商业用途,否则由此造成的相关法律问题,[本人](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.
|
||||||
- 任何单位或个人因下载使用软件所产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),[本人](https://github.com/real-zony) 不承担任何法律责任。
|
- 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.
|
||||||
- 用户明确并同意本声明条款列举的全部内容,对使用本工具可能存在的风险和相关后果将完全由用户自行承担,[本人](https://github.com/real-zony) 不承担任何法律责任。
|
- 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 的跨平台歌词下载工具。
|
ZonyLrcToolX 4 is a cross-platform lyrics download tool based on CEF. **QQ Group: 337656932**. Detailed video tutorials are available in the group files.
|
||||||
|
|
||||||
🚧 当前版本正在开发当中。
|
🚧 The current version is under development.
|
||||||
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
|
🚧 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
|
||||||
|
|
||||||
## 子命令
|
ZonyLrcTools.Cli --help
|
||||||
|
|
||||||
### 下载功能
|
|
||||||
|
|
||||||
子命令为 `download`,可用于下载歌词数据和专辑图像,支持多个下载器进行下载。
|
|
||||||
|
|
||||||
```shell
|
|
||||||
.\ZonyLrcTools.Cli.exe download -d|dir <WAIT_SCAN_DIRECTORY> [-l|--lyric] [-a|--album] [-n|--number]
|
|
||||||
|
|
||||||
.\ZonyLrcTools.Cli.exe download -h|--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
|
# Donation
|
||||||
# 下载歌词
|
[爱发电](https://afdian.net/a/zony-lrc-tools)
|
||||||
.\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。
|
|
||||||
|
|
||||||
由于网易云音乐的限制,要想获取完整的歌单信息,必须扫码登录程序,还是以最上面的为例,我需要下载歌单内的歌词数据,就必须扫码之后程序才会执行。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
**从 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"/>
|
|
||||||
|
|
||||||
# Star History
|
# Star History
|
||||||
|
|
||||||
[](https://star-history.com/#real-zony/ZonyLrcToolsX&Timeline)
|
[](https://star-history.com/#real-zony/ZonyLrcToolsX&Timeline)
|
||||||
|
|
||||||
# 路线图
|
|
||||||
|
|
||||||
- [x] 支持跨平台的 CLI 工具。
|
|
||||||
- [x] 基于 Web GUI 的操作站点。
|
|
||||||
- [ ] 支持插件系统(Lua 引擎)。
|
|
||||||
|
@ -11,8 +11,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Cli", "src\Zon
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Tests", "tests\ZonyLrcTools.Tests\ZonyLrcTools.Tests.csproj", "{FFBD3200-568F-455B-8390-5E76C51D522C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Tests", "tests\ZonyLrcTools.Tests\ZonyLrcTools.Tests.csproj", "{FFBD3200-568F-455B-8390-5E76C51D522C}"
|
||||||
EndProject
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Common", "src\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj", "{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
@ -29,10 +27,6 @@ Global
|
|||||||
{FFBD3200-568F-455B-8390-5E76C51D522C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{FFBD3200-568F-455B-8390-5E76C51D522C}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}.Debug|Any CPU.Build.0 = 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
|
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@ -47,7 +41,6 @@ Global
|
|||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{55D74323-ABFA-4A73-A3BF-F3E8D5DE6DE8} = {C29FB05C-54B1-4020-94D2-87E192EB2F98}
|
{55D74323-ABFA-4A73-A3BF-F3E8D5DE6DE8} = {C29FB05C-54B1-4020-94D2-87E192EB2F98}
|
||||||
{FFBD3200-568F-455B-8390-5E76C51D522C} = {AF8ADB2F-E46C-4DEE-8316-652D9FE1A69B}
|
{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}
|
{9B42E4CA-61AA-4798-8D2B-2D8A7035EB67} = {C29FB05C-54B1-4020-94D2-87E192EB2F98}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -1,18 +1,39 @@
|
|||||||
English | [简体中文](./zh_CN.md)
|
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.
|
# Introduction
|
||||||
🚧 If you want to see the working code, please switch to the 1.0 branch.
|
|
||||||
|
|
||||||
## 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.
|
To get the latest version, please visit the **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** page for download.
|
||||||
- [ ] Web GUI based site (local).
|
|
||||||
- [ ] Support plug-in system (Lua Engine).
|
## 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
|
||||||
|
|
||||||
|
[](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
40
docs/zh_CN.md
Normal 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
|
||||||
|
|
||||||
|
[](https://star-history.com/#real-zony/ZonyLrcToolsX&Timeline)
|
@ -46,7 +46,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
|||||||
[Option("-a|--album", CommandOptionType.NoValue, Description = "指定程序需要下载专辑图像。")]
|
[Option("-a|--album", CommandOptionType.NoValue, Description = "指定程序需要下载专辑图像。")]
|
||||||
public bool DownloadAlbum { get; set; }
|
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;
|
public int ParallelNumber { get; set; } = 1;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -65,7 +65,7 @@ namespace ZonyLrcTools.Cli.Commands.SubCommand
|
|||||||
[Option("-f|--file", Description = "指定 CSV 文件的路径。")]
|
[Option("-f|--file", Description = "指定 CSV 文件的路径。")]
|
||||||
public string CsvFilePath { get; set; }
|
public string CsvFilePath { get; set; }
|
||||||
|
|
||||||
[Option("-s|--song-list-id", Description = "指定网易云音乐歌单的 ID。")]
|
[Option("-s|--song-list-id", Description = "指定网易云音乐歌单的 ID,如果有多个歌单,请使用 ';' 分割 ID。")]
|
||||||
public string SongListId { get; set; }
|
public string SongListId { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using McMaster.Extensions.CommandLineUtils;
|
using McMaster.Extensions.CommandLineUtils;
|
||||||
@ -79,11 +78,10 @@ namespace ZonyLrcTools.Cli
|
|||||||
private static Task<int> BuildHostedService(string[] args)
|
private static Task<int> BuildHostedService(string[] args)
|
||||||
{
|
{
|
||||||
return new HostBuilder()
|
return new HostBuilder()
|
||||||
.ConfigureLogging(builder => builder.AddSerilog())
|
.ConfigureLogging(l => l.AddSerilog())
|
||||||
.ConfigureHostConfiguration(builder =>
|
.ConfigureHostConfiguration(builder =>
|
||||||
{
|
{
|
||||||
builder
|
builder.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
|
||||||
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
|
|
||||||
.AddYamlFile("config.yaml");
|
.AddYamlFile("config.yaml");
|
||||||
})
|
})
|
||||||
.ConfigureServices((_, services) =>
|
.ConfigureServices((_, services) =>
|
||||||
|
@ -1,46 +1,40 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.2" />
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils"/>
|
||||||
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.2" />
|
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
<PackageReference Include="Serilog.Extensions.Hosting"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
<PackageReference Include="Serilog.Sinks.Async"/>
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="Serilog.Sinks.Console"/>
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
<PackageReference Include="Serilog.Sinks.File"/>
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages"/>
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageReference Include="Ude.NetStandard"/>
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="appsettings.json" />
|
<None Remove="appsettings.json"/>
|
||||||
<None Remove="Resources\error_msg.json" />
|
<None Remove="Resources\error_msg.json"/>
|
||||||
<Content Include="Resources\error_msg.json">
|
<Content Include="Resources\error_msg.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<None Remove="BlockWords.json" />
|
<None Remove="BlockWords.json"/>
|
||||||
<Content Include="BlockWords.json">
|
<Content Include="BlockWords.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<None Remove="config.yaml" />
|
<None Remove="config.yaml"/>
|
||||||
<Content Include="config.yaml">
|
<Content Include="config.yaml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj" />
|
<ProjectReference Include="..\ZonyLrcTools.Common\ZonyLrcTools.Common.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,58 +1,61 @@
|
|||||||
globalOption:
|
# 允许扫描的歌曲文件后缀名。
|
||||||
# 允许扫描的歌曲文件后缀名。
|
supportFileExtensions:
|
||||||
supportFileExtensions:
|
- '*.mp3'
|
||||||
- '*.mp3'
|
- '*.flac'
|
||||||
- '*.flac'
|
- '*.wav'
|
||||||
- '*.wav'
|
- '*.m4a'
|
||||||
# 网络代理服务设置,仅支持 HTTP 代理。
|
- '*.ogg'
|
||||||
networkOptions:
|
- '*.opus'
|
||||||
isEnable: false # 是否启用代理。
|
|
||||||
ip: 127.0.0.1 # 代理服务 IP 地址。
|
# 网络代理服务设置,仅支持 HTTP 代理。
|
||||||
port: 4780 # 代理服务端口号。
|
networkOptions:
|
||||||
updateUrl: https://api.myzony.com/lrc-tools/update # 更新检查地址。
|
isEnable: false # 是否启用代理。
|
||||||
|
ip: 127.0.0.1 # 代理服务 IP 地址。
|
||||||
# 下载器的相关参数配置。
|
port: 4780 # 代理服务端口号。
|
||||||
provider:
|
updateUrl: https://api.myzony.com/lrc-tools/update # 更新检查地址。
|
||||||
# 标签扫描器的相关参数配置。
|
|
||||||
tag:
|
# 下载器的相关参数配置。
|
||||||
# 支持的标签扫描器。
|
provider:
|
||||||
plugin:
|
# 标签扫描器的相关参数配置。
|
||||||
- name: Taglib # 基于 Taglib 库的标签扫描器。
|
tag:
|
||||||
priority: 1 # 优先级,升序排列。
|
# 支持的标签扫描器。
|
||||||
- name: FileName # 基于文件名的标签扫描器。
|
plugin:
|
||||||
priority: 2
|
- name: Taglib # 基于 Taglib 库的标签扫描器。
|
||||||
# 基于文件名扫描器的扩展参数。
|
priority: 1 # 优先级,升序排列。
|
||||||
extensions:
|
- name: FileName # 基于文件名的标签扫描器。
|
||||||
# 正则表达式,用于匹配文件名中的作者信息和歌曲信息,可根据
|
priority: 2
|
||||||
# 自己的需求进行调整。
|
# 基于文件名扫描器的扩展参数。
|
||||||
regularExpressions: "(?'artist'.+)\\s-\\s(?'name'.+)"
|
extensions:
|
||||||
# 歌曲标签屏蔽字典替换功能。
|
# 正则表达式,用于匹配文件名中的作者信息和歌曲信息,可根据
|
||||||
blockWord:
|
# 自己的需求进行调整。
|
||||||
isEnable: false # 是否启用屏蔽字典。
|
regularExpressions: "(?'artist'.+)\\s-\\s(?'name'.+)"
|
||||||
filePath: 'BlockWords.json' # 屏蔽字典的路径。
|
# 歌曲标签屏蔽字典替换功能。
|
||||||
# 歌词下载器的相关参数配置。
|
blockWord:
|
||||||
lyric:
|
isEnable: false # 是否启用屏蔽字典。
|
||||||
# 支持的歌词下载器。
|
filePath: 'BlockWords.json' # 屏蔽字典的路径。
|
||||||
plugin:
|
# 歌词下载器的相关参数配置。
|
||||||
- name: NetEase # 基于网易云音乐的歌词下载器。
|
lyric:
|
||||||
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
|
# 支持的歌词下载器。
|
||||||
depth: 10 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
|
plugin:
|
||||||
additional:
|
- name: NetEase # 基于网易云音乐的歌词下载器。
|
||||||
isEnableRomanOutput: false # 是否启用罗马音输出,本参数仅当对应歌曲有罗马音歌词时有效。
|
priority: 1 # 优先级,升序排列,改为 -1 时禁用。
|
||||||
- name: QQ # 基于 QQ 音乐的歌词下载器。
|
depth: 10 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
|
||||||
priority: 2
|
additional:
|
||||||
# depth: 10 # 暂时不支持。
|
isEnableRomanOutput: false # 是否启用罗马音输出,本参数仅当对应歌曲有罗马音歌词时有效。
|
||||||
- name: KuGou # 基于酷狗音乐的歌词下载器。
|
- name: QQ # 基于 QQ 音乐的歌词下载器。
|
||||||
priority: 3
|
priority: 2
|
||||||
depth: 10
|
# depth: 10 # 暂时不支持。
|
||||||
- name: KuWo # 基于酷我音乐的歌词下载器。
|
- name: KuGou # 基于酷狗音乐的歌词下载器。
|
||||||
priority: 4
|
priority: 3
|
||||||
depth: 10
|
depth: 10
|
||||||
# 歌词下载的一些共有配置参数。
|
- name: KuWo # 基于酷我音乐的歌词下载器。
|
||||||
config:
|
priority: 4
|
||||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
depth: 10
|
||||||
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
# 歌词下载的一些共有配置参数。
|
||||||
isEnableTranslation: true # 是否启用翻译歌词。
|
config:
|
||||||
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
|
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||||
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
|
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
||||||
fileEncoding: 'utf-8' # 歌词文件的编码格式。
|
isEnableTranslation: true # 是否启用翻译歌词。
|
||||||
|
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
|
||||||
|
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
|
||||||
|
fileEncoding: 'utf-8' # 歌词文件的编码格式。
|
@ -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)) {
|
if (-not (Test-Path ./TempFiles)) {
|
||||||
New-Item -ItemType Directory -Path ./TempFiles | Out-Null
|
New-Item -ItemType Directory -Path ./TempFiles | Out-Null
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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' ];
|
if ! [ -d './TempFiles' ];
|
||||||
then
|
then
|
||||||
@ -12,9 +12,9 @@ for platform in "${Platforms[@]}"
|
|||||||
do
|
do
|
||||||
dotnet publish -r "$platform" -c Release -p:PublishSingleFile=true -p:DebugType=none --self-contained true || exit 1
|
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
|
zip -r ./ZonyLrcTools_"$platform"_"${PUBLISH_VERSION}".zip ./ || exit 1
|
||||||
cd ../../../../../ || 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
|
done
|
@ -13,6 +13,6 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。
|
/// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,16 +5,16 @@ namespace ZonyLrcTools.Common.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 支持的音乐文件后缀集合。
|
/// 支持的音乐文件后缀集合。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> SupportFileExtensions { get; set; }
|
public List<string> SupportFileExtensions { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 网络代理相关的配置信息。
|
/// 网络代理相关的配置信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NetworkOptions NetworkOptions { get; set; }
|
public NetworkOptions NetworkOptions { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 定义下载器的相关配置信息。
|
/// 定义下载器的相关配置信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ProviderOptions Provider { get; set; }
|
public ProviderOptions Provider { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,12 +2,12 @@ namespace ZonyLrcTools.Common.Configuration;
|
|||||||
|
|
||||||
public class LyricsOptions
|
public class LyricsOptions
|
||||||
{
|
{
|
||||||
public IEnumerable<LyricsProviderOptions> Plugin { get; set; }
|
public IEnumerable<LyricsProviderOptions> Plugin { get; set; } = null!;
|
||||||
|
|
||||||
public GlobalLyricsConfigOptions Config { get; set; }
|
public GlobalLyricsConfigOptions Config { get; set; } = null!;
|
||||||
|
|
||||||
public LyricsProviderOptions GetLyricProviderOption(string name)
|
public LyricsProviderOptions GetLyricProviderOption(string name)
|
||||||
{
|
{
|
||||||
return Plugin.FirstOrDefault(x => x.Name == name);
|
return Plugin.FirstOrDefault(x => x.Name == name)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词下载器的唯一标识。
|
/// 歌词下载器的唯一标识。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词下载时的优先级,当值为 -1 时是禁用。
|
/// 歌词下载时的优先级,当值为 -1 时是禁用。
|
||||||
@ -16,5 +16,10 @@
|
|||||||
/// 搜索深度,值越大搜索结果越多,但搜索时间越长。
|
/// 搜索深度,值越大搜索结果越多,但搜索时间越长。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Depth { get; set; }
|
public int Depth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 歌词下载器的扩展属性。
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string>? Additional { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,11 +13,16 @@ namespace ZonyLrcTools.Common.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 代理服务器的 Ip。
|
/// 代理服务器的 Ip。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Ip { get; set; }
|
public string Ip { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 代理服务器的 端口。
|
/// 代理服务器的 端口。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新检查的 Url。
|
||||||
|
/// </summary>
|
||||||
|
public string UpdateUrl { get; set; } = default!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,10 +5,10 @@ public class ProviderOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 标签加载器相关的配置属性。
|
/// 标签加载器相关的配置属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TagInfoOptions Tag { get; set; }
|
public TagInfoOptions Tag { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词下载相关的配置信息。
|
/// 歌词下载相关的配置信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LyricsOptions Lyric { get; set; }
|
public LyricsOptions Lyric { get; set; } = null!;
|
||||||
}
|
}
|
@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
public class TagInfoOptions
|
public class TagInfoOptions
|
||||||
{
|
{
|
||||||
public IEnumerable<TagInfoProviderOptions> Plugin { get; set; }
|
public IEnumerable<TagInfoProviderOptions> Plugin { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 屏蔽词功能相关配置。
|
/// 屏蔽词功能相关配置。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BlockWordOptions BlockWord { get; set; }
|
public BlockWordOptions BlockWord { get; set; } = null!;
|
||||||
|
|
||||||
|
public TagInfoProviderOptions GetTagProviderOption(string name)
|
||||||
|
{
|
||||||
|
return Plugin.FirstOrDefault(x => x.Name == name)!;
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,10 +2,10 @@ namespace ZonyLrcTools.Common.Configuration
|
|||||||
{
|
{
|
||||||
public class TagInfoProviderOptions
|
public class TagInfoProviderOptions
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Extensions { get; set; }
|
public Dictionary<string, string>? Extensions { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置工具会用到的服务。
|
/// 配置工具会用到的服务。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection ConfigureToolService(this IServiceCollection services)
|
public static IServiceCollection? ConfigureToolService(this IServiceCollection? services)
|
||||||
{
|
{
|
||||||
if (services == null)
|
if (services == null)
|
||||||
{
|
{
|
||||||
@ -47,7 +47,7 @@ namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
|||||||
.AddYamlFile("config.yaml")
|
.AddYamlFile("config.yaml")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
services.Configure<GlobalOptions>(configuration.GetSection("globalOption"));
|
services.Configure<GlobalOptions>(configuration);
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
|||||||
{
|
{
|
||||||
public int ErrorCode { get; }
|
public int ErrorCode { get; }
|
||||||
|
|
||||||
public object AttachObject { get; }
|
public object? AttachObject { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建一个新的 <see cref="ErrorCodeException"/> 对象。
|
/// 构建一个新的 <see cref="ErrorCodeException"/> 对象。
|
||||||
@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
|||||||
/// <param name="errorCode">错误码,参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
/// <param name="errorCode">错误码,参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
||||||
/// <param name="message">错误信息。</param>
|
/// <param name="message">错误信息。</param>
|
||||||
/// <param name="attachObj">附加的对象数据。</param>
|
/// <param name="attachObj">附加的对象数据。</param>
|
||||||
public ErrorCodeException(int errorCode, string? message = null, object attachObj = null) : base(message)
|
public ErrorCodeException(int errorCode, string? message = null, object? attachObj = null) : base(message)
|
||||||
{
|
{
|
||||||
ErrorCode = errorCode;
|
ErrorCode = errorCode;
|
||||||
AttachObject = attachObj;
|
AttachObject = attachObj;
|
||||||
|
@ -33,7 +33,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
|||||||
var errors = jsonObj.SelectTokens("$.Error.*");
|
var errors = jsonObj.SelectTokens("$.Error.*");
|
||||||
var warnings = jsonObj.SelectTokens("$.Warning.*");
|
var warnings = jsonObj.SelectTokens("$.Warning.*");
|
||||||
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
|
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
|
||||||
.ForEach(m => ErrorMessages.Add(int.Parse(m.Name), m.Value.Value<string>()));
|
.ForEach(m => ErrorMessages.Add(int.Parse(m.Name), m.Value.Value<string>() ?? string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetMessage(int errorCode) => ErrorMessages[errorCode];
|
public static string GetMessage(int errorCode) => ErrorMessages[errorCode];
|
||||||
|
@ -17,7 +17,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
|
|||||||
/// <param name="logger">日志记录器实例。</param>
|
/// <param name="logger">日志记录器实例。</param>
|
||||||
/// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
/// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
||||||
/// <param name="e">异常实例,可为空。</param>
|
/// <param name="e">异常实例,可为空。</param>
|
||||||
public static void LogWarningWithErrorCode(this IWarpLogger logger, int errorCode, Exception e = null)
|
public static void LogWarningWithErrorCode(this IWarpLogger logger, int errorCode, Exception? e = null)
|
||||||
{
|
{
|
||||||
logger.WarnAsync($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}").GetAwaiter().GetResult();
|
logger.WarnAsync($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}").GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,9 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<string> PostAsync(string url,
|
public async ValueTask<string> PostAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
using var responseMessage = await PostReturnHttpResponseAsync(url, parameters, isQueryStringParam, requestOption);
|
using var responseMessage = await PostReturnHttpResponseAsync(url, parameters, isQueryStringParam, requestOption);
|
||||||
var responseContentString = await responseMessage.Content.ReadAsStringAsync();
|
var responseContentString = await responseMessage.Content.ReadAsStringAsync();
|
||||||
@ -30,18 +30,18 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<TResponse> PostAsync<TResponse>(string url,
|
public async ValueTask<TResponse> PostAsync<TResponse>(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var responseString = await PostAsync(url, parameters, isQueryStringParam, requestOption);
|
var responseString = await PostAsync(url, parameters, isQueryStringParam, requestOption);
|
||||||
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
public async ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var parametersStr = isQueryStringParam ? BuildQueryString(parameters) : BuildJsonBodyString(parameters);
|
var parametersStr = isQueryStringParam ? BuildQueryString(parameters) : BuildJsonBodyString(parameters);
|
||||||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
||||||
@ -53,8 +53,8 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<string> GetAsync(string url,
|
public async ValueTask<string> GetAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var requestParamsStr = BuildQueryString(parameters);
|
var requestParamsStr = BuildQueryString(parameters);
|
||||||
var requestMsg = new HttpRequestMessage(HttpMethod.Get, new Uri($"{url}?{requestParamsStr}"));
|
var requestMsg = new HttpRequestMessage(HttpMethod.Get, new Uri($"{url}?{requestParamsStr}"));
|
||||||
@ -67,8 +67,8 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<TResponse> GetAsync<TResponse>(string url,
|
public async ValueTask<TResponse> GetAsync<TResponse>(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null)
|
Action<HttpRequestMessage>? requestOption = null)
|
||||||
{
|
{
|
||||||
var responseString = await GetAsync(url, parameters, requestOption);
|
var responseString = await GetAsync(url, parameters, requestOption);
|
||||||
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
||||||
@ -79,7 +79,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
return _httpClientFactory.CreateClient(HttpClientNameConstant);
|
return _httpClientFactory.CreateClient(HttpClientNameConstant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildQueryString(object parameters)
|
private string BuildQueryString(object? parameters)
|
||||||
{
|
{
|
||||||
if (parameters == null)
|
if (parameters == null)
|
||||||
{
|
{
|
||||||
@ -89,7 +89,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
var type = parameters.GetType();
|
var type = parameters.GetType();
|
||||||
if (type == typeof(string))
|
if (type == typeof(string))
|
||||||
{
|
{
|
||||||
return parameters as string;
|
return parameters as string ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var properties = type.GetProperties();
|
var properties = type.GetProperties();
|
||||||
@ -106,7 +106,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
return paramBuilder.ToString().TrimEnd('&');
|
return paramBuilder.ToString().TrimEnd('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildJsonBodyString(object parameters)
|
private string BuildJsonBodyString(object? parameters)
|
||||||
{
|
{
|
||||||
if (parameters == null) return string.Empty;
|
if (parameters == null) return string.Empty;
|
||||||
if (parameters is string result) return result;
|
if (parameters is string result) return result;
|
||||||
@ -122,7 +122,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
||||||
/// <returns>如果响应正常,则返回具体的响应内容。</returns>
|
/// <returns>如果响应正常,则返回具体的响应内容。</returns>
|
||||||
/// <exception cref="ErrorCodeException">如果 Http 响应不正常,则可能抛出本异常。</exception>
|
/// <exception cref="ErrorCodeException">如果 Http 响应不正常,则可能抛出本异常。</exception>
|
||||||
private string ValidateHttpResponse(HttpResponseMessage responseMessage, object requestParameters, string responseString)
|
private string ValidateHttpResponse(HttpResponseMessage responseMessage, object? requestParameters, string responseString)
|
||||||
{
|
{
|
||||||
return responseMessage.StatusCode switch
|
return responseMessage.StatusCode switch
|
||||||
{
|
{
|
||||||
@ -139,7 +139,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Network
|
|||||||
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
||||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||||
/// <exception cref="ErrorCodeException">如果反序列化失败,则可能抛出本异常。</exception>
|
/// <exception cref="ErrorCodeException">如果反序列化失败,则可能抛出本异常。</exception>
|
||||||
private TResponse ConvertHttpResponseToObject<TResponse>(object requestParameters, string responseString)
|
private TResponse ConvertHttpResponseToObject<TResponse>(object? requestParameters, string responseString)
|
||||||
{
|
{
|
||||||
var throwException = new ErrorCodeException(ErrorCodes.HttpResponseConvertJsonFailed, attachObj: new { requestParameters, responseString });
|
var throwException = new ErrorCodeException(ErrorCodes.HttpResponseConvertJsonFailed, attachObj: new { requestParameters, responseString });
|
||||||
|
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
/// <param name="requestOption">请求时的配置动作。</param>
|
/// <param name="requestOption">请求时的配置动作。</param>
|
||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<string> PostAsync(string url,
|
ValueTask<string> PostAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据指定的配置执行 POST 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
/// 根据指定的配置执行 POST 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
||||||
@ -28,14 +28,14 @@
|
|||||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<TResponse> PostAsync<TResponse>(string url,
|
ValueTask<TResponse> PostAsync<TResponse>(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
ValueTask<HttpResponseMessage> PostReturnHttpResponseAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
bool isQueryStringParam = false,
|
bool isQueryStringParam = false,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据指定的配置执行 GET 请求,并以 <see cref="string"/> 作为返回值。
|
/// 根据指定的配置执行 GET 请求,并以 <see cref="string"/> 作为返回值。
|
||||||
@ -45,8 +45,8 @@
|
|||||||
/// <param name="requestOption">请求时的配置动作。</param>
|
/// <param name="requestOption">请求时的配置动作。</param>
|
||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<string> GetAsync(string url,
|
ValueTask<string> GetAsync(string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据指定的配置执行 GET 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
/// 根据指定的配置执行 GET 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
||||||
@ -58,7 +58,7 @@
|
|||||||
/// <returns>服务端的响应结果。</returns>
|
/// <returns>服务端的响应结果。</returns>
|
||||||
ValueTask<TResponse> GetAsync<TResponse>(
|
ValueTask<TResponse> GetAsync<TResponse>(
|
||||||
string url,
|
string url,
|
||||||
object parameters = null,
|
object? parameters = null,
|
||||||
Action<HttpRequestMessage> requestOption = null);
|
Action<HttpRequestMessage>? requestOption = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,24 @@
|
|||||||
namespace ZonyLrcTools.Common.Lyrics;
|
namespace ZonyLrcTools.Common.Lyrics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 歌词下载核心逻辑的接口定义。
|
||||||
|
/// </summary>
|
||||||
public interface ILyricsDownloader
|
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,
|
Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
|
||||||
int parallelCount = 2,
|
int parallelCount = 1,
|
||||||
|
Func<MusicInfo, Task>? callback = null,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取目前可用的歌词下载器。
|
||||||
|
/// </summary>
|
||||||
IEnumerable<ILyricsProvider> AvailableProviders { get; }
|
IEnumerable<ILyricsProvider> AvailableProviders { get; }
|
||||||
}
|
}
|
@ -18,6 +18,6 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <param name="sourceLyric">原始歌词数据。</param>
|
/// <param name="sourceLyric">原始歌词数据。</param>
|
||||||
/// <param name="translationLyric">翻译歌词数据。</param>
|
/// <param name="translationLyric">翻译歌词数据。</param>
|
||||||
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
|
/// <returns>构建完成的 <see cref="LyricsItemCollection"/> 对象。</returns>
|
||||||
LyricsItemCollection Build(string sourceLyric, string? translationLyric);
|
LyricsItemCollection Build(string? sourceLyric, string? translationLyric);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <param name="artist">歌曲的作者。</param>
|
/// <param name="artist">歌曲的作者。</param>
|
||||||
/// <param name="duration">歌曲的时长。</param>
|
/// <param name="duration">歌曲的时长。</param>
|
||||||
/// <returns>歌曲的歌词数据对象。</returns>
|
/// <returns>歌曲的歌词数据对象。</returns>
|
||||||
ValueTask<LyricsItemCollection> DownloadAsync(string? songName, string? artist, long? duration = null);
|
ValueTask<LyricsItemCollection> DownloadAsync(string songName, string artist, long? duration = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 下载器的名称。
|
/// 下载器的名称。
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text;
|
using System.Collections.Immutable;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Polly;
|
|
||||||
using ZonyLrcTools.Common.Configuration;
|
using ZonyLrcTools.Common.Configuration;
|
||||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||||
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||||
@ -10,6 +10,7 @@ using ZonyLrcTools.Common.Infrastructure.Threading;
|
|||||||
|
|
||||||
namespace ZonyLrcTools.Common.Lyrics;
|
namespace ZonyLrcTools.Common.Lyrics;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ZonyLrcTools.Common.Lyrics.ILyricsDownloader" />
|
||||||
public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<ILyricsProvider> _lyricsProviders;
|
private readonly IEnumerable<ILyricsProvider> _lyricsProviders;
|
||||||
@ -38,6 +39,7 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
|
|
||||||
public async Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
|
public async Task DownloadAsync(List<MusicInfo> needDownloadMusicInfos,
|
||||||
int parallelCount = 1,
|
int parallelCount = 1,
|
||||||
|
Func<MusicInfo, Task>? callback = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await _logger.InfoAsync("开始下载歌词文件数据...");
|
await _logger.InfoAsync("开始下载歌词文件数据...");
|
||||||
@ -57,17 +59,20 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
{
|
{
|
||||||
await DownloadAndWriteLyricsAsync(lyricsProvider, info);
|
await DownloadAndWriteLyricsAsync(lyricsProvider, info);
|
||||||
|
|
||||||
if (info.IsSuccessful)
|
if (!info.IsSuccessful) continue;
|
||||||
{
|
_logger.LogSuccessful(info);
|
||||||
_logger.LogSuccessful(info);
|
break;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (callback != null) await callback(info);
|
||||||
}, cancellationToken), cancellationToken));
|
}, cancellationToken), cancellationToken));
|
||||||
|
|
||||||
await Task.WhenAll(downloadTasks);
|
await Task.WhenAll(downloadTasks);
|
||||||
|
|
||||||
await _logger.InfoAsync($"歌词数据下载完成,成功: {needDownloadMusicInfos.Count(m => m.IsSuccessful)} 失败{needDownloadMusicInfos.Count(m => m.IsSuccessful == false)}。");
|
var successfulCount = needDownloadMusicInfos.Count(m => m is { IsSuccessful: true, IsPruneMusic: false });
|
||||||
|
var skippedCount = needDownloadMusicInfos.Count(m => m is { IsSuccessful: true, IsPruneMusic: true });
|
||||||
|
var failedCount = needDownloadMusicInfos.Count(m => m.IsSuccessful == false);
|
||||||
|
await _logger.InfoAsync($"歌词数据下载完成,成功: {successfulCount} 跳过(纯音乐): {skippedCount} 失败{failedCount}。");
|
||||||
await LogFailedSongFilesInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"歌词下载失败列表_{DateTime.Now:yyyyMMddHHmmss}.txt"), needDownloadMusicInfos);
|
await LogFailedSongFilesInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"歌词下载失败列表_{DateTime.Now:yyyyMMddHHmmss}.txt"), needDownloadMusicInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +85,7 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
if (lyrics.IsPruneMusic)
|
if (lyrics.IsPruneMusic)
|
||||||
{
|
{
|
||||||
info.IsSuccessful = true;
|
info.IsSuccessful = true;
|
||||||
|
info.IsPruneMusic = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,12 +140,18 @@ public class LyricsDownloader : ILyricsDownloader, ISingletonDependency
|
|||||||
return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(_options.Provider.Lyric.Config.FileEncoding), lyrics.GetUtf8Bytes());
|
return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(_options.Provider.Lyric.Config.FileEncoding), lyrics.GetUtf8Bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LogFailedSongFilesInfo(string outFilePath, List<MusicInfo> musicInfos)
|
private async Task LogFailedSongFilesInfo(string outFilePath, IEnumerable<MusicInfo> musicInfos)
|
||||||
{
|
{
|
||||||
|
var failedSongList = musicInfos.Where(m => m.IsSuccessful == false).ToImmutableList();
|
||||||
|
if (!failedSongList.Any())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var failedSongFiles = new StringBuilder();
|
var failedSongFiles = new StringBuilder();
|
||||||
failedSongFiles.AppendLine("歌曲名,歌手,路径");
|
failedSongFiles.AppendLine("歌曲名,歌手,路径");
|
||||||
|
|
||||||
foreach (var failedSongFile in musicInfos.Where(m => m.IsSuccessful == false))
|
foreach (var failedSongFile in failedSongList)
|
||||||
{
|
{
|
||||||
failedSongFiles.AppendLine($"{failedSongFile.Name},{failedSongFile.Artist},{failedSongFile.FilePath}");
|
failedSongFiles.AppendLine($"{failedSongFile.Name},{failedSongFile.Artist},{failedSongFile.FilePath}");
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词文本数据。
|
/// 歌词文本数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LyricText { get; }
|
public string? LyricText { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌词所在的时间(分)。
|
/// 歌词所在的时间(分)。
|
||||||
@ -55,21 +55,21 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// <param name="minute">歌词所在的时间(分)。</param>
|
/// <param name="minute">歌词所在的时间(分)。</param>
|
||||||
/// <param name="second">歌词所在的时间(秒)。</param>
|
/// <param name="second">歌词所在的时间(秒)。</param>
|
||||||
/// <param name="lyricText">歌词文本数据。</param>
|
/// <param name="lyricText">歌词文本数据。</param>
|
||||||
public LyricsItem(int minute, double second, string lyricText)
|
public LyricsItem(int minute, double second, string? lyricText)
|
||||||
{
|
{
|
||||||
Minute = minute;
|
Minute = minute;
|
||||||
Second = second;
|
Second = second;
|
||||||
LyricText = lyricText;
|
LyricText = lyricText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CompareTo(LyricsItem other)
|
public int CompareTo(LyricsItem? other)
|
||||||
{
|
{
|
||||||
if (SortScore > other.SortScore)
|
if (SortScore > other?.SortScore)
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SortScore < other.SortScore)
|
if (SortScore < other?.SortScore)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -87,12 +87,12 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
return left.SortScore < right.SortScore;
|
return left.SortScore < right.SortScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(LyricsItem left, LyricsItem right)
|
public static bool operator ==(LyricsItem? left, LyricsItem? right)
|
||||||
{
|
{
|
||||||
return (int?)left?.SortScore == (int?)right?.SortScore;
|
return (int?)left?.SortScore == (int?)right?.SortScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(LyricsItem item1, LyricsItem item2)
|
public static bool operator !=(LyricsItem? item1, LyricsItem? item2)
|
||||||
{
|
{
|
||||||
return !(item1 == item2);
|
return !(item1 == item2);
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
return LyricText == other.LyricText && Minute == other.Minute && Second.Equals(other.Second);
|
return LyricText == other.LyricText && Minute == other.Minute && Second.Equals(other.Second);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(null, obj)) return false;
|
if (ReferenceEquals(null, obj)) return false;
|
||||||
if (ReferenceEquals(this, obj)) return true;
|
if (ReferenceEquals(this, obj)) return true;
|
||||||
|
@ -14,7 +14,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPruneMusic => Count == 0;
|
public bool IsPruneMusic => Count == 0;
|
||||||
|
|
||||||
public GlobalLyricsConfigOptions? Options { get; private set; }
|
public GlobalLyricsConfigOptions? Options { get; }
|
||||||
|
|
||||||
public LyricsItemCollection(GlobalLyricsConfigOptions? options)
|
public LyricsItemCollection(GlobalLyricsConfigOptions? options)
|
||||||
{
|
{
|
||||||
@ -29,6 +29,11 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
}
|
}
|
||||||
|
|
||||||
var option = left.Options;
|
var option = left.Options;
|
||||||
|
if (option == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("LyricsItemCollection.Options");
|
||||||
|
}
|
||||||
|
|
||||||
var newCollection = new LyricsItemCollection(option);
|
var newCollection = new LyricsItemCollection(option);
|
||||||
var indexDiff = left.Count - right.Count;
|
var indexDiff = left.Count - right.Count;
|
||||||
if (!option.IsOneLine)
|
if (!option.IsOneLine)
|
||||||
@ -100,6 +105,11 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
if (Options == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("LyricsItemCollection.Options");
|
||||||
|
}
|
||||||
|
|
||||||
var lyricBuilder = new StringBuilder();
|
var lyricBuilder = new StringBuilder();
|
||||||
ForEach(lyric => lyricBuilder.Append(lyric).Append(Options.LineBreak));
|
ForEach(lyric => lyricBuilder.Append(lyric).Append(Options.LineBreak));
|
||||||
return lyricBuilder.ToString().TrimEnd(Options.LineBreak);
|
return lyricBuilder.ToString().TrimEnd(Options.LineBreak);
|
||||||
|
@ -30,7 +30,7 @@ namespace ZonyLrcTools.Common.Lyrics
|
|||||||
return lyric;
|
return lyric;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LyricsItemCollection Build(string sourceLyric, string? translationLyric)
|
public LyricsItemCollection Build(string? sourceLyric, string? translationLyric)
|
||||||
{
|
{
|
||||||
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
|
var lyric = new LyricsItemCollection(_options.Provider.Lyric.Config);
|
||||||
if (string.IsNullOrEmpty(sourceLyric))
|
if (string.IsNullOrEmpty(sourceLyric))
|
||||||
|
@ -10,9 +10,9 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("client")] public string UnknownParameters3 { get; }
|
[JsonProperty("client")] public string UnknownParameters3 { get; }
|
||||||
|
|
||||||
[JsonProperty("hash")] public string FileHash { get; }
|
[JsonProperty("hash")] public string? FileHash { get; }
|
||||||
|
|
||||||
public GetLyricAccessKeyRequest(string fileHash)
|
public GetLyricAccessKeyRequest(string? fileHash)
|
||||||
{
|
{
|
||||||
UnknownParameters1 = 1;
|
UnknownParameters1 = 1;
|
||||||
UnknownParameters2 = "yes";
|
UnknownParameters2 = "yes";
|
||||||
|
@ -8,13 +8,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("errcode")] public int ErrorCode { get; set; }
|
[JsonProperty("errcode")] public int ErrorCode { get; set; }
|
||||||
|
|
||||||
[JsonProperty("candidates")] public List<GetLyricAccessKeyDataObject> AccessKeyDataObjects { get; set; }
|
[JsonProperty("candidates")] public List<GetLyricAccessKeyDataObject>? AccessKeyDataObjects { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetLyricAccessKeyDataObject
|
public class GetLyricAccessKeyDataObject
|
||||||
{
|
{
|
||||||
[JsonProperty("accesskey")] public string AccessKey { get; set; }
|
[JsonProperty("accesskey")] public string? AccessKey { get; set; }
|
||||||
|
|
||||||
[JsonProperty("id")] public string Id { get; set; }
|
[JsonProperty("id")] public string? Id { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,11 +12,11 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("charset")] public string UnknownParameters4 { get; }
|
[JsonProperty("charset")] public string UnknownParameters4 { get; }
|
||||||
|
|
||||||
[JsonProperty("id")] public string Id { get; }
|
[JsonProperty("id")] public string? Id { get; }
|
||||||
|
|
||||||
[JsonProperty("accesskey")] public string AccessKey { get; }
|
[JsonProperty("accesskey")] public string? AccessKey { get; }
|
||||||
|
|
||||||
public GetLyricRequest(string id, string accessKey)
|
public GetLyricRequest(string? id, string? accessKey)
|
||||||
{
|
{
|
||||||
UnknownParameters1 = 1;
|
UnknownParameters1 = 1;
|
||||||
UnknownParameters2 = "iphone";
|
UnknownParameters2 = "iphone";
|
||||||
|
@ -6,20 +6,20 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel
|
|||||||
{
|
{
|
||||||
[JsonProperty("status")] public int Status { get; set; }
|
[JsonProperty("status")] public int Status { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public SongSearchResponseInnerData Data { get; set; }
|
[JsonProperty("data")] public SongSearchResponseInnerData? Data { get; set; }
|
||||||
|
|
||||||
[JsonProperty("error_code")] public int ErrorCode { get; set; }
|
[JsonProperty("error_code")] public int ErrorCode { get; set; }
|
||||||
|
|
||||||
[JsonProperty("error_msg")] public string ErrorMessage { get; set; }
|
[JsonProperty("error_msg")] public string? ErrorMessage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongSearchResponseInnerData
|
public class SongSearchResponseInnerData
|
||||||
{
|
{
|
||||||
[JsonProperty("lists")] public List<SongSearchResponseSongDetail> List { get; set; }
|
[JsonProperty("lists")] public List<SongSearchResponseSongDetail>? List { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongSearchResponseSongDetail
|
public class SongSearchResponseSongDetail
|
||||||
{
|
{
|
||||||
public string FileHash { get; set; }
|
public string? FileHash { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ using ZonyLrcTools.Common.Lyrics.Providers.KuGou.JsonModel;
|
|||||||
|
|
||||||
namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
|
namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
|
||||||
{
|
{
|
||||||
public class KuGourLyricsProvider : LyricsProvider
|
public class KuGouLyricsProvider : LyricsProvider
|
||||||
{
|
{
|
||||||
public override string DownloaderName => InternalLyricsProviderNames.KuGou;
|
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 KuGouGetLyricAccessKeyUrl = @"http://lyrics.kugou.com/search";
|
||||||
private const string KuGouGetLyricUrl = @"http://lyrics.kugou.com/download";
|
private const string KuGouGetLyricUrl = @"http://lyrics.kugou.com/download";
|
||||||
|
|
||||||
public KuGourLyricsProvider(IWarpHttpClient warpHttpClient,
|
public KuGouLyricsProvider(IWarpHttpClient warpHttpClient,
|
||||||
ILyricsItemCollectionFactory lyricsItemCollectionFactory,
|
ILyricsItemCollectionFactory lyricsItemCollectionFactory,
|
||||||
IOptions<GlobalOptions> options)
|
IOptions<GlobalOptions> options)
|
||||||
{
|
{
|
||||||
@ -38,9 +38,9 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
|
|||||||
|
|
||||||
// 获得特殊的 AccessToken 与 Id,真正请求歌词数据。
|
// 获得特殊的 AccessToken 与 Id,真正请求歌词数据。
|
||||||
var accessKeyResponse = await _warpHttpClient.GetAsync<GetLyricAccessKeyResponse>(KuGouGetLyricAccessKeyUrl,
|
var accessKeyResponse = await _warpHttpClient.GetAsync<GetLyricAccessKeyResponse>(KuGouGetLyricAccessKeyUrl,
|
||||||
new GetLyricAccessKeyRequest(searchResult.Data.List[0].FileHash));
|
new GetLyricAccessKeyRequest(searchResult.Data?.List?[0].FileHash));
|
||||||
|
|
||||||
if (accessKeyResponse.AccessKeyDataObjects.Count == 0)
|
if (accessKeyResponse.AccessKeyDataObjects == null || accessKeyResponse.AccessKeyDataObjects.Count == 0)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
||||||
@ -54,18 +54,18 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuGou
|
|||||||
{
|
{
|
||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
var lyricJsonObj = JObject.Parse((data as string)!);
|
var lyricJsonObj = JObject.Parse((data as string)!);
|
||||||
if (lyricJsonObj.SelectToken("$.status").Value<int>() != 200)
|
if (lyricJsonObj.SelectToken("$.status")?.Value<int>() != 200)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
||||||
|
|
||||||
var lyricText = Encoding.UTF8.GetString(Convert.FromBase64String(lyricJsonObj.SelectToken("$.content").Value<string>()));
|
var lyricText = Encoding.UTF8.GetString(Convert.FromBase64String(lyricJsonObj.SelectToken("$.content")?.Value<string>() ?? string.Empty));
|
||||||
return _lyricsItemCollectionFactory.Build(lyricText);
|
return _lyricsItemCollectionFactory.Build(lyricText);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
||||||
{
|
{
|
||||||
if ((response.ErrorCode != 0 && response.Status != 1) || response.Data.List.Count == 0)
|
if ((response.ErrorCode != 0 && response.Status != 1) || response.Data?.List?.Count == 0)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ public class GetLyricsResponse
|
|||||||
{
|
{
|
||||||
[JsonProperty("status")] public int Status { get; set; }
|
[JsonProperty("status")] public int Status { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public GetLyricsResponseInnerData Data { get; set; }
|
[JsonProperty("data")] public GetLyricsResponseInnerData? Data { get; set; }
|
||||||
|
|
||||||
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ public class GetLyricsResponseInnerData
|
|||||||
|
|
||||||
public class GetLyricsItem
|
public class GetLyricsItem
|
||||||
{
|
{
|
||||||
[JsonProperty("lineLyric")] public string Text { get; set; }
|
[JsonProperty("lineLyric")] public string? Text { get; set; }
|
||||||
|
|
||||||
[JsonProperty("time")] public string Position { get; set; }
|
[JsonProperty("time")] public string Position { get; set; } = null!;
|
||||||
}
|
}
|
@ -4,13 +4,49 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
|
|||||||
|
|
||||||
public class SongSearchRequest
|
public class SongSearchRequest
|
||||||
{
|
{
|
||||||
[JsonProperty("key")] public string Keyword { get; set; }
|
[JsonProperty("all")] public string Keyword { get; set; }
|
||||||
|
|
||||||
[JsonProperty("pn")] public int PageNumber { get; }
|
[JsonProperty("pn")] public int PageNumber { get; }
|
||||||
|
|
||||||
[JsonProperty("rn")] public int PageSize { 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}";
|
Keyword = $"{name} {artist}";
|
||||||
PageNumber = pageNumber;
|
PageNumber = pageNumber;
|
||||||
|
@ -4,15 +4,14 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.KuWo.JsonModel;
|
|||||||
|
|
||||||
public class SongSearchResponse
|
public class SongSearchResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("code")] public int Code { get; set; }
|
[JsonProperty("TOTAL")] public int TotalCount { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public SongSearchResponseInnerData InnerData { get; set; }
|
[JsonProperty("abslist")]
|
||||||
|
public IList<SongSearchResponseSongDetail> SongList { get; set; }
|
||||||
[JsonProperty("msg")] public string? ErrorMessage { get; set; }
|
|
||||||
|
|
||||||
public long GetMatchedMusicId(string musicName, string artistName, long? duration)
|
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)
|
if (prefectMatch != null)
|
||||||
{
|
{
|
||||||
return prefectMatch.MusicId;
|
return prefectMatch.MusicId;
|
||||||
@ -20,27 +19,42 @@ public class SongSearchResponse
|
|||||||
|
|
||||||
if (duration is null or 0)
|
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; }
|
/// <summary>
|
||||||
}
|
/// 歌手名称。
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("ARTIST")]
|
||||||
|
public string Artist { get; set; }
|
||||||
|
|
||||||
public class SongSearchResponseDetail
|
/// <summary>
|
||||||
{
|
/// 歌曲名称。
|
||||||
[JsonProperty("artist")] public string? Artist { get; set; }
|
/// </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; }
|
/// <summary>
|
||||||
|
/// 歌曲的时间长度。
|
||||||
[JsonProperty("duration")] public long Duration { get; set; }
|
/// </summary>
|
||||||
|
[JsonProperty("DURATION")]
|
||||||
|
public long Duration { get; set; }
|
||||||
}
|
}
|
@ -13,29 +13,27 @@ public class KuWoLyricsProvider : LyricsProvider
|
|||||||
{
|
{
|
||||||
public override string DownloaderName => InternalLyricsProviderNames.KuWo;
|
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 KuWoSearchLyricsUrl = @"https://m.kuwo.cn/newh5/singles/songinfoandlrc";
|
||||||
private const string KuWoDefaultToken = "ABCDE12345";
|
private const string KuWoDefaultToken = "ABCDE12345";
|
||||||
|
|
||||||
private readonly IWarpHttpClient _warpHttpClient;
|
private readonly IWarpHttpClient _warpHttpClient;
|
||||||
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
|
|
||||||
private readonly GlobalOptions _options;
|
private readonly GlobalOptions _options;
|
||||||
|
|
||||||
private static readonly ProductInfoHeaderValue UserAgent = new("Chrome", "81.0.4044.138");
|
private static readonly ProductInfoHeaderValue UserAgent = new("Chrome", "81.0.4044.138");
|
||||||
|
|
||||||
public KuWoLyricsProvider(IWarpHttpClient warpHttpClient,
|
public KuWoLyricsProvider(IWarpHttpClient warpHttpClient,
|
||||||
ILyricsItemCollectionFactory lyricsItemCollectionFactory,
|
|
||||||
IOptions<GlobalOptions> options)
|
IOptions<GlobalOptions> options)
|
||||||
{
|
{
|
||||||
_warpHttpClient = warpHttpClient;
|
_warpHttpClient = warpHttpClient;
|
||||||
_lyricsItemCollectionFactory = lyricsItemCollectionFactory;
|
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async ValueTask<object> DownloadDataAsync(LyricsProviderArgs args)
|
protected override async ValueTask<object> DownloadDataAsync(LyricsProviderArgs args)
|
||||||
{
|
{
|
||||||
var songSearchResponse = await _warpHttpClient.GetAsync<SongSearchResponse>(KuWoSearchMusicUrl,
|
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 =>
|
||||||
{
|
{
|
||||||
op.Headers.UserAgent.Add(UserAgent);
|
op.Headers.UserAgent.Add(UserAgent);
|
||||||
@ -55,12 +53,13 @@ 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;
|
await ValueTask.CompletedTask;
|
||||||
|
|
||||||
var lyricsResponse = (GetLyricsResponse)lyricsObject;
|
var lyricsResponse = (GetLyricsResponse)lyricsObject;
|
||||||
if (lyricsResponse.Data.Lyrics == null)
|
if (lyricsResponse.Data?.Lyrics == null)
|
||||||
{
|
{
|
||||||
return new LyricsItemCollection(null);
|
return new LyricsItemCollection(null);
|
||||||
}
|
}
|
||||||
@ -69,7 +68,8 @@ public class KuWoLyricsProvider : LyricsProvider
|
|||||||
{
|
{
|
||||||
var position = double.Parse(l.Position);
|
var position = double.Parse(l.Position);
|
||||||
var positionSpan = TimeSpan.FromSeconds(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);
|
var lyricsItemCollection = new LyricsItemCollection(_options.Provider.Lyric.Config);
|
||||||
@ -79,12 +79,7 @@ public class KuWoLyricsProvider : LyricsProvider
|
|||||||
|
|
||||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
||||||
{
|
{
|
||||||
if (response.Code != 200)
|
if (response.TotalCount == 0)
|
||||||
{
|
|
||||||
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, response.ErrorMessage, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.InnerData.SongItems.Count == 0)
|
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.NoMatchingSong, attachObj: args);
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 原始的歌词。
|
/// 原始的歌词。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("lrc")]
|
[JsonProperty("lrc")]
|
||||||
public InnerLyric OriginalLyric { get; set; }
|
public InnerLyric? OriginalLyric { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 卡拉 OK 歌词。
|
/// 卡拉 OK 歌词。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("klyric")]
|
[JsonProperty("klyric")]
|
||||||
public InnerLyric KaraokeLyric { get; set; }
|
public InnerLyric? KaraokeLyric { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 如果存在翻译歌词,则本项内容为翻译歌词。
|
/// 如果存在翻译歌词,则本项内容为翻译歌词。
|
||||||
@ -26,13 +26,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。
|
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("romalrc")]
|
[JsonProperty("romalrc")]
|
||||||
public InnerLyric RomaLyric { get; set; }
|
public InnerLyric? RomaLyric { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 状态码。
|
/// 状态码。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("code")]
|
[JsonProperty("code")]
|
||||||
public string StatusCode { get; set; }
|
public string? StatusCode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -40,12 +40,12 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class InnerLyric
|
public class InnerLyric
|
||||||
{
|
{
|
||||||
[JsonProperty("version")] public string Version { get; set; }
|
[JsonProperty("version")] public string? Version { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 具体的歌词数据。
|
/// 具体的歌词数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("lyric")]
|
[JsonProperty("lyric")]
|
||||||
public string Text { get; set; }
|
public string? Text { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,13 +4,13 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
{
|
{
|
||||||
public class GetSongDetailsRequest
|
public class GetSongDetailsRequest
|
||||||
{
|
{
|
||||||
public GetSongDetailsRequest(int songId)
|
public GetSongDetailsRequest(long songId)
|
||||||
{
|
{
|
||||||
SongId = songId;
|
SongId = songId;
|
||||||
SongIds = $"%5B{songId}%5D";
|
SongIds = $"%5B{songId}%5D";
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("id")] public int SongId { get; }
|
[JsonProperty("id")] public long SongId { get; }
|
||||||
|
|
||||||
[JsonProperty("ids")] public string SongIds { get; }
|
[JsonProperty("ids")] public string SongIds { get; }
|
||||||
}
|
}
|
||||||
|
@ -45,17 +45,14 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
|
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
|
||||||
|
|
||||||
public SongSearchRequest()
|
public SongSearchRequest(string musicName, string artistName, int limit = 10)
|
||||||
{
|
{
|
||||||
CsrfToken = string.Empty;
|
CsrfToken = string.Empty;
|
||||||
Type = 1;
|
Type = 1;
|
||||||
Offset = 0;
|
Offset = 0;
|
||||||
IsTotal = true;
|
IsTotal = true;
|
||||||
Limit = 10;
|
Limit = 10;
|
||||||
}
|
|
||||||
|
|
||||||
public SongSearchRequest(string musicName, string artistName, int limit = 10) : this()
|
|
||||||
{
|
|
||||||
// Remove all the brackets and the content inside them.
|
// Remove all the brackets and the content inside them.
|
||||||
var regex = new Regex(@"\([^)]*\)");
|
var regex = new Regex(@"\([^)]*\)");
|
||||||
musicName = regex.Replace(musicName, string.Empty);
|
musicName = regex.Replace(musicName, string.Empty);
|
||||||
|
@ -4,11 +4,11 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
{
|
{
|
||||||
public class SongSearchResponse
|
public class SongSearchResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("result")] public InnerListItemModel? Items { get; set; }
|
[JsonProperty("result")] public InnerListItemModel Items { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("code")] public int StatusCode { get; set; }
|
[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);
|
var perfectMatch = Items.SongItems.FirstOrDefault(x => x.Name == songName);
|
||||||
if (perfectMatch != null)
|
if (perfectMatch != null)
|
||||||
@ -27,7 +27,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
|
|
||||||
public class InnerListItemModel
|
public class InnerListItemModel
|
||||||
{
|
{
|
||||||
[JsonProperty("songs")] public IList<SongModel>? SongItems { get; set; }
|
[JsonProperty("songs")] public IList<SongModel> SongItems { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("songCount")] public int SongCount { get; set; }
|
[JsonProperty("songCount")] public int SongCount { get; set; }
|
||||||
}
|
}
|
||||||
@ -38,25 +38,25 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 歌曲的名称。
|
/// 歌曲的名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的 Sid (Song Id)。
|
/// 歌曲的 Sid (Song Id)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public int Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的演唱者。
|
/// 歌曲的演唱者。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("artists")]
|
[JsonProperty("artists")]
|
||||||
public IList<SongArtistModel> Artists { get; set; }
|
public IList<SongArtistModel>? Artists { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的专辑信息。
|
/// 歌曲的专辑信息。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("album")]
|
[JsonProperty("album")]
|
||||||
public SongAlbumModel Album { get; set; }
|
public SongAlbumModel? Album { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的实际长度。
|
/// 歌曲的实际长度。
|
||||||
@ -71,7 +71,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 歌手/艺术家的名称。
|
/// 歌手/艺术家的名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SongAlbumModel
|
public class SongAlbumModel
|
||||||
@ -80,12 +80,12 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase.JsonModel
|
|||||||
/// 专辑的名称。
|
/// 专辑的名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 专辑图像的 Url 地址。
|
/// 专辑图像的 Url 地址。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("img1v1Url")]
|
[JsonProperty("img1v1Url")]
|
||||||
public string PictureUrl { get; set; }
|
public string? PictureUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.NetEase
|
|||||||
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
|
private readonly ILyricsItemCollectionFactory _lyricsItemCollectionFactory;
|
||||||
private readonly GlobalOptions _options;
|
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 NetEaseGetLyricUrl = @"https://music.163.com/weapi/song/lyric?csrf_token=";
|
||||||
|
|
||||||
private const string NetEaseRequestReferer = @"https://music.163.com";
|
private const string NetEaseRequestReferer = @"https://music.163.com";
|
||||||
|
@ -16,10 +16,6 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic.JsonModel
|
|||||||
|
|
||||||
[JsonProperty("g_tk")] public int Gtk { get; set; }
|
[JsonProperty("g_tk")] public int Gtk { get; set; }
|
||||||
|
|
||||||
protected GetLyricRequest()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetLyricRequest(string? songId)
|
public GetLyricRequest(string? songId)
|
||||||
{
|
{
|
||||||
IsNoBase64Encoding = 1;
|
IsNoBase64Encoding = 1;
|
||||||
|
@ -19,8 +19,8 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic.JsonModel
|
|||||||
public string Platform { get; protected set; }
|
public string Platform { get; protected set; }
|
||||||
|
|
||||||
[JsonProperty("key")]
|
[JsonProperty("key")]
|
||||||
public string Keyword { get; protected set; }
|
public string Keyword { get; protected set; } = null!;
|
||||||
|
|
||||||
protected SongSearchRequest()
|
protected SongSearchRequest()
|
||||||
{
|
{
|
||||||
Format = "json";
|
Format = "json";
|
||||||
|
@ -6,21 +6,21 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic.JsonModel
|
|||||||
{
|
{
|
||||||
[JsonProperty("code")] public int StatusCode { get; set; }
|
[JsonProperty("code")] public int StatusCode { get; set; }
|
||||||
|
|
||||||
[JsonProperty("data")] public QQMusicInnerDataModel Data { get; set; }
|
[JsonProperty("data")] public QQMusicInnerDataModel? Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QQMusicInnerDataModel
|
public class QQMusicInnerDataModel
|
||||||
{
|
{
|
||||||
[JsonProperty("song")] public QQMusicInnerSongModel Song { get; set; }
|
[JsonProperty("song")] public QQMusicInnerSongModel? Song { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QQMusicInnerSongModel
|
public class QQMusicInnerSongModel
|
||||||
{
|
{
|
||||||
[JsonProperty("itemlist")] public List<QQMusicInnerSongItem> SongItems { get; set; }
|
[JsonProperty("itemlist")] public List<QQMusicInnerSongItem>? SongItems { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QQMusicInnerSongItem
|
public class QQMusicInnerSongItem
|
||||||
{
|
{
|
||||||
[JsonProperty("mid")] public string SongId { get; set; }
|
[JsonProperty("mid")] public string? SongId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -35,7 +35,7 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic
|
|||||||
ValidateSongSearchResponse(searchResult, args);
|
ValidateSongSearchResponse(searchResult, args);
|
||||||
|
|
||||||
return await _warpHttpClient.GetAsync(QQGetLyricUrl,
|
return await _warpHttpClient.GetAsync(QQGetLyricUrl,
|
||||||
new GetLyricRequest(searchResult.Data.Song.SongItems.FirstOrDefault()?.SongId),
|
new GetLyricRequest(searchResult.Data?.Song?.SongItems?.FirstOrDefault()?.SongId),
|
||||||
op => op.Headers.Referrer = new Uri(QQMusicRequestReferer));
|
op => op.Headers.Referrer = new Uri(QQMusicRequestReferer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,15 +57,15 @@ namespace ZonyLrcTools.Common.Lyrics.Providers.QQMusic
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lyricJsonObj = JObject.Parse(lyricJsonString);
|
var lyricJsonObj = JObject.Parse(lyricJsonString);
|
||||||
var sourceLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.lyric").Value<string>()));
|
var sourceLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.lyric")!.Value<string>()));
|
||||||
var translateLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.trans").Value<string>()));
|
var translateLyric = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(lyricJsonObj.SelectToken("$.trans")!.Value<string>()));
|
||||||
|
|
||||||
return _lyricsItemCollectionFactory.Build(sourceLyric, translateLyric);
|
return _lyricsItemCollectionFactory.Build(sourceLyric, translateLyric);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricsProviderArgs args)
|
||||||
{
|
{
|
||||||
if (response is not { StatusCode: 0 } || response.Data.Song.SongItems == null)
|
if (response is not { StatusCode: 0 } || response.Data?.Song?.SongItems == null)
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
|
throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ZonyLrcTools.Common
|
namespace ZonyLrcTools.Common
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲信息的承载类,携带歌曲的相关数据。
|
/// 歌曲信息的承载类,携带歌曲的相关数据。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MusicInfo
|
public partial class MusicInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲对应的物理文件路径。
|
/// 歌曲对应的物理文件路径。
|
||||||
@ -30,6 +32,11 @@ namespace ZonyLrcTools.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSuccessful { get; set; } = true;
|
public bool IsSuccessful { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否时纯音乐?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPruneMusic { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构建一个新的 <see cref="MusicInfo"/> 对象。
|
/// 构建一个新的 <see cref="MusicInfo"/> 对象。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -38,9 +45,37 @@ namespace ZonyLrcTools.Common
|
|||||||
/// <param name="artist">歌曲的作者。</param>
|
/// <param name="artist">歌曲的作者。</param>
|
||||||
public MusicInfo(string filePath, string name, string artist)
|
public MusicInfo(string filePath, string name, string artist)
|
||||||
{
|
{
|
||||||
FilePath = filePath;
|
FilePath = Path.Combine(Path.GetDirectoryName(filePath)!, HandleInvalidFilePath(Path.GetFileName(filePath)));
|
||||||
Name = name;
|
Name = name;
|
||||||
Artist = artist;
|
Artist = artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string HandleInvalidFilePath(string srcText)
|
||||||
|
{
|
||||||
|
return InvalidFilePathRegex().Replace(srcText, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Ude;
|
||||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||||
|
|
||||||
namespace ZonyLrcTools.Common.MusicScanner;
|
namespace ZonyLrcTools.Common.MusicScanner;
|
||||||
@ -13,11 +15,15 @@ public class CsvFileMusicScanner : ITransientDependency
|
|||||||
/// <param name="csvFilePath">CSV 文件的路径。</param>
|
/// <param name="csvFilePath">CSV 文件的路径。</param>
|
||||||
/// <param name="outputDirectory">歌词文件的输出目录。</param>
|
/// <param name="outputDirectory">歌词文件的输出目录。</param>
|
||||||
/// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</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 encoding = DetectFileEncoding(csvFilePath);
|
||||||
var csvFileLines = csvFileContent.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
return csvFileLines.Skip(1).Select(line => GetMusicInfoFromCsvFileLine(line, outputDirectory, pattern)).ToList();
|
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)
|
private MusicInfo GetMusicInfoFromCsvFileLine(string csvFileLine, string outputDirectory, string pattern)
|
||||||
@ -29,4 +35,19 @@ public class CsvFileMusicScanner : ITransientDependency
|
|||||||
var musicInfo = new MusicInfo(fakeFilePath, name, artist);
|
var musicInfo = new MusicInfo(fakeFilePath, name, artist);
|
||||||
return musicInfo;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -38,7 +38,7 @@ public sealed class PlayListSongModel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 歌曲的艺术家信息,可能会有多位艺术家/歌手。
|
/// 歌曲的艺术家信息,可能会有多位艺术家/歌手。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("al")]
|
[JsonProperty("ar")]
|
||||||
[JsonConverter(typeof(PlayListSongArtistModelJsonConverter))]
|
[JsonConverter(typeof(PlayListSongArtistModelJsonConverter))]
|
||||||
public ICollection<PlayListSongArtistModel>? Artists { get; set; }
|
public ICollection<PlayListSongArtistModel>? Artists { get; set; }
|
||||||
}
|
}
|
||||||
@ -62,15 +62,12 @@ public class PlayListSongArtistModelJsonConverter : JsonConverter
|
|||||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var token = JToken.Load(reader);
|
var token = JToken.Load(reader);
|
||||||
switch (token.Type)
|
return token.Type switch
|
||||||
{
|
{
|
||||||
case JTokenType.Array:
|
JTokenType.Array => token.ToObject(objectType),
|
||||||
return token.ToObject(objectType);
|
JTokenType.Object => new List<PlayListSongArtistModel> { token.ToObject<PlayListSongArtistModel>()! },
|
||||||
case JTokenType.Object:
|
_ => null
|
||||||
return new List<PlayListSongArtistModel> { token.ToObject<PlayListSongArtistModel>() };
|
};
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
|
@ -37,11 +37,11 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从网易云歌单获取需要下载的歌曲列表,调用这个 API 需要用户登录,否则获取的歌单数据不全。
|
/// 从网易云歌单获取需要下载的歌曲列表,调用这个 API 需要用户登录,否则获取的歌单数据不全。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="songListId">网易云音乐歌单的 ID。</param>
|
/// <param name="songListIds">网易云音乐歌单的 ID。</param>
|
||||||
/// <param name="outputDirectory">歌词文件的输出路径。</param>
|
/// <param name="outputDirectory">歌词文件的输出路径。</param>
|
||||||
/// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</param>
|
/// <param name="pattern">输出的歌词文件格式,默认是 "{Artist} - {Title}.lrc" 的形式。</param>
|
||||||
/// <returns>返回获取到的歌曲列表。</returns>
|
/// <returns>返回获取到的歌曲列表。</returns>
|
||||||
public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListId, string outputDirectory, string pattern)
|
public async Task<List<MusicInfo>> GetMusicInfoFromNetEaseMusicSongListAsync(string songListIds, string outputDirectory, string pattern)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Cookie))
|
if (string.IsNullOrEmpty(Cookie))
|
||||||
{
|
{
|
||||||
@ -50,40 +50,52 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
|
|||||||
CsrfToken = loginResponse.csrfToken ?? string.Empty;
|
CsrfToken = loginResponse.csrfToken ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
|
async Task<List<MusicInfo>> GetMusicInfoBySongIdAsync(string songId)
|
||||||
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
|
|
||||||
var response = await _warpHttpClient.PostAsync<GetMusicInfoFromNetEaseMusicSongListResponse>(
|
|
||||||
$"{Host}/weapi/v6/playlist/detail?csrf_token=e5044820d8b66e14b8c31d39f9651a98", requestOption:
|
|
||||||
request =>
|
|
||||||
{
|
|
||||||
request.Headers.Add("Cookie", Cookie);
|
|
||||||
request.Content = new FormUrlEncodedContent(HandleRequest(new
|
|
||||||
{
|
|
||||||
csrf_token = CsrfToken,
|
|
||||||
id = songListId,
|
|
||||||
n = 1000,
|
|
||||||
offset = 0,
|
|
||||||
total = true,
|
|
||||||
limit = 1000,
|
|
||||||
}, secretKey, encSecKey));
|
|
||||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.Code != 200 || response.PlayList?.SongList == null)
|
|
||||||
{
|
{
|
||||||
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
|
var secretKey = NetEaseMusicEncryptionHelper.CreateSecretKey(16);
|
||||||
|
var encSecKey = NetEaseMusicEncryptionHelper.RsaEncode(secretKey);
|
||||||
|
var response = await _warpHttpClient.PostAsync<GetMusicInfoFromNetEaseMusicSongListResponse>(
|
||||||
|
$"{Host}/weapi/v6/playlist/detail?csrf_token={CsrfToken}", requestOption:
|
||||||
|
request =>
|
||||||
|
{
|
||||||
|
request.Headers.Add("Cookie", Cookie);
|
||||||
|
request.Content = new FormUrlEncodedContent(HandleRequest(new
|
||||||
|
{
|
||||||
|
csrf_token = CsrfToken,
|
||||||
|
id = songId,
|
||||||
|
n = 1000,
|
||||||
|
offset = 0,
|
||||||
|
total = true,
|
||||||
|
limit = 1000,
|
||||||
|
}, secretKey, encSecKey));
|
||||||
|
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.Code != 200 || response.PlayList?.SongList == null)
|
||||||
|
{
|
||||||
|
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.PlayList.SongList
|
||||||
|
.Where(song => !string.IsNullOrEmpty(song.Name))
|
||||||
|
.Select(song =>
|
||||||
|
{
|
||||||
|
var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty;
|
||||||
|
var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName));
|
||||||
|
|
||||||
|
return new MusicInfo(fakeFilePath, song.Name!, artistName);
|
||||||
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.PlayList.SongList
|
var musicInfoList = new List<MusicInfo>();
|
||||||
.Where(song => !string.IsNullOrEmpty(song.Name))
|
foreach (var songListId in songListIds.Split(';'))
|
||||||
.Select(song =>
|
{
|
||||||
{
|
_logger.LogInformation("正在获取歌单 {SongListId} 的歌曲列表。", songListId);
|
||||||
var artistName = song.Artists?.FirstOrDefault()?.Name ?? string.Empty;
|
var musicInfos = await GetMusicInfoBySongIdAsync(songListId);
|
||||||
var fakeFilePath = Path.Combine(outputDirectory, pattern.Replace("{Name}", song.Name).Replace("{Artist}", artistName));
|
musicInfoList.AddRange(musicInfos);
|
||||||
|
}
|
||||||
|
|
||||||
var info = new MusicInfo(fakeFilePath, song.Name!, artistName);
|
return musicInfoList;
|
||||||
return info;
|
|
||||||
}).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -127,7 +139,7 @@ public class NetEaseMusicSongListMusicScanner : ISingletonDependency
|
|||||||
var asciiQrCodeString = qrCode.GetGraphic(1, drawQuietZones: false);
|
var asciiQrCodeString = qrCode.GetGraphic(1, drawQuietZones: false);
|
||||||
|
|
||||||
_logger.LogInformation("请使用网易云 APP 扫码登录:");
|
_logger.LogInformation("请使用网易云 APP 扫码登录:");
|
||||||
_logger.LogInformation(asciiQrCodeString);
|
_logger.LogInformation("\n{AsciiQrCodeString}", asciiQrCodeString);
|
||||||
|
|
||||||
// Wait for login success.
|
// Wait for login success.
|
||||||
var isLogin = false;
|
var isLogin = false;
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
// namespace ZonyLrcTools.Common.MusicScanner;
|
|
||||||
//
|
|
||||||
// public class QQMusicSongListMusicScanner : IMusicScanner
|
|
||||||
// {
|
|
||||||
// }
|
|
@ -22,11 +22,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
_wordsDictionary = new Lazy<Dictionary<string, string>>(() =>
|
_wordsDictionary = new Lazy<Dictionary<string, string>>(() =>
|
||||||
{
|
{
|
||||||
var jsonData = File.ReadAllText(_options.Provider.Tag.BlockWord.FilePath);
|
var jsonData = File.ReadAllText(_options.Provider.Tag.BlockWord.FilePath);
|
||||||
return JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData);
|
return JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData) ?? throw new InvalidOperationException("屏蔽词字典文件格式错误。");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetValue(string key)
|
public string? GetValue(string key)
|
||||||
{
|
{
|
||||||
if (_wordsDictionary.Value.TryGetValue(key, out var value))
|
if (_wordsDictionary.Value.TryGetValue(key, out var value))
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<MusicInfo> LoadAsync(string filePath)
|
public async ValueTask<MusicInfo?> LoadAsync(string filePath)
|
||||||
{
|
{
|
||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
|
|
||||||
|
@ -13,6 +13,6 @@
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="key">原始单词。</param>
|
/// <param name="key">原始单词。</param>
|
||||||
/// <returns>原始单词对应的屏蔽词。</returns>
|
/// <returns>原始单词对应的屏蔽词。</returns>
|
||||||
string GetValue(string key);
|
string? GetValue(string key);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,6 +18,6 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">歌曲文件的路径。</param>
|
/// <param name="filePath">歌曲文件的路径。</param>
|
||||||
/// <returns>加载完成的歌曲信息。</returns>
|
/// <returns>加载完成的歌曲信息。</returns>
|
||||||
ValueTask<MusicInfo> LoadAsync(string filePath);
|
ValueTask<MusicInfo?> LoadAsync(string filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
public string Name => ConstantName;
|
public string Name => ConstantName;
|
||||||
public const string ConstantName = "Taglib";
|
public const string ConstantName = "Taglib";
|
||||||
|
|
||||||
public async ValueTask<MusicInfo> LoadAsync(string filePath)
|
public async ValueTask<MusicInfo?> LoadAsync(string filePath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -30,12 +30,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
|||||||
|
|
||||||
await ValueTask.CompletedTask;
|
await ValueTask.CompletedTask;
|
||||||
|
|
||||||
if (songName == null && songArtist == null)
|
return songName == null ? null : new MusicInfo(filePath, songName, songArtist);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MusicInfo(filePath, songName, songArtist);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,11 @@ public class DefaultUpdater : IUpdater, ISingletonDependency
|
|||||||
|
|
||||||
public async Task CheckUpdateAsync()
|
public async Task CheckUpdateAsync()
|
||||||
{
|
{
|
||||||
|
if (!IsCheckUpdate())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var response = await _warpHttpClient.GetAsync<NewVersionResponse?>(UpdateUrl);
|
var response = await _warpHttpClient.GetAsync<NewVersionResponse?>(UpdateUrl);
|
||||||
if (response == null)
|
if (response == null)
|
||||||
{
|
{
|
||||||
@ -56,4 +61,27 @@ public class DefaultUpdater : IUpdater, ISingletonDependency
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsCheckUpdate()
|
||||||
|
{
|
||||||
|
var lockFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update.lock");
|
||||||
|
|
||||||
|
if (!File.Exists(lockFile))
|
||||||
|
{
|
||||||
|
File.WriteAllText(lockFile, DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (long.TryParse(File.ReadAllText(lockFile), out var time))
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||||
|
if (now - time <= 86400 /* 1 Day */)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(lockFile, DateTimeOffset.Now.ToUnixTimeSeconds().ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,26 +1,26 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>4.0.0.51</Version>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
|
<PackageReference Include="MusicDecrypto.Library"/>
|
||||||
<PackageReference Include="MusicDecrypto.Library" Version="2.2.0" />
|
<PackageReference Include="Newtonsoft.Json"/>
|
||||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.0.0" />
|
<PackageReference Include="NetEscapades.Configuration.Yaml"/>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Polly"/>
|
||||||
<PackageReference Include="Polly" Version="7.2.3" />
|
<PackageReference Include="QRCoder"/>
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="TagLibSharp"/>
|
||||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
<PackageReference Include="Ude.NetStandard" />
|
||||||
|
<PackageReference Include="YamlDotNet" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Lyrics\Providers\Kugeci\KugeciDownloader.cs" />
|
<Compile Remove="Lyrics\Providers\Kugeci\KugeciDownloader.cs"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
namespace ZonyLrcTools.LocalServer.Contract.Dtos;
|
|
||||||
|
|
||||||
public class PagedListRequestDto
|
|
||||||
{
|
|
||||||
public int PageIndex { get; set; }
|
|
||||||
public int PageSize { get; set; }
|
|
||||||
}
|
|
@ -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>();
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="7.0.4" />
|
|
||||||
<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="5.0.1" />
|
|
||||||
<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>
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Debug",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*",
|
|
||||||
"serverOptions": {
|
|
||||||
"name": "ZonyLRcToolsServer",
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"ip": "Any",
|
|
||||||
"port": 50001
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
213
src/ui/.gitignore
vendored
213
src/ui/.gitignore
vendored
@ -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/
|
|
||||||
|
|
@ -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/).
|
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"module": "esnext",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lib": [
|
|
||||||
"esnext",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"scripthost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 |
@ -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>
|
|
@ -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 |
@ -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 |
@ -1,5 +0,0 @@
|
|||||||
import Vue from "vue";
|
|
||||||
|
|
||||||
const eventBus = new Vue();
|
|
||||||
|
|
||||||
export default eventBus;
|
|
@ -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;
|
|
@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "setting"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -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')
|
|
@ -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'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
@ -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
|
|
@ -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: {}
|
|
||||||
})
|
|
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,6 +0,0 @@
|
|||||||
const { defineConfig } = require('@vue/cli-service')
|
|
||||||
module.exports = defineConfig({
|
|
||||||
transpileDependencies: [
|
|
||||||
'vuetify'
|
|
||||||
]
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user