Compare commits

...
25 Commits
Author SHA1 Message Date
lyswhut 39466f157c 发布 v2.12.4
Build / Windows (push) Waiting to run
Build / Windows_7 (push) Waiting to run
Build / Mac (push) Waiting to run
Build / Linux (push) Waiting to run
2026-09-12 20:40:02 +08:00
lyswhut d1fa51c2bb fix: 修复主题背景图片URL构建逻辑 2026-09-12 20:38:08 +08:00
lyswhut a0e16fc4f9 发布 v2.12.3 2026-09-12 13:04:34 +08:00
lyswhut f64da62aa8 更新依赖&版本号(2.12.3-beta.12) 2026-09-06 14:38:42 +08:00
3gf8jv4dv 75185cb50b chore: adjust item order of language options (#2946) 2026-08-25 13:39:41 +08:00
3gf8jv4dv bf9b34b47f fix(l10n): correct window size option text in zh-tw (#2943)
resolves https://github.com/lyswhut/lx-music-desktop/issues/2932
2026-08-23 10:05:41 +08:00
lyswhut 6573a5cc8a fix: 修正armv7l架构映射以匹配正确的值 2026-08-21 20:38:31 +08:00
lyswhut 7c3ca2a0fc fix: 修复架构映射以正确复制库文件 2026-08-21 20:33:01 +08:00
lyswhut e8214393e9 fix: 修复构建 2026-08-21 19:48:00 +08:00
lyswhut f4f9496fda 更新依赖&版本号(2.12.3-beta.11) 2026-08-21 19:32:41 +08:00
jtcaiandjtcai c68361719d 修正tx字段拼写错误 (#2911)
* 修正部分qq歌单返回字段

* 修正tx字段total_song_sum->total_song_num

---------

Co-authored-by: jtcai <jtcai@users.noreply.github.com>
2026-08-05 00:14:05 +08:00
lyswhut 936fcb79fa 更新依赖&版本号(2.12.3-beta.10) 2026-08-05 00:09:26 +08:00
lyswhut 65ff972d1a feat: 添加版本字符常量以优化歌曲匹配机制 2026-08-04 23:57:28 +08:00
moduvoice 9ff8ae2587 feat(i18n): add Korean (ko-kr) translation (#2902)
* feat(i18n): add Korean (ko-kr) translation

Add full Korean (ko-kr) locale for the app UI, translated from the
English (en-us) source with full key parity (746/746 keys) and all
placeholders preserved. Registers the new locale in languages.json
and src/lang/index.ts, mirroring how existing locales are wired up.

* feat(i18n): add Korean tray menu strings to tray.ts

Add ko-kr entry to the tray module's messages map, matching the
existing en-us/zh-cn/zh-tw pattern with full key parity (19/19 keys):
play/pause, next/prev song, collect/uncollect, show/hide main window,
show/hide/lock/unlock/top desktop lyric window, statusbar lyric
toggle, exit, and tooltip labels.

Addresses reviewer feedback on PR #2902.
2026-07-15 09:36:41 +08:00
3gf8jv4dv c0dd64c71c fix: use IEC units for binary sizes (#2901) 2026-07-09 09:40:19 +08:00
lyswhut f3fbbb3720 chore: 更新版本号至 2.12.3-beta.9 2026-07-07 22:23:02 +08:00
lyswhut 5279fbba56 chore: 更新版本号至 2.12.3-beta.8 2026-07-06 21:02:49 +08:00
lyswhut bbbbfeda12 fix: 修复歌词解析中毫秒时间的处理逻辑 2026-07-06 20:56:58 +08:00
lyswhut 75ba99d9d1 修复歌词标签解析格式没有严格按照标准的问题(#2855) 2026-07-06 20:56:58 +08:00
jtcaiandjtcai af4603d87b 修正部分qq歌单返回字段 (#2899)
Co-authored-by: jtcai <jtcai@users.noreply.github.com>
2026-07-06 20:56:44 +08:00
lyswhut 1cb9e2fd29 chore: 更新版本号至 2.12.3-beta.7 2026-07-02 12:43:43 +08:00
lyswhut d5b1b3fd5f fix: 修复在某些情况下移动歌曲时可能导致保存的顺序不对的问题(#2842) 2026-07-02 12:42:54 +08:00
lyswhut f0776becff 更新依赖&版本号(2.12.3-beta.6) 2026-06-21 13:11:44 +08:00
lyswhut 03fea45166 降级 electron 2026-06-21 13:08:23 +08:00
ikun0014andClaude Opus 4.8 905b2c3c42 feat: kg 源搜索改用 AndroidFilter 接口并提供 albumAudioId (#2849)
将搜索接口 platform 从 WebFilter 切换到 AndroidFilter,新接口返回
MixSongID(即 album_audio_id),用于后续歌词精确搜索。

- songmid 各处仍保持原取值(Audioid / audio_id)不变,避免影响
  既有歌曲 id 与旧收藏/列表数据
- 所有歌曲列表产出点均新增 albumAudioId 字段:
    musicSearch(MixSongID)、songList 顶层(album_audio_id)、
    songList 详情/leaderboard/musicInfo(audio_group_id / album_audio_id)
- music.d.ts 的 MusicInfoMeta_kg 增加 albumAudioId 字段,
  tools.ts 新旧格式双向转换中保留,确保持久化后不丢失

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:31:46 +08:00
46 changed files with 2213 additions and 925 deletions
+8
View File
@@ -16,10 +16,15 @@ module.exports = {
'eslint-webpack-plugin', 'eslint-webpack-plugin',
'typescript', 'typescript',
'undici', 'undici',
'@types/better-sqlite3',
'changelog-parser',
'webpack-dev-server',
'eslint-plugin-vue', 'eslint-plugin-vue',
'vue-eslint-parser', 'vue-eslint-parser',
// 'eslint-config-standard-with-typescript', // 'eslint-config-standard-with-typescript',
'webpack',
], ],
// target: 'newest', // target: 'newest',
@@ -47,5 +52,8 @@ module.exports = {
// 'eslint-plugin-vue', // 'eslint-plugin-vue',
// 'vue-eslint-parser', // 'vue-eslint-parser',
// 'better-sqlite3',
// '@types/better-sqlite3',
// 'webpack-dev-server',
// ], // ],
} }
+35
View File
@@ -6,6 +6,41 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org). Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/). Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [2.12.4](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.3...v2.12.4) - 2026-09-12
目前新项目 Any Listen 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
对于日常使用 LX Music 的人可以试试迁移到 Any Listen,若遇到任何问题可以发 issue 反馈。
Any Listen 的项目地址为 https://github.com/any-listen/any-listen
### 修复
- 修复自定义主题背景图片无法显示的问题
## [2.12.3](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.2...v2.12.3) - 2026-09-12
目前新项目 Any Listen 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
对于日常使用 LX Music 的人可以试试迁移到 Any Listen,若遇到任何问题可以发 issue 反馈。
Any Listen 的项目地址为 https://github.com/any-listen/any-listen
### 优化
- 优化kw歌单列表数据显示
- 同步服务在连接时允许URL重定向
- Linux 点击托盘时将显示主界面(#2840)
- 优化自动换源歌曲匹配机制
### 修复
- 修复 kg 搜索结果显示问题 (#2782)
- 修复某些情况下开放 API 获取到的音量为 0 的问题 (#2790)
- 修复 mg 图片、歌词获取
- 修复某些 tx 歌单打开失败的问题 (#1060, @ght-000)
- 修复在某些情况下添加、移动歌曲时可能导致保存的歌曲顺序不对的问题(#2842)
- 修复歌词标签解析格式没有严格按照标准的问题(#2855)
- 修复 tx 歌曲搜索失败的问题(#2848, @ikun0014)
## [2.12.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.1...v2.12.2) - 2026-05-01 ## [2.12.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.1...v2.12.2) - 2026-05-01
我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。 我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。
+6 -1
View File
@@ -47,7 +47,12 @@
*移动版项目地址:https://github.com/lyswhut/lx-music-mobile* *移动版项目地址:https://github.com/lyswhut/lx-music-mobile*
*LX Music 项目发展调整与新项目计划:https://github.com/lyswhut/lx-music-desktop/issues/1912* > [!NOTE]
> 目前新项目 [Any Listen](https://github.com/any-listen/any-listen) 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
> 我们以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
> 对于日常使用 LX Music 的人可以试试迁移到 Any Listen,若遇到任何问题可以发 issue 反馈。
>
> 关于我们开发新项目的原因,可以参考:[LX Music 项目发展调整与新项目计划](https://github.com/lyswhut/lx-music-desktop/issues/1912)。
软件变化请查看[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)。 软件变化请查看[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)。
+3
View File
@@ -1,9 +1,12 @@
const { afterPack } = require('./deps')
const fs = require('fs').promises const fs = require('fs').promises
// https://github.com/electron-userland/electron-builder/issues/4630 // https://github.com/electron-userland/electron-builder/issues/4630
// https://github.com/electron-userland/electron-builder/issues/4630#issuecomment-782020139 // https://github.com/electron-userland/electron-builder/issues/4630#issuecomment-782020139
module.exports = async(context) => { module.exports = async(context) => {
await afterPack()
const { electronPlatformName, appOutDir } = context const { electronPlatformName, appOutDir } = context
if (electronPlatformName !== 'darwin') return if (electronPlatformName !== 'darwin') return
const { const {
+53 -44
View File
@@ -1,51 +1,60 @@
const fs = require('fs') // const fs = require('fs')
const fsPromises = require('fs').promises // const fsPromises = require('fs').promises
const path = require('path') // const path = require('path')
const { Arch } = require('electron-builder') const { Arch } = require('electron-builder')
const nodeAbi = require('node-abi') // const nodeAbi = require('node-abi')
const { beforePack, copyLib } = require('./deps')
const better_sqlite3_fileNameMap = { // const better_sqlite3_fileNameMap = {
[Arch.x64]: 'linux-x64', // [Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64', // [Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm', // [Arch.armv7l]: 'linux-arm',
// }
// const replaceSqliteLib = async(arch) => {
// // console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
// // if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
// // https://github.com/lyswhut/lx-music-desktop/issues/1102
// // https://github.com/lyswhut/lx-music-desktop/issues/1161
// console.log('replace sqlite lib...')
// const filePath = path.join(__dirname, `./lib/better_sqlite3_${better_sqlite3_fileNameMap[arch]}.node`)
// console.log(filePath)
// const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
// await fsPromises.unlink(targetPath).catch(_ => _)
// await fsPromises.copyFile(filePath, targetPath)
// }
const archMap = {
[Arch.x64]: 'x64',
[Arch.ia32]: 'ia32',
[Arch.arm64]: 'arm64',
[Arch.armv7l]: 'arm',
} }
const replaceSqliteLib = async(electronNodeAbi, arch) => {
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
// https://github.com/lyswhut/lx-music-desktop/issues/1102
// https://github.com/lyswhut/lx-music-desktop/issues/1161
console.log('replace sqlite lib...')
const filePath = path.join(__dirname, `./lib/better_sqlite3_electron-v${electronNodeAbi}-${better_sqlite3_fileNameMap[arch]}.node`)
console.log(filePath)
const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
await fsPromises.unlink(targetPath).catch(_ => _)
await fsPromises.copyFile(filePath, targetPath)
}
module.exports = async(context) => { module.exports = async(context) => {
const { electronPlatformName, arch } = context await beforePack()
const { arch } = context
const electronVersion = context.packager?.info?._framework?.version ?? require('../package.json').devDependencies.electron.replace(/^[^\d]*?(\d+)/, '$1') const electronVersion = context.packager?.info?._framework?.version ?? require('../package.json').devDependencies.electron.replace(/^[^\d]*?(\d+)/, '$1')
const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron') await copyLib(archMap[arch], parseInt(electronVersion) == 22)
if (electronPlatformName !== 'linux' || process.env.FORCE) return // const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp') // if (electronPlatformName !== 'linux' || process.env.FORCE) return
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak') // // const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
switch (arch) { // // const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
case Arch.x64: // switch (arch) {
case Arch.arm64: // case Arch.x64:
case Arch.armv7l: // case Arch.arm64:
if (fs.existsSync(bindingFilePath)) { // case Arch.armv7l:
// console.log('rename binding file...') // // if (fs.existsSync(bindingFilePath)) {
await fsPromises.rename(bindingFilePath, bindingBakFilePath) // // // console.log('rename binding file...')
} // // await fsPromises.rename(bindingFilePath, bindingBakFilePath)
await replaceSqliteLib(electronNodeAbi, arch) // // }
break // await replaceSqliteLib(arch)
// break
default: // default:
if (fs.existsSync(bindingFilePath)) return // // if (fs.existsSync(bindingFilePath)) return
// console.log('restore binding file...') // // console.log('restore binding file...')
await fsPromises.rename(bindingBakFilePath, bindingFilePath) // // await fsPromises.rename(bindingBakFilePath, bindingFilePath)
break // await copyLib(arch)
} // break
// }
} }
+21 -13
View File
@@ -6,20 +6,28 @@ const path = require('node:path')
const rootPath = path.join(__dirname, '../') const rootPath = path.join(__dirname, '../')
const patchs = [ const patchs = [
// [
// path.join(rootPath, './node_modules/ws/package.json'),
// '\n "browser": "./browser.js",',
// '',
// ],
// [
// path.join(rootPath, './node_modules/music-metadata/package.json'),
// '"default": "./lib/core.js"',
// '"default": "./lib/index.js"',
// ],
// [
// path.join(rootPath, './node_modules/strtok3/package.json'),
// '"default": "./lib/core.js"',
// '"default": "./lib/index.js"',
// ],
[ [
path.join(rootPath, './node_modules/ws/package.json'), path.join(rootPath, './node_modules/better-sqlite3/package.json'),
'\n "browser": "./browser.js",', `{
'', "build-release": "node-gyp clean && node-gyp rebuild --release --force_build=1",`,
], `{
[ "install": "node -e \\"process.exit(require('fs').existsSync('build/Release/better_sqlite3.node') ? 0 : 1)\\" || node-gyp rebuild --release --force_build=1",
path.join(rootPath, './node_modules/music-metadata/package.json'), "build-release": "node-gyp clean && node-gyp rebuild --release --force_build=1",`,
'"default": "./lib/core.js"',
'"default": "./lib/index.js"',
],
[
path.join(rootPath, './node_modules/strtok3/package.json'),
'"default": "./lib/core.js"',
'"default": "./lib/index.js"',
], ],
] ]
+55
View File
@@ -0,0 +1,55 @@
const fs = require('fs')
const path = require('path')
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
exports.beforePack = async() => {
if (!fs.existsSync(bindingFilePath)) return
fs.renameSync(bindingFilePath, bindingBakFilePath)
// try {
// fs.writeFileSync(
// bindingFilePath,
// fs.readFileSync(bindingFilePath, 'utf-8').replace('\'force_build%\': 0,', '\'force_build%\': 1,'),
// )
// } catch (error) {
// console.error(error)
// }
}
exports.afterPack = async() => {
if (fs.existsSync(bindingFilePath)) return
fs.renameSync(bindingBakFilePath, bindingFilePath)
// try {
// fs.writeFileSync(
// bindingFilePath,
// fs.readFileSync(bindingFilePath, 'utf-8').replace('\'force_build%\': 1,', '\'force_build%\': 0,'),
// )
// } catch (error) {
// console.error(error)
// }
}
const replaceSqliteLib = async(arch) => {
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
// https://github.com/lyswhut/lx-music-desktop/issues/1102
// https://github.com/lyswhut/lx-music-desktop/issues/1161
console.log('replace sqlite lib...')
const filePath = path.join(__dirname, `./lib/better_sqlite3_${process.platform}-${arch}.node`)
console.log(filePath)
const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
await fs.promises.unlink(targetPath).catch(_ => _)
await fs.promises.copyFile(filePath, targetPath)
}
exports.copyLib = async(arch = process.arch, replaceLocal = false) => {
if (process.platform === 'linux' || replaceLocal) {
await replaceSqliteLib(arch)
return
}
const libPath = path.join(__dirname, `../node_modules/better-sqlite3/prebuilds/${process.platform}-${arch}.node`)
if (!fs.existsSync(libPath)) {
console.error(`Better-sqlite3 prebuild not found for ${process.platform}-${arch}`)
return
}
const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
await fs.promises.cp(libPath, targetPath, { recursive: true, force: true })
}
+6 -9
View File
@@ -9,7 +9,7 @@ const getGzipFiles = async() => {
// for (const name of names) { // for (const name of names) {
// if (name.endsWith('.node')) await fs.promises.unlink(path.join(libDir, name)) // if (name.endsWith('.node')) await fs.promises.unlink(path.join(libDir, name))
// } // }
return names.filter(name => name.endsWith('.gz')) return names.filter((name) => name.endsWith('.gz'))
} }
const unzip = async(filePath) => { const unzip = async(filePath) => {
@@ -18,22 +18,20 @@ const unzip = async(filePath) => {
await fs.promises.mkdir(targetDir) await fs.promises.mkdir(targetDir)
await tar.x({ await tar.x({
file: filePath, file: filePath,
strip: 1, strip: 0,
C: targetDir, C: targetDir,
}) })
return targetDir return targetDir
} }
const files = [ const files = ['better_sqlite3']
'better_sqlite3',
]
const moveFile = async(filePath) => { const moveFile = async(filePath) => {
const name = 'electron-' + path.basename(filePath).split('-electron-')[1] const name = `${path.basename(filePath).split('_v')[0].replace('_', '-')}`
for (const fileName of files) { for (const fileName of files) {
if (fileName == 'better_sqlite3' && !name.includes('linux')) continue // if (fileName == 'better_sqlite3' && !name.includes('linux')) continue
const targetPath = path.join(libDir, `${fileName}_${name}.node`) const targetPath = path.join(libDir, `${fileName}_${name}.node`)
if (fs.existsSync(targetPath)) await fs.promises.unlink(targetPath) if (fs.existsSync(targetPath)) await fs.promises.unlink(targetPath)
await fs.promises.rename(path.join(filePath, 'Release', fileName + '.node'), targetPath) await fs.promises.rename(path.join(filePath, `${fileName}.node`), targetPath)
} }
await fs.promises.rm(filePath, { recursive: true }) await fs.promises.rm(filePath, { recursive: true })
} }
@@ -49,4 +47,3 @@ const run = async() => {
} }
run() run()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
const { copyLib } = require('./deps')
copyLib()
+1016 -784
View File
File diff suppressed because it is too large Load Diff
+32 -26
View File
@@ -1,6 +1,6 @@
{ {
"name": "lx-music-desktop", "name": "lx-music-desktop",
"version": "2.12.3-beta.5", "version": "2.12.4",
"description": "一个免费的音乐查找助手", "description": "一个免费的音乐查找助手",
"main": "./dist/main.js", "main": "./dist/main.js",
"scripts": { "scripts": {
@@ -63,7 +63,7 @@
"build:renderer-scripts": "cross-env NODE_ENV=production webpack --config build-config/renderer-scripts/webpack.config.prod.js --progress", "build:renderer-scripts": "cross-env NODE_ENV=production webpack --config build-config/renderer-scripts/webpack.config.prod.js --progress",
"lint": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly src", "lint": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly src",
"lint:fix": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly --fix src", "lint:fix": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly --fix src",
"postinstall": "electron-builder install-app-deps", "postinstall": "node build-config/postinstall.js",
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm run pack", "dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm i" "up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm i"
}, },
@@ -110,39 +110,39 @@
"@tsconfig/recommended": "^1.0.13", "@tsconfig/recommended": "^1.0.13",
"@types/better-sqlite3": "^7.6.13", "@types/better-sqlite3": "^7.6.13",
"@types/needle": "^3.3.0", "@types/needle": "^3.3.0",
"@types/node": "^20.19.41", "@types/node": "^20.19.43",
"@types/tunnel": "^0.0.7", "@types/tunnel": "^0.0.7",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@vue/language-plugin-pug": "^3.3.5", "@vue/language-plugin-pug": "^3.3.11",
"browserslist": "^4.28.2", "browserslist": "^4.28.9",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"changelog-parser": "^3.0.1", "changelog-parser": "3.0.1",
"copy-webpack-plugin": "^14.0.0", "copy-webpack-plugin": "^14.0.0",
"core-js": "^3.49.0", "core-js": "^3.50.0",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"css-loader": "^7.1.4", "css-loader": "^7.1.5",
"css-minimizer-webpack-plugin": "^8.0.0", "css-minimizer-webpack-plugin": "^8.0.0",
"del": "^6.1.1", "del": "^6.1.1",
"electron": "42.4.0", "electron": "42.11.3",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.7",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
"electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03", "electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03",
"electron-to-chromium": "^1.5.372", "electron-to-chromium": "^1.5.427",
"electron-updater": "6.8.9", "electron-updater": "6.8.9",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0", "eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^43.0.1", "eslint-config-standard-with-typescript": "^43.0.1",
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53", "eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
"eslint-plugin-html": "^8.1.4", "eslint-plugin-html": "^8.2.0",
"eslint-plugin-vue": "^9.33.0", "eslint-plugin-vue": "^9.33.0",
"eslint-plugin-vue-pug": "^0.6.2", "eslint-plugin-vue-pug": "^0.6.2",
"eslint-webpack-plugin": "^4.2.0", "eslint-webpack-plugin": "^4.2.0",
"html-webpack-plugin": "^5.6.7", "html-webpack-plugin": "^5.6.8",
"less": "^4.6.6", "less": "^4.9.1",
"less-loader": "^13.0.0", "less-loader": "^13.0.0",
"mini-css-extract-plugin": "^2.10.2", "mini-css-extract-plugin": "^2.10.2",
"node-loader": "^2.1.0", "node-loader": "^2.1.0",
"postcss": "^8.5.15", "postcss": "^8.5.28",
"postcss-loader": "^8.2.1", "postcss-loader": "^8.2.1",
"postcss-pxtorem": "^6.1.0", "postcss-pxtorem": "^6.1.0",
"pug": "^3.0.4", "pug": "^3.0.4",
@@ -152,42 +152,42 @@
"svg-sprite-loader": "^6.0.11", "svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13", "svg-transform-loader": "^2.0.13",
"svgo-loader": "^5.0.0", "svgo-loader": "^5.0.0",
"terser": "^5.48.0", "terser": "^5.51.2",
"terser-webpack-plugin": "^5.6.1", "terser-webpack-plugin": "^5.6.1",
"tree-kill": "^1.2.2", "tree-kill": "^1.2.2",
"ts-loader": "^9.6.0", "ts-loader": "^9.6.2",
"typescript": "5.9.3", "typescript": "5.9.3",
"vue-eslint-parser": "^9.4.3", "vue-eslint-parser": "^9.4.3",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
"webpack": "^5.107.2", "webpack": "5.107.2",
"webpack-cli": "^7.0.3", "webpack-cli": "^7.2.3",
"webpack-dev-server": "5.2.5", "webpack-dev-server": "5.2.6",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1", "webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^6.0.1" "webpack-merge": "^6.0.1"
}, },
"dependencies": { "dependencies": {
"@simonwep/pickr": "^1.9.1", "@simonwep/pickr": "^1.10.2",
"better-sqlite3": "^12.10.1", "better-sqlite3": "^13.0.3",
"bufferutil": "^4.1.0", "bufferutil": "^4.1.0",
"comlink": "~4.3.1", "comlink": "~4.3.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"electron-log": "^5.4.4", "electron-log": "^5.4.4",
"font-list": "^2.1.0", "font-list": "^2.1.0",
"iconv-lite": "^0.7.2", "iconv-lite": "^0.7.3",
"image-size": "^1.1.0", "image-size": "^1.1.0",
"jschardet": "^3.1.4", "jschardet": "^3.1.4",
"long": "^5.3.2", "long": "^5.3.2",
"message2call": "^0.1.3", "message2call": "^0.1.3",
"music-metadata": "^11.13.0", "music-metadata": "^11.15.0",
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060", "needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
"node-id3": "^0.2.9", "node-id3": "^0.2.9",
"sortablejs": "^1.15.7", "sortablejs": "^1.15.7",
"tunnel": "^0.0.6", "tunnel": "^0.0.6",
"undici": "^7.27.1", "undici": "^7.29.1",
"utf-8-validate": "^6.0.6", "utf-8-validate": "^6.0.6",
"vue": "~3.3.13", "vue": "~3.3.13",
"vue-router": "~4.5.1", "vue-router": "~4.5.1",
"ws": "^8.21.0" "ws": "^8.21.3"
}, },
"overrides": { "overrides": {
"got": "^11", "got": "^11",
@@ -213,5 +213,11 @@
"node-gyp-build": "latest", "node-gyp-build": "latest",
"micromatch": "latest", "micromatch": "latest",
"http-cache-semantics": "latest" "http-cache-semantics": "latest"
},
"allowScripts": {
"bufferutil": false,
"core-js": false,
"electron-winstaller": false,
"utf-8-validate": false
} }
} }
+5 -10
View File
@@ -1,13 +1,8 @@
### 优化 目前新项目 Any Listen 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
- 优化kw歌单列表数据显示 对于日常使用 LX Music 的人可以试试迁移到 Any Listen,若遇到任何问题可以发 issue 反馈。
- 同步服务在连接时允许URL重定向 Any Listen 的项目地址为 https://github.com/any-listen/any-listen
- Linux 点击托盘时将显示主界面(#2840)
### 修复 ### 修复
- 修复 kg 搜索结果显示问题 (#2782) - 修复自定义主题背景图片无法显示的问题
- 修复某些情况下开放 API 获取到的音量为 0 的问题 (#2790)
- 修复 mg 图片、歌词获取
- 修复某些 tx 歌单打开失败的问题 (#1060, @ght-000)
- 修复在某些情况下添加歌曲时可能导致保存的歌曲顺序不对的问题(#2842)
+1 -1
View File
@@ -34,7 +34,7 @@ function fm(value) {
exports.sizeFormate = size => { exports.sizeFormate = size => {
// https://gist.github.com/thomseddon/3511330 // https://gist.github.com/thomseddon/3511330
if (!size) return '0 b' if (!size) return '0 b'
let units = ['b', 'kB', 'MB', 'GB', 'TB'] let units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
let number = Math.floor(Math.log(size) / Math.log(1024)) let number = Math.floor(Math.log(size) / Math.log(1024))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}` return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
} }
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -58,6 +58,7 @@ declare namespace LX {
qualitys: MusicQualityTypeKg[] qualitys: MusicQualityTypeKg[]
_qualitys: _MusicQualityTypeKg _qualitys: _MusicQualityTypeKg
hash: string // 歌曲hash hash: string // 歌曲hash
albumAudioId?: string // 专辑内歌曲ID(MixSongID),用于歌词精确搜索
} }
interface MusicInfo_kg extends MusicInfoBase<'kg'> { interface MusicInfo_kg extends MusicInfoBase<'kg'> {
meta: MusicInfoMeta_kg meta: MusicInfoMeta_kg
+1 -1
View File
@@ -12,7 +12,7 @@ export const getRandom = (min: number, max: number): number => Math.floor(Math.r
export const sizeFormate = (size: number): string => { export const sizeFormate = (size: number): string => {
// https://gist.github.com/thomseddon/3511330 // https://gist.github.com/thomseddon/3511330
if (!size) return '0 B' if (!size) return '0 B'
let units = ['B', 'KB', 'MB', 'GB', 'TB'] let units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
let number = Math.floor(Math.log(size) / Math.log(1024)) let number = Math.floor(Math.log(size) / Math.log(1024))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}` return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
} }
@@ -2,6 +2,7 @@ import { getNow, TimeoutTools } from './utils'
const timeFieldExp = /^(?:\[[\d:.]+\])+/g const timeFieldExp = /^(?:\[[\d:.]+\])+/g
const timeExp = /\d{1,3}(:\d{1,3}){0,2}(?:\.\d{1,3})/g const timeExp = /\d{1,3}(:\d{1,3}){0,2}(?:\.\d{1,3})/g
const msTimeRxp = /\[\d{1,3}(:\d{1,3}){0,2}\.\d{3}]/
const tagRegMap = { const tagRegMap = {
title: 'ti', title: 'ti',
artist: 'ar', artist: 'ar',
@@ -82,6 +83,7 @@ export default class LinePlayer {
_initLines() { _initLines() {
this.lines = [] this.lines = []
const isMsTime = msTimeRxp.test(this.lyric)
const lines = this.lyric.split(/\r\n|\r|\n/) const lines = this.lyric.split(/\r\n|\r|\n/)
const linesMap = {} const linesMap = {}
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
@@ -103,9 +105,10 @@ export default class LinePlayer {
if (timeArr.length > 3) continue if (timeArr.length > 3) continue
else if (timeArr.length < 3) for (let i = 3 - timeArr.length; i--;) timeArr.unshift('0') else if (timeArr.length < 3) for (let i = 3 - timeArr.length; i--;) timeArr.unshift('0')
if (timeArr[2].indexOf('.') > -1) timeArr.splice(2, 1, ...timeArr[2].split('.')) if (timeArr[2].indexOf('.') > -1) timeArr.splice(2, 1, ...timeArr[2].split('.'))
const msTime = timeArr[3] || '0'
linesMap[timeStr] = { linesMap[timeStr] = {
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0), time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(isMsTime ? msTime : msTime.padEnd(3, '0')),
text, text,
extendedLyrics: [], extendedLyrics: [],
} }
+1 -1
View File
@@ -42,7 +42,7 @@ const parseLyric = str => {
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => { let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
let result = str.match(/\[((\d+),\d+)\].*/) let result = str.match(/\[((\d+),\d+)\].*/)
let time = parseInt(result[2]) let time = parseInt(result[2])
let ms = time % 1000 let ms = (time % 1000).toString().padStart(3, '0')
time /= 1000 time /= 1000
let m = parseInt(time / 60).toString().padStart(2, '0') let m = parseInt(time / 60).toString().padStart(2, '0')
time %= 60 time %= 60
+2
View File
@@ -35,6 +35,7 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
switch (oldMusicInfo.source) { switch (oldMusicInfo.source) {
case 'kg': case 'kg':
meta.hash = oldMusicInfo.hash meta.hash = oldMusicInfo.hash
meta.albumAudioId = oldMusicInfo.albumAudioId
newInfo.id = oldMusicInfo.songmid + '_' + oldMusicInfo.hash newInfo.id = oldMusicInfo.songmid + '_' + oldMusicInfo.hash
break break
case 'tx': case 'tx':
@@ -79,6 +80,7 @@ export const toOldMusicInfo = (minfo: LX.Music.MusicInfo) => {
switch (minfo.source) { switch (minfo.source) {
case 'kg': case 'kg':
oInfo.hash = minfo.meta.hash oInfo.hash = minfo.meta.hash
oInfo.albumAudioId = minfo.meta.albumAudioId
break break
case 'tx': case 'tx':
oInfo.strMediaMid = minfo.meta.strMediaMid oInfo.strMediaMid = minfo.meta.strMediaMid
+14 -6
View File
@@ -1,14 +1,28 @@
import zh_cn from './zh-cn.json' import zh_cn from './zh-cn.json'
import zh_tw from './zh-tw.json' import zh_tw from './zh-tw.json'
import en_us from './en-us.json' import en_us from './en-us.json'
import ko_kr from './ko-kr.json'
type Message = Record<keyof typeof zh_cn, string> type Message = Record<keyof typeof zh_cn, string>
| Record<keyof typeof zh_tw, string> | Record<keyof typeof zh_tw, string>
| Record<keyof typeof en_us, string> | Record<keyof typeof en_us, string>
| Record<keyof typeof ko_kr, string>
type Messages = Record<(typeof langs)[number]['locale'], Message> type Messages = Record<(typeof langs)[number]['locale'], Message>
const langs = [ const langs = [
{
name: 'English',
locale: 'en-us',
country: 'us',
message: en_us,
},
{
name: '한국어',
locale: 'ko-kr',
country: 'kr',
message: ko_kr,
},
{ {
name: '简体中文', name: '简体中文',
locale: 'zh-cn', locale: 'zh-cn',
@@ -24,12 +38,6 @@ const langs = [
country: 'cn', country: 'cn',
message: zh_tw, message: zh_tw,
}, },
{
name: 'English',
locale: 'en-us',
country: 'us',
message: en_us,
},
] as const ] as const
const langList: Array<{ const langList: Array<{
+748
View File
@@ -0,0 +1,748 @@
{
"action": "동작",
"agree": "동의",
"alert_button_text": "확인",
"audio_visualization": "오디오 시각화 (실험적 기능)",
"back": "뒤로",
"btn_cancel": "취소",
"btn_close": "닫기",
"btn_confirm": "확인",
"btn_save": "저장",
"cancel_button_text": "취소",
"cancel_button_text_2": "아니요, 잘못 눌렀어요",
"close": "닫기",
"comment__hot_load_error": "인기 댓글을 불러오지 못했습니다. 클릭하여 다시 시도하세요.",
"comment__hot_loading": "인기 댓글을 불러오는 중...",
"comment__hot_title": "인기 댓글",
"comment__location": "{location}에서",
"comment__new_load_error": "최신 댓글을 불러오지 못했습니다. 클릭하여 다시 시도하세요.",
"comment__new_loading": "최신 댓글을 불러오는 중...",
"comment__new_title": "최신 댓글",
"comment__no_content": "아직 댓글이 없습니다",
"comment__refresh": "댓글 새로고침",
"comment__show": "노래 댓글",
"comment__title": "\"{name}\"의 댓글",
"comment__unavailable": "이 노래의 댓글을 가져올 수 없습니다.",
"confirm_button_text": "예",
"copy_tip": " (클릭하여 복사)",
"date_format_hour": "{num}시간 전",
"date_format_minute": "{num}분 전",
"date_format_second": "{num}초 전",
"deep_link__handle_error_tip": "호출 실패: {message}",
"default": "기본값",
"default_list": "기본 목록",
"desktop_lyric__back": "뒤로",
"desktop_lyric__close": "닫기",
"desktop_lyric__font_decrease": "글자 크기 줄이기",
"desktop_lyric__font_increase": "글자 크기 늘리기",
"desktop_lyric__lock": "잠금",
"desktop_lyric__lrc_active_zoom_off": "재생 중인 가사 확대 해제",
"desktop_lyric__lrc_active_zoom_on": "재생 중인 가사 확대",
"desktop_lyric__opacity_decrease": "투명도 높이기\n(우클릭하여 세밀 조정)",
"desktop_lyric__opacity_increase": "투명도 낮추기\n(우클릭하여 세밀 조정)",
"desktop_lyric__theme": "테마 색상",
"desktop_lyric__unlock": "잠금 해제",
"desktop_lyric__win_top_off": "항상 위 유지",
"desktop_lyric__win_top_on": "항상 위 해제",
"download": "다운로드",
"download___status_completed": "다운로드 완료",
"download___status_error": "오류",
"download___status_paused": "일시정지됨",
"download___status_running": "다운로드 중",
"download___status_waiting": "다운로드 대기 중",
"download__all": "전체 작업",
"download__error": "오류",
"download__finished": "완료",
"download__high_quality": "고음질",
"download__lossless": "무손실",
"download__multiple_tip": "{len}곡 선택됨",
"download__multiple_tip2": "원하는 다운로드 음질을 선택하세요",
"download__normal": "일반",
"download__not_available_tip": "해당 음질을 사용할 수 없습니다.",
"download__paused": "일시정지됨",
"download__progress": "진행률",
"download__quality": "음질",
"download__running": "다운로드 중",
"download__status": "상태",
"download_status_error_check_path": "다운로드 경로 확인 중 오류가 발생했습니다. 설정한 다운로드 폴더가 정상인지 확인해 주세요",
"download_status_error_check_path_exist": "동일한 이름의 파일이 존재하여 다운로드를 건너뛰었습니다",
"download_status_error_refresh_url": "링크가 만료되어 새로고침 중...",
"download_status_error_response": "다운로드 실패: ",
"download_status_error_url_failed": "노래 링크를 가져오지 못했습니다",
"download_status_error_write": "다운로드 위치가 사용 중이거나 쓰기 권한이 없습니다. 다운로드 폴더를 변경하거나 앱/기기를 재시작해 보세요. 오류 상세: ",
"download_status_start": "다운로드 시작",
"download_status_url_getting": "노래 링크를 가져오는 중...",
"download_status_write_queue": "데이터 쓰는 중 ({num})",
"duplicate_list_tip": "이미 \"{name}\" 목록을 담은 적이 있습니다. 목록의 곡을 업데이트하시겠습니까?",
"export": "내보내기",
"fullscreen_exit": "전체화면 종료",
"history_clear": "기록 지우기",
"history_remove": "우클릭하여 이 항목 삭제",
"history_search": "검색 기록",
"import": "가져오기",
"leaderboard": "차트",
"list__add_to": "추가할 위치...",
"list__collect": "담기",
"list__copy_name": "이름 복사",
"list__dislike": "싫어요",
"list__download": "다운로드",
"list__export_part_desc": "목록 파일을 저장할 위치를 선택하세요",
"list__file": "파일 위치 열기",
"list__import_part_button_cancel": "취소",
"list__import_part_button_confirm": "덮어쓰기",
"list__import_part_confirm": "가져온 목록({importName})의 ID가 로컬 목록({localName})과 동일합니다. 로컬 목록을 덮어쓰시겠습니까?",
"list__import_part_desc": "목록 파일을 선택하세요",
"list__load_failed": "이런, 불러오기에 실패했습니다 😭",
"list__loading": "목록을 불러오는 중...⏳",
"list__move_to": "이동할 위치...",
"list__movedown": "아래로 이동",
"list__moveup": "위로 이동",
"list__name_default": "기본 목록",
"list__name_love": "좋아요",
"list__new_list_btn": "새 목록",
"list__new_list_input": "새 목록...",
"list__pause": "정지",
"list__play": "재생",
"list__play_later": "다음에 재생",
"list__remove": "삭제",
"list__remove_tip": "\"{name}\"을(를) 정말 삭제하시겠습니까?",
"list__remove_tip_button": "예, 맞습니다",
"list__rename": "이름 변경",
"list__search": "검색",
"list__sort": "순서 조정",
"list__source_detail": "노래 상세정보",
"list__start": "재개",
"list__sync": "업데이트",
"list__toggle_source": "소스 변경",
"list_add__btn_title": "\"{name}\"에 곡 추가",
"list_add__multiple_btn_title": "이 곡들을 \"{name}\"에 추가",
"list_add__multiple_title_add": "선택한 {num}곡을 다음 위치에 추가...",
"list_add__multiple_title_move": "선택한 {num}곡을 다음 위치로 이동...",
"list_add__title_first_add": "추가",
"list_add__title_first_move": "이동",
"list_add__title_last": "위치로...",
"list_duplicate_tip": "동일한 이름의 목록이 이미 존재합니다. 계속 생성하시겠습니까?",
"list_import_tip__alldata": "가져오기에 실패했습니다. 이 파일은 \"전체 데이터\" 백업 파일입니다. 다음 위치에서 가져와야 합니다: \n\n설정 → 백업 및 복원 → 전체 데이터 → 가져오기",
"list_import_tip__playlist": "가져오기에 실패했습니다. 이 파일은 \"목록\" 백업 파일입니다. 다음 위치에서 가져와야 합니다: \n\n설정 → 백업 및 복원 → 일부 데이터 → 목록 가져오기",
"list_import_tip__playlist_part": "가져오기에 실패했습니다. 이 파일은 \"목록 전용\" 백업 파일입니다. 다음 위치에서 가져와야 합니다: \n\n내 서재 → 아무 목록 이름에서 우클릭 → 메뉴에서 '가져오기' 선택",
"list_import_tip__setting": "가져오기에 실패했습니다. 이 파일은 \"설정\" 백업 파일입니다. 다음 위치에서 가져와야 합니다: \n\n설정 → 백업 및 복원 → 일부 데이터 → 설정 가져오기",
"list_import_tip__unknown": "가져오기에 실패했습니다. 알 수 없는 파일 형식입니다. 앱을 최신 버전으로 업데이트한 후 다시 시도해 주세요.",
"list_sort_modal_by_album": "앨범",
"list_sort_modal_by_down": "내림차순",
"list_sort_modal_by_field": "정렬 기준",
"list_sort_modal_by_name": "제목",
"list_sort_modal_by_random": "무작위",
"list_sort_modal_by_singer": "아티스트",
"list_sort_modal_by_source": "음악 서비스",
"list_sort_modal_by_time": "길이",
"list_sort_modal_by_type": "정렬 항목",
"list_sort_modal_by_up": "오름차순",
"list_sort_modal_tip_confirm": "정말로 실행하시겠습니까?",
"list_update_modal__auto_update": "자동 업데이트",
"list_update_modal__tips": "💡 \"자동 업데이트\"가 체크된 목록은 앱을 시작할 때마다 자동으로 업데이트됩니다.",
"list_update_modal__title": "목록 업데이트 관리",
"list_update_modal__update": "동기화",
"lists__add_local_file_desc": "노래 파일을 선택하세요",
"lists__dislike_music_singer_tip": "{singer}의 \"{name}\"을(를) 정말 싫어요 처리하시겠습니까?",
"lists__dislike_music_tip": "\"{name}\"을(를) 정말 싫어요 처리하시겠습니까?",
"lists__duplicate": "중복된 곡",
"lists__export": "내보내기",
"lists__export_part_desc": "목록 파일을 저장할 위치를 선택하세요",
"lists__import": "가져오기",
"lists__import_part_button_cancel": "아니요",
"lists__import_part_button_confirm": "덮어쓰기",
"lists__import_part_confirm": "가져온 목록({importName})의 ID가 로컬 목록({localName})과 동일합니다. 로컬 목록을 덮어쓰시겠습니까?",
"lists__import_part_desc": "목록 파일을 선택하세요",
"lists__new_list_btn": "목록 만들기",
"lists__new_list_input": "새 목록...",
"lists__remove": "삭제",
"lists__remove_music_tip": "선택한 {len}곡을 정말 삭제하시겠습니까?",
"lists__remove_tip": "\"{name}\"을(를) 정말 삭제하시겠습니까?",
"lists__remove_tip_button": "예, 맞습니다",
"lists__rename": "이름 변경",
"lists__select_local_file": "로컬 곡 추가",
"lists__sort_list": "곡 정렬",
"lists__source_detail": "재생목록 페이지",
"lists__sync": "업데이트",
"lists__sync_confirm_tip": "\"{name}\"의 곡을 온라인 목록의 곡으로 교체합니다. 업데이트하시겠습니까?",
"load_list_file_error_detail": "이전 목록 파일을 {path}에 백업해 두었습니다.\nJSON 형식으로 저장되어 있으며, 수동으로 복구를 시도할 수 있습니다.\n\n오류 상세: {detail}",
"load_list_file_error_title": "재생목록 데이터 로드 오류",
"loading": "불러오는 중...",
"love_list": "즐겨찾기",
"lyric__load_error": "가사를 가져오지 못했습니다",
"lyric__select": "가사 텍스트 선택 허용",
"lyric_menu__align": "가사 정렬",
"lyric_menu__align_center": "가운데",
"lyric_menu__align_left": "왼쪽",
"lyric_menu__lrc_size": "글자 크기 [{size}]",
"lyric_menu__offset": "오프셋 [{offset}ms]",
"lyric_menu__offset_add_10": "10ms 늘리기",
"lyric_menu__offset_add_100": "100ms 늘리기",
"lyric_menu__offset_dec_10": "10ms 줄이기",
"lyric_menu__offset_dec_100": "100ms 줄이기",
"lyric_menu__offset_reset": "초기화",
"lyric_menu__size_add": "글자 크기 늘리기\n(우클릭하여 세밀 조정)",
"lyric_menu__size_dec": "글자 크기 줄이기\n(우클릭하여 세밀 조정)",
"lyric_menu__size_reset": "초기화",
"media_device__empty_device_tip": "오디오 출력 장치가 비어 있습니다. 재생이 되지 않는다면 사운드 카드 드라이버가 설치되어 있는지 확인해 주세요. \n또는 소프트웨어를 제거한 후 재설치할 때 \"나만 사용\"을 선택하고 기본 설치 경로를 유지해 보세요.",
"min": "최소화",
"music_album": "앨범",
"music_duplicate": "중복된 곡",
"music_name": "제목",
"music_singer": "아티스트",
"music_sort__input_tip": "이동할 위치를 입력하세요",
"music_sort__title": "\"{name}\"의 위치를 다음으로 조정: ",
"music_sort__title_multiple": "선택한 {num}곡의 위치를 다음으로 조정: ",
"music_time": "길이",
"music_toggle_clean": "변경 취소",
"music_toggle_confirm": "확인",
"music_toggle_duplicate_tip": "목록에 동일한 곡이 이미 존재합니다. 삭제하고 계속할까요?",
"my_list": "내 서재",
"no_item": "아무것도 없습니다...",
"not_agree": "거부",
"ok": "확인",
"pagination__next": "다음 페이지",
"pagination__page": "{num} 페이지",
"pagination__prev": "이전 페이지",
"play_timeout": "타이머 정지",
"play_timeout_close": "닫기",
"play_timeout_confirm": "확인",
"play_timeout_end": "곡이 끝난 후 정지",
"play_timeout_stop": "타이머 취소",
"play_timeout_tip": "{time} 후 정지",
"play_timeout_unit": "분",
"play_timeout_update": "시간 업데이트",
"player__add_music_to": "현재 곡을 다음 위치에 추가...",
"player__buffering": "버퍼링 중...",
"player__desktop_lyric_lock": "우클릭하여 가사창 잠금",
"player__desktop_lyric_off": "가사창 숨기기",
"player__desktop_lyric_on": "가사창 표시",
"player__desktop_lyric_unlock": "우클릭하여 가사창 잠금 해제",
"player__end": "정지됨",
"player__error": "음악을 불러오는 중 오류가 발생했습니다. 5초 후 다음 곡으로 전환합니다",
"player__getting_url": "음악 링크를 가져오는 중...",
"player__getting_url_delay_retry": "서버가 혼잡합니다. {time}초 후 다시 시도합니다...",
"player__hide_detail_tip": "상세 페이지 숨기기\n(앱 창에서 우클릭하면 상세 페이지를 빠르게 숨길 수 있습니다)",
"player__loading": "음악을 불러오는 중...",
"player__music_album": "앨범: ",
"player__music_name": "제목: ",
"player__music_singer": "아티스트: ",
"player__next": "다음 곡",
"player__pause": "일시정지",
"player__pic_tip": "재생 상세 페이지\n(우클릭하면 \"내 서재\"에서 현재 재생 중인 곡을 찾을 수 있습니다)",
"player__play": "재생",
"player__play_toggle_mode_list": "순서대로",
"player__play_toggle_mode_list_loop": "목록 반복",
"player__play_toggle_mode_off": "사용 안 함",
"player__play_toggle_mode_random": "무작위 재생",
"player__play_toggle_mode_single_loop": "한 곡 반복",
"player__playback_preserves_pitch": "음정 보정",
"player__playback_rate": "현재 재생 속도: ",
"player__playback_rate_reset_btn": "초기화",
"player__playing": "재생 중...",
"player__prev": "이전 곡",
"player__refresh_url": "음악 URL이 만료되어 새로고침 중...",
"player__sound_effect": "음향 옵션 (실험적 기능)",
"player__sound_effect_biquad_filter": "이퀄라이저",
"player__sound_effect_biquad_filter_preset_classical": "클래식",
"player__sound_effect_biquad_filter_preset_dance": "댄스",
"player__sound_effect_biquad_filter_preset_electronic": "일렉트로닉",
"player__sound_effect_biquad_filter_preset_pop": "팝",
"player__sound_effect_biquad_filter_preset_rock": "록",
"player__sound_effect_biquad_filter_preset_slow": "슬로우",
"player__sound_effect_biquad_filter_preset_soft": "소프트",
"player__sound_effect_biquad_filter_preset_subwoofer": "서브우퍼",
"player__sound_effect_biquad_filter_preset_vocal": "보컬",
"player__sound_effect_biquad_filter_reset_btn": "초기화",
"player__sound_effect_biquad_filter_save_btn": "프리셋 다른 이름으로 저장",
"player__sound_effect_biquad_filter_save_input": "새 프리셋...",
"player__sound_effect_convolution": "환경 리버브 음향 효과",
"player__sound_effect_convolution_file_bright_hall": "홀",
"player__sound_effect_convolution_file_cardiod_35_10_spread": "하트형 확산",
"player__sound_effect_convolution_file_cinema_diningroom": "영화관",
"player__sound_effect_convolution_file_dining_living_true_stereo": "식당",
"player__sound_effect_convolution_file_feedback_spring": "피드백 스프링",
"player__sound_effect_convolution_file_living_bedroom_leveled": "욕실",
"player__sound_effect_convolution_file_matrix_1": "매트릭스 (1)",
"player__sound_effect_convolution_file_matrix_2": "매트릭스 (2)",
"player__sound_effect_convolution_file_s2_r4_bd": "교회",
"player__sound_effect_convolution_file_s3_r1_bd": "스테레오",
"player__sound_effect_convolution_file_spreader50_65ms": "실내",
"player__sound_effect_convolution_file_telephone": "전화기",
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "마그네틱 스테레오",
"player__sound_effect_convolution_main_gain": "원음 게인",
"player__sound_effect_convolution_send_gain": "환경 음향 효과 게인",
"player__sound_effect_features_tip": "안내: 음향 옵션은 사용자 지정 오디오 출력 장치와 충돌합니다. 음향 옵션을 활성화하면 오디오 출력 장치가 기본 출력 장치로 재설정됩니다. 현재로서는 이 문제를 해결할 수 없습니다.",
"player__sound_effect_panner": "3D 입체 서라운드 (헤드폰 사용 필요)",
"player__sound_effect_panner_enabled": "사용",
"player__sound_effect_panner_sound_r": "음원 거리",
"player__sound_effect_panner_sound_speed": "서라운드 속도",
"player__sound_effect_pitch_shifter": "음정 조정",
"player__sound_effect_pitch_shifter_preset_semitones": "{num}반음",
"player__sound_effect_pitch_shifter_reset_btn": "초기화",
"player__sound_effect_pitch_shifter_tip": "음정을 높이거나 낮추려면 오디오 데이터를 실시간으로 처리해야 하므로 CPU 사용량이 추가로 발생합니다.\n\n알려진 문제:\nCPU 자원이 부족하면 처리 작업이 쌓여 소리에 이상이 발생할 수 있습니다.\n이 경우 재생을 잠시 멈추고 쌓인 작업이 처리될 때까지 기다린 후 재생해야 합니다.",
"player__stop": "일시정지됨",
"player__volume": "볼륨: ",
"player__volume_mute_label": "음소거",
"player__volume_muted": "음소거됨",
"search": "검색",
"search__hot_search": "인기 검색어",
"search__type_music": "노래",
"search__type_songlist": "재생목록",
"search__welcome": "원하는 것을 검색해보세요~~ 😉",
"setting": "설정",
"setting__about": "LX Music 정보",
"setting__backup": "백업 및 복원",
"setting__backup_all": "전체 데이터 (\"목록\" 데이터와 \"설정\" 데이터)",
"setting__backup_all_export": "내보내기",
"setting__backup_all_export_desc": "백업을 저장할 위치...",
"setting__backup_all_import": "가져오기",
"setting__backup_all_import_desc": "백업 파일을 선택하세요",
"setting__backup_other": "기타 백업 형식 (해당 백업 파일의 복원은 현재 지원하지 않습니다)",
"setting__backup_other_export_dir": "파일을 저장할 위치를 선택하세요",
"setting__backup_other_export_list_csv": "CSV 형식으로 목록 내보내기",
"setting__backup_other_export_list_text": "TXT 형식으로 목록 내보내기",
"setting__backup_other_export_list_text_confirm": "모든 목록을 하나의 파일로 합치시겠습니까?",
"setting__backup_part": "일부 데이터 (\"목록\" 데이터에는 \"기본\", \"좋아요\" 및 사용자가 만든 목록이 포함됩니다. \"설정\" 데이터에는 \"단축키\" 설정이 포함되지 않습니다)",
"setting__backup_part_export_list": "목록 내보내기",
"setting__backup_part_export_list_desc": "목록을 저장할 위치...",
"setting__backup_part_export_setting": "설정 내보내기",
"setting__backup_part_export_setting_desc": "설정을 저장할 위치...",
"setting__backup_part_import_list": "목록 가져오기",
"setting__backup_part_import_list_confirm": "백업 파일의 목록이 기존 목록과 동일한 ID를 가지고 있으면 기존 목록의 곡이 덮어써집니다. 계속하시겠습니까?",
"setting__backup_part_import_list_desc": "목록 백업 파일을 선택하세요",
"setting__backup_part_import_setting": "설정 가져오기",
"setting__backup_part_import_setting_desc": "설정 백업 파일을 선택하세요",
"setting__basic": "일반",
"setting__basic_animation": "팝업 애니메이션 무작위 적용",
"setting__basic_control_btn_position": "컨트롤 버튼 위치",
"setting__basic_control_btn_position_left": "왼쪽",
"setting__basic_control_btn_position_right": "오른쪽",
"setting__basic_font": "글꼴",
"setting__basic_font_size": "글자 크기",
"setting__basic_font_size_14px": "더 작게",
"setting__basic_font_size_15px": "작게",
"setting__basic_font_size_16px": "표준",
"setting__basic_font_size_17px": "크게",
"setting__basic_font_size_18px": "더 크게",
"setting__basic_font_size_19px": "매우 크게",
"setting__basic_lang": "언어",
"setting__basic_lang_title": "앱에 표시되는 언어",
"setting__basic_playbar_progress_style": "재생바 진행바 스타일",
"setting__basic_playbar_progress_style_full": "전체 너비",
"setting__basic_playbar_progress_style_middle": "중간",
"setting__basic_playbar_progress_style_mini": "미니",
"setting__basic_show_animation": "애니메이션 표시",
"setting__basic_source": "음악 API",
"setting__basic_source_status_failed": "초기화 실패",
"setting__basic_source_status_initing": "초기화 중...",
"setting__basic_source_status_success": "초기화 성공",
"setting__basic_source_temp": "임시 API (일부 기능 사용 불가. 테스트 API를 사용할 수 없을 때의 대안)",
"setting__basic_source_test": "테스트 API (대부분의 앱 기능 사용 가능)",
"setting__basic_source_user_api_btn": "음악 API 관리",
"setting__basic_sourcename": "음악 스트리밍 서비스 이름",
"setting__basic_sourcename_alias": "별칭",
"setting__basic_sourcename_real": "원래 이름",
"setting__basic_sourcename_title": "음악 스트리밍 서비스 이름 표시 방식을 선택하세요",
"setting__basic_start_in_fullscreen": "전체화면 모드로 시작",
"setting__basic_theme": "테마",
"setting__basic_theme_auto_tip": "이것은 동적 테마입니다. 라이트 테마와 다크 테마를 미리 설정해 두면 시스템의 라이트/다크 모드에 따라 자동으로 해당 테마로 전환됩니다.\n\n참고: 이 테마 항목을 우클릭하면 라이트/다크 테마 설정 창이 열립니다.",
"setting__basic_to_tray": "앱 창을 닫을 때 시스템 트레이로 최소화",
"setting__basic_window_size": "창 크기",
"setting__basic_window_size_big": "크게",
"setting__basic_window_size_huge": "아주 크게",
"setting__basic_window_size_larger": "더 크게",
"setting__basic_window_size_medium": "중간",
"setting__basic_window_size_oversized": "매우 크게",
"setting__basic_window_size_small": "작게",
"setting__basic_window_size_smaller": "더 작게",
"setting__basic_window_size_title": "창 크기를 설정하세요",
"setting__click_copy": "클릭하여 복사",
"setting__click_open": "클릭하여 열기",
"setting__desktop_lyric": "데스크톱 가사",
"setting__desktop_lyric_align": "가사 정렬",
"setting__desktop_lyric_align_center": "가운데",
"setting__desktop_lyric_align_left": "왼쪽",
"setting__desktop_lyric_align_right": "오른쪽",
"setting__desktop_lyric_always_on_top": "항상 위 유지",
"setting__desktop_lyric_always_on_top_loop": "\"항상 위 유지\"가 활성화된 경우 가사창을 반복적으로 새로고침",
"setting__desktop_lyric_always_on_top_loop_tip": "다른 프로그램에 가사창이 계속 가려질 경우 이 옵션을 활성화해 보세요.",
"setting__desktop_lyric_audio_visualization": "오디오 시각화 활성화 (실험적 기능)",
"setting__desktop_lyric_color": "가사 글자 색상",
"setting__desktop_lyric_color_reset": "색상 초기화",
"setting__desktop_lyric_delay_scroll": "가사 스크롤 지연",
"setting__desktop_lyric_direction": "가사 표시 방향",
"setting__desktop_lyric_direction_horizontal": "가로",
"setting__desktop_lyric_direction_vertical": "세로",
"setting__desktop_lyric_ellipsis": "가사 줄바꿈 안 함",
"setting__desktop_lyric_enable": "가사창 표시",
"setting__desktop_lyric_font": "가사 글꼴",
"setting__desktop_lyric_font_default": "기본값",
"setting__desktop_lyric_font_weight": "굵은 글꼴",
"setting__desktop_lyric_font_weight_extended": "번역 및 로마자 가사",
"setting__desktop_lyric_font_weight_font": "글자 단위 가사",
"setting__desktop_lyric_font_weight_line": "줄 단위 가사",
"setting__desktop_lyric_fullscreen_hide": "전체화면일 때 가사창 숨기기",
"setting__desktop_lyric_hover_hide": "마우스가 가사창 위로 이동하면 가사창 투명도 높이기",
"setting__desktop_lyric_hover_hide_tip": "이 기능은 플랫폼 호환성 문제가 있을 수 있습니다.",
"setting__desktop_lyric_line_gap": "가사 간격 ({num})",
"setting__desktop_lyric_line_gap_add": "간격 늘리기",
"setting__desktop_lyric_line_gap_dec": "간격 줄이기",
"setting__desktop_lyric_lock": "가사창 잠금",
"setting__desktop_lyric_lock_screen": "가사창을 메인 화면 밖으로 드래그하지 못하도록 제한",
"setting__desktop_lyric_pause_hide": "음악을 일시정지하면 가사창 투명도 높이기",
"setting__desktop_lyric_played_color": "재생된 부분",
"setting__desktop_lyric_reset": "초기화",
"setting__desktop_lyric_reset_window": "데스크톱 가사 옵션 초기화",
"setting__desktop_lyric_scroll_align": "재생 중 가사 스크롤 위치",
"setting__desktop_lyric_scroll_align_center": "가운데",
"setting__desktop_lyric_scroll_align_top": "위쪽",
"setting__desktop_lyric_shadow_color": "그림자",
"setting__desktop_lyric_show_taskbar": "작업 표시줄에 가사창 프로세스 표시",
"setting__desktop_lyric_show_taskbar_tip": "화면 녹화 프로그램이 가사창을 캡처하지 못할 때 대안으로 사용하는 옵션입니다.",
"setting__desktop_lyric_unplay_color": "재생 안 된 부분",
"setting__dislike_list_input_tip": "곡명@아티스트명\n곡명\n@아티스트명",
"setting__dislike_list_save_btn": "저장",
"setting__dislike_list_tips": "1. 한 줄에 하나의 항목을 입력합니다. 곡명이나 아티스트명에 \"@\" 기호가 있으면 \"#\"로 대체해야 합니다\n2. 특정 아티스트의 곡 지정: 곡명@아티스트명\n3. 곡 지정: 곡명\n4. 아티스트 지정: @아티스트명",
"setting__dislike_list_title": "싫어요 곡 규칙 목록",
"setting__download": "다운로드",
"setting__download_data_embed": "다음 항목을 오디오 파일에 삽입",
"setting__download_embed_lxlyric": "LX Music 가사도 함께 삽입 (있는 경우, LX Music으로 재생 시 글자 단위 가사를 지원합니다)",
"setting__download_embed_lyric": "가사 삽입",
"setting__download_embed_pic": "커버 이미지 삽입",
"setting__download_embed_rlyric": "가능한 경우 로마자 가사도 함께 삽입",
"setting__download_embed_tlyric": "가능한 경우 번역 가사도 함께 삽입",
"setting__download_enable": "다운로드 활성화",
"setting__download_lxlyric": "LX Music 가사도 가사 파일에 함께 기록 (있는 경우, LX Music으로 재생 시 글자 단위 가사를 지원합니다)",
"setting__download_lyric": "가사 다운로드",
"setting__download_lyric_format": "다운로드한 가사 파일의 인코딩 형식",
"setting__download_lyric_format_gbk": "GBK",
"setting__download_lyric_format_tip": "일부 기기에서 중국어가 깨져 보인다면 GBK 형식을 선택해 보세요.",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "가사 파일 다운로드 여부를 선택하세요",
"setting__download_max_num": "동시 다운로드 개수",
"setting__download_max_num_tip": "동시 다운로드 개수가 너무 많으면 음악 API에서 IP가 차단될 수 있습니다. 변경하시겠습니까?",
"setting__download_max_num_tooltip": "값을 너무 높게 설정하면 음악 API에 따라 IP가 차단될 수 있습니다.",
"setting__download_name": "음악 파일 이름 규칙",
"setting__download_name1": "\"제목 - 아티스트\"",
"setting__download_name2": "\"아티스트 - 제목\"",
"setting__download_name3": "제목만",
"setting__download_name_title": "다운로드 시 음악 파일 이름 형식을 선택하세요",
"setting__download_path": "다운로드 경로",
"setting__download_path_change_btn": "변경",
"setting__download_path_label": "현재: ",
"setting__download_path_open_label": "클릭하여 이 경로 열기",
"setting__download_path_title": "다운로드 경로를 선택하세요",
"setting__download_rlyric": "가능한 경우 로마자 가사도 가사 파일에 함께 기록",
"setting__download_select_save_path": "저장 경로를 선택하세요",
"setting__download_skip_exist_file": "다운로드 폴더에 동일한 이름의 파일이 있으면 다운로드 작업 건너뛰기",
"setting__download_tlyric": "가능한 경우 번역 가사도 가사 파일에 함께 기록",
"setting__download_use_other_source": "다운로드 시 자동 소스 전환",
"setting__download_use_other_source_tip": "원래 소스에서 곡을 다운로드할 수 없을 때 다른 소스로 전환하여 다운로드를 시도합니다.\n참고: 소스를 변경한 후의 곡 버전이 원래 버전과 100% 일치한다고 보장하지는 않습니다.",
"setting__hot_key": "단축키",
"setting__hot_key_common_focus_search_input": "검색창 포커스",
"setting__hot_key_common_min": "앱 최소화",
"setting__hot_key_common_toggle_close": "앱 종료",
"setting__hot_key_common_toggle_hide": "앱 표시/숨기기",
"setting__hot_key_common_toggle_min": "앱 최소화/복원",
"setting__hot_key_desktop_lyric_toggle_always_top": "데스크톱 가사 항상 위/해제",
"setting__hot_key_desktop_lyric_toggle_lock": "데스크톱 가사 잠금/잠금 해제",
"setting__hot_key_desktop_lyric_toggle_visible": "데스크톱 가사 표시/숨기기",
"setting__hot_key_global_title": "전역 단축키",
"setting__hot_key_local_title": "앱 내 단축키",
"setting__hot_key_player_music_dislike": "곡 싫어요",
"setting__hot_key_player_music_love": "곡 좋아요",
"setting__hot_key_player_music_unlove": "곡 좋아요 취소",
"setting__hot_key_player_next": "다음 곡",
"setting__hot_key_player_prev": "이전 곡",
"setting__hot_key_player_seekbackward": "5초 되감기",
"setting__hot_key_player_seekforward": "5초 빨리감기",
"setting__hot_key_player_toggle_play": "재생/일시정지",
"setting__hot_key_player_volume_down": "볼륨 낮추기",
"setting__hot_key_player_volume_mute": "음소거 전환",
"setting__hot_key_player_volume_up": "볼륨 높이기",
"setting__hot_key_tip_input": "새 키를 입력하세요",
"setting__hot_key_unset_input": "설정 안 됨",
"setting__is_enable": "사용",
"setting__is_show": "표시",
"setting__list": "목록",
"setting__list_action_btn": "목록 작업 버튼 표시",
"setting__list_add_music_location_type": "목록에 곡 추가 시 위치",
"setting__list_add_music_location_type_bottom": "맨 아래",
"setting__list_add_music_location_type_top": "맨 위",
"setting__list_click_action": "목록에서 곡을 더블클릭하면 자동으로 현재 목록으로 전환 (\"재생목록\" 및 \"차트\" 페이지에서만 적용)",
"setting__list_scroll": "재생목록 스크롤 위치 기억 (\"내 서재\" 페이지에서만 적용)",
"setting__list_source": "곡이 어느 음악 스트리밍 서비스에서 왔는지 표시 (\"내 서재\" 페이지에서만 적용)",
"setting__network": "네트워크",
"setting__network_proxy_host": "호스트",
"setting__network_proxy_password": "비밀번호",
"setting__network_proxy_port": "포트",
"setting__network_proxy_title": "HTTP 프록시 (설정이 잘못되면 앱이 네트워크에 연결되지 않습니다)",
"setting__network_proxy_username": "사용자 이름",
"setting__odc": "자동 비우기",
"setting__odc_clear_search_input": "검색하지 않을 때 검색창 비우기",
"setting__odc_clear_search_list": "검색하지 않을 때 검색 목록 비우기",
"setting__open_api": "오픈 API",
"setting__open_api_address": "서비스 주소: ",
"setting__open_api_bind_lan": "LAN에서 접속 허용",
"setting__open_api_enable": "오픈 API 서비스 활성화",
"setting__open_api_port": "서비스 포트",
"setting__open_api_port_tip": "오픈 API 서비스 포트를 입력하세요",
"setting__open_api_tip": "이 기능은 타사 프로그램이 LX Music을 호출할 수 있도록 제공합니다. 현재 사용 가능한 기능을 확인하세요: ",
"setting__open_api_tip_link": "문서 보기",
"setting__other": "기타",
"setting__other_dislike_list": "싫어요 곡 규칙",
"setting__other_dislike_list_label": "규칙 개수: ",
"setting__other_dislike_list_show_btn": "규칙 편집",
"setting__other_listdata": "목록 데이터 정리",
"setting__other_listdata_clear_btn": "\"내 서재\" 데이터 지우기",
"setting__other_listdata_clear_tip_confirm": "생성한 모든 목록과 좋아요한 모든 곡이 지워집니다. 정말 계속하시겠습니까?",
"setting__other_lyric_edited_cache": "오프셋 조정된 가사 관리",
"setting__other_lyric_edited_clear_btn": "시간 조정된 가사 지우기",
"setting__other_lyric_edited_clear_tip_confirm": "이전에 오프셋을 조정한 모든 가사가 지워집니다. 지우시겠습니까? \n(손 떨림으로 확인 🤪)",
"setting__other_lyric_edited_label": "가사 개수: ",
"setting__other_lyric_raw_clear_btn": "가사 캐시 지우기",
"setting__other_lyric_raw_label": "가사 개수: ",
"setting__other_music_url_clear_btn": "곡 URL 캐시 지우기",
"setting__other_music_url_label": "곡 URL 개수: ",
"setting__other_other_cache": "기타 캐시 관리",
"setting__other_other_source_clear_btn": "소스 변경된 곡 캐시 지우기",
"setting__other_other_source_label": "소스가 변경된 곡 정보 개수: ",
"setting__other_resource_cache": "리소스 캐시 관리",
"setting__other_resource_cache_clear_btn": "리소스 캐시 지우기",
"setting__other_resource_cache_confirm": "지우겠습니다",
"setting__other_resource_cache_label": "앱이 사용 중인 캐시 크기: ",
"setting__other_resource_cache_tip": "이미지, 오디오 등의 캐시를 포함합니다. 정리 후에는 해당 리소스를 다시 다운로드해야 합니다. 정리하지 않는 것을 권장하며, 앱이 디스크 공간에 따라 캐시 크기를 동적으로 관리합니다.",
"setting__other_resource_cache_tip_confirm": "이미지, 오디오 등의 캐시를 포함하며, 정리 후에는 해당 리소스를 다시 다운로드해야 합니다. 정리하지 않는 것을 권장하며, 앱이 디스크 공간에 따라 캐시 크기를 동적으로 관리합니다. 그래도 정리하시겠습니까?",
"setting__other_transparent_window": "메인 창에 내장된 둥근 모서리와 그림자 사용",
"setting__other_transparent_window_tip": "이 옵션을 끄면 시스템 기본 창 스타일을 사용합니다. 변경 사항을 적용하려면 소프트웨어를 재시작해야 합니다.",
"setting__other_tray_theme": "트레이 아이콘 스타일",
"setting__other_tray_theme_auto": "시스템 따르기",
"setting__other_tray_theme_black": "검정",
"setting__other_tray_theme_native": "흰색",
"setting__other_tray_theme_origin": "기본값",
"setting__play": "재생",
"setting__play_advanced_audio_features_tip": "사용자 지정 오디오 출력 장치는 이 기능과 충돌합니다. 이 기능을 활성화하면 오디오 출력 장치가 기본 출력 장치로 재설정됩니다. 현재로서는 이 문제를 해결할 수 없습니다. 그래도 활성화하시겠습니까?",
"setting__play_auto_clean_played_list": "현재 재생목록과 동일한 목록을 재생할 때 재생 완료 목록 비우기",
"setting__play_auto_clean_played_list_tip": "무작위 재생 모드에서 목록의 모든 곡이 다시 무작위 대상에 포함됩니다.",
"setting__play_auto_skip_on_error": "재생 오류 발생 시 자동으로 곡 전환",
"setting__play_detail": "재생 상세 페이지",
"setting__play_detail_align": "가사 정렬",
"setting__play_detail_align_center": "가운데",
"setting__play_detail_align_left": "왼쪽",
"setting__play_detail_align_right": "오른쪽",
"setting__play_detail_font_size": "가사 글자 크기 (재생 상세 페이지에서 키보드 \"+\"와 \"-\"로 글자 크기를 조정할 수 있습니다)",
"setting__play_detail_font_size_current": "현재 글자 크기: {size}",
"setting__play_detail_font_size_reset": "초기화",
"setting__play_detail_font_zoom": "재생 중인 가사 확대",
"setting__play_detail_lyric_delay_scroll": "가사 스크롤 지연",
"setting__play_detail_lyric_progress": "가사를 드래그하여 재생 진행 위치 조정 허용",
"setting__play_lyric_lxlrc": "가능한 경우 노래방 스타일 가사로 재생",
"setting__play_lyric_lxlrc_tip": "이 기능은 높은 성능을 요구하므로 저사양 기기에서는 사용을 권장하지 않습니다!",
"setting__play_lyric_roma": "가능한 경우 로마자 가사 표시",
"setting__play_lyric_s2t": "재생 및 다운로드 중인 중국어 가사를 번체자로 변환",
"setting__play_lyric_transition": "가능한 경우 번역 가사 표시",
"setting__play_max_output_channel_count": "기기가 지원하는 최대 채널 수로 오디오 출력",
"setting__play_mediaDevice": "오디오 출력",
"setting__play_mediaDevice_remove_stop_play": "현재 오디오 출력 장치가 변경되면 재생 일시정지",
"setting__play_mediaDevice_title": "오디오 출력에 사용할 미디어 장치를 선택하세요",
"setting__play_media_device_error_tip": "이 기능은 고급 오디오 기능(오디오 시각화, 음향 옵션, 기기 최대 채널 수 오디오 출력)과 충돌합니다. 이번 앱 실행 시 해당 기능이 활성화되어 있어 이 옵션을 일시적으로 사용할 수 없습니다. 해당 기능을 끄고 앱을 재시작한 후 다시 변경해 주세요.",
"setting__play_media_device_tip": "이 기능은 오디오 시각화 기능과 충돌하여 동시에 사용할 수 없습니다. 오디오 시각화를 끄고 선택한 오디오 출력 옵션을 적용하시겠습니까?",
"setting__play_playQuality": "가능한 경우 재생 시 음질 우선",
"setting__play_power_save_blocker": "곡 재생 중 기기 절전 모드 방지",
"setting__play_save_play_time": "재생 진행 위치 기억",
"setting__play_startup_auto_play": "시작 시 자동으로 음악 재생",
"setting__play_statusbar_lyric": "상태 메뉴에 가사 표시",
"setting__play_statusbar_lyric_tip": "설정 → 일반에서 \"앱 창을 닫을 때 시스템 트레이로 최소화\"를 활성화해야 합니다",
"setting__play_task_bar": "작업 표시줄에 재생 진행률 표시",
"setting__play_timeout": "타이머 정지",
"setting__player_audio_visualization_tip": "사용자 지정 오디오 출력 장치 기능은 오디오 시각화 기능과 충돌합니다. 오디오 시각화를 활성화하면 오디오 출력 장치가 기본값으로 재설정됩니다. 현재로서는 이 문제를 해결할 수 없습니다. 그래도 활성화하시겠습니까?",
"setting__player_swap_lyric_trans_roma": "번역 가사와 로마자 가사의 위치 바꾸기",
"setting__search": "검색",
"setting__search_focus_search_box": "시작 시 검색창 자동 포커스",
"setting__search_history": "검색 기록 활성화",
"setting__search_hot": "인기 검색어 활성화",
"setting__sync": "데이터 동기화",
"setting__sync_client_address": "현재 기기 주소: {address}",
"setting__sync_client_host": "동기화 서비스 주소",
"setting__sync_client_host_tip": "http://<호스트>:<포트>",
"setting__sync_client_mode": "클라이언트 모드",
"setting__sync_client_status": "상태: {status}",
"setting__sync_code_blocked_ip": "현재 기기의 IP가 서버에 의해 차단되었습니다!",
"setting__sync_code_fail": "유효하지 않은 연결 코드",
"setting__sync_enable": "동기화 활성화",
"setting__sync_mode": "동기화 모드",
"setting__sync_mode_client": "클라이언트",
"setting__sync_mode_server": "서버",
"setting__sync_server_address": "동기화 서비스 주소: {address}",
"setting__sync_server_auth_code": "연결 코드: {code}",
"setting__sync_server_device": "연결된 기기: {devices}",
"setting__sync_server_device_list_btn_remove": "삭제",
"setting__sync_server_device_list_noitem": "아무것도 없습니다 ┗( ▔, ▔ )┛",
"setting__sync_server_device_list_time": "마지막 연결 시간: {time}",
"setting__sync_server_device_list_tips": "💡 기기를 삭제한 후 다시 연결하려면 연결 코드를 다시 입력해야 합니다",
"setting__sync_server_device_list_title": "인증된 기기",
"setting__sync_server_mode": "서버 모드 (데이터가 평문으로 전송되므로 신뢰할 수 있는 네트워크에서 사용하세요)",
"setting__sync_server_port": "동기화 포트",
"setting__sync_server_port_tip": "포트 번호를 입력하세요",
"setting__sync_server_refresh_code": "연결 코드 새로고침",
"setting__sync_server_show_device_list": "인증된 기기 목록",
"setting__sync_tip": "사용 방법은 FAQ의 \"동기화 기능\" 항목을 참고하세요",
"setting__update": "업데이트",
"setting__update_checking": "업데이트 확인 중...",
"setting__update_commit_date": "커밋 날짜: ",
"setting__update_commit_id": "커밋 해시: ",
"setting__update_current_label": "현재 버전: ",
"setting__update_downloading": "업데이트를 찾아 다운로드하는 중...⏳",
"setting__update_init": "업데이트 중...",
"setting__update_latest": "앱이 최신 버전입니다. 즐거운 시간 되세요!🥂",
"setting__update_latest_label": "최신 버전: ",
"setting__update_new_version": "새 버전을 찾았습니다. 어서 업데이트하세요~🚀🚀",
"setting__update_open_version_modal_btn": "업데이트 창 열기",
"setting__update_progress": "상태: ",
"setting__update_show_change_log": "업데이트 후 첫 실행 시 변경 로그 표시",
"setting__update_try_auto_update": "새 버전을 찾으면 자동으로 업데이트 다운로드 시도",
"setting__update_unknown": "알 수 없음",
"setting__update_unknown_tip": "❓ 최신 버전 정보를 가져오지 못했습니다. 정보 페이지에서 프로젝트 릴리스 주소를 열어 현재 버전이 최신인지 확인하는 것을 권장합니다",
"setting_download_save_group_list_name": "해당 목록 이름의 하위 폴더에 파일 저장",
"setting_sync_status_enabled": "연결됨",
"song_list": "재생목록",
"songlist__import_input_btn_confirm": "열기",
"songlist__import_input_show_btn": "재생목록 열기",
"songlist__import_input_tip": "재생목록 링크/ID를 입력하세요",
"songlist__import_input_tip_1": "다른 서비스 간 재생목록은 지원되지 않습니다. 열려는 재생목록이 현재 선택한 서비스에 해당하는지 확인해 주세요",
"songlist__import_input_tip_2": "열리지 않는 재생목록 링크가 있다면 피드백을 보내주세요",
"songlist__import_input_tip_3": "간편판의 재생목록 ID나 링크로는 쿠거우 재생목록을 열 수 없지만, 정식판의 쿠거우 코드나 링크로는 열 수 있습니다",
"songlist__import_input_tip_4": "넷이즈의 \"프라이빗 레이더\" 재생목록은 접근하려면 토큰이 필요합니다. 텐센트의 개인 재생목록은 프로필을 공개해야 접근할 수 있습니다. 자세한 내용은 다음을 참고하세요: ",
"songlist__import_input_title": "공유된 재생목록 열기",
"songlist__open_list": "\"{name}\" 재생목록 열기",
"songlist__tag_info_hot_tag": "인기 태그",
"source_alias_all": "통합",
"source_alias_bd": "BD 뮤직",
"source_alias_kg": "KG 뮤직",
"source_alias_kw": "KW 뮤직",
"source_alias_mg": "MG 뮤직",
"source_alias_tx": "TX 뮤직",
"source_alias_wy": "WY 뮤직",
"source_alias_xm": "XM 뮤직",
"source_all": "통합",
"source_bd": "바이두",
"source_kg": "쿠거우",
"source_kw": "쿠워",
"source_mg": "미구",
"source_tx": "텐센트",
"source_wy": "넷이즈",
"source_xm": "샤미",
"sync__auth_code_input_tip": "연결 코드를 입력하세요",
"sync__auth_code_title": "연결 코드 입력이 필요합니다",
"sync__dislike_merge_tip_desc": "두 목록의 내용을 병합하고 중복 항목을 제거합니다.",
"sync__dislike_other_tip_desc": "\"동기화 취소\"를 선택하면 \"싫어요 곡\" 목록을 동기화하지 않습니다.",
"sync__dislike_overwrite_tip_desc": "덮어써지는 쪽의 목록이 덮어쓰는 쪽의 목록으로 교체됩니다.",
"sync__dislike_title": "{name}의 \"싫어요 곡\" 목록과 동기화할 방법을 선택하세요",
"sync__list_merge_tip_desc": "두 목록을 병합합니다. 동일한 곡은 제거되고(병합 대상의 곡이 제거됨), 다른 곡은 추가됩니다.",
"sync__list_other_tip_desc": "\"동기화 취소\"를 선택하면 목록을 동기화하지 않습니다.",
"sync__list_overwrite_tip_desc": "덮어써지는 목록과 동일한 ID를 가진 목록은 삭제되고 덮어쓰는 쪽의 목록으로 교체됩니다(ID가 다른 목록은 병합됩니다). \"전체 덮어쓰기\"가 선택된 경우, 덮어써지는 쪽의 모든 목록이 제거되고 덮어쓰는 쪽의 목록으로 교체됩니다.",
"sync__list_title": "\"{name}\"과(와) 목록을 동기화할 방법을 선택하세요",
"sync__merge_btn_local_remote": "\"로컬 목록\"을 \"원격 목록\"과 병합",
"sync__merge_btn_remote_local": "\"원격 목록\"을 \"로컬 목록\"과 병합",
"sync__merge_label": "병합",
"sync__merge_tip": "병합: ",
"sync__other_label": "기타",
"sync__other_tip": "기타: ",
"sync__overwrite": "전체 덮어쓰기",
"sync__overwrite_btn_cancel": "동기화 취소",
"sync__overwrite_btn_local_remote": "\"로컬 목록\"으로 \"원격 목록\" 덮어쓰기",
"sync__overwrite_btn_none": "실시간 동기화만 사용",
"sync__overwrite_btn_remote_local": "\"원격 목록\"으로 \"로컬 목록\" 덮어쓰기",
"sync__overwrite_label": "덮어쓰기",
"sync__overwrite_tip": "덮어쓰기: ",
"sync_status_disabled": "연결 안 됨",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
"tag__lossless_24bit": "24bit",
"theme_add": "테마 추가",
"theme_auto": "자동",
"theme_auto_tip": "우클릭하여 라이트/다크 테마 옵션 창 열기",
"theme_black": "블랙",
"theme_blue": "블루",
"theme_blue2": "퍼플 블루",
"theme_blue_plus": "블루 플러스",
"theme_china_ink": "수묵",
"theme_edit_modal__app_bg": "앱 배경 색상",
"theme_edit_modal__aside_color": "사이드바 버튼 색상",
"theme_edit_modal__badge": "라벨 색상",
"theme_edit_modal__badge_primary": "메인 색상",
"theme_edit_modal__badge_secondary": "보조 색상",
"theme_edit_modal__badge_tertiary": "세 번째 색상",
"theme_edit_modal__bg_image": "배경 이미지",
"theme_edit_modal__bg_image_add": "배경 이미지 추가",
"theme_edit_modal__bg_image_change": "배경 이미지 변경",
"theme_edit_modal__bg_image_remove": "배경 이미지 제거",
"theme_edit_modal__close_btn": "닫기",
"theme_edit_modal__control_btn": "왼쪽 컨트롤 버튼 색상",
"theme_edit_modal__copy": "테마 복사",
"theme_edit_modal__dark": "다크 테마",
"theme_edit_modal__dark_font": "다크 글꼴",
"theme_edit_modal__font": "글꼴 색상",
"theme_edit_modal__hide_btn": "상세 페이지 숨기기",
"theme_edit_modal__main_bg": "콘텐츠 영역 배경 색상",
"theme_edit_modal__min_btn": "최소화",
"theme_edit_modal__pick_cancel": "초기화",
"theme_edit_modal__pick_color": "색상 선택",
"theme_edit_modal__pick_last_color": "이전 색상 사용",
"theme_edit_modal__pick_save": "확인",
"theme_edit_modal__preview": "미리보기",
"theme_edit_modal__primary": "테마 색상",
"theme_edit_modal__remove": "삭제",
"theme_edit_modal__remove_tip": "이 테마를 정말 삭제하시겠습니까?",
"theme_edit_modal__save_new": "복사",
"theme_edit_modal__select_bg_file": "배경 이미지를 선택하세요",
"theme_edit_modal__title_add": "테마 추가",
"theme_edit_modal__title_edit": "테마 편집",
"theme_green": "그린",
"theme_grey": "그레이",
"theme_happy_new_year": "새해",
"theme_max_tip": "테마는 최대 10개까지 추가할 수 있습니다. 일부를 삭제하고 다시 추가해 보세요 😜",
"theme_mid_autumn": "추석",
"theme_ming": "밍",
"theme_more_btn_show": "더 많은 테마",
"theme_naruto": "나루토",
"theme_orange": "오렌지",
"theme_pink": "핑크",
"theme_purple": "퍼플",
"theme_red": "레드",
"theme_selector_modal__dark_title": "다크 테마",
"theme_selector_modal__light_title": "라이트 테마",
"theme_selector_modal__theme_name": "테마 이름",
"theme_selector_modal__title": "시스템 테마 따르기 옵션",
"theme_selector_modal__title_tip": "참고: 라이트 테마와 다크 테마를 미리 설정해 두면 시스템의 라이트/다크 모드에 따라 자동으로 미리 설정한 테마로 전환됩니다.",
"toggle_source_failed": "소스 변경에 실패했습니다. 검색 페이지에서 다른 서비스를 지정하여 직접 곡을 검색해 보세요.",
"toggle_source_try": "다른 소스로 전환을 시도하는 중...",
"update__downgrade_tip": "버전을 다운그레이드({ver})하신 것을 확인했습니다. 새 버전을 사용하다가 문제가 발생하면 먼저 FAQ를 확인해 주세요. FAQ에 문서화되지 않았거나 해결되지 않는 문제는 문서에 안내된 피드백 채널을 통해 알려주세요😘!\n\n참고: 새 버전에서 이전 버전으로 다운그레이드할 때는 먼저 재생목록을 백업하는 것을 권장합니다. 문제가 발생하면 데이터를 정리하여 해결할 수 있습니다. 데이터 폴더 경로는 문서에서 확인할 수 있습니다.",
"update__error_top": "새 버전 다운로드에 실패했습니다. 다시 다운로드를 시도하거나 GitHub에서 직접 다운로드해 보세요.\n\n새 버전의 주소는 업데이트 팝업창에 포함되어 있습니다. 새 버전을 다운로드하여 바로 설치하세요. 설치에 실패하면 FAQ를 참고하세요.\n\n참고: 현재는 Windows Setup 버전만 자동 업데이트가 가능합니다. Linux의 AppImage 및 deb 버전도 동작하는 것으로 보이나 테스트되지 않았습니다. 다른 버전으로 설치한 사용자는 수동으로 다운로드하여 업데이트해야 합니다.",
"update__ignore_cancel": "업데이트하지 않겠습니다 🤨",
"update__ignore_confirm": "네, 업데이트할게요 ❤️",
"update__ignore_confirm_tip": "현재는 Windows Setup 버전만 자동 업데이트가 가능합니다. Linux의 AppImage 및 deb 버전도 동작하는 것으로 보이나 테스트되지 않았습니다. 다른 버전으로 설치한 사용자는 수동으로 다운로드하여 업데이트해야 합니다.\n\n새 버전의 주소는 업데이트 팝업창에 포함되어 있습니다. 새 버전을 다운로드하여 바로 설치하세요. 설치에 실패하면 FAQ를 참고하세요.",
"update__ignore_confirm_tip_confirm": "네, 이해했습니다",
"update__ignore_tip": "현재 사용 중인 버전이 최신 버전보다 {num}개 버전 뒤처져 있습니다🤪. 더 나은 사용 경험을 위해 최신 버전으로 업데이트하는 것을 권장합니다~!\n참고: 새 버전을 사용하다가 문제가 발생하면 먼저 FAQ를 확인해 주세요. FAQ에 문서화되지 않았거나 해결되지 않는 문제는 문서에 안내된 피드백 채널을 통해 알려주세요😘!",
"update__timeout_top": "다운로드 시간 초과 안내\n\n현재 GitHub 접속 속도가 느려 새 버전을 한 시간째 다운로드하고 있지만 아직 완료되지 않았습니다😳. 계속 기다릴 수도 있지만, 수동으로 업데이트하는 것을 강력히 권장합니다!",
"user_api__allow_show_update_alert": "업데이트 팝업 표시 허용",
"user_api__btn_export": "내보내기",
"user_api__btn_import": "로컬 파일에서 가져오기",
"user_api__btn_import_online": "네트워크에서 가져오기",
"user_api__btn_remove": "삭제",
"user_api__import_file": "음악 API 스크립트 파일을 선택하세요",
"user_api__init_failed_alert": "음악 API \"{name}\" 초기화 실패: ",
"user_api__max_tip": "동시에 최대 20개의 API만 사용할 수 있습니다🤪.\n\n계속 가져오려면 불필요한 API를 삭제하여 공간을 확보해 주세요.",
"user_api__noitem": "아무것도 없습니다...😲",
"user_api__note": "안내: API 스크립트의 실행 환경을 최대한 격리했지만, 악의적인 동작을 포함한 API 스크립트를 가져오면 시스템에 영향을 줄 수 있습니다. 신중하게 가져와 주세요.",
"user_api__readme": "API 작성 안내: ",
"user_api__title": "음악 API 관리",
"user_api__update_alert": "음악 API \"{name}\"의 새 버전을 찾았습니다: ",
"user_api__update_alert_open_url": "업데이트 주소 열기",
"user_api_import__failed": "음악 API 가져오기에 실패했습니다:\n\n{message}",
"user_api_import_online__input_confirm": "가져오기",
"user_api_import_online__input_loading": "가져오는 중...",
"user_api_import_online__input_tip": "HTTP 링크를 입력하세요",
"user_api_import_online__title": "네트워크에서 음악 API 가져오기"
}
+10 -5
View File
@@ -1,4 +1,14 @@
[ [
{
"name": "English",
"locale": "en-us",
"country": "us"
},
{
"name": "한국어",
"locale": "ko-kr",
"country": "kr"
},
{ {
"name": "简体中文", "name": "简体中文",
"locale": "zh-cn", "locale": "zh-cn",
@@ -11,10 +21,5 @@
"locale": "zh-tw", "locale": "zh-tw",
"alternate": "zh-hk", "alternate": "zh-hk",
"country": "cn" "country": "cn"
},
{
"name": "English",
"locale": "en-us",
"country": "us"
} }
] ]
+1 -1
View File
@@ -355,7 +355,7 @@
"setting__basic_window_size_medium": "中", "setting__basic_window_size_medium": "中",
"setting__basic_window_size_oversized": "超大", "setting__basic_window_size_oversized": "超大",
"setting__basic_window_size_small": "小", "setting__basic_window_size_small": "小",
"setting__basic_window_size_smaller": "更大", "setting__basic_window_size_smaller": "更小",
"setting__basic_window_size_title": "設定軟體視窗尺寸", "setting__basic_window_size_title": "設定軟體視窗尺寸",
"setting__click_copy": "點擊複製", "setting__click_copy": "點擊複製",
"setting__click_open": "點擊開啟", "setting__click_open": "點擊開啟",
+21
View File
@@ -116,6 +116,27 @@ const messages = {
music_name: '標題: ', music_name: '標題: ',
music_singer: '演出者: ', music_singer: '演出者: ',
}, },
'ko-kr': {
collect: '즐겨찾기 추가',
uncollect: '즐겨찾기 해제',
play: '재생',
pause: '일시정지',
next: '다음 곡',
prev: '이전 곡',
hide_win_main: '메인 창 숨기기',
show_win_main: '메인 창 표시',
hide_win_lyric: '가사 창 닫기',
show_win_lyric: '가사 창 열기',
lock_win_lyric: '가사 창 잠금',
unlock_win_lyric: '가사 창 잠금 해제',
top_win_lyric: '가사 항상 위에 표시',
untop_win_lyric: '항상 위에 표시 해제',
show_statusbar_lyric: '상태 표시줄에 가사 표시',
hide_statusbar_lyric: '상태 표시줄 가사 숨기기',
exit: '종료',
music_name: '제목: ',
music_singer: '아티스트: ',
},
} as const } as const
type Messages = typeof messages type Messages = typeof messages
type Langs = keyof Messages type Langs = keyof Messages
+1 -1
View File
@@ -263,7 +263,7 @@ export const getTheme = () => {
theme.config.extInfo['--background-image'] = theme.config.extInfo['--background-image'] =
isUrl(theme.config.extInfo['--background-image']) isUrl(theme.config.extInfo['--background-image'])
? `url(${theme.config.extInfo['--background-image']})` ? `url(${theme.config.extInfo['--background-image']})`
: `url(file:///${encodePath(joinPath(global.lxDataPath, 'theme_images', theme.config.extInfo['--background-image']))})` : `url(${encodePath(joinPath(global.lxDataPath, 'theme_images', theme.config.extInfo['--background-image']))})`
} }
} else { } else {
themeId = global.lx.appSetting['theme.id'] == 'auto' && shouldUseDarkColors ? 'black' : 'green' themeId = global.lx.appSetting['theme.id'] == 'auto' && shouldUseDarkColors ? 'black' : 'green'
@@ -275,11 +275,15 @@ export const musicsMove = (fromId: string, toId: string, musicInfos: LX.Music.Mu
arrUnshift(toList, musicInfos) arrUnshift(toList, musicInfos)
break break
case 'bottom': case 'bottom':
default: default:{
moveMusicInfo(fromId, ids, toDBMusicInfo(musicInfos, toId, toList.length)) // 如果是添加到最后,那么取最后一个歌曲的原始位置 + 1 作为新歌曲的原始位置,否则新歌曲的原始位置为 position
// 因为原始 pos 可能比 targetList.length 大,所以不能直接用 targetList.length 作为新歌曲的原始位置
const order = toList.length ? (getMusicInfoOrder(toId, toList.at(-1)!.id)?.order ?? toList.length) + 1 : 0
moveMusicInfo(fromId, ids, toDBMusicInfo(musicInfos, toId, order))
arrPush(toList, musicInfos) arrPush(toList, musicInfos)
break break
} }
}
listSet = new Set<string>(ids) listSet = new Set<string>(ids)
musicLists.set(fromId, fromList.filter(mInfo => !listSet.has(mInfo.id))) musicLists.set(fromId, fromList.filter(mInfo => !listSet.has(mInfo.id)))
+1 -1
View File
@@ -18,7 +18,7 @@ export const assertApiSupport = (source: LX.Source): boolean => {
export const buildBgUrl = (originUrl: string, dataPath: string): string => { export const buildBgUrl = (originUrl: string, dataPath: string): string => {
return isUrl(originUrl) return isUrl(originUrl)
? `url(${originUrl})` ? `url(${originUrl})`
: `url(file:///${encodePath(joinPath(dataPath, originUrl).replaceAll('\\', '/'))})` : `url(${encodePath(joinPath(dataPath, originUrl).replaceAll('\\', '/'))})`
} }
export const getThemes = (callback: (themeInfo: LX.ThemeInfo) => void) => { export const getThemes = (callback: (themeInfo: LX.ThemeInfo) => void) => {
+9 -2
View File
@@ -6,6 +6,7 @@ import mg from './mg/index'
import bd from './bd/index' import bd from './bd/index'
import xm from './xm' import xm from './xm'
import { supportQuality } from './api-source' import { supportQuality } from './api-source'
import { versionChars } from './versionChars'
const sources = { const sources = {
@@ -114,8 +115,14 @@ export default {
const fSinger = filterStr(sortSingle(singer)).toLowerCase() const fSinger = filterStr(sortSingle(singer)).toLowerCase()
const fAlbumName = filterStr(albumName).toLowerCase() const fAlbumName = filterStr(albumName).toLowerCase()
const fInterval = getIntv(interval) const fInterval = getIntv(interval)
const isEqualsInterval = (intv) => Math.abs((fInterval || intv) - (intv || fInterval)) < 5 const isEqualsInterval = (intv) => Math.abs((fInterval || intv) - (intv || fInterval)) <= 5
const isIncludesName = (name) => (fMusicName.includes(name) || name.includes(fMusicName)) const isEqualsVersionMusicNameChar = (name) => {
for (const char of versionChars) {
if (name.includes(char) != fMusicName.includes(char)) return false
}
return true
}
const isIncludesName = (name) => (fMusicName.includes(name) || name.includes(fMusicName)) && isEqualsVersionMusicNameChar(name)
const isIncludesSinger = (singer) => fSinger ? (fSinger.includes(singer) || singer.includes(fSinger)) : true const isIncludesSinger = (singer) => fSinger ? (fSinger.includes(singer) || singer.includes(fSinger)) : true
const isEqualsAlbum = (album) => fAlbumName ? fAlbumName == album : true const isEqualsAlbum = (album) => fAlbumName ? fAlbumName == album : true
@@ -132,6 +132,7 @@ export default {
albumName: decodeName(item.remark), albumName: decodeName(item.remark),
albumId: item.album_id, albumId: item.album_id,
songmid: item.audio_id, songmid: item.audio_id,
albumAudioId: item.album_audio_id,
source: 'kg', source: 'kg',
interval: formatPlayTime(item.duration), interval: formatPlayTime(item.duration),
img: null, img: null,
@@ -85,6 +85,7 @@ export const filterMusicInfoList = (rawList) => {
albumName: decodeName(item.album_info.album_name), albumName: decodeName(item.album_info.album_name),
albumId: item.album_info.album_id, albumId: item.album_info.album_id,
songmid: item.audio_info.audio_id, songmid: item.audio_info.audio_id,
albumAudioId: item.audio_info.audio_group_id,
source: 'kg', source: 'kg',
interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000), interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000),
img: null, img: null,
@@ -9,7 +9,7 @@ export default {
page: 0, page: 0,
allPage: 1, allPage: 1,
musicSearch(str, page, limit) { musicSearch(str, page, limit) {
const searchRequest = httpFetch(`https://songsearch.kugou.com/song_search_v2?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&userid=0&clientver=&platform=WebFilter&filter=2&iscorrection=1&privilege_filter=0&area_code=1`) const searchRequest = httpFetch(`http://songsearch.kugou.com/song_search_v2?platform=AndroidFilter&iscorrection=1&keyword=${encodeURIComponent(str)}&hifiquality=0&pagesize=${limit}&PrivilegeFilter=0&page=${page}`)
return searchRequest.promise.then(({ body }) => body) return searchRequest.promise.then(({ body }) => body)
}, },
filterData(rawData) { filterData(rawData) {
@@ -53,6 +53,7 @@ export default {
albumName: decodeName(rawData.AlbumName), albumName: decodeName(rawData.AlbumName),
albumId: rawData.AlbumID, albumId: rawData.AlbumID,
songmid: rawData.Audioid, songmid: rawData.Audioid,
albumAudioId: rawData.MixSongID,
source: 'kg', source: 'kg',
interval: formatPlayTime(rawData.Duration), interval: formatPlayTime(rawData.Duration),
_interval: rawData.Duration, _interval: rawData.Duration,
@@ -740,6 +740,7 @@ export default {
albumName: decodeName(item.album_name), albumName: decodeName(item.album_name),
albumId: item.album_id, albumId: item.album_id,
songmid: item.audio_id, songmid: item.audio_id,
albumAudioId: item.album_audio_id,
source: 'kg', source: 'kg',
interval: formatPlayTime(item.duration / 1000), interval: formatPlayTime(item.duration / 1000),
img: null, img: null,
@@ -863,6 +864,7 @@ export default {
albumName: decodeName(item.album_info.album_name), albumName: decodeName(item.album_info.album_name),
albumId: item.album_info.album_id, albumId: item.album_info.album_id,
songmid: item.audio_info.audio_id, songmid: item.audio_info.audio_id,
albumAudioId: item.audio_info.audio_group_id,
source: 'kg', source: 'kg',
interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000), interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000),
img: null, img: null,
+4 -4
View File
@@ -108,8 +108,8 @@ export const lrcTools = {
getWordInfo(str, str2, prevWord) { getWordInfo(str, str2, prevWord) {
const offset = parseInt(str) const offset = parseInt(str)
const offset2 = parseInt(str2) const offset2 = parseInt(str2)
let startTime = Math.abs((offset + offset2) / (this.offset * 2)) let startTime = Math.trunc(Math.abs((offset + offset2) / (this.offset * 2)))
let endTime = Math.abs((offset - offset2) / (this.offset2 * 2)) + startTime let endTime = Math.trunc(Math.abs((offset - offset2) / (this.offset2 * 2)) + startTime)
if (prevWord) { if (prevWord) {
if (startTime < prevWord.endTime) { if (startTime < prevWord.endTime) {
prevWord.endTime = startTime prevWord.endTime = startTime
@@ -117,14 +117,14 @@ export const lrcTools = {
prevWord.startTime = prevWord.endTime prevWord.startTime = prevWord.endTime
} }
prevWord.newTimeStr = `<${prevWord.startTime},${prevWord.endTime - prevWord.startTime}>` prevWord.newTimeStr = `<${prevWord.startTime},${Math.trunc(prevWord.endTime - prevWord.startTime)}>`
// console.log(prevWord) // console.log(prevWord)
} }
} }
return { return {
startTime, startTime,
endTime, endTime,
timeStr: `<${startTime},${endTime - startTime}>`, timeStr: `<${startTime},${Math.trunc(endTime - startTime)}>`,
} }
}, },
parseLine(line) { parseLine(line) {
+2 -2
View File
@@ -21,7 +21,7 @@ const mrcTools = {
const startTime = parseInt(result[1]) const startTime = parseInt(result[1])
let time = startTime let time = startTime
let ms = time % 1000 let ms = (time % 1000).toString().padStart(3, '0')
time /= 1000 time /= 1000
let m = parseInt(time / 60).toString().padStart(2, '0') let m = parseInt(time / 60).toString().padStart(2, '0')
time %= 60 time %= 60
@@ -36,7 +36,7 @@ const mrcTools = {
if (!times) continue if (!times) continue
times = times.map(time => { times = times.map(time => {
const result = /\((\d+),(\d+)\)/.exec(time) const result = /\((\d+),(\d+)\)/.exec(time)
return `<${parseInt(result[1]) - startTime},${result[2]}>` return `<${Math.trunc(parseInt(result[1]) - startTime)},${result[2]}>`
}) })
const wordArr = words.split(this.rxps.wordTime) const wordArr = words.split(this.rxps.wordTime)
const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('') const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('')
+3 -3
View File
@@ -22,12 +22,12 @@ const parseTools = {
}, },
msFormat(timeMs) { msFormat(timeMs) {
if (Number.isNaN(timeMs)) return '' if (Number.isNaN(timeMs)) return ''
let ms = timeMs % 1000 let ms = (timeMs % 1000).toString().padStart(3, '0')
timeMs /= 1000 timeMs /= 1000
let m = parseInt(timeMs / 60).toString().padStart(2, '0') let m = parseInt(timeMs / 60).toString().padStart(2, '0')
timeMs %= 60 timeMs %= 60
let s = parseInt(timeMs).toString().padStart(2, '0') let s = parseInt(timeMs).toString().padStart(2, '0')
return `[${m}:${s}.${String(ms).padStart(3, '0')}]` return `[${m}:${s}.${ms}]`
}, },
parseLyric(lrc) { parseLyric(lrc) {
lrc = lrc.trim() lrc = lrc.trim()
@@ -65,7 +65,7 @@ const parseTools = {
if (!times) continue if (!times) continue
times = times.map(time => { times = times.map(time => {
const result = /\((\d+),(\d+)\)/.exec(time) const result = /\((\d+),(\d+)\)/.exec(time)
return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>` return `<${Math.trunc(Math.max(parseInt(result[1]) - startMsTime, 0))},${result[2]}>`
}) })
const wordArr = words.split(this.rxps.wordTime) const wordArr = words.split(this.rxps.wordTime)
const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('') const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('')
+1 -1
View File
@@ -240,7 +240,7 @@ export default {
list: this.filterListDetail(result.songlist), list: this.filterListDetail(result.songlist),
page: 1, page: 1,
limit: this.limit_song, limit: this.limit_song,
total: result.songnum, total: result.total_song_num,
source: 'tx', source: 'tx',
info: { info: {
name: dirinfo.title, name: dirinfo.title,
+124
View File
@@ -0,0 +1,124 @@
export const versionChars = [
// 伴奏 / 无人声
// instrumental: [
'instrumental',
'inst',
'karaoke',
'backing track',
'minus one',
'minus vocal',
'no vocal',
'off vocal',
'vocal removed',
'伴奏',
'伴唱',
'卡拉ok',
'纯音乐',
'无人声',
'去人声',
'消音',
'オフボーカル',
'インスト',
'반주',
'노래방',
// ],
// 现场
// live: [
'live',
'concert',
'tour',
'festival',
'stage',
'session',
'现场',
'演唱会',
'巡演',
'音乐会',
'ライブ',
'공연',
'라이브',
// ],
// 翻唱
// cover: [
'cover',
'tribute',
'rendition',
'remake',
'翻唱',
'致敬',
'改编',
'カバー',
'커버',
// ],
// 混音
// remix: [
'remix',
'mix',
'club',
'dance',
'extended',
'house',
'edm',
'dj',
'dub',
'trap',
'混音',
'重混',
'电音',
'リミックス',
'리믹스',
// ],
// 原声 / 不插电
// acoustic: [
'acoustic',
'unplugged',
'stripped',
'原声',
'不插电',
'アコースティック',
'어쿠스틱',
// ],
// Demo
// demo: [
'demo',
'小样',
'试听',
'デモ',
'데모',
// ],
// 速度变化
// speed: [
'slowed',
'slowed reverb',
'sped up',
'speed up',
'nightcore',
'慢速',
'加速',
'变速',
'ナイトコア',
// ],
// 特殊乐器版
// special_instrumental: [
'piano',
'guitar',
'orchestra',
'symphony',
'violin',
'钢琴',
'吉他',
'管弦乐',
'小提琴',
'ピアノ',
'ギター',
'피아노',
'기타',
// ]
]
+8 -4
View File
@@ -60,10 +60,13 @@ const parseTools = {
lineTime: /^\[(\d+),\d+\]/, lineTime: /^\[(\d+),\d+\]/,
wordTime: /\(\d+,\d+,\d+\)/, wordTime: /\(\d+,\d+,\d+\)/,
wordTimeAll: /(\(\d+,\d+,\d+\))/g, wordTimeAll: /(\(\d+,\d+,\d+\))/g,
timeMs2: /\[\d+:\d+\.\d{2}]/,
timeMs3: /\[\d+:\d+\.\d{3}]/,
}, },
msFormat(timeMs) { msFormat(timeMs, pad3 = true) {
if (Number.isNaN(timeMs)) return '' if (Number.isNaN(timeMs)) return ''
let ms = timeMs % 1000 let ms = (timeMs % 1000).toString().padStart(pad3 ? 3 : 2, '0')
if (!pad3 && ms.length > 2) ms = ms.slice(0, 2)
timeMs /= 1000 timeMs /= 1000
let m = parseInt(timeMs / 60).toString().padStart(2, '0') let m = parseInt(timeMs / 60).toString().padStart(2, '0')
timeMs %= 60 timeMs %= 60
@@ -97,7 +100,7 @@ const parseTools = {
if (!times) continue if (!times) continue
times = times.map(time => { times = times.map(time => {
const result = /\((\d+),(\d+),\d+\)/.exec(time) const result = /\((\d+),(\d+),\d+\)/.exec(time)
return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>` return `<${Math.trunc(Math.max(parseInt(result[1]) - startMsTime, 0))},${result[2]}>`
}) })
const wordArr = words.split(this.rxps.wordTime) const wordArr = words.split(this.rxps.wordTime)
wordArr.shift() wordArr.shift()
@@ -113,12 +116,13 @@ const parseTools = {
str = str.trim() str = str.trim()
str = str.replace(/\r/g, '') str = str.replace(/\r/g, '')
if (!str) return null if (!str) return null
const isPad3 = this.rxps.timeMs3.test(str) || !this.rxps.timeMs2.test(str)
const lines = str.split('\n') const lines = str.split('\n')
return lines.map(line => { return lines.map(line => {
if (!this.rxps.info.test(line)) return line if (!this.rxps.info.test(line)) return line
try { try {
const info = JSON.parse(line) const info = JSON.parse(line)
const timeTag = this.msFormat(info.t) const timeTag = this.msFormat(info.t, isPad3)
return timeTag ? `${timeTag}${info.c.map(t => t.tx).join('')}` : '' return timeTag ? `${timeTag}${info.c.map(t => t.tx).join('')}` : ''
} catch { } catch {
return '' return ''