95 Commits

Author SHA1 Message Date
real-zony
ba91108ca4 fix: Fixed jobs variable share problem. 2022-09-22 19:45:34 +08:00
real-zony
f597e24ff7 ci: Fixed path issue. 2022-09-22 19:17:21 +08:00
real-zony
d453d520d4 ci: debug workflow. 2022-09-22 18:50:27 +08:00
real-zony
9f254aae8e ci: debug workflow. 2022-09-22 18:50:11 +08:00
real-zony
c9fa0de95f ci: Build new version. 2022-09-22 18:44:42 +08:00
real-zony
6d7ee04b74 feat: Support song depth search. 2022-09-22 18:43:42 +08:00
real-zony
f519eb1251 feat(Test NetEaseMusic's Romance lyrics.): 2022-09-22 18:08:26 +08:00
real-zony
85f325b300 fix: Fixed null reference exception. 2022-09-22 17:53:19 +08:00
real-zony
140043db79 feat: Enhanced song matching logic (NetEaseMusic). 2022-09-22 17:49:54 +08:00
real-zony
c583a9c278 build: Upgrade dependency nuget packages. 2022-09-22 16:11:53 +08:00
real-zony
8385f6f118 feat: Provide the 'isOnlyOutputTranslation option to output only translated lyrics. 2022-09-22 15:48:20 +08:00
real-zony
be4380c744 fix: Fixed issue #106. 2022-09-22 15:30:28 +08:00
real-zony
87f6a98668 fix: Fixed the issue with #82.
Reason may be that the song name has brackets, causing the search to
failure.
2022-09-22 15:06:54 +08:00
real-zony
83fcc91fb8 ci: Fixed github workflow file errors. 2022-09-22 14:51:17 +08:00
real-zony
b4c229ccca ci: test release. 2022-09-22 14:45:36 +08:00
real-zony
71302c7262 Merge branch 'dev' of https://github.com/real-zony/ZonyLrcToolsX into dev 2022-09-22 14:43:13 +08:00
real-zony
1bec50e409 ci: update workflow. 2022-09-22 14:42:48 +08:00
Zony
f7cae8062c Update release.md 2022-09-22 14:28:37 +08:00
real-zony
c73f0d8eea ci: debug workflow. 2022-09-22 14:28:15 +08:00
real-zony
fe7c9115d5 ci: debug workflow. 2022-09-22 14:24:45 +08:00
real-zony
e13fb47aeb ci: debug workflow. 2022-09-22 13:30:34 +08:00
real-zony
32891d4208 ci: debug workflow. 2022-09-22 13:27:08 +08:00
real-zony
770d246787 ci: debug workflow. 2022-09-22 13:22:59 +08:00
real-zony
589151a77d ci: debug workflow. 2022-09-22 13:21:07 +08:00
real-zony
65af048f72 ci: debug workflow. 2022-09-22 13:12:49 +08:00
real-zony
99fd3b5021 ci: debug workflow. 2022-09-22 13:09:41 +08:00
real-zony
e4420e749a ci: debug workflow. 2022-09-22 13:02:33 +08:00
real-zony
5f22dafb1e ci: debug workflow. 2022-09-22 13:01:03 +08:00
real-zony
c2d8ee4341 ci: debug workflow. 2022-09-22 12:57:14 +08:00
real-zony
6a4435eb0d ci: debug workflow. 2022-09-22 12:51:50 +08:00
real-zony
346e07e9e7 ci: debug workflow. 2022-09-22 12:49:42 +08:00
real-zony
efdb3e8441 ci: debug workflow. 2022-09-22 12:45:25 +08:00
real-zony
0438433284 ci: debug workflow. 2022-09-22 12:40:52 +08:00
real-zony
d34a830660 ci: debug workflow. 2022-09-22 12:39:44 +08:00
real-zony
975f61a8e7 ci: debug workflow. 2022-09-22 12:37:37 +08:00
real-zony
8ba78c2681 ci: debug workflows. 2022-09-22 12:35:16 +08:00
real-zony
019bdb571c ci: debug workflow. 2022-09-22 12:31:23 +08:00
real-zony
a2aeed021d ci: debug workflow. 2022-09-22 12:28:15 +08:00
real-zony
0e8d361ed9 ci: debug workflow. 2022-09-22 12:24:27 +08:00
real-zony
c3b381a5f7 ci: debug workflow. 2022-09-22 12:23:09 +08:00
real-zony
8acba586b0 ci: debug workflow. 2022-09-22 12:12:16 +08:00
real-zony
5905d4b384 ci: debug workflow. 2022-09-22 12:03:06 +08:00
real-zony
2ad771e62e ci: debug workflow. 2022-09-22 11:49:11 +08:00
real-zony
403d7b151d ci: debug workflow. 2022-09-22 11:47:03 +08:00
real-zony
5c8c9af9ab ci: debug workflow. 2022-09-22 11:40:09 +08:00
real-zony
bfd23fc14e ci: debug workflow. 2022-09-22 11:39:01 +08:00
real-zony
81271a5aa5 ci: debug workflow. 2022-09-22 11:36:17 +08:00
real-zony
80a0bcdcee ci: debug workflow. 2022-09-22 11:34:14 +08:00
real-zony
172174d9e3 ci: debug github actions. 2022-09-21 23:00:29 +08:00
real-zony
d4a6a46078 feat: Restore config yaml file. 2022-07-25 20:12:19 +08:00
real-zony
0772f5888f fix: Fix the problem of QQ lyrics downloader prompting error.
The reason for this is that the old API has been deprecated.
2022-07-25 20:11:30 +08:00
real-zony
950652c040 fix: Fixed the issue of inaccurate download results. 2022-07-25 19:45:35 +08:00
real-zony
55f720c1a1 Fixed the null reference exception prompted when downloading pure music. 2022-07-24 23:48:46 +08:00
real-zony
7463709a39 feat: Deleted unused components. 2022-04-28 23:58:03 +08:00
real-zony
07e660e13b Add new frontend code. 2022-04-28 23:53:14 +08:00
real-zony
a51b399a6c Delete frontend code. 2022-04-28 23:50:18 +08:00
real-zony
279eba48f8 feat: Support configure lyrics file encoding. 2022-04-27 21:38:51 +08:00
real-zony
f3b1dacb0c fix: Improve tag loader conditions. 2022-04-27 21:25:15 +08:00
real-zony
559efff928 style: remove ui project from solution file. 2022-04-26 10:28:52 +08:00
real-zony
8370172aef Ignore unit test. 2022-04-26 10:26:38 +08:00
real-zony
cb8572e599 fix: Fixed the number of error hints. 2022-04-26 10:25:53 +08:00
real-zony
3b0f9fa89b fix: Fixed an issue with netease music downloader. 2022-04-25 23:24:13 +08:00
real-zony
c655ac4cbb docs: Update readme doc. 2022-04-25 13:41:40 +08:00
Zony
893b1e7918 Create dotnet.yml 2022-04-25 13:35:55 +08:00
real-zony
e900a92f37 enhanced: change the console theme color. 2022-04-25 12:26:06 +08:00
real-zony
0f84621919 fix: Fixed multi-downloader not working problem. 2022-04-25 12:16:00 +08:00
real-zony
61ca863770 feat: Support skip exist lyric files. 2022-04-25 12:03:59 +08:00
real-zony
2044a0b8fa add web ui initialize code. 2022-04-23 21:11:18 +08:00
real-zony
ffd76f5f2b enhanced: Optimized tag reader workflow.
Closes #93
2022-04-12 10:15:23 +08:00
real-zony
3705c6c8b5 perf: Optimized error log information. 2022-04-12 09:57:49 +08:00
real-zony
f8531e82ff Merge remote-tracking branch 'origin/dev' into dev 2022-04-07 16:50:05 +08:00
real-zony
969d5e9e19 Fixed tag info provider bug. 2022-04-07 16:49:49 +08:00
Zony
88ed247430 Update README.md 2022-03-24 11:49:46 +08:00
real-zony
be68b3474c fix: Fixed the error in the count of failure times. 2022-03-22 22:11:02 +08:00
real-zony
363d104dbe refactor: Configuration class unified storage. 2022-03-20 19:27:26 +08:00
real-zony
a1e9669843 feat: Fixed a possible null reference exception. 2022-03-20 19:10:51 +08:00
real-zony
2b41658783 fix: Fixed the problem that the default line break is invalid. 2022-03-20 19:06:09 +08:00
real-zony
02f82e43d3 docs: Update readme document. 2022-03-20 11:05:28 +08:00
real-zony
dbe63c390f Merge remote-tracking branch 'origin/dev' into dev 2022-03-20 11:04:30 +08:00
real-zony
9f70d5d379 fix: Resolve possible NullReferenceException issues.
Closes https://github.com/real-zony/ZonyLrcToolsX/issues/85
2022-03-20 11:04:17 +08:00
Zony
8010333485 Update README.md 2022-03-19 22:47:47 +08:00
Zony
454cd58f97 Update README.md 2022-03-19 16:22:23 +08:00
real-zony
cf8d2f8515 feat: Add donation QR code. 2022-03-19 16:20:38 +08:00
real-zony
4b91c3e25b docs: Update readme. 2022-03-19 13:39:24 +08:00
real-zony
1840a10f1e fix: Fixed the problem of incorrect lyric data for dual language lyrics when the OneLine option is turned on. 2022-03-19 12:03:29 +08:00
real-zony
d250524208 feat: QQ Music Downloader support dual language lyrics. 2022-03-19 11:42:00 +08:00
real-zony
2c2c34f1a6 feat: NetEase Cloud Music Downloader support dual language lyrics. 2022-03-19 11:38:10 +08:00
real-zony
c380fd83f6 Delete unwanted files. 2022-03-19 11:01:16 +08:00
real-zony
e12264466e docs: Provides new documentation for profile instructions. 2022-03-18 21:11:44 +08:00
real-zony
2b0b14cd7a Use YAML as configuration file; fix a download failure issue with QQ Music. (Reason: QQ Music API changed) 2022-03-18 19:51:08 +08:00
real-zony
d1c50323b1 refactor: Change the file structure. 2022-03-17 22:01:06 +08:00
real-zony
e06f9eaaa9 build: Upgrade the dependent NuGet packages. 2022-03-17 20:18:57 +08:00
real-zony
ed1974dfbc refactor: Rename main method. 2022-02-11 11:52:35 +08:00
real-zony
ba3dd5f1bc refactor: reformat source code. 2022-02-03 18:10:53 +08:00
real-zony
089559d058 test: Try to reproduce the issue 84. 2022-02-03 17:17:49 +08:00
85 changed files with 6474 additions and 893 deletions

63
.gitattributes vendored
View File

@@ -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

61
.github/workflows/dotnet.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: .NET
on:
push:
branches: [ dev ]
# paths:
# - "versions/**"
pull_request:
branches: [ dev ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get build version
id: date
run: echo "::set-output name=date::$(date +'%Y%m%d')${{github.run_number}}"
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Publish
working-directory: ./src/ZonyLrcTools.Cli
run: |
ls -a
chmod +x ./publish.sh
./publish.sh
mv ./TempFiles ../../
shell: bash
env:
PUBLISH_VERSION: ${{ steps.date.outputs.date }}
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: release-files
path: |
./TempFiles
./versions/release.md
outputs:
version: ${{ steps.date.outputs.date }}
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: release-files
path: .
- name: Upload Release
uses: ncipollo/release-action@v1
with:
artifacts: "./TempFiles/*.zip"
token: ${{ secrets.GITHUBACTIONS }}
tag: ZonyLrcToolsX_Alpha.${{ needs.build.outputs.version }}
commit: dev
bodyFile: ./versions/release.md

452
.gitignore vendored
View File

@@ -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/

View File

@@ -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 分支。 🚧 如果你想查看可以工作的代码,请切换到 dev 分支。
## 下载
工具会执行每日构建动作,请访问 **[Release](https://github.com/real-zony/ZonyLrcToolsX/releases)** 页面进行下载。
## 用法 ## 用法
Windows 用户请在软件目录当中,按住 Shift + 右键呼出菜单,然后选择 PowerShell (部分用户可能显示的是 *命令提示符*),根据下述说明执行命令即可。 Windows 用户请在软件目录当中,按住 Shift + 右键呼出菜单,然后选择 PowerShell/命令提示符/Windows 终端,根据下述说明执行命令即可。
macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命令即可。 macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命令即可。
### 命令 ### 命令
#### 歌曲下载 #### 歌曲下载
@@ -36,6 +45,8 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命
#### 加密格式转换 #### 加密格式转换
子命令为 `util`,可用于转换部分加密歌曲,**仅供个人研究学习使用,思路与源码都来自于网络**。
目前软件支持 NCM、QCM(开发中...🚧) 格式的音乐文件转换,命令如下。 目前软件支持 NCM、QCM(开发中...🚧) 格式的音乐文件转换,命令如下。
```shell ```shell
@@ -44,19 +55,57 @@ macOS 和 Linux 用户请打开终端,切换到软件目录,一样执行命
### 配置文件 ### 配置文件
程序的部分配置信息需要`appsettings.json` 进行更改,下面标注了各个配置的说明。 程序的所有的配置信息,都`config.yaml` 进行更改,下面标注了各个配置的说明。
| 属性 | 说明 | 示例值 | ```yaml
| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------- | globalOption:
| ToolOption.SupportFileExtensions | 允许扫描的歌曲文件后缀名,以 `;` 号隔开多个后缀。 | `*.mp3;*.flac` | # 允许扫描的歌曲文件后缀名。
| ToolOption.NetworkOptions.Enable | 是否启用 HTTP 网络代理服务true 表示启用false 表示禁用。 | false | supportFileExtensions:
| ToolOption.NetworkOptions.ProxyIp | HTTP 网络代理服务的 IP`Enable` 为 false 时会忽略该属性值。 | 127.0.0.1 | - '*.mp3'
| ToolOption.NetworkOptions.ProxyPort | HTTP 网络代理服务的 端口,在 `Enable` 为 false 时会忽略该属性值。 | 8080 | - '*.flac'
| TagInfoProviderOptions.FileNameRegularExpressions | 文件名 Tag 标签信息读取器使用,使用正则表达式匹配歌曲名和歌手,请使用命名分组编写正则表达式。 | (?'artist'.+)\\s-\\s(?'name'.+) | - '*.wav'
| LyricDownloader.[n].Name | 指定歌词下载器的配置项标识,对应具体的歌词下载器。 | NetEase 或 QQ | # 网络代理服务设置,仅支持 HTTP 代理。
| LyricDownloader.[n].Priority | 指定歌词下载器的优先级,按升序排列,如果值设置为 `-1` 则代表禁用。 | `1` | networkOptions:
| BlockWordOptions.IsEnable | 是否启用屏蔽词词典。 | false | isEnable: false # 是否启用代理。
| BlockWordOptions.BlockWordDictionaryFile | 屏蔽词词典的位置。 | `./BlockWords.json` | 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"/>
## 路线图 ## 路线图

View File

@@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C29FB05C-54B
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AF8ADB2F-E46C-4DEE-8316-652D9FE1A69B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AF8ADB2F-E46C-4DEE-8316-652D9FE1A69B}"
EndProject 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}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZonyLrcTools.Cli", "src\ZonyLrcTools.Cli\ZonyLrcTools.Cli.csproj", "{55D74323-ABFA-4A73-A3BF-F3E8D5DE6DE8}"
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}"

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

BIN
docs/img/wechat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NAudio.Wave;
using ZonyLrcTools.Cli.Config; using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure; using ZonyLrcTools.Cli.Infrastructure;
using ZonyLrcTools.Cli.Infrastructure.Album; using ZonyLrcTools.Cli.Infrastructure.Album;
@@ -16,8 +18,9 @@ using ZonyLrcTools.Cli.Infrastructure.IO;
using ZonyLrcTools.Cli.Infrastructure.Lyric; using ZonyLrcTools.Cli.Infrastructure.Lyric;
using ZonyLrcTools.Cli.Infrastructure.Tag; using ZonyLrcTools.Cli.Infrastructure.Tag;
using ZonyLrcTools.Cli.Infrastructure.Threading; using ZonyLrcTools.Cli.Infrastructure.Threading;
using File = System.IO.File;
namespace ZonyLrcTools.Cli.Commands namespace ZonyLrcTools.Cli.Commands.SubCommand
{ {
[Command("download", Description = "下载歌词文件或专辑图像。")] [Command("download", Description = "下载歌词文件或专辑图像。")]
public class DownloadCommand : ToolCommandBase public class DownloadCommand : ToolCommandBase
@@ -49,7 +52,7 @@ namespace ZonyLrcTools.Cli.Commands
[Option("-d|--dir", CommandOptionType.SingleValue, Description = "指定需要扫描的目录。")] [Option("-d|--dir", CommandOptionType.SingleValue, Description = "指定需要扫描的目录。")]
[DirectoryExists] [DirectoryExists]
public string Directory { get; set; } public string SongsDirectory { get; set; }
[Option("-l|--lyric", CommandOptionType.NoValue, Description = "指定程序需要下载歌词文件。")] [Option("-l|--lyric", CommandOptionType.NoValue, Description = "指定程序需要下载歌词文件。")]
public bool DownloadLyric { get; set; } public bool DownloadLyric { get; set; }
@@ -60,21 +63,25 @@ namespace ZonyLrcTools.Cli.Commands
[Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")] [Option("-n|--number", CommandOptionType.SingleValue, Description = "指定下载时候的线程数量。(默认值 2)")]
public int ParallelNumber { get; set; } = 2; public int ParallelNumber { get; set; } = 2;
[Option] public string ErrorMessage { get; set; } = Path.Combine(Directory.GetCurrentDirectory(), "error.log");
#endregion #endregion
protected override async Task<int> OnExecuteAsync(CommandLineApplication app) protected override async Task<int> OnExecuteAsync(CommandLineApplication app)
{ {
var files = await ScanMusicFilesAsync();
var musicInfos = await LoadMusicInfoAsync(files);
if (DownloadLyric) if (DownloadLyric)
{ {
await DownloadLyricFilesAsync(musicInfos); await DownloadLyricFilesAsync(
await LoadMusicInfoAsync(
RemoveExistLyricFiles(
await ScanMusicFilesAsync())));
} }
if (DownloadAlbum) if (DownloadAlbum)
{ {
await DownloadAlbumAsync(musicInfos); await DownloadAlbumAsync(
await LoadMusicInfoAsync(
await ScanMusicFilesAsync()));
} }
return 0; return 0;
@@ -82,7 +89,7 @@ namespace ZonyLrcTools.Cli.Commands
private async Task<List<string>> ScanMusicFilesAsync() 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) .SelectMany(t => t.FilePaths)
.ToList(); .ToList();
@@ -97,13 +104,40 @@ namespace ZonyLrcTools.Cli.Commands
return files; 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) private async Task<ImmutableList<MusicInfo>> LoadMusicInfoAsync(IReadOnlyCollection<string> files)
{ {
_logger.LogInformation("开始加载音乐文件的标签信息..."); _logger.LogInformation("开始加载音乐文件的标签信息...");
var warpTask = new WarpTask(ParallelNumber); var warpTask = new WarpTask(ParallelNumber);
var warpTaskList = files.Select(file => warpTask.RunAsync(() => Task.Run(async () => await _tagLoader.LoadTagAsync(file)))); 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))
.ToList();
// Load music total time info.
result.Foreach(m => { m.TotalTime = (long?)new AudioFileReader(m.FilePath).TotalTime.TotalMilliseconds; });
_logger.LogInformation($"已成功加载 {files.Count} 个音乐文件的标签信息。"); _logger.LogInformation($"已成功加载 {files.Count} 个音乐文件的标签信息。");
@@ -112,7 +146,7 @@ namespace ZonyLrcTools.Cli.Commands
private IEnumerable<ILyricDownloader> GetLyricDownloaderList() private IEnumerable<ILyricDownloader> GetLyricDownloaderList()
{ {
var downloader = _options.LyricDownloaderOptions var downloader = _options.Provider.Lyric.Plugin
.Where(op => op.Priority != -1) .Where(op => op.Priority != -1)
.OrderBy(op => op.Priority) .OrderBy(op => op.Priority)
.Join(_lyricDownloaderList, .Join(_lyricDownloaderList,
@@ -123,7 +157,7 @@ namespace ZonyLrcTools.Cli.Commands
return downloader; return downloader;
} }
#region > < #region > Lyric download logic <
private async ValueTask DownloadLyricFilesAsync(ImmutableList<MusicInfo> musicInfos) private async ValueTask DownloadLyricFilesAsync(ImmutableList<MusicInfo> musicInfos)
{ {
@@ -136,63 +170,76 @@ namespace ZonyLrcTools.Cli.Commands
await Task.WhenAll(warpTaskList); 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) private async Task DownloadLyricTaskLogicAsync(IEnumerable<ILyricDownloader> downloaderList, MusicInfo info)
{ {
async Task<bool> InternalDownloadLogicAsync(ILyricDownloader downloader) async Task InternalDownloadLogicAsync(ILyricDownloader downloader)
{ {
_logger.LogMusicInfoWithInformation(info);
try try
{ {
var lyric = await downloader.DownloadAsync(info.Name, info.Artist); var lyric = await downloader.DownloadAsync(info.Name, info.Artist, info.TotalTime);
var filePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!, $"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc"); var lyricFilePath = Path.Combine(Path.GetDirectoryName(info.FilePath)!,
if (File.Exists(filePath)) $"{Path.GetFileNameWithoutExtension(info.FilePath)}.lrc");
if (File.Exists(lyricFilePath))
{ {
return true; File.Delete(lyricFilePath);
} }
info.IsSuccessful = true;
if (lyric.IsPruneMusic) if (lyric.IsPruneMusic)
{ {
return true; return;
} }
await using var stream = new FileStream(filePath, FileMode.Create); await using var stream = new FileStream(lyricFilePath, FileMode.Create);
await using var sw = new StreamWriter(stream); await using var sw = new BinaryWriter(stream);
await sw.WriteAsync(lyric.ToString());
await sw.FlushAsync(); sw.Write(EncodingConvert(lyric));
await stream.FlushAsync();
} }
catch (ErrorCodeException ex) catch (ErrorCodeException ex)
{ {
if (ex.ErrorCode == ErrorCodes.NoMatchingSong) info.IsSuccessful = ex.ErrorCode == ErrorCodes.NoMatchingSong;
{
return false;
}
_logger.LogWarningInfo(ex); _logger.LogWarningInfo(ex);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError($"下载歌词文件时发生错误:{ex.Message},歌曲名: {info.Name},歌手: {info.Artist}。"); _logger.LogError($"下载歌词文件时发生错误:{ex.Message},歌曲名: {info.Name},歌手: {info.Artist}。");
info.IsSuccessful = false;
} }
return true;
} }
foreach (var downloader in downloaderList) 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 #endregion
#region > < #region > Ablum image download logic <
private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos) private async ValueTask DownloadAlbumAsync(ImmutableList<MusicInfo> musicInfos)
{ {
@@ -205,12 +252,12 @@ namespace ZonyLrcTools.Cli.Commands
await Task.WhenAll(warpTaskList); 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) private async Task DownloadAlbumTaskLogicAsync(IAlbumDownloader downloader, MusicInfo info)
{ {
_logger.LogMusicInfoWithInformation(info); _logger.LogSuccessful(info);
try try
{ {

View File

@@ -11,7 +11,7 @@ using ZonyLrcTools.Cli.Infrastructure.IO;
using ZonyLrcTools.Cli.Infrastructure.MusicDecryption; using ZonyLrcTools.Cli.Infrastructure.MusicDecryption;
using ZonyLrcTools.Cli.Infrastructure.Threading; using ZonyLrcTools.Cli.Infrastructure.Threading;
namespace ZonyLrcTools.Cli.Commands namespace ZonyLrcTools.Cli.Commands.SubCommand
{ {
public enum SupportFileType public enum SupportFileType
{ {
@@ -53,7 +53,7 @@ namespace ZonyLrcTools.Cli.Commands
{ {
_logger.LogInformation("开始扫描文件夹,请稍等..."); _logger.LogInformation("开始扫描文件夹,请稍等...");
var files = (await _fileScanner.ScanAsync(FilePath, new[] {"*.ncm"})) var files = (await _fileScanner.ScanAsync(FilePath, new[] { "*.ncm" }))
.SelectMany(f => f.FilePaths) .SelectMany(f => f.FilePaths)
.ToList(); .ToList();
@@ -95,7 +95,7 @@ namespace ZonyLrcTools.Cli.Commands
// TODO: Large Object Issue!!!!! // TODO: Large Object Issue!!!!!
var result = await _musicDecryptor.ConvertMusic(memoryStream.ToArray()); var result = await _musicDecryptor.ConvertMusic(memoryStream.ToArray());
var newFileName = Path.Combine(Path.GetDirectoryName(filePath), 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 using var musicFileStream = File.Create(newFileName);
await musicFileStream.WriteAsync(result.Data); await musicFileStream.WriteAsync(result.Data);

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils;

View File

@@ -1,4 +1,4 @@
namespace ZonyLrcTools.Cli.Infrastructure.Tag namespace ZonyLrcTools.Cli.Config
{ {
/// <summary> /// <summary>
/// 屏蔽词选项类。 /// 屏蔽词选项类。
@@ -13,6 +13,6 @@
/// <summary> /// <summary>
/// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。 /// 屏蔽词字典文件,用于替换歌曲名或者歌手名称。
/// </summary> /// </summary>
public string BlockWordDictionaryFile { get; set; } public string FilePath { get; set; }
} }
} }

View File

@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
using ZonyLrcTools.Cli.Infrastructure.Lyric;
namespace ZonyLrcTools.Cli.Config;
public class LyricOption
{
public IEnumerable<LyricProviderOption> Plugin { get; set; }
public LyricConfigOption Config { get; set; }
public LyricProviderOption GetLyricProviderOption(string name)
{
return Plugin.FirstOrDefault(x => x.Name == name);
}
}
public class LyricConfigOption
{
/// <summary>
/// 双语歌词是否合并为一行。
/// </summary>
public bool IsOneLine { get; set; } = false;
/// <summary>
/// 换行符格式,取值来自 <see cref="LineBreakType"/> 常量类。
/// </summary>
public string LineBreak { get; set; } = LineBreakType.Windows;
/// <summary>
/// 是否启用歌词翻译功能。
/// </summary>
public bool IsEnableTranslation { get; set; } = true;
/// <summary>
/// 如果歌词文件已经存在,是否跳过这些文件
/// </summary>
public bool IsSkipExistLyricFiles { get; set; } = false;
/// <summary>
/// 歌词文件的编码格式。
/// </summary>
public string FileEncoding { get; set; } = "utf-8";
/// <summary>
/// 是否只输出翻译歌词。
/// </summary>
public bool IsOnlyOutputTranslation { get; set; } = false;
}

View File

@@ -1,6 +1,6 @@
namespace ZonyLrcTools.Cli.Infrastructure.Lyric namespace ZonyLrcTools.Cli.Config
{ {
public class LyricDownloaderOption public class LyricProviderOption
{ {
/// <summary> /// <summary>
/// 歌词下载器的唯一标识。 /// 歌词下载器的唯一标识。
@@ -11,5 +11,10 @@
/// 歌词下载时的优先级,当值为 -1 时是禁用。 /// 歌词下载时的优先级,当值为 -1 时是禁用。
/// </summary> /// </summary>
public int Priority { get; set; } public int Priority { get; set; }
/// <summary>
/// 搜索深度,值越大搜索结果越多,但搜索时间越长。
/// </summary>
public int Depth { get; set; }
} }
} }

View File

@@ -0,0 +1,16 @@
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; }
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using YamlDotNet.Serialization;
using ZonyLrcTools.Cli.Infrastructure.Lyric; using ZonyLrcTools.Cli.Infrastructure.Lyric;
using ZonyLrcTools.Cli.Infrastructure.Network; using ZonyLrcTools.Cli.Infrastructure.Network;
using ZonyLrcTools.Cli.Infrastructure.Tag; using ZonyLrcTools.Cli.Infrastructure.Tag;
@@ -8,19 +9,9 @@ namespace ZonyLrcTools.Cli.Config
public class ToolOptions public class ToolOptions
{ {
/// <summary> /// <summary>
/// 支持的音乐文件后缀集合,以 ; 进行分隔 /// 支持的音乐文件后缀集合。
/// </summary> /// </summary>
public string SupportFileExtensions { get; set; } public List<string> SupportFileExtensions { get; set; }
/// <summary>
/// 歌词下载相关的配置信息。
/// </summary>
public LyricItemCollectionOption LyricOption { get; set; }
/// <summary>
/// 标签加载器相关的配置属性。
/// </summary>
public IEnumerable<TagInfoProviderInstance> TagInfoProviderOptions { get; set; }
/// <summary> /// <summary>
/// 网络代理相关的配置信息。 /// 网络代理相关的配置信息。
@@ -28,13 +19,8 @@ namespace ZonyLrcTools.Cli.Config
public NetworkOptions NetworkOptions { get; set; } public NetworkOptions NetworkOptions { get; set; }
/// <summary> /// <summary>
/// 歌词下载器相关配置属性 /// 定义下载器相关配置信息
/// </summary> /// </summary>
public IEnumerable<LyricDownloaderOption> LyricDownloaderOptions { get; set; } public ProviderOption Provider { get; set; }
/// <summary>
/// 屏蔽词功能相关配置。
/// </summary>
public BlockWordOption BlockWordOptions { get; set; }
} }
} }

View File

@@ -44,14 +44,14 @@ namespace ZonyLrcTools.Cli.Infrastructure.Album.NetEase
true, true,
_defaultOption); _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); throw new ErrorCodeException(ErrorCodes.NoMatchingSong);
} }
var songDetailJsonStr = await _warpHttpClient.GetAsync( var songDetailJsonStr = await _warpHttpClient.GetAsync(
GetMusicInfoApi, GetMusicInfoApi,
new GetSongDetailsRequest(searchResult.GetFirstMatchSongId(songName)), new GetSongDetailsRequest(searchResult.GetFirstMatchSongId(songName, null)),
_defaultOption); _defaultOption);
var url = JObject.Parse(songDetailJsonStr).SelectToken("$.songs[0].album.picUrl")?.Value<string>(); var url = JObject.Parse(songDetailJsonStr).SelectToken("$.songs[0].album.picUrl")?.Value<string>();

View File

@@ -39,7 +39,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Album.QQMusic
SearchApi, SearchApi,
requestParameter, _defaultOption); requestParameter, _defaultOption);
return new byte[] {0x1, 0x2}; return new byte[] { 0x1, 0x2 };
} }
} }
} }

View File

@@ -31,8 +31,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.DependencyInject
return new HttpClientHandler return new HttpClientHandler
{ {
UseProxy = option.NetworkOptions.Enable, UseProxy = option.NetworkOptions.IsEnable,
Proxy = new WebProxy($"{option.NetworkOptions.ProxyIp}:{option.NetworkOptions.ProxyPort}") Proxy = new WebProxy($"{option.NetworkOptions.Ip}:{option.NetworkOptions.Port}")
}; };
}); });
@@ -46,10 +46,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.DependencyInject
{ {
var configuration = new ConfigurationBuilder() var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json") .AddYamlFile("config.yaml")
.Build(); .Build();
services.Configure<ToolOptions>(configuration.GetSection("ToolOption")); services.Configure<ToolOptions>(configuration.GetSection("globalOption"));
return services; return services;
} }

View File

@@ -27,6 +27,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Exceptions
/// </summary> /// </summary>
public const int NoFilesWereScanned = 10004; public const int NoFilesWereScanned = 10004;
/// <summary>
/// 文本: 指定的编码不受支持,请检查配置,所有受支持的编码名称。
/// </summary>
public const int NotSupportedFileEncoding = 10005;
#endregion #endregion
#region > < #region > <

View File

@@ -41,13 +41,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Extensions
} }
/// <summary> /// <summary>
/// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲信息。 /// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲下载成功信息。
/// </summary> /// </summary>
/// <param name="logger">日志记录器的实例。</param> /// <param name="logger">日志记录器的实例。</param>
/// <param name="musicInfo">需要打印的歌曲信息。</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}, 下载成功.");
} }
} }
} }

View File

@@ -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",
});
}

View File

@@ -12,8 +12,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
/// </summary> /// </summary>
/// <param name="songName">歌曲的名称。</param> /// <param name="songName">歌曲的名称。</param>
/// <param name="artist">歌曲的作者。</param> /// <param name="artist">歌曲的作者。</param>
/// <param name="duration">歌曲的时长。</param>
/// <returns>歌曲的歌词数据对象。</returns> /// <returns>歌曲的歌词数据对象。</returns>
ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist); ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist, long? duration = null);
/// <summary> /// <summary>
/// 下载器的名称。 /// 下载器的名称。

View File

@@ -9,8 +9,15 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
/// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。 /// 根据指定的歌曲数据构建新的 <see cref="LyricItemCollection"/> 实例。
/// </summary> /// </summary>
/// <param name="sourceLyric">原始歌词数据。</param> /// <param name="sourceLyric">原始歌词数据。</param>
/// <param name="translateLyric">翻译歌词数据。</param>
/// <returns>构建完成的 <see cref="LyricItemCollection"/> 对象。</returns> /// <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);
} }
} }

View File

@@ -4,17 +4,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
{ {
public class GetLyricAccessKeyRequest public class GetLyricAccessKeyRequest
{ {
[JsonProperty("ver")] [JsonProperty("ver")] public int UnknownParameters1 { get; }
public int UnknownParameters1 { get; }
[JsonProperty("man")] [JsonProperty("man")] public string UnknownParameters2 { get; }
public string UnknownParameters2 { get; }
[JsonProperty("client")] [JsonProperty("client")] public string UnknownParameters3 { get; }
public string UnknownParameters3 { get; }
[JsonProperty("hash")] [JsonProperty("hash")] public string FileHash { get; }
public string FileHash { get; }
public GetLyricAccessKeyRequest(string fileHash) public GetLyricAccessKeyRequest(string fileHash)
{ {

View File

@@ -5,22 +5,17 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
{ {
public class GetLyricAccessKeyResponse public class GetLyricAccessKeyResponse
{ {
[JsonProperty("status")] [JsonProperty("status")] public int Status { get; set; }
public int Status { get; set; }
[JsonProperty("errcode")] [JsonProperty("errcode")] public int ErrorCode { get; set; }
public int ErrorCode { get; set; }
[JsonProperty("candidates")] [JsonProperty("candidates")] public List<GetLyricAccessKeyDataObject> AccessKeyDataObjects { get; set; }
public List<GetLyricAccessKeyDataObject> AccessKeyDataObjects { get; set; }
} }
public class GetLyricAccessKeyDataObject public class GetLyricAccessKeyDataObject
{ {
[JsonProperty("accesskey")] [JsonProperty("accesskey")] public string AccessKey { get; set; }
public string AccessKey { get; set; }
[JsonProperty("id")] [JsonProperty("id")] public string Id { get; set; }
public string Id { get; set; }
} }
} }

View File

@@ -6,20 +6,23 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
{ {
public class SongSearchRequest public class SongSearchRequest
{ {
[JsonProperty("filter")] [JsonProperty("filter")] public int Filter { get; }
public int Filter { get; }
[JsonProperty("platform")] [JsonProperty("platform")] public string Platform { get; }
public string Platform { get; }
[JsonProperty("keyword")] [JsonProperty("keyword")] public string Keyword { get; }
public string Keyword { get; }
public SongSearchRequest(string musicName, string artistName) [JsonProperty("pagesize")] public int PageSize { get; }
[JsonProperty("page")] public int Page { get; }
public SongSearchRequest(string musicName, string artistName, int pageSize = 30)
{ {
Filter = 2; Filter = 2;
Platform = "WebFilter"; Platform = "WebFilter";
Keyword = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8); Keyword = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
PageSize = pageSize;
Page = 1;
} }
} }
} }

View File

@@ -5,23 +5,18 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel
{ {
public class SongSearchResponse public class SongSearchResponse
{ {
[JsonProperty("status")] [JsonProperty("status")] public int Status { get; set; }
public int Status { get; set; }
[JsonProperty("data")] [JsonProperty("data")] public SongSearchResponseInnerData Data { get; set; }
public SongSearchResponseInnerData Data { get; set; }
[JsonProperty("error_code")] [JsonProperty("error_code")] public int ErrorCode { get; set; }
public int ErrorCode { get; set; }
[JsonProperty("error_msg")] [JsonProperty("error_msg")] public string ErrorMessage { get; set; }
public string ErrorMessage { get; set; }
} }
public class SongSearchResponseInnerData public class SongSearchResponseInnerData
{ {
[JsonProperty("lists")] [JsonProperty("lists")] public List<SongSearchResponseSongDetail> List { get; set; }
public List<SongSearchResponseSongDetail> List { get; set; }
} }
public class SongSearchResponseSongDetail public class SongSearchResponseSongDetail

View File

@@ -1,7 +1,9 @@
using System; using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure.Exceptions; using ZonyLrcTools.Cli.Infrastructure.Exceptions;
using ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel; using ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou.JsonModel;
using ZonyLrcTools.Cli.Infrastructure.Network; using ZonyLrcTools.Cli.Infrastructure.Network;
@@ -14,22 +16,25 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.KuGou
private readonly IWarpHttpClient _warpHttpClient; private readonly IWarpHttpClient _warpHttpClient;
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory; private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
private readonly ToolOptions _options;
private const string KuGouSearchMusicUrl = @"https://songsearch.kugou.com/song_search_v2"; private const string KuGouSearchMusicUrl = @"https://songsearch.kugou.com/song_search_v2";
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 KuGourLyricDownloader(IWarpHttpClient warpHttpClient, public KuGourLyricDownloader(IWarpHttpClient warpHttpClient,
ILyricItemCollectionFactory lyricItemCollectionFactory) ILyricItemCollectionFactory lyricItemCollectionFactory,
IOptions<ToolOptions> options)
{ {
_warpHttpClient = warpHttpClient; _warpHttpClient = warpHttpClient;
_lyricItemCollectionFactory = lyricItemCollectionFactory; _lyricItemCollectionFactory = lyricItemCollectionFactory;
_options = options.Value;
} }
protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args) protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
{ {
var searchResult = await _warpHttpClient.GetAsync<SongSearchResponse>(KuGouSearchMusicUrl, var searchResult = await _warpHttpClient.GetAsync<SongSearchResponse>(KuGouSearchMusicUrl,
new SongSearchRequest(args.SongName, args.Artist)); new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth));
ValidateSongSearchResponse(searchResult, args); ValidateSongSearchResponse(searchResult, args);

View File

@@ -16,10 +16,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
/// </summary> /// </summary>
/// <param name="songName">歌曲名称。</param> /// <param name="songName">歌曲名称。</param>
/// <param name="artist">歌曲作者/艺术家。</param> /// <param name="artist">歌曲作者/艺术家。</param>
/// <param name="duration">歌曲的时长。</param>
/// <returns>下载完成的歌曲数据。</returns> /// <returns>下载完成的歌曲数据。</returns>
public virtual async ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist) public virtual async ValueTask<LyricItemCollection> DownloadAsync(string songName, string artist, long? duration = null)
{ {
var args = new LyricDownloaderArgs(songName, artist); var args = new LyricDownloaderArgs(songName, artist, duration ?? 0);
await ValidateAsync(args); await ValidateAsync(args);
var downloadDataBytes = await DownloadDataAsync(args); var downloadDataBytes = await DownloadDataAsync(args);
return await GenerateLyricAsync(downloadDataBytes, args); return await GenerateLyricAsync(downloadDataBytes, args);

View File

@@ -6,10 +6,13 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
public string Artist { get; set; } public string Artist { get; set; }
public LyricDownloaderArgs(string songName, string artist) public long Duration { get; set; }
public LyricDownloaderArgs(string songName, string artist, long duration)
{ {
SongName = songName; SongName = songName;
Artist = artist; Artist = artist;
Duration = duration;
} }
} }
} }

View File

@@ -90,7 +90,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
public static bool operator ==(LyricItem left, LyricItem right) 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) 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(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true; if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false; if (obj.GetType() != this.GetType()) return false;
return Equals((LyricItem) obj); return Equals((LyricItem)obj);
} }
public override int GetHashCode() public override int GetHashCode()

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure.Extensions; using ZonyLrcTools.Cli.Infrastructure.Extensions;
namespace ZonyLrcTools.Cli.Infrastructure.Lyric namespace ZonyLrcTools.Cli.Infrastructure.Lyric
@@ -16,9 +17,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
/// </summary> /// </summary>
public bool IsPruneMusic => Count == 0; 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; Option = option;
} }
@@ -33,7 +34,6 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
var option = left.Option; var option = left.Option;
var newCollection = new LyricItemCollection(option); var newCollection = new LyricItemCollection(option);
var indexDiff = left.Count - right.Count; var indexDiff = left.Count - right.Count;
if (!option.IsOneLine) if (!option.IsOneLine)
{ {
left.ForEach(item => newCollection.Add(item)); left.ForEach(item => newCollection.Add(item));
@@ -74,10 +74,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
// 遍历未处理的歌词项,将其添加到返回集合当中。 // 遍历未处理的歌词项,将其添加到返回集合当中。
var leftWaitProcessIndex = leftMarkDict var leftWaitProcessIndex = leftMarkDict
.Where(item => item.Value) .Where(item => !item.Value)
.Select(pair => pair.Key); .Select(pair => pair.Key);
var rightWaitProcessIndex = rightMarkDict var rightWaitProcessIndex = rightMarkDict
.Where(item => item.Value) .Where(item => !item.Value)
.Select(pair => pair.Key); .Select(pair => pair.Key);
leftWaitProcessIndex.Foreach(index => newCollection.Add(left[index])); leftWaitProcessIndex.Foreach(index => newCollection.Add(left[index]));
@@ -97,7 +97,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
private static Dictionary<int, bool> BuildMarkDictionary(LyricItemCollection items) private static Dictionary<int, bool> BuildMarkDictionary(LyricItemCollection items)
{ {
return items return items
.Select((item, index) => new {index, item}) .Select((item, index) => new { index, item })
.ToDictionary(item => item.index, item => false); .ToDictionary(item => item.index, item => false);
} }
@@ -107,5 +107,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
ForEach(lyric => lyricBuilder.Append(lyric).Append(Option.LineBreak)); ForEach(lyric => lyricBuilder.Append(lyric).Append(Option.LineBreak));
return lyricBuilder.ToString().TrimEnd(Option.LineBreak); return lyricBuilder.ToString().TrimEnd(Option.LineBreak);
} }
public byte[] GetUtf8Bytes()
{
return Encoding.UTF8.GetBytes(ToString());
}
} }
} }

View File

@@ -17,21 +17,52 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric
_options = options.Value; _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)) if (string.IsNullOrEmpty(sourceLyric))
{ {
return items; return lyric;
} }
var regex = new Regex(@"\[\d+:\d+.\d+\].+\n?"); InternalBuildLyricObject(lyric, sourceLyric);
foreach (Match match in regex.Matches(sourceLyric))
return lyric;
}
public LyricItemCollection Build(string sourceLyric, string translationLyric)
{
var lyric = new LyricItemCollection(_options.Provider.Lyric.Config);
if (string.IsNullOrEmpty(sourceLyric))
{ {
items.Add(new LyricItem(match.Value)); 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);
if (_options.Provider.Lyric.Config.IsOnlyOutputTranslation)
{
return translatedLyric;
}
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;
} }
} }
} }

View File

@@ -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();
}
}

View File

@@ -8,9 +8,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
{ {
public GetLyricRequest(long songId) public GetLyricRequest(long songId)
{ {
OS = "osx"; OS = "ios";
Id = songId; Id = songId;
Lv = Kv = Tv = -1; Lv = Kv = Tv = Rv = -1;
} }
/// <summary> /// <summary>
@@ -30,5 +30,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
[JsonProperty("kv")] public int Kv { get; } [JsonProperty("kv")] public int Kv { get; }
[JsonProperty("tv")] public int Tv { get; } [JsonProperty("tv")] public int Tv { get; }
[JsonProperty("rv")] public int Rv { get; set; }
[JsonProperty("crypto")] public string Protocol { get; set; } = "api";
} }
} }

View File

@@ -22,6 +22,12 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
[JsonProperty("tlyric")] [JsonProperty("tlyric")]
public InnerLyric TranslationLyric { get; set; } public InnerLyric TranslationLyric { get; set; }
/// <summary>
/// 如果存在罗马音歌词,则本项内容为罗马音歌词。
/// </summary>
[JsonProperty("romalrc")]
public InnerLyric RomaLyric { get; set; }
/// <summary> /// <summary>
/// 状态码。 /// 状态码。
/// </summary> /// </summary>

View File

@@ -1,4 +1,5 @@
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Web; using System.Web;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -42,18 +43,25 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
[JsonProperty("limit")] [JsonProperty("limit")]
public int Limit { get; set; } public int Limit { get; set; }
[JsonProperty("crypto")] public string Crypto { get; set; } = "weapi";
public SongSearchRequest() public SongSearchRequest()
{ {
CsrfToken = string.Empty; CsrfToken = string.Empty;
Type = 1; Type = 1;
Offset = 0; Offset = 0;
IsTotal = true; IsTotal = true;
Limit = 5; Limit = 10;
} }
public SongSearchRequest(string musicName, string artistName) : this() public SongSearchRequest(string musicName, string artistName, int limit = 10) : this()
{ {
// Remove all the brackets and the content inside them.
var regex = new Regex(@"\([^)]*\)");
musicName = regex.Replace(musicName, string.Empty);
SearchKey = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8); SearchKey = HttpUtility.UrlEncode($"{musicName}+{artistName}", Encoding.UTF8);
Limit = limit;
} }
} }
} }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -10,10 +11,20 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
[JsonProperty("code")] public int StatusCode { get; set; } [JsonProperty("code")] public int StatusCode { get; set; }
public int GetFirstMatchSongId(string songName) public int GetFirstMatchSongId(string songName, long? duration)
{ {
var item = Items.SongItems.FirstOrDefault(x => x.Name == songName); var perfectMatch = Items.SongItems.FirstOrDefault(x => x.Name == songName);
return item?.Id ?? Items.SongItems[0].Id; if (perfectMatch != null)
{
return perfectMatch.Id;
}
if (duration is null or 0)
{
return Items.SongItems.First().Id;
}
return Items.SongItems.OrderBy(t => Math.Abs(t.Duration - duration.Value)).First().Id;
} }
} }
@@ -49,6 +60,12 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel
/// </summary> /// </summary>
[JsonProperty("album")] [JsonProperty("album")]
public SongAlbumModel Album { get; set; } public SongAlbumModel Album { get; set; }
/// <summary>
/// 歌曲的实际长度。
/// </summary>
[JsonProperty("duration")]
public long Duration { get; set; }
} }
public class SongArtistModel public class SongArtistModel

View File

@@ -2,7 +2,9 @@ using System;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure.Exceptions; using ZonyLrcTools.Cli.Infrastructure.Exceptions;
using ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel; using ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase.JsonModel;
using ZonyLrcTools.Cli.Infrastructure.Network; using ZonyLrcTools.Cli.Infrastructure.Network;
@@ -15,6 +17,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
private readonly IWarpHttpClient _warpHttpClient; private readonly IWarpHttpClient _warpHttpClient;
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory; private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory;
private readonly ToolOptions _options;
private const string NetEaseSearchMusicUrl = @"https://music.163.com/api/search/get/web"; private const string NetEaseSearchMusicUrl = @"https://music.163.com/api/search/get/web";
private const string NetEaseGetLyricUrl = @"https://music.163.com/api/song/lyric"; private const string NetEaseGetLyricUrl = @"https://music.163.com/api/song/lyric";
@@ -23,17 +26,19 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
private const string NetEaseRequestContentType = @"application/x-www-form-urlencoded"; private const string NetEaseRequestContentType = @"application/x-www-form-urlencoded";
public NetEaseLyricDownloader(IWarpHttpClient warpHttpClient, public NetEaseLyricDownloader(IWarpHttpClient warpHttpClient,
ILyricItemCollectionFactory lyricItemCollectionFactory) ILyricItemCollectionFactory lyricItemCollectionFactory,
IOptions<ToolOptions> options)
{ {
_warpHttpClient = warpHttpClient; _warpHttpClient = warpHttpClient;
_lyricItemCollectionFactory = lyricItemCollectionFactory; _lyricItemCollectionFactory = lyricItemCollectionFactory;
_options = options.Value;
} }
protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args) protected override async ValueTask<byte[]> DownloadDataAsync(LyricDownloaderArgs args)
{ {
var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>( var searchResult = await _warpHttpClient.PostAsync<SongSearchResponse>(
NetEaseSearchMusicUrl, NetEaseSearchMusicUrl,
new SongSearchRequest(args.SongName, args.Artist), new SongSearchRequest(args.SongName, args.Artist, _options.Provider.Lyric.GetLyricProviderOption(DownloaderName).Depth),
true, true,
msg => msg =>
{ {
@@ -48,7 +53,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
var lyricResponse = await _warpHttpClient.GetAsync( var lyricResponse = await _warpHttpClient.GetAsync(
NetEaseGetLyricUrl, NetEaseGetLyricUrl,
new GetLyricRequest(searchResult.GetFirstMatchSongId(args.SongName)), new GetLyricRequest(searchResult.GetFirstMatchSongId(args.SongName, args.Duration)),
msg => msg.Headers.Referrer = new Uri(NetEaseRequestReferer)); msg => msg.Headers.Referrer = new Uri(NetEaseRequestReferer));
return Encoding.UTF8.GetBytes(lyricResponse); return Encoding.UTF8.GetBytes(lyricResponse);
@@ -59,9 +64,9 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
await ValueTask.CompletedTask; await ValueTask.CompletedTask;
var json = JsonConvert.DeserializeObject<GetLyricResponse>(Encoding.UTF8.GetString(data)); 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("纯音乐,请欣赏")) if (json.OriginalLyric.Text.Contains("纯音乐,请欣赏"))
@@ -70,7 +75,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.NetEase
} }
return _lyricItemCollectionFactory.Build( return _lyricItemCollectionFactory.Build(
json.OriginalLyric.Text, json.OriginalLyric?.Text,
json.TranslationLyric?.Text); json.TranslationLyric?.Text);
} }

View File

@@ -6,70 +6,26 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
{ {
public class SongSearchRequest 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("key")]
public string Keyword { get; protected 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; }
protected SongSearchRequest() protected SongSearchRequest()
{ {
UnknownParameter1 = 24; Format = "json";
ClientVersion = 1298; InCharset = OutCharset = "utf-8";
UnknownParameter2 = 1; Platform = "yqq.json";
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;
} }
public SongSearchRequest(string musicName, string artistName) : this() public SongSearchRequest(string musicName, string artistName) : this()

View File

@@ -17,16 +17,11 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic.JsonModel
public class QQMusicInnerSongModel public class QQMusicInnerSongModel
{ {
[JsonProperty("list")] 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; }
} }
public class AlbumInfo
{
[JsonProperty("id")] public long Id { get; set; }
}
} }

View File

@@ -17,7 +17,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.Lyric.QQMusic
private readonly IWarpHttpClient _warpHttpClient; private readonly IWarpHttpClient _warpHttpClient;
private readonly ILyricItemCollectionFactory _lyricItemCollectionFactory; 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 QQGetLyricUrl = @"https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg";
private const string QQMusicRequestReferer = @"https://y.qq.com/"; 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) 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); throw new ErrorCodeException(ErrorCodes.TheReturnValueIsIllegal, attachObj: args);
} }

View File

@@ -14,8 +14,8 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
/// </summary> /// </summary>
public class NcmMusicDecryptor : IMusicDecryptor, ITransientDependency 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[] 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[] AesModifyKey = { 0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28 };
public async Task<DecryptionResult> ConvertMusic(byte[] sourceBytes) public async Task<DecryptionResult> ConvertMusic(byte[] sourceBytes)
{ {
@@ -72,7 +72,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
{ {
ExtensionObjects = new Dictionary<string, object> ExtensionObjects = new Dictionary<string, object>
{ {
{"JSON", musicInfoJson} { "JSON", musicInfoJson }
} }
}; };
} }
@@ -105,10 +105,10 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
byte[] box = new byte[256]; byte[] box = new byte[256];
for (int i = 0; i < 256; ++i) 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 c;
byte lastByte = 0; byte lastByte = 0;
byte keyOffset = 0; byte keyOffset = 0;
@@ -117,7 +117,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
for (int i = 0; i < 256; ++i) for (int i = 0; i < 256; ++i)
{ {
swap = box[i]; swap = box[i];
c = (byte) ((swap + lastByte + key[keyOffset++]) & 0xff); c = (byte)((swap + lastByte + key[keyOffset++]) & 0xff);
if (keyOffset >= keyLength) if (keyOffset >= keyLength)
{ {
@@ -169,7 +169,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.MusicDecryption
for (int i = 0; i < n; i++) 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]; tb[i] ^= sBox[sBox[j] + sBox[(sBox[j] + j) & 0xff] & 0xff];
} }

View File

@@ -1,3 +1,5 @@
using System;
namespace ZonyLrcTools.Cli.Infrastructure namespace ZonyLrcTools.Cli.Infrastructure
{ {
/// <summary> /// <summary>
@@ -10,6 +12,11 @@ namespace ZonyLrcTools.Cli.Infrastructure
/// </summary> /// </summary>
public string FilePath { get; } public string FilePath { get; }
/// <summary>
/// 歌曲的实际歌曲长度。
/// </summary>
public long? TotalTime { get; set; }
/// <summary> /// <summary>
/// 歌曲的名称。 /// 歌曲的名称。
/// </summary> /// </summary>
@@ -20,6 +27,11 @@ namespace ZonyLrcTools.Cli.Infrastructure
/// </summary> /// </summary>
public string Artist { get; set; } public string Artist { get; set; }
/// <summary>
/// 是否下载成功?
/// </summary>
public bool IsSuccessful { get; set; } = true;
/// <summary> /// <summary>
/// 构建一个新的 <see cref="MusicInfo"/> 对象。 /// 构建一个新的 <see cref="MusicInfo"/> 对象。
/// </summary> /// </summary>

View File

@@ -123,7 +123,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Network
{ {
HttpStatusCode.OK => responseString, HttpStatusCode.OK => responseString,
HttpStatusCode.ServiceUnavailable => throw new ErrorCodeException(ErrorCodes.ServiceUnavailable), 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> /// <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 });
try try
{ {

View File

@@ -8,16 +8,16 @@ namespace ZonyLrcTools.Cli.Infrastructure.Network
/// <summary> /// <summary>
/// 是否启用了网络代理功能。 /// 是否启用了网络代理功能。
/// </summary> /// </summary>
public bool Enable { get; set; } public bool IsEnable { get; set; }
/// <summary> /// <summary>
/// 代理服务器的 Ip。 /// 代理服务器的 Ip。
/// </summary> /// </summary>
public string ProxyIp { get; set; } public string Ip { get; set; }
/// <summary> /// <summary>
/// 代理服务器的 端口。 /// 代理服务器的 端口。
/// </summary> /// </summary>
public int ProxyPort { get; set; } public int Port { get; set; }
} }
} }

View File

@@ -21,7 +21,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
_wordsDictionary = new Lazy<Dictionary<string, string>>(() => _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); return JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData);
}); });
} }

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using ZonyLrcTools.Cli.Config; using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure.DependencyInject; using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
@@ -15,40 +17,64 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
{ {
protected readonly IEnumerable<ITagInfoProvider> TagInfoProviders; protected readonly IEnumerable<ITagInfoProvider> TagInfoProviders;
protected readonly IBlockWordDictionary BlockWordDictionary; protected readonly IBlockWordDictionary BlockWordDictionary;
protected readonly ILogger<DefaultTagLoader> Logger;
protected ToolOptions Options; protected ToolOptions Options;
private readonly IEnumerable<ITagInfoProvider> _sortedTagInfoProviders;
public DefaultTagLoader(IEnumerable<ITagInfoProvider> tagInfoProviders, public DefaultTagLoader(IEnumerable<ITagInfoProvider> tagInfoProviders,
IBlockWordDictionary blockWordDictionary, IBlockWordDictionary blockWordDictionary,
IOptions<ToolOptions> options) IOptions<ToolOptions> options,
ILogger<DefaultTagLoader> logger)
{ {
TagInfoProviders = tagInfoProviders; TagInfoProviders = tagInfoProviders;
BlockWordDictionary = blockWordDictionary; BlockWordDictionary = blockWordDictionary;
Logger = logger;
Options = options.Value; Options = options.Value;
_sortedTagInfoProviders = GetTagInfoProviders();
} }
public virtual async ValueTask<MusicInfo> LoadTagAsync(string filePath) 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()) if (!TagInfoProviders.Any())
{ {
throw new ErrorCodeException(ErrorCodes.LoadTagInfoProviderError); throw new ErrorCodeException(ErrorCodes.LoadTagInfoProviderError);
} }
foreach (var provider in TagInfoProviders) return Options.Provider.Tag.Plugin
{ .Where(x => x.Priority != -1)
var info = await provider.LoadAsync(filePath); .OrderBy(x => x.Priority)
if (info != null) .Join(TagInfoProviders, x => x.Name, y => y.Name, (x, y) => y);
{
HandleBlockWord(info);
return info;
}
}
return null;
} }
protected void HandleBlockWord(MusicInfo info) protected void HandleBlockWord(MusicInfo info)
{ {
if (Options.BlockWordOptions.IsEnable) if (Options.Provider.Tag.BlockWord.IsEnable)
{ {
info.Name = BlockWordDictionary.GetValue(info.Name) ?? info.Name; info.Name = BlockWordDictionary.GetValue(info.Name) ?? info.Name;
info.Artist = BlockWordDictionary.GetValue(info.Name) ?? info.Artist; info.Artist = BlockWordDictionary.GetValue(info.Name) ?? info.Artist;

View File

@@ -15,7 +15,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
{ {
public string Name => ConstantName; public string Name => ConstantName;
public const string ConstantName = "FileName"; public const string ConstantName = "FileName";
public const string RegularExpressionsOption = "RegularExpressions"; public const string RegularExpressionsOption = "regularExpressions";
private readonly ToolOptions _options; private readonly ToolOptions _options;
@@ -28,7 +28,7 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
{ {
await ValueTask.CompletedTask; await ValueTask.CompletedTask;
var regex = _options.TagInfoProviderOptions var regex = _options.Provider.Tag.Plugin
.First(t => t.Name == ConstantName) .First(t => t.Name == ConstantName)
.Extensions[RegularExpressionsOption]; .Extensions[RegularExpressionsOption];

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using YamlDotNet.Serialization;
using ZonyLrcTools.Cli.Config;
namespace ZonyLrcTools.Cli.Infrastructure.Tag namespace ZonyLrcTools.Cli.Infrastructure.Tag
{ {
public class TagInfoProviderInstance public class TagInfoProviderOption
{ {
public string Name { get; set; } public string Name { get; set; }
@@ -10,4 +12,14 @@ namespace ZonyLrcTools.Cli.Infrastructure.Tag
public Dictionary<string, string> Extensions { get; set; } public Dictionary<string, string> Extensions { get; set; }
} }
public class TagOption
{
public IEnumerable<TagInfoProviderOption> Plugin { get; set; }
/// <summary>
/// 屏蔽词功能相关配置。
/// </summary>
public BlockWordOption BlockWord { get; set; }
}
} }

View File

@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@@ -8,18 +8,24 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Serilog; using Serilog;
using Serilog.Events; 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.DependencyInject;
using ZonyLrcTools.Cli.Infrastructure.Exceptions; using ZonyLrcTools.Cli.Infrastructure.Exceptions;
using ZonyLrcTools.Cli.Infrastructure.Logging;
namespace ZonyLrcTools.Cli.Commands namespace ZonyLrcTools.Cli
{ {
[Command("lyric-tool")] [Command("lyric-tool")]
[Subcommand(typeof(DownloadCommand), [Subcommand(typeof(DownloadCommand),
typeof(UtilityCommand))] typeof(UtilityCommand))]
public class ToolCommand : ToolCommandBase public class Program : ToolCommandBase
{ {
public static async Task<int> Main(string[] args) public static async Task<int> Main(string[] args)
{ {
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
ConfigureLogger(); ConfigureLogger();
ConfigureErrorMessage(); ConfigureErrorMessage();
@@ -52,7 +58,7 @@ namespace ZonyLrcTools.Cli.Commands
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error) .MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Error)
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.Async(c => c.Console()) .WriteTo.Async(c => c.Console(theme: CustomConsoleTheme.Code))
.WriteTo.Logger(lc => .WriteTo.Logger(lc =>
{ {
lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Warning) lc.Filter.ByIncludingOnly(lc => lc.Level == LogEventLevel.Warning)
@@ -74,16 +80,16 @@ namespace ZonyLrcTools.Cli.Commands
{ {
builder builder
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json"); .AddYamlFile("config.yaml");
}) })
.ConfigureServices((_, services) => .ConfigureServices((_, services) =>
{ {
services.AddSingleton(PhysicalConsole.Singleton); services.AddSingleton(PhysicalConsole.Singleton);
services.BeginAutoDependencyInject<ToolCommand>(); services.BeginAutoDependencyInject<Program>();
services.ConfigureConfiguration(); services.ConfigureConfiguration();
services.ConfigureToolService(); services.ConfigureToolService();
}) })
.RunCommandLineApplicationAsync<ToolCommand>(args); .RunCommandLineApplicationAsync<Program>(args);
} }
private static int HandleException(Exception ex) private static int HandleException(Exception ex)
@@ -91,7 +97,8 @@ namespace ZonyLrcTools.Cli.Commands
switch (ex) switch (ex)
{ {
case ErrorCodeException exception: 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); Environment.Exit(exception.ErrorCode);
return exception.ErrorCode; return exception.ErrorCode;
case { } unknownException: case { } unknownException:

View File

@@ -3,7 +3,8 @@
"10001": "待搜索的后缀不能为空。", "10001": "待搜索的后缀不能为空。",
"10002": "需要扫描的目录不存在,请确认路径是否正确。", "10002": "需要扫描的目录不存在,请确认路径是否正确。",
"10003": "不能获取文件的后缀信息。", "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": { "Warning": {
"50001": "扫描文件时出现了错误。", "50001": "扫描文件时出现了错误。",

View File

@@ -6,26 +6,26 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.0" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.1" />
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.0" /> <PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="4.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="NAudio" Version="2.1.0" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Refit" Version="6.1.15" /> <PackageReference Include="Refit" Version="6.3.2" />
<PackageReference Include="Refit.HttpClientFactory" Version="6.1.15" /> <PackageReference Include="Refit.HttpClientFactory" Version="6.3.2" />
<PackageReference Include="Refit.Newtonsoft.Json" Version="6.1.15" /> <PackageReference Include="Refit.Newtonsoft.Json" Version="6.3.2" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" /> <PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="TagLibSharp" Version="2.2.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="appsettings.json" /> <None Remove="appsettings.json" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<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>
@@ -34,6 +34,10 @@
<Content Include="BlockWords.json"> <Content Include="BlockWords.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<None Remove="config.yaml" />
<Content Include="config.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -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"
}
}
}

View File

@@ -0,0 +1,52 @@
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 时禁用。
depth: 30 # 搜索深度,值越大搜索结果越多,但搜索时间越长。
- name: QQ # 基于 QQ 音乐的歌词下载器。
priority: 2
# depth: 10 # 暂时不支持。
- name: KuGou # 基于酷狗音乐的歌词下载器。
priority: 3
depth: 10
# 歌词下载的一些共有配置参数。
config:
isOneLine: true # 双语歌词是否合并为一行展示。
lineBreak: "\n" # 换行符的类型,记得使用双引号指定。
isEnableTranslation: true # 是否启用翻译歌词。
isOnlyOutputTranslation: false # 是否只输出翻译歌词。
isSkipExistLyricFiles: false # 如果歌词文件已经存在,是否跳过这些文件。
fileEncoding: 'utf-8' # 歌词文件的编码格式。

View File

@@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
read -r -p "请输入版本号:" Version
Platforms=('win-x64' 'linux-x64' 'osx-x64') Platforms=('win-x64' 'linux-x64' 'osx-x64')
if ! [ -d './TempFiles' ]; if ! [ -d './TempFiles' ];
@@ -11,11 +10,11 @@ rm -rf ./TempFiles/*
for platform in "${Platforms[@]}" for platform in "${Platforms[@]}"
do do
dotnet publish -r "$platform" -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true dotnet publish -r "$platform" -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true || exit 1
cd ./bin/Release/net5.0/"$platform"/publish/ cd ./bin/Release/net6.0/"$platform"/publish/ || exit 1
zip -r ./ZonyLrcTools_"$platform"_"$Version".zip ./ zip -r ./ZonyLrcTools_"$platform"_"${PUBLISH_VERSION}".zip ./ || exit 1
cd ../../../../../ cd ../../../../../ || exit 1
mv ./bin/Release/net5.0/"$platform"/publish/ZonyLrcTools_"$platform"_"$Version".zip ./TempFiles mv ./bin/Release/net6.0/"$platform"/publish/ZonyLrcTools_"$platform"_"$PUBLISH_VERSION".zip ./TempFiles
done done

3
src/ui/.browserslistrc Normal file
View File

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

23
src/ui/.gitignore vendored Normal file
View 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
View 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
View File

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

19
src/ui/jsconfig.json Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
src/ui/public/index.html Normal file
View 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
View File

@@ -0,0 +1,5 @@
<template>
<div id="app">
<router-view/>
</div>
</template>

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
View 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')

View 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
View 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: {
}
})

View 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
View File

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

5518
src/ui/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,8 @@ namespace ZonyLrcTools.Tests
public async Task ScanAsync_Test() public async Task ScanAsync_Test()
{ {
var tempMusicFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Temp.mp3"); var tempMusicFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Temp.mp3");
File.Create(tempMusicFilePath); var fs = File.Create(tempMusicFilePath);
fs.Close();
var fileScanner = ServiceProvider.GetRequiredService<IFileScanner>(); var fileScanner = ServiceProvider.GetRequiredService<IFileScanner>();
var result = await fileScanner.ScanAsync( var result = await fileScanner.ScanAsync(

View File

@@ -12,7 +12,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Exceptions
ErrorCodeHelper.LoadErrorMessage(); ErrorCodeHelper.LoadErrorMessage();
ErrorCodeHelper.ErrorMessages.ShouldNotBeNull(); ErrorCodeHelper.ErrorMessages.ShouldNotBeNull();
ErrorCodeHelper.ErrorMessages.Count.ShouldBe(11); ErrorCodeHelper.ErrorMessages.Count.ShouldBe(15);
} }
[Fact] [Fact]

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Shouldly; using Shouldly;
using Xunit; using Xunit;

View File

@@ -1,5 +1,6 @@
using Shouldly; using Shouldly;
using Xunit; using Xunit;
using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure.Lyric; using ZonyLrcTools.Cli.Infrastructure.Lyric;
namespace ZonyLrcTools.Tests.Infrastructure.Lyric namespace ZonyLrcTools.Tests.Infrastructure.Lyric
@@ -9,7 +10,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
[Fact] [Fact]
public void LyricCollectionLineBreak_Test() public void LyricCollectionLineBreak_Test()
{ {
var lyricObject = new LyricItemCollection(new LyricItemCollectionOption var lyricObject = new LyricItemCollection(new LyricConfigOption
{ {
IsOneLine = false, IsOneLine = false,
LineBreak = LineBreakType.MacOs LineBreak = LineBreakType.MacOs

View File

@@ -2,8 +2,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
using ZonyLrcTools.Cli.Config;
using ZonyLrcTools.Cli.Infrastructure.Lyric; using ZonyLrcTools.Cli.Infrastructure.Lyric;
namespace ZonyLrcTools.Tests.Infrastructure.Lyric namespace ZonyLrcTools.Tests.Infrastructure.Lyric
@@ -40,7 +42,60 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
{ {
var lyric = await _lyricDownloader.DownloadAsync("シンデレラ (Giga First Night Remix)", "DECO 27 ギガP"); var lyric = await _lyricDownloader.DownloadAsync("シンデレラ (Giga First Night Remix)", "DECO 27 ギガP");
lyric.ShouldNotBeNull(); lyric.ShouldNotBeNull();
lyric.IsPruneMusic.ShouldBe(true); lyric.IsPruneMusic.ShouldBe(false);
}
[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();
}
[Fact]
public async Task DownloaderAsync_Issue101_Test()
{
var lyric = await _lyricDownloader.DownloadAsync("君への嘘", "VALSHE");
lyric.ShouldNotBeEmpty();
}
[Fact]
public async Task DownloadAsync_Issue114_Test()
{
var options = ServiceProvider.GetRequiredService<IOptions<ToolOptions>>();
options.Value.Provider.Lyric.Config.IsOnlyOutputTranslation = true;
var lyric = await _lyricDownloader.DownloadAsync("Bones", "Image Dragons");
lyric.ToString().ShouldNotContain("Gimme, gimme, gimme some time to think");
} }
} }
} }

View File

@@ -21,9 +21,21 @@ namespace ZonyLrcTools.Tests.Infrastructure.Lyric
[Fact] [Fact]
public async Task DownloadAsync_Test() public async Task DownloadAsync_Test()
{ {
var lyric = await _lyricDownloader.DownloadAsync("Hollow", "Janet Leon"); var lyric = await _lyricDownloader.DownloadAsync("东风破", "周杰伦");
lyric.ShouldNotBeNull(); lyric.ShouldNotBeNull();
lyric.IsPruneMusic.ShouldBe(false); 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("你好像快要不能呼吸");
}
} }
} }

View File

@@ -34,8 +34,8 @@ namespace ZonyLrcTools.Tests.Infrastructure.Network
public async Task GetAsyncWithProxy_Test() public async Task GetAsyncWithProxy_Test()
{ {
var option = ServiceProvider.GetRequiredService<IOptions<ToolOptions>>(); var option = ServiceProvider.GetRequiredService<IOptions<ToolOptions>>();
option.Value.NetworkOptions.ProxyIp = "127.0.0.1"; option.Value.NetworkOptions.Ip = "127.0.0.1";
option.Value.NetworkOptions.ProxyPort = 4780; option.Value.NetworkOptions.Port = 4780;
var client = ServiceProvider.GetRequiredService<IWarpHttpClient>(); var client = ServiceProvider.GetRequiredService<IWarpHttpClient>();

View File

@@ -1,3 +1,4 @@
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Shouldly; using Shouldly;
@@ -14,7 +15,7 @@ namespace ZonyLrcTools.Tests.Infrastructure.Tag
var tagLoader = ServiceProvider.GetRequiredService<ITagLoader>(); var tagLoader = ServiceProvider.GetRequiredService<ITagLoader>();
tagLoader.ShouldNotBeNull(); 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.ShouldNotBeNull();
info.Name.ShouldBe("荀彧(纯音乐版)"); info.Name.ShouldBe("荀彧(纯音乐版)");
info.Artist.ShouldBe("曾经艺也"); info.Artist.ShouldBe("曾经艺也");

View File

@@ -1,5 +1,6 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ZonyLrcTools.Cli;
using ZonyLrcTools.Cli.Commands; using ZonyLrcTools.Cli.Commands;
using ZonyLrcTools.Cli.Infrastructure.DependencyInject; using ZonyLrcTools.Cli.Infrastructure.DependencyInject;
@@ -20,7 +21,7 @@ namespace ZonyLrcTools.Tests
{ {
var service = new ServiceCollection(); var service = new ServiceCollection();
service.BeginAutoDependencyInject<ToolCommand>(); service.BeginAutoDependencyInject<Program>();
service.ConfigureToolService(); service.ConfigureToolService();
service.ConfigureConfiguration(); service.ConfigureConfiguration();

View File

@@ -7,14 +7,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Shouldly" Version="4.0.3" /> <PackageReference Include="Shouldly" Version="4.1.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </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> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

16
versions/release.md Normal file
View File

@@ -0,0 +1,16 @@
New Features:
- [[#114](https://github.com/real-zony/ZonyLrcToolsX/issues/114)] 下载双语歌词的时候,可以控制是否仅输出翻译歌词。
- [[6d7ee04](https://github.com/real-zony/ZonyLrcToolsX/commit/6d7ee04b741191b5526075cdd3c3fc2c5737880f)] 提供深度搜索选项,以便更加准确地匹配歌曲歌词,值越大结果越准确,但是搜索速度越慢。
Breaking Changes: None
Enhancement:
- 关于网易云歌词下载器在搜索歌曲名带括号的歌曲时,会搜索不到的情况,现在默认会移除掉歌曲名中的括号进行搜索以增强准确性。
- [[#100](https://github.com/real-zony/ZonyLrcToolsX/issues/100)] 优化歌曲查找逻辑,优先匹配长度符合实际歌曲长度的歌词。
Fixed Bugs:
- [[#106](https://github.com/real-zony/ZonyLrcToolsX/issues/106)] 跳过已存在歌词的选项存在逻辑问题,会导致下载专辑图像的时候也会跳过带歌词的歌曲文件。
- [[85f325b](https://github.com/real-zony/ZonyLrcToolsX/commit/85f325b300dd4e6f54c783b2f9065ab4c566e3ad)] 修复某些极端情况下网易云歌词下载器报错的问题。