Compare commits

...

324 Commits

Author SHA1 Message Date
lyswhut
ad95d5091c 发布 v2.12.6 2026-09-19 09:57:31 +08:00
3gf8jv4dv
a04cf3b307 fix: correct wrong product name on win legacy prompt (#2978) 2026-09-19 08:39:57 +08:00
lyswhut
f9f0e61c5b 更新版本号(2.12.6-beta.2) 2026-09-19 00:51:18 +08:00
lyswhut
d2f5485996 新增 Windows 旧版提示功能,提醒用户升级至新版系统以提高安全性和性能 2026-09-19 00:50:49 +08:00
lyswhut
e20b2844fb 优化透明窗口边框显示效果,修复全屏模式下的相关问题 2026-09-19 00:29:30 +08:00
lyswhut
e9bc6a572b 修复打开某些 kg 歌单时歌曲丢失的问题 2026-09-16 23:27:12 +08:00
lyswhut
bb8d07274f 优化 tx 推荐歌单列表 2026-09-16 21:18:54 +08:00
lyswhut
950029884c 更新版本号(2.12.6-beta.1) 2026-09-14 21:01:01 +08:00
lyswhut
7c78e557ed 修复 win7 无法启动的问题 2026-09-14 21:00:00 +08:00
lyswhut
78eb5c48d7 更新版本号(2.12.6-beta.0) 2026-09-14 20:14:29 +08:00
lyswhut
96fd33065a 修复 win7 无法启动的问题 2026-09-14 20:05:55 +08:00
lyswhut
abcbf5fa00 发布 v2.12.5 2026-09-14 01:07:30 +08:00
lyswhut
378174c277 fix: 降级 @simonwep/pickr 和 webpack 版本,修复依赖问题 2026-09-14 00:59:43 +08:00
lyswhut
39466f157c 发布 v2.12.4 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
jtcai
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
jtcai
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
ikun0014
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
ikun0014
21619e72dc fix: 再次修复 tx 源搜索偶现风控 (#2848)
* fix: 修复 tx 源搜索偶现风控

将搜索接口从移动端 DoSearchForQQMusicMobile 切换到 PC 端
DoSearchForQQMusicDesktop,更新 comm 参数与 searchid 生成方式,
并兼容新旧返回结构(music.search.SearchCgiService / req)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* update

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: lyswhut <lyswhut@qq.com>
2026-06-21 10:30:06 +08:00
啊呜
33742bca6b Win文件名中存在#特殊字符时无法正常播放 (#2862)
(cherry picked from commit 890b6ea3da)
2026-06-14 20:34:42 +08:00
lyswhut
e0575fc179 fix: 移除请求函数中的不必要的类型注释 2026-06-14 20:14:01 +08:00
lyswhut
97fe5fa496 更新依赖&版本号(2.12.3-beta.5) 2026-06-14 19:41:56 +08:00
lyswhut
97533cd4aa fix: 修复在某些情况下添加列表、歌曲时可能导致保存的顺序不对的问题(#2842) 2026-06-10 09:24:34 +08:00
lyswhut
a7672168c6 修复旧版请求函数中的重定向选项 2026-06-07 11:45:59 +08:00
lyswhut
5c46ca648d 修复某些 tx 歌单打开失败的问题 2026-06-07 11:45:59 +08:00
lyswhut
38c7bc0d0d 修复 mg 图片、歌词获取 2026-06-07 11:45:59 +08:00
ikun0014
cb2798cba9 refactor: 将QRC解密替换为纯JS实现并简化kw歌词获取 (#2845)
* 简化kw歌词获取 && kw歌词解密逻辑移动

将酷我歌词解密从主进程 native/IPC 方案迁移到渲染进程纯 JS 实现,
移除 kw_decodeLyric 主进程入口与 handle_kw_decode_lyric IPC 通道。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* tx歌词解密改用js实现

将 QRC 歌词的 3DES 解密由主进程 native 插件(qrc_decode.node)按原 C++
算法 1:1 移植为渲染进程纯 JS 实现,移除 tx_decodeLyric 主进程入口、
handle_tx_decode_lyric IPC 通道,以及 build 配置中对 qrc_decode.node
的引用与各平台预构建二进制。

注意:QRC 使用的是非标准 DES 变体(S2[23]=15、S4[53]=10,标准为
14、1),新实现已逐字节对照原 native 二进制保持一致。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* update

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: lyswhut <lyswhut@qq.com>
2026-06-07 11:14:17 +08:00
lyswhut
f0342b852b 更新版本号(2.12.3-beta.4) 2026-06-04 21:43:22 +08:00
lyswhut
97346617a5 更新版本号(2.12.3-beta.3) 2026-06-04 21:22:23 +08:00
lyswhut
ecdc018fa1 Linux 点击托盘时将显示主界面(#2840) 2026-06-04 20:54:55 +08:00
lyswhut
cbcbe3e8c8 更新版本号(2.12.3-beta.2) 2026-05-23 11:01:51 +08:00
lyswhut
b12c5ed12b 同步服务在连接时允许URL重定向 2026-05-23 11:00:21 +08:00
3gf8jv4dv
96921b255f fix(l10n): update import songlist dialog text (#2797)
refs https://github.com/lyswhut/lx-music-doc/issues/64
refs https://github.com/lyswhut/lx-music-desktop/issues/2711
2026-05-17 14:42:49 +08:00
lyswhut
2df1077ee4 更新依赖&版本号(2.12.3-beta.1) 2026-05-15 12:36:43 +08:00
lyswhut
4937ddb09c 修复某些情况下开放 API 获取到的音量为0 的问题(#2790) 2026-05-15 12:15:04 +08:00
lyswhut
4b00a06a88 优化kw歌单列表数据显示 2026-05-15 12:13:13 +08:00
lyswhut
be48e78a6f 更新依赖&版本号(2.12.3-beta.0) 2026-05-05 15:31:34 +08:00
lyswhut
b7a3663078 修复 kg 搜索结果显示问题(#2782) 2026-05-05 14:50:34 +08:00
lyswhut
9c364b482e 更新依赖 2026-05-01 13:12:19 +08:00
lyswhut
355c5e584b 修复音调调节问题 2026-05-01 13:05:57 +08:00
lyswhut
f956fd93d5 将 actions/cache 版本从 v6 降级至 v5 2026-05-01 02:28:50 +08:00
lyswhut
69e285d3ca 升级 GitHub Actions 依赖至最新版本 2026-05-01 02:26:18 +08:00
lyswhut
728fed2ed3 发布 v2.12.2 2026-05-01 01:31:36 +08:00
lyswhut
bc0d2264a3 更新依赖 2026-05-01 01:30:51 +08:00
lyswhut
ba8a6bf76c 修复音乐名称和歌手信息格式化(#2733) 2026-04-23 18:08:44 +08:00
lyswhut
2cf84a796d 更新依赖&版本号(2.12.2-beta.1) 2026-04-10 12:16:30 +08:00
lyswhut
ba62ae27af 修复 tx 偶现搜索失败的问题 2026-04-10 09:11:29 +08:00
lyswhut
e35895d752 移除 babel 2026-04-10 09:10:58 +08:00
lyswhut
cc192cab44 fix: 修复 tx 搜索结果显示异常的问题(#2753) 2026-04-09 12:47:28 +08:00
lyswhut
e36792b76a 更新依赖&版本号(2.12.2-beta.0) 2026-03-31 18:14:35 +08:00
lyswhut
61231ab99e feat: 优化搜索功能,增强歌曲匹配逻辑 2026-03-31 18:11:31 +08:00
lyswhut
ae2ee0e61e 更新依赖 2026-03-23 09:29:30 +08:00
Stanislav Chernov
a339f0fd0c fix: 音源导入去重 (#2681)
* fix: 音源导入添加去重逻辑
修复音源导入时未对重复数据进行去重的问题。现在会根据url去重,避免导入重复的音源。
Fixes lyswhut/lx-music-desktop#2649

* fix: check script content instead of name for dedup

Allow duplicate source names (intentional behavior).
Only reject import when script content is identical to an existing
source, and report which source name conflicts.
2026-03-11 14:52:52 +08:00
lyswhut
b9c3c3506e 修复桌面歌词的鼠标悬停隐藏功能,优化鼠标事件处理逻辑 2026-03-07 14:11:44 +08:00
lyswhut
44d96264fb 修复某些情况下可能出现播放歌曲没有声音的问题 (#2693) 2026-03-07 14:06:12 +08:00
derr
19eab0418a fix(mac): 修复 Mac 睡眠导致音乐播放中断的问题 (#2692)
* fix(mac): prevent sleep on mac during playback

* Remove space between async and parentheses in functions

---------

Co-authored-by: ydm <ydm@49os.com>
Co-authored-by: lyswhut <lyswhut@qq.com>
2026-03-07 13:56:55 +08:00
Little_100
0695bbaa70 修复桌面歌词的 鼠标移入歌词区域时提高透明度 设置不稳定的问题 (#2679)
Co-authored-by: lyswhut <lyswhut@qq.com>
2026-03-07 13:52:35 +08:00
lyswhut
8d6b207833 发布 v2.12.1 2026-02-16 21:11:25 +08:00
lyswhut
3371a42822 更新依赖 2026-02-16 21:03:26 +08:00
lyswhut
fe712318b8 更新依赖&版本号(2.12.1-beta.3) 2026-01-31 12:44:16 +08:00
ikun0014
593d8327b2 fix: 网易云部分歌曲无法被搜索 (#2666)
* fix: 网易云部分歌曲无法被搜索

将搜索请求由原有 eapiRequest 改为通过 httpFetch 调用 /eapi/search/song/list/page 并使用 eapi 加密表单;调整导入(使用 eapi)、移除部分注释和冗余代码。更新结果解析以兼容新响应结构(使用 item.baseInfo.simpleSongData、result.data.resources 和 result.data.totalCount 等字段),并保留原有的分页与重试逻辑。

* update

* update

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2026-01-31 12:36:28 +08:00
lyswhut
d5c54b770f 更新依赖&版本号(2.12.1-beta.2) 2026-01-01 11:58:30 +08:00
lyswhut
32a9a21be1 优化托盘图标行为:在非 Windows 系统中,点击托盘图标时不再显示主窗口 2025-12-31 09:53:47 +08:00
lyswhut
e0735f8928 更新依赖&版本号(2.12.1-beta.1) 2025-12-21 13:46:31 +08:00
lyswhut
c6dc962134 修复某些情况下搜索框的搜索按钮布局错位的问题(#2622) 2025-12-21 13:45:40 +08:00
lyswhut
e068103e4d 修复构建 2025-12-12 12:52:31 +08:00
lyswhut
6eb5ade9ea 更新依赖&版本号(2.12.1-beta.0) 2025-12-12 12:37:16 +08:00
lyswhut
c40168cacd 更新构建流程,集成 undici 库并优化请求处理 2025-12-12 12:28:04 +08:00
lyswhut
a546b75986 修复音量条在调整音量时实际音量与显示的数值不一致的问题(#2606) 2025-12-07 14:07:12 +08:00
lyswhut
8686ecfa54 更新二进制库 2025-11-29 16:31:23 +08:00
lyswhut
270c48a1dd 发布 v2.12.0 2025-11-29 16:27:28 +08:00
lyswhut
fdbec2cbac 更新说明 2025-11-29 16:26:14 +08:00
lyswhut
46049fd15c 更新版本号(2.12.0-beta.15) 2025-11-28 12:06:21 +08:00
lyswhut
a81fe6ff7f 修复MG最新评论翻页 2025-11-28 12:06:17 +08:00
lyswhut
fd4e8ecb01 更新版本号(2.12.0-beta.14) 2025-11-26 13:41:57 +08:00
lyswhut
8142e5e79e 修复MG源评论加载失败 2025-11-26 13:41:22 +08:00
lyswhut
0c71772953 修复MG源歌单加载失败 2025-11-26 13:41:22 +08:00
lyswhut
d54bc21441 更新版本号(2.12.0-beta.13) 2025-11-23 10:21:40 +08:00
lyswhut
be4bca2c15 更新字体设置,支持最多设置两种字体,并调整选择组件宽度 2025-11-23 10:20:27 +08:00
lyswhut
2bd54673de 更新依赖&版本号(2.12.0-beta.12) 2025-11-21 09:09:01 +08:00
lyswhut
4a3359f773 更新依赖 2025-11-14 18:52:51 +08:00
lyswhut
c35095f90e 修复获取歌曲图片的函数,改为直接返回图片链接 2025-11-14 18:52:51 +08:00
lyswhut
32abccd8c2 更新版本号(2.12.0-beta.11) 2025-11-02 21:21:15 +08:00
Folltoshe
5d68ca3e18 修复TX源搜索失败 (#2575)
* 修复TX源搜索失败

* 清理音乐搜索模块中的多余空行

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2025-11-02 21:19:26 +08:00
lyswhut
8efa5335b0 更新版本号(2.12.0-beta.10) 2025-10-01 13:30:12 +08:00
lyswhut
4710bb16d8 下载列表菜单新增歌曲添加弹窗(#2537) 2025-10-01 13:29:35 +08:00
lyswhut
db4f3e4e1b 更新版本号(2.12.0-beta.9) 2025-09-29 12:48:33 +08:00
lyswhut
e18272a6d7 更新依赖 2025-09-29 12:43:24 +08:00
lyswhut
85aed9ce9d 修复隐藏状态下的透明度问题,增加鼠标悬停时恢复不透明度的效果 2025-09-29 12:43:12 +08:00
lyswhut
d03f93166f 新增完整歌词相关字段和API支持 2025-09-29 12:37:22 +08:00
lyswhut
a3b1a433c6 更新版本号(2.12.0-beta.8) 2025-09-19 23:54:44 +08:00
lyswhut
608f63bc9c 临时修复桌面歌词交互时背景异常的问题 2025-09-19 23:53:45 +08:00
lyswhut
a4c118648a mac下默认不使用透明窗口 2025-09-19 23:51:00 +08:00
lyswhut
42866b8e4f 修复构建 2025-09-07 12:14:31 +08:00
lyswhut
0b7455b74f 更新依赖&版本号(2.12.0-beta.7) 2025-09-07 11:53:50 +08:00
lyswhut
eab7bc3f5f 优化歌词解析 2025-08-11 13:49:26 +08:00
lyswhut
af2623d16c 更新版本号(2.12.0-beta.6) 2025-08-10 13:46:52 +08:00
lyswhut
e683034e77 新增 Any Listen 歌词标签数据嵌入、下载、读取播放(#2485) 2025-08-10 13:42:54 +08:00
lyswhut
ffa979ddc8 更新依赖版本,优化组件逻辑 2025-08-09 18:07:16 +08:00
3gf8jv4dv
8a8c8a5699 Revise text for lyric position switching feature (#2463)
- Fix incorrect i18n key
- Revise English translation
- Revise changelog descriptions to be more accurate
2025-08-06 12:24:03 +08:00
lyswhut
2aea956fd0 更新依赖&版本号(2.12.0-beta.5) 2025-08-06 12:22:20 +08:00
lyswhut
1803517943 优化 hidden 启动参数的行为 2025-08-06 12:21:47 +08:00
lyswhut
49c8081f68 尝试修复进度为0时仍然显示下载完成的问题(#2471) 2025-08-05 12:16:24 +08:00
lyswhut
3d64ca37e7 更新版本号(2.12.0-beta.4) 2025-07-19 14:29:32 +08:00
lyswhut
ccc88745a6 降级 electron 版本至 35.3.0 2025-07-19 14:28:47 +08:00
lyswhut
0bf6dbd402 更新依赖&版本号(2.12.0-beta.3) 2025-07-19 02:16:52 +08:00
lyswhut
b9abb4ce47 新增启动参数 -hidden,在启动时将软件最小化到系统托盘(#2459) 2025-07-18 22:24:15 +08:00
lyswhut
ad0c0f37c9 新增调换翻译歌词与罗马音歌词位置设置,优化歌词显示逻辑(#2451) 2025-07-09 08:58:34 +08:00
Folltoshe
4c6257c145 更新代理配置规则,现在不启用代理时,图片、音频加载将不再走系统代理 (#2382)
* 完善代理设置

* 更新

* update

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2025-05-10 10:43:58 +08:00
lyswhut
6c5231dbff 恢复更新日志 2025-05-10 10:07:45 +08:00
lyswhut
0454bb9b3c 更新依赖&版本号(2.12.0-beta.2) 2025-05-10 01:30:48 +08:00
lyswhut
021f89da29 新增播放器声音大小、静音、播放进度控制(#2386) 2025-05-10 00:58:19 +08:00
lyswhut
2ccca633ed 优化滚动事件监听,启用 passive 选项以提升性能 2025-05-09 10:02:37 +08:00
lyswhut
fe15e025aa 更新数据库备份逻辑 2025-05-09 10:02:37 +08:00
3gf8jv4dv
9e0c416db5 Revise chinese terminology for window and font size (#2384)
addresses https://github.com/lyswhut/lx-music-mobile/issues/769
2025-05-07 21:53:24 +08:00
3gf8jv4dv
fa9b84a9c9 Revise wording of main window style options - 2 (#2379)
- Not all operating systems use rounded corners. Like Windows 10.
- Further optimize wording.
- Forgot to add line breaks to CHANGELOG :(
2025-05-06 09:15:31 +08:00
lyswhut
b3b1bd7bc8 更新版本号(2.12.0-beta.1) 2025-05-03 11:23:53 +08:00
lyswhut
91e806a619 为数据库启用 WAL 模式 2025-05-03 11:22:35 +08:00
3gf8jv4dv
6df6eefd3d Revise wording of main window style options (#2372)
- Correct typos
- Edit i18n key name
- Avoid displaying long descriptions directly
2025-05-03 11:06:28 +08:00
lyswhut
ae35d05649 修正透明窗口设置逻辑 2025-05-02 13:02:01 +08:00
lyswhut
72939db2df 更新版本号(2.12.0-beta.0) 2025-05-02 12:36:08 +08:00
lyswhut
b6f5770dc2 新增「设置 → 其他设置 → 主窗口使用软件内置的圆角及阴影」设置 2025-05-02 12:28:16 +08:00
lyswhut
c1e7faa7bf 发布 2.11.0 2025-05-01 16:55:22 +08:00
lyswhut
37a18eae9c 更新依赖 2025-05-01 16:55:14 +08:00
lyswhut
010bc5e328 修复 kg 歌单打开失败的问题 2025-04-23 09:13:45 +08:00
lyswhut
a166051781 更新版本号(2.11.0-beta.6) 2025-04-19 19:55:36 +08:00
3gf8jv4dv
c2341d6697 Disable rounded corners for lyric window (#2356)
* Disable rounded corners for lyric window

Ensure that the lyric window looks the same
on Windows 11 as it does on other systems.

Ref: https://github.com/electron/electron/issues/46468

* update

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2025-04-19 19:54:43 +08:00
lyswhut
03bba4259e 更新版本号(2.11.0-beta.5) 2025-04-19 15:35:56 +08:00
lyswhut
80844580df 更新 Electron 到 35(#2354) 2025-04-19 15:34:29 +08:00
lyswhut
a8790b9835 更新依赖 2025-04-09 09:39:34 +08:00
lyswhut
36262ff166 更新 歌曲换源 机制 2025-04-09 09:24:45 +08:00
lyswhut
98d67be9d9 歌词选择模式下的歌词调整为跟随歌词字体大小显示 2025-03-29 13:11:07 +08:00
lyswhut
131f077c3f 更新空音频输出设备提示信息 2025-03-21 09:31:47 +08:00
lyswhut
2c428ef099 减少桌面歌词窗口自动置顶刷新间隔,修复窗口被遮挡时歌词会被暂停的问题(#2320) 2025-03-21 09:15:39 +08:00
lyswhut
08887cd6ed 更新 Node.js 版本至 22 2025-03-20 13:49:37 +08:00
lyswhut
f786a29b70 更新依赖&版本号(2.11.0-beta.3) 2025-03-20 13:08:01 +08:00
lyswhut
d3f1d7f378 更新依赖&版本号(2.11.0-beta.2) 2025-03-20 12:54:54 +08:00
lyswhut
c48a31fd63 修复 kw 歌单详情打开失败和 kg 热门评论获取问题 2025-03-20 09:22:21 +08:00
lyswhut
1217f06324 修复潜在播放暂停的问题 2025-03-07 12:20:41 +08:00
lyswhut
4cc3197be8 修复构建 2025-03-04 12:04:20 +08:00
lyswhut
eb940d5968 更新依赖&版本号(2.11.0-beta.1) 2025-03-04 11:59:44 +08:00
lyswhut
6381389cac 减少歌词延迟隐藏时间 2025-03-04 11:57:08 +08:00
3gf8jv4dv
ab939e859a Improve description of seek backward/forward shortcuts (english only) (#2297)
Match the style of other shortcuts' descriptions
2025-03-04 11:56:51 +08:00
3gf8jv4dv
9bdc71c487 Fix description of transparent lyric window in desktop lyrics settings (#2296)
- Fix incorrect i18n key
- Fix the wrong description
2025-03-04 11:55:40 +08:00
lyswhut
56a4abfaff 更新版本号(2.11.0-beta.1) 2025-03-03 22:32:57 +08:00
lyswhut
769bc7f109 新增「设置 → 桌面歌词设置 → 暂停时降低歌词透明度」设置,默认启用(#2294) 2025-03-03 22:14:21 +08:00
lyswhut
a587847e41 更新版本号(2.11.0-beta.0) 2025-03-01 20:04:08 +08:00
lyswhut
bda4650de8 新增 快进/快退5秒 自定义快捷键设置(#2289) 2025-03-01 20:03:25 +08:00
lyswhut
4f0af93b72 允许拖动桌面歌词控制栏空白处移动歌词窗口(#2280) 2025-02-28 18:40:47 +08:00
lyswhut
4a656268b8 update electron to 34.3.0 2025-02-28 13:01:10 +08:00
lyswhut
2ad9d5990f 更新依赖&版本号(2.10.1-beta.4) 2025-02-22 15:37:03 +08:00
3gf8jv4dv
32585bc453 Revise License (#2273)
* Revise License

* update rtf file

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2025-02-22 15:35:48 +08:00
3gf8jv4dv
66307d4545 Refine English translation (#2259) 2025-02-22 15:33:16 +08:00
3gf8jv4dv
8b9ae2734f Update publish/changeLog.md [skip ci] (#2270) 2025-02-20 00:29:22 +08:00
3gf8jv4dv
96f9e12d93 Optimize statusbar lyrics option description (#2269)
Clarify the meaning of enabling the system tray.

ref: https://support.apple.com/guide/mac-help/mchlp1446

related: comments on https://github.com/lyswhut/lx-music-desktop/pull/1940
2025-02-17 22:58:34 +08:00
lyswhut
43c4e13c11 修复本地 FLAC 文件内嵌歌词无法读取的问题 2025-02-12 22:00:03 +08:00
3gf8jv4dv
27f6168a29 Revise description about Kugou on "Open Playlist" dialog (#2266)
Related: https://github.com/lyswhut/lx-music-desktop/issues/2246
2025-02-12 20:27:24 +08:00
lyswhut
70dbbc93f4 更新依赖&版本号(2.10.1-beta.3) 2025-02-10 18:17:45 +08:00
lyswhut
92c9808bfb 修复 Windows 下桌面歌词最小高度与宽度设置问题,允许更小的桌面歌词窗口宽度(#2244) 2025-02-10 18:17:45 +08:00
3gf8jv4dv
1f8df84b87 Fix music api management dialog layout (#2247) 2025-02-08 09:57:46 +08:00
3gf8jv4dv
271c1bcd51 Change window title to LX Music (#2248) 2025-02-08 09:57:22 +08:00
lyswhut
f830b1199a 更新依赖&版本号(2.10.1-beta.2) 2025-02-07 12:55:24 +08:00
lyswhut
b80d48aa91 修复windows下界面缩放后移动桌面歌词会改变歌词窗口大小的问题(#2244) 2025-02-07 12:54:41 +08:00
lyswhut
691d835d16 更新依赖&版本号(2.10.1-beta.1) 2025-02-04 23:26:00 +08:00
lyswhut
3ac6d0ff5f 修复 tx 歌单搜索名字、描述出现乱码的问题(#2250) 2025-02-04 23:20:51 +08:00
lyswhut
68b32a20a4 更新版本号(2.10.1-beta.0) 2025-01-28 19:50:56 +08:00
lyswhut
88e4df4388 修复 Windows 下桌面歌词最小高度设置问题(#2244) 2025-01-28 19:49:33 +08:00
lyswhut
3e13980870 发布 v2.10.0 2025-01-27 00:59:37 +08:00
lyswhut
dd23af7051 更新依赖 2025-01-27 00:57:32 +08:00
lyswhut
7d5fc6de12 更新依赖&版本号(2.10.0-beta.12) 2025-01-25 20:11:32 +08:00
lyswhut
28233fbe58 更新依赖 2025-01-25 20:11:32 +08:00
3gf8jv4dv
32a35c6706 i18n cleanup 3 (#2229) 2025-01-18 22:15:39 +08:00
3gf8jv4dv
228ddace91 Update publish/changeLog.md (#2230) 2025-01-14 14:22:24 +08:00
lyswhut
f9ffdbbce7 更新依赖&版本号(2.10.0-beta.11) 2024-12-28 13:17:38 +08:00
3gf8jv4dv
9b233683a2 i18n cleanup 2 (#2215) 2024-12-28 13:14:05 +08:00
3gf8jv4dv
ff78fb6b30 i18n cleanup (#2211)
* i18n cleanup

- A final trimming of the language. (I hope it's the last.)
- For en-US and zh-TW, some of the terms have been revised again.
- For zh-CN, some of the terms have been revised to ensure that they do
not change the habits of users.

* update

* update

* update

* Apply suggestions from lyswhut

* Apply suggestions from lyswhut

* update

* update

* update

* Apply suggestions from lyswhut
2024-12-26 23:35:42 +08:00
3gf8jv4dv
8efda6478c Update publish/changeLog.md (#2212)
* Update publish/changeLog.md

* Apply suggestions from lyswhut

* update

* update

* update

* update
2024-12-26 23:31:57 +08:00
3gf8jv4dv
851c7871e3 Disable randomize popup animation option when animation is not turned on (#2210)
This option has no real purpose if "Show switch animation" is not turned
on. So I mimicked the option about lyrics in the download settings page.
2024-12-25 23:35:14 +08:00
3gf8jv4dv
a78c78f3e5 Revise app language text (3) (#2209) 2024-12-25 00:17:46 +08:00
3gf8jv4dv
05e8442ff1 Fix change music source dialog layout (#2204)
* Fix change music source dialog layout

* Apply suggestions from lyswhut

Co-authored-by: lyswhut <lyswhut@qq.com>

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-24 13:53:05 +08:00
3gf8jv4dv
977b842df3 Add missing quotes to language files (#2206) 2024-12-24 09:33:37 +08:00
3gf8jv4dv
5bde54aac1 Fix edit theme dialog title logic (#2205)
* Fix edit theme dialog title logic

As you can see from the code and the actual behavior, both share the
same code whether adding or editing a theme. So why does the title show
up as "Edit Theme" no matter what? It does seem strange in a way.

This feature appears to have been added in v2.0.0, but no one seems to
have cared about the issue during the two years.

Nonetheless, when I realized this recently, I decided to fix it.
Hopefully this will make things clearer for those users who may not be
able to tell the difference.

Now, when the "Add theme" button is clicked, the dialog title will
show "Add theme". If you right-click on a user-created theme, the dialog
title will show "Edit theme".

* Apply suggestions from lyswhut

Simpler code

Co-authored-by: lyswhut <lyswhut@qq.com>

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-23 23:53:10 +08:00
3gf8jv4dv
0f27e3ea42 Fix duplicate songs dialog layout (#2203) 2024-12-23 23:41:31 +08:00
3gf8jv4dv
d170725f0c Add scrollbar to "open shared playlist" dialog box (#2201) 2024-12-22 23:39:05 +08:00
3gf8jv4dv
a26fcf0cd8 Fix search page UI (#2200) 2024-12-22 23:38:33 +08:00
3gf8jv4dv
18a4455511 Fix edit theme dialog layout 3 (#2199) 2024-12-22 23:37:59 +08:00
3gf8jv4dv
2ab3286d0e Add scrollbar to the Sort Songs dialog box (#2197) 2024-12-22 23:32:59 +08:00
3gf8jv4dv
d4a411ab4a Fix list update management dialog UI (#2198) 2024-12-22 01:19:45 +08:00
lyswhut
c7facb38b1 更新依赖&版本号(2.10.0-beta.10) 2024-12-22 00:09:31 +08:00
3gf8jv4dv
b7434e9eaf Revise app language text (2) (#2195) 2024-12-21 23:57:50 +08:00
3gf8jv4dv
4a174c0084 Fix edit theme dialog layout again (#2192)
* Fix edit theme dialog layout again

Ensures that the layout looks better and doesn't get out of place in the
smallest windows and largest fonts, as well as in both English and
Chinese environments.

* update

* update

* fix

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-21 23:52:15 +08:00
3gf8jv4dv
db8ad1e8eb Optimize theme item name display (#2189)
Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-19 00:27:06 +08:00
3gf8jv4dv
1feaac003a Revise app language text (#2187)
* Revise app language text

Some additional revisions.

* Apply suggestion from lyswhut

Co-authored-by: lyswhut <lyswhut@qq.com>

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-18 23:54:02 +08:00
3gf8jv4dv
c42b2fb34c Fix edit theme dialog layout (#2188)
Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-18 23:37:59 +08:00
14K
5d48fa93ba fix: 修正拼写错误 inert*insert* (#2180) 2024-12-17 19:49:19 +08:00
3gf8jv4dv
527ac04c4e Revise zh-TW language (#2174)
* Revise zh-TW language

Revise according to Taiwanese language habits

Special thanks: Fanhuaji <https://zhconvert.org/>

* update

* Apply suggestion from lyswhut
2024-12-17 19:44:48 +08:00
14K
9644163dc5 fix: 修复了当listPosition未初始化时错误的删除操作 (#2178)
* feat: 新增了 当`列表歌曲位置`未初始化时删除列表时的判断

* fix

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-17 17:32:49 +08:00
3gf8jv4dv
d0090abd01 Optimize sort songs dialog layout (#2176)
Slightly increased width to ensure no layout issues in various languages
2024-12-17 17:16:13 +08:00
3gf8jv4dv
918d2ad156 Fix English (#2173)
- Fix some `playlist` terms
2024-12-16 23:28:41 +08:00
3gf8jv4dv
b028fb955f Optimize options with too long descriptions in settings (#2172) 2024-12-16 09:52:55 +08:00
3gf8jv4dv
ba33d5a39b Fix i18n keys (#2170)
`setting__setting__desktop_lyric_font_weight` to
`setting__desktop_lyric_font_weight`
2024-12-15 14:45:38 +08:00
3gf8jv4dv
fd0b9c55fc Revise zh-CN language (2) (#2169)
* Revise zh-CN language (2)

* Apply suggestion from lyswhut
2024-12-15 14:36:40 +08:00
3gf8jv4dv
b28fb5c9b4 Overhaul English (#2166)
* Overhaul English

Let's go!!!

Overhaul with reference to Simplified Chinese text
and English user habits.

* Clarify `Music API` and `Song Source`

* fix
2024-12-15 14:08:11 +08:00
lyswhut
77ecd4be9b 更新依赖&版本号(2.10.0-beta.9) 2024-12-14 12:48:20 +08:00
lyswhut
6f74003e14 新增下载的歌曲按列表名分组的功能(#2145) 2024-12-14 12:48:19 +08:00
lyswhut
63b2c2fb2f 修复本地歌曲同名歌词文件调整偏移时间后,下次再播放时调整的设置未被应用的问题(#2139) 2024-12-14 12:47:59 +08:00
3gf8jv4dv
08da7a37e6 Revise zh-CN language (#2159)
* Revise zh-CN language

- Optimize text typesetting.
- Fix some typos.
- Unify some terms.

* update

* update

* update
2024-12-10 23:30:29 +08:00
3gf8jv4dv
6b87a8d91b Fix zh-TW language name (#2155)
Write it in the native language.
2024-12-08 13:48:14 +08:00
3gf8jv4dv
0f1364a8d7 Revise ISSUE_TEMPLATE [skip ci] (#2153)
* Revise ISSUE_TEMPLATE [skip ci]

- Optimize typesetting and fix links.
- Rename the template file.
  - I don't know why git shows it as deleted & new files.

* update [skip ci]

* update [skip ci]

* update [skip ci]
2024-12-08 13:47:16 +08:00
3gf8jv4dv
9dbf70780d Revise English license (#2152)
* Revise English license

Fix some terms and mistakes.

* update
2024-12-08 13:40:55 +08:00
lyswhut
58e5592c2b 修复构建 2024-12-07 16:27:12 +08:00
lyswhut
02cc842cc8 更新依赖&版本号(2.10.0-beta.8) 2024-12-07 15:59:33 +08:00
lyswhut
e962e866b3 修复 Windows 任务管理器中的进程名显示为描述的问题(#2147) 2024-12-07 15:47:48 +08:00
lyswhut
af3b30096d 移除多余逻辑 2024-12-07 15:47:48 +08:00
3gf8jv4dv
aa7ab86b62 Fix some i18n keys (#2148) 2024-12-07 15:47:00 +08:00
3gf8jv4dv
7f864a9e35 Revise repo docs [skip ci] (#2146)
* Revise repo docs [skip ci]

Fix typos, optimize typesetting and descriptions.

* update

* update

* update

* update

* update

---------

Co-authored-by: lyswhut <lyswhut@qq.com>
2024-12-07 15:03:43 +08:00
lyswhut
609d345443 更新依赖&版本号(2.10.0-beta.7) 2024-12-04 09:07:04 +08:00
lyswhut
426815fa50 优化换源歌曲查找 2024-12-03 17:54:16 +08:00
lyswhut
5d1b1e48d7 Windows 下改用原生窗口调整方式(#2137) 2024-12-02 00:07:00 +08:00
lyswhut
28d0412adc 更新依赖&版本号(2.10.0-beta.6) 2024-11-30 18:50:11 +08:00
lyswhut
9d070b3671 修复kg id重复导致的问题 2024-11-30 11:54:26 +08:00
lyswhut
cd423752d1 优化歌曲换源机制,提升换源正确率 2024-11-29 12:39:42 +08:00
lyswhut
1f8a0eaf67 更新依赖与更新日志 2024-11-28 08:52:39 +08:00
lyswhut
42d9fcd8b0 更新依赖&版本号(2.10.0-beta.5) 2024-11-26 18:04:06 +08:00
lyswhut
4e1510c840 更新软件默认使用的字体 2024-11-26 18:03:52 +08:00
lyswhut
7ebfd43f65 更新关于说明 2024-11-24 00:06:05 +08:00
lyswhut
41d6157116 修复歌曲检查事件清理 2024-11-23 17:41:45 +08:00
lyswhut
f9f8297fd6 优化歌曲URL检查机制 2024-11-23 17:15:05 +08:00
lyswhut
dfbe3f665a 修复构建 2024-11-23 14:49:34 +08:00
lyswhut
b36df472e5 更新更新日志 2024-11-23 14:30:27 +08:00
lyswhut
0b147f708d 更新依赖&版本号(2.10.0-beta.4) 2024-11-23 14:29:31 +08:00
lyswhut
4e3bdbcc74 添加歌曲预加载功能 2024-11-23 14:21:59 +08:00
lyswhut
0345419ef3 客户端模式的同步服务连接允许重定向 5 次(#2109) 2024-11-10 11:05:59 +08:00
lyswhut
9237dbbc18 播放本地歌曲时,将优先尝试读取本地同名字的 jpgpng 图片作为播放封面显示 2024-10-28 23:04:48 +08:00
lyswhut
68ac1b7641 更新依赖&版本号(2.10.0-beta.3) 2024-10-25 20:13:30 +08:00
lyswhut
2cfe4d29ba 更新 electron 到 v32.2.2 2024-10-25 20:12:29 +08:00
lyswhut
b436497555 添加debug日志 2024-10-25 19:56:59 +08:00
lyswhut
7ac638c52c 修复 m4a 文件歌曲内嵌歌词无法读取的问题(#2090) 2024-10-25 19:56:41 +08:00
lyswhut
f21d2bc7c7 更新readme 2024-10-19 00:25:38 +08:00
lyswhut
550917bd16 更新依赖&版本号(2.10.0-beta.2) 2024-10-17 12:38:05 +08:00
lyswhut
c1c0311586 不再缓存换源歌曲信息 2024-10-17 12:36:59 +08:00
lyswhut
489bbb6902 Open API 新增播放器播放/暂停、切歌、收藏当前播放歌曲调用,详情看开放API文档 (原始 PR #2077) 2024-10-12 09:59:04 +08:00
lyswhut
86af25bde3 Scheme URL 的播放歌曲允许更长的专辑名称 2024-09-27 21:25:46 +08:00
lyswhut
a599e0716f 支持本地同名 krc 格式歌词文件的读取(#2053) 2024-09-25 18:04:42 +08:00
lyswhut
30fc818771 更新依赖&版本号(2.10.0-beta.1) 2024-09-24 18:49:46 +08:00
lyswhut
df08f677bd 优化播放详情页UI,歌曲名字、歌手等文字过长时被截断的问题(#2049) 2024-09-24 18:47:40 +08:00
lyswhut
7247bcad1b 修正搜索歌曲提示框文案(#2050) 2024-09-24 18:40:32 +08:00
lyswhut
c0bcac25ea fix build 2024-09-21 14:10:33 +08:00
lyswhut
ae06019bdb 更新依赖&版本号(2.10.0-beta.0) 2024-09-21 14:01:59 +08:00
lyswhut
432861780a 修复获取自定义环境音效预设列表逻辑问题 2024-09-21 12:45:04 +08:00
lyswhut
111ee88833 更新依赖 2024-09-06 09:44:30 +08:00
lyswhut
487ee7015e 修复歌单详情页内歌单名字过长时的UI显示问题(#2028) 2024-09-04 13:04:41 +08:00
lyswhut
061cc16c27 新增托盘图标颜色 跟随系统亮暗模式 设置,可以在 设置-其他 启用 (#2016) 2024-08-30 20:21:18 +08:00
lyswhut
af8d9dff2d 更新检查连接链接顺序 2024-08-24 13:39:27 +08:00
lyswhut
cb16bd0324 发布 v2.9.0 2024-08-24 12:57:27 +08:00
lyswhut
acf8171617 npm audit fix 2024-08-24 12:56:59 +08:00
lyswhut
da7429f03e 将下载歌曲的歌手信息中的分隔符从 替换为 ; 以确保音乐元数据在写入时的兼容性和一致性(#1989 @qnnp-me) 2024-08-24 12:51:09 +08:00
lyswhut
4a121db39d 更新 better-sqlite3 11.2.1 二进制文件 2024-08-24 12:28:50 +08:00
lyswhut
0e8e287fe2 update actions cache dir 2024-08-23 21:22:31 +08:00
lyswhut
cf942121dd 更新依赖&版本号(2.9.0-beta.12) 2024-08-23 20:07:09 +08:00
lyswhut
77c2e962ec 优化本地歌曲内嵌封面过大时的加载方式 2024-08-23 20:06:17 +08:00
lyswhut
05f5cc4fe8 更新依赖&版本号(2.9.0-beta.11) 2024-08-18 13:14:01 +08:00
lyswhut
684f09feaf 修复列表数据同步问题 2024-08-18 10:10:38 +08:00
lyswhut
38bea3ced1 优化换源机制 2024-08-17 20:30:07 +08:00
lyswhut
6a6f9a53bd 更新依赖&版本号(2.9.0-beta.10) 2024-08-17 13:45:33 +08:00
lyswhut
584c12e60f 新增 我的列表-换源播放 功能 2024-08-17 13:44:11 +08:00
lyswhut
bb5b667613 更新版本号(2.9.0-beta.9) 2024-08-11 11:24:25 +08:00
lyswhut
0628d3533f 新增设置-下载设置-同时下载任务数设置(#1498) 2024-08-10 14:31:35 +08:00
lyswhut
e1bd4c9a0a 更新依赖&版本号(2.9.0-beta.8) 2024-08-10 10:27:05 +08:00
lyswhut
85c9ec38db 新增鼠标在音量按钮使用滚轮时可以调整音量大小的功能(#2000) 2024-08-10 10:18:27 +08:00
lyswhut
6ff7cfde11 优化某些UI的字体可能出现被截断的问题 2024-08-04 11:33:44 +08:00
lyswhut
c52034f992 更新版本号(2.9.0-beta.7) 2024-08-04 10:49:41 +08:00
lyswhut
b982dd90e4 修复程序目录路径存在 #% 时,自定义源、托盘等图标异常的问题(#1997) 2024-08-04 10:48:31 +08:00
lyswhut
80e36688e8 更新依赖 2024-08-03 12:26:53 +08:00
lyswhut
c6c1071d2d 优化添加不喜欢歌曲时的提示文案 2024-08-02 12:32:20 +08:00
lyswhut
481817c607 更新依赖&版本号(2.9.0-beta.6) 2024-07-27 17:23:02 +08:00
lyswhut
2e5739f042 Revert "优化代码"
This reverts commit 72d44ec783.
2024-07-27 17:21:09 +08:00
lyswhut
0bb9f5ece5 更新依赖&版本号(2.9.0-beta.5) 2024-07-26 18:55:59 +08:00
lyswhut
d454f51993 新增设置-播放详情页设置-延迟歌词滚动设置(#1985) 2024-07-26 18:52:27 +08:00
lyswhut
7f3ab31ad5 尝试修复某些情况下播放音频时,处于播放状态但是进度条不走的问题 2024-07-26 18:50:45 +08:00
lyswhut
98fe433819 更新依赖&版本号(2.9.0-beta.4) 2024-07-24 19:32:39 +08:00
lyswhut
4b01326420 更新依赖 2024-07-20 13:11:35 +08:00
lyswhut
3a4acd2693 更新依赖&版本号(2.9.0-beta.3) 2024-07-16 19:06:16 +08:00
lyswhut
b8e9937083 托盘图标添加当前播放歌曲名字显示 2024-07-15 19:42:51 +08:00
lyswhut
72d44ec783 优化代码 2024-07-11 18:52:48 +08:00
lyswhut
680b60cb07 更新依赖&版本号(2.9.0-beta.2) 2024-07-04 19:49:39 +08:00
lyswhut
45527b320d 修复开发模式下 vue devtools 扩展没有加载的问题 2024-07-04 19:41:49 +08:00
lyswhut
0acc9a553e 优化侧栏图标显示,修复图标可能被裁切的问题(#1960) 2024-06-26 19:21:55 +08:00
lyswhut
19f81cf379 优化代码 2024-06-19 19:25:21 +08:00
lyswhut
96ddbdf0f0 更新依赖&版本号(2.9.0-beta.1) 2024-06-19 12:40:04 +08:00
lyswhut
9eb87911a6 修复界面不可见时状态栏歌词没有更新的问题 2024-06-19 12:39:44 +08:00
lyswhut
b0881726fa 更新依赖&版本号(2.9.0-beta.0) 2024-06-18 19:33:46 +08:00
lyswhut
308248d8ec 新增 是否将歌词显示在状态栏 设置,默认关闭,该功能只在 MacOS 下可用(#1940) 2024-06-18 19:30:54 +08:00
lyswhut
de5ea4f0aa 修复切换音源时可能出现切换死循环的问题 2024-06-17 12:37:26 +08:00
lyswhut
305ce6ff11 更新依赖&版本号(2.8.1-beta.3) 2024-06-11 19:47:40 +08:00
lyswhut
bd4dbb55e4 现在如果在设置或者启动参数配置了代理服务,那么应用内的图片、音频加载,歌曲下载也将走代理 2024-06-11 19:41:10 +08:00
lyswhut
1fefb2bcaf 更新 electron 到 v29.4.2 2024-06-11 14:09:31 +08:00
3gf8jv4dv
e094e80ecf Some fixes to lang files (#1935)
* `Closure` -> `Close`

* Fix `player__sound_effect_features_tip`
2024-06-10 22:44:00 +08:00
lyswhut
9715db1229 更新版本号(2.8.1-beta.2) 2024-06-04 18:44:46 +08:00
lyswhut
3c0daa2bf1 修复 OpenAPI 的 lyricLineAllText 在切换到无歌词的音乐时内容没有更新的问题(#1925) 2024-06-04 18:42:52 +08:00
lyswhut
435fec2f1c 更新版本号(2.8.1-beta.1) 2024-06-03 18:30:26 +08:00
lyswhut
69ba619f24 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923) 2024-06-03 18:29:54 +08:00
lyswhut
b7bb21f091 更新版本号(2.8.1-beta.0) 2024-06-03 12:50:19 +08:00
lyswhut
77f51a3e9f 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923) 2024-06-03 12:49:26 +08:00
323 changed files with 13978 additions and 11341 deletions

View File

@@ -1,26 +0,0 @@
{
"presets": [
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"corejs": "3",
"useBuiltIns": "usage"
}
]
// [
// "minify",
// {
// "builtIns": false,
// "evaluate": false,
// "mangle": false
// }
// ]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-modules-umd",
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties"
]
}

View File

@@ -55,7 +55,6 @@ const vueRule = {
exports.base = {
extends: ['standard'],
rules: baseRule,
parser: '@babel/eslint-parser',
}
exports.html = {

View File

@@ -1,55 +0,0 @@
name: 🐞报告Bug
description: 报告bug请先查看常见问题及搜索issue列表中有无你要提的问题
title: "[Bug]: "
body:
- type: checkboxes
id: check-answer
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作
options:
- label: 我已阅读常见问题(<https://lyswhut.github.io/lx-music-doc/desktop/faq>),并没有找到解决方案
required: true
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),并没有发现类似的问题
required: true
- type: textarea
id: expected-behavior
attributes:
label: 预期行为
description: 对期望发生的事情的清晰简明描述
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: 实际行为
description: 对实际发生的事情的清晰简明描述
validations:
required: true
- type: input
id: version
attributes:
label: Lx Music 版本
description: 你使用什么版本的LX Music
placeholder: 1.15.0
validations:
required: true
- type: input
id: last-known-working-version
attributes:
label: 最后正常的版本
description: 如果有,请在此处填写最后正常的版本是多少?
placeholder: 1.15.0
- type: input
id: operating-system-version
attributes:
label: 操作系统版本
description: 您使用的是什么操作系统版本?在 Windows 上,单击开始按钮 > 设置 > 系统 > 关于;在 macOS 上,单击 Apple 菜单 > 关于本机;在 Linux 上,使用 lsb_release 或 uname -a
placeholder: "例如 Windows 10 版本 1909、macOS Catalina 10.15.7 或 Ubuntu 20.04"
validations:
required: true
- type: textarea
id: additional-information
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者你所遇到的问题不容易重现,请在此处添加更多信息。(直接把图片、视频拖到编辑框即可添加图片或视频)

59
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: 🐞 报告错误
description: 报告一个错误Bug请先查看常见问题及搜索 Issue 列表中有无你要提的问题。
title: "[Bug]: "
body:
- type: checkboxes
id: check-answer
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作。
options:
- label: 我已阅读 [常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq),并没有找到解决方案。
required: true
- label: 我已搜索 [Issue 列表](https://github.com/lyswhut/lx-music-desktop/issues?q=is%3Aissue+),并没有发现类似的问题。
required: true
- type: textarea
id: expected-behavior
attributes:
label: 预期行为
description: 对期望发生的事情的清晰简明描述。
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: 实际行为
description: 对实际发生的事情的清晰简明描述。
validations:
required: true
- type: input
id: version
attributes:
label: LX Music 版本
description: 你使用什么版本的 LX Music
placeholder: 例如 2.9.0
validations:
required: true
- type: input
id: last-known-working-version
attributes:
label: 最后正常的版本
description: 如果有,请在此处填写最后正常的版本。
placeholder: 例如 2.8.0
- type: input
id: operating-system-version
attributes:
label: 操作系统版本
description: |
你使用什么版本的操作系统?
在 macOS 上单击「Apple 菜单 > 关于本机」;
在 Linux 上,执行 `lsb_release` 或 `uname -a` 命令;
在 Windows 上,单击「开始按钮 > 设置 > 系统 > 关于」。
placeholder: "例如 Windows 11 版本 24H2、macOS Sequoia 15.1.1 或 Ubuntu 24.10"
validations:
required: true
- type: textarea
id: additional-information
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者你所遇到的问题不容易重现,请在此处添加更多信息。(直接把图片/视频拖到编辑框即可添加图片/视频)

View File

@@ -1,40 +1,38 @@
name: ✨功能请求
description: 为这个项目提出一个想法,请先查看常见问题及搜索issue列表中有无你要提的问题
name: 功能请求
description: 为这个项目提出一个想法,请先查看常见问题及搜索 Issue 列表中有无你要提的问题
title: "[Feature]: "
body:
- type: checkboxes
id: check-answer
attributes:
label: 解决方案检查
description: 请确保你已完成以下所有操作
description: 请确保你已完成以下所有操作
options:
- label: 我已阅读常见问题(<https://lyswhut.github.io/lx-music-doc/desktop/faq>),但没有找到解决方案
- label: 我已阅读 [常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq),但没有找到解决方案
required: true
- label: 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>),但没有发现类似的问题
- label: 我已搜索 [Issue 列表](https://github.com/lyswhut/lx-music-desktop/issues?q=is%3Aissue+),但没有发现类似的问题
required: true
- type: textarea
id: problem-description
attributes:
label: 问题描述
description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题
description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: 描述你想要的解决方案
description: 简洁明了地描述你要发生的事情
description: 简洁明了地描述你要发生的事情
validations:
required: true
- type: textarea
id: alternatives-considered
attributes:
label: 描述你考虑过的替代方案
description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述
validations:
required: false
description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述
- type: textarea
id: additional-information
attributes:
label: 附加信息
description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片视频拖到编辑框即可添加图片视频)
description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片/视频拖到编辑框即可添加图片/视频)

View File

@@ -5,9 +5,9 @@ runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '20'
node-version: '22'
# - name: Get npm cache directory
# run: node -p -e '`NPM_CACHE_DIR=${require("child_process").execSync("npm config get cache").toString()}`' >> $GITHUB_ENV
@@ -16,10 +16,9 @@ runs:
# https://docs.npmjs.com/cli/v10/configuring-npm/folders#cache
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
${{ env.NPM_CACHE }}
path: ${{ env.NPM_CACHE }}
key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-cache-

View File

@@ -14,7 +14,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - name: Check out git repository
# uses: actions/checkout@v4
# uses: actions/checkout@v6
# - name: Install Node.js
# uses: actions/setup-node@v4
@@ -43,14 +43,18 @@ jobs:
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%LocalAppData%/npm-cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
@@ -61,7 +65,7 @@ jobs:
- name: Build Package Setup x64
run: npm run pack:win:setup:x64
- name: Upload Artifact Setup x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-x64-Setup
path: build/*-x64-Setup.exe
@@ -69,7 +73,7 @@ jobs:
- name: Build Package 7z x64
run: npm run pack:win:7z:x64
- name: Upload Artifact 7z x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-win_x64-green
path: build/*win_x64-green.7z
@@ -77,7 +81,7 @@ jobs:
- name: Build Package Setup arm64
run: npm run pack:win:setup:arm64
- name: Upload Artifact Setup arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-arm64-Setup
path: build/*-arm64-Setup.exe
@@ -85,22 +89,56 @@ jobs:
- name: Build Package 7z arm64
run: npm run pack:win:7z:arm64
- name: Upload Artifact 7z arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-win_arm64-green
path: build/*win_arm64-green.7z
- name: Generate file MD5
run: |
cd build
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
- name: Install old electron
run: npm install electron@22
Windows_7:
name: Windows_7
runs-on: windows-latest
env:
BUILD_WIN7: true
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v6
- name: Install python setuptools
run: pip.exe install setuptools
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Prepare win7 undici env
run: |
npm install undici@5
Set-Content -Path .\src\common\utils\request.ts -Value "export * from './request_node16'"
- name: Build src code
env:
BUILD_WIN7: true
run: |
git status --porcelain
npm run build
- name: Prepare win7 electron env
run: |
npm install electron@22 better-sqlite3@12
pip.exe install setuptools
- name: Build Package win7 Setup x64
run: npm run pack:win7:setup:x64
- name: Upload Artifact win7 Setup x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-win7_x64-Setup
path: build/*win7_x64-Setup.exe
@@ -108,7 +146,7 @@ jobs:
- name: Build Package win7 7z x64
run: npm run pack:win7:7z:x64
- name: Upload Artifact win7 7z x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-win7_x64-green
path: build/*win7_x64-green.7z
@@ -116,7 +154,7 @@ jobs:
- name: Build Package win7 7z x86
run: npm run pack:win7:7z:x86
- name: Upload Artifact win7 7z x86
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-win7_x86-green
path: build/*win7_x86-green.7z
@@ -130,20 +168,20 @@ jobs:
name: Mac
runs-on: macos-latest
# needs: CheckCode
env:
NPM_CACHE: ~/.npm
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install python setuptools
run: |
mkdir ~/.venv
python3 -m venv ~/.venv
source ~/.venv/bin/activate
python3 -m pip install setuptools
run: brew install python-setuptools
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
@@ -157,14 +195,14 @@ jobs:
npm run pack:mac:dmg:arm64
- name: Upload Artifact dmg
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-mac-dmg
path: |
build/*.dmg
!build/*-arm64.dmg
- name: Upload Artifact dmg
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-mac-dmg-arm64
path: build/*-arm64.dmg
@@ -177,17 +215,21 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: ~/.npm
# needs: CheckCode
steps:
- name: Install package
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
@@ -198,7 +240,7 @@ jobs:
- name: Build Package deb amd64
run: npm run pack:linux:deb:amd64
- name: Upload Artifact deb amd64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-deb-amd64
path: build/*_amd64.deb
@@ -206,7 +248,7 @@ jobs:
- name: Build Package deb arm64
run: npm run pack:linux:deb:arm64
- name: Upload Artifact deb arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-deb-arm64
path: build/*_arm64.deb
@@ -214,7 +256,7 @@ jobs:
- name: Build Package deb armv7l
run: npm run pack:linux:deb:armv7l
- name: Upload Artifact deb armv7l
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-deb-armv7l
path: build/*_armv7l.deb
@@ -222,7 +264,7 @@ jobs:
- name: Build Package x64 appImage
run: npm run pack:linux:appImage
- name: Upload Artifact x64 appImage
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-x64-appImage
path: build/*_x64.AppImage
@@ -230,7 +272,7 @@ jobs:
- name: Build Package x64 rpm
run: npm run pack:linux:rpm
- name: Upload Artifact x64 rpm
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-x64-rpm
path: build/*.x64.rpm
@@ -238,7 +280,7 @@ jobs:
- name: Build Package x64 pacman
run: npm run pack:linux:pacman
- name: Upload Artifact x64 pacman
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: lx-music-desktop-x64-pacman
path: build/*_x64.pacman

View File

@@ -11,12 +11,12 @@ jobs:
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22
- name: Install Dependencies
run: npm ci

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v2
uses: peter-evans/repository-dispatch@v4
with:
token: ${{ secrets.PAT }}
repository: lyswhut/lx-music-desktop-version-info

View File

@@ -14,7 +14,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - name: Check out git repository
# uses: actions/checkout@v4
# uses: actions/checkout@v6
# - name: Install Node.js
# uses: actions/setup-node@v4
@@ -43,14 +43,21 @@ jobs:
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%LocalAppData%/npm-cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Show Env
run: echo "${{ env.NPM_CACHE }}"
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
@@ -77,24 +84,35 @@ jobs:
Windows_7:
name: Windows_7
runs-on: windows-latest
env:
NPM_CACHE: '%LocalAppData%/npm-cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get npm cache directory
shell: pwsh
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Prepare win7 undici env
run: |
npm install undici@5
Set-Content -Path .\src\common\utils\request.ts -Value "export * from './request_node16'"
- name: Build src code
env:
BUILD_WIN7: true
run: |
git status --porcelain
npm run build
- name: Prepare win7 electron env
run: |
npm install electron@22
npm install electron@22 better-sqlite3@12
pip.exe install setuptools
- name: Release win7 package
@@ -114,21 +132,23 @@ jobs:
Mac:
name: Mac
runs-on: macos-latest
env:
NPM_CACHE: ~/.npm
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install python3 setuptools
run: |
mkdir ~/.venv
python3 -m venv ~/.venv
source ~/.venv/bin/activate
python3 -m pip install setuptools
run: brew install python-setuptools
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Show Env
run: echo "${{ env.NPM_CACHE }}"
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code
@@ -152,17 +172,23 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: ~/.npm
# needs: CheckCode
steps:
- name: Install package
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Get npm cache directory
shell: bash
run: echo "NPM_CACHE=$(npm config get cache)" >> $GITHUB_ENV
- name: Show Env
run: echo "${{ env.NPM_CACHE }}"
- name: Setup Node Env
env:
NPM_CACHE: ${{ env.NPM_CACHE }}
uses: ./.github/actions/setup
- name: Build src code

View File

@@ -6,13 +6,25 @@ module.exports = {
'del',
'comlink',
'vue',
'vue-router',
'image-size',
'message2call',
'@types/ws',
'eslint',
'@types/node',
'electron-debug',
'eslint-webpack-plugin',
'typescript',
'undici',
'@types/better-sqlite3',
'changelog-parser',
'webpack-dev-server',
'eslint-plugin-vue',
'vue-eslint-parser',
// 'eslint-config-standard-with-typescript',
// 'typescript', // https://github.com/microsoft/TypeScript/pull/54567
'webpack',
],
// target: 'newest',
@@ -23,13 +35,25 @@ module.exports = {
// target: 'patch',
// filter: [
// 'electron',
// 'vue',
// 'vue-router',
// ],
// target: 'minor',
// filter: [
// 'electron',
// 'eslint',
// 'eslint-webpack-plugin',
// 'electron-debug',
// 'typescript',
// '@types/node',
// 'undici',
// 'eslint-plugin-vue',
// 'vue-eslint-parser',
// 'better-sqlite3',
// '@types/better-sqlite3',
// 'webpack-dev-server',
// ],
}

View File

@@ -6,6 +6,276 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [2.12.6](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.5...v2.12.6) - 2026-09-19
目前新项目 Any Listen 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
对于日常使用 LX Music 的人可以试试迁移到 Any Listen若遇到任何问题可以发 issue 反馈。
Any Listen 的项目地址为 https://github.com/any-listen/any-listen
### 优化
- 优化 tx 推荐歌单列表
- 优化禁用透明窗口的窗口边框显示效果
### 修复
- 修复打开某些 kg 歌单时歌曲丢失的问题
- 修复 Windows 7 无法启动的问题
- 修复禁用透明窗口时按 F11 无法全屏的问题
## [2.12.5](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.4...v2.12.5) - 2026-09-14
目前新项目 Any Listen 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
对于日常使用 LX Music 的人可以试试迁移到 Any Listen若遇到任何问题可以发 issue 反馈。
Any Listen 的项目地址为 https://github.com/any-listen/any-listen
### 修复
- 修复自定义主题编辑器的颜色选择器默认值显示异常的问题(#2957
- 修复下载的 MP3 文件内嵌歌曲数据失败的问题(#2959
## [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
我们很高兴地宣布新项目 Any Listen 的桌面版已发布目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能更多功能仍在积极开发中桌面版与Web版将同步更新。
对于有播放本地音乐或播放服务器上音乐需求的人可以试试,若遇到任何问题可以发 issue 反馈。
### 优化
- 优化歌单内歌曲搜索结果排序 (#2734)
### 修复
- 修复桌面歌词的 鼠标移入歌词区域时提高透明度 设置不稳定的问题(#2679, @Little100
- 修复某些情况下可能播放没有声音的问题(#2693
- 修复 tx 搜索结果显示异常的问题(#2753
- 修复音乐名称和歌手信息格式化问题(#2733
### 其他
- 更新 Electron 到 40.8.3
## [2.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.0...v2.12.1) - 2026-02-16
我们很高兴地宣布新项目 Any Listen 的桌面版已发布目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能更多功能仍在积极开发中桌面版与Web版将同步更新。
对于有播放本地音乐或播放服务器上音乐需求的人可以试试,若遇到任何问题可以发 issue 反馈。
### 优化
- 优化托盘图标行为:在非 Windows 系统中,点击托盘图标时不再显示主窗口
### 修复
- 修复音量条在调整音量时实际音量与显示的数值不一致的问题(#2606
- 修复某些情况下搜索框的搜索按钮布局错位的问题(#2622
## [2.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.11.0...v2.12.0) - 2025-11-29
我们很高兴地宣布新项目 Any Listen 的桌面版已发布目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能更多功能仍在积极开发中桌面版与Web版将同步更新。
对于有播放本地音乐或播放服务器上音乐需求的人可以试试,若遇到任何问题可以发 issue 反馈。
### 新增
- 新增「设置 → 其他设置 → 主窗口使用软件内置的圆角及阴影」选项 (#2360)
*默认启用,关闭后将使用系统原生的窗口样式,该选项重启软件后生效*
- 开放 API 新增播放器声音大小、静音、播放进度控制、完整歌词获取,详情看接入文档 (#2386)
- 新增「设置 → 播放设置 → 调换歌词翻译与歌词罗马音位置」选项,默认关闭 (#2451)
- 新增启动参数 `-hidden`,在启动时将软件最小化到系统托盘 (#2459)
- 新增 Any Listen 歌词(用于支持已下载歌曲的歌词逐字播放)标签数据读取与播放 (#2485)
- 新增 Any Listen 歌词(包含逐字歌词、翻译、罗马音歌词,如果有)嵌入与下载,默认启用
- 下载列表菜单新增歌曲添加弹窗,允许将所选歌曲的在线版本添加到收藏列表 (#2537)
### 修复
- 尝试修复进度为0时仍然显示下载完成的问题 (#2471)
- 修复TX源搜索失败 (#2575 @Folltoshe)
- 修复MG源歌单加载失败
- 修复MG源评论加载失败
### 变更
- 调换「歌词翻译」与「歌词罗马音」的位置,现在歌词罗马音在歌词翻译的上方展示
*若你想要恢复以前的行为,可以开启「调换歌词翻译与歌词罗马音位置」选项*
- 更新代理配置规则,现在不启用代理时,图片、音频加载将不再走系统代理 (#2382 @Folltoshe)
- 字体设置可以最多设置两种字体([any-listen#82](https://github.com/any-listen/any-listen/issues/82)
### 其他
- 更新 Electron 到 37.6.0
## [2.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.10.0...v2.11.0) - 2025-05-01
### 新增
- 新增「快进/快退5秒」自定义快捷键设置#2289
- 新增「设置 → 桌面歌词设置 → 暂停时提高歌词透明度」设置,默认启用(#2294
### 修复
- 修复 Windows 下桌面歌词最小高度与宽度设置问题(#2244
- 修复 Windows 下界面缩放后移动桌面歌词会改变歌词窗口大小的问题(#2244
- 修复 tx 歌单搜索名字、描述出现乱码的问题(#2250
- 修复本地 FLAC 文件内嵌歌词无法读取的问题
- 修复潜在播放暂停的问题
- 修复 kw 歌单详情出现打开失败的问题(#2317
- 修复 kg 热门评论无法获取的问题
- 修复桌面歌词被遮挡时会被暂停的问题(#2320
- 修复 kg 歌单打开失败的问题thanks @Folltoshe
### 优化
- 允许更小的桌面歌词窗口宽度
- 允许拖动桌面歌词控制栏空白处移动歌词窗口(#2280
- 优化「自定义源管理」对话框在小窗口下的布局(#2247, @3gf8jv4dv
- 优化软件文案编排(#2259, #2266, #2269, #2296, @3gf8jv4dv
### 变更
- 我的列表-歌曲菜单中的 歌曲换源 功能从之前的类似软连接的形式改成替换歌曲的形式,也就是说,现在该功能相当于快速在线搜索歌曲,确认换源后将自动将原来的歌曲删除再将选择的歌曲插入被删除歌曲的位置。
### 其他
- 更新项目文档(@3gf8jv4dv
- 更新 Electron 到 35.2.2
## [2.10.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.9.0...v2.10.0) - 2025-01-27
落雪祝大家新年快乐!
### 关于之前提到的新项目
新项目我取名叫 Any Listen希望它能像它的名字一样让我们能到处任意听歌。
经过一年多的开发因各种原因实际进度比预期的慢但还是赶在年前发布了第一个web服务预览版第一个版本仅支持播放服务器上的歌曲扩展功能暂时未能开放但已趋于完成一两个月内可以搞定。
目前的版本仅是“能用”的状态因时间关系部分UI未能重新设计但后面会继续完善。
该项目目前的目标用户是拥有自己服务器且上面存储有歌曲的人使用。
项目刚发布,文档未能完善,遇到使用问题或有任何建议欢迎提 issue 交流,
项目地址: https://github.com/any-listen/any-listen
---
*感谢 @3gf8jv4dv 对 LX 系列项目翻译、文档等文案的大幅修订优化。*
### 不兼容性变更
Linux 系统至少需要 `GLIBC_2.29` 版本才能运行。
由于将 Electron 升级到 v32.x原生库的编译被限制到不低于 C++ 20试了几次无法在 docker 镜像 `node:16` 安装 gcc-10最终将构建使用镜像更新到 `node:18`
### 新增
- 新增下载的歌曲按列表名分组的功能,默认关闭,可以通过「设置 → 下载设置 → 将文件保存到以对应列表命名的子目录中」启用(#2145
- 新增托盘图标样式「跟随系统亮暗模式」设置,可以在「设置 → 其他」里启用 #2016
- 支持本地同名 `.krc` 格式歌词文件的读取(#2053
- 开放 API 新增播放器播放/暂停、切歌、收藏当前播放歌曲等接口调用,详情看文档「开放 API 服务」部分(#2077, @14Kay
### 优化
- 优化正常播放结束时的下一首歌曲播放衔接度,在歌曲即将结束播放时将预获取下一首歌曲的播放链接,减少自动切歌时的等待时间(#2126
- 优化歌曲换源机制,提升换源正确率
- 优化 Windows 平台上桌面歌词窗口大小调整机制,改用原生的窗口调整方式(#2137
- 修正搜索歌曲提示框文案(#2050
- 优化播放详情页 UI修复「歌曲名」「艺术家」等文字过长时被截断的问题#2049
- Scheme URL 的播放歌曲允许更长的专辑名称
- 播放本地歌曲时,将优先尝试读取本地同名 `.jpg``.png` 图片作为播放封面显示,若文件不存在则从音频文件内读取,最后再尝试使用在线图片(#2096
- 客户端模式的同步服务连接允许重定向 5 次(#2109
- 更新软件默认使用的字体,修复 macOS Sequoia (15) 上界面出现乱码的问题(#2076
- 优化简体、繁体中文文案编排,大幅修订英语文案编排(#2159, #2166, #2174 等, @3gf8jv4dv
- 优化排序歌曲、主题名称、添加/编辑主题、列表更新管理等对话框布局及长文本显示效果(#2176, #2188, #2189, #2198 等, @3gf8jv4dv
### 修复
- 修复歌单详情页内歌单名字过长时的 UI 显示问题(#2028
- 修复获取自定义环境音效预设列表逻辑问题
- 修复 `.m4a` 文件内嵌歌词无法读取的问题(#2090
- 修复 Windows 任务管理器中的进程名显示为软件描述的问题(#2147
- 修复本地歌曲同名歌词文件调整偏移时间后,下次再播放时调整的设置未被应用的问题(#2139
- 修复首次打开软件后直接创建并删除列表时的报错问题(#2175, @14Kay
### 变更
- 不再长期缓存换源歌曲信息
- 更新软件默认使用的字体,现在软件尽量使用系统自带的默认字体
- Linux 系统至少需要 `GLIBC_2.29` 版本才能运行
### 其他
- 更新 Readme 文档,优化文案编排(#2146, Thanks @3gf8jv4dv
- 更新 Issue 模板(#2153, @3gf8jv4dv
- 更新项目文档(@3gf8jv4dv
- 修订项目协议文件(#2146, #2152, @3gf8jv4dv
- 更新 Electron 到 v32.3.0
## [2.9.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.8.0...v2.9.0) - 2024-08-24
### 新增
- 新增 设置-播放设置-是否将歌词显示在状态栏 设置,默认关闭,该功能只在 MacOS 下可用(#1940
- 新增设置-播放详情页设置-延迟歌词滚动设置(#1985
- 新增鼠标在音量按钮使用滚轮时可以调整音量大小的功能(#2000
- 新增设置-下载设置-同时下载任务数设置(#1498
- 新增 我的列表-歌曲右击菜单-歌曲换源 功能,换源后下次再播放该列表的该歌曲时将优先尝试播放所选源的歌曲,该功能允许你手动指定来源以解决自动换源失败或者换源不准确的问题
### 优化
- 优化侧栏图标显示,修复图标可能被裁切的问题(#1960
- 托盘图标添加当前播放歌曲名字显示
- 优化本地歌曲内嵌封面过大时的加载方式
- 将下载歌曲的歌手信息中的分隔符从 `、` 替换为 `;` 以确保音乐元数据在写入时的兼容性和一致性(#1989 @qnnp-me
### 修复
- 修复 MacOS 下点击 dock 右键菜单的退出按钮时,程序没有退出的问题(#1923
- 修复 OpenAPI 的 `lyricLineAllText` 在切换到无歌词的音乐时内容没有更新的问题(#1925
- 修复切换音源时可能出现切换死循环的问题
- 尝试修复某些情况下播放音频时,处于播放状态但是进度条不走的问题
- 修复程序目录路径存在 `#``%` 时,自定义源、托盘等图标异常的问题(#1997
### 变更
- 简化了应用退出行为,据测试,现在 linux 下若启用了托盘dock 右键菜单的 退出、关闭所有 之类的功能将不再退出程序,需改用托盘的退出按钮退出程序
- 现在如果在设置或者启动参数配置了代理服务,那么应用内的图片、音频加载,歌曲下载也将走代理
### 其他
- 更新 electron 到 v30.4.0
## [2.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.7.0...v2.8.0) - 2024-06-01
我们发布了关于 LX Music 项目发展调整与新项目计划的说明,

2
FAQ.md
View File

@@ -1,6 +1,6 @@
# lx-music-desktop 常见问题
本文档已迁移<https://lyswhut.github.io/lx-music-doc/desktop/faq>
本文档已迁移<https://lyswhut.github.io/lx-music-doc/desktop/faq>
<!--
在阅读本常见问题后仍然无法解决你的问题请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!

131
README.md
View File

@@ -1,5 +1,7 @@
<p align="center"><a href="https://github.com/lyswhut/lx-music-desktop"><img width="200" src="https://github.com/lyswhut/lx-music-desktop/blob/master/doc/images/icon.png" alt="lx-music logo"></a></p>
<h1 align="center">LX Music 桌面版</h1>
<p align="center">
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/release.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build/badge.svg" alt="Build status"></a>
@@ -28,143 +30,138 @@
[9]: https://img.shields.io/github/license/lyswhut/lx-music-desktop
[10]: https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE -->
<h2 align="center">洛雪音乐助手桌面版</h2>
<p align="center">一个基于 Electron & Vue 开发的音乐软件</p>
### 说明
一个基于 Electron + Vue 开发的音乐软件。
## 说明
所用技术栈:
- Electron 15+
- Electron 30+
- Vue 3
已支持的平台:
- Windows 7 及以上
- Mac OS
- Linux
- macOS
- Windows 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>
使用常见问题请转至:[常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)
*移动版项目地址:https://github.com/lyswhut/lx-music-mobile*
目前本项目的原始发布地址只有**GitHub**及**蓝奏网盘**,其他渠道均为第三方转载发布,与本项目无关!
> [!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)。
为了提高使用门槛本软件内的默认设置、UI操作不以新手友好为目标所以使用前建议先根据你的喜好浏览调整一遍软件设置阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)
软件变化请查看[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)
#### Scheme URL支持
软件下载请查看 [GitHub Releases](https://github.com/lyswhut/lx-music-desktop/releases)。
从v1.17.0起支持 Scheme URL可以使用此功能从浏览器等场景下调用LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
脚本安装地址:<https://greasyfork.org/zh-CN/scripts/438148><br>
使用常见问题请参阅[桌面版常见问题](https://lyswhut.github.io/lx-music-doc/desktop/faq)。
若你想自己调用LX Music可以看[Scheme URL支持](https://lyswhut.github.io/lx-music-doc/desktop/scheme-url)
目前本项目的原始发布地址只有 [**GitHub**](https://github.com/lyswhut/lx-music-desktop/releases),其他渠道均为第三方转载发布,与本项目无关!
#### 数据同步服务
为了提高使用门槛本软件内的默认设置、UI 操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)。
从v2.2.0起,我们发布了一个独立版的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme),如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明
### Scheme URL 支持
#### 启动参数
从 v1.17.0 起支持 Scheme URL可以使用此功能在浏览器等场景下调用 LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用。
目前软件已支持的启动参数如下:
脚本安装地址:[LX Music 辅助脚本](https://greasyfork.org/zh-CN/scripts/438148)。
- `-proxy-server` 设置代理服务器,代理应用的所有流量
- `-proxy-bypass-list` 以分号分隔的主机列表绕过代理服务器
- `-play` 启动时播放指定列表的音乐
- `-search` 启动软件时自动在搜索框搜索指定的内容
- `-dha` 禁用硬件加速启动Disable Hardware Acceleration
- `-dt` 以非透明模式启动Disable Transparent
- `-dhmkh` 禁用硬件媒体密钥处理Disable Hardware Media Key Handling
若你想自己调用 LX Music可以参考文档「[Scheme URL 支持](https://lyswhut.github.io/lx-music-doc/desktop/scheme-url)」部分。
启动参数的详细说明请看[启动参数说明](https://lyswhut.github.io/lx-music-doc/desktop/run-params)
### 数据同步服务
#### 数据存储路径
从 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)」部分。
### 数据存储目录
默认情况下,软件的数据存储在:
- Windows`%APPDATA%/lx-music-desktop`
- Linux`$XDG_CONFIG_HOME/lx-music-desktop``~/.config/lx-music-desktop`
- macOS`~/Library/Application Support/lx-music-desktop`
- Windows`%APPDATA%/lx-music-desktop`
在Windows平台,若程序目录下存在`portable`目录,则自动使用此目录作为数据存储目录(v1.17.0新增)。
Windows 平台,若程序文件夹中存在 `portable` 文件夹,则自动使用此文件夹作为数据存储文件夹(适用于 v1.17.0 及以上版本)。
### 源码使用方法
## 用户界面
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
<p><img width="100%" src="./doc/images/app.png" alt="lx-music desktop UI"></p>
### 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>
本项目欢迎 PR但为了 PR 能顺利合并,需要注意以下几点:
### 常见问题
常见问题已移至:<https://lyswhut.github.io/lx-music-doc/desktop/faq>
### 贡献代码
本项目欢迎PR但为了PR能顺利合并需要注意以下几点
- 对于添加新功能的PR建议在PR前先创建issue说明以确认该功能是否确实需要
- 对于修复Bug PR请提供修复前后的说明及重现方式
- 其他类型的PR则适当附上说明
- 对于添加新功能的 PR建议在提交 PR 前先创建 Issue 进行说明,以确认该功能是否确实需要。
- 对于修复 bug 的 PR请提供修复前后的说明及重现方式。
- 对于其他类型的 PR则适当附上说明。
贡献代码步骤:
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境
2. 克隆本仓库代码并切换`dev`分支开发
3. 提交PR`dev`分支
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境
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 的补充,如有冲突,以以下协议为准。
---
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
*词语约定:本协议中的“本项目”指 LX Music洛雪音乐助手)桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。*
#### 一、数据来源
### 一、数据来源
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据与未登录状态在官方平台APP获取的数据相同经过对数据简单地筛选与合并后进行展示因此本项目不对数据的合法性、准确性负责。
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据(与未登录状态在官方平台 APP 获取的数据相同),经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“自定义源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名、艺术家等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
1.3 本项目的非官方平台数据(例如我的列表”内列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
#### 二、版权数据
### 二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在**24小时内**清除使用本项目的过程中所产生的版权数据。
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 **24 小时内** 清除使用本项目的过程中所产生的版权数据。
#### 三、音乐平台别名
### 三、音乐平台别名
3.1 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意。如果官方音乐平台觉得不妥,可联系本项目更改或移除。
#### 四、资源使用
### 四、资源使用
4.1 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网。如果出现侵权可联系本项目移除。
#### 五、免责声明
### 五、免责声明
5.1 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
#### 六、使用限制
### 六、使用限制
6.1 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流。本项目不对项目内的技术可能存在违反当地法律法规的行为作保证。
6.2 **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
#### 七、版权保护
### 七、版权保护
7.1 音乐平台不易,请尊重版权,支持正版。
#### 八、非商业性质
### 八、非商业性质
8.1 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
#### 九、接受协议
### 九、接受协议
9.1 若你使用了本项目,代表你接受本协议。
9.1 若你使用了本项目,代表你接受本协议。
---
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换`@`)
若对此有疑问请 mail to: lyswhut+qq.com (请将 `+` 替换`@`)

View File

@@ -1,9 +1,12 @@
const { afterPack } = require('./deps')
const fs = require('fs').promises
// https://github.com/electron-userland/electron-builder/issues/4630
// https://github.com/electron-userland/electron-builder/issues/4630#issuecomment-782020139
module.exports = async(context) => {
await afterPack()
const { electronPlatformName, appOutDir } = context
if (electronPlatformName !== 'darwin') return
const {

View File

@@ -1,79 +1,60 @@
const fs = require('fs')
const fsPromises = require('fs').promises
const path = require('path')
// const fs = require('fs')
// const fsPromises = require('fs').promises
// const path = require('path')
const { Arch } = require('electron-builder')
const nodeAbi = require('node-abi')
// const nodeAbi = require('node-abi')
const { beforePack, copyLib } = require('./deps')
const better_sqlite3_fileNameMap = {
[Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm',
// const better_sqlite3_fileNameMap = {
// [Arch.x64]: 'linux-x64',
// [Arch.arm64]: 'linux-arm64',
// [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 qrc_decode_fileNameMap = {
win32: {
[Arch.x64]: 'win32-x64',
[Arch.ia32]: 'win32-ia32',
[Arch.arm64]: 'win32-arm64',
},
linux: {
[Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm',
},
darwin: {
[Arch.x64]: 'darwin-x64',
[Arch.arm64]: 'darwin-arm64',
},
}
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)
}
const replaceQrcDecodeLib = async(electronNodeAbi, platform, arch) => {
console.log('replace qrc_decode lib...', platform, electronNodeAbi, qrc_decode_fileNameMap[platform][arch])
const filePath = path.join(__dirname, `./lib/qrc_decode_electron-v${electronNodeAbi}-${qrc_decode_fileNameMap[platform][arch]}.node`)
const targetPath = path.join(__dirname, '../build/Release/qrc_decode.node')
const targetDir = path.dirname(targetPath)
if (fs.existsSync(targetDir)) await fsPromises.unlink(targetPath).catch(_ => _)
else await fsPromises.mkdir(targetDir, { recursive: true })
await fsPromises.copyFile(filePath, targetPath)
}
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 electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
await replaceQrcDecodeLib(electronNodeAbi, electronPlatformName, arch)
if (electronPlatformName !== 'linux' || process.env.FORCE) return
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
switch (arch) {
case Arch.x64:
case Arch.arm64:
case Arch.armv7l:
if (fs.existsSync(bindingFilePath)) {
// console.log('rename binding file...')
await fsPromises.rename(bindingFilePath, bindingBakFilePath)
}
await replaceSqliteLib(electronNodeAbi, arch)
break
await copyLib(archMap[arch], parseInt(electronVersion) == 22)
// const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
// if (electronPlatformName !== 'linux' || process.env.FORCE) return
// // const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
// // const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
// switch (arch) {
// case Arch.x64:
// case Arch.arm64:
// case Arch.armv7l:
// // if (fs.existsSync(bindingFilePath)) {
// // // console.log('rename binding file...')
// // await fsPromises.rename(bindingFilePath, bindingBakFilePath)
// // }
// await replaceSqliteLib(arch)
// break
default:
if (fs.existsSync(bindingFilePath)) return
// console.log('restore binding file...')
await fsPromises.rename(bindingBakFilePath, bindingFilePath)
break
}
// default:
// // if (fs.existsSync(bindingFilePath)) return
// // console.log('restore binding file...')
// // await fsPromises.rename(bindingBakFilePath, bindingFilePath)
// await copyLib(arch)
// break
// }
}

View File

@@ -10,6 +10,7 @@ const afterPack = require('./build-after-pack')
*/
const options = {
appId: 'cn.toside.music.desktop',
productName: 'lx-music-desktop',
beforePack,
afterPack,
protocols: {
@@ -34,7 +35,6 @@ const options = {
'node_modules/node-gyp-build',
'node_modules/bufferutil',
'node_modules/utf-8-validate',
'build/Release/qrc_decode.node',
'dist/**/*',
],
asar: {
@@ -65,7 +65,7 @@ const winOptions = {
oneClick: false,
language: '2052',
allowToChangeInstallationDirectory: true,
differentialPackage: true,
// differentialPackage: true,
license: './licenses/license.rtf',
shortcutName: 'LX Music',
},
@@ -81,12 +81,18 @@ const linuxOptions = {
icon: './resources/icons',
category: 'Utility;AudioVideo;Audio;Player;Music;',
desktop: {
Name: 'LX Music',
'Name[zh_CN]': 'LX Music',
'Name[zh_TW]': 'LX Music',
Encoding: 'UTF-8',
MimeType: 'x-scheme-handler/lxmusic',
StartupNotify: 'false',
// https://www.electron.build/app-builder-lib.interface.linuxdesktopfile
// https://www.electronjs.org/docs/latest/tutorial/linux-desktop-actions
// https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files
entry: {
Name: 'LX Music',
'Name[zh_CN]': 'LX Music',
'Name[zh_TW]': 'LX Music',
Encoding: 'UTF-8',
MimeType: 'x-scheme-handler/lxmusic',
StartupNotify: 'false',
},
},
},
appImage: {
@@ -106,23 +112,22 @@ const macOptions = {
},
dmg: {
window: {
width: 600,
height: 400,
width: 530,
height: 380,
},
contents: [
{
x: 106,
y: 252,
name: 'LX Music',
x: 140,
y: 200,
},
{
x: 490,
y: 252,
x: 390,
y: 200,
type: 'link',
path: '/Applications',
},
],
title: '洛雪音乐助手 v${version}',
title: 'LX Music v${version}',
},
}

View File

@@ -6,20 +6,28 @@ const path = require('node:path')
const rootPath = path.join(__dirname, '../')
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'),
'\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/better-sqlite3/package.json'),
`{
"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",
"build-release": "node-gyp clean && node-gyp rebuild --release --force_build=1",`,
],
]

55
build-config/deps.js Normal file
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 })
}

View File

@@ -9,7 +9,7 @@ const getGzipFiles = async() => {
// for (const name of names) {
// 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) => {
@@ -18,23 +18,20 @@ const unzip = async(filePath) => {
await fs.promises.mkdir(targetDir)
await tar.x({
file: filePath,
strip: 1,
strip: 0,
C: targetDir,
})
return targetDir
}
const files = [
'qrc_decode',
'better_sqlite3',
]
const files = ['better_sqlite3']
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) {
if (fileName == 'better_sqlite3' && !name.includes('linux')) continue
// if (fileName == 'better_sqlite3' && !name.includes('linux')) continue
const targetPath = path.join(libDir, `${fileName}_${name}.node`)
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 })
}
@@ -50,4 +47,3 @@ const run = async() => {
}
run()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +1,6 @@
const path = require('path')
const ESLintPlugin = require('eslint-webpack-plugin')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
target: 'electron-main',
output: {
@@ -18,7 +16,6 @@ module.exports = {
'electron-font-manager': 'electron-font-manager',
bufferutil: 'bufferutil',
'utf-8-validate': 'utf-8-validate',
'qrc_decode.node': isDev ? path.join(__dirname, '../../build/Release/qrc_decode.node') : path.join('../build/Release/qrc_decode.node'),
},
resolve: {
alias: {

View File

@@ -37,6 +37,7 @@ module.exports = merge(baseConfig, {
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
BUILD_WIN7: `'${process.env.BUILD_WIN7}'`,
},
}),
],

View File

@@ -0,0 +1,3 @@
const { copyLib } = require('./deps')
copyLib()

View File

@@ -19,15 +19,6 @@ module.exports = merge(baseConfig, {
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {

View File

@@ -18,15 +18,6 @@ module.exports = merge(baseConfig, {
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {

View File

@@ -19,7 +19,13 @@ const gitInfo = {
}
try {
if (!execSync('git status --porcelain').toString().trim()) {
let isClean = !execSync('git status --porcelain').toString().trim()
if (process.env.BUILD_WIN7) {
console.warn('BUILD_WIN7 is set, skipping git status check.')
console.log('Workspace status:', execSync('git status --porcelain').toString().trim())
isClean = true
}
if (isClean) {
gitInfo.commit_id = execSync('git log -1 --pretty=format:"%H"').toString().trim()
gitInfo.commit_date = execSync('git log -1 --pretty=format:"%ad" --date=iso-strict').toString().trim()
} else if (process.env.IS_CI) {
@@ -33,15 +39,6 @@ module.exports = merge(baseConfig, {
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new CopyWebpackPlugin({
patterns: [

View File

@@ -61,7 +61,9 @@ function startRenderer() {
},
setupMiddlewares(middlewares, devServer) {
devServer.app.use(hotMiddlewareRenderer)
devServer.middleware.waitUntilValid(resolve)
setImmediate(() => {
devServer.middleware.waitUntilValid(resolve)
})
return middlewares
},
@@ -107,7 +109,9 @@ function startRendererLyric() {
},
setupMiddlewares(middlewares, devServer) {
devServer.app.use(hotMiddlewareRenderer)
devServer.middleware.waitUntilValid(resolve)
setImmediate(() => {
devServer.middleware.waitUntilValid(resolve)
})
return middlewares
},
}, compiler)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 487 KiB

View File

@@ -1,12 +1,12 @@
{
"compilerOptions": {
"baseUrl": "./",
"rootDir": "./",
"paths": {
"@main/*": ["src/main/*"],
"@renderer/*": ["src/renderer/*"],
"@lyric/*": ["src/renderer-lyric/*"],
"@static/*": ["src/static/*"],
"@common/*": ["src/common/*"],
"@main/*": ["./src/main/*"],
"@renderer/*": ["./src/renderer/*"],
"@lyric/*": ["./src/renderer-lyric/*"],
"@static/*": ["./src/static/*"],
"@common/*": ["./src/common/*"],
}
},
"vueCompilerOptions": {

View File

@@ -1,16 +1,19 @@
{\rtf1\adeflang1025\ansi\ansicpg936\uc2\adeff0\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1033\deflangfe2052\themelang1033\themelangfe2052\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}
{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'cb\'ce\'cc\'e5{\*\falt SimSun};}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}
{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@\'cb\'ce\'cc\'e5;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\f36\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}{\f44\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@\'cb\'ce\'cc\'e5;}
{\f45\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@\'b5\'c8\'cf\'df;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbmajor\f31501\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}{\fhimajor\f31502\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df Light;}
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbminor\f31505\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}{\fhiminor\f31506\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}\'b5\'c8\'cf\'df{\*\falt DengXian};}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f65\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f66\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
{\f68\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f69\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f70\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f71\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}
{\f72\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f73\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f177\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f385\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}
{\f386\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f388\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f389\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f392\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}
{\f393\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f487\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f46\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f47\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f49\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f50\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f51\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f52\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f53\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f54\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f66\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f67\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}
{\f69\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f70\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f71\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f72\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}
{\f73\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f74\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f178\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt SimSun};}{\f386\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}
{\f387\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f389\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f390\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f393\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}
{\f394\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f408\fbidi \fnil\fcharset0\fprq2 DengXian Western{\*\falt DengXian};}{\f406\fbidi \fnil\fcharset238\fprq2 DengXian CE{\*\falt DengXian};}
{\f407\fbidi \fnil\fcharset204\fprq2 DengXian Cyr{\*\falt DengXian};}{\f409\fbidi \fnil\fcharset161\fprq2 DengXian Greek{\*\falt DengXian};}{\f488\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\f498\fbidi \fnil\fcharset0\fprq2 @DengXian Western;}
{\f496\fbidi \fnil\fcharset238\fprq2 @DengXian CE;}{\f497\fbidi \fnil\fcharset204\fprq2 @DengXian Cyr;}{\f499\fbidi \fnil\fcharset161\fprq2 @DengXian Greek;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 DengXian Light Western;}{\fdbmajor\f31518\fbidi \fnil\fcharset238\fprq2 DengXian Light CE;}
@@ -38,12 +41,12 @@
\fs21\lang1033\langfe2052\kerning2\loch\f31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext15 \slink16 \sunhideused Plain Text;}{\*\cs16 \additive \rtlch\fcs1 \af2 \ltrch\fcs0 \loch\f31505\hich\af2 \sbasedon10 \slink15 \slocked
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}{\*\cs17 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid9533173 Hyperlink;}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21
\sbasedon10 \ssemihidden \sunhideused \styrsid9533173 Unresolved Mention;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf22 \sbasedon10 \ssemihidden \sunhideused \styrsid9533173 FollowedHyperlink;}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3758332\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid14511311\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0
\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2023\mo10\dy5\hr14\min42}{\version10}{\edmins4}{\nofpages1}{\nofwords193}{\nofchars1105}
{\nofcharsws1296}{\vern77}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid2766548\rsid3758332\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid14511311\rsid15225067\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0
\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2025\mo2\dy22\hr15\min34}{\version12}{\edmins5}{\nofpages2}
{\nofwords195}{\nofchars1117}{\nofcharsws1310}{\vern77}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
\deftab420\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\formshade\horzdoc\dgmargin\dghspace180\dgvspace156
\dghorigin450\dgvorigin0\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind5\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\dghorigin2253\dgvorigin1440\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind1\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat {\upr{\*\fchars
!%),.:\'3b>?]\'7d\'a1\'e9\'a1\'a7\'a1\'e3\'a1\'a4\'a1\'a6\'a1\'a5\'a8\'44\'a1\'ac\'a1\'af\'a1\'b1\'a1\'ad\'a1\'eb\'a1\'e4\'a1\'e5?\'a1\'e6\'a1\'c3\'a1\'a2\'a1\'a3\'a1\'a8\'a1\'b5\'a1\'b7\'a1\'b9\'a1\'bb\'a1\'bf\'a1\'b3\'a1\'bd\'a8\'95\'a6\'e1\'a6\'e3\'a6\'e7\'a6\'e5\'a6\'eb\'a9\'77\'a9\'79\'a9\'7b\'a3\'a1\'a3\'a2\'a3\'a5\'a3\'a7\'a3\'a9\'a3\'ac\'a3\'ae\'a3\'ba\'a3\'bb\'a3\'bf\'a3\'dd\'a3\'e0\'a3\'fc\'a3\'fd\'a1\'ab\'a1\'e9
}{\*\ud\uc0{\*\fchars
@@ -53,122 +56,128 @@ $([\'7b{\uc2\u163 \'a1\'ea\u165 \'a3\'a4\'a1\'a4\'a1\'ae\'a1\'b0\'a1\'b4\'a1\'b6
\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery851\footery992\colsx425\endnhere\sectlinegrid312\sectspecifyl\sectrsid2109456\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang
{\pntxta \dbch .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta \dbch )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl6
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl9
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf
\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2\'d0\'d0\'a3\'ac\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b5\'c4
\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid2766548 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf
\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2\'d0\'d0\'a3\'ac\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b5\'c4\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4
\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'cf\'ee\'c4\'bf\'a1\'b1\'d6\'b8
\'c2\'e5\'d1\'a9\'d2\'f4\'c0\'d6\'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb\'a1\'b0\'ca\'b9\'d3\'c3\'d5\'df\'a1\'b1\'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb\'a1\'b0\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6
\'c6\'bd\'cc\'a8\'a1\'b1\'d6\'b8\'b6\'d4\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3
\'b3\'c6\'a3\'bb\'a1\'b0\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'b1\'d6\'b8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0
\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'cf\'ee\'c4\'bf\'a1\'b1\'d6\'b8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \hich\af13\dbch\af13\loch\f13 LX Music }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\loch\af13\hich\af13\dbch\f13 \'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'d5\'df}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d6\'b8\'b6\'d4\'b1\'be
\'cf\'ee\'c4\'bf\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3\'b3\'c6\'a3\'bb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d6\'b8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb
\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'bb\'a1\'a2\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d2\'bb\'a1\'a2\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 1.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b8\'f7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'d4\'da\'cf\'df
\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7\'b4\'d3\'c6\'e4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'a8\'d3\'eb\'ce\'b4\'b5\'c7\'c2\'bc\'d7\'b4\'cc\'ac\'d4\'da\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 APP}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13
\'bb\'f1\'c8\'a1\'b5\'c4\'ca\'fd\'be\'dd\'cf\'e0\'cd\'ac\'a3\'a9\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be
\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.2 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b1\'be\'c9\'ed\'c3\'bb\'d3\'d0\'bb\'f1\'c8\'a1\'c4\'b3\'b8\'f6
\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b5\'c4\'c4\'dc\'c1\'a6\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'ca\'b9\'d3\'c3\'b5\'c4\'d4\'da\'cf\'df\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'c0\'b4\'d7\'d4\'c8\'ed\'bc\'fe\'c9\'e8\'d6\'c3\'c4\'da}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c0\'b4\'d4\'b4}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c9\'e8\'d6\'c3
\'cb\'f9\'d1\'a1\'d4\'f1\'b5\'c4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b8\'f7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'d4\'da\'cf\'df\'ca\'fd
\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7\'b4\'d3\'c6\'e4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'a8\'d3\'eb\'ce\'b4\'b5\'c7\'c2\'bc\'d7\'b4\'cc\'ac\'d4\'da\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8}{\rtlch\fcs1
\af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \hich\af13\dbch\af13\loch\f13 APP }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'bb\'f1\'c8\'a1
\'b5\'c4\'ca\'fd\'be\'dd\'cf\'e0\'cd\'ac\'a3\'a9\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be\'cf\'ee\'c4\'bf
\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 1.2 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b1\'be\'c9\'ed\'c3\'bb\'d3\'d0\'bb\'f1\'c8\'a1\'c4\'b3\'b8\'f6\'d2\'f4
\'c6\'b5\'ca\'fd\'be\'dd\'b5\'c4\'c4\'dc\'c1\'a6\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'ca\'b9\'d3\'c3\'b5\'c4\'d4\'da\'cf\'df\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'c0\'b4\'d7\'d4\'c8\'ed\'bc\'fe\'c9\'e8\'d6\'c3\'c4\'da}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d7\'d4\'b6\'a8\'d2\'e5\'d4\'b4}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c9\'e8\'d6\'c3\'cb\'f9
\'d1\'a1\'d4\'f1\'b5\'c4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'b5\'c4\'d4\'da\'cf\'df\'c1\'b4\'bd\'d3\'a1\'a3\'c0\'fd\'c8\'e7\'b2\'a5\'b7\'c5\'c4\'b3\'ca\'d7\'b8\'e8\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d7\'f6\'b5\'c4\'d6\'bb\'ca\'c7\'bd\'ab\'cf\'a3\'cd\'fb\'b2\'a5
\'b7\'c5\'b5\'c4\'b8\'e8\'c7\'fa\'c3\'fb\'d7\'d6\'a1\'a2\'b8\'e8\'ca\'d6\'c3\'fb\'d7\'d6\'b5\'c8\'d0\'c5\'cf\'a2\'b4\'ab\'b5\'dd\'b8\'f8}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'a3\'ac\'c8\'f4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'c1\'cb\'d2\'bb\'b8\'f6\'c1\'b4\'bd\'d3\'a3\'ac\'d4\'f2\'b1\'be\'cf\'ee\'c4\'bf\'bd\'ab\'c8\'cf\'ce\'aa\'d5\'e2
\'be\'cd\'ca\'c7\'b8\'c3\'b8\'e8\'c7\'fa\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b6\'f8\'bd\'f8\'d0\'d0\'ca\'b9\'d3\'c3\'a3\'ac\'d6\'c1\'d3\'da\'d5\'e2\'ca\'c7\'b2\'bb\'ca\'c7\'d5\'fd\'c8\'b7\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b1\'be\'cf\'ee
\'c4\'bf\'ce\'de\'b7\'a8\'d0\'a3\'d1\'e9\'c6\'e4\'d7\'bc\'c8\'b7\'d0\'d4\'a3\'ac\'cb\'f9\'d2\'d4\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc\'bb\'e1\'b3\'f6\'cf\'d6\'cf\'a3\'cd\'fb\'b2\'a5\'b7\'c5\'b5\'c4
\'d2\'f4\'c6\'b5\'d3\'eb\'ca\'b5\'bc\'ca\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'b2\'bb\'b6\'d4\'d3\'a6\'bb\'f2\'d5\'df\'ce\'de\'b7\'a8\'b2\'a5\'b7\'c5\'b5\'c4\'ce\'ca\'cc\'e2\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.3 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b7\'c7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'ca\'fd\'be\'dd
\'a3\'a8\'c0\'fd\'c8\'e7\'ce\'d2\'b5\'c4\'ca\'d5\'b2\'d8\'c1\'d0\'b1\'ed\'a3\'a9\'c0\'b4\'d7\'d4\'ca\'b9\'d3\'c3\'d5\'df\'b1\'be\'b5\'d8\'cf\'b5\'cd\'b3\'bb\'f2\'d5\'df\'ca\'b9\'d3\'c3\'d5\'df\'c1\'ac\'bd\'d3\'b5\'c4\'cd\'ac\'b2\'bd\'b7\'fe\'ce\'f1
\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'d5\'e2\'d0\'a9\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'b7\'c5\'b5\'c4\'b8\'e8\'c7\'fa\'c3\'fb\'a1\'a2\'d2\'d5\'ca\'f5\'bc\'d2\'b5\'c8\'d0\'c5\'cf\'a2\'b4\'ab\'b5\'dd\'b8\'f8}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'a3\'ac\'c8\'f4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'c1\'cb\'d2\'bb\'b8\'f6\'c1\'b4\'bd\'d3\'a3\'ac\'d4\'f2\'b1\'be\'cf\'ee\'c4\'bf\'bd\'ab\'c8\'cf\'ce\'aa\'d5\'e2\'be\'cd\'ca\'c7\'b8\'c3\'b8\'e8
\'c7\'fa\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b6\'f8\'bd\'f8\'d0\'d0\'ca\'b9\'d3\'c3\'a3\'ac\'d6\'c1\'d3\'da\'d5\'e2\'ca\'c7\'b2\'bb\'ca\'c7\'d5\'fd\'c8\'b7\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b1\'be\'cf\'ee\'c4\'bf\'ce\'de\'b7\'a8\'d0\'a3
\'d1\'e9\'c6\'e4\'d7\'bc\'c8\'b7\'d0\'d4\'a3\'ac\'cb\'f9\'d2\'d4\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc\'bb\'e1\'b3\'f6\'cf\'d6\'cf\'a3\'cd\'fb\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'d3\'eb\'ca\'b5
\'bc\'ca\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'b2\'bb\'b6\'d4\'d3\'a6\'bb\'f2\'d5\'df\'ce\'de\'b7\'a8\'b2\'a5\'b7\'c5\'b5\'c4\'ce\'ca\'cc\'e2\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'fe\'a1\'a2\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.3 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b7\'c7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'ca\'fd\'be\'dd\'a3\'a8
\'c0\'fd\'c8\'e7}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13
\'ce\'d2\'b5\'c4\'c1\'d0\'b1\'ed}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\loch\af13\hich\af13\dbch\f13 \'c4\'da\'c1\'d0\'b1\'ed\'a3\'a9\'c0\'b4\'d7\'d4\'ca\'b9\'d3\'c3\'d5\'df\'b1\'be\'b5\'d8\'cf\'b5\'cd\'b3\'bb\'f2\'d5\'df\'ca\'b9\'d3\'c3\'d5\'df\'c1\'ac\'bd\'d3\'b5\'c4\'cd\'ac\'b2\'bd\'b7\'fe\'ce\'f1\'a3\'ac\'b1\'be\'cf\'ee
\'c4\'bf\'b2\'bb\'b6\'d4\'d5\'e2\'d0\'a9\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 2.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0\'c8\'a8\'a1\'a3\'ce\'aa
\'c1\'cb\'b1\'dc\'c3\'e2\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 **24}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\hich\af13\dbch\af13\loch\f13 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'cb\'f9
\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b6\'fe\'a1\'a2\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'fd\'a1\'a2\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0\'c3\'fb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 2\hich\af13\dbch\af13\loch\f13 .1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc
\'d6\'d0\'bf\'c9\'c4\'dc\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0
\'c8\'a8\'a1\'a3\'ce\'aa\'c1\'cb\'b1\'dc\'c3\'e2\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \hich\af13\dbch\af13\loch\f13 **24 }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'cb\'f9
\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 3.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a1\'a3\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4
\'c0\'d6\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c8\'fd\'a1\'a2\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0\'c3\'fb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'cb\'c4\'a1\'a2\'d7\'ca\'d4\'b4\'ca\'b9\'d3\'c3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 3.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0
\'c3\'fb\'ce\'aa\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a1\'a3\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6
\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 4.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a1\'a3\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'cb\'c4\'a1\'a2\'d7\'ca\'d4\'b4\'ca\'b9\'d3\'c3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ce\'e5\'a1\'a2\'c3\'e2\'d4\'f0\'c9\'f9\'c3\'f7}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 4.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8\'b5\'ab
\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a1\'a3\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf\'d2\'c6
\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 5.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
\'c0\'a8\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1
\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'cb\'f0\'ba\'a6\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d2\'f2\'c9\'cc\'d3\'fe\'cb\'f0\'ca\'a7\'a1\'a2\'cd\'a3\'b9\'a4
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\'cb\'f9\loch\af13\hich\af13\dbch\f13 \'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0
\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'ce\'e5\'a1\'a2\'c3\'e2\'d4\'f0\'c9\'f9\'c3\'f7}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c1\'f9\'a1\'a2\'ca\'b9\'d3\'c3\'cf\'de\'d6\'c6}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 5.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc\'c0\'a8
\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3
\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'cb\'f0\'ba\'a6\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d2\'f2\'c9\'cc\'d3\'fe\'cb\'f0\'ca\'a7\'a1\'a2\'cd\'a3\'b9\'a4\'a1\'a2
\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\'cb\'f9\'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7
\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 6.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c1\'f9\'a1\'a2\'ca\'b9\'d3\'c3\'cf\'de\'d6\'c6}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 6.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4\'b7\'a2
\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\loch\af13\hich\af13\dbch\f13 \'c3\'e6\'cf\'f2\'c8\'ab\'ca\'c0\'bd\'e7\'c8\'cb\'d3\'c3\'d7\'f7\'b6\'d4\'bc\'bc\'ca\'f5\'b5\'c4\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'a1\'a3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'bc\'bc\'ca\'f5
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 6.2 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed
\par \hich\af13\dbch\af13\loch\f13 6.2 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf
\'b2\'bb\'b3\'d0\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid3758332 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c6\'df
\'a1\'a2\'b0\'e6\'c8\'a8\'b1\'a3\'bb\'a4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c6\'df\'a1\'a2\'b0\'e6\'c8\'a8\'b1\'a3\'bb\'a4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 7.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'c7\'eb\'d7\'f0\'d6\'d8\'b0\'e6\'c8\'a8
\'a3\'ac\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par \hich\af13\dbch\af13\loch\f13 7.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'c7\'eb\'d7\'f0\'d6\'d8\'b0\'e6\'c8\'a8
\'a3\'ac\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b0\'cb\'a1\'a2\'b7\'c7\'c9\'cc\'d2\'b5\'d0\'d4\'d6\'ca}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b0\'cb\'a1\'a2\'b7\'c7\'c9\'cc\'d2\'b5\'d0\'d4\'d6\'ca}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 8.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'bd\'f6\'d3\'c3\'d3\'da\'b6\'d4\'bc\'bc\'ca\'f5\'bf\'c9\'d0\'d0\'d0\'d4
\par \hich\af13\dbch\af13\loch\f13 8.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'bd\'f6\'d3\'c3\'d3\'da\'b6\'d4\'bc\'bc\'ca\'f5\'bf\'c9\'d0\'d0\'d0\'d4
\'b5\'c4\'cc\'bd\'cb\'f7\'bc\'b0\'d1\'d0\'be\'bf\'a3\'ac\'b2\'bb\'bd\'d3\'ca\'dc\'c8\'ce\'ba\'ce\'c9\'cc\'d2\'b5\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'b9\'e3\'b8\'e6\'b5\'c8\'a3\'a9\'ba\'cf\'d7\'f7\'bc\'b0\'be\'e8\'d4\'f9\'a1\'a3}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'be\'c5\'a1\'a2\'bd\'d3\'ca\'dc\'d0\'ad\'d2\'e9}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'be\'c5\'a1\'a2\'bd\'d3\'ca\'dc\'d0\'ad\'d2\'e9}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 9.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'c4\'e3\'ca\'b9\'d3\'c3\'c1\'cb\'b1\'be\'cf\'ee\'c4\'bf\'a3\'ac\'bd\'ab\'b4\'fa\'b1\'ed
\'c4\'e3\'bd\'d3\'ca\'dc\'b1\'be\'d0\'ad\'d2\'e9\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid14511311
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 * }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\loch\af13\hich\af13\dbch\f13 \'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa
\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid9533173
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid15226681
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \hich\af13\dbch\af13\loch\f13 By: }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
\par \hich\af13\dbch\af13\loch\f13 9.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'c4\'e3\'ca\'b9\'d3\'c3\'c1\'cb\'b1\'be\'cf\'ee\'c4\'bf\'a3\'ac\'bc\'b4\'b4\'fa\'b1\'ed
\'c4\'e3\'bd\'d3\'ca\'dc\'b1\'be\'d0\'ad\'d2\'e9\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa
\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548
\par
\par \hich\af13\dbch\af13\loch\f13 By: }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid2766548\charrsid2766548 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid12910709\charrsid2766548
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
@@ -311,8 +320,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000c03d
9b1957f7d901feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000d096
bf3efc84db01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000105000000000000}}

View File

@@ -1,48 +1,52 @@
This project is issued based on the Apache License 2.0 license. The following protocols are supplemented by Apache License 2.0.
This project is issued based on the Apache License 2.0 license. The following agreements are supplemental to the Apache License 2.0. In the event of a conflict, the following agreements shall prevail.
Words stipulate: "this project" in this agreement refers to the Luoxue Music Desktop Edition project; "user" refers to the user who signed this agreement; the "official music platform" refers to The official platform of the music source is collectively referred to; "copyright data" refers to the data that includes but not limited to images, audio, names, etc.
DEFINITIONS:
1. Data source
"This Project" refers to the LX Music Desktop Edition (aka lx-music-desktop) project. "User" refers to the user who signed this agreement. "Official Music Streaming Service" refers to collectively the official streaming service corresponding to the music source. "Copyrighted Data" refers to data including, but not limited to, pictures, audio, names, etc., for which others own the copyright.
1.1 The principle of online data sources of various official platforms of this project is to draw data from its open server (the same data obtained from the unsproting state in the official platform app). After simply screening and merging data, this project Responsible for the legitimacy and accuracy of the data.
1.2 The ability of this project itself does not obtain a certain audio data. The online audio data source used in this project comes from the online link of the "Source" selected in the software settings. For example, when playing a song, the project only transmits information such as the song name, singer name and other information to be played to "source". If the "source" returns a link, this project will think that this is the audio data of the song For use, as for whether this is the correct audio data, this project cannot verify its accuracy, so the audio that you want to play may occur during the process of using this project.
1.3 The unofficial platform data (such as my collection list) of this project comes from synchronous services connected by the user's local system or user connection. This project is not responsible for the legality and accuracy of these data.
1. Data Source
2. Copyright data
1.1 The principle of the online data sources of the various official music streaming services of this project is to draw data from its open servers (the same as the data obtained in the unlogged-in state in the official music streaming service app). After simply screening and merging the data, this project is responsible for the legitimacy and accuracy of the data.
2.1 During the process of using this project, copyright data may be generated. For these copyright data, this project does not have their ownership. In order to avoid infringement, users must remove copyright data generated during the process of using this project within ** 24 hours.
1.2 The ability of this project itself does not obtain certain audio data. The online audio data source used in this project comes from the online link provided by the "API" selected in the software settings. For example, when playing a song, this project only transmits information such as the song title, artist, and other information to the "API". If the "API" returns a link, this project will think that this is the audio data of the song for use, but as for whether this is the correct audio data, this project cannot verify its accuracy. So, in the process of using this project, there may be a problem that the audio you want to play does not correspond to the audio you actually play, or you cannot play it.
3. Alias of Music Platform
1.3 Data in this project other than official music streaming service (such as lists in "Your Library") comes from the user's local system or a synchronization service that the user connected to. This project is not responsible for the legality and accuracy of this data.
3.1 The official music platform in this project is named a name for the official music platform in this project, without maliciousness. If the official music platform feels inappropriate, you can contact this project to change or remove.
2. Copyrighted Data
Fourth, resource use
2.1 During the process of using this project, copyrighted data may be generated. For this copyrighted data, this project does not have their ownership. In order to avoid infringement, users must remove copyrighted data generated during the process of using this project ** within 24 hours **.
4.1 The parts used in this project include, but not limited to fonts, pictures and other resources from the Internet. If infringement occurs, you can contact this project.
3. Alias of Music Streaming Service
3.1 The official music streaming service alias within this project is a term used within this project to refer to the official music streaming service and does not contain malicious intent. If the operator of the official music streaming service feels it is inappropriate, you can contact this project to request changes or removals.
4. Use of Resources
4.1 The parts used in this project include, but are not limited to, fonts, pictures, and other resources from the Internet. If infringement occurs, you can contact this project.
5. Disclaimer
5.1 The use of this project includes any direct, indirect, special, accidental, or result damage due to any nature caused by this agreement or use or unable to use this item (including but not limited to the loss of goodwill, stop work, computer, computer Damage compensation caused by faults or faults, or any or all other commercial damage or losses) is responsible for users.
5.1 The use of this project includes any direct, indirect, special, accidental, or result damage due to any nature caused by this agreement or use or inability to use this item (including but not limited to the loss of goodwill, stop work, computer, computer Damage compensation caused by faults or any or all other commercial damage or losses) is the responsibility of users.
6. Use restrictions
6. Use Restrictions
6.1 This project is completely free, and open source is published in GitHub for the learning exchanges of technology for people all over the world. This project does not guarantee that the technology in the project may violate local laws and regulations.
6.1 This project is completely free and open source and is published on GitHub for the learning exchanges of technology for people all over the world. This project does not guarantee that the technology in this project may violate local laws and regulations.
6.2 ** This project is prohibited in violation of local laws and regulations. ** For the user's use of any illegal and illegal acts caused by the use of the project if the user is not allowed to be allowed to the user, it is undertaken by the user. Sexual responsibility.
6.2 ** This project is prohibited in violation of local laws and regulations. ** The user is solely responsible for any violation of law caused by the use of this project that the user knows or does not know is not permitted by local law and regulations.
7. Copyright protection
7. Copyright Protection
7.1 Music platform is not easy, please respect the copyright and support genuine.
7.1 The operation of a music streaming service is not an easy task. Please respect copyrights and support the genuine.
8. Non -commercial nature
8. Non-commercial Nature
8.1 This project is only used for exploring and research on technical feasibility, and does not accept any business (including but not limited to advertising, etc.) cooperation and donations.
8.1 This project is only used for exploring and research on technical feasibility and does not accept any business (including but not limited to advertising, etc.) cooperation and donations.
9. Accepting agreement
9. Accepting Agreement
9.1 If you use this project, it will represent you accept this agreement.
* If the agreement is updated, you will not be notified separately, you can check the open source address.
* The content of this agreement is translated from the Chinese version of the agreement
* If the agreement is updated, you will not be notified separately. You can check it out by visiting the project address.
* The content of this agreement is translated from the Chinese version of the agreement.
By: lyswhut

View File

@@ -1,16 +1,18 @@
本项目基于 Apache License 2.0 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
词语约定:本协议中的“本项目”指 LX Music 桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
一、数据来源
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据与未登录状态在官方平台APP获取的数据相同经过对数据简单地筛选与合并后进行展示因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据(与未登录状态在官方平台 APP 获取的数据相同),经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“自定义源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名、艺术家等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题
1.3 本项目的非官方平台数据(例如“我的列表”内列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在**24小时内**清除使用本项目的过程中所产生的版权数据。
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 **24 小时内** 清除使用本项目的过程中所产生的版权数据。
三、音乐平台别名
@@ -40,7 +42,7 @@
九、接受协议
9.1 若你使用了本项目,代表你接受本协议。
9.1 若你使用了本项目,代表你接受本协议。
* 若协议更新,恕不另行通知,可到开源地址查看。

14183
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,8 @@
{
"name": "lx-music-desktop",
"version": "2.8.0",
"version": "2.12.6",
"description": "一个免费的音乐查找助手",
"main": "./dist/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x64 && npm run pack:win:setup:x86 && npm run pack:win:setup:arm64 && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
@@ -64,7 +63,7 @@
"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: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",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm i"
},
@@ -72,7 +71,7 @@
"Electron 22.3.0"
],
"engines": {
"node": ">= 18",
"node": ">= 22",
"npm": ">=8.5.2"
},
"macLanguagesInfoPlistStrings": {
@@ -108,112 +107,117 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.24.6",
"@babel/eslint-parser": "^7.24.6",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.24.6",
"@babel/plugin-transform-runtime": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@babel/preset-typescript": "^7.24.6",
"@tsconfig/recommended": "^1.0.6",
"@types/better-sqlite3": "^7.6.10",
"@tsconfig/recommended": "^1.0.13",
"@types/better-sqlite3": "^7.6.13",
"@types/needle": "^3.3.0",
"@types/node": "^20.19.43",
"@types/tunnel": "^0.0.7",
"@types/ws": "8.5.4",
"@volar/vue-language-plugin-pug": "^1.6.5",
"@vue/language-plugin-pug": "^2.0.19",
"babel-loader": "^9.1.3",
"browserslist": "^4.23.0",
"@vue/language-plugin-pug": "^3.3.11",
"browserslist": "^4.28.9",
"chalk": "^4.1.2",
"changelog-parser": "^3.0.1",
"copy-webpack-plugin": "^12.0.2",
"core-js": "^3.37.1",
"cross-env": "^7.0.3",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"changelog-parser": "3.0.1",
"copy-webpack-plugin": "^14.0.0",
"core-js": "^3.50.0",
"cross-env": "^10.1.0",
"css-loader": "^7.1.5",
"css-minimizer-webpack-plugin": "^8.0.0",
"del": "^6.1.1",
"electron": "^28.3.3",
"electron-builder": "^24.13.3",
"electron": "42.11.6",
"electron-builder": "^26.15.7",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.787",
"electron-updater": "^6.1.8",
"eslint": "^8.57.0",
"electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03",
"electron-to-chromium": "^1.5.427",
"electron-updater": "6.8.9",
"eslint": "^8.57.1",
"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.1",
"eslint-plugin-vue": "^9.26.0",
"eslint-plugin-html": "^8.2.0",
"eslint-plugin-vue": "^9.33.0",
"eslint-plugin-vue-pug": "^0.6.2",
"eslint-webpack-plugin": "^4.2.0",
"html-webpack-plugin": "^5.6.0",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.9.0",
"node-loader": "^2.0.0",
"postcss": "^8.4.38",
"postcss-loader": "^8.1.1",
"html-webpack-plugin": "^5.6.8",
"less": "^4.9.1",
"less-loader": "^13.0.0",
"mini-css-extract-plugin": "^2.10.2",
"node-loader": "^2.1.0",
"postcss": "^8.5.28",
"postcss-loader": "^8.2.1",
"postcss-pxtorem": "^6.1.0",
"pug": "^3.0.3",
"pug": "^3.0.4",
"pug-plain-loader": "^1.1.0",
"rimraf": "^5.0.7",
"rimraf": "^6.1.3",
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
"svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13",
"svgo-loader": "^4.0.0",
"terser": "^5.31.0",
"terser-webpack-plugin": "^5.3.10",
"svgo-loader": "^5.0.0",
"terser": "^5.51.2",
"terser-webpack-plugin": "^5.6.1",
"tree-kill": "^1.2.2",
"ts-loader": "^9.5.1",
"typescript": "^5.4.5",
"ts-loader": "^9.6.2",
"typescript": "5.9.3",
"vue-eslint-parser": "^9.4.3",
"vue-loader": "^17.4.2",
"vue-template-compiler": "^2.7.16",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack": "5.106.2",
"webpack-cli": "^7.2.3",
"webpack-dev-server": "5.2.6",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^5.10.0"
"webpack-merge": "^6.0.1"
},
"dependencies": {
"@simonwep/pickr": "^1.9.1",
"better-sqlite3": "10.0.0",
"bufferutil": "^4.0.8",
"@simonwep/pickr": "1.9.1",
"better-sqlite3": "^13.0.3",
"bufferutil": "^4.1.0",
"comlink": "~4.3.1",
"crypto-js": "^4.2.0",
"electron-log": "^5.1.5",
"font-list": "^1.5.1",
"iconv-lite": "^0.6.3",
"electron-log": "^5.4.4",
"font-list": "^2.1.0",
"iconv-lite": "^0.7.3",
"image-size": "^1.1.0",
"jschardet": "^3.1.2",
"long": "^5.2.3",
"jschardet": "^3.1.4",
"long": "^5.3.2",
"message2call": "^0.1.3",
"music-metadata": "^8.3.0",
"music-metadata": "^11.15.0",
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
"node-id3": "^0.2.6",
"sortablejs": "^1.15.2",
"node-id3": "^0.2.9",
"sortablejs": "^1.15.7",
"tunnel": "^0.0.6",
"utf-8-validate": "^6.0.4",
"undici": "^7.29.1",
"utf-8-validate": "^6.0.6",
"vue": "~3.3.13",
"vue-router": "^4.3.2",
"ws": "^8.17.0"
"vue-router": "~4.5.1",
"ws": "^8.21.3"
},
"overrides": {
"got": "^11",
"json5": "latest",
"node-abi": "latest",
"minimatch": "latest",
"semver": "latest",
"posthtml": "latest",
"node-gyp": "latest",
"sockjs": {
"uuid": "^12"
},
"svg-transform-loader": {
"postcss": "latest"
"postcss": "^8"
},
"svg-sprite-loader": {
"postcss": "latest"
"postcss": "^8"
},
"svg-baker": {
"postcss": "latest"
"postcss": "^8"
},
"braces": "latest",
"node-gyp-build": "latest",
"micromatch": "latest",
"http-cache-semantics": "latest"
},
"allowScripts": {
"bufferutil": false,
"core-js": false,
"electron-winstaller": false,
"utf-8-validate": false
}
}

View File

@@ -1,42 +1,15 @@
我们发布了关于 LX Music 项目发展调整与新项目计划的说明,
详情看: https://github.com/lyswhut/lx-music-desktop/issues/1912
### 新增
- 新增 设置-播放设置-使用设备能处理的最大声道数输出音频 设置未启用时固定为2声道输出由于这用到高级音频API考虑到在某些设备上的兼容问题默认禁用#1873
- 允许添加 `m4a``oga` 格式的本地歌曲到列表中(#1864
- 开放API支持跨域请求#1872 @Ceale
- Scheme URL API新增 `music/searchPlay` 支持,用于搜索并播放指定的歌曲名字,详细入参请阅读 Scheme URL 支持文档(#1886
目前新项目 Any Listen 的桌面版、Web 版已实现 LX Music 的大部分功能,并额外支持 WebDAV 歌曲播放、WebDAV 数据同步、独立播放列表等功能。
以后的开发精力将主要集中在新项目上,之前大家在 LX Music 提的功能我们也会考虑在新项目中添加。
对于日常使用 LX Music 的人可以试试迁移到 Any Listen若遇到任何问题可以发 issue 反馈。
Any Listen 的项目地址为 https://github.com/any-listen/any-listen
### 优化
- 优化白色托盘图标显示修复windows下托盘图标不清晰的问题#1842
- 优化 tx 推荐歌单列表
- 优化禁用透明窗口的窗口边框显示效果
### 修复
- 修复存在多级弹窗时的背景显示问题
- 增大在线导入自定义源文件的大小限制问题(#1857
- 修复Mac下窗口出现残留阴影的问题这解决了Mac下桌面歌词出现残留阴影的远古bug感谢 @zclorne #1869, Thanks @zclorne
- 增大在线导入自定义源文件的大小限制,解决某些音源无法导入的问题(#1857
- 修复Mac下即使开启了托盘 `cmd+w` 仍会中断播放的问题(#1844
- 修复播放详情页的歌词无法使用触碰拖动的问题(#1865
- 修复与优化繁体中文、英语翻译显示(#1845
- 修复歌曲时文件名过长导致歌曲无法下载的问题(#1877
- 修复文本提示气泡在内容过长时,文本未被换行而被截断的问题
- 修复翻页按钮栏切页按钮只显示前几页的问题
### 变更
- 设置-播放设置-优先播放320k音质选项改为“优先播放的音质”允许选择更高优先播放的音质如果歌曲及音源支持的话#1839
### 开放API变更
- `/status` 的入参现在与 `/subscribe-player-status` 保持一致
- `/status` 新增 `filter` 入参用于过滤返回的字段,并内置了默认值,与之前相比默认不再返回 `picUrl`
- `/status``/subscribe-player-status` 的可用字段名添加了 `lyricLineAllText`,它对应的值是当前句歌词及扩展歌词文本(扩展歌词包含翻译、罗马音等,按换行符分割)
详情看开放API接入文档
### 其他
- 更新 electron 到 v28.3.3
- 修复打开某些 kg 歌单时歌曲丢失的问题
- 修复 Windows 7 无法启动的问题
- 修复禁用透明窗口时按 F11 无法全屏的问题

View File

@@ -34,7 +34,7 @@ function fm(value) {
exports.sizeFormate = size => {
// https://gist.github.com/thomseddon/3511330
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))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
}

View File

@@ -0,0 +1,40 @@
/**
*
* @param {string} text
* @returns
*/
export const parseChangelog = async(text) => {
const versions = []
const lines = text.split(/\r\n|\r|\n/)
let currentVersion = null
let currentDate = null
let currentDesc = ''
for (const line of lines) {
const versionMatch = line.match(/^\s*##\s+\[?(\d+\.\d+\.\d+)\]?.*?-\s+(\d{4}-\d{2}-\d{2})$/)
if (versionMatch) {
if (currentVersion) {
versions.push({
version: currentVersion,
date: currentDate,
desc: currentDesc.trim(),
})
}
currentVersion = versionMatch[1]
currentDate = versionMatch[3]
currentDesc = ''
} else {
currentDesc += `${line}\n`
}
}
if (currentVersion) {
versions.push({
version: currentVersion,
date: currentDate,
desc: currentDesc.trim(),
})
}
return versions
}

View File

@@ -6,8 +6,8 @@ const version = require('../version.json')
const chalk = require('chalk')
const pkg_bak = JSON.stringify(pkg, null, 2)
const version_bak = JSON.stringify(version, null, 2)
const parseChangelog = require('changelog-parser')
const changelogPath = jp('../../CHANGELOG.md')
const { parseChangelog } = require('./parseChangelog')
// const md_renderer = markdownStr => new (require('markdown-it'))({
// html: true,
@@ -16,27 +16,23 @@ const changelogPath = jp('../../CHANGELOG.md')
// breaks: true,
// }).render(markdownStr)
const getPrevVer = () => parseChangelog(changelogPath).then(res => {
if (!res.versions.length) throw new Error('CHANGELOG 无法解析到版本号')
return res.versions[0].version
const getPrevVer = () => parseChangelog(fs.readFileSync(changelogPath, 'utf-8').toString()).then(versions => {
if (!versions.length) throw new Error('CHANGELOG 无法解析到版本号')
return versions[0].version
})
const updateChangeLog = async(newVerNum, newChangeLog) => {
let changeLog = fs.readFileSync(changelogPath, 'utf-8')
const prevVer = await getPrevVer()
const log = `## [${newVerNum}](${pkg.repository.url.replace(/^git\+(http.+)\.git$/, '$1')}/compare/v${prevVer}...v${newVerNum}) - ${formatTime()}\n\n${newChangeLog}`
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp('(## [?0.1.1]?)'), log + '\n$1'), 'utf-8')
fs.writeFileSync(changelogPath, changeLog.replace(/(## \[(?:\d+\.))/, log + '\n$1'), 'utf-8')
}
// const renderChangeLog = md => md_renderer(md)
module.exports = async newVerNum => {
if (!newVerNum) {
let verArr = pkg.version.split('.')
verArr[verArr.length - 1] = parseInt(verArr[verArr.length - 1]) + 1
newVerNum = verArr.join('.')
}
if (!newVerNum) newVerNum = pkg.version
const newMDChangeLog = fs.readFileSync(jp('../changeLog.md'), 'utf-8')
// const newChangeLog = renderChangeLog(newMDChangeLog)
version.history.unshift({

File diff suppressed because one or more lines are too long

View File

@@ -80,3 +80,5 @@ export const DOWNLOAD_STATUS = {
} as const
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
export const TRAY_AUTO_ID = -1

View File

@@ -19,6 +19,7 @@ const defaultSetting: LX.AppSetting = {
'common.isAgreePact': false,
'common.controlBtnPosition': isMac ? 'left' : 'right',
'common.playBarProgressStyle': 'mini',
'common.transparentWindow': !isMac,
'common.tryAutoUpdate': true,
'common.showChangeLog': true,
@@ -26,6 +27,7 @@ const defaultSetting: LX.AppSetting = {
'player.togglePlayMethod': 'listLoop',
'player.playQuality': '128k',
'player.isShowTaskProgess': true,
'player.isShowStatusBarLyric': false,
'player.volume': 1,
'player.powerSaveBlocker': true,
'player.isMute': false,
@@ -36,6 +38,7 @@ const defaultSetting: LX.AppSetting = {
'player.isMediaDeviceRemovedStopPlay': false,
'player.isShowLyricTranslation': false,
'player.isShowLyricRoma': false,
'player.isSwapLyricTranslationAndRoma': false,
'player.isS2t': false,
'player.isPlayLxlrc': !isMac,
'player.isSavePlayTime': false,
@@ -66,6 +69,7 @@ const defaultSetting: LX.AppSetting = {
'playDetail.isShowLyricProgressSetting': false,
'playDetail.style.fontSize': 140,
'playDetail.style.align': 'center',
'playDetail.isDelayScroll': true,
'desktopLyric.enable': false,
'desktopLyric.isLock': false,
@@ -74,6 +78,7 @@ const defaultSetting: LX.AppSetting = {
'desktopLyric.isShowTaskbar': false,
'desktopLyric.audioVisualization': false,
'desktopLyric.fullscreenHide': true,
'desktopLyric.pauseHide': true,
'desktopLyric.width': 450,
'desktopLyric.height': 300,
'desktopLyric.x': null,
@@ -105,16 +110,19 @@ const defaultSetting: LX.AppSetting = {
'list.actionButtonsVisible': false,
'download.enable': false,
'download.isSavePathGroupByListName': false,
'download.savePath': path.join(os.homedir(), 'Desktop'),
'download.fileName': '歌名 - 歌手',
'download.maxDownloadNum': 3,
'download.skipExistFile': true,
'download.isDownloadLrc': false,
'download.isDownloadLxLrc': true,
'download.isDownloadTLrc': false,
'download.isDownloadRLrc': false,
'download.lrcFormat': 'utf8',
'download.isEmbedPic': true,
'download.isEmbedLyric': false,
'download.isEmbedLyricLx': true,
'download.isEmbedLyricT': false,
'download.isEmbedLyricR': false,
'download.isUseOtherSource': false,

View File

@@ -51,6 +51,16 @@ const hotKey = {
action: 'prev',
type: '',
},
seekbackward: {
name: 'seekbackward',
action: 'seekbackward',
type: '',
},
seekforward: {
name: 'seekforward',
action: 'seekforward',
type: '',
},
volume_up: {
name: 'volume_up',
action: 'volume_up',

View File

@@ -78,10 +78,6 @@ const modules = {
restart_window: 'restart_window',
// lang_s2t: 'lang_s2t',
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
handle_tx_decode_lyric: 'handle_tx_decode_lyric',
get_lyric_info: 'get_lyric_info',
set_lyric_info: 'set_lyric_info',
set_config: 'set_config',
@@ -162,9 +158,11 @@ const modules = {
on_config_change: 'on_config_change',
main_window_inited: 'main_window_inited',
set_win_bounds: 'set_win_bounds',
set_win_resizeable: 'set_win_resizeable',
key_down: 'key_down',
request_main_window_channel: 'request_main_window_channel',
provide_main_window_channel: 'provide_main_window_channel',
mouse_enter_leave: 'mouse_enter_leave',
},
hotKey: {
enable: 'enable',

View File

@@ -5,7 +5,7 @@
"./types"
],
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@common/*": ["common/*"],
"@common/*": ["./*"],
},
},
}

View File

@@ -68,6 +68,11 @@ declare global {
*/
'common.playBarProgressStyle': 'mini' | 'full' | 'middle'
/**
* 启用透明窗口
*/
'common.transparentWindow': boolean
/**
* 尝试自动更新
*/
@@ -98,6 +103,12 @@ declare global {
*/
'player.isShowTaskProgess': boolean
/**
* 是否将歌词显示在状态栏
*/
'player.isShowStatusBarLyric': boolean
/**
* 音量大小
*/
@@ -148,6 +159,11 @@ declare global {
*/
'player.isShowLyricRoma': boolean
/**
* 是否调换翻译歌词与罗马音歌词位置
*/
'player.isSwapLyricTranslationAndRoma': boolean
/**
* 是否将歌词从简体转换为繁体
*/
@@ -293,6 +309,11 @@ declare global {
*/
'playDetail.style.align': 'center' | 'left' | 'right'
/**
* 播放详情页-是否延迟桌面歌词滚动
*/
'playDetail.isDelayScroll': boolean
/**
* 是否启用桌面歌词
@@ -329,6 +350,11 @@ declare global {
*/
'desktopLyric.fullscreenHide': boolean
/**
* 是否在暂停时隐藏歌词
*/
'desktopLyric.pauseHide': boolean
/**
* 桌面歌词窗口宽度
*/
@@ -474,6 +500,11 @@ declare global {
*/
'download.enable': boolean
/**
* 按列表名分组保存
*/
'download.isSavePathGroupByListName': boolean
/**
* 下载路径
*/
@@ -499,6 +530,11 @@ declare global {
*/
'download.isDownloadLrc': boolean
/**
* 是否在下载 lx 歌词
*/
'download.isDownloadLxLrc': boolean
/**
* 是否下载翻译歌词文件
*/
@@ -519,6 +555,11 @@ declare global {
*/
'download.isEmbedPic': boolean
/**
* 是否在音频文件中嵌入 lx 歌词
*/
'download.isEmbedLyricLx': boolean
/**
* 是否在音频文件中嵌入歌词
*/

View File

@@ -37,6 +37,11 @@ declare namespace LX {
*/
play?: string
/**
* 启动后最小化到系统托盘
*/
hidden?: boolean
[key: string]: boolean | number | string
}

View File

@@ -6,6 +6,7 @@ declare namespace LX {
'desktopLyric.isAlwaysOnTop': LX.AppSetting['desktopLyric.isAlwaysOnTop']
'desktopLyric.isAlwaysOnTopLoop': LX.AppSetting['desktopLyric.isAlwaysOnTopLoop']
'desktopLyric.isShowTaskbar': LX.AppSetting['desktopLyric.isShowTaskbar']
'desktopLyric.pauseHide': LX.AppSetting['desktopLyric.pauseHide']
'desktopLyric.audioVisualization': LX.AppSetting['desktopLyric.audioVisualization']
'desktopLyric.width': LX.AppSetting['desktopLyric.width']
'desktopLyric.height': LX.AppSetting['desktopLyric.height']
@@ -33,6 +34,7 @@ declare namespace LX {
'common.langId': LX.AppSetting['common.langId']
'player.isShowLyricTranslation': LX.AppSetting['player.isShowLyricTranslation']
'player.isShowLyricRoma': LX.AppSetting['player.isShowLyricRoma']
'player.isSwapLyricTranslationAndRoma': LX.AppSetting['player.isSwapLyricTranslationAndRoma']
'player.isPlayLxlrc': LX.AppSetting['player.isPlayLxlrc']
'player.playbackRate': LX.AppSetting['player.playbackRate']
}

View File

@@ -59,6 +59,7 @@ declare global {
ext: FileExt
fileName: string
filePath: string
listId?: string
}
}

View File

@@ -22,6 +22,7 @@ declare namespace LX {
songId: string | number // 歌曲IDmg源为copyrightIdlocal为文件路径
albumName: string // 歌曲专辑名称
picUrl?: string | null // 歌曲图片链接
toggleMusicInfo?: MusicInfoOnline | null
}
interface MusicInfoMeta_online extends MusicInfoMetaBase {
@@ -57,6 +58,7 @@ declare namespace LX {
qualitys: MusicQualityTypeKg[]
_qualitys: _MusicQualityTypeKg
hash: string // 歌曲hash
albumAudioId?: string // 专辑内歌曲ID(MixSongID),用于歌词精确搜索
}
interface MusicInfo_kg extends MusicInfoBase<'kg'> {
meta: MusicInfoMeta_kg

View File

@@ -11,6 +11,9 @@ declare namespace LX {
| 'pause'
| 'play'
| 'next'
| 'seek'
| 'volume'
| 'mute'
interface LyricInfo extends LX.Music.LyricInfo {
rawlrcInfo: LX.Music.LyricInfo
@@ -28,7 +31,12 @@ declare namespace LX {
lyricLineText: string
lyricLineAllText: string
lyric: string
tlyric: string
rlyric: string
lxlyric: string
collect: boolean
volume: number
mute: boolean
}
}
}

View File

@@ -1,5 +1,6 @@
// 非业务工具方法
import { pathToFileURL } from 'url'
/**
* 获取两个数之间的随机整数大于等于min小于max
* @param {*} min
@@ -11,7 +12,7 @@ export const getRandom = (min: number, max: number): number => Math.floor(Math.r
export const sizeFormate = (size: number): string => {
// https://gist.github.com/thomseddon/3511330
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))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
}
@@ -71,19 +72,6 @@ export const formatPlayTime2 = (time: number) => {
}
const encodeNames = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': "'",
'&#039;': "'",
} as const
export const decodeName = (str: string | null = '') => {
return str?.replace(/(?:&amp;|&lt;|&gt;|&quot;|&apos;|&#039;|&nbsp;)/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
}
export const isUrl = (path: string) => /https?:\/\//.test(path)
// 解析URL参数为对象
@@ -201,7 +189,7 @@ export const sortInsert = <T>(arr: Array<{ num: number, data: T }>, data: { num:
}
export const encodePath = (path: string) => {
return encodeURI(path.replaceAll('\\', '/'))
return pathToFileURL(path).href
}

View File

@@ -230,6 +230,11 @@ class Task extends EventEmitter {
__handleComplete() {
if (this.status == STATUS.error) return
this.__clearTimeout()
if (this.progress.progress <= 0) {
this.status = STATUS.error
this.emit('error', new Error('Progress is 0, download failed.'))
return
}
void this.__closeWriteStream().then(() => {
if (this.progress.downloaded == this.progress.total) {
this.status = STATUS.completed

View File

@@ -1,4 +1,5 @@
import log from 'electron-log/node'
import os from 'node:os'
export const isLinux = process.platform == 'linux'
@@ -6,6 +7,19 @@ export const isWin = process.platform == 'win32'
export const isMac = process.platform == 'darwin'
export const isProd = process.env.NODE_ENV == 'production'
export const getPlatform = (platform: NodeJS.Platform = process.platform) => {
switch (platform) {
case 'win32': return 'windows'
case 'darwin': return 'mac'
default: return 'linux'
}
}
export const getOSVersion = (): string => {
const release = os.release()
const versionMatch = /^(\d+)/.exec(release)
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return versionMatch?.[1] || release
}
// https://stackoverflow.com/a/53387532
export function compareVer(currentVer: string, targetVer: string): -1 | 0 | 1 {

View File

@@ -228,7 +228,7 @@ export default class Lyric {
} else this.playingLineNum = 0
}
setAutoPause(autoPause) {
this.linePlayer.setAutoPause(autoPause)
setDisabledAutoPause(autoPause) {
this.linePlayer.setDisabledAutoPause(autoPause)
}
}

View File

@@ -2,6 +2,7 @@ import { getNow, TimeoutTools } from './utils'
const timeFieldExp = /^(?:\[[\d:.]+\])+/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 = {
title: 'ti',
artist: 'ar',
@@ -82,6 +83,7 @@ export default class LinePlayer {
_initLines() {
this.lines = []
const isMsTime = msTimeRxp.test(this.lyric)
const lines = this.lyric.split(/\r\n|\r|\n/)
const linesMap = {}
for (let i = 0; i < lines.length; i++) {
@@ -103,9 +105,10 @@ export default class LinePlayer {
if (timeArr.length > 3) continue
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('.'))
const msTime = timeArr[3] || '0'
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,
extendedLyrics: [],
}
@@ -225,15 +228,15 @@ export default class LinePlayer {
this._init()
}
setAutoPause(autoPause) {
if (autoPause) {
timeoutTools.nextTick = window.requestAnimationFrame.bind(window)
timeoutTools.cancelNextTick = window.cancelAnimationFrame.bind(window)
} else {
setDisabledAutoPause(disabledAutoPause) {
if (disabledAutoPause) {
timeoutTools.nextTick = (handler) => {
return setTimeout(handler, 20)
}
timeoutTools.cancelNextTick = clearTimeout.bind(global)
} else {
timeoutTools.nextTick = window.requestAnimationFrame.bind(window)
timeoutTools.cancelNextTick = window.cancelAnimationFrame.bind(window)
}
}
}

View File

@@ -0,0 +1,74 @@
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).toString().padStart(3, '0')
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)
}

View File

@@ -0,0 +1,13 @@
const encodeNames = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': "'",
'&#039;': "'",
} as const
export const decodeName = (str: string | null = '') => {
return str?.replace(/(?:&amp;|&lt;|&gt;|&quot;|&apos;|&#039;|&nbsp;)/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
}

View File

@@ -1,14 +1,21 @@
const http = require('http')
const https = require('https')
const fs = require('fs')
const { httpOverHttp, httpsOverHttp } = require('tunnel')
const sendRequest = (url) => {
const httpsRxp = /^https:/
const getRequestAgent = (url, proxy) => {
return proxy ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)({ proxy }) : undefined
}
const sendRequest = (url, proxy) => {
const urlParse = new URL(url)
const httpOptions = {
method: 'get',
host: urlParse.hostname,
port: urlParse.port,
path: urlParse.pathname + urlParse.search,
agent: getRequestAgent(url, proxy),
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
},
@@ -20,9 +27,9 @@ const sendRequest = (url) => {
: http.request(httpOptions)
}
module.exports = (url, filePath) => {
module.exports = (url, filePath, proxy) => {
return new Promise((resolve) => {
sendRequest(url)
sendRequest(url, proxy)
.on('response', response => {
// console.log(response.statusCode)
if (response.statusCode !== 200 && response.statusCode != 206) {

View File

@@ -57,7 +57,7 @@ const writeMeta = async(filePath, meta, picPath) => {
})
}
module.exports = (filePath, meta) => {
module.exports = (filePath, meta, proxy) => {
if (!meta.APIC) return writeMeta(filePath, meta)
let picUrl = meta.APIC
delete meta.APIC
@@ -68,7 +68,7 @@ module.exports = (filePath, meta) => {
let picPath = filePath.replace(/\.flac$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
download(picUrl, picPath, proxy).then(success => {
if (success) {
writeMeta(filePath, meta, picPath).finally(() => {
fs.unlink(picPath, err => {

View File

@@ -5,4 +5,4 @@ export interface MusicMeta {
APIC: string | null
lyrics: string | null
}
export function setMeta(filePath: string, meta: MusicMeta): void
export function setMeta(filePath: string, meta: MusicMeta, proxy?: { host: string, port: number }): void

View File

@@ -2,13 +2,13 @@ const path = require('path')
const mp3Meta = require('./mp3Meta')
const flacMeta = require('./flacMeta')
exports.setMeta = (filePath, meta) => {
exports.setMeta = (filePath, meta, proxy) => {
switch (path.extname(filePath)) {
case '.mp3':
mp3Meta(filePath, meta)
mp3Meta(filePath, meta, proxy)
break
case '.flac':
flacMeta(filePath, meta)
flacMeta(filePath, meta, proxy)
break
}
}

View File

@@ -15,7 +15,7 @@ const handleWriteMeta = (meta, filePath) => {
NodeID3.write(meta, filePath)
}
module.exports = (filePath, meta) => {
module.exports = (filePath, meta, proxy) => {
if (!meta.APIC) return handleWriteMeta(meta, filePath)
if (!/^http/.test(meta.APIC)) {
delete meta.APIC
@@ -26,7 +26,7 @@ module.exports = (filePath, meta) => {
let picUrl = meta.APIC
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
download(picUrl, picPath, proxy).then(success => {
if (success) {
meta.APIC = picPath
handleWriteMeta(meta, filePath)

View File

@@ -31,6 +31,25 @@ export const checkPath = async(path: string): Promise<boolean> => {
})
}
/**
* 检查路径并创建目录
* @param path
* @returns
*/
export const checkAndCreateDir = async(path: string) => {
return fs.promises.access(path, fs.constants.F_OK | fs.constants.W_OK)
.catch(async(err: NodeJS.ErrnoException) => {
if (err.code != 'ENOENT') throw err as Error
return fs.promises.mkdir(path, { recursive: true })
})
.then(() => true)
.catch((err) => {
console.error(err)
return false
})
}
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
return new Promise(resolve => {
if (!path) {

View File

@@ -342,7 +342,7 @@ export const scrollXRTo = (element: ScrollElement<HTMLElement>, to: number, dura
*/
let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = (title: string | null) => {
title ||= '洛雪音乐助手'
title ||= 'LX Music'
dom_title.innerText = title
}

274
src/common/utils/request.ts Normal file
View File

@@ -0,0 +1,274 @@
import qs from 'node:querystring'
import {
FormData,
getGlobalDispatcher,
interceptors,
request as nodeRrequest,
ProxyAgent,
setGlobalDispatcher,
type Dispatcher,
} from 'undici'
const defaultOptions: Options = {
timeout: 15000,
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
},
maxRedirect: 5,
} as const
const redirectDispatcher = interceptors.redirect({ maxRedirections: defaultOptions.maxRedirect })
const dispatchers = [
interceptors.retry({
maxRetries: 3,
minTimeout: 1000,
maxTimeout: 10000,
timeoutFactor: 2,
retryAfter: true,
}),
// interceptors.responseError(),
] as const
let proxyAgent: ProxyAgent | null = null
let globalDispatcher = getGlobalDispatcher()
const buildDispatcher = (redirectDispatcher: Dispatcher.DispatcherComposeInterceptor | null, retryNum = 3) => {
const otherInterceptors =
retryNum == 3
? dispatchers
: [
interceptors.retry({
maxRetries: retryNum,
minTimeout: 1000,
maxTimeout: 6000,
timeoutFactor: 2,
retryAfter: true,
}),
]
if (redirectDispatcher) {
return (proxyAgent ?? globalDispatcher).compose(redirectDispatcher, ...otherInterceptors)
}
return (proxyAgent ?? globalDispatcher).compose(...otherInterceptors)
}
setGlobalDispatcher(buildDispatcher(redirectDispatcher))
export const setProxy = (url?: string) => {
proxyAgent = url ? new ProxyAgent(url) : null
setGlobalDispatcher(buildDispatcher(redirectDispatcher))
}
export const setProxyByHost = (host?: string, port?: string) => {
setProxy(host ? `http://${host}:${port}` : undefined)
}
const CONTENT_TYPE = {
json: 'application/json',
form: 'application/x-www-form-urlencoded',
text: 'text/plain',
formdata: 'multipart/form-data',
xml: 'application/xml',
binary: 'application/octet-stream',
}
type ParamsData = Record<string, string | number | null | undefined | boolean>
export interface Options {
method?:
| 'GET'
| 'HEAD'
| 'POST'
| 'PUT'
| 'DELETE'
| 'OPTIONS'
| 'PATCH'
| 'PROPFIND'
| 'COPY'
| 'MOVE'
| 'MKCOL'
| 'PROPPATCH'
| 'QUOTA'
query?: ParamsData
headers?: Record<string, string | string[]>
timeout?: number
maxRedirect?: number
signal?: AbortController['signal']
json?: Record<string, unknown>
form?: ParamsData
binary?: Buffer | Uint8Array
text?: string
formdata?: Record<string, string | Buffer | Uint8Array>
xml?: string
needBody?: boolean
needRaw?: boolean
retryNum?: number
}
export interface Response<Res> {
headers: {
accept?: string | undefined
'accept-language'?: string | undefined
'accept-patch'?: string | undefined
'accept-ranges'?: string | undefined
'access-control-allow-credentials'?: string | undefined
'access-control-allow-headers'?: string | undefined
'access-control-allow-methods'?: string | undefined
'access-control-allow-origin'?: string | undefined
'access-control-expose-headers'?: string | undefined
'access-control-max-age'?: string | undefined
'access-control-request-headers'?: string | undefined
'access-control-request-method'?: string | undefined
age?: string | undefined
allow?: string | undefined
'alt-svc'?: string | undefined
authorization?: string | undefined
'cache-control'?: string | undefined
connection?: string | undefined
'content-disposition'?: string | undefined
'content-encoding'?: string | undefined
'content-language'?: string | undefined
'content-length'?: string | undefined
'content-location'?: string | undefined
'content-range'?: string | undefined
'content-type'?: string | undefined
cookie?: string | undefined
date?: string | undefined
etag?: string | undefined
expect?: string | undefined
expires?: string | undefined
forwarded?: string | undefined
from?: string | undefined
host?: string | undefined
'if-match'?: string | undefined
'if-modified-since'?: string | undefined
'if-none-match'?: string | undefined
'if-unmodified-since'?: string | undefined
'last-modified'?: string | undefined
location?: string | undefined
origin?: string | undefined
pragma?: string | undefined
'proxy-authenticate'?: string | undefined
'proxy-authorization'?: string | undefined
'public-key-pins'?: string | undefined
range?: string | undefined
referer?: string | undefined
'retry-after'?: string | undefined
'sec-websocket-accept'?: string | undefined
'sec-websocket-extensions'?: string | undefined
'sec-websocket-key'?: string | undefined
'sec-websocket-protocol'?: string | undefined
'sec-websocket-version'?: string | undefined
'set-cookie'?: string[] | undefined
'strict-transport-security'?: string | undefined
tk?: string | undefined
trailer?: string | undefined
'transfer-encoding'?: string | undefined
upgrade?: string | undefined
'user-agent'?: string | undefined
vary?: string | undefined
via?: string | undefined
warning?: string | undefined
'www-authenticate'?: string | undefined
}
body: Res
raw: Uint8Array
statusCode?: number
statusMessage?: string
}
const buildRequestBody = (options: Options) => {
let contentType: string | undefined
let body: string | Buffer | Uint8Array | FormData | undefined
if (options.json != null) {
contentType = CONTENT_TYPE.json
body = JSON.stringify(options.json)
} else if (options.form != null) {
contentType = CONTENT_TYPE.form
body = qs.stringify(options.form)
} else if (options.binary != null) {
contentType = CONTENT_TYPE.binary
body = options.binary
} else if (options.formdata != null) {
const formdata = new FormData()
// eslint-disable-next-line guard-for-in
for (const key in options.formdata) {
formdata.append(key, options.formdata[key])
}
contentType = CONTENT_TYPE.formdata
body = formdata
} else if (options.xml != null) {
contentType = CONTENT_TYPE.xml
body = options.xml
} else if (options.text != null) {
contentType = CONTENT_TYPE.text
body = options.text
}
const headers = options.headers ?? {}
if (headers['Content-Type']) {
contentType = headers['Content-Type'] as string
}
const finalHeaders: NonNullable<Options['headers']> = { ...defaultOptions.headers, ...headers }
if (contentType) finalHeaders['Content-Type'] = contentType
return [finalHeaders, body] as const
}
const buildRequestDispatcher = (options: Options) => {
let dispatcher: Dispatcher.ComposedDispatcher | undefined
if (options.maxRedirect != null) {
if (options.maxRedirect != defaultOptions.maxRedirect) {
if (options.maxRedirect) {
dispatcher = buildDispatcher(interceptors.redirect({ maxRedirections: options.maxRedirect }), options.retryNum)
} else {
dispatcher = buildDispatcher(null, options.retryNum)
}
}
}
return dispatcher
}
export const request = async <T = unknown>(url: string, options: Options = {}): Promise<Response<T>> => {
const method = options.method?.toUpperCase() ?? 'GET'
const timeout = options.timeout ?? defaultOptions.timeout
const [headers, body] = buildRequestBody(options)
// console.log(url, {
// method,
// bodyTimeout: timeout,
// headersTimeout: timeout,
// headers,
// query: options.query,
// body,
// signal: options.signal,
// dispatcher: buildRequestDispatcher(options),
// })
return nodeRrequest(url, {
method,
bodyTimeout: timeout,
headersTimeout: timeout,
headers,
query: options.query,
body,
signal: options.signal,
dispatcher: buildRequestDispatcher(options),
}).then(async(response) => {
if (options.needBody) {
return {
headers: response.headers,
statusCode: response.statusCode,
body: response.body as unknown as T,
} satisfies Omit<Response<T>, 'raw'> as Response<T>
}
if (options.needRaw) {
return {
headers: response.headers,
statusCode: response.statusCode,
raw: await response.body.bytes(),
} satisfies Omit<Response<T>, 'body'> as Response<T>
}
// console.log(response)
let body = (await response.body.text()) as T
if (!headers['Content-Type'] || headers['Content-Type'].includes(CONTENT_TYPE.json)) {
try {
body = JSON.parse(body as string) as T
} catch {}
}
return {
body,
headers: response.headers,
statusCode: response.statusCode,
} satisfies Omit<Response<T>, 'raw'> as Response<T>
})
}

View File

@@ -0,0 +1,238 @@
import qs from 'node:querystring'
import {
FormData,
getGlobalDispatcher,
request as nodeRrequest,
ProxyAgent,
setGlobalDispatcher,
type Dispatcher,
} from 'undici'
const defaultOptions: Options = {
timeout: 15000,
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
},
maxRedirect: 5,
} as const
let proxyAgent: ProxyAgent | null = null
let globalDispatcher = getGlobalDispatcher()
const buildDispatcher = () => {
return proxyAgent ?? globalDispatcher
}
setGlobalDispatcher(buildDispatcher())
export const setProxy = (url?: string) => {
proxyAgent = url ? new ProxyAgent(url) : null
setGlobalDispatcher(buildDispatcher())
}
export const setProxyByHost = (host?: string, port?: string) => {
console.log(host)
setProxy(host ? `http://${host}:${port}` : undefined)
}
const CONTENT_TYPE = {
json: 'application/json',
form: 'application/x-www-form-urlencoded',
text: 'text/plain',
formdata: 'multipart/form-data',
xml: 'application/xml',
binary: 'application/octet-stream',
}
type ParamsData = Record<string, string | number | null | undefined | boolean>
export interface Options {
method?:
| 'GET'
| 'HEAD'
| 'POST'
| 'PUT'
| 'DELETE'
| 'OPTIONS'
| 'PATCH'
| 'PROPFIND'
| 'COPY'
| 'MOVE'
| 'MKCOL'
| 'PROPPATCH'
| 'QUOTA'
query?: ParamsData
headers?: Record<string, string | string[]>
timeout?: number
maxRedirect?: number
signal?: AbortController['signal']
json?: Record<string, unknown>
form?: ParamsData
binary?: Buffer | Uint8Array
text?: string
formdata?: Record<string, string | Buffer | Uint8Array>
xml?: string
needBody?: boolean
needRaw?: boolean
retryNum?: number
}
export interface Response<Res> {
headers: {
accept?: string | undefined
'accept-language'?: string | undefined
'accept-patch'?: string | undefined
'accept-ranges'?: string | undefined
'access-control-allow-credentials'?: string | undefined
'access-control-allow-headers'?: string | undefined
'access-control-allow-methods'?: string | undefined
'access-control-allow-origin'?: string | undefined
'access-control-expose-headers'?: string | undefined
'access-control-max-age'?: string | undefined
'access-control-request-headers'?: string | undefined
'access-control-request-method'?: string | undefined
age?: string | undefined
allow?: string | undefined
'alt-svc'?: string | undefined
authorization?: string | undefined
'cache-control'?: string | undefined
connection?: string | undefined
'content-disposition'?: string | undefined
'content-encoding'?: string | undefined
'content-language'?: string | undefined
'content-length'?: string | undefined
'content-location'?: string | undefined
'content-range'?: string | undefined
'content-type'?: string | undefined
cookie?: string | undefined
date?: string | undefined
etag?: string | undefined
expect?: string | undefined
expires?: string | undefined
forwarded?: string | undefined
from?: string | undefined
host?: string | undefined
'if-match'?: string | undefined
'if-modified-since'?: string | undefined
'if-none-match'?: string | undefined
'if-unmodified-since'?: string | undefined
'last-modified'?: string | undefined
location?: string | undefined
origin?: string | undefined
pragma?: string | undefined
'proxy-authenticate'?: string | undefined
'proxy-authorization'?: string | undefined
'public-key-pins'?: string | undefined
range?: string | undefined
referer?: string | undefined
'retry-after'?: string | undefined
'sec-websocket-accept'?: string | undefined
'sec-websocket-extensions'?: string | undefined
'sec-websocket-key'?: string | undefined
'sec-websocket-protocol'?: string | undefined
'sec-websocket-version'?: string | undefined
'set-cookie'?: string[] | undefined
'strict-transport-security'?: string | undefined
tk?: string | undefined
trailer?: string | undefined
'transfer-encoding'?: string | undefined
upgrade?: string | undefined
'user-agent'?: string | undefined
vary?: string | undefined
via?: string | undefined
warning?: string | undefined
'www-authenticate'?: string | undefined
}
body: Res
raw: Uint8Array
statusCode?: number
statusMessage?: string
}
const buildRequestBody = (options: Options) => {
let contentType: string | undefined
let body: string | Buffer | Uint8Array | FormData | undefined
if (options.json != null) {
contentType = CONTENT_TYPE.json
body = JSON.stringify(options.json)
} else if (options.form != null) {
contentType = CONTENT_TYPE.form
body = qs.stringify(options.form)
} else if (options.binary != null) {
contentType = CONTENT_TYPE.binary
body = options.binary
} else if (options.formdata != null) {
const formdata = new FormData()
// eslint-disable-next-line guard-for-in
for (const key in options.formdata) {
formdata.append(key, options.formdata[key])
}
contentType = CONTENT_TYPE.formdata
body = formdata
} else if (options.xml != null) {
contentType = CONTENT_TYPE.xml
body = options.xml
} else if (options.text != null) {
contentType = CONTENT_TYPE.text
body = options.text
}
const headers = options.headers ?? {}
if (headers['Content-Type']) {
contentType = headers['Content-Type'] as string
}
const finalHeaders: NonNullable<Options['headers']> = { ...defaultOptions.headers, ...headers }
if (contentType) finalHeaders['Content-Type'] = contentType
return [finalHeaders, body] as const
}
const buildRequestDispatcher = (options: Options) => {
return buildDispatcher()
}
export const request = async <T = unknown>(url: string, options: Options = {}): Promise<Response<T>> => {
const method = (options.method?.toUpperCase() ?? 'GET') as Dispatcher.RequestOptions['method']
const timeout = options.timeout ?? defaultOptions.timeout
const [headers, body] = buildRequestBody(options)
// console.log(url, {
// method,
// bodyTimeout: timeout,
// headersTimeout: timeout,
// headers,
// query: options.query,
// body,
// signal: options.signal,
// dispatcher: buildRequestDispatcher(options),
// })
return nodeRrequest(url, {
method,
bodyTimeout: timeout,
headersTimeout: timeout,
headers,
query: options.query,
body,
signal: options.signal,
dispatcher: buildRequestDispatcher(options),
maxRedirections: options.maxRedirect ?? defaultOptions.maxRedirect,
}).then(async(response) => {
if (options.needBody) {
return {
headers: response.headers,
statusCode: response.statusCode,
body: response.body as unknown as T,
} satisfies Omit<Response<T>, 'raw'> as Response<T>
}
if (options.needRaw) {
return {
headers: response.headers,
statusCode: response.statusCode,
raw: new Uint8Array(await response.body.arrayBuffer()),
} satisfies Omit<Response<T>, 'body'> as unknown as Response<T>
}
// console.log(response)
let body = (await response.body.text()) as T
if (!headers['Content-Type'] || headers['Content-Type'].includes(CONTENT_TYPE.json)) {
try {
body = JSON.parse(body as string) as T
} catch {}
}
return {
body,
headers: response.headers,
statusCode: response.statusCode,
} satisfies Omit<Response<T>, 'raw'> as Response<T>
})
}

View File

@@ -6,6 +6,14 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
albumName: oldMusicInfo.albumName, // 歌曲专辑名称
picUrl: oldMusicInfo.img, // 歌曲图片链接
}
const newInfo = {
id: `${oldMusicInfo.source}_${oldMusicInfo.songmid}`,
name: oldMusicInfo.name,
singer: oldMusicInfo.singer,
source: oldMusicInfo.source,
interval: oldMusicInfo.interval,
meta: meta as LX.Music.MusicInfoOnline['meta'],
}
if (oldMusicInfo.source == 'local') {
meta.filePath = oldMusicInfo.filePath ?? oldMusicInfo.songmid ?? ''
@@ -27,6 +35,8 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
switch (oldMusicInfo.source) {
case 'kg':
meta.hash = oldMusicInfo.hash
meta.albumAudioId = oldMusicInfo.albumAudioId
newInfo.id = oldMusicInfo.songmid + '_' + oldMusicInfo.hash
break
case 'tx':
meta.strMediaMid = oldMusicInfo.strMediaMid
@@ -42,14 +52,7 @@ export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfo => {
}
}
return {
id: `${oldMusicInfo.source as string}_${oldMusicInfo.songmid as string}`,
name: oldMusicInfo.name,
singer: oldMusicInfo.singer,
source: oldMusicInfo.source,
interval: oldMusicInfo.interval,
meta: meta as LX.Music.MusicInfoOnline['meta'],
}
return newInfo
}
export const toOldMusicInfo = (minfo: LX.Music.MusicInfo) => {
@@ -77,6 +80,7 @@ export const toOldMusicInfo = (minfo: LX.Music.MusicInfo) => {
switch (minfo.source) {
case 'kg':
oInfo.hash = minfo.meta.hash
oInfo.albumAudioId = minfo.meta.albumAudioId
break
case 'tx':
oInfo.strMediaMid = minfo.meta.strMediaMid
@@ -128,3 +132,25 @@ export const filterMusicList = <T extends LX.Music.MusicInfo>(list: T[]): T[] =>
return true
})
}
const MAX_NAME_LENGTH = 80
const MAX_FILE_NAME_LENGTH = 150
export const clipNameLength = (name: string) => {
if (name.length <= MAX_NAME_LENGTH || !name.includes('、')) return name
const names = name.split('、')
let newName = names.shift()!
for (const name of names) {
if (newName.length + name.length > MAX_NAME_LENGTH) break
newName = newName + '、' + name
}
return newName
}
export const clipFileNameLength = (name: string) => {
return name.length > MAX_FILE_NAME_LENGTH ? name.substring(0, MAX_FILE_NAME_LENGTH) : name
}
export const formatMusicName = (format: string, name: string, singer: string) => {
// return format.replace(/歌名|歌手/g, match => match === '歌名' ? name : singer)
return format.replace('歌手', singer).replace('歌名', name)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,28 @@
import zh_cn from './zh-cn.json'
import zh_tw from './zh-tw.json'
import en_us from './en-us.json'
import ko_kr from './ko-kr.json'
type Message = Record<keyof typeof zh_cn, string>
| Record<keyof typeof zh_tw, string>
| Record<keyof typeof en_us, string>
| Record<keyof typeof ko_kr, string>
type Messages = Record<(typeof langs)[number]['locale'], Message>
const langs = [
{
name: 'English',
locale: 'en-us',
country: 'us',
message: en_us,
},
{
name: '한국어',
locale: 'ko-kr',
country: 'kr',
message: ko_kr,
},
{
name: '简体中文',
locale: 'zh-cn',
@@ -18,18 +32,12 @@ const langs = [
message: zh_cn,
},
{
name: '繁中文',
name: '繁中文',
locale: 'zh-tw',
// alternate: 'zh-hk',
country: 'cn',
message: zh_tw,
},
{
name: 'English',
locale: 'en-us',
country: 'us',
message: en_us,
},
] as const
const langList: Array<{

748
src/lang/ko-kr.json Normal file
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 가져오기"
}

View File

@@ -1,4 +1,14 @@
[
{
"name": "English",
"locale": "en-us",
"country": "us"
},
{
"name": "한국어",
"locale": "ko-kr",
"country": "kr"
},
{
"name": "简体中文",
"locale": "zh-cn",
@@ -7,14 +17,9 @@
"fallback": true
},
{
"name": "繁中文",
"name": "繁中文",
"locale": "zh-tw",
"alternate": "zh-hk",
"country": "cn"
},
{
"name": "English",
"locale": "en-us",
"country": "us"
}
]

View File

@@ -12,7 +12,7 @@
"cancel_button_text_2": "不不不,点错了",
"close": "关闭",
"comment__hot_load_error": "热门评论加载失败,点击尝试重新加载",
"comment__hot_loading": "热门评论加载中",
"comment__hot_loading": "热门评论加载中...",
"comment__hot_title": "热门评论",
"comment__location": "来自{location}",
"comment__new_load_error": "最新评论加载失败,点击尝试重新加载",
@@ -21,13 +21,13 @@
"comment__no_content": "暂无评论",
"comment__refresh": "刷新评论",
"comment__show": "歌曲评论",
"comment__title": "{name} 的评论",
"comment__title": "{name}的评论",
"comment__unavailable": "此歌曲不支持获取评论",
"confirm_button_text": "是的",
"copy_tip": "(点击复制)",
"date_format_hour": "{num}小时前",
"date_format_minute": "{num}分钟前",
"date_format_second": "{num}秒前",
"date_format_hour": "{num} 小时前",
"date_format_minute": "{num} 分钟前",
"date_format_second": "{num} 秒前",
"deep_link__handle_error_tip": "调用失败:{message}",
"default": "默认",
"default_list": "试听列表",
@@ -38,22 +38,22 @@
"desktop_lyric__lock": "锁定歌词",
"desktop_lyric__lrc_active_zoom_off": "取消缩放当前播放的歌词",
"desktop_lyric__lrc_active_zoom_on": "缩放当前播放的歌词",
"desktop_lyric__opactiy_decrease": "增加透明度(右击可微调)",
"desktop_lyric__opactiy_increase": "减小透明度(右击可微调)",
"desktop_lyric__opacity_decrease": "增加透明度(右击可微调)",
"desktop_lyric__opacity_increase": "减小透明度(右击可微调)",
"desktop_lyric__theme": "主题配色",
"desktop_lyric__unlock": "解锁歌词",
"desktop_lyric__win_top_off": "取消置顶歌词界面",
"desktop_lyric__win_top_on": "置顶歌词界面",
"download": "下载",
"download___status_complated": "下载完成",
"download___status_completed": "下载完成",
"download___status_error": "任务出错",
"download___status_paused": "暂停下载",
"download___status_runing": "正在下载",
"download___status_wating": "等待下载",
"download__all": "全部任务",
"download___status_running": "正在下载",
"download___status_waiting": "等待下载",
"download__all": "所有任务",
"download__error": "出错",
"download__finished": "下载完成",
"download__high_quality": "高音质",
"download__high_quality": "高音质",
"download__lossless": "无损音质",
"download__multiple_tip": "已选择 {len} 首歌曲",
"download__multiple_tip2": "请选择要优先下载的音质",
@@ -62,7 +62,7 @@
"download__paused": "已暂停",
"download__progress": "进度",
"download__quality": "品质",
"download__runing": "正在下载",
"download__running": "正在下载",
"download__status": "状态",
"download_status_error_check_path": "检查下载路径出错,请检查设置的下载目录是否正常",
"download_status_error_check_path_exist": "存在同名文件,跳过下载",
@@ -71,9 +71,9 @@
"download_status_error_url_failed": "获取音乐链接失败",
"download_status_error_write": "歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:",
"download_status_start": "开始下载",
"download_status_url_geting": "音乐链接获取中...",
"download_status_url_getting": "音乐链接获取中...",
"download_status_write_queue": "数据写入中({num}",
"duplicate_list_tip": "你之前已收藏过列表 [{name}],是否需要更新里面的歌曲?",
"duplicate_list_tip": "你之前已收藏过列表{name},是否需要更新里面的歌曲?",
"export": "导出",
"fullscreen_exit": "退出全屏",
"history_clear": "清空搜索历史",
@@ -90,7 +90,7 @@
"list__file": "定位文件",
"list__import_part_button_cancel": "不要啊",
"list__import_part_button_confirm": "覆盖掉",
"list__import_part_confirm": "导入的列表{importName}与本地列表{localName})的ID相同是否覆盖本地列表",
"list__import_part_confirm": "导入的列表{importName}与本地列表{localName}」的 ID 相同,是否覆盖本地列表?",
"list__import_part_desc": "选择列表文件",
"list__load_failed": "啊,加载失败了😭",
"list__loading": "列表加载中...⏳",
@@ -104,68 +104,70 @@
"list__pause": "暂停任务",
"list__play": "播放",
"list__play_later": "稍后播放",
"list__remove": "除",
"list__remove_tip": "你真的想要移除 {name} 吗?",
"list__remove_tip_button": "是的 没错",
"list__remove": "除",
"list__remove_tip": "你真的想要移除{name}吗?",
"list__remove_tip_button": "是的没错",
"list__rename": "重命名",
"list__search": "搜索",
"list__sort": "调整位置",
"list__source_detail": "歌曲详情页",
"list__start": "开始任务",
"list__sync": "更新",
"list_add__btn_title": "把该歌曲添加到 {name}",
"list_add__multiple_btn_title": "把这些歌曲添加到 {name}",
"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设置 -> 备份与恢复 -> 所有数据 -> 导入",
"list_import_tip__playlist": "导入失败,这是一个列表备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 部分数据 -> 导入列表",
"list_import_tip__playlist_part": "导入失败,这是一个单列表文件,你需要去这里导入:\n我的列表 -> 右击任意一个列表名 -> 在弹出的菜单中选择导入",
"list_import_tip__setting": "导入失败,这是一个设置备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 部分数据 -> 设置导入",
"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_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__tips": "💡 每次启动软件时将会自动更新已勾选自动更新的列表",
"list_update_modal__title": "列表更新管理",
"list_update_modal__update": "立即更新",
"lists__add_local_file_desc": "选择歌曲文件",
"lists__dislike_music_tip": "你真的不喜欢 {name} 吗?",
"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}与本地列表{localName})的ID相同是否覆盖本地列表",
"lists__import_part_confirm": "导入的列表{importName}与本地列表{localName}」的 ID 相同,是否覆盖本地列表?",
"lists__import_part_desc": "选择列表文件",
"lists__new_list_btn": "新建列表",
"lists__new_list_input": "新列表...",
"lists__remove": "除",
"lists__remove": "除",
"lists__remove_music_tip": "你真的要移除所选的 {len} 首歌曲吗?",
"lists__remove_tip": "你真的想要移除 {name} 吗?",
"lists__remove_tip_button": "是的 没错",
"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}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情{detail}",
"load_list_file_error_title": "播放列表数据加载错误建议到GitHub或加群反馈)",
"loding": "加载中...",
"lists__sync_confirm_tip": "这将会把{name}内的歌曲替换成在线列表的歌曲,你确认要更新吗?",
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情{detail}",
"load_list_file_error_title": "播放列表数据加载错误(建议到 GitHub 反馈)",
"loading": "加载中...",
"love_list": "收藏",
"lyric__load_error": "歌词获取失败",
"lyric__select": "歌词文本选择",
@@ -174,24 +176,27 @@
"lyric_menu__align_left": "居左",
"lyric_menu__lrc_size": "字体大小 [ {size} ]",
"lyric_menu__offset": "歌词偏移 [ {offset}ms ]",
"lyric_menu__offset_add_10": "加快10毫秒",
"lyric_menu__offset_add_100": "加快100毫秒",
"lyric_menu__offset_dec_10": "减慢10毫秒",
"lyric_menu__offset_dec_100": "减慢100毫秒",
"lyric_menu__offset_add_10": "加快 10 毫秒",
"lyric_menu__offset_add_100": "加快 100 毫秒",
"lyric_menu__offset_dec_10": "减慢 10 毫秒",
"lyric_menu__offset_dec_100": "减慢 100 毫秒",
"lyric_menu__offset_reset": "重置",
"lyric_menu__size_add": "加大字体(右击可微调)",
"lyric_menu__size_dec": "减小字体(右击可微调)",
"lyric_menu__size_reset": "重置",
"media_device__emtpy_device_tip": "音频输出设备为空,若出现无法播放的情况,请检查声卡驱动是否已安装或重装声卡驱动!",
"media_device__empty_device_tip": "音频输出设备为空,若出现无法播放的情况,请检查声卡驱动是否已安装。或卸载并重新安装软件,安装时选「仅为我安装」,并保持默认安装路径。",
"min": "最小化",
"music_album": "专辑名",
"music_duplicate": "重复歌曲",
"music_name": "歌曲名",
"music_singer": "艺术家",
"music_sort__input_tip": "请输入要调整到第几个位置",
"music_sort__title": "将 {name} 的位置调整到:",
"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": "不接受",
@@ -215,8 +220,8 @@
"player__desktop_lyric_unlock": "右击解锁歌词",
"player__end": "播放完毕",
"player__error": "音频加载出错5 秒后切换下一首",
"player__geting_url": "歌曲链接获取中...",
"player__geting_url_delay_retry": "服务繁忙,{time}秒后重试...",
"player__getting_url": "歌曲链接获取中...",
"player__getting_url_delay_retry": "服务繁忙,{time} 秒后重试...",
"player__hide_detail_tip": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
"player__loading": "音乐加载中...",
"player__music_album": "专辑名:",
@@ -224,19 +229,19 @@
"player__music_singer": "艺术家:",
"player__next": "下一首",
"player__pause": "暂停",
"player__pic_tip": "播放详情页(右击在我的列表定位当前播放的歌曲)",
"player__pic_tip": "播放详情页(右击在我的列表定位当前播放的歌曲)",
"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__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过期正在刷新URL...",
"player__refresh_url": "URL 过期,正在刷新 URL...",
"player__sound_effect": "音效设置(实验性)",
"player__sound_effect_biquad_filter": "均衡器",
"player__sound_effect_biquad_filter_preset_classical": "古典",
@@ -258,8 +263,8 @@
"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": "矩阵混响",
"player__sound_effect_convolution_file_matrix_2": "矩阵混响2",
"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": "室内",
@@ -267,15 +272,15 @@
"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_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已知问题\n如果CPU资源不够时将处理导致任务堆积而出现声音异常这时需要暂停播放一段时间等堆积的任务处理完毕再播放",
"player__sound_effect_pitch_shifter_tip": "由于升降调需要实时处理音频数据,这会导致额外的 CPU 占用\n\n已知问题\n如果 CPU 资源不够将导致处理任务堆积而出现声音异常。\n这时需要暂停播放一段时间等堆积的任务处理完毕再播放",
"player__stop": "暂停播放",
"player__volume": "当前音量:",
"player__volume_mute_label": "静音",
@@ -286,7 +291,7 @@
"search__type_songlist": "歌单",
"search__welcome": "搜我所想~~😉",
"setting": "设置",
"setting__about": "关于洛雪音乐",
"setting__about": "关于 LX Music",
"setting__backup": "备份与恢复",
"setting__backup_all": "所有数据(列表数据与设置数据)",
"setting__backup_all_export": "导出",
@@ -295,16 +300,16 @@
"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_csv": "导出 CSV 格式列表",
"setting__backup_other_export_list_text": "导出 TXT 格式列表",
"setting__backup_other_export_list_text_confirm": "是否将所有列表合并为一个文件?",
"setting__backup_part": "部分数据(列表数据包括试听列表、收藏列表、用户自定义列表设置数据不包括快捷键设置)",
"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_confirm": "备份文件中列表与现有列表 ID 相同时,现有列表内歌曲将会被覆盖,是否要继续?",
"setting__backup_part_import_list_desc": "选择列表文件",
"setting__backup_part_import_setting": "导入设置",
"setting__backup_part_import_setting_desc": "选择配置文件",
@@ -315,11 +320,11 @@
"setting__basic_control_btn_position_right": "右边",
"setting__basic_font": "字体",
"setting__basic_font_size": "字体大小",
"setting__basic_font_size_14px": "小",
"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_18px": "大",
"setting__basic_font_size_19px": "非常大",
"setting__basic_lang": "语言",
"setting__basic_lang_title": "软件显示的语言",
@@ -327,30 +332,30 @@
"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": "音乐来源",
"setting__basic_show_animation": "显示动画效果",
"setting__basic_source": "自定义源",
"setting__basic_source_status_failed": "初始化失败",
"setting__basic_source_status_initing": "初始化中",
"setting__basic_source_status_initing": "初始化中...",
"setting__basic_source_status_success": "初始化成功",
"setting__basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)",
"setting__basic_source_test": "测试接口(几乎软件的所有功能都可用)",
"setting__basic_source_user_api_btn": "自定义源管理",
"setting__basic_sourcename": "源名",
"setting__basic_sourcename": "歌曲来源名",
"setting__basic_sourcename_alias": "别名",
"setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "选择源名类型",
"setting__basic_sourcename_title": "选择歌曲来源名类型",
"setting__basic_start_in_fullscreen": "以全屏模式启动",
"setting__basic_theme": "主题颜色",
"setting__basic_theme_auto_tip": "此乃动态主题,你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。\n注鼠标 右击 此主题项即可打开亮、暗色主题设置窗口。",
"setting__basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
"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_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_smaller": "小",
"setting__basic_window_size_title": "设置软件窗口尺寸",
"setting__click_copy": "点击复制",
"setting__click_open": "点击打开",
@@ -360,7 +365,8 @@
"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": "自动刷新歌词置顶",
"setting__desktop_lyric_always_on_top_loop_tip": "当歌词置顶后仍被某些程序遮挡时可尝试启用此选项",
"setting__desktop_lyric_audio_visualization": "音频可视化(实验性)",
"setting__desktop_lyric_color": "歌词字体颜色",
"setting__desktop_lyric_color_reset": "重置颜色",
@@ -373,13 +379,18 @@
"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": "鼠标移入歌词区域时提高歌词透明度",
"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": "重置窗口设置",
@@ -387,28 +398,35 @@
"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": "在任务栏显示歌词进程",
"setting__desktop_lyric_show_taskbar_tip": "此选项作为在录屏软件无法捕获歌词窗口时的变通解决方法",
"setting__desktop_lyric_unplay_color": "未播放颜色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"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__dislike_list_tips": "1. 每条一行,若歌曲或者艺术家名字中存在「@」符号,需要将其替换成「#」\n2. 指定某艺术家的某首歌:歌曲名@艺术家\n3. 指定某首歌:歌曲名\n4. 指定某艺术家:@艺术家",
"setting__dislike_list_title": "不喜欢的歌曲规则列表",
"setting__download": "下载设置",
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
"setting__download_embed_lyric": "歌词嵌入",
"setting__download_embed_pic": "封面嵌入",
"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_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_gbk": "GBK",
"setting__download_lyric_format_tip": "在某些设备上出现中文乱码时可尝试选择 GBK 格式",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "是否同时下载歌词文件",
"setting__download_lyric_title": "同时下载歌词文件",
"setting__download_max_num": "同时下载任务数",
"setting__download_max_num_tip": "过大的同时下载数量可能会导致你的 IP 被自定义源封禁,是否确认修改?",
"setting__download_max_num_tooltip": "设置过大可能会导致 IP 被封,这取决于自定义源",
"setting__download_name": "文件命名方式",
"setting__download_name1": "歌名 - 歌手",
"setting__download_name2": "歌手 - 歌名",
"setting__download_name3": "歌名",
"setting__download_name1": "歌名 - 艺术家",
"setting__download_name2": "艺术家 - 歌名",
"setting__download_name3": "歌名",
"setting__download_name_title": "下载歌曲时的命名方式",
"setting__download_path": "下载路径",
"setting__download_path_change_btn": "更改",
@@ -417,10 +435,10 @@
"setting__download_path_title": "下载歌曲保存的路径",
"setting__download_rlyric": "同时将罗马音歌词写入歌词文件中(如果有)",
"setting__download_select_save_path": "选择歌曲保存路径",
"setting__download_skip_exist_file": "下载目录存在同名文件时跳过下载此任务",
"setting__download_skip_exist_file": "下载目录存在同名文件时跳过下载此任务",
"setting__download_tlyric": "同时将翻译歌词写入歌词文件中(如果有)",
"setting__download_use_other_source": "自动换源下载",
"setting__download_use_other_source_tip": "当无法从歌曲的原始源下载时,尝试切换到其他源下载此功能不100%保证换源后的歌曲版本与原版一致",
"setting__download_use_other_source_tip": "当无法从歌曲的原始源下载时,尝试切换到其他源下载。\n注:此功能不 100% 保证换源后的歌曲版本与原版一致",
"setting__hot_key": "快捷键设置",
"setting__hot_key_common_focus_search_input": "聚焦搜索框",
"setting__hot_key_common_min": "最小化程序",
@@ -437,54 +455,56 @@
"setting__hot_key_player_music_unlove": "取消收藏",
"setting__hot_key_player_next": "下一首歌曲",
"setting__hot_key_player_prev": "上一首歌曲",
"setting__hot_key_player_seekbackward": "快退 5s",
"setting__hot_key_player_seekforward": "快进 5s",
"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__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__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_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": "开放 API",
"setting__open_api_address": "服务地址:",
"setting__open_api_bind_lan": "允许来自局域网的访问",
"setting__open_api_enable": "启用开放API服务",
"setting__open_api_enable": "启用开放 API 服务",
"setting__open_api_port": "服务端口",
"setting__open_api_port_tip": "请输入开放API服务端口",
"setting__open_api_tip": "该功能用于为第三方软件提供调用LX Music的能力目前可用的功能可以看",
"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": "不喜欢的歌曲规则",
"setting__other_dislike_list_label": "规则数量:",
"setting__other_dislike_list_show_btn": "编辑不喜欢歌曲规则",
"setting__other_dislike_list_show_btn": "编辑规则",
"setting__other_listdata": "列表数据清理",
"setting__other_listdata_clear_btn": "清空我的列表数据",
"setting__other_listdata_clear_tip_confirm": "这将清理你创建的 所有列表 及收藏的 所有歌曲,是否真的要继续?",
"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": "这将清理所有你之前已调整过偏移时间的歌词,是否确认清理?(手抖确认🤪)",
"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_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": "换源歌曲信息数量:",
@@ -492,57 +512,63 @@
"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_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_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": "歌词字体大小(可以在播放详情页使用键盘的「+」「-」调整字体大小)",
"setting__play_detail_font_size_current": "当前字体大小:{size}",
"setting__play_detail_font_size_reset": "重置",
"setting__play_detail_font_zoom": "缩放当前正在播放的歌词",
"setting__play_detail_lyric_progress": "允许通过歌词调整播放进度",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放如果可用此功能比较耗性能低配置电脑不建议开启",
"setting__play_detail_lyric_delay_scroll": "延迟歌词滚动",
"setting__play_detail_lyric_progress": "允许通过拖拽歌词调整播放进度",
"setting__play_lyric_lxlrc": "使用卡拉 OK 式歌词播放(如果可用)",
"setting__play_lyric_lxlrc_tip": "此功能比较耗性能,低配置电脑不建议开启!",
"setting__play_lyric_roma": "显示歌词罗马音(如果可用)",
"setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文",
"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_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_audio_visualization_tip": "自定义音频输出设备与音频可视化功能冲突,启用音频可视化后音频输出设备将会被重置为默认,目前此问题暂无法解决是否仍要开启?",
"setting__player_swap_lyric_trans_roma": "调换歌词翻译与歌词罗马音位置",
"setting__search": "搜索设置",
"setting__search_focus_search_box": "启动时自动聚焦搜索框",
"setting__search_history": "显示历史搜索记录",
"setting__search_hot": "显示热门搜索",
"setting__setting__desktop_lyric_font_weight_extended": "翻译、罗马音歌词",
"setting__setting__desktop_lyric_font_weight_font": "逐字歌词",
"setting__setting__desktop_lyric_font_weight_line": "逐行歌词",
"setting__sync": "数据同步",
"setting__sync_client_address": "当前设备地址:{address}",
"setting__sync_client_host": "同步服务地址",
"setting__sync_client_host_tip": "http://IP地址:端口号",
"setting__sync_client_host_tip": "http://<IP 地址>:<端口号>",
"setting__sync_client_mode": "客户端模式",
"setting__sync_client_status": "状态:{status}",
"setting__sync_code_blocked_ip": "当前设备的IP已被服务端封禁",
"setting__sync_code_blocked_ip": "当前设备的 IP 已被服务端封禁!",
"setting__sync_code_fail": "连接码无效",
"setting__sync_enable": "启用同步功能",
"setting__sync_mode": "同步模式",
@@ -561,7 +587,7 @@
"setting__sync_server_port_tip": "请输入同步服务端口号",
"setting__sync_server_refresh_code": "刷新连接码",
"setting__sync_server_show_device_list": "已认证设备列表",
"setting__sync_tip": "使用方式请看常见问题同步功能部分",
"setting__sync_tip": "使用方式请看常见问题同步功能部分",
"setting__update": "软件更新",
"setting__update_checking": "检查更新中...",
"setting__update_commit_date": "提交日期:",
@@ -577,18 +603,19 @@
"setting__update_show_change_log": "更新版本后的首次启动时显示更新日志",
"setting__update_try_auto_update": "发现新版本时尝试自动下载更新",
"setting__update_unknown": "未知",
"setting__update_unknown_tip": "❓ 获取最新版本信息失败,建议去关于面打开项目发布地址查看当前版本是否最新",
"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": "输入歌单链接或歌单 ID",
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前选择的歌单源是否对应",
"songlist__import_input_tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
"songlist__import_input_tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
"songlist__import_input_tip_4": "网易源的“我喜欢”歌单需要 Token 才能打开,详情看 ",
"songlist__import_input_tip_3": "酷狗源歌单不支持用歌单 ID 或概念版链接打开,但支持用普通版链接或酷狗码打开",
"songlist__import_input_tip_4": "网易源「私人雷达」歌单需要 Token 才能打开,企鹅源个人歌单需要将个人主页公开才能打开,请参阅 ",
"songlist__import_input_title": "打开分享的歌单",
"songlist__open_list": "打开{name}歌单",
"songlist__open_list": "打开{name}歌单",
"songlist__tag_info_hot_tag": "热门标签",
"source_alias_all": "聚合大会",
"source_alias_bd": "小杜音乐",
@@ -609,24 +636,24 @@
"sync__auth_code_input_tip": "请输入连接码",
"sync__auth_code_title": "需要输入连接码",
"sync__dislike_merge_tip_desc": "合并两边列表内容并去重",
"sync__dislike_other_tip_desc": "取消同步将不使用不喜欢列表同步功能",
"sync__dislike_other_tip_desc": "取消同步将不使用不喜欢的歌曲」列表同步功能",
"sync__dislike_overwrite_tip_desc": "被覆盖者的列表将被替换成覆盖者的列表",
"sync__dislike_title": "选择与 {name} 的不喜欢列表同步方式",
"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__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__merge_tip": "合并",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip": "其他",
"sync__overwrite": "完全覆盖",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
"sync__overwrite_btn_local_remote": "本机列表覆盖远程列表",
"sync__overwrite_btn_none": "仅使用实时同步功能",
"sync__overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
"sync__overwrite_btn_remote_local": "远程列表覆盖本机列表",
"sync__overwrite_label": "覆盖",
"sync__overwrite_tip": "覆盖:",
"sync_status_disabled": "未连接",
@@ -635,7 +662,7 @@
"tag__lossless_24bit": "24bit",
"theme_add": "添加主题",
"theme_auto": "道法自然",
"theme_auto_tip": "鼠标 右击 可打开亮、暗主题设置窗口",
"theme_auto_tip": "鼠标右击可打开亮、暗主题设置窗口",
"theme_black": "黑灯瞎火",
"theme_blue": "蓝田生玉",
"theme_blue2": "清热版蓝",
@@ -666,15 +693,16 @@
"theme_edit_modal__pick_save": "确认",
"theme_edit_modal__preview": "预览主题",
"theme_edit_modal__primary": "主题色",
"theme_edit_modal__remove": "除",
"theme_edit_modal__remove": "除",
"theme_edit_modal__remove_tip": "是否真的要移除这个主题?",
"theme_edit_modal__save_new": "另存",
"theme_edit_modal__select_bg_file": "选择背景图片",
"theme_edit_modal__title": "编辑主题",
"theme_edit_modal__title_add": "添加主题",
"theme_edit_modal__title_edit": "编辑主题",
"theme_green": "绿意盎然",
"theme_grey": "灰常美丽",
"theme_happy_new_year": "新年快乐",
"theme_max_tip": "最多只能添加10个主题哦删掉一些再添加吧 😜 ",
"theme_max_tip": "最多只能添加 10 个主题哦,删掉一些再添加吧😜",
"theme_mid_autumn": "月里嫦娥",
"theme_ming": "青出于黑",
"theme_more_btn_show": "更多主题",
@@ -688,29 +716,29 @@
"theme_selector_modal__theme_name": "主题名称",
"theme_selector_modal__title": "跟随系统主题设置",
"theme_selector_modal__title_tip": "注:你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。",
"toggle_source_failed": "换源失败,请尝试手动在其他源搜索该歌曲播放",
"toggle_source_try": "尝试切换到其他源...",
"update__downgrade_tip": "我们发现你降级了版本({ver}),若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!\n注意从新版本降级旧版时建议先备份歌单若出现异常则可通过清理数据解决数据目录路径文档有记录。",
"update__error_top": "自动下载新版本失败,你可以尝试重新下载更新或者手动去下载更新\n新版地址在更新弹窗下面有写下载新版直接覆盖安装即可若安装失败则看常见问题解决\n注意目前只有Windows安装版可以自动更新LinuxAppImage、deb版似乎也可以未测试其他版本请手动下载更新",
"toggle_source_failed": "换源失败,请尝试手动在搜索页指定其他源搜索该歌曲播放",
"toggle_source_try": "尝试切换到其他源...",
"update__downgrade_tip": "我们发现你降级了版本({ver}),若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!\n\n注意:从新版本降级旧版时建议先备份歌单,若出现异常则可通过清理数据解决,数据目录路径文档有记录。",
"update__error_top": "自动下载新版本失败,你可以尝试重新下载更新或者手动去下载更新。\n\n新版地址在更新弹窗下面有写下载新版直接覆盖安装即可若安装失败则看常见问题解决。\n\n注意目前只有 Windows 安装版可以自动更新LinuxAppImage、deb 版似乎也可以,未测试),其他版本请手动下载更新!",
"update__ignore_cancel": "我就不想更新🤨",
"update__ignore_confirm": "好 去更新看看❤️",
"update__ignore_confirm_tip": "目前只有Windows安装版可以自动更新LinuxAppImage、deb版似乎也可以未测试其他版本请手动下载更新\n新版地址在更新弹窗下面有写下载新版直接覆盖安装即可若安装失败看常见问题解决。",
"update__ignore_confirm_tip_confirm": "OK 已了解",
"update__ignore_tip": "你现在使用的版本距离最新版本已经落后了 {num} 个版本🤪,为了更好的使用体验,建议更新到最新版本哦~\n注若使用新版本时遇到问题请先尝试阅读常见问题解决若你遇到的问题在常见问题中未记录或无法解决可以通过文档中提到的反馈渠道给我们反馈😘",
"update__timeout_top": "下载时间过长提示\n\n你当前所在网络访问GitHub较慢新版本已经下了一个钟了还没完成😳你仍可选择继续等但墙裂建议手动更新版本",
"update__ignore_confirm": "好去更新看看❤️",
"update__ignore_confirm_tip": "目前只有 Windows 安装版可以自动更新LinuxAppImage、deb 版似乎也可以,未测试),其他版本请手动下载更新,\n新版地址在更新弹窗下面有写下载新版直接覆盖安装即可若安装失败看常见问题解决。",
"update__ignore_confirm_tip_confirm": "OK已了解",
"update__ignore_tip": "你现在使用的版本距离最新版本已经落后了 {num} 个版本🤪,为了更好的使用体验,建议更新到最新版本哦~\n\n注:若使用新版本时遇到问题,请先尝试阅读常见问题解决,若你遇到的问题在常见问题中未记录或无法解决,可以通过文档中提到的反馈渠道给我们反馈😘!",
"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": "自定义源 [{name}] 初始化失败:",
"user_api__max_tip": "最多只能同时存在20个源哦🤪\n想要继续导入的话请先移除一些旧的源腾出位置吧",
"user_api__noitem": "这里竟然是空的 😲",
"user_api__import_file": "选择自定义源 API 脚本文件",
"user_api__init_failed_alert": "自定义源{name}初始化失败:",
"user_api__max_tip": "最多只能同时存在 20 个源哦🤪\n想要继续导入的话请先移除一些旧的源腾出位置吧",
"user_api__noitem": "这里竟然是空的😲",
"user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
"user_api__readme": "源编写说明:",
"user_api__title": "自定义源管理",
"user_api__update_alert": "自定义源 [{name}] 发现新版本:",
"user_api__update_alert": "自定义源{name}发现新版本:",
"user_api__update_alert_open_url": "打开更新地址",
"user_api_import__failed": "自定义源导入失败:\n{message}",
"user_api_import_online__input_confirm": "导入",

File diff suppressed because it is too large Load Diff

View File

@@ -2,27 +2,82 @@ import path from 'node:path'
import { existsSync, mkdirSync, renameSync } from 'fs'
import { app, shell, screen, nativeTheme, dialog } from 'electron'
import { URL_SCHEME_RXP } from '@common/constants'
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
import { getProxy, getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
import { navigationUrlWhiteList } from '@common/config'
import defaultSetting from '@common/defaultSetting'
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
import { isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
import { createAppEvent, createDislikeEvent, createListEvent } from '@main/event'
import { isMac, log } from '@common/utils'
import createWorkers from './worker'
import { migrateDBData } from './utils/migrate'
import { encodePath, openDirInExplorer } from '@common/utils/electron'
import { openDirInExplorer } from '@common/utils/electron'
import { setProxyByHost } from '@common/utils/request'
export const initGlobalData = () => {
const envParams = parseEnvParams()
// envParams.cmdParams.dt = !!envParams.cmdParams.dt
global.envParams = {
cmdParams: envParams.cmdParams,
deeplink: envParams.deeplink,
}
global.lx = {
inited: false,
isSkipTrayQuit: false,
// mainWindowClosed: true,
event_app: createAppEvent(),
event_list: createListEvent(),
event_dislike: createDislikeEvent(),
appSetting: defaultSetting,
worker: createWorkers(),
hotKey: {
enable: true,
config: {
local: {
enable: false,
keys: {},
},
global: {
enable: false,
keys: {},
},
},
state: new Map(),
},
theme: {
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
theme: {
id: '',
name: '',
isDark: false,
colors: {},
},
},
player_status: {
status: 'stoped',
name: '',
singer: '',
albumName: '',
picUrl: '',
progress: 0,
duration: 0,
playbackRate: 1,
lyricLineText: '',
lyricLineAllText: '',
lyric: '',
tlyric: '',
rlyric: '',
lxlyric: '',
collect: false,
volume: 0,
mute: false,
},
}
global.staticPath =
process.env.NODE_ENV !== 'production'
? webpackStaticPath
: path.join(encodePath(__dirname), 'static')
: path.join(__dirname, 'static')
}
export const initSingleInstanceHandle = () => {
@@ -33,16 +88,16 @@ export const initSingleInstanceHandle = () => {
}
app.on('second-instance', (event, argv, cwd) => {
for (const param of argv) {
if (URL_SCHEME_RXP.test(param)) {
global.envParams.deeplink = param
break
}
}
if (isExistMainWindow()) {
if (global.envParams.deeplink) global.lx.event_app.deeplink(global.envParams.deeplink)
else showMainWindow()
const envParams = parseEnvParams(argv)
if (envParams.deeplink) {
global.envParams.deeplink = envParams.deeplink
global.lx.event_app.deeplink(global.envParams.deeplink)
return
}
if (envParams.cmdParams.hidden !== true) {
showMainWindow()
}
} else {
app.quit()
}
@@ -158,6 +213,9 @@ export const listenerAppEvent = (startApp: () => void) => {
}
})
app.on('before-quit', () => {
global.lx.isSkipTrayQuit = true
})
app.on('window-all-closed', () => {
if (isMac) return
@@ -178,6 +236,30 @@ export const listenerAppEvent = (startApp: () => void) => {
global.lx.theme.shouldUseDarkColors = shouldUseDarkColors
global.lx?.event_app.system_theme_change(shouldUseDarkColors)
})
const setProxy = () => {
const proxy = getProxy()
if (proxy) {
setProxyByHost(proxy.host, proxy.port ? String(proxy.port) : undefined)
} else setProxyByHost()
}
global.lx.event_app.on('updated_config', (keys, setting) => {
if (keys.includes('network.proxy.enable') || (global.lx.appSetting['network.proxy.enable'] && keys.some(k => k.includes('network.proxy.')))) {
setProxy()
}
if (keys.includes('player.volume')) {
global.lx.event_app.player_status({ volume: Math.trunc(setting['player.volume']! * 100) })
}
if (keys.includes('player.isMute')) {
global.lx.event_app.player_status({ mute: setting['player.isMute'] })
}
})
global.lx.event_app.on('app_inited', () => {
setProxy()
global.lx.event_app.player_status({ volume: Math.trunc(global.lx.appSetting['player.volume'] * 100) })
global.lx.event_app.player_status({ mute: global.lx.appSetting['player.isMute'] })
})
}
const initTheme = () => {
@@ -204,69 +286,45 @@ const initTheme = () => {
})
}
const backupDB = (backupPath: string) => {
const dbPath = path.join(global.lxDataPath, 'lx.data.db')
try {
renameSync(dbPath, backupPath)
} catch {}
try {
renameSync(`${dbPath}-wal`, `${backupPath}-wal`)
} catch {}
try {
renameSync(`${dbPath}-shm`, `${backupPath}-shm`)
} catch {}
openDirInExplorer(backupPath)
}
let isInitialized = false
export const initAppSetting = async() => {
if (!global.lx) {
if (!global.lx.inited) {
const config = await initHotKey()
global.lx = {
isTrafficLightClose: false,
isSkipTrayQuit: false,
// mainWindowClosed: true,
event_app: createAppEvent(),
event_list: createListEvent(),
event_dislike: createDislikeEvent(),
appSetting: defaultSetting,
worker: createWorkers(),
hotKey: {
enable: true,
config: {
local: config.local,
global: config.global,
},
state: new Map(),
},
theme: {
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
theme: {
id: '',
name: '',
isDark: false,
colors: {},
},
},
player_status: {
status: 'stoped',
name: '',
singer: '',
albumName: '',
picUrl: '',
progress: 0,
duration: 0,
playbackRate: 1,
lyricLineText: '',
lyricLineAllText: '',
lyric: '',
collect: false,
},
}
global.lx.hotKey.config.local = config.local
global.lx.hotKey.config.global = config.global
global.lx.inited = true
}
if (!isInitialized) {
let dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
if (dbFileExists === null) {
const backPath = path.join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
const backupPath = path.join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
dialog.showMessageBoxSync({
type: 'warning',
message: 'Database verify failed',
detail: `数据库表结构校验失败,我们将把有问题的数据库备份到:${backPath}\n若此问题导致你的数据丢失你可以尝试从备份文件找回它们。\n\nThe database table structure verification failed, we will back up the problematic database to: ${backPath}\nIf this problem causes your data to be lost, you can try to retrieve them from the backup file.`,
detail: `数据库表结构校验失败,我们将把有问题的数据库备份到:${backupPath}\n若此问题导致你的数据丢失你可以尝试从备份文件找回它们。\n\nThe database table structure verification failed, we will back up the problematic database to: ${backupPath}\nIf this problem causes your data to be lost, you can try to retrieve them from the backup file.`,
})
renameSync(path.join(global.lxDataPath, 'lx.data.db'), backPath)
openDirInExplorer(backPath)
backupDB(backupPath)
dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
}
global.lx.appSetting = (await initSetting()).setting
if (!dbFileExists) await migrateDBData().catch(err => { log.error(err) })
initTheme()
if (envParams.cmdParams.dt == null) envParams.cmdParams.dt = !global.lx.appSetting['common.transparentWindow']
}
// global.lx.theme = getTheme()
@@ -275,5 +333,5 @@ export const initAppSetting = async() => {
export const quitApp = () => {
global.lx.isSkipTrayQuit = true
closeWindow()
app.quit()
}

View File

@@ -1,6 +1,7 @@
import { EventEmitter } from 'events'
import { saveAppHotKeyConfig, updateSetting } from '@main/utils'
import type { BrowserWindow } from 'electron'
export class Event extends EventEmitter {
// closeAll() {
@@ -69,6 +70,10 @@ export class Event extends EventEmitter {
this.emit('hot_key_config_update', config)
}
main_window_created(win: BrowserWindow) {
this.emit('main_window_created', win)
}
main_window_ready_to_show() {
this.emit('main_window_ready_to_show')
}
@@ -100,6 +105,10 @@ export class Event extends EventEmitter {
main_window_fullscreen(isFullscreen: boolean) {
this.emit('main_window_fullscreen', isFullscreen)
}
desktop_lyric_window_created(win: BrowserWindow) {
this.emit('desktop_lyric_window_created', win)
}
}

View File

@@ -8,21 +8,35 @@
import { app } from 'electron'
import electronDebug from 'electron-debug'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import { openDevTools } from './utils'
// Install `electron-debug` with `devtron`
electronDebug({
showDevTools: true,
showDevTools: false,
devToolsMode: 'undocked',
})
// Install `vue-devtools`
app.on('ready', () => {
installExtension(VUEJS_DEVTOOLS)
.then((name: string) => {
console.log(`Added Extension: ${name}`)
})
.catch((err: Error) => {
console.log('An error occurred: ', err)
})
global.lx.event_app.on('main_window_created', (win) => {
openDevTools(win.webContents)
installExtension(VUEJS_DEVTOOLS, { session: win.webContents.session })
.then((name: string) => {
console.log(`[main window] Added Extension: ${name}`)
})
.catch((err: Error) => {
console.log('[main window] An error occurred: ', err)
})
})
global.lx.event_app.on('desktop_lyric_window_created', (win) => {
openDevTools(win.webContents)
installExtension(VUEJS_DEVTOOLS, { session: win.webContents.session })
.then((name: string) => {
console.log(`[lyric window] Added Extension: ${name}`)
})
.catch((err: Error) => {
console.log('[lyric window] An error occurred: ', err)
})
})
})
// Require `main` process to boot app

View File

@@ -16,6 +16,7 @@ import registerModules from '@main/modules'
// 初始化应用
const init = () => {
console.log('init')
if (process.env.BUILD_WIN7 == 'true') import('./utils/winLegacy')
void initAppSetting().then(() => {
registerModules()
global.lx.event_app.app_inited()

View File

@@ -2,6 +2,19 @@ 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,
@@ -37,10 +50,16 @@ 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]
res.setHeader('Content-Type', 'application/json; charset=utf-8')
res.setHeader('Access-Control-Allow-Origin', '*')
res.writeHead(200)
res.end(JSON.stringify(resp))
sendResponse(res, 200, resp, 'application/json; charset=utf-8')
}
const handleSendAllLyric = (res: http.ServerResponse<http.IncomingMessage>) => {
const resp: Partial<Record<SubscribeKeys, any>> = {
lyric: global.lx.player_status.lyric,
tlyric: global.lx.player_status.tlyric,
rlyric: global.lx.player_status.rlyric,
lxlyric: global.lx.player_status.lxlyric,
}
sendResponse(res, 200, resp, 'application/json; charset=utf-8')
}
const handleSubscribePlayerStatus = (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>, query?: string) => {
res.writeHead(200, {
@@ -67,8 +86,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
let msg
let code = 200
let msg = 'OK'
switch (endUrl) {
case '/status':
handleSendStatus(res, query)
@@ -118,11 +137,61 @@ 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 '/lyric-all':
handleSendAllLyric(res)
return
case '/play':
sendTaskbarButtonClick('play')
break
case '/pause':
sendTaskbarButtonClick('pause')
break
case '/skip-next':
sendTaskbarButtonClick('next')
break
case '/skip-prev':
sendTaskbarButtonClick('prev')
break
case '/seek': {
const offset = parseFloat(querystring.parse(query ?? '').offset as string)
if (Number.isNaN(offset) || offset < 0 || offset > global.lx.player_status.duration) {
code = 400
msg = 'Invalid offset'
} else {
sendTaskbarButtonClick('seek', parseFloat(offset.toFixed(3)))
}
break
}
case '/collect':
sendTaskbarButtonClick('collect')
break
case '/uncollect':
sendTaskbarButtonClick('unCollect')
break
case '/volume': {
const volume = parseInt(querystring.parse(query ?? '').volume as string)
if (Number.isNaN(volume) || volume < 0 || volume > 100) {
code = 400
msg = 'Invalid volume'
} else {
sendTaskbarButtonClick('volume', volume / 100)
}
break
}
case '/mute': {
const mute = querystring.parse(query ?? '').mute
if (mute == 'true') {
sendTaskbarButtonClick('mute', true)
} else if (mute == 'false') {
sendTaskbarButtonClick('mute', false)
} else {
code = 400
msg = 'Invalid mute value'
}
break
}
case '/subscribe-player-status':
try {
handleSubscribePlayerStatus(req, res, query)
@@ -138,9 +207,7 @@ const handleStartServer = async(port: number, ip: string) => new Promise<void>((
msg = 'Forbidden'
break
}
if (!code) return
res.writeHead(code)
res.end(msg)
sendResponse(res, code, msg)
})
httpServer.on('error', error => {
console.log(error)

View File

@@ -151,6 +151,7 @@ let client: LX.Sync.Client.Socket | null
// let listSyncPromise: Promise<void>
export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => {
client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}/socket?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, {
followRedirects: true,
}) as LX.Sync.Client.Socket
client.data = {
keyInfo,

View File

@@ -3,7 +3,7 @@ import { httpFetch, type RequestOptions } from '@main/utils/request'
import { decodeData, encodeData } from '../utils'
export const request = async(url: string, options: RequestOptions = { }) => {
return httpFetch(url, {
return httpFetch<string>(url, {
...options,
timeout: options.timeout ?? 10000,
}).then(response => {

View File

@@ -1,5 +1,5 @@
import { Tray, Menu, nativeImage } from 'electron'
import { isWin } from '@common/utils'
import { isMac, isWin } from '@common/utils'
import path from 'node:path'
import {
hideWindow as hideMainWindow,
@@ -9,10 +9,12 @@ 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
let themeId: number
let isShowStatusBarLyric: boolean = false
const playerState = {
empty: false,
@@ -28,6 +30,7 @@ const watchConfigKeys = [
'desktopLyric.isAlwaysOnTop',
'tray.themeId',
'tray.enable',
'player.isShowStatusBarLyric',
'common.langId',
] satisfies Array<keyof LX.AppSetting>
@@ -51,21 +54,25 @@ const themeList = [
const messages = {
'en-us': {
collect: 'Collection',
uncollect: 'Cancel collection',
collect: 'Love',
uncollect: 'Unlove',
play: 'Play',
pause: 'Pause',
next: 'Next song',
prev: 'Previous song',
next: 'Next Song',
prev: 'Prev Song',
hide_win_main: 'Hide Main Window',
show_win_main: 'Show Main Window',
hide_win_lyric: 'Close desktop lyrics',
show_win_lyric: 'Open desktop lyrics',
lock_win_lyric: 'Lock desktop lyrics',
unlock_win_lyric: 'Unlock desktop lyrics',
top_win_lyric: 'Set top lyrics',
untop_win_lyric: 'Cancel top lyrics',
hide_win_lyric: 'Hide Lyric Window',
show_win_lyric: 'Show Lyric Window',
lock_win_lyric: 'Lock Lyric Window',
unlock_win_lyric: 'Unlock Lyric Window',
top_win_lyric: 'On-top Lyric Window',
untop_win_lyric: 'Un-top Lyric Window',
show_statusbar_lyric: 'Show Lyrics on Statusbar',
hide_statusbar_lyric: 'Hide Lyrics on Statusbar',
exit: 'Exit',
music_name: 'Title: ',
music_singer: 'Artist: ',
},
'zh-cn': {
collect: '收藏',
@@ -82,7 +89,11 @@ const messages = {
unlock_win_lyric: '解锁桌面歌词',
top_win_lyric: '置顶歌词',
untop_win_lyric: '取消置顶',
show_statusbar_lyric: '显示状态栏歌词',
hide_statusbar_lyric: '隐藏状态栏歌词',
exit: '退出',
music_name: '歌曲名: ',
music_singer: '艺术家: ',
},
'zh-tw': {
collect: '收藏',
@@ -91,15 +102,40 @@ const messages = {
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: '取消置頂',
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: '演出者: ',
},
'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
type Messages = typeof messages
@@ -118,7 +154,10 @@ const i18n = {
}
const getIconPath = (id: number) => {
let theme = themeList.find(item => item.id === id) ?? themeList[0]
let theme = id == TRAY_AUTO_ID
? global.lx.theme.shouldUseDarkColors
? themeList[0] : themeList[2]
: themeList.find(item => item.id === id) ?? themeList[0]
return path.join(global.staticPath, 'images/tray', theme.fileName + (isWin ? '.ico' : '.png'))
}
@@ -129,23 +168,26 @@ export const createTray = () => {
// 托盘
tray = new Tray(nativeImage.createFromPath(getIconPath(global.lx.appSetting['tray.themeId'])))
tray.setToolTip('LX Music')
createMenu()
// tray.setToolTip('LX Music')
// createMenu()
tray.setIgnoreDoubleClickEvents(true)
tray.on('click', () => {
showMainWindow()
})
if (!isMac) {
tray.on('click', () => {
showMainWindow()
})
}
}
export const destroyTray = () => {
if (!tray) return
tray.destroy()
isEnableTray = false
isShowStatusBarLyric = false
tray = null
}
const handleUpdateConfig = (config: any) => {
global.lx.event_app.update_config(config)
const handleUpdateConfig = (setting: Partial<LX.AppSetting>) => {
global.lx.event_app.update_config(setting)
}
const createPlayerMenu = () => {
@@ -231,6 +273,22 @@ export const createMenu = () => {
handleUpdateConfig({ 'desktopLyric.isAlwaysOnTop': true })
},
})
if (isMac) {
menu.push({ type: 'separator' })
menu.push(isShowStatusBarLyric
? {
label: i18n.getMessage('hide_statusbar_lyric'),
click() {
handleUpdateConfig({ 'player.isShowStatusBarLyric': false })
},
}
: {
label: i18n.getMessage('show_statusbar_lyric'),
click() {
handleUpdateConfig({ 'player.isShowStatusBarLyric': true })
},
})
}
menu.push({ type: 'separator' })
if (isExistMainWindow()) {
const isShow = isShowMainWindow()
@@ -263,6 +321,28 @@ export const setTrayImage = (themeId: number) => {
tray.setImage(nativeImage.createFromPath(getIconPath(themeId)))
}
const setLyric = (lyricLineText?: string) => {
if (isShowStatusBarLyric && tray && lyricLineText != null) {
tray.setTitle(lyricLineText)
}
}
const defaultTip = 'LX Music'
const setTip = () => {
if (!tray) return
let name = global.lx.player_status.name
let tip: string
if (name) {
if (name.length > 20) name = name.substring(0, 20) + '...'
let singer = global.lx.player_status.singer
if (singer?.length > 20) singer = singer.substring(0, 20) + '...'
tip = `${defaultTip}\n${i18n.getMessage('music_name')}${name}${singer ? `\n${i18n.getMessage('music_singer')}${singer}` : ''}`
} else tip = defaultTip
tray.setToolTip(tip)
}
const init = () => {
if (themeId != global.lx.appSetting['tray.themeId']) {
themeId = global.lx.appSetting['tray.themeId']
@@ -272,6 +352,15 @@ const init = () => {
isEnableTray = global.lx.appSetting['tray.enable']
global.lx.appSetting['tray.enable'] ? createTray() : destroyTray()
}
if (isShowStatusBarLyric !== global.lx.appSetting['player.isShowStatusBarLyric']) {
isShowStatusBarLyric = global.lx.appSetting['player.isShowStatusBarLyric']
if (isShowStatusBarLyric) {
setLyric(global.lx.player_status.lyricLineText)
} else {
tray?.setTitle('')
}
}
setTip()
createMenu()
}
@@ -310,6 +399,11 @@ 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) {
@@ -317,22 +411,30 @@ export default () => {
case 'paused':
playerState.play = false
playerState.empty &&= false
setLyric('')
break
case 'error':
playerState.play = false
playerState.empty &&= false
setLyric('')
break
case 'playing':
playerState.play = true
playerState.empty &&= false
setLyric(global.lx.player_status.lyricLineText)
break
case 'stoped':
playerState.play &&= false
playerState.empty = true
setLyric('')
break
}
updated = true
} else {
setLyric(status.lyricLineText)
}
if (status.name != null) setTip()
if (status.singer != null) setTip()
if (status.collect != null) {
playerState.collect = status.collect
updated = true

Some files were not shown because too many files have changed in this diff Show More