Compare commits
No commits in common. "beta" and "master" have entirely different histories.
6
.github/workflows/beta-pack.yml
vendored
6
.github/workflows/beta-pack.yml
vendored
@ -139,7 +139,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install python setuptools
|
||||
run: brew install python-setuptools
|
||||
run: |
|
||||
mkdir ~/.venv
|
||||
python3 -m venv ~/.venv
|
||||
source ~/.venv/bin/activate
|
||||
python3 -m pip install setuptools
|
||||
|
||||
- name: Get npm cache directory
|
||||
shell: bash
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -131,7 +131,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install python3 setuptools
|
||||
run: brew install python-setuptools
|
||||
run: |
|
||||
mkdir ~/.venv
|
||||
python3 -m venv ~/.venv
|
||||
source ~/.venv/bin/activate
|
||||
python3 -m pip install setuptools
|
||||
|
||||
- name: Get npm cache directory
|
||||
shell: bash
|
||||
|
||||
@ -10,9 +10,7 @@ module.exports = {
|
||||
'message2call',
|
||||
'@types/ws',
|
||||
'eslint',
|
||||
'@types/node',
|
||||
'electron-debug',
|
||||
'webpack-dev-server',
|
||||
// 'eslint-config-standard-with-typescript',
|
||||
// 'typescript', // https://github.com/microsoft/TypeScript/pull/54567
|
||||
],
|
||||
@ -33,6 +31,5 @@ module.exports = {
|
||||
// 'electron',
|
||||
// 'eslint',
|
||||
// 'electron-debug',
|
||||
// '@types/node',
|
||||
// ],
|
||||
}
|
||||
|
||||
26
README.md
26
README.md
@ -36,7 +36,7 @@
|
||||
|
||||
所用技术栈:
|
||||
|
||||
- Electron 30+
|
||||
- Electron 15+
|
||||
- Vue 3
|
||||
|
||||
已支持的平台:
|
||||
@ -47,7 +47,7 @@
|
||||
|
||||
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
|
||||
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
|
||||
~~或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>~~
|
||||
或者到网盘下载(网盘内有MAC、windows版):`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
|
||||
使用常见问题请转至:[常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)
|
||||
|
||||
目前本项目的原始发布地址只有**GitHub**及**蓝奏网盘**,其他渠道均为第三方转载发布,与本项目无关!
|
||||
@ -65,9 +65,19 @@
|
||||
|
||||
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
|
||||
|
||||
#### 开放 API 支持
|
||||
#### 启动参数
|
||||
|
||||
从 v2.7.0 起支持开放API服务,启用该功能后,将会在本地启动一个 HTTP 服务,提供播放器相关的接口供第三方软件调用,详情看[开放 API 文档](https://lyswhut.github.io/lx-music-doc/desktop/open-api)。
|
||||
目前软件已支持的启动参数如下:
|
||||
|
||||
- `-proxy-server` 设置代理服务器,代理应用的所有流量
|
||||
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器
|
||||
- `-play` 启动时播放指定列表的音乐
|
||||
- `-search` 启动软件时自动在搜索框搜索指定的内容
|
||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration)
|
||||
- `-dt` 以非透明模式启动(Disable Transparent)
|
||||
- `-dhmkh` 禁用硬件媒体密钥处理(Disable Hardware Media Key Handling)
|
||||
|
||||
启动参数的详细说明请看[启动参数说明](https://lyswhut.github.io/lx-music-doc/desktop/run-params)
|
||||
|
||||
#### 数据存储路径
|
||||
|
||||
@ -79,6 +89,10 @@
|
||||
|
||||
在Windows平台下,若程序目录下存在`portable`目录,则自动使用此目录作为数据存储目录(v1.17.0新增)。
|
||||
|
||||
### 源码使用方法
|
||||
|
||||
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
|
||||
|
||||
### UI界面
|
||||
|
||||
<p><a href="https://github.com/lyswhut/lx-music-desktop"><img width="100%" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/app.png" alt="lx-music UI"></a></p>
|
||||
@ -101,10 +115,6 @@
|
||||
2. 克隆本仓库代码并切换到`dev`分支开发
|
||||
3. 提交PR至`dev`分支
|
||||
|
||||
### 源码使用方法
|
||||
|
||||
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
|
||||
|
||||
### 项目协议
|
||||
|
||||
本项目基于 [Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
|
||||
|
||||
BIN
build-config/lib/better_sqlite3_electron-v123-linux-arm.node
Normal file
BIN
build-config/lib/better_sqlite3_electron-v123-linux-arm.node
Normal file
Binary file not shown.
BIN
build-config/lib/better_sqlite3_electron-v123-linux-arm64.node
Normal file
BIN
build-config/lib/better_sqlite3_electron-v123-linux-arm64.node
Normal file
Binary file not shown.
BIN
build-config/lib/better_sqlite3_electron-v123-linux-x64.node
Normal file
BIN
build-config/lib/better_sqlite3_electron-v123-linux-x64.node
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build-config/lib/qrc_decode_electron-v123-linux-arm.node
Normal file
BIN
build-config/lib/qrc_decode_electron-v123-linux-arm.node
Normal file
Binary file not shown.
BIN
build-config/lib/qrc_decode_electron-v123-linux-arm64.node
Normal file
BIN
build-config/lib/qrc_decode_electron-v123-linux-arm64.node
Normal file
Binary file not shown.
BIN
build-config/lib/qrc_decode_electron-v123-linux-x64.node
Normal file
BIN
build-config/lib/qrc_decode_electron-v123-linux-x64.node
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 487 KiB After Width: | Height: | Size: 51 KiB |
4779
package-lock.json
generated
4779
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
61
package.json
61
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "2.10.0-beta.3",
|
||||
"version": "2.9.0",
|
||||
"description": "一个免费的音乐查找助手",
|
||||
"main": "./dist/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
@ -108,24 +108,23 @@
|
||||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.9",
|
||||
"@babel/eslint-parser": "^7.25.9",
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-modules-umd": "^7.25.9",
|
||||
"@babel/plugin-transform-runtime": "^7.25.9",
|
||||
"@babel/preset-env": "^7.25.9",
|
||||
"@babel/preset-typescript": "^7.25.9",
|
||||
"@babel/plugin-transform-modules-umd": "^7.24.7",
|
||||
"@babel/plugin-transform-runtime": "^7.25.4",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@tsconfig/recommended": "^1.0.7",
|
||||
"@types/better-sqlite3": "^7.6.11",
|
||||
"@types/needle": "^3.3.0",
|
||||
"@types/node": "^20.17.0",
|
||||
"@types/tunnel": "^0.0.7",
|
||||
"@types/ws": "8.5.4",
|
||||
"@volar/vue-language-plugin-pug": "^1.6.5",
|
||||
"@vue/language-plugin-pug": "^2.1.6",
|
||||
"babel-loader": "^9.2.1",
|
||||
"browserslist": "^4.24.2",
|
||||
"@vue/language-plugin-pug": "^2.0.29",
|
||||
"babel-loader": "^9.1.3",
|
||||
"browserslist": "^4.23.3",
|
||||
"chalk": "^4.1.2",
|
||||
"changelog-parser": "^3.0.1",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
@ -134,26 +133,26 @@
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^32.2.2",
|
||||
"electron-builder": "^25.1.8",
|
||||
"electron": "^30.4.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03",
|
||||
"electron-to-chromium": "^1.5.45",
|
||||
"electron-updater": "^6.3.9",
|
||||
"eslint": "^8.57.1",
|
||||
"electron-to-chromium": "^1.5.13",
|
||||
"electron-updater": "^6.2.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
||||
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||
"eslint-plugin-html": "^8.1.2",
|
||||
"eslint-plugin-vue": "^9.29.1",
|
||||
"eslint-plugin-html": "^8.1.1",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"eslint-plugin-vue-pug": "^0.6.2",
|
||||
"eslint-webpack-plugin": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"node-loader": "^2.0.0",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss": "^8.4.41",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss-pxtorem": "^6.1.0",
|
||||
"pug": "^3.0.3",
|
||||
@ -163,47 +162,46 @@
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svg-transform-loader": "^2.0.13",
|
||||
"svgo-loader": "^4.0.0",
|
||||
"terser": "^5.36.0",
|
||||
"terser": "^5.31.6",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"tree-kill": "^1.2.2",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript": "^5.5.4",
|
||||
"vue-eslint-parser": "^9.4.3",
|
||||
"vue-loader": "^17.4.2",
|
||||
"vue-template-compiler": "^2.7.16",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-dev-server": "^5.0.4",
|
||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||
"webpack-merge": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@simonwep/pickr": "^1.9.1",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"better-sqlite3": "^11.2.1",
|
||||
"bufferutil": "^4.0.8",
|
||||
"comlink": "~4.3.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"electron-log": "^5.2.0",
|
||||
"electron-log": "^5.1.7",
|
||||
"font-list": "^1.5.1",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"image-size": "^1.1.0",
|
||||
"jschardet": "^3.1.4",
|
||||
"jschardet": "^3.1.3",
|
||||
"long": "^5.2.3",
|
||||
"message2call": "^0.1.3",
|
||||
"music-metadata": "^10.5.1",
|
||||
"music-metadata": "^10.2.0",
|
||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||
"node-id3": "^0.2.6",
|
||||
"sortablejs": "^1.15.3",
|
||||
"sortablejs": "^1.15.2",
|
||||
"tunnel": "^0.0.6",
|
||||
"utf-8-validate": "^6.0.4",
|
||||
"vue": "~3.3.13",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-router": "^4.4.3",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"overrides": {
|
||||
"got": "^11",
|
||||
"json5": "latest",
|
||||
"node-abi": "latest",
|
||||
"minimatch": "latest",
|
||||
"semver": "latest",
|
||||
"svg-transform-loader": {
|
||||
@ -217,7 +215,6 @@
|
||||
},
|
||||
"braces": "latest",
|
||||
"node-gyp-build": "latest",
|
||||
"micromatch": "latest",
|
||||
"http-cache-semantics": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +1,31 @@
|
||||
### 新增
|
||||
|
||||
- 新增托盘图标颜色 跟随系统亮暗模式 设置,可以在 设置-其他 启用 (#2016)
|
||||
- 支持本地同名 `krc` 格式歌词文件的读取(#2053)
|
||||
- Open API 新增播放器播放/暂停、切歌、收藏当前播放歌曲调用,详情看开放API文档 (原始 PR #2077)
|
||||
- 新增 设置-播放设置-是否将歌词显示在状态栏 设置,默认关闭,该功能只在 MacOS 下可用(#1940)
|
||||
- 新增设置-播放详情页设置-延迟歌词滚动设置(#1985)
|
||||
- 新增鼠标在音量按钮使用滚轮时可以调整音量大小的功能(#2000)
|
||||
- 新增设置-下载设置-同时下载任务数设置(#1498)
|
||||
- 新增 我的列表-歌曲右击菜单-歌曲换源 功能,换源后下次再播放该列表的该歌曲时将优先尝试播放所选源的歌曲,该功能允许你手动指定来源以解决自动换源失败或者换源不准确的问题
|
||||
|
||||
### 优化
|
||||
|
||||
- 修正搜索歌曲提示框文案(#2050)
|
||||
- 优化播放详情页UI,歌曲名字、歌手等文字过长时被截断的问题(#2049)
|
||||
- Scheme URL 的播放歌曲允许更长的专辑名称
|
||||
- 优化侧栏图标显示,修复图标可能被裁切的问题(#1960)
|
||||
- 托盘图标添加当前播放歌曲名字显示
|
||||
- 优化本地歌曲内嵌封面过大时的加载方式
|
||||
- 将下载歌曲的歌手信息中的分隔符从 `、` 替换为 `;` 以确保音乐元数据在写入时的兼容性和一致性(#1989 @qnnp-me)
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复歌单详情页内歌单名字过长时的UI显示问题(#2028)
|
||||
- 修复获取自定义环境音效预设列表逻辑问题
|
||||
- 修复 m4a 文件歌曲内嵌歌词无法读取的问题(#2090)
|
||||
- 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923)
|
||||
- 修复 OpenAPI 的 `lyricLineAllText` 在切换到无歌词的音乐时内容没有更新的问题(#1925)
|
||||
- 修复切换音源时可能出现切换死循环的问题
|
||||
- 尝试修复某些情况下播放音频时,处于播放状态但是进度条不走的问题
|
||||
- 修复程序目录路径存在 `#` 或 `%` 时,自定义源、托盘等图标异常的问题(#1997)
|
||||
|
||||
### 变更
|
||||
|
||||
- 不再缓存换源歌曲信息
|
||||
- 简化了应用退出行为,据测试,现在 linux 下,若启用了托盘,dock 右键菜单的 退出、关闭所有 之类的功能将不再退出程序,需改用托盘的退出按钮退出程序
|
||||
- 现在如果在设置或者启动参数配置了代理服务,那么应用内的图片、音频加载,歌曲下载也将走代理
|
||||
|
||||
### 其他
|
||||
|
||||
- 更新 electron 到 v32.2.2
|
||||
- 更新 electron 到 v30.4.0
|
||||
|
||||
@ -80,5 +80,3 @@ export const DOWNLOAD_STATUS = {
|
||||
} as const
|
||||
|
||||
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
|
||||
|
||||
export const TRAY_AUTO_ID = -1
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
import { inflate } from 'zlib'
|
||||
import { decodeName } from './util'
|
||||
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784
|
||||
const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary')
|
||||
const decodeLyric = str => new Promise((resolve, reject) => {
|
||||
if (!str.length) return
|
||||
const buf_str = Buffer.from(str, 'base64').subarray(4)
|
||||
for (let i = 0, len = buf_str.length; i < len; i++) {
|
||||
buf_str[i] = buf_str[i] ^ enc_key[i % 16]
|
||||
}
|
||||
inflate(buf_str, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
|
||||
const headExp = /^.*\[id:\$\w+\]\n/
|
||||
|
||||
const parseLyric = str => {
|
||||
str = str.replace(/\r/g, '')
|
||||
if (headExp.test(str)) str = str.replace(headExp, '')
|
||||
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
|
||||
let lyric
|
||||
let rlyric
|
||||
let tlyric
|
||||
if (trans) {
|
||||
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
|
||||
let json = JSON.parse(Buffer.from(trans[1], 'base64').toString())
|
||||
for (const item of json.content) {
|
||||
switch (item.type) {
|
||||
case 0:
|
||||
rlyric = item.lyricContent
|
||||
break
|
||||
case 1:
|
||||
tlyric = item.lyricContent
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let i = 0
|
||||
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
|
||||
let result = str.match(/\[((\d+),\d+)\].*/)
|
||||
let time = parseInt(result[2])
|
||||
let ms = time % 1000
|
||||
time /= 1000
|
||||
let m = parseInt(time / 60).toString().padStart(2, '0')
|
||||
time %= 60
|
||||
let s = parseInt(time).toString().padStart(2, '0')
|
||||
time = `${m}:${s}.${ms}`
|
||||
if (rlyric) rlyric[i] = `[${time}]${rlyric[i]?.join('') ?? ''}`
|
||||
if (tlyric) tlyric[i] = `[${time}]${tlyric[i]?.join('') ?? ''}`
|
||||
i++
|
||||
return str.replace(result[1], time)
|
||||
})
|
||||
rlyric = rlyric ? rlyric.join('\n') : ''
|
||||
tlyric = tlyric ? tlyric.join('\n') : ''
|
||||
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
|
||||
lxlyric = decodeName(lxlyric)
|
||||
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
|
||||
rlyric = decodeName(rlyric)
|
||||
tlyric = decodeName(tlyric)
|
||||
return {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const decodeKrc = async(data) => {
|
||||
return decodeLyric(data).then(parseLyric)
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
const encodeNames = {
|
||||
' ': ' ',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'",
|
||||
''': "'",
|
||||
} as const
|
||||
|
||||
export const decodeName = (str: string | null = '') => {
|
||||
return str?.replace(/(?:&|<|>|"|'|'| )/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
|
||||
}
|
||||
@ -500,7 +500,6 @@
|
||||
"setting__other_resource_cache_tip": "Picture, audio, and other caches. After cleaning, resources such as pictures will need to be downloaded again. It is not recommended to clean up. The software will dynamically manage the cache size according to the disk space.",
|
||||
"setting__other_resource_cache_tip_confirm": "Involving the cache of pictures, audios, etc., the pictures and other resources will need to be downloaded again after cleaning. It is not recommended to clean up. The software will dynamically manage the cache size according to the disk space. Do you still need to clean up?",
|
||||
"setting__other_tray_theme": "Tray Icon Style",
|
||||
"setting__other_tray_theme_auto": "Follow the system light and dark themes",
|
||||
"setting__other_tray_theme_black": "Black Color",
|
||||
"setting__other_tray_theme_native": "White",
|
||||
"setting__other_tray_theme_origin": "Primary Color",
|
||||
|
||||
@ -500,7 +500,6 @@
|
||||
"setting__other_resource_cache_tip": "图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小",
|
||||
"setting__other_resource_cache_tip_confirm": "涉及图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小,是否仍要清理?",
|
||||
"setting__other_tray_theme": "托盘图标样式",
|
||||
"setting__other_tray_theme_auto": "跟随系统亮暗主题",
|
||||
"setting__other_tray_theme_black": "黑色",
|
||||
"setting__other_tray_theme_native": "白色",
|
||||
"setting__other_tray_theme_origin": "原色",
|
||||
|
||||
@ -500,7 +500,6 @@
|
||||
"setting__other_resource_cache_tip": "圖片、音訊等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟體會根據磁碟空間動態管理快取大小",
|
||||
"setting__other_resource_cache_tip_confirm": "涉及圖片、音訊等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟體會根據磁碟空間動態管理快取大小,是否仍要清理?",
|
||||
"setting__other_tray_theme": "托盤圖示樣式",
|
||||
"setting__other_tray_theme_auto": "跟隨系統亮暗主題",
|
||||
"setting__other_tray_theme_black": "黑色",
|
||||
"setting__other_tray_theme_native": "白色",
|
||||
"setting__other_tray_theme_origin": "原色",
|
||||
|
||||
@ -2,19 +2,6 @@ import http from 'node:http'
|
||||
import querystring from 'node:querystring'
|
||||
import type { Socket } from 'node:net'
|
||||
import { getAddress } from '@common/utils/nodejs'
|
||||
import { sendTaskbarButtonClick } from '@main/modules/winMain'
|
||||
|
||||
const sendResponse = (res: http.ServerResponse, code = 200, msg: string | Record<any, unknown> = 'OK', contentType = 'text/plain; charset=utf-8') => {
|
||||
res.writeHead(code, {
|
||||
'Content-Type': contentType,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
})
|
||||
if (typeof msg === 'object') {
|
||||
res.end(JSON.stringify(msg))
|
||||
} else {
|
||||
res.end(msg)
|
||||
}
|
||||
}
|
||||
|
||||
let status: LX.OpenAPI.Status = {
|
||||
status: false,
|
||||
@ -50,7 +37,10 @@ const handleSendStatus = (res: http.ServerResponse<http.IncomingMessage>, query?
|
||||
const keys = parseFilter(querystring.parse(query ?? '').filter)
|
||||
const resp: Partial<Record<SubscribeKeys, any>> = {}
|
||||
for (const k of keys) resp[k] = global.lx.player_status[k]
|
||||
sendResponse(res, 200, resp, 'application/json; charset=utf-8')
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
res.writeHead(200)
|
||||
res.end(JSON.stringify(resp))
|
||||
}
|
||||
const handleSubscribePlayerStatus = (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>, query?: string) => {
|
||||
res.writeHead(200, {
|
||||
@ -77,8 +67,8 @@ const handleStartServer = async(port: number, ip: string) => new Promise<void>((
|
||||
playerStatusKeys = Object.keys(global.lx.player_status) as SubscribeKeys[]
|
||||
httpServer = http.createServer((req, res): void => {
|
||||
const [endUrl, query] = `/${req.url?.split('/').at(-1) ?? ''}`.split('?')
|
||||
let code = 200
|
||||
let msg = 'OK'
|
||||
let code
|
||||
let msg
|
||||
switch (endUrl) {
|
||||
case '/status':
|
||||
handleSendStatus(res, query)
|
||||
@ -128,26 +118,11 @@ const handleStartServer = async(port: number, ip: string) => new Promise<void>((
|
||||
// </html>`
|
||||
// break
|
||||
case '/lyric':
|
||||
code = 200
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
msg = global.lx.player_status.lyric
|
||||
break
|
||||
case '/play':
|
||||
sendTaskbarButtonClick('play')
|
||||
break
|
||||
case '/pause':
|
||||
sendTaskbarButtonClick('pause')
|
||||
break
|
||||
case '/skip-next':
|
||||
sendTaskbarButtonClick('next')
|
||||
break
|
||||
case '/skip-prev':
|
||||
sendTaskbarButtonClick('prev')
|
||||
break
|
||||
case '/collect':
|
||||
sendTaskbarButtonClick('collect')
|
||||
break
|
||||
case '/uncollect':
|
||||
sendTaskbarButtonClick('unCollect')
|
||||
break
|
||||
case '/subscribe-player-status':
|
||||
try {
|
||||
handleSubscribePlayerStatus(req, res, query)
|
||||
@ -163,7 +138,9 @@ const handleStartServer = async(port: number, ip: string) => new Promise<void>((
|
||||
msg = 'Forbidden'
|
||||
break
|
||||
}
|
||||
sendResponse(res, code, msg)
|
||||
if (!code) return
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
})
|
||||
httpServer.on('error', error => {
|
||||
console.log(error)
|
||||
|
||||
@ -9,7 +9,6 @@ import {
|
||||
showWindow as showMainWindow,
|
||||
} from './winMain'
|
||||
import { quitApp } from '@main/app'
|
||||
import { TRAY_AUTO_ID } from '@common/constants'
|
||||
|
||||
let tray: Electron.Tray | null
|
||||
let isEnableTray: boolean = false
|
||||
@ -133,10 +132,7 @@ const i18n = {
|
||||
}
|
||||
|
||||
const getIconPath = (id: number) => {
|
||||
let theme = id == TRAY_AUTO_ID
|
||||
? global.lx.theme.shouldUseDarkColors
|
||||
? themeList[0] : themeList[2]
|
||||
: themeList.find(item => item.id === id) ?? themeList[0]
|
||||
let theme = themeList.find(item => item.id === id) ?? themeList[0]
|
||||
return path.join(global.staticPath, 'images/tray', theme.fileName + (isWin ? '.ico' : '.png'))
|
||||
}
|
||||
|
||||
@ -376,11 +372,6 @@ export default () => {
|
||||
init()
|
||||
})
|
||||
|
||||
global.lx.event_app.on('system_theme_change', () => {
|
||||
if (global.lx.appSetting['tray.themeId'] != TRAY_AUTO_ID) return
|
||||
setTrayImage(global.lx.appSetting['tray.themeId'])
|
||||
})
|
||||
|
||||
global.lx.event_app.on('player_status', (status) => {
|
||||
let updated = false
|
||||
if (status.status) {
|
||||
|
||||
@ -73,7 +73,7 @@ const matchInfo = (scriptInfo: string) => {
|
||||
for (const [key, len] of Object.entries(INFO_NAMES) as Array<{ [K in keyof INFO_NAMES_Type]: [K, INFO_NAMES_Type[K]] }[keyof INFO_NAMES_Type]>) {
|
||||
infos[key] ||= ''
|
||||
if (infos[key] == null) infos[key] = ''
|
||||
else if (infos[key].length > len) infos[key] = infos[key].substring(0, len) + '...'
|
||||
else if (infos[key]!.length > len) infos[key] = infos[key]!.substring(0, len) + '...'
|
||||
}
|
||||
|
||||
return infos as Record<keyof typeof INFO_NAMES, string>
|
||||
|
||||
@ -11,7 +11,7 @@ transition(enter-active-class="animated slideInRight" leave-active-class="animat
|
||||
//- div(:class="$style.info")
|
||||
div(:class="$style.info")
|
||||
img(v-if="musicInfo.pic" :class="$style.img" :src="musicInfo.pic")
|
||||
div.description(:class="['scroll', $style.description]")
|
||||
div.description(:class="$style.description")
|
||||
p {{ $t('player__music_name') }}{{ musicInfo.name }}
|
||||
p {{ $t('player__music_singer') }}{{ musicInfo.singer }}
|
||||
p(v-if="musicInfo.album") {{ $t('player__music_album') }}{{ musicInfo.album }}
|
||||
@ -244,11 +244,11 @@ export default {
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
max-width: 300px;
|
||||
min-height: 0;
|
||||
|
||||
}
|
||||
.img {
|
||||
max-width: 100%;
|
||||
max-height: 80%;
|
||||
max-height: 100%;
|
||||
min-width: 100%;
|
||||
box-shadow: 0 0 6px var(--color-primary-alpha-500);
|
||||
border-radius: 6px;
|
||||
@ -256,9 +256,8 @@ export default {
|
||||
}
|
||||
.description {
|
||||
max-width: 300px;
|
||||
margin-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
min-height: 0;
|
||||
padding: 15px 0;
|
||||
overflow: hidden;
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
|
||||
@ -2,8 +2,8 @@ import { qualityList } from '@renderer/store'
|
||||
import { assertApiSupport } from '@renderer/store/utils'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import {
|
||||
// getOtherSource as getOtherSourceFromStore,
|
||||
// saveOtherSource as saveOtherSourceFromStore,
|
||||
getOtherSource as getOtherSourceFromStore,
|
||||
saveOtherSource as saveOtherSourceFromStore,
|
||||
getMusicUrl as getStoreMusicUrl,
|
||||
getPlayerLyric as getStoreLyric,
|
||||
} from '@renderer/utils/ipc'
|
||||
@ -17,10 +17,10 @@ const getOtherSourcePromises = new Map()
|
||||
export const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
|
||||
export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false): Promise<LX.Music.MusicInfoOnline[]> => {
|
||||
// if (!isRefresh && musicInfo.id) {
|
||||
// const cachedInfo = await getOtherSourceFromStore(musicInfo.id)
|
||||
// if (cachedInfo.length) return cachedInfo
|
||||
// }
|
||||
if (!isRefresh && musicInfo.id) {
|
||||
const cachedInfo = await getOtherSourceFromStore(musicInfo.id)
|
||||
if (cachedInfo.length) return cachedInfo
|
||||
}
|
||||
let key: string
|
||||
let searchMusicInfo: {
|
||||
name: string
|
||||
@ -61,7 +61,7 @@ export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download.
|
||||
if (timeout) clearTimeout(timeout)
|
||||
})
|
||||
}).then((otherSource) => {
|
||||
// if (otherSource.length) void saveOtherSourceFromStore(musicInfo.id, otherSource)
|
||||
if (otherSource.length) void saveOtherSourceFromStore(musicInfo.id, otherSource)
|
||||
return otherSource
|
||||
}).finally(() => {
|
||||
if (getOtherSourcePromises.has(key)) getOtherSourcePromises.delete(key)
|
||||
|
||||
@ -259,20 +259,17 @@ const handleToggleStop = () => {
|
||||
* @returns
|
||||
*/
|
||||
export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
console.log('skip next', isAutoToggle)
|
||||
if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
|
||||
const playMusicInfo = tempPlayList[0]
|
||||
removeTempPlayList(0)
|
||||
pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
console.log('play temp list')
|
||||
return
|
||||
}
|
||||
|
||||
if (playMusicInfo.musicInfo == null) {
|
||||
handleToggleStop()
|
||||
console.log('musicInfo empty')
|
||||
return
|
||||
}
|
||||
|
||||
@ -280,7 +277,6 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
const currentListId = playInfo.playerListId
|
||||
if (!currentListId) {
|
||||
handleToggleStop()
|
||||
console.log('currentListId empty')
|
||||
return
|
||||
}
|
||||
const currentList = getList(currentListId)
|
||||
@ -310,7 +306,6 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
console.log('play played list')
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -325,7 +320,6 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
|
||||
if (!filteredList.length) {
|
||||
handleToggleStop()
|
||||
console.log('filtered list empty')
|
||||
return
|
||||
}
|
||||
// let currentIndex: number = filteredList.indexOf(currentList[playInfo.playerPlayIndex])
|
||||
@ -355,13 +349,9 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
console.log('stop toggle play', togglePlayMethod, isAutoToggle)
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) {
|
||||
console.log('next index empty')
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
|
||||
@ -64,7 +64,7 @@ const usePlayMusic = () => {
|
||||
{ key: 'img', types: ['string'], max: 1024 },
|
||||
{ key: 'albumId', types: ['string', 'number'], max: 64 },
|
||||
{ key: 'interval', types: ['string'], max: 64 },
|
||||
{ key: 'albumName', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 64 },
|
||||
{ key: 'types', types: ['object'], required: true },
|
||||
], musicInfo)
|
||||
break
|
||||
@ -78,7 +78,7 @@ const usePlayMusic = () => {
|
||||
{ key: 'albumId', types: ['string', 'number'], max: 64 },
|
||||
{ key: 'interval', types: ['string'], max: 64 },
|
||||
{ key: '_interval', types: ['number'], max: 64 },
|
||||
{ key: 'albumName', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 64 },
|
||||
{ key: 'types', types: ['object'], required: true },
|
||||
|
||||
{ key: 'hash', types: ['string'], required: true, max: 64 },
|
||||
@ -93,7 +93,7 @@ const usePlayMusic = () => {
|
||||
{ key: 'img', types: ['string'], max: 1024 },
|
||||
{ key: 'albumId', types: ['string', 'number'], max: 64 },
|
||||
{ key: 'interval', types: ['string'], max: 64 },
|
||||
{ key: 'albumName', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 64 },
|
||||
{ key: 'types', types: ['object'], required: true },
|
||||
|
||||
{ key: 'strMediaMid', types: ['string'], required: true, max: 64 },
|
||||
@ -109,7 +109,7 @@ const usePlayMusic = () => {
|
||||
{ key: 'img', types: ['string'], max: 1024 },
|
||||
{ key: 'albumId', types: ['string', 'number'], max: 64 },
|
||||
{ key: 'interval', types: ['string'], max: 64 },
|
||||
{ key: 'albumName', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 64 },
|
||||
{ key: 'types', types: ['object'], required: true },
|
||||
], musicInfo)
|
||||
break
|
||||
@ -122,7 +122,7 @@ const usePlayMusic = () => {
|
||||
{ key: 'img', types: ['string'], max: 1024 },
|
||||
{ key: 'albumId', types: ['string', 'number'], max: 64 },
|
||||
{ key: 'interval', types: ['string'], max: 64 },
|
||||
{ key: 'albumName', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 64 },
|
||||
{ key: 'types', types: ['object'], required: true },
|
||||
|
||||
{ key: 'copyrightId', types: ['string', 'number'], required: true, max: 64 },
|
||||
@ -166,7 +166,7 @@ const useSearchPlayMusic = () => {
|
||||
return dataVerify([
|
||||
{ key: 'name', types: ['string'], required: true, max: 200 },
|
||||
{ key: 'singer', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 200 },
|
||||
{ key: 'albumName', types: ['string'], max: 64 },
|
||||
{ key: 'interval', types: ['string'], max: 64 },
|
||||
{ key: 'playLater', types: ['boolean'] },
|
||||
], info)
|
||||
|
||||
@ -85,7 +85,6 @@ export default () => {
|
||||
// setTimeout(() => {
|
||||
if (window.lx.isPlayedStop) {
|
||||
setAllStatus(t('player__end'))
|
||||
console.log('played stop')
|
||||
return
|
||||
}
|
||||
// resetPlayerMusicInfo()
|
||||
|
||||
@ -62,7 +62,7 @@ export const getList = async(source: Source): Promise<string[]> => {
|
||||
return setLists(results)
|
||||
})
|
||||
} else {
|
||||
if (sourceList[source]?.length) return Promise.resolve(sourceList[source])
|
||||
if (sourceList[source]?.length) return Promise.resolve(sourceList[source]!)
|
||||
if (!music[source]?.hotSearch) {
|
||||
setList(source, [])
|
||||
return Promise.resolve([])
|
||||
|
||||
@ -41,8 +41,7 @@ export const removeUserEQPreset = async(id: string) => {
|
||||
|
||||
let userConvolutionPresetList: LX.SoundEffect.ConvolutionPreset[] | null = null
|
||||
export const getUserConvolutionPresetList = async() => {
|
||||
if (userConvolutionPresetList == null) {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
if (userEqPresetList == null) {
|
||||
userConvolutionPresetList = reactive(await getUserSoundEffectConvolutionPresetList())
|
||||
}
|
||||
return userConvolutionPresetList
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { checkPath, joinPath, extname, basename, readFile, getFileStats } from '@common/utils/nodejs'
|
||||
import { formatPlayTime } from '@common/utils/common'
|
||||
import type { IComment } from 'music-metadata/lib/type'
|
||||
import { decodeKrc } from '@common/utils/lyricUtils/kg'
|
||||
|
||||
export const checkDownloadFileAvailable = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<boolean> => {
|
||||
return musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName) &&
|
||||
@ -162,11 +161,10 @@ export const getLocalMusicFilePic = async(path: string) => {
|
||||
* 获取歌曲文件歌词
|
||||
* @param path 路径
|
||||
*/
|
||||
export const getLocalMusicFileLyric = async(path: string): Promise<LX.Music.LyricInfo | null> => {
|
||||
export const getLocalMusicFileLyric = async(path: string): Promise<string | null> => {
|
||||
// 尝试读取同目录下的同名lrc文件
|
||||
const filePath = new RegExp('\\' + extname(path) + '$')
|
||||
let lrcPath = path.replace(filePath, '.lrc')
|
||||
let stats = await getFileStats(lrcPath)
|
||||
const lrcPath = path.replace(new RegExp('\\' + extname(path) + '$'), '.lrc')
|
||||
const stats = await getFileStats(lrcPath)
|
||||
// console.log(lrcPath, stats)
|
||||
if (stats && stats.size < 1024 * 1024 * 10) {
|
||||
const lrcBuf = await readFile(lrcPath)
|
||||
@ -177,51 +175,23 @@ export const getLocalMusicFileLyric = async(path: string): Promise<LX.Music.Lyri
|
||||
const iconv = await import('iconv-lite')
|
||||
if (iconv.encodingExists(encoding)) {
|
||||
const lrc = iconv.decode(lrcBuf, encoding)
|
||||
if (lrc) {
|
||||
return {
|
||||
lyric: lrc,
|
||||
}
|
||||
}
|
||||
if (lrc) return lrc
|
||||
}
|
||||
}
|
||||
}
|
||||
// 尝试读取同目录下的同名krc文件
|
||||
lrcPath = path.replace(filePath, '.krc')
|
||||
stats = await getFileStats(lrcPath)
|
||||
console.log(lrcPath, stats?.size)
|
||||
if (stats && stats.size < 1024 * 1024 * 10) {
|
||||
const lrcBuf = await readFile(lrcPath)
|
||||
try {
|
||||
return await decodeKrc(lrcBuf)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 尝试读取文件内歌词
|
||||
const metadata = await getFileMetadata(path)
|
||||
// console.log(metadata)
|
||||
if (!metadata) return null
|
||||
let lyricInfo = metadata.common.lyrics?.[0]
|
||||
if (lyricInfo) {
|
||||
let lyric: string | undefined
|
||||
if (typeof lyricInfo == 'object') lyric = lyricInfo.text
|
||||
else if (typeof lyricInfo == 'string') lyric = lyricInfo
|
||||
if (lyric && lyric.length > 10) {
|
||||
return { lyric }
|
||||
}
|
||||
if (metadata.common.lyrics?.[0]?.text && metadata.common.lyrics[0].text.length > 10) {
|
||||
return metadata.common.lyrics[0].text
|
||||
}
|
||||
// console.log(metadata)
|
||||
for (const info of Object.values(metadata.native)) {
|
||||
const ust = info.find(i => i.id == 'USLT')
|
||||
if (ust) {
|
||||
const value = ust.value as IComment
|
||||
if (value.text && value.text.length > 10) {
|
||||
return {
|
||||
lyric: value.text,
|
||||
}
|
||||
}
|
||||
if (value.text && value.text.length > 10) return value.text
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
||||
@ -1,5 +1,59 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeKrc } from '@common/utils/lyricUtils/kg'
|
||||
import { decodeLyric } from './util'
|
||||
import { decodeName } from '../../index'
|
||||
|
||||
const headExp = /^.*\[id:\$\w+\]\n/
|
||||
|
||||
const parseLyric = str => {
|
||||
str = str.replace(/\r/g, '')
|
||||
if (headExp.test(str)) str = str.replace(headExp, '')
|
||||
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
|
||||
let lyric
|
||||
let rlyric
|
||||
let tlyric
|
||||
if (trans) {
|
||||
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
|
||||
let json = JSON.parse(Buffer.from(trans[1], 'base64').toString())
|
||||
for (const item of json.content) {
|
||||
switch (item.type) {
|
||||
case 0:
|
||||
rlyric = item.lyricContent
|
||||
break
|
||||
case 1:
|
||||
tlyric = item.lyricContent
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let i = 0
|
||||
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
|
||||
let result = str.match(/\[((\d+),\d+)\].*/)
|
||||
let time = parseInt(result[2])
|
||||
let ms = time % 1000
|
||||
time /= 1000
|
||||
let m = parseInt(time / 60).toString().padStart(2, '0')
|
||||
time %= 60
|
||||
let s = parseInt(time).toString().padStart(2, '0')
|
||||
time = `${m}:${s}.${ms}`
|
||||
if (rlyric) rlyric[i] = `[${time}]${rlyric[i]?.join('') ?? ''}`
|
||||
if (tlyric) tlyric[i] = `[${time}]${tlyric[i]?.join('') ?? ''}`
|
||||
i++
|
||||
return str.replace(result[1], time)
|
||||
})
|
||||
rlyric = rlyric ? rlyric.join('\n') : ''
|
||||
tlyric = tlyric ? tlyric.join('\n') : ''
|
||||
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
|
||||
lxlyric = decodeName(lxlyric)
|
||||
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
|
||||
rlyric = decodeName(rlyric)
|
||||
tlyric = decodeName(tlyric)
|
||||
return {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getIntv(interval) {
|
||||
@ -76,7 +130,7 @@ export default {
|
||||
|
||||
switch (body.fmt) {
|
||||
case 'krc':
|
||||
return decodeKrc(body.content)
|
||||
return decodeLyric(body.content).then(result => parseLyric(result))
|
||||
case 'lrc':
|
||||
return {
|
||||
lyric: Buffer.from(body.content, 'base64').toString('utf-8'),
|
||||
|
||||
@ -1,6 +1,21 @@
|
||||
import { inflate } from 'zlib'
|
||||
import { toMD5 } from '../utils'
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784
|
||||
const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary')
|
||||
export const decodeLyric = str => new Promise((resolve, reject) => {
|
||||
if (!str.length) return
|
||||
const buf_str = Buffer.from(str, 'base64').slice(4)
|
||||
for (let i = 0, len = buf_str.length; i < len; i++) {
|
||||
buf_str[i] = buf_str[i] ^ enc_key[i % 16]
|
||||
}
|
||||
inflate(buf_str, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
|
||||
// s.content[0].lyricContent.forEach(([str]) => {
|
||||
// console.log(str)
|
||||
// })
|
||||
|
||||
@ -9,10 +9,10 @@ const name = pkg.name
|
||||
const address = [
|
||||
[`https://raw.githubusercontent.com/${author}/${name}/master/publish/version.json`, 'direct'],
|
||||
['https://registry.npmjs.org/lx-music-desktop-version-info/latest', 'npm'],
|
||||
['https://registry.npmmirror.com/lx-music-desktop-version-info/latest', 'npm'],
|
||||
[`https://cdn.jsdelivr.net/gh/${author}/${name}/publish/version.json`, 'direct'],
|
||||
[`https://fastly.jsdelivr.net/gh/${author}/${name}/publish/version.json`, 'direct'],
|
||||
[`https://gcore.jsdelivr.net/gh/${author}/${name}/publish/version.json`, 'direct'],
|
||||
['https://registry.npmmirror.com/lx-music-desktop-version-info/latest', 'npm'],
|
||||
['https://gitee.com/lyswhut/lx-music-desktop-versions/raw/master/version.json', 'direct'],
|
||||
['http://cdn.stsky.cn/lx-music/desktop/version.json', 'direct'],
|
||||
]
|
||||
|
||||
@ -46,7 +46,7 @@ export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Search for something...',
|
||||
default: 'Find for something...',
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
|
||||
@ -77,7 +77,6 @@ import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { overwriteListFull } from '@renderer/store/list/listManage'
|
||||
import { dislikeRuleCount } from '@renderer/store/dislikeList'
|
||||
import DislikeListModal from './DislikeListModal.vue'
|
||||
import { TRAY_AUTO_ID } from '@common/constants'
|
||||
|
||||
export default {
|
||||
name: 'SettingOther',
|
||||
@ -92,7 +91,6 @@ export default {
|
||||
{ id: 0, name: 'native', label: t('setting__other_tray_theme_native') },
|
||||
{ id: 2, name: 'black', label: t('setting__other_tray_theme_black') },
|
||||
{ id: 1, name: 'origin', label: t('setting__other_tray_theme_origin') },
|
||||
{ id: TRAY_AUTO_ID, name: 'auto', label: t('setting__other_tray_theme_auto') },
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@ -206,7 +206,6 @@ export default {
|
||||
.songListHeaderMiddle {
|
||||
flex: auto;
|
||||
padding: 2px 7px;
|
||||
min-width: 0;
|
||||
h3 {
|
||||
.mixin-ellipsis-1;
|
||||
line-height: 1.2;
|
||||
|
||||
@ -118,8 +118,6 @@ const getIntv = (musicInfo: LX.Music.MusicInfo) => {
|
||||
return intv
|
||||
}
|
||||
|
||||
export type SortFieldName = 'name' | 'singer' | 'albumName' | 'interval' | 'source'
|
||||
export type SortFieldType = 'up' | 'down' | 'random'
|
||||
/**
|
||||
* 排序歌曲
|
||||
* @param list 歌曲列表
|
||||
@ -128,7 +126,7 @@ export type SortFieldType = 'up' | 'down' | 'random'
|
||||
* @param localeId 排序语言
|
||||
* @returns
|
||||
*/
|
||||
export const sortListMusicInfo = async(list: LX.Music.MusicInfo[], sortType: SortFieldType, fieldName: SortFieldName, localeId: string) => {
|
||||
export const sortListMusicInfo = async(list: LX.Music.MusicInfo[], sortType: 'up' | 'down' | 'random', fieldName: 'name' | 'singer' | 'albumName' | 'interval' | 'source', localeId: string) => {
|
||||
// console.log(sortType, fieldName, localeId)
|
||||
// const locale = new Intl.Locale(localeId)
|
||||
switch (sortType) {
|
||||
|
||||
@ -31,5 +31,7 @@ export const getMusicFilePic = async(filePath: string) => {
|
||||
export const getMusicFileLyric = async(filePath: string): Promise<LX.Music.LyricInfo | null> => {
|
||||
const lyric = await getLocalMusicFileLyric(filePath)
|
||||
if (!lyric) return null
|
||||
return lyric
|
||||
return {
|
||||
lyric,
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user