mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-05 13:07:26 +00:00
Compare commits
46 Commits
Alpha.2022
...
Alpha.2022
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d4a6a46078 | ||
![]() |
0772f5888f | ||
![]() |
950652c040 | ||
![]() |
55f720c1a1 | ||
![]() |
7463709a39 | ||
![]() |
07e660e13b | ||
![]() |
a51b399a6c | ||
![]() |
279eba48f8 | ||
![]() |
f3b1dacb0c | ||
![]() |
559efff928 | ||
![]() |
8370172aef | ||
![]() |
cb8572e599 | ||
![]() |
3b0f9fa89b | ||
![]() |
c655ac4cbb | ||
![]() |
893b1e7918 | ||
![]() |
e900a92f37 | ||
![]() |
0f84621919 | ||
![]() |
61ca863770 | ||
![]() |
2044a0b8fa | ||
![]() |
ffd76f5f2b | ||
![]() |
3705c6c8b5 | ||
![]() |
f8531e82ff | ||
![]() |
969d5e9e19 | ||
![]() |
88ed247430 | ||
![]() |
be68b3474c | ||
![]() |
363d104dbe | ||
![]() |
a1e9669843 | ||
![]() |
2b41658783 | ||
![]() |
02f82e43d3 | ||
![]() |
dbe63c390f | ||
![]() |
9f70d5d379 | ||
![]() |
8010333485 | ||
![]() |
454cd58f97 | ||
![]() |
cf8d2f8515 | ||
![]() |
4b91c3e25b | ||
![]() |
1840a10f1e | ||
![]() |
d250524208 | ||
![]() |
2c2c34f1a6 | ||
![]() |
c380fd83f6 | ||
![]() |
e12264466e | ||
![]() |
2b0b14cd7a | ||
![]() |
d1c50323b1 | ||
![]() |
e06f9eaaa9 | ||
![]() |
ed1974dfbc | ||
![]() |
ba3dd5f1bc | ||
![]() |
089559d058 |
63
.gitattributes
vendored
63
.gitattributes
vendored
@@ -1,63 +0,0 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
25
.github/workflows/dotnet.yml
vendored
Normal file
25
.github/workflows/dotnet.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: .NET
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
# - name: Test
|
||||
# run: dotnet test --no-build --verbosity normal
|
452
.gitignore
vendored
452
.gitignore
vendored
@@ -1,452 +0,0 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### VisualStudioCode template
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
### VisualStudio template
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
### 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
|
||||
|
||||
.idea/
|
||||
/.idea
|
||||
/src/ZonyLrcTools.Cli/TempFiles/
|
81
README.md
81
README.md
@@ -1,4 +1,9 @@
|
||||
简体中文 | [English](./en_US.md)
|
||||
简体中文 | [English](./docs/en_US.md)
|
||||
|
||||
## 免责声明
|
||||
- 本工具仅作个人学习研究使用,可运行的二进制文件仅用于演示功能,不得将源码及其产物用于商业用途,否则由此造成的相关法律问题,[本人](https://github.com/real-zony) 不承担任何法律责任。
|
||||
- 任何单位或个人因下载使用软件所产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),[本人](https://github.com/real-zony) 不承担任何法律责任。
|
||||
- 用户明确并同意本声明条款列举的全部内容,对使用本工具可能存在的风险和相关后果将完全由用户自行承担,[本人](https://github.com/real-zony) 不承担任何法律责任。
|
||||
|
||||
## 简介
|
||||
|
||||
@@ -7,13 +12,17 @@ ZonyLrcToolX 4 是一个基于 CEF 的跨平台歌词下载工具。
|
||||
🚧 当前版本正在开发当中。
|
||||
🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
|
||||
|
||||
## 下载
|
||||
|
||||
工具会执行每日构建动作,请访问 **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** 页面进行下载。
|
||||
|
||||
## 用法
|
||||
|
||||
Windows 用户请在软件目录当中,按住 Shift + 右键呼出菜单,然后选择 PowerShell (部分用户可能显示的是 *命令提示符*),根据下述说明执行命令即可。
|
||||
Windows 用户请在软件目录当中,按住 Shift + 右键呼出菜单,然后选择 PowerShell/命令提示符/Windows 终端,根据下述说明执行命令即可。
|
||||
|
||||
macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命令即可。
|
||||
|
||||
### 命令
|
||||
### 子命令
|
||||
|
||||
#### 歌曲下载
|
||||
|
||||
@@ -36,6 +45,8 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命
|
||||
|
||||
#### 加密格式转换
|
||||
|
||||
子命令为 `util`,可用于转换部分加密歌曲,**仅供个人研究学习使用,思路与源码都来自于网络**。
|
||||
|
||||
目前软件支持 NCM、QCM(开发中...🚧) 格式的音乐文件转换,命令如下。
|
||||
|
||||
```shell
|
||||
@@ -44,19 +55,57 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命
|
||||
|
||||
### 配置文件
|
||||
|
||||
程序的部分配置信息需要在 `appsettings.json` 进行更改,下面标注了各个配置的说明。
|
||||
程序的所有的配置信息,都在 `config.yaml` 进行更改,下面标注了各个配置的说明。
|
||||
|
||||
| 属性 | 说明 | 示例值 |
|
||||
| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------- |
|
||||
| ToolOption.SupportFileExtensions | 允许扫描的歌曲文件后缀名,以 `;` 号隔开多个后缀。 | `*.mp3;*.flac` |
|
||||
| ToolOption.NetworkOptions.Enable | 是否启用 HTTP 网络代理服务,true 表示启用,false 表示禁用。 | false |
|
||||
| ToolOption.NetworkOptions.ProxyIp | HTTP 网络代理服务的 IP,在 `Enable` 为 false 时会忽略该属性值。 | 127.0.0.1 |
|
||||
| ToolOption.NetworkOptions.ProxyPort | HTTP 网络代理服务的 端口,在 `Enable` 为 false 时会忽略该属性值。 | 8080 |
|
||||
| TagInfoProviderOptions.FileNameRegularExpressions | 文件名 Tag 标签信息读取器使用,使用正则表达式匹配歌曲名和歌手,请使用命名分组编写正则表达式。 | (?'artist'.+)\\s-\\s(?'name'.+) |
|
||||
| LyricDownloader.[n].Name | 指定歌词下载器的配置项标识,对应具体的歌词下载器。 | NetEase 或 QQ |
|
||||
| LyricDownloader.[n].Priority | 指定歌词下载器的优先级,按升序排列,如果值设置为 `-1` 则代表禁用。 | `1` |
|
||||
| BlockWordOptions.IsEnable | 是否启用屏蔽词词典。 | false |
|
||||
| BlockWordOptions.BlockWordDictionaryFile | 屏蔽词词典的位置。 | `./BlockWords.json` |
|
||||
```yaml
|
||||
globalOption:
|
||||
# 允许扫描的歌曲文件后缀名。
|
||||
supportFileExtensions:
|
||||
- '*.mp3'
|
||||
- '*.flac'
|
||||
- '*.wav'
|
||||
# 网络代理服务设置,仅支持 HTTP 代理。
|
||||
networkOptions:
|
||||
isEnable: false # 是否启用代理。
|
||||
ip: 127.0.0.1 # 代理服务 IP 地址。
|
||||
port: 4780 # 代理服务端口号。
|
||||
|
||||
# 下载器的相关参数配置。
|
||||
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 时禁用。
|
||||
- name: QQ # 基于 QQ 音乐的歌词下载器。
|
||||
priority: 2
|
||||
- name: KuGou # 基于酷狗音乐的歌词下载器。
|
||||
priority: 3
|
||||
# 歌词下载的一些共有配置参数。
|
||||
config:
|
||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
||||
isEnableTranslation: true # 是否启用翻译歌词。
|
||||
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
|
||||
```
|
||||
|
||||
### 屏蔽字典
|
||||
|
||||
@@ -72,7 +121,7 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命
|
||||
|
||||
## 捐赠
|
||||
|
||||
暂无
|
||||
<img src="./docs/img/alipay.jpg" width="200"/><img src="./docs/img/wechat.jpg" width="200"/>
|
||||
|
||||
## 路线图
|
||||
|
||||
|
@@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C29FB05C-54B
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AF8ADB2F-E46C-4DEE-8316-652D9FE1A69B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ui", "ui", "{D6E0DAF5-8171-44C0-817E-2FF9CF574E4F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Cli", "src\ZonyLrcTools.Cli\ZonyLrcTools.Cli.csproj", "{55D74323-ABFA-4A73-A3BF-F3E8D5DE6DE8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Tests", "tests\ZonyLrcTools.Tests\ZonyLrcTools.Tests.csproj", "{FFBD3200-568F-455B-8390-5E76C51D522C}"
|
||||
|
@@ -1,6 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QQ/@EntryIndexedValue">QQ</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Decryptor/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gour/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Zony/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
BIN
docs/img/alipay.jpg
Normal file
BIN
docs/img/alipay.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 247 KiB |
BIN
docs/img/wechat.jpg
Normal file
BIN
docs/img/wechat.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 KiB |
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -17,7 +18,7 @@ using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Threading;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
{
|
||||
[Command("download", Description = "下载歌词文件或专辑图像。")]
|
||||
public class DownloadCommand : ToolCommandBase
|
||||
@@ -49,7 +50,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
|
||||
[Option("-d|--dir", CommandOptionType.SingleValue, Description = "指定需要扫描的目录。")]
|
||||
[DirectoryExists]
|
||||
public string Directory { get; set; }
|
||||
public string SongsDirectory { get; set; }
|
||||
|
||||
[Option("-l|--lyric", CommandOptionType.NoValue, Description = "指定程序需要下载歌词文件。")]
|
||||
public bool DownloadLyric { get; set; }
|
||||
@@ -60,6 +61,8 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
|
||||
public int ParallelNumber { get; set; } = 2;
|
||||
|
||||
[Option] public string ErrorMessage { get; set; } = Path.Combine(Directory.GetCurrentDirectory(), "error.log");
|
||||
|
||||
#endregion
|
||||
|
||||
protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
|
||||
@@ -82,7 +85,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
|
||||
private async Task<List<string>> ScanMusicFilesAsync()
|
||||
{
|
||||
var files = (await _fileScanner.ScanAsync(Directory, _options.SupportFileExtensions.Split(';')))
|
||||
var files = (await _fileScanner.ScanAsync(SongsDirectory, _options.SupportFileExtensions))
|
||||
.SelectMany(t => t.FilePaths)
|
||||
.ToList();
|
||||
|
||||
@@ -92,18 +95,43 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
throw new ErrorCodeException(ErrorCodes.NoFilesWereScanned);
|
||||
}
|
||||
|
||||
files = RemoveExistLyricFiles(files);
|
||||
|
||||
_logger.LogInformation($"已经扫描到了 {files.Count} 个音乐文件。");
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private List<string> RemoveExistLyricFiles(List<string> filePaths)
|
||||
{
|
||||
if (!_options.Provider.Lyric.Config.IsSkipExistLyricFiles)
|
||||
{
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
return filePaths
|
||||
.Where(path =>
|
||||
{
|
||||
if (!File.Exists(Path.ChangeExtension(path, ".lrc")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.LogWarning($"已经存在歌词文件 {path},跳过。");
|
||||
return false;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<ImmutableList<MusicInfo>> LoadMusicInfoAsync(IReadOnlyCollection<string> files)
|
||||
{
|
||||
_logger.LogInformation("开始加载音乐文件的标签信息...");
|
||||
|
||||
var warpTask = new WarpTask(ParallelNumber);
|
||||
var warpTaskList = files.Select(file => warpTask.RunAsync(() => Task.Run(async () => await _tagLoader.LoadTagAsync(file))));
|
||||
var result = await Task.WhenAll(warpTaskList);
|
||||
var result = (await Task.WhenAll(warpTaskList))
|
||||
.Where(m => m != null)
|
||||
.Where(m => !string.IsNullOrEmpty(m.Name) || !string.IsNullOrEmpty(m.Artist));
|
||||
|
||||
_logger.LogInformation($"已成功加载 {files.Count} 个音乐文件的标签信息。");
|
||||
|
||||
@@ -112,7 +140,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
|
||||
private IEnumerable<ILyricDownloader> GetLyricDownloaderList()
|
||||
{
|
||||
var downloader = _options.LyricDownloaderOptions
|
||||
var downloader = _options.Provider.Lyric.Plugin
|
||||
.Where(op => op.Priority != -1)
|
||||
.OrderBy(op => op.Priority)
|
||||
.Join(_lyricDownloaderList,
|
||||
@@ -123,7 +151,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
return downloader;
|
||||
}
|
||||
|
||||
#region > 歌词下载逻辑 <
|
||||
#region > Lyric download logic <
|
||||
|
||||
private async ValueTask DownloadLyricFilesAsync(ImmutableList<MusicInfo> musicInfos)
|
||||
{
|
||||
@@ -136,63 +164,76 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
|
||||
await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"歌词数据下载完成,成功: {musicInfos.Count} 失败{0}。");
|
||||
_logger.LogInformation($"歌词数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。");
|
||||
}
|
||||
|
||||
private async Task DownloadLyricTaskLogicAsync(IEnumerable<ILyricDownloader> downloaderList, MusicInfo info)
|
||||
{
|
||||
async Task<bool> InternalDownloadLogicAsync(ILyricDownloader downloader)
|
||||
async Task InternalDownloadLogicAsync(ILyricDownloader downloader)
|
||||
{
|
||||
_logger.LogMusicInfoWithInformation(info);
|
||||
|
||||
try
|
||||
{
|
||||
var lyric = await downloader.DownloadAsync(info.Name, info.Artist);
|
||||
var filePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!, $"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc");
|
||||
if (File.Exists(filePath))
|
||||
var lyricFilePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!,
|
||||
$"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc");
|
||||
|
||||
if (File.Exists(lyricFilePath))
|
||||
{
|
||||
return true;
|
||||
File.Delete(lyricFilePath);
|
||||
}
|
||||
|
||||
info.IsSuccessful = true;
|
||||
|
||||
if (lyric.IsPruneMusic)
|
||||
{
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
await using var stream = new FileStream(filePath, FileMode.Create);
|
||||
await using var sw = new StreamWriter(stream);
|
||||
await sw.WriteAsync(lyric.ToString());
|
||||
await sw.FlushAsync();
|
||||
await using var stream = new FileStream(lyricFilePath, FileMode.Create);
|
||||
await using var sw = new BinaryWriter(stream);
|
||||
|
||||
sw.Write(EncodingConvert(lyric));
|
||||
await stream.FlushAsync();
|
||||
}
|
||||
catch (ErrorCodeException ex)
|
||||
{
|
||||
if (ex.ErrorCode == ErrorCodes.NoMatchingSong)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
info.IsSuccessful = ex.ErrorCode == ErrorCodes.NoMatchingSong;
|
||||
|
||||
_logger.LogWarningInfo(ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"下载歌词文件时发生错误:{ex.Message},歌曲名: {info.Name},歌手: {info.Artist}。");
|
||||
info.IsSuccessful = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var downloader in downloaderList)
|
||||
{
|
||||
if (await InternalDownloadLogicAsync(downloader))
|
||||
await InternalDownloadLogicAsync(downloader);
|
||||
|
||||
if (info.IsSuccessful)
|
||||
{
|
||||
break;
|
||||
_logger.LogSuccessful(info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] EncodingConvert(LyricItemCollection lyric)
|
||||
{
|
||||
var supportEncodings = Encoding.GetEncodings();
|
||||
if (supportEncodings.All(x => x.Name != _options.Provider.Lyric.Config.FileEncoding))
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.NotSupportedFileEncoding);
|
||||
}
|
||||
|
||||
return Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(_options.Provider.Lyric.Config.FileEncoding), lyric.GetUtf8Bytes());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region > 专辑图像下载逻辑 <
|
||||
#region > Ablum image download logic <
|
||||
|
||||
private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos)
|
||||
{
|
||||
@@ -205,12 +246,12 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
|
||||
await Task.WhenAll(warpTaskList);
|
||||
|
||||
_logger.LogInformation($"专辑数据下载完成,成功: {musicInfos.Count} 失败{0}。");
|
||||
_logger.LogInformation($"专辑数据下载完成,成功: {musicInfos.Count(m => m.IsSuccessful)} 失败{musicInfos.Count(m => m.IsSuccessful == false)}。");
|
||||
}
|
||||
|
||||
private async Task DownloadAlbumTaskLogicAsync(IAlbumDownloader downloader, MusicInfo info)
|
||||
{
|
||||
_logger.LogMusicInfoWithInformation(info);
|
||||
_logger.LogSuccessful(info);
|
||||
|
||||
try
|
||||
{
|
@@ -11,7 +11,7 @@ using ZonyLrcTools.Cli.Infrastructure.IO;
|
||||
using ZonyLrcTools.Cli.Infrastructure.MusicDecryption;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Threading;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
namespace ZonyLrcTools.Cli.Commands.SubCommand
|
||||
{
|
||||
public enum SupportFileType
|
||||
{
|
||||
@@ -53,7 +53,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
_logger.LogInformation("开始扫描文件夹,请稍等...");
|
||||
|
||||
var files = (await _fileScanner.ScanAsync(FilePath, new[] {"*.ncm"}))
|
||||
var files = (await _fileScanner.ScanAsync(FilePath, new[] { "*.ncm" }))
|
||||
.SelectMany(f => f.FilePaths)
|
||||
.ToList();
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
// TODO: Large Object Issue!!!!!
|
||||
var result = await _musicDecryptor.ConvertMusic(memoryStream.ToArray());
|
||||
var newFileName = Path.Combine(Path.GetDirectoryName(filePath),
|
||||
$"{Path.GetFileNameWithoutExtension(filePath)}.{((JObject) result.ExtensionObjects["JSON"]).SelectToken("$.format").Value<string>()}");
|
||||
$"{Path.GetFileNameWithoutExtension(filePath)}.{((JObject)result.ExtensionObjects["JSON"]).SelectToken("$.format").Value<string>()}");
|
||||
|
||||
await using var musicFileStream = File.Create(newFileName);
|
||||
await musicFileStream.WriteAsync(result.Data);
|
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
namespace ZonyLrcTools.Cli.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 屏蔽词选项类。
|
||||
@@ -13,6 +13,6 @@
|
||||
/// <summary>
|
||||
/// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。
|
||||
/// </summary>
|
||||
public string BlockWordDictionaryFile { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
}
|
||||
}
|
39
src/ZonyLrcTools.Cli/Config/LyricOption.cs
Normal file
39
src/ZonyLrcTools.Cli/Config/LyricOption.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Config;
|
||||
|
||||
public class LyricOption
|
||||
{
|
||||
public IEnumerable<LyricProviderOption> Plugin { get; set; }
|
||||
|
||||
public LyricConfigOption Config { get; set; }
|
||||
}
|
||||
|
||||
public class LyricConfigOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 双语歌词是否合并为一行。
|
||||
/// </summary>
|
||||
public bool IsOneLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 换行符格式,取值来自 <see cref="LineBreakType"/> 常量类。
|
||||
/// </summary>
|
||||
public string LineBreak { get; set; } = LineBreakType.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用歌词翻译功能。
|
||||
/// </summary>
|
||||
public bool IsEnableTranslation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 如果歌词文件已经存在,是否跳过这些文件
|
||||
/// </summary>
|
||||
public bool IsSkipExistLyricFiles { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 歌词文件的编码格式。
|
||||
/// </summary>
|
||||
public string FileEncoding { get; set; } = "utf-8";
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
namespace ZonyLrcTools.Cli.Config
|
||||
{
|
||||
public class LyricDownloaderOption
|
||||
public class LyricProviderOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 歌词下载器的唯一标识。
|
17
src/ZonyLrcTools.Cli/Config/ProviderOption.cs
Normal file
17
src/ZonyLrcTools.Cli/Config/ProviderOption.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Config;
|
||||
|
||||
public class ProviderOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 标签加载器相关的配置属性。
|
||||
/// </summary>
|
||||
public TagOption Tag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌词下载相关的配置信息。
|
||||
/// </summary>
|
||||
public LyricOption Lyric { get; set; }
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using YamlDotNet.Serialization;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Network;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Tag;
|
||||
@@ -8,19 +9,9 @@ namespace ZonyLrcTools.Cli.Config
|
||||
public class ToolOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持的音乐文件后缀集合,以 ; 进行分隔。
|
||||
/// 支持的音乐文件后缀集合。
|
||||
/// </summary>
|
||||
public string SupportFileExtensions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌词下载相关的配置信息。
|
||||
/// </summary>
|
||||
public LyricItemCollectionOption LyricOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标签加载器相关的配置属性。
|
||||
/// </summary>
|
||||
public IEnumerable<TagInfoProviderInstance> TagInfoProviderOptions { get; set; }
|
||||
public List<string> SupportFileExtensions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 网络代理相关的配置信息。
|
||||
@@ -28,13 +19,8 @@ namespace ZonyLrcTools.Cli.Config
|
||||
public NetworkOptions NetworkOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 歌词下载器相关的配置属性。
|
||||
/// 定义下载器的相关配置信息。
|
||||
/// </summary>
|
||||
public IEnumerable<LyricDownloaderOption> LyricDownloaderOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 屏蔽词功能相关配置。
|
||||
/// </summary>
|
||||
public BlockWordOption BlockWordOptions { get; set; }
|
||||
public ProviderOption Provider { get; set; }
|
||||
}
|
||||
}
|
@@ -44,7 +44,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Album.NetEase
|
||||
true,
|
||||
_defaultOption);
|
||||
|
||||
if (searchResult is not {StatusCode: 200} || searchResult.Items?.SongCount <= 0)
|
||||
if (searchResult is not { StatusCode: 200 } || searchResult.Items?.SongCount <= 0)
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.NoMatchingSong);
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Album.QQMusic
|
||||
SearchApi,
|
||||
requestParameter, _defaultOption);
|
||||
|
||||
return new byte[] {0x1, 0x2};
|
||||
return new byte[] { 0x1, 0x2 };
|
||||
}
|
||||
}
|
||||
}
|
@@ -31,8 +31,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.DependencyInject
|
||||
|
||||
return new HttpClientHandler
|
||||
{
|
||||
UseProxy = option.NetworkOptions.Enable,
|
||||
Proxy = new WebProxy($"{option.NetworkOptions.ProxyIp}:{option.NetworkOptions.ProxyPort}")
|
||||
UseProxy = option.NetworkOptions.IsEnable,
|
||||
Proxy = new WebProxy($"{option.NetworkOptions.Ip}:{option.NetworkOptions.Port}")
|
||||
};
|
||||
});
|
||||
|
||||
@@ -46,10 +46,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.DependencyInject
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json")
|
||||
.AddYamlFile("config.yaml")
|
||||
.Build();
|
||||
|
||||
services.Configure<ToolOptions>(configuration.GetSection("ToolOption"));
|
||||
services.Configure<ToolOptions>(configuration.GetSection("globalOption"));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@@ -27,6 +27,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Exceptions
|
||||
/// </summary>
|
||||
public const int NoFilesWereScanned = 10004;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 指定的编码不受支持,请检查配置,所有受支持的编码名称。
|
||||
/// </summary>
|
||||
public const int NotSupportedFileEncoding = 10005;
|
||||
|
||||
#endregion
|
||||
|
||||
#region > 警告信息 <
|
||||
|
@@ -41,13 +41,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲信息。
|
||||
/// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲下载成功信息。
|
||||
/// </summary>
|
||||
/// <param name="logger">日志记录器的实例。</param>
|
||||
/// <param name="musicInfo">需要打印的歌曲信息。</param>
|
||||
public static void LogMusicInfoWithInformation(this ILogger logger, MusicInfo musicInfo)
|
||||
public static void LogSuccessful(this ILogger logger, MusicInfo musicInfo)
|
||||
{
|
||||
logger.LogInformation($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 歌曲路径: {musicInfo.FilePath}");
|
||||
logger.LogInformation($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 下载成功.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Logging;
|
||||
|
||||
public static class CustomConsoleTheme
|
||||
{
|
||||
public static AnsiConsoleTheme Code { get; } = new AnsiConsoleTheme(
|
||||
new Dictionary<ConsoleThemeStyle, string>
|
||||
{
|
||||
[ConsoleThemeStyle.Text] = "\x1b[38;5;0253m",
|
||||
[ConsoleThemeStyle.SecondaryText] = "\x1b[38;5;0246m",
|
||||
[ConsoleThemeStyle.TertiaryText] = "\x1b[38;5;0242m",
|
||||
[ConsoleThemeStyle.Invalid] = "\x1b[33;1m",
|
||||
[ConsoleThemeStyle.Null] = "\x1b[38;5;0038m",
|
||||
[ConsoleThemeStyle.Name] = "\x1b[38;5;0081m",
|
||||
[ConsoleThemeStyle.String] = "\x1b[38;5;0216m",
|
||||
[ConsoleThemeStyle.Number] = "\x1b[38;5;151m",
|
||||
[ConsoleThemeStyle.Boolean] = "\x1b[38;5;0038m",
|
||||
[ConsoleThemeStyle.Scalar] = "\x1b[38;5;0079m",
|
||||
[ConsoleThemeStyle.LevelVerbose] = "\x1b[37m",
|
||||
[ConsoleThemeStyle.LevelDebug] = "\x1b[37m",
|
||||
[ConsoleThemeStyle.LevelInformation] = "\x1b[32m\x1b[48;5;0238m",
|
||||
[ConsoleThemeStyle.LevelWarning] = "\x1b[38;5;0229m",
|
||||
[ConsoleThemeStyle.LevelError] = "\x1b[38;5;0197m\x1b[48;5;0238m",
|
||||
[ConsoleThemeStyle.LevelFatal] = "\x1b[38;5;0197m\x1b[48;5;0238m",
|
||||
});
|
||||
}
|
@@ -9,8 +9,15 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
/// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="sourceLyric">原始歌词数据。</param>
|
||||
/// <param name="translateLyric">翻译歌词数据。</param>
|
||||
/// <returns>构建完成的 <see cref="LyricItemCollection"/> 对象。</returns>
|
||||
LyricItemCollection Build(string sourceLyric, string translateLyric = null);
|
||||
LyricItemCollection Build(string sourceLyric);
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="sourceLyric">原始歌词数据。</param>
|
||||
/// <param name="translationLyric">翻译歌词数据。</param>
|
||||
/// <returns>构建完成的 <see cref="LyricItemCollection"/> 对象。</returns>
|
||||
LyricItemCollection Build(string sourceLyric, string translationLyric);
|
||||
}
|
||||
}
|
@@ -4,17 +4,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
|
||||
{
|
||||
public class GetLyricAccessKeyRequest
|
||||
{
|
||||
[JsonProperty("ver")]
|
||||
public int UnknownParameters1 { get; }
|
||||
[JsonProperty("ver")] public int UnknownParameters1 { get; }
|
||||
|
||||
[JsonProperty("man")]
|
||||
public string UnknownParameters2 { get; }
|
||||
[JsonProperty("man")] public string UnknownParameters2 { get; }
|
||||
|
||||
[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)
|
||||
{
|
||||
|
@@ -5,22 +5,17 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
|
||||
{
|
||||
public class GetLyricAccessKeyResponse
|
||||
{
|
||||
[JsonProperty("status")]
|
||||
public int Status { get; set; }
|
||||
[JsonProperty("status")] public int Status { get; set; }
|
||||
|
||||
[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
|
||||
{
|
||||
[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; }
|
||||
}
|
||||
}
|
@@ -6,14 +6,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
|
||||
{
|
||||
public class SongSearchRequest
|
||||
{
|
||||
[JsonProperty("filter")]
|
||||
public int Filter { get; }
|
||||
[JsonProperty("filter")] public int Filter { get; }
|
||||
|
||||
[JsonProperty("platform")]
|
||||
public string Platform { get; }
|
||||
[JsonProperty("platform")] public string Platform { get; }
|
||||
|
||||
[JsonProperty("keyword")]
|
||||
public string Keyword { get; }
|
||||
[JsonProperty("keyword")] public string Keyword { get; }
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName)
|
||||
{
|
||||
|
@@ -5,23 +5,18 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
|
||||
{
|
||||
public class SongSearchResponse
|
||||
{
|
||||
[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
|
||||
{
|
||||
[JsonProperty("lists")]
|
||||
public List<SongSearchResponseSongDetail> List { get; set; }
|
||||
[JsonProperty("lists")] public List<SongSearchResponseSongDetail> List { get; set; }
|
||||
}
|
||||
|
||||
public class SongSearchResponseSongDetail
|
||||
|
@@ -90,7 +90,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
|
||||
public static bool operator ==(LyricItem left, LyricItem right)
|
||||
{
|
||||
return (int?) left?.SortScore == (int?) right?.SortScore;
|
||||
return (int?)left?.SortScore == (int?)right?.SortScore;
|
||||
}
|
||||
|
||||
public static bool operator !=(LyricItem item1, LyricItem item2)
|
||||
@@ -113,7 +113,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((LyricItem) obj);
|
||||
return Equals((LyricItem)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Extensions;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
@@ -16,9 +17,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
/// </summary>
|
||||
public bool IsPruneMusic => Count == 0;
|
||||
|
||||
public LyricItemCollectionOption Option { get; private set; }
|
||||
public LyricConfigOption Option { get; private set; }
|
||||
|
||||
public LyricItemCollection(LyricItemCollectionOption option)
|
||||
public LyricItemCollection(LyricConfigOption option)
|
||||
{
|
||||
Option = option;
|
||||
}
|
||||
@@ -33,7 +34,6 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
var option = left.Option;
|
||||
var newCollection = new LyricItemCollection(option);
|
||||
var indexDiff = left.Count - right.Count;
|
||||
|
||||
if (!option.IsOneLine)
|
||||
{
|
||||
left.ForEach(item => newCollection.Add(item));
|
||||
@@ -74,10 +74,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
|
||||
// 遍历未处理的歌词项,将其添加到返回集合当中。
|
||||
var leftWaitProcessIndex = leftMarkDict
|
||||
.Where(item => item.Value)
|
||||
.Where(item => !item.Value)
|
||||
.Select(pair => pair.Key);
|
||||
var rightWaitProcessIndex = rightMarkDict
|
||||
.Where(item => item.Value)
|
||||
.Where(item => !item.Value)
|
||||
.Select(pair => pair.Key);
|
||||
|
||||
leftWaitProcessIndex.Foreach(index => newCollection.Add(left[index]));
|
||||
@@ -97,7 +97,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
private static Dictionary<int, bool> BuildMarkDictionary(LyricItemCollection items)
|
||||
{
|
||||
return items
|
||||
.Select((item, index) => new {index, item})
|
||||
.Select((item, index) => new { index, item })
|
||||
.ToDictionary(item => item.index, item => false);
|
||||
}
|
||||
|
||||
@@ -107,5 +107,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
ForEach(lyric => lyricBuilder.Append(lyric).Append(Option.LineBreak));
|
||||
return lyricBuilder.ToString().TrimEnd(Option.LineBreak);
|
||||
}
|
||||
|
||||
public byte[] GetUtf8Bytes()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,21 +17,47 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public LyricItemCollection Build(string sourceLyric, string translateLyric = null)
|
||||
public LyricItemCollection Build(string sourceLyric)
|
||||
{
|
||||
var items = new LyricItemCollection(_options.LyricOption);
|
||||
var lyric = new LyricItemCollection(_options.Provider.Lyric.Config);
|
||||
if (string.IsNullOrEmpty(sourceLyric))
|
||||
{
|
||||
return items;
|
||||
return lyric;
|
||||
}
|
||||
|
||||
var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?");
|
||||
foreach (Match match in regex.Matches(sourceLyric))
|
||||
InternalBuildLyricObject(lyric, sourceLyric);
|
||||
|
||||
return lyric;
|
||||
}
|
||||
|
||||
public LyricItemCollection Build(string sourceLyric, string translationLyric)
|
||||
{
|
||||
items.Add(new LyricItem(match.Value));
|
||||
var lyric = new LyricItemCollection(_options.Provider.Lyric.Config);
|
||||
if (string.IsNullOrEmpty(sourceLyric))
|
||||
{
|
||||
return lyric;
|
||||
}
|
||||
|
||||
return items;
|
||||
lyric = InternalBuildLyricObject(lyric, sourceLyric);
|
||||
|
||||
if (_options.Provider.Lyric.Config.IsEnableTranslation && !string.IsNullOrEmpty(translationLyric))
|
||||
{
|
||||
var translatedLyric = InternalBuildLyricObject(new LyricItemCollection(_options.Provider.Lyric.Config), translationLyric);
|
||||
return lyric + translatedLyric;
|
||||
}
|
||||
|
||||
return lyric;
|
||||
}
|
||||
|
||||
private LyricItemCollection InternalBuildLyricObject(LyricItemCollection lyric, string sourceText)
|
||||
{
|
||||
var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?");
|
||||
foreach (Match match in regex.Matches(sourceText))
|
||||
{
|
||||
lyric.Add(new LyricItem(match.Value));
|
||||
}
|
||||
|
||||
return lyric;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Lyric
|
||||
{
|
||||
public class LyricItemCollectionOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 双语歌词是否合并为一行。
|
||||
/// </summary>
|
||||
public bool IsOneLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 换行符格式,取值来自 <see cref="LineBreakType"/> 常量类。
|
||||
/// </summary>
|
||||
public string LineBreak { get; set; } = LineBreakType.Windows;
|
||||
|
||||
public static readonly LyricItemCollectionOption NullInstance = new();
|
||||
}
|
||||
}
|
@@ -59,9 +59,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
await ValueTask.CompletedTask;
|
||||
|
||||
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data));
|
||||
if (json?.OriginalLyric == null)
|
||||
if (json?.OriginalLyric == null || string.IsNullOrEmpty(json.OriginalLyric.Text))
|
||||
{
|
||||
return new LyricItemCollection(LyricItemCollectionOption.NullInstance);
|
||||
return new LyricItemCollection(null);
|
||||
}
|
||||
|
||||
if (json.OriginalLyric.Text.Contains("纯音乐,请欣赏"))
|
||||
@@ -71,7 +71,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
|
||||
|
||||
return _lyricItemCollectionFactory.Build(
|
||||
json.OriginalLyric.Text,
|
||||
json.TranslationLyric?.Text);
|
||||
json.TranslationLyric.Text);
|
||||
}
|
||||
|
||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricDownloaderArgs args)
|
||||
|
@@ -6,70 +6,26 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
|
||||
{
|
||||
public class SongSearchRequest
|
||||
{
|
||||
[JsonProperty("ct")] public int UnknownParameter1 { get; set; }
|
||||
[JsonProperty("format")]
|
||||
public string Format { get; protected set; }
|
||||
|
||||
[JsonProperty("qqmusic_ver")] public int ClientVersion { get; set; }
|
||||
[JsonProperty("inCharset")]
|
||||
public string InCharset { get; protected set; }
|
||||
|
||||
[JsonProperty("new_json")] public int UnknownParameter2 { get; set; }
|
||||
[JsonProperty("outCharset")]
|
||||
public string OutCharset { get; protected set; }
|
||||
|
||||
[JsonProperty("remoteplace")] public string RemotePlace { get; set; }
|
||||
[JsonProperty("platform")]
|
||||
public string Platform { get; protected set; }
|
||||
|
||||
[JsonProperty("t")] public int UnknownParameter3 { get; set; }
|
||||
|
||||
[JsonProperty("aggr")] public int UnknownParameter4 { get; set; }
|
||||
|
||||
[JsonProperty("cr")] public int UnknownParameter5 { get; set; }
|
||||
|
||||
[JsonProperty("catZhida")] public int UnknownParameter6 { get; set; }
|
||||
|
||||
[JsonProperty("lossless")] public int LossLess { get; set; }
|
||||
|
||||
[JsonProperty("flag_qc")] public int UnknownParameter7 { get; set; }
|
||||
|
||||
[JsonProperty("p")] public int Page { get; set; }
|
||||
|
||||
[JsonProperty("n")] public int Limit { get; set; }
|
||||
|
||||
[JsonProperty("w")] public string Keyword { get; set; }
|
||||
|
||||
[JsonProperty("g_tk")] public int UnknownParameter8 { get; set; }
|
||||
|
||||
[JsonProperty("hostUin")] public int UnknownParameter9 { get; set; }
|
||||
|
||||
[JsonProperty("format")] public string ResultFormat { get; set; }
|
||||
|
||||
[JsonProperty("inCharset")] public string InCharset { get; set; }
|
||||
|
||||
[JsonProperty("outCharset")] public string OutCharset { get; set; }
|
||||
|
||||
[JsonProperty("notice")] public int UnknownParameter10 { get; set; }
|
||||
|
||||
[JsonProperty("platform")] public string Platform { get; set; }
|
||||
|
||||
[JsonProperty("needNewCode")] public int UnknownParameter11 { get; set; }
|
||||
[JsonProperty("key")]
|
||||
public string Keyword { get; protected set; }
|
||||
|
||||
protected SongSearchRequest()
|
||||
{
|
||||
UnknownParameter1 = 24;
|
||||
ClientVersion = 1298;
|
||||
UnknownParameter2 = 1;
|
||||
RemotePlace = "txt.yqq.song";
|
||||
UnknownParameter3 = 0;
|
||||
UnknownParameter4 = 1;
|
||||
UnknownParameter5 = 1;
|
||||
UnknownParameter6 = 1;
|
||||
LossLess = 0;
|
||||
UnknownParameter7 = 0;
|
||||
Page = 1;
|
||||
Limit = 5;
|
||||
UnknownParameter8 = 5381;
|
||||
UnknownParameter9 = 0;
|
||||
ResultFormat = "json";
|
||||
InCharset = "utf8";
|
||||
OutCharset = "utf8";
|
||||
UnknownParameter10 = 0;
|
||||
Platform = "yqq";
|
||||
UnknownParameter11 = 0;
|
||||
Format = "json";
|
||||
InCharset = OutCharset = "utf-8";
|
||||
Platform = "yqq.json";
|
||||
}
|
||||
|
||||
public SongSearchRequest(string musicName, string artistName) : this()
|
||||
|
@@ -17,16 +17,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
|
||||
|
||||
public class QQMusicInnerSongModel
|
||||
{
|
||||
[JsonProperty("list")] public List<QQMusicInnerSongItem> SongItems { get; set; }
|
||||
[JsonProperty("itemlist")] public List<QQMusicInnerSongItem> SongItems { get; set; }
|
||||
}
|
||||
|
||||
public class QQMusicInnerSongItem
|
||||
{
|
||||
[JsonProperty("mid")] public string SongId { get; set; }
|
||||
}
|
||||
|
||||
public class AlbumInfo
|
||||
{
|
||||
[JsonProperty("id")] public long Id { get; set; }
|
||||
}
|
||||
}
|
@@ -17,7 +17,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic
|
||||
private readonly IWarpHttpClient _warpHttpClient;
|
||||
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
|
||||
|
||||
private const string QQSearchMusicUrl = @"https://c.y.qq.com/soso/fcgi-bin/client_search_cp";
|
||||
// private const string QQSearchMusicUrl = @"https://c.y.qq.com/soso/fcgi-bin/client_search_cp";
|
||||
private const string QQSearchMusicUrl = @"https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg";
|
||||
private const string QQGetLyricUrl = @"https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg";
|
||||
|
||||
private const string QQMusicRequestReferer = @"https://y.qq.com/";
|
||||
@@ -70,7 +71,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic
|
||||
|
||||
protected virtual void ValidateSongSearchResponse(SongSearchResponse response, LyricDownloaderArgs 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);
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
/// </summary>
|
||||
public class NcmMusicDecryptor : IMusicDecryptor, ITransientDependency
|
||||
{
|
||||
protected readonly byte[] AesCoreKey = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57};
|
||||
protected readonly byte[] AesModifyKey = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28};
|
||||
protected readonly byte[] AesCoreKey = { 0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57 };
|
||||
protected readonly byte[] AesModifyKey = { 0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28 };
|
||||
|
||||
public async Task<DecryptionResult> ConvertMusic(byte[] sourceBytes)
|
||||
{
|
||||
@@ -72,7 +72,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
{
|
||||
ExtensionObjects = new Dictionary<string, object>
|
||||
{
|
||||
{"JSON", musicInfoJson}
|
||||
{ "JSON", musicInfoJson }
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -105,10 +105,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
byte[] box = new byte[256];
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
box[i] = (byte) i;
|
||||
box[i] = (byte)i;
|
||||
}
|
||||
|
||||
byte keyLength = (byte) key.Length;
|
||||
byte keyLength = (byte)key.Length;
|
||||
byte c;
|
||||
byte lastByte = 0;
|
||||
byte keyOffset = 0;
|
||||
@@ -117,7 +117,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
swap = box[i];
|
||||
c = (byte) ((swap + lastByte + key[keyOffset++]) & 0xff);
|
||||
c = (byte)((swap + lastByte + key[keyOffset++]) & 0xff);
|
||||
|
||||
if (keyOffset >= keyLength)
|
||||
{
|
||||
@@ -169,7 +169,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var j = (byte) ((i + 1) & 0xff);
|
||||
var j = (byte)((i + 1) & 0xff);
|
||||
tb[i] ^= sBox[sBox[j] + sBox[(sBox[j] + j) & 0xff] & 0xff];
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,11 @@ namespace ZonyLrcTools.Cli.Infrastructure
|
||||
/// </summary>
|
||||
public string Artist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否下载成功?
|
||||
/// </summary>
|
||||
public bool IsSuccessful { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 构建一个新的 <see cref="MusicInfo"/> 对象。
|
||||
/// </summary>
|
||||
|
@@ -123,7 +123,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Network
|
||||
{
|
||||
HttpStatusCode.OK => responseString,
|
||||
HttpStatusCode.ServiceUnavailable => throw new ErrorCodeException(ErrorCodes.ServiceUnavailable),
|
||||
_ => throw new ErrorCodeException(ErrorCodes.HttpRequestFailed, attachObj: new {requestParameters, responseString})
|
||||
_ => throw new ErrorCodeException(ErrorCodes.HttpRequestFailed, attachObj: new { requestParameters, responseString })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Network
|
||||
/// <exception cref="ErrorCodeException">如果反序列化失败,则可能抛出本异常。</exception>
|
||||
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 });
|
||||
|
||||
try
|
||||
{
|
||||
|
@@ -8,16 +8,16 @@ namespace ZonyLrcTools.Cli.Infrastructure.Network
|
||||
/// <summary>
|
||||
/// 是否启用了网络代理功能。
|
||||
/// </summary>
|
||||
public bool Enable { get; set; }
|
||||
public bool IsEnable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代理服务器的 Ip。
|
||||
/// </summary>
|
||||
public string ProxyIp { get; set; }
|
||||
public string Ip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代理服务器的 端口。
|
||||
/// </summary>
|
||||
public int ProxyPort { get; set; }
|
||||
public int Port { get; set; }
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
|
||||
_wordsDictionary = new Lazy<Dictionary<string, string>>(() =>
|
||||
{
|
||||
var jsonData = File.ReadAllText(_options.BlockWordOptions.BlockWordDictionaryFile);
|
||||
var jsonData = File.ReadAllText(_options.Provider.Tag.BlockWord.FilePath);
|
||||
return JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData);
|
||||
});
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
@@ -15,40 +17,64 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
protected readonly IEnumerable<ITagInfoProvider> TagInfoProviders;
|
||||
protected readonly IBlockWordDictionary BlockWordDictionary;
|
||||
protected readonly ILogger<DefaultTagLoader> Logger;
|
||||
|
||||
protected ToolOptions Options;
|
||||
|
||||
private readonly IEnumerable<ITagInfoProvider> _sortedTagInfoProviders;
|
||||
|
||||
public DefaultTagLoader(IEnumerable<ITagInfoProvider> tagInfoProviders,
|
||||
IBlockWordDictionary blockWordDictionary,
|
||||
IOptions<ToolOptions> options)
|
||||
IOptions<ToolOptions> options,
|
||||
ILogger<DefaultTagLoader> logger)
|
||||
{
|
||||
TagInfoProviders = tagInfoProviders;
|
||||
BlockWordDictionary = blockWordDictionary;
|
||||
Logger = logger;
|
||||
Options = options.Value;
|
||||
|
||||
_sortedTagInfoProviders = GetTagInfoProviders();
|
||||
}
|
||||
|
||||
public virtual async ValueTask<MusicInfo> LoadTagAsync(string filePath)
|
||||
{
|
||||
foreach (var provider in _sortedTagInfoProviders)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = await provider.LoadAsync(filePath);
|
||||
if (info != null && !string.IsNullOrEmpty(info.Name))
|
||||
{
|
||||
HandleBlockWord(info);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogWarning($"{filePath} 没有找到正确的标签信息,请考虑调整正则表达式。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<ITagInfoProvider> GetTagInfoProviders()
|
||||
{
|
||||
if (!TagInfoProviders.Any())
|
||||
{
|
||||
throw new ErrorCodeException(ErrorCodes.LoadTagInfoProviderError);
|
||||
}
|
||||
|
||||
foreach (var provider in TagInfoProviders)
|
||||
{
|
||||
var info = await provider.LoadAsync(filePath);
|
||||
if (info != null)
|
||||
{
|
||||
HandleBlockWord(info);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Options.Provider.Tag.Plugin
|
||||
.Where(x => x.Priority != -1)
|
||||
.OrderBy(x => x.Priority)
|
||||
.Join(TagInfoProviders, x => x.Name, y => y.Name, (x, y) => y);
|
||||
}
|
||||
|
||||
protected void HandleBlockWord(MusicInfo info)
|
||||
{
|
||||
if (Options.BlockWordOptions.IsEnable)
|
||||
if (Options.Provider.Tag.BlockWord.IsEnable)
|
||||
{
|
||||
info.Name = BlockWordDictionary.GetValue(info.Name) ?? info.Name;
|
||||
info.Artist = BlockWordDictionary.GetValue(info.Name) ?? info.Artist;
|
||||
|
@@ -15,7 +15,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
public string Name => ConstantName;
|
||||
public const string ConstantName = "FileName";
|
||||
public const string RegularExpressionsOption = "RegularExpressions";
|
||||
public const string RegularExpressionsOption = "regularExpressions";
|
||||
|
||||
private readonly ToolOptions _options;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
await ValueTask.CompletedTask;
|
||||
|
||||
var regex = _options.TagInfoProviderOptions
|
||||
var regex = _options.Provider.Tag.Plugin
|
||||
.First(t => t.Name == ConstantName)
|
||||
.Extensions[RegularExpressionsOption];
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using YamlDotNet.Serialization;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
{
|
||||
public class TagInfoProviderInstance
|
||||
public class TagInfoProviderOption
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
@@ -10,4 +12,14 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
|
||||
|
||||
public Dictionary<string, string> Extensions { get; set; }
|
||||
}
|
||||
|
||||
public class TagOption
|
||||
{
|
||||
public IEnumerable<TagInfoProviderOption> Plugin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 屏蔽词功能相关配置。
|
||||
/// </summary>
|
||||
public BlockWordOption BlockWord { get; set; }
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -8,18 +8,24 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
using ZonyLrcTools.Cli.Commands;
|
||||
using ZonyLrcTools.Cli.Commands.SubCommand;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Exceptions;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Logging;
|
||||
|
||||
namespace ZonyLrcTools.Cli.Commands
|
||||
namespace ZonyLrcTools.Cli
|
||||
{
|
||||
[Command("lyric-tool")]
|
||||
[Subcommand(typeof(DownloadCommand),
|
||||
typeof(UtilityCommand))]
|
||||
public class ToolCommand : ToolCommandBase
|
||||
public class Program : ToolCommandBase
|
||||
{
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
ConfigureLogger();
|
||||
ConfigureErrorMessage();
|
||||
|
||||
@@ -52,7 +58,7 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Async(c => c.Console())
|
||||
.WriteTo.Async(c => c.Console(theme: CustomConsoleTheme.Code))
|
||||
.WriteTo.Logger(lc =>
|
||||
{
|
||||
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Warning)
|
||||
@@ -74,16 +80,16 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
{
|
||||
builder
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json");
|
||||
.AddYamlFile("config.yaml");
|
||||
})
|
||||
.ConfigureServices((_, services) =>
|
||||
{
|
||||
services.AddSingleton(PhysicalConsole.Singleton);
|
||||
services.BeginAutoDependencyInject<ToolCommand>();
|
||||
services.BeginAutoDependencyInject<Program>();
|
||||
services.ConfigureConfiguration();
|
||||
services.ConfigureToolService();
|
||||
})
|
||||
.RunCommandLineApplicationAsync<ToolCommand>(args);
|
||||
.RunCommandLineApplicationAsync<Program>(args);
|
||||
}
|
||||
|
||||
private static int HandleException(Exception ex)
|
||||
@@ -91,7 +97,8 @@ namespace ZonyLrcTools.Cli.Commands
|
||||
switch (ex)
|
||||
{
|
||||
case ErrorCodeException exception:
|
||||
Log.Logger.Error($"出现了未处理的异常,错误代码: {exception.ErrorCode},错误信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}\n调用栈:\n{exception.StackTrace}");
|
||||
Log.Logger.Error(
|
||||
$"出现了未处理的异常。\n错误代码: {exception.ErrorCode}\n错误信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}\n原始信息:{exception.Message}\n调用栈:{exception.StackTrace}");
|
||||
Environment.Exit(exception.ErrorCode);
|
||||
return exception.ErrorCode;
|
||||
case { } unknownException:
|
@@ -3,7 +3,8 @@
|
||||
"10001": "待搜索的后缀不能为空。",
|
||||
"10002": "需要扫描的目录不存在,请确认路径是否正确。",
|
||||
"10003": "不能获取文件的后缀信息。",
|
||||
"10004": "没有扫描到任何音乐文件。"
|
||||
"10004": "没有扫描到任何音乐文件。",
|
||||
"10005": "指定的编码不受支持,请检查配置,所有受支持的编码名称,请参考: https://docs.microsoft.com/en-us/dotnet/api/system.text.encodinginfo.codepage?view=net-6.0#system-text-encodinginfo-codepage。"
|
||||
},
|
||||
"Warning": {
|
||||
"50001": "扫描文件时出现了错误。",
|
||||
|
@@ -6,26 +6,25 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.0" />
|
||||
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.1" />
|
||||
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Refit" Version="6.1.15" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="6.1.15" />
|
||||
<PackageReference Include="Refit.Newtonsoft.Json" Version="6.1.15" />
|
||||
<PackageReference Include="Refit" Version="6.3.2" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />
|
||||
<PackageReference Include="Refit.Newtonsoft.Json" Version="6.3.2" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="appsettings.json" />
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Resources\error_msg.json" />
|
||||
<Content Include="Resources\error_msg.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
@@ -34,6 +33,10 @@
|
||||
<Content Include="BlockWords.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="config.yaml" />
|
||||
<Content Include="config.yaml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"ToolOption": {
|
||||
"SupportFileExtensions": "*.mp3;*.flac;*.wav",
|
||||
"NetworkOptions": {
|
||||
"Enable": false,
|
||||
"ProxyIp": "127.0.0.1",
|
||||
"ProxyPort": 4780
|
||||
},
|
||||
"TagInfoProviderOptions": [
|
||||
{
|
||||
"Name": "Taglib",
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Name": "FileName",
|
||||
"Priority": 2,
|
||||
"Extensions": {
|
||||
"RegularExpressions": "(?'artist'.+)\\s-\\s(?'name'.+)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"LyricDownloaderOptions": [
|
||||
{
|
||||
"Name": "NetEase",
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Name": "QQ",
|
||||
"Priority": 2
|
||||
},
|
||||
{
|
||||
"Name": "KuGou",
|
||||
"Priority": 3
|
||||
}
|
||||
],
|
||||
"LyricOption": {
|
||||
"IsOneLine": true,
|
||||
"LineBreak": "\n"
|
||||
},
|
||||
"BlockWordOptions": {
|
||||
"IsEnable": false,
|
||||
"BlockWordDictionaryFile": "BlockWords.json"
|
||||
}
|
||||
}
|
||||
}
|
48
src/ZonyLrcTools.Cli/config.yaml
Normal file
48
src/ZonyLrcTools.Cli/config.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
globalOption:
|
||||
# 允许扫描的歌曲文件后缀名。
|
||||
supportFileExtensions:
|
||||
- '*.mp3'
|
||||
- '*.flac'
|
||||
- '*.wav'
|
||||
# 网络代理服务设置,仅支持 HTTP 代理。
|
||||
networkOptions:
|
||||
isEnable: false # 是否启用代理。
|
||||
ip: 127.0.0.1 # 代理服务 IP 地址。
|
||||
port: 4780 # 代理服务端口号。
|
||||
|
||||
# 下载器的相关参数配置。
|
||||
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 时禁用。
|
||||
- name: QQ # 基于 QQ 音乐的歌词下载器。
|
||||
priority: 2
|
||||
- name: KuGou # 基于酷狗音乐的歌词下载器。
|
||||
priority: 3
|
||||
# 歌词下载的一些共有配置参数。
|
||||
config:
|
||||
isOneLine: true # 双语歌词是否合并为一行展示。
|
||||
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
|
||||
isEnableTranslation: true # 是否启用翻译歌词。
|
||||
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
|
||||
fileEncoding: 'utf-8' # 歌词文件的编码格式。
|
3
src/ui/.browserslistrc
Normal file
3
src/ui/.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
23
src/ui/.gitignore
vendored
Normal file
23
src/ui/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
19
src/ui/README.md
Normal file
19
src/ui/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# ui
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
src/ui/babel.config.js
Normal file
5
src/ui/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
19
src/ui/jsconfig.json
Normal file
19
src/ui/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
23
src/ui/package.json
Normal file
23
src/ui/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"buefy": "^0.9.20",
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
}
|
||||
}
|
BIN
src/ui/public/favicon.ico
Normal file
BIN
src/ui/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
17
src/ui/public/index.html
Normal file
17
src/ui/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
5
src/ui/src/App.vue
Normal file
5
src/ui/src/App.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
BIN
src/ui/src/assets/logo.png
Normal file
BIN
src/ui/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
12
src/ui/src/main.js
Normal file
12
src/ui/src/main.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
15
src/ui/src/router/index.js
Normal file
15
src/ui/src/router/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [{
|
||||
path: '/', name: 'home', component: HomeView
|
||||
}]
|
||||
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
17
src/ui/src/store/index.js
Normal file
17
src/ui/src/store/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
}
|
||||
})
|
12
src/ui/src/views/HomeView.vue
Normal file
12
src/ui/src/views/HomeView.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
|
||||
export default {
|
||||
name: 'HomeView'
|
||||
}
|
||||
</script>
|
4
src/ui/vue.config.js
Normal file
4
src/ui/vue.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true
|
||||
})
|
5518
src/ui/yarn.lock
Normal file
5518
src/ui/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,8 @@ namespace ZonyLrcTools.Tests
|
||||
public async Task ScanAsync_Test()
|
||||
{
|
||||
var tempMusicFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Temp.mp3");
|
||||
File.Create(tempMusicFilePath);
|
||||
var fs = File.Create(tempMusicFilePath);
|
||||
fs.Close();
|
||||
|
||||
var fileScanner = ServiceProvider.GetRequiredService<IFileScanner>();
|
||||
var result = await fileScanner.ScanAsync(
|
||||
|
@@ -12,7 +12,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Exceptions
|
||||
ErrorCodeHelper.LoadErrorMessage();
|
||||
|
||||
ErrorCodeHelper.ErrorMessages.ShouldNotBeNull();
|
||||
ErrorCodeHelper.ErrorMessages.Count.ShouldBe(11);
|
||||
ErrorCodeHelper.ErrorMessages.Count.ShouldBe(15);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZonyLrcTools.Cli.Config;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
|
||||
namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
@@ -9,7 +10,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
[Fact]
|
||||
public void LyricCollectionLineBreak_Test()
|
||||
{
|
||||
var lyricObject = new LyricItemCollection(new LyricItemCollectionOption
|
||||
var lyricObject = new LyricItemCollection(new LyricConfigOption
|
||||
{
|
||||
IsOneLine = false,
|
||||
LineBreak = LineBreakType.MacOs
|
||||
|
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZonyLrcTools.Cli.Infrastructure.Lyric;
|
||||
@@ -42,5 +41,41 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
lyric.ShouldNotBeNull();
|
||||
lyric.IsPruneMusic.ShouldBe(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadAsync_Issue84_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("太空彈", "01");
|
||||
lyric.ShouldNotBeNull();
|
||||
lyric.IsPruneMusic.ShouldBe(false);
|
||||
}
|
||||
|
||||
// About the new feature mentioned in issue #87.
|
||||
// Github Issue: https://github.com/real-zony/ZonyLrcToolsX/issues/87
|
||||
[Fact]
|
||||
public async Task DownloadAsync_Issue85_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("Looking at Me", "Sabrina Carpenter");
|
||||
|
||||
lyric.ShouldNotBeNull();
|
||||
lyric.IsPruneMusic.ShouldBeFalse();
|
||||
lyric.ToString().ShouldContain("你看起来失了呼吸");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloaderAsync_Issue88_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("茫茫草原", "姚璎格");
|
||||
|
||||
lyric.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownIssue_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("主題歌Arrietty's Song", "Cécile Corbel");
|
||||
|
||||
lyric.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,9 +21,21 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
|
||||
[Fact]
|
||||
public async Task DownloadAsync_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("Hollow", "Janet Leon");
|
||||
var lyric = await _lyricDownloader.DownloadAsync("东风破", "周杰伦");
|
||||
lyric.ShouldNotBeNull();
|
||||
lyric.IsPruneMusic.ShouldBe(false);
|
||||
}
|
||||
|
||||
// About the new feature mentioned in issue #87.
|
||||
// Github Issue: https://github.com/real-zony/ZonyLrcToolsX/issues/87
|
||||
[Fact]
|
||||
public async Task DownloadAsync_Issue85_Test()
|
||||
{
|
||||
var lyric = await _lyricDownloader.DownloadAsync("Looking at Me", "Sabrina Carpenter");
|
||||
|
||||
lyric.ShouldNotBeNull();
|
||||
lyric.IsPruneMusic.ShouldBeFalse();
|
||||
lyric.ToString().ShouldContain("你好像快要不能呼吸");
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,8 +34,8 @@ namespace ZonyLrcTools.Tests.Infrastructure.Network
|
||||
public async Task GetAsyncWithProxy_Test()
|
||||
{
|
||||
var option = ServiceProvider.GetRequiredService<IOptions<ToolOptions>>();
|
||||
option.Value.NetworkOptions.ProxyIp = "127.0.0.1";
|
||||
option.Value.NetworkOptions.ProxyPort = 4780;
|
||||
option.Value.NetworkOptions.Ip = "127.0.0.1";
|
||||
option.Value.NetworkOptions.Port = 4780;
|
||||
|
||||
var client = ServiceProvider.GetRequiredService<IWarpHttpClient>();
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Shouldly;
|
||||
@@ -14,7 +15,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Tag
|
||||
var tagLoader = ServiceProvider.GetRequiredService<ITagLoader>();
|
||||
|
||||
tagLoader.ShouldNotBeNull();
|
||||
var info = await tagLoader.LoadTagAsync(@"D:\はるまきごはん 煮ル果実 くらげP 蜂屋ななし じん かいりきベア - ダンスロボットダンス (アレンジメドレー (キメラver) はるまきごはん×煮ル果実×和田たけあき×栗山夕璃(蜂屋.flac");
|
||||
var info = await tagLoader.LoadTagAsync(Path.Combine(Directory.GetCurrentDirectory(), "MusicFiles", "曾经艺也 - 荀彧(纯音乐版).mp3"));
|
||||
info.ShouldNotBeNull();
|
||||
info.Name.ShouldBe("荀彧(纯音乐版)");
|
||||
info.Artist.ShouldBe("曾经艺也");
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZonyLrcTools.Cli;
|
||||
using ZonyLrcTools.Cli.Commands;
|
||||
using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
|
||||
|
||||
@@ -20,7 +21,7 @@ namespace ZonyLrcTools.Tests
|
||||
{
|
||||
var service = new ServiceCollection();
|
||||
|
||||
service.BeginAutoDependencyInject<ToolCommand>();
|
||||
service.BeginAutoDependencyInject<Program>();
|
||||
service.ConfigureToolService();
|
||||
service.ConfigureConfiguration();
|
||||
|
||||
|
@@ -7,14 +7,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
Reference in New Issue
Block a user