Compare commits

..

185 Commits

Author SHA1 Message Date
Zhe Fang
b043f9acd0 新增:
- 歌词原文译文分隔符现可自定义
- 音乐库新增“播放全部”按钮
- 接入 Last.fm 服务(分别为播放源配置是否开启 Last.fm 听歌记录服务)

改动:
- 分别为播放源配置歌词源、设置歌词同步阈值、歌词偏移值

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
2025-08-06 19:21:05 -04:00
Zhe Fang
7b2ff0cc8f 新增:
- 歌词原文译文分隔符现可自定义
- 音乐库新增“播放全部”按钮
- 接入 Last.fm 服务(分别为播放源配置是否开启 Last.fm 听歌记录服务)

改动:
- 分别为播放源配置歌词源、设置歌词同步阈值、歌词偏移值

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
2025-08-06 19:19:49 -04:00
Zhe Fang
7c9ab73a34 Fix #76 2025-08-04 14:38:02 -04:00
Zhe Fang
a08bf91784 fix 2025-08-04 13:36:56 -04:00
Zhe Fang
61906670fd fix 2025-08-04 11:51:09 -04:00
Zhe Fang
430b2f4d28 fix 2025-08-04 11:23:07 -04:00
Zhe Fang
eb05c1ea13 update readme 2025-08-04 10:01:37 -04:00
Zhe Fang
9bebf36e6a update readme 2025-08-04 09:53:02 -04:00
Zhe Fang
d2b0b6afb1 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-04 09:13:33 -04:00
Zhe Fang
9c2f4fbff9 update readme 2025-08-04 09:13:31 -04:00
Zhe Fang
1b69493afd Update release-to-telegram.yml 2025-08-03 20:38:37 -04:00
Zhe Fang
c524dc013c Update release-to-telegram.yml 2025-08-03 20:35:37 -04:00
Zhe Fang
9ca5939e57 fix 2025-08-03 20:29:58 -04:00
Zhe Fang
860abd4037 fix 2025-08-03 19:43:14 -04:00
Zhe Fang
618415016f fix 2025-08-03 17:05:40 -04:00
Zhe Fang
bfcba1425d 更新 FAQ.md 中的链接文本
在 FAQ.md 文件中,修改了 FAQ 部分的链接文本。将原有的“点此访问”替换为“Click here to visit”,并在中文、日文和韩文版本中相应更新为“点此前往”、“こちらをクリック”和“여기를 클릭하세요”。
2025-08-02 12:26:36 -04:00
Zhe Fang
0dc9ebf18e Revert "Add GitHub stars badge to README files and fix encoding issues in Chinese translations"
This reverts commit 35ca28ac7b.
2025-08-02 12:24:30 -04:00
Zhe Fang
366d396b93 revert 2025-08-02 12:22:09 -04:00
Zhe Fang
35ca28ac7b Add GitHub stars badge to README files and fix encoding issues in Chinese translations 2025-08-02 12:11:16 -04:00
Zhe Fang
1505933107 update FAQ links for consistency in language and clarity 2025-08-02 11:36:51 -04:00
Zhe Fang
958227d0f2 refactor font size settings and update lyrics renderer initialization; add FAQ documentation 2025-08-02 11:35:09 -04:00
Zhe Fang
b1978fec09 update version to 1.0.40.0 and refactor image drawing methods for better opacity handling 2025-08-02 09:15:28 -04:00
Zhe Fang
010040b376 fix incorrect lyrics/translation language detection 2025-08-01 15:50:19 -04:00
Zhe Fang
35272d8324 update readme 2025-08-01 12:57:43 -04:00
Zhe Fang
29a714fe87 update readme 2025-08-01 12:45:06 -04:00
Zhe Fang
78edc2b3ce update readme 2025-08-01 12:19:39 -04:00
Zhe Fang
932f9d3a4f 更新 README.ko.md 文件内容
删除旧说明并添加浮动窗口控制栏功能描述。
2025-08-01 12:18:29 -04:00
Zhe Fang
a56a7af08c 添加 issue 翻译工作流,删除 README 翻译工作流
在 `issues-translator.yml` 中添加了 "issue-translator" 工作流,支持在创建评论和打开问题时自动翻译非英语内容。使用 `usthe/issues-translate-action@v2.7` 动作,并提供自定义机器人的提示信息。

同时,删除了 `readme-translator.yml` 中的整个工作流,原本用于翻译 README 文件的多种语言(简体中文、繁体中文、日语和韩语)。
2025-08-01 12:14:29 -04:00
Zhe Fang
5427c1992f 更新多个 README 文件内容
- 在 README.ja.md 中添加了 NetEase Cloud Music 和 foobar2000 的详细说明,并更新了设置提示。
- 更新了 README.ja.md 中关于 Bilibili 的信息,增加了“现在就试试”的呼吁。
- 在 README.zh-TW.md 中更新了歌词获取工具的链接和描述,增加了对 QQ、NetEase 和 Kugou 的支持说明。
- 修改了 README.zh-TW.md 中 FAQ 部分的标题和内容,以更好地反映用户问题。
- 更新了 README.zh-TW.md 中关于 Apple Music 的说明,提供了更清晰的操作指导。
2025-08-01 12:13:34 -04:00
Zhe Fang
64d69f44c3 更新版本号并优化多语言文档
- 将 `Package.appxmanifest` 中的版本号更新为 `1.0.37.0`。
- 更新 `README.ja.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合日语表达。
- 更新 `README.ko.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合韩语表达。
- 更新 `README.zh-CN.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合中文表达。
- 更新 `README.zh-TW.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合繁体中文表达。
2025-08-01 12:12:39 -04:00
Zhe Fang
d75fe4b27a Delete .github/workflows/readme-translator.yml 2025-08-01 11:58:06 -04:00
github-actions[bot]
eac9b3e28a docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:29:35 +00:00
github-actions[bot]
d6975ba2d4 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:29:22 +00:00
github-actions[bot]
aae8e1322a docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:29:09 +00:00
github-actions[bot]
88aac0eaf0 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:28:57 +00:00
Zhe Fang
c463d9bc5c Create issues-translator.yml 2025-08-01 11:28:33 -04:00
github-actions[bot]
f305b469f2 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:56 +00:00
github-actions[bot]
ea276f3e2e docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:42 +00:00
github-actions[bot]
15601e6ee4 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:28 +00:00
github-actions[bot]
44b14cab17 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:14 +00:00
Zhe Fang
0a97c56c77 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-01 10:26:58 -04:00
Zhe Fang
ac5dc5991f add using 2025-08-01 10:26:56 -04:00
github-actions[bot]
d22dc673e8 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:39:25 +00:00
github-actions[bot]
9eb2b83796 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:39:12 +00:00
github-actions[bot]
1cf6226a06 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:39:00 +00:00
github-actions[bot]
bce330a9e0 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:38:47 +00:00
Zhe Fang
687e286c2e Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-31 20:38:34 -04:00
Zhe Fang
fc05035053 fix 2025-07-31 20:38:32 -04:00
github-actions[bot]
f45f5ead01 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:21:01 +00:00
github-actions[bot]
0eeea77896 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:20:49 +00:00
github-actions[bot]
78ca7d8435 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:20:35 +00:00
github-actions[bot]
98d2ac404d docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:20:23 +00:00
Zhe Fang
b4b1ffd58e Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-31 20:20:09 -04:00
Zhe Fang
2522bd00ab fix #32 and some improvemnets 2025-07-31 20:20:08 -04:00
github-actions[bot]
0601039fcf docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:44:32 +00:00
github-actions[bot]
857b95e525 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:44:20 +00:00
github-actions[bot]
4be855f11a docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:44:07 +00:00
github-actions[bot]
9e1018770d docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:43:55 +00:00
Zhe Fang
1235a09d19 docs: Add warning to foobar2000 timeline issue in README 2025-07-30 18:43:54 -04:00
github-actions[bot]
1647c3a2f1 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:42:15 +00:00
github-actions[bot]
588838acaa docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:41:59 +00:00
github-actions[bot]
25c772434c docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:41:44 +00:00
github-actions[bot]
2c597a3b37 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:41:28 +00:00
Zhe Fang
80a44977a6 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-30 18:41:20 -04:00
Zhe Fang
0389fa6a56 docs: Update FAQ section link format and heading in README.md 2025-07-30 18:41:19 -04:00
github-actions[bot]
30ca476d8d docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:36:00 +00:00
github-actions[bot]
6439dee5ef docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:35:48 +00:00
github-actions[bot]
f6e5a24fe4 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:35:35 +00:00
Zhe Fang
77aad546bc Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-30 18:35:24 -04:00
github-actions[bot]
7fdbe664ba docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:35:23 +00:00
Zhe Fang
df76074ce9 docs: Remove outdated FAQ section and integrate relevant content into README.md 2025-07-30 18:35:23 -04:00
github-actions[bot]
31939630a3 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:14:23 +00:00
github-actions[bot]
50500626f8 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:14:11 +00:00
github-actions[bot]
fb1f0c8fc7 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:14:00 +00:00
Zhe Fang
9036b3be5f docs: Rearranged language badge section in README.md for better visibility 2025-07-30 18:13:49 -04:00
github-actions[bot]
51f840c0ef docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:13:49 +00:00
github-actions[bot]
b3a98cdaa2 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:58:24 +00:00
github-actions[bot]
7799a8dd94 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:58:12 +00:00
github-actions[bot]
e84d1faf71 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:58:02 +00:00
github-actions[bot]
66f0fd0f8f docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:57:51 +00:00
Zhe Fang
4f0dbe4836 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-30 17:57:53 -04:00
Zhe Fang
d7a53e360a Update README.md to enhance language support and clarify features 2025-07-30 17:57:51 -04:00
Zhe Fang
cb76341666 Create readme-translator.yml 2025-07-30 17:29:00 -04:00
Zhe Fang
537584e557 更新版本并优化窗口管理功能
在 `Package.appxmanifest` 中将版本号更新为 `1.0.32.0`。
在 `App.xaml.cs` 中优化 `OnLaunched` 方法,增加对 `lyricsWindow` 的空值检查。
在 `SystemTray.xaml` 中添加双击和左击命令以打开歌词窗口。
在 `SystemTrayViewModel.cs` 中新增 `OpenLyricsWindow` 方法,支持通过命令机制打开歌词窗口。
2025-07-30 16:50:41 -04:00
Zhe Fang
e06a50d8e8 Update releases-to-discord.yml 2025-07-28 19:39:13 -04:00
Zhe Fang
01776ed9a8 Update release-to-telegram.yml 2025-07-28 19:38:47 -04:00
Zhe Fang
1b7f53c055 Create release-to-telegram.yml 2025-07-28 19:34:59 -04:00
Zhe Fang
3233d35a05 Delete .github/workflows/telegram-notification.yml 2025-07-28 19:16:55 -04:00
Zhe Fang
ee52dafa0b Create telegram-notification.yml 2025-07-28 19:11:23 -04:00
Zhe Fang
03ddf42152 fix #30 2025-07-28 18:02:26 -04:00
Zhe Fang
bb4ee6bae7 Enhance image resizing logic to return original bytes if no scaling is needed 2025-07-28 16:27:36 -04:00
Zhe Fang
cb5a9f8042 fix #68 2025-07-28 15:55:18 -04:00
Zhe Fang
1d5d3bfa72 Update README files and enhance SettingsPageViewModel
- Improved the Chinese and English README files for clarity and consistency, including updates to feature descriptions and links.
- Added detailed support information for multiple music players in the README.
- Implemented the SettingsPageViewModel with properties and methods for managing application settings, including language, display options, and playback settings.
- Introduced partial methods for handling property changes in SettingsPageViewModel to ensure settings are saved and applied correctly.
- Enhanced the observable properties for better data binding and UI updates.
2025-07-28 12:10:02 -04:00
Zhe Fang
06379ebaba fix #37 2025-07-28 09:18:38 -04:00
Zhe Fang
45eb2a1506 Enhance application functionality to support single instance and multiple languages
Added single instance support in `App.xaml.cs` to ensure that only one instance of the application can run, and introduced `Mutex` and `EnsureSingleInstance()` methods. Updated multi-language support and added new string resources to serve users of different languages.

Refactored the play queue processing logic, using `PlayQueueItem` instead of `Track`, and introduced a new song tag information class in `SongsTabInfo.cs`. Updated UI components, replaced old image resources, and improved user experience.

In addition, removed the unused `BuildDate` property and simplified the logic of the settings page. Together, these changes improve the stability, usability, and maintainability of the application.
2025-07-27 23:27:04 -04:00
Zhe Fang
0e96c35d2d fix #62 2025-07-27 14:15:40 -04:00
Zhe Fang
152ecc7ff0 Update version and enhance play queue function
- Update the version number in `Package.appxmanifest` to `1.0.30.0`.
- Restore the event handler registration of unhandled exception in `App.xaml.cs`.
- Add `InsertRange` method in `CollectionHelper.cs`.
- Update the text entry in `Resources.resw` and change "play list" to "play queue".
- Add `SelectedTracks` property in `MusicGalleryViewModel.cs` to support multi-selection function.
- Update the data template of `MusicGalleryPage.xaml`, disable some buttons and add new buttons.
- Refactor the event handler in `MusicGalleryPage.xaml.cs` to support play queue logic and select all functions.
- Adjust the maximum value and step size of the slider in `SettingsPage.xaml`.
- Add `FindChildIndex` method in `ListViewHelper.cs`.
2025-07-26 23:04:49 -04:00
Zhe Fang
8b38aa1e33 fix #63 2025-07-26 07:51:27 -04:00
Zhe Fang
79e6995384 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-25 16:12:04 -04:00
Zhe Fang
1d6e19a718 Update localization resources and improve Music Gallery functionality
- Added new strings for file information and sorting options in Japanese, Korean, Simplified Chinese, and Traditional Chinese resource files.
- Removed unused property `IsMusicGalleryPageExpanded` from `LyricsWindowViewModel`.
- Enhanced `PlayTrack` method in `MusicGalleryViewModel` to handle null tracks more gracefully.
- Refactored `LyricsWindow` to remove overlay frame and simplify the UI.
- Updated `MusicGalleryPage` to bind new localized strings and improve layout.
- Implemented closing event for `MusicGalleryWindow` to manage resources effectively.
- Revised README files to update feedback group links and improve formatting.
2025-07-25 16:12:03 -04:00
Zhe Fang
23cb4b11a9 Update Crowdin configuration file 2025-07-25 11:30:32 -04:00
Zhe Fang
11461a615b Translate resource files and optimize XML schema
This code change mainly involves translating Chinese text in resource files into English to improve the internationalization support of the application. Specific changes include:
- Translate Chinese prompts, titles, and descriptions in the interface into English.
- Made minor adjustments to the XML schema, mainly adding spaces to comply with XML format standards.
- Added some new prompts for translation failures to ensure that users can get clear feedback when the translation service is not set up or the request fails.
2025-07-25 11:21:49 -04:00
Zhe Fang
e8f0359fb2 fix 2025-07-25 11:09:01 -04:00
Zhe Fang
eba81e8be3 fix 2025-07-25 11:07:34 -04:00
Zhe Fang
2e0d437b08 Merge pull request #57 from jayfunc/l10n_dev
New Crowdin updates
2025-07-25 11:06:46 -04:00
Zhe Fang
cbb0b87392 update readme 2025-07-25 09:54:25 -04:00
Zhe Fang
3f63043150 update readme enhance music gallery 2025-07-25 09:51:43 -04:00
Zhe Fang
77f2474562 add another way of noise byte generation 2025-07-24 12:51:46 -04:00
Zhe Fang
8aa5c392df New translations resources.resw (English) 2025-07-24 10:42:51 -04:00
Zhe Fang
a453f4862d New translations resources.resw (Chinese Traditional) 2025-07-24 10:42:49 -04:00
Zhe Fang
0e6702f3c3 New translations resources.resw (Korean) 2025-07-24 10:42:47 -04:00
Zhe Fang
412cdbdaf4 New translations resources.resw (Japanese) 2025-07-24 10:42:46 -04:00
Zhe Fang
02dbbf983c Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-24 10:37:48 -04:00
Zhe Fang
8cf9830644 add search function for music gallery 2025-07-24 10:37:46 -04:00
Zhe Fang
b04dfedf7e Merge pull request #56 from ZHider/dev
新增背景亚克力效果粗糙度调节功能,但未实装。
2025-07-24 10:36:57 -04:00
Ivand
3d4e3209e6 新增背景亚克力效果粗糙度调节功能,但未实装。
涉及文件修改说明:
- 接口层:ISettingsService.cs新增CoverAcrylicEffectAmount属性
- 服务层:SettingsService.cs实现亚克力效果参数存储
- 视图模型:LyricsRendererViewModel系列文件新增噪点贴图处理逻辑
- 设置页面:新增CoverAcrylicEffectAmount绑定属性及通知机制
- 字符串资源:新增多语言支持(en-US, ja-JP, ko-KR, zh-CN, zh-TW)
- XAML界面:SettingsPage.xaml新增调节滑块控件

Signed-off-by: Ivand <littlehider@gmail.com>
2025-07-24 10:52:26 +08:00
Zhe Fang
e92130af85 update readme 2025-07-23 14:14:12 -04:00
Zhe Fang
de3d6f1695 fix 2025-07-23 13:54:30 -04:00
Zhe Fang
da377838e8 fix 2025-07-22 22:29:34 -04:00
Zhe Fang
67cf6e47c8 fix 2025-07-22 21:15:32 -04:00
Zhe Fang
abf4c3498f Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-22 20:36:55 -04:00
Zhe Fang
ff8c85b2d0 fix playbackservice 2025-07-22 20:36:52 -04:00
Zhe Fang
8cbdb32931 Merge pull request #54 from jayfunc/l10n_dev
New Crowdin updates
2025-07-21 22:22:16 -04:00
Zhe Fang
757f9f4156 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-21 21:41:08 -04:00
Zhe Fang
c632f4b01a update readme 2025-07-21 21:41:07 -04:00
Zhe Fang
a843d0a0e3 New translations resources.resw (English) 2025-07-21 20:38:58 -04:00
Zhe Fang
905df92b05 New translations resources.resw (Chinese Traditional) 2025-07-21 19:38:04 -04:00
Zhe Fang
7445299537 New translations resources.resw (Chinese Simplified) 2025-07-21 19:38:03 -04:00
Zhe Fang
ba4958f837 New translations resources.resw (Korean) 2025-07-21 19:38:02 -04:00
Zhe Fang
8ca5245bf5 New translations resources.resw (Japanese) 2025-07-21 19:38:00 -04:00
Zhe Fang
89aa97552a Update Crowdin configuration file 2025-07-21 19:36:41 -04:00
Zhe Fang
aa692e2735 fix auto transparent issue after restart and lock when hover on non-transparent window 2025-07-21 17:17:57 -04:00
Zhe Fang
c7ee26f284 fix timeline update strategy 2025-07-21 11:29:52 -04:00
Zhe Fang
b103e6efd1 update readme 2025-07-21 10:28:44 -04:00
Zhe Fang
16cd12e22b fix 2025-07-21 09:51:14 -04:00
Zhe Fang
34bdbc89bc fix #50 2025-07-20 23:28:37 -04:00
Zhe Fang
b649e9761d update readme 2025-07-19 14:50:47 -04:00
Zhe Fang
a9807f4f09 fix #41 2025-07-19 14:33:45 -04:00
Zhe Fang
def287715d fix 2025-07-19 12:54:20 -04:00
Zhe Fang
966f926112 fix #47 fix #44 2025-07-19 08:30:29 -04:00
Zhe Fang
4568293b51 update readme 2025-07-18 11:05:51 -04:00
Zhe Fang
10115ab0a8 update readme 2025-07-18 11:00:42 -04:00
Zhe Fang
ecefaedcb9 update readme 2025-07-17 21:06:15 -04:00
Zhe Fang
b9aae0866d update readme 2025-07-17 21:05:25 -04:00
Zhe Fang
afbfcc921e update readme 2025-07-17 20:55:53 -04:00
Zhe Fang
8f997ca3d9 fix #42 2025-07-17 14:45:00 -04:00
Zhe Fang
042d557eb1 fix #40 fix #14 2025-07-17 14:29:47 -04:00
Zhe Fang
153679228d fix playbackservice 2025-07-17 13:04:50 -04:00
Zhe Fang
5a4fe54ac2 update webhook username 2025-07-17 10:42:12 -04:00
Zhe Fang
14e8d88cd1 update webhook logo 2025-07-17 10:39:32 -04:00
Zhe Fang
79e726db33 fix closing issue 2025-07-17 10:37:59 -04:00
Zhe Fang
d4f1949833 update doc 2025-07-17 09:02:26 -04:00
Zhe Fang
6135f996bf update readme 2025-07-17 08:49:08 -04:00
Zhe Fang
5b97621af4 fix for dock mode (color sampling issue for bottom position) 2025-07-17 08:48:02 -04:00
Zhe Fang
0f084d04f8 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-17 07:52:14 -04:00
Zhe Fang
bcb114a171 fix #39 2025-07-17 07:52:13 -04:00
Zhe Fang
7bbe2a3045 Update releases-to-discord.yml 2025-07-16 21:37:45 -04:00
Zhe Fang
fab4e8fe22 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-16 21:21:40 -04:00
Zhe Fang
bc4a06577e fix 2025-07-16 21:21:38 -04:00
Zhe Fang
22a790fff0 Update FUNDING.yml 2025-07-16 21:19:39 -04:00
Zhe Fang
5e3d0cb78f Create FUNDING.yml 2025-07-16 21:15:20 -04:00
Zhe Fang
59ee2a6fc3 fix #25 2025-07-16 20:23:01 -04:00
Zhe Fang
fd5e752b43 fix #33 2025-07-16 19:22:47 -04:00
Zhe Fang
df31d03da7 fix #34 fix #28 update faq 2025-07-16 16:22:34 -04:00
Zhe Fang
a7ebba4a62 fix #26 fix #27 fix #29 2025-07-16 07:33:43 -04:00
Zhe Fang
6ca1a84a54 update doc 2025-07-14 20:26:58 -04:00
Zhe Fang
bab5a827f6 update readme 2025-07-14 20:10:17 -04:00
Zhe Fang
3b1e0389aa fix #13 , fix #20 , fix #21 , fix #24 2025-07-13 20:32:10 -04:00
Zhe Fang
ae05a55d31 update doc 2025-07-11 21:55:25 -04:00
Zhe Fang
4b6d417db3 update docs 2025-07-11 21:48:16 -04:00
Zhe Fang
86118bac02 Rename github-releases-to-discord.yml to releases-to-discord.yml 2025-07-11 19:47:19 -04:00
Zhe Fang
7bfbec4b01 Update and rename notify-discord.yml to github-releases-to-discord.yml 2025-07-11 19:41:07 -04:00
Zhe Fang
a5d6dd1305 Create notify-discord.yml 2025-07-11 19:20:52 -04:00
Zhe Fang
68f690e1a7 update doc 2025-07-11 19:00:43 -04:00
Zhe Fang
34d7f3f319 fix 2025-07-11 18:08:05 -04:00
Zhe Fang
07b82191d0 change language detection model 2025-07-10 23:08:29 -04:00
Zhe Fang
f8c6060d32 fix #17 2025-07-09 20:11:01 -04:00
Zhe Fang
bfdb36ff95 init docs 2025-07-09 13:13:23 -04:00
Zhe Fang
ce83777c1d Delete docs directory 2025-07-09 12:56:56 -04:00
Zhe Fang
d709e70fa2 Create jekyll-gh-pages.yml 2025-07-09 12:53:33 -04:00
Zhe Fang
8fe4f8fd58 add docs 2025-07-09 12:46:06 -04:00
Zhe Fang
b6319e522a fix #16 2025-07-09 10:29:28 -04:00
Zhe Fang
58d74c1515 fix #15 2025-07-09 09:37:38 -04:00
Zhe Fang
806f3fdd63 fix #11 #12 update readme 2025-07-09 09:19:48 -04:00
Zhe Fang
90d2055dff update readme 2025-07-07 20:32:48 -04:00
219 changed files with 343042 additions and 5600 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: [jayfunc]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: founchoo
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

18
.github/workflows/issues-translator.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: false
# not require, default false, . Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿
# not require. Customize the translation robot prefix message.

View File

@@ -0,0 +1,28 @@
name: Notify Telegram on GitHub Release
on:
release:
types: [published]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send release info to Telegram
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
RELEASE_URL: ${{ github.event.release.html_url }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_NAME: ${{ github.event.release.name }}
RELEASE_BODY: ${{ github.event.release.body }}
run: |
TEXT="🚀 *New Release:* ${RELEASE_TAG} - ${RELEASE_NAME}%0A"
TEXT+="📝 *Description:*%0A${RELEASE_BODY}%0A"
TEXT+="🔗 [View on GitHub](${RELEASE_URL})"
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d chat_id="${CHAT_ID}" \
-d message_thread_id="${THREAD_ID}" \
-d text="${TEXT}"

View File

@@ -0,0 +1,22 @@
name: Notify Discord on GitHub Release
on:
release:
types: [published]
jobs:
github-releases-to-discord:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: GitHub Releases to Discord
uses: SethCohen/github-releases-to-discord@v1
with:
webhook_url: ${{ secrets.WEBHOOK_URL }}
color: "2105893"
username: "GitHub"
avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
content: "||@everyone||"
footer_title: "Changelog"
reduce_headings: true

1
.gitignore vendored
View File

@@ -406,3 +406,4 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LastFM.cs

View File

@@ -1,149 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x86">
<Configuration>Debug</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x86">
<Configuration>Release</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />
<Content Include="Images\LargeTile.scale-200.png" />
<Content Include="Images\LargeTile.scale-400.png" />
<Content Include="Images\SmallTile.scale-100.png" />
<Content Include="Images\SmallTile.scale-125.png" />
<Content Include="Images\SmallTile.scale-150.png" />
<Content Include="Images\SmallTile.scale-200.png" />
<Content Include="Images\SmallTile.scale-400.png" />
<Content Include="Images\SplashScreen.scale-100.png" />
<Content Include="Images\SplashScreen.scale-125.png" />
<Content Include="Images\SplashScreen.scale-150.png" />
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\SplashScreen.scale-400.png" />
<Content Include="Images\Square150x150Logo.scale-100.png" />
<Content Include="Images\Square150x150Logo.scale-125.png" />
<Content Include="Images\Square150x150Logo.scale-150.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.scale-100.png" />
<Content Include="Images\Square44x44Logo.scale-125.png" />
<Content Include="Images\Square44x44Logo.scale-150.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
<Content Include="Images\StoreLogo.scale-100.png" />
<Content Include="Images\StoreLogo.scale-125.png" />
<Content Include="Images\StoreLogo.scale-150.png" />
<Content Include="Images\StoreLogo.scale-200.png" />
<Content Include="Images\StoreLogo.scale-400.png" />
<Content Include="Images\Wide310x150Logo.scale-100.png" />
<Content Include="Images\Wide310x150Logo.scale-125.png" />
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x86">
<Configuration>Debug</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x86">
<Configuration>Release</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />
<Content Include="Images\LargeTile.scale-200.png" />
<Content Include="Images\LargeTile.scale-400.png" />
<Content Include="Images\SmallTile.scale-100.png" />
<Content Include="Images\SmallTile.scale-125.png" />
<Content Include="Images\SmallTile.scale-150.png" />
<Content Include="Images\SmallTile.scale-200.png" />
<Content Include="Images\SmallTile.scale-400.png" />
<Content Include="Images\SplashScreen.scale-100.png" />
<Content Include="Images\SplashScreen.scale-125.png" />
<Content Include="Images\SplashScreen.scale-150.png" />
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\SplashScreen.scale-400.png" />
<Content Include="Images\Square150x150Logo.scale-100.png" />
<Content Include="Images\Square150x150Logo.scale-125.png" />
<Content Include="Images\Square150x150Logo.scale-150.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.scale-100.png" />
<Content Include="Images\Square44x44Logo.scale-125.png" />
<Content Include="Images\Square44x44Logo.scale-150.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
<Content Include="Images\StoreLogo.scale-100.png" />
<Content Include="Images\StoreLogo.scale-125.png" />
<Content Include="Images\StoreLogo.scale-150.png" />
<Content Include="Images\StoreLogo.scale-200.png" />
<Content Include="Images\StoreLogo.scale-400.png" />
<Content Include="Images\Wide310x150Logo.scale-100.png" />
<Content Include="Images\Wide310x150Logo.scale-125.png" />
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
<EnableMsixTooling>true</EnableMsixTooling>
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
</Project>

View File

@@ -12,7 +12,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.9.0" />
Version="1.0.45.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -12,6 +12,7 @@
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
@@ -47,6 +48,11 @@
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
@@ -64,7 +70,7 @@
</Style>
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Margin" Value="0" />
@@ -77,11 +83,14 @@
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
<Style
x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}"
TargetType="ToggleButton">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
@@ -91,11 +100,212 @@
<Setter Property="Padding" Value="8" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="CardGridStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
</Style>
<Style x:Key="GhostSliderStyle" TargetType="Slider">
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="0,1,1,0" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter
x:Name="HeaderContentPresenter"
Grid.Row="0"
Margin="{ThemeResource SliderTopHeaderMargin}"
x:DeferLoadStrategy="Lazy"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
Foreground="{ThemeResource SliderHeaderForeground}"
TextWrapping="Wrap"
Visibility="Collapsed" />
<Grid
x:Name="SliderContainer"
Grid.Row="1"
Background="{ThemeResource SliderContainerBackground}"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
</Grid.RowDefinitions>
<Rectangle
x:Name="HorizontalTrackRect"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
Fill="{TemplateBinding Background}" />
<Rectangle
x:Name="HorizontalDecreaseRect"
Grid.Row="1"
Fill="{TemplateBinding Foreground}" />
<TickBar
x:Name="TopTickBar"
Grid.ColumnSpan="3"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,0,0,4"
VerticalAlignment="Bottom"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="HorizontalInlineTickBar"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="BottomTickBar"
Grid.Row="2"
Grid.ColumnSpan="3"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,4,0,0"
VerticalAlignment="Top"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb
x:Name="HorizontalThumb"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
Width="2"
Height="2"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}"
FocusVisualMargin="-14,-6,-14,-6"
Style="{StaticResource SliderThumbStyle}" />
</Grid>
<Grid
x:Name="VerticalTemplate"
MinWidth="{ThemeResource SliderVerticalWidth}"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
</Grid.ColumnDefinitions>
<Rectangle
x:Name="VerticalTrackRect"
Grid.RowSpan="3"
Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{TemplateBinding Background}" />
<Rectangle
x:Name="VerticalDecreaseRect"
Grid.Row="2"
Grid.Column="1"
Fill="{TemplateBinding Foreground}" />
<TickBar
x:Name="LeftTickBar"
Grid.RowSpan="3"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="0,0,4,0"
HorizontalAlignment="Right"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="VerticalInlineTickBar"
Grid.RowSpan="3"
Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar
x:Name="RightTickBar"
Grid.RowSpan="3"
Grid.Column="2"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Margin="4,0,0,0"
HorizontalAlignment="Left"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb
x:Name="VerticalThumb"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Width="24"
Height="8"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}"
FocusVisualMargin="-6,-14,-6,-14"
Style="{StaticResource SliderThumbStyle}" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ListViewStretchedItemContainerStyle" TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
<!-- Dimensions -->
<!-- Fonts -->
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -3,20 +3,33 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;
using ShadowViewer.Controls;
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3
{
@@ -33,6 +46,8 @@ namespace BetterLyrics.WinUI3
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
private static Mutex? _instanceMutex;
public App()
{
this.InitializeComponent();
@@ -41,8 +56,10 @@ namespace BetterLyrics.WinUI3
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
ResourceLoader = new ResourceLoader();
EnsureSingleInstance();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
AppInfo.EnsureDirectories();
PathHelper.EnsureDirectories();
ConfigureServices();
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
@@ -53,30 +70,35 @@ namespace BetterLyrics.WinUI3
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
private void EnsureSingleInstance()
{
bool createdNew;
_instanceMutex = new Mutex(true, Constants.App.AppName, out createdNew);
if (!createdNew)
{
User32.MessageBox(HWND.NULL, ResourceLoader!.GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL);
Environment.Exit(0);
}
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHelper.OpenOrShowWindow<LyricsWindow>();
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow == null) return;
WindowHelper.OpenWindow<LyricsWindow>();
string[] commandLineArguments = Environment.GetCommandLineArgs();
if (commandLineArguments.Length > 1)
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
commandLineArguments = commandLineArguments.Skip(1).ToArray();
if (commandLineArguments.First() == AppInfo.UnlockWindowTag)
{
lyricsWindow.AutoSelectLyricsMode(AutoStartWindowType.DesktopMode, false);
return;
}
lyricsWindow.ViewModel.InitLockHotKey();
lyricsWindow.AutoSelectLyricsMode();
}
lyricsWindow.AutoSelectLyricsMode();
}
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
.CreateLogger();
// Register services
@@ -89,16 +111,19 @@ namespace BetterLyrics.WinUI3
})
// Services
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IPlaybackService, PlaybackService>()
.AddSingleton<IMusicSearchService, MusicSearchService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ILibreTranslateService, LibreTranslateService>()
.AddSingleton<ITranslateService, TranslateService>()
.AddSingleton<ILastFMService, LastFMService>()
// ViewModels
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<MusicGalleryViewModel>()
.AddSingleton<LyricsRendererViewModel>()
.BuildServiceProvider()
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,116 +1,226 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ViewModels\Lyrics\**" />
<Content Remove="ViewModels\Lyrics\**" />
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
<None Remove="ViewModels\Lyrics\**" />
<Page Remove="ViewModels\Lyrics\**" />
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="iTunesSearch" Version="1.0.44" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--Disable Trimming for Specific Packages-->
<ItemGroup>
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Page Update="Views\SettingsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\SystemTray.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants)</DefineConstants>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Logo.ico</ApplicationIcon>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ShouldCreateLogs>True</ShouldCreateLogs>
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
<UpdatePackageVersion>True</UpdatePackageVersion>
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
<Version>2025.6.0</Version>
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
<FileVersion>2025.6.18.0110</FileVersion>
</PropertyGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ViewModels\Lyrics\**" />
<Content Remove="ViewModels\Lyrics\**" />
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
<None Remove="ViewModels\Lyrics\**" />
<Page Remove="ViewModels\Lyrics\**" />
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Assets\Wiki82.profile.xml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
<PackageReference Include="ColorThief.ImageSharp" Version="1.0.0" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.MetadataControl" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="7.2.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Hqub.Lastfm">
<HintPath>..\..\..\Last.fm\src\Hqub.Lastfm\bin\Release\netstandard2.0\Hqub.Lastfm.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--Disable Trimming for Specific Packages-->
<ItemGroup>
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AppleMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Chrome.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Discord.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Edge.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyBox.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyState.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\iTunes.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\KugouMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LastFM.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Leaf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LXMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MediaPlayerWindows11.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MusicBee.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQ.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Spotify.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Telegram.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Wiki82.profile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\SettingsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\SystemTray.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants)</DefineConstants>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Logo.ico</ApplicationIcon>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ShouldCreateLogs>True</ShouldCreateLogs>
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
<UpdatePackageVersion>True</UpdatePackageVersion>
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
<Version>2025.6.0</Version>
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
<FileVersion>2025.6.18.0110</FileVersion>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class AmllTTmlDB
{
private const string BaseUrl = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/";
public const string QueryPrefix = $"{BaseUrl}raw-lyrics/";
public const string Index = $"{BaseUrl}metadata/raw-lyrics-index.jsonl";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class App
{
public const string AppAuthor = "Zhe Fang";
public const string AppName = "BetterLyrics";
public const string AutoStartupTaskId = "AutoStartup";
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class LXMusic
{
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration";
}
}

View File

@@ -0,0 +1,9 @@
namespace BetterLyrics.WinUI3.Constants
{
public static class LastFM
{
public const string ApiKey = "Your api key here";
public const string SharedSecret = "Your shared secret here";
public const string UnAuthUrl = "https://www.last.fm/settings/applications";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class Link
{
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
namespace BetterLyrics.WinUI3.Constants
{
public static class PlayerID
{
public const string LXMusic = "cn.toside.music.desktop";
public const string MediaPlayerWindows11 = "Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic";
public const string AIMP = "AIMP.exe";
public const string Foobar2000 = "foobar2000.exe";
public const string MusicBee = "MusicBee.exe";
public const string PotPlayer = "PotPlayerMini64.exe";
public const string Spotify = "Spotify.exe";
public const string AppleMusic = "AppleInc.AppleMusicWin_nzyj5cx40ttqa!App";
public const string NetEaseCloudMusic = "cloudmusic.exe";
public const string KugouMusic = "kugou";
public const string QQMusic = "QQMusic.exe";
public const string iTunes = "49586DaveAntoine.MediaControllerforiTunes_9bzempp7dntjg!App";
public const string Chrome = "Chrome";
public const string Edge = "MSEdge";
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public class PlayerName
{
public const string LXMusic = "LX Music";
public const string MediaPlayerWindows11 = "Media Player";
public const string AIMP = "AIMP";
public const string Foobar2000 = "foobar2000";
public const string MusicBee = "MusicBee";
public const string PotPlayer = "PotPlayer";
public const string Spotify = "Spotify";
public const string AppleMusic = "Apple Music";
public const string NetEaseCloudMusic = "网易云音乐";
public const string KugouMusic = "酷狗音乐";
public const string QQMusic = "QQ 音乐";
public const string iTunes = "iTunes";
public const string Chrome = "Google Chrome";
public const string Edge = "Microsoft Edge";
public const string BetterLyrics = "BetterLyrics";
public const string BetterLyricsDebug = "BetterLyrics (Debug)";
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class Time
{
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(300);
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class iTunes
{
public const string QueryPrefix = "https://itunes.apple.com/search?";
}
}

View File

@@ -14,7 +14,9 @@
x:Name="TrayIcon"
x:FieldModifier="public"
ContextMenuMode="SecondWindow"
DoubleClickCommand="{x:Bind ViewModel.OpenLyricsWindowCommand}"
IconSource="ms-appx:///Assets/Logo.ico"
LeftClickCommand="{x:Bind ViewModel.OpenLyricsWindowCommand}"
NoLeftClickDelay="True"
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
<tb:TaskbarIcon.ContextFlyout>
@@ -22,7 +24,10 @@
AreOpenCloseAnimationsEnabled="True"
LightDismissOverlayMode="On"
ShowMode="TransientWithDismissOnPointerMoveAway">
<MenuFlyoutItem x:Uid="SystemTrayMusicGallery" Command="{x:Bind ViewModel.OpenMusicGalleryCommand}" />
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayResetWindowPosition" Command="{x:Bind ViewModel.ResetWindowPositionCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayRestart" Command="{x:Bind ViewModel.RestartAppCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
<MenuFlyoutItem
x:Uid="SystemTrayUnlock"

View File

@@ -0,0 +1,33 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is AlbumArtSearchProvider provider)
{
return provider switch
{
AlbumArtSearchProvider.Local => App.ResourceLoader!.GetString("AlbumArtSearchLocalProvider"),
AlbumArtSearchProvider.SMTC => App.ResourceLoader!.GetString("AlbumArtSearchSMTCProvider"),
AlbumArtSearchProvider.iTunes => "iTunes",
_ => throw new Exception($"Unknown AlbumArtSearchProvider: {provider}"),
};
}
throw new ArgumentException("Value must be of type AlbumArtSearchProvider", nameof(value));
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -3,7 +3,7 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
internal partial class CornerRadiusToDoubleConverter : IValueConverter
public partial class CornerRadiusToDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{

View File

@@ -5,7 +5,7 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
internal partial class EnumToIntConverter : IValueConverter
public partial class EnumToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{

View File

@@ -14,19 +14,19 @@ namespace BetterLyrics.WinUI3.Converter
{
return provider switch
{
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString("LyricsSearchProviderLrcLib"),
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString("LyricsSearchProviderQQ"),
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString("LyricsSearchProviderNetease"),
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString("LyricsSearchProviderKugou"),
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString("LyricsSearchProviderAmllTtmlDb"),
LyricsSearchProvider.LrcLib => "LrcLib",
LyricsSearchProvider.QQ => "QQ 音乐",
LyricsSearchProvider.Netease => "网易云音乐",
LyricsSearchProvider.Kugou => "酷狗音乐",
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
_ => "",
_ => "N/A",
};
}
return "";
return "N/A";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -0,0 +1,47 @@
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MediaSourceProviderToDisplayedNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string provider)
{
return provider switch
{
PlayerID.Spotify => PlayerName.Spotify,
PlayerID.AppleMusic => PlayerName.AppleMusic,
PlayerID.iTunes => PlayerName.iTunes,
PlayerID.KugouMusic => PlayerName.KugouMusic,
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
PlayerID.QQMusic => PlayerName.QQMusic,
PlayerID.LXMusic => PlayerName.LXMusic,
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
PlayerID.AIMP => PlayerName.AIMP,
PlayerID.Foobar2000 => PlayerName.Foobar2000,
PlayerID.MusicBee => PlayerName.MusicBee,
PlayerID.PotPlayer => PlayerName.PotPlayer,
PlayerID.Chrome => PlayerName.Chrome,
PlayerID.Edge => PlayerName.Edge,
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
_ => provider,
};
}
return value?.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,47 @@
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MediaSourceProviderToLogoUriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string provider)
{
return provider switch
{
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
PlayerID.iTunes => PathHelper.iTunesLogoPath,
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
PlayerID.AIMP => PathHelper.AIMPLogoPath,
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
PlayerID.Chrome => PathHelper.ChromeLogoPath,
PlayerID.Edge => PathHelper.EdgeLogoPath,
PlayerID.BetterLyrics => PathHelper.LogoPath,
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
_ => PathHelper.UnknownPlayerLogoPath,
};
}
return PathHelper.UnknownPlayerLogoPath;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public partial class SecondsToFormattedTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is double seconds)
{
return TimeSpan.FromSeconds(seconds).ToString(@"mm\:ss");
}
else if (value is int secondsInt)
{
return TimeSpan.FromSeconds(secondsInt).ToString(@"mm\:ss");
}
return value?.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,38 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
public partial class TranslationSearchProviderToDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TranslationSearchProvider provider)
{
return provider switch
{
TranslationSearchProvider.LrcLib => "LrcLib",
TranslationSearchProvider.QQ => "QQ",
TranslationSearchProvider.Netease => "Netease",
TranslationSearchProvider.Kugou => "Kugou",
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
TranslationSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
TranslationSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
TranslationSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
TranslationSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
TranslationSearchProvider.LibreTranslate => "LibreTranslate",
_ => "N/A",
};
}
return "N/A";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum AlbumArtSearchProvider
{
Local,
SMTC,
iTunes,
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum CommonSongProperty
{
Title,
Album,
Artist
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum DockPlacement
{
Top,
Bottom
}
public static class DockPlacementExtensions
{
public static WindowPixelSampleMode ToWindowPixelSampleMode(this DockPlacement placement)
{
return placement switch
{
DockPlacement.Top => WindowPixelSampleMode.BelowWindow,
DockPlacement.Bottom => WindowPixelSampleMode.AboveWindow,
_ => throw new ArgumentOutOfRangeException(nameof(placement), placement, null)
};
}
}
}

View File

@@ -4,7 +4,8 @@ namespace BetterLyrics.WinUI3.Enums
{
public enum LineRenderingType
{
UntilCurrentChar,
CurrentCharOnly,
CurrentChar,
LineStartToCurrentChar,
CurrentLine
}
}

View File

@@ -7,6 +7,5 @@ namespace BetterLyrics.WinUI3.Enums
AlbumArtOnly,
LyricsOnly,
SplitView,
PlaceholderOnly,
}
}

View File

@@ -19,8 +19,11 @@ namespace BetterLyrics.WinUI3.Enums
if (string.IsNullOrWhiteSpace(content))
return null;
// TTML
if (content.StartsWith("<?xml") && System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b"))
// TTML: 检查 <tt ... xmlns="http://www.w3.org/ns/ttml"
if (System.Text.RegularExpressions.Regex.IsMatch(
content,
@"<tt\b[^>]*\bxmlns\s*=\s*[""']http://www\.w3\.org/ns/ttml[""']",
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
{
return LyricsFormat.Ttml;
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsLayoutOrientation
{
Horizontal,
Vertical,
}
}

View File

@@ -23,11 +23,11 @@ namespace BetterLyrics.WinUI3.Enums
{
return provider switch
{
LyricsSearchProvider.LrcLib => AppInfo.LrcLibLyricsCacheDirectory,
LyricsSearchProvider.QQ => AppInfo.QQLyricsCacheDirectory,
LyricsSearchProvider.Netease => AppInfo.NeteaseLyricsCacheDirectory,
LyricsSearchProvider.Kugou => AppInfo.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => AppInfo.AmllTtmlDbLyricsCacheDirectory,
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
};
}
@@ -61,5 +61,22 @@ namespace BetterLyrics.WinUI3.Enums
{
return !provider.IsLocal();
}
public static TranslationSearchProvider? ToTranslationSearchProvider(this LyricsSearchProvider? provider)
{
return provider switch
{
LyricsSearchProvider.LrcLib => TranslationSearchProvider.LrcLib,
LyricsSearchProvider.QQ => TranslationSearchProvider.QQ,
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
_ => null,
};
}
}
}

View File

@@ -1,10 +0,0 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
public enum MusicSearchMatchMode
{
TitleAndArtist,
TitleArtistAlbumAndDuration,
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum PlaybackOrder
{
RepeatAll,
RepeatOne,
Shuffle,
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum TranslationSearchProvider
{
QQ,
Kugou,
Netease,
LrcLib,
AmllTtmlDb,
LocalMusicFile,
LocalLrcFile,
LocalEslrcFile,
LocalTtmlFile,
LibreTranslate,
}
}

View File

@@ -1,8 +1,9 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum WindowColorSampleMode
public enum WindowPixelSampleMode
{
BelowWindow,
AboveWindow,
WindowArea,
WindowEdge,
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
{
public byte[]? Bytes { get; set; } = bytes;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
public Color? AlbumArtLightAccentColor { get; set; } = albumArtLightAccentColor;
public Color? AlbumArtDarkAccentColor { get; set; } = albumArtDarkAccentColor;
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class LastFMIsAuthenticatedChangedEventArgs : EventArgs
{
public bool IsAuthenticated { get; set; }
public LastFMIsAuthenticatedChangedEventArgs(bool isAuthenticated)
{
IsAuthenticated = isAuthenticated;
}
}
}

View File

@@ -0,0 +1,18 @@
using Hqub.Lastfm.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class LastFMUserChangedEventArgs : EventArgs
{
public User? User { get; set; }
public LastFMUserChangedEventArgs(User? user)
{
User = user;
}
}
}

View File

@@ -4,8 +4,9 @@ using System;
namespace BetterLyrics.WinUI3.Events
{
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
public class TimelineChangedEventArgs(TimeSpan position, TimeSpan end) : EventArgs()
{
public TimeSpan Position { get; set; } = position;
public TimeSpan End { get; set; } = end;
}
}

View File

@@ -1,115 +0,0 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Helper
{
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.FileProperties;
public static class AppInfo
{
public const string AppAuthor = "Zhe Fang";
public const string AppDisplayName = "Better Lyrics";
public const string AppName = "BetterLyrics";
public static string AppVersion
{
get
{
var version = Package.Current.Id.Version;
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
}
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
public const string UnlockWindowTag = "UnlockWindow";
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string iTunesAlbumArtCacheDirectory => Path.Combine(CacheFolder, "itunes-album-art");
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static void EnsureDirectories()
{
Directory.CreateDirectory(LocalFolder);
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
Directory.CreateDirectory(QQLyricsCacheDirectory);
Directory.CreateDirectory(KugouLyricsCacheDirectory);
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}
public static async Task<DateTime> GetBuildDate()
{
var assembly = Assembly.GetExecutingAssembly();
var filePath = assembly.Location;
if (!File.Exists(filePath))
return DateTime.MinValue;
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
// 获取文件基本属性
BasicProperties props = await file.GetBasicPropertiesAsync();
// 返回修改日期
return props.DateModified.DateTime;
}
public static List<LanguageInfo> GetAllTranslationLanguagesInfo() =>
[
new LanguageInfo("ar", "العربية"),
new LanguageInfo("az", "Azərbaycan dili"),
new LanguageInfo("zh", "中文"),
new LanguageInfo("cs", "Čeština"),
new LanguageInfo("da", "Dansk"),
new LanguageInfo("nl", "Nederlands"),
new LanguageInfo("en", "English"),
new LanguageInfo("eo", "Esperanto"),
new LanguageInfo("fi", "Suomi"),
new LanguageInfo("fr", "Français"),
new LanguageInfo("de", "Deutsch"),
new LanguageInfo("el", "Ελληνικά"),
new LanguageInfo("he", "עברית"),
new LanguageInfo("hi", "हिन्दी"),
new LanguageInfo("hu", "Magyar"),
new LanguageInfo("id", "Bahasa Indonesia"),
new LanguageInfo("ga", "Gaeilge"),
new LanguageInfo("it", "Italiano"),
new LanguageInfo("ja", "日本語"),
new LanguageInfo("ko", "한국어"),
new LanguageInfo("fa", "فارسی"),
new LanguageInfo("pl", "Polski"),
new LanguageInfo("pt", "Português"),
new LanguageInfo("ru", "Русский"),
new LanguageInfo("sk", "Slovenčina"),
new LanguageInfo("es", "Español"),
new LanguageInfo("sv", "Svenska"),
new LanguageInfo("tr", "Türkçe"),
new LanguageInfo("uk", "Українська"),
new LanguageInfo("vi", "Tiếng Việt"),
];
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.UI.Windowing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class AppWindowHelper
{
public static void SetIcons(this AppWindow appWindow)
{
appWindow.SetIcon(PathHelper.LogoPath);
appWindow.SetTaskbarIcon(PathHelper.LogoPath);
appWindow.SetTitleBarIcon(PathHelper.LogoPath);
}
}
}

View File

@@ -1,15 +1,51 @@
// 2025/6/23 by Zhe Fang
using ATL;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class CollectionHelper
{
public static T? SafeGet<T>(this IList<T> list, int index)
public static ObservableCollection<GroupInfoList> GetGroupedBy<T>(
this IEnumerable<T> items,
Func<T, object> groupKeySelector,
Func<object, object>? orderSelector = null)
{
if (list == null || index < 0 || index >= list.Count) return default;
return list[index];
var query = from item in items
group item by groupKeySelector(item) into g
orderby g.Key
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
return new ObservableCollection<GroupInfoList>(query);
}
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
{
if (collection == null) return;
if (items == null) return;
foreach (var item in items)
{
collection.Add(item);
}
}
public static void InsertRange<T>(this IList<T> list, int index, IEnumerable<T> items)
{
if (list == null) return;
if (items == null) return;
if (index < 0 || index > list.Count) return;
foreach (var item in items)
{
list.Insert(index++, item);
}
}
}
}

View File

@@ -1,10 +1,17 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using Vanara.PInvoke;
using Windows.UI;
using Color = Windows.UI.Color;
namespace BetterLyrics.WinUI3.Helper
{
public static class ColorHelper
@@ -98,5 +105,147 @@ namespace BetterLyrics.WinUI3.Helper
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
}
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
{
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
switch (mode)
{
case WindowPixelSampleMode.BelowWindow:
{
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.AboveWindow:
{
int sampleHeight = 1;
int sampleY = myRect.Top - 1;
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.WindowArea:
{
int width = myRect.Right - myRect.Left;
int height = myRect.Bottom - myRect.Top;
if (width <= 0 || height <= 0)
return System.Drawing.Color.Transparent;
// 采集窗口区域的平均色
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
}
case WindowPixelSampleMode.WindowEdge:
{
int width = myRect.Right - myRect.Left;
int height = myRect.Bottom - myRect.Top;
if (width <= 0 || height <= 0)
return System.Drawing.Color.Transparent;
var edgeThickness = new Thickness(36, 0, 36, 0);
List<System.Drawing.Color> edgeColors = [];
// Top edge
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top,
width,
(int)edgeThickness.Top
)
);
// Bottom edge
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Bottom - (int)edgeThickness.Bottom,
width,
(int)edgeThickness.Bottom
)
);
// Left edge
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Left,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
// Right edge
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Right - (int)edgeThickness.Right,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Right,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
// 合并四边平均色
if (edgeColors.Count == 0)
return System.Drawing.Color.Transparent;
long r = 0,
g = 0,
b = 0;
foreach (var c in edgeColors)
{
r += c.R;
g += c.G;
b += c.B;
}
return System.Drawing.Color.FromArgb(
255,
(int)(r / edgeColors.Count),
(int)(g / edgeColors.Count),
(int)(b / edgeColors.Count)
);
}
default:
return System.Drawing.Color.Transparent;
}
}
private static System.Drawing.Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
{
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
using Graphics gDest = Graphics.FromImage(bmp);
IntPtr hdcDest = gDest.GetHdc();
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
gDest.ReleaseHdc(hdcDest);
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
return ComputeAverageColor(bmp);
}
private static System.Drawing.Color ComputeAverageColor(Bitmap bmp)
{
long r = 0, g = 0, b = 0;
int count = 0;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
System.Drawing.Color pixel = bmp.GetPixel(x, y);
r += pixel.R;
g += pixel.G;
b += pixel.B;
count++;
}
}
if (count == 0) return System.Drawing.Color.Transparent;
return System.Drawing.Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
}
}
}

View File

@@ -1,9 +1,12 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
using WinRT.Interop;
@@ -15,7 +18,6 @@ namespace BetterLyrics.WinUI3.Helper
{
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
@@ -47,13 +49,6 @@ namespace BetterLyrics.WinUI3.Helper
_originalWindowBounds.Remove(hwnd);
}
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
{
window.SetWindowStyle(style);
_originalWindowStyles.Remove(hwnd);
}
window.SetIsShownInSwitchers(true);
}
@@ -80,13 +75,14 @@ namespace BetterLyrics.WinUI3.Helper
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
new Windows.Graphics.RectInt32(
targetX,
targetY,
targetWidth,
targetHeight
)
);
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
if (!_originalWindowStyles.ContainsKey(hwnd))
_originalWindowStyles[hwnd] = window.GetWindowStyle();
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
if (!_originalTopmostStates.ContainsKey(hwnd))
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
@@ -97,48 +93,30 @@ namespace BetterLyrics.WinUI3.Helper
window.SetIsShownInSwitchers(false);
}
public static void Lock(Window window)
{
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD>͸<EFBFBD><CDB8>
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
window.ExtendsContentIntoTitleBar = false;
SetClickThrough(window, true);
}
public static void SetClickThrough(Window window, bool enable)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
if (enable)
{
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
if (!_originalWindowStyles.ContainsKey(hwnd))
_originalWindowStyles[hwnd] = window.GetWindowStyle();
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
_clickThroughStates[hwnd] = true;
}
else
{
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
_clickThroughStates[hwnd] = false;
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
{
window.SetWindowStyle(style);
_originalWindowStyles.Remove(hwnd);
}
}
}
public static void Unlock(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD> Disable ʱ<><CAB1><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD>
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
{
window.SetWindowStyle(style);
}
window.ExtendsContentIntoTitleBar = true;
SetClickThrough(window, false);
// To recover the system backdrop, we need to reopen the window
WindowHelper.RestartApp(AppInfo.UnlockWindowTag);
}
}
}

View File

@@ -1,4 +1,8 @@
using Microsoft.UI.Xaml;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -14,21 +18,29 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class DockModeHelper
{
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly HashSet<IntPtr> _registered = [];
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
public static void Disable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (!_registered.Contains(hwnd)) return;
window.SetIsShownInSwitchers(true);
window.ExtendsContentIntoTitleBar = true;
window.SetIsAlwaysOnTop(false);
IntPtr hwnd = WindowNative.GetWindowHandle(window);
UnregisterAppBar(hwnd);
window.SetWindowStyle(_originalWindowStyle[hwnd]);
_originalWindowStyle.Remove(hwnd);
window.ExtendsContentIntoTitleBar = true;
if (_originalPositions.TryGetValue(hwnd, out var rect))
{
User32.SetWindowPos(
@@ -42,14 +54,11 @@ namespace BetterLyrics.WinUI3.Helper
);
_originalPositions.Remove(hwnd);
}
UnregisterAppBar(hwnd);
}
public static void Enable(Window window, int appBarHeight)
public static void Enable(Window window, string monitorDeviceName, int appBarHeight, DockPlacement dockPlacement)
{
window.SetIsShownInSwitchers(false);
window.ExtendsContentIntoTitleBar = false;
window.SetIsAlwaysOnTop(true);
IntPtr hwnd = WindowNative.GetWindowHandle(window);
@@ -58,7 +67,6 @@ namespace BetterLyrics.WinUI3.Helper
{
_originalWindowStyle[hwnd] = window.GetWindowStyle();
}
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
if (!_originalPositions.ContainsKey(hwnd))
{
@@ -68,40 +76,55 @@ namespace BetterLyrics.WinUI3.Helper
}
}
RegisterAppBar(hwnd, appBarHeight);
RegisterAppBar(hwnd, monitorDeviceName, appBarHeight, dockPlacement);
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.DockMonitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int screenHeight = monitorInfo.rcMonitor.Bottom - monitorInfo.rcMonitor.Top;
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - appBarHeight;
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
monitorInfo.rcMonitor.Left,
y,
screenWidth,
appBarHeight,
User32.SetWindowPosFlags.SWP_SHOWWINDOW
User32.SetWindowPosFlags.SWP_HIDEWINDOW
);
window.ExtendsContentIntoTitleBar = false;
window.ToggleWindowStyle(true, WindowStyle.Popup);
window.Show();
}
private static void RegisterAppBar(IntPtr hwnd, int height)
private static void RegisterAppBar(IntPtr hwnd, string monitorDeviceName, int height, DockPlacement dockPlacement)
{
if (_registered.Contains(hwnd)) return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - height;
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + height : monitorInfo.rcMonitor.Bottom;
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = Shell32.ABE.ABE_TOP,
uEdge = uEdge,
rc = new RECT
{
Left = 0,
Top = 0,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Bottom = height,
Left = monitorInfo.rcMonitor.Left,
Top = top,
Right = monitorInfo.rcMonitor.Right,
Bottom = bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
_registered.Add(hwnd);
@@ -119,40 +142,59 @@ namespace BetterLyrics.WinUI3.Helper
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
_registered.Remove(hwnd);
}
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
public static void UpdateAppBarHeight(IntPtr hwnd, string monitorDeviceName, int newHeight, DockPlacement dockPlacement)
{
if (!_registered.Contains(hwnd))
return;
Shell32.APPBARDATA abd = new()
App.DispatcherQueueTimer?.Debounce(() =>
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = Shell32.ABE.ABE_TOP,
rc = new RECT
if (!_registered.Contains(hwnd))
return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + newHeight : monitorInfo.rcMonitor.Bottom;
Shell32.APPBARDATA abd = new()
{
Left = 0,
Top = 0,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Bottom = newHeight,
},
};
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = uEdge,
rc = new RECT
{
Left = monitorInfo.rcMonitor.Left,
Top = top,
Right = monitorInfo.rcMonitor.Right,
Bottom = bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
// 同步窗口实际高度
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
newHeight,
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
// 同步窗口实际高度和位置
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
int repeatCount = 2;
while (repeatCount > 0)
{
repeatCount--;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
monitorInfo.rcMonitor.Left,
y,
screenWidth,
newHeight,
newHeight == 0 ? User32.SetWindowPosFlags.SWP_HIDEWINDOW : User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
}
}, TimeSpan.FromMilliseconds(100));
}
}
}

View File

@@ -107,6 +107,12 @@ namespace BetterLyrics.WinUI3.Helper
return t * t * (3f - 2f * t);
}
public static float CubicBezier(float t, float p0, float p1, float p2, float p3)
{
float u = 1 - t;
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
}
public static float Linear(float t) => t;
}
}

View File

@@ -1,5 +1,7 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using System;
using System.IO;
using System.Text;
using Ude;
@@ -21,5 +23,60 @@ namespace BetterLyrics.WinUI3.Helper
}
return Encoding.GetEncoding(encoding);
}
public static string SanitizeFileName(string fileName, char replacement = '_')
{
var invalidChars = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(fileName.Length);
foreach (var c in fileName)
{
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
}
return sb.ToString();
}
public static string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
}
return null;
}
public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
if (File.Exists(cacheFilePath))
{
return File.ReadAllBytes(cacheFilePath);
}
return null;
}
public static void WriteLyricsCache(string title, string artist, string lyrics, LyricsFormat format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
File.WriteAllText(cacheFilePath, lyrics);
}
public static void WriteAlbumArtCache(string album, string artist, byte[] img, string format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
File.WriteAllBytes(cacheFilePath, img);
}
public static bool IsSwitchableNormalizedMatch(string fileName, string q1, string q2)
{
var normFileName = StringHelper.Normalize(fileName.Normalize());
var normQ1 = StringHelper.Normalize(q1);
var normQ2 = StringHelper.Normalize(q2);
// 常见两种顺序
return normFileName == normQ1 + normQ2
|| normFileName == normQ2 + normQ1;
}
}
}

View File

@@ -0,0 +1,20 @@
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Graphics.Canvas.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class FontHelper
{
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
public static string GetUserPreferredFontFamily() => SystemFontFamilies.ElementAtOrDefault(_settingsService.SelectedFontFamilyIndex) ?? "Segoe UI";
}
}

View File

@@ -4,37 +4,35 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Vanara.PInvoke;
using Windows.System;
namespace BetterLyrics.WinUI3.Helper
{
public class ForegroundWindowWatcherHelper
public class ForegroundWindowWatcher
{
private readonly User32.WinEventProc _winEventDelegate;
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
private HWND _currentForeground = HWND.NULL;
private readonly IntPtr _selfHwnd;
private readonly DispatcherTimer _pollingTimer;
private DateTime _lastEventTime = DateTime.MinValue;
private const int ThrottleIntervalMs = 1000;
private readonly ThrottleHelper _winEventProcThrottle = new(TimeSpan.FromSeconds(1));
public delegate void WindowChangedHandler(HWND hwnd);
private readonly WindowChangedHandler _onWindowChanged;
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
private readonly DispatcherTimer _timer;
public ForegroundWindowWatcher(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
{
_selfHwnd = selfHwnd;
_onWindowChanged = onWindowChanged;
_winEventDelegate = new User32.WinEventProc(WinEventProc);
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
_pollingTimer.Tick += (_, _) =>
{
if (_currentForeground != IntPtr.Zero && _currentForeground != _selfHwnd)
_onWindowChanged?.Invoke(_currentForeground);
};
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += Timer_Tick;
}
public void Start()
@@ -65,7 +63,7 @@ namespace BetterLyrics.WinUI3.Helper
)
);
_pollingTimer.Start();
_timer.Start();
}
public void Stop()
@@ -74,7 +72,16 @@ namespace BetterLyrics.WinUI3.Helper
User32.UnhookWinEvent(hook);
_hooks.Clear();
_pollingTimer.Stop();
_timer.Stop();
}
private void Timer_Tick(object? sender, object e)
{
if (_currentForeground != HWND.NULL)
{
_onWindowChanged?.Invoke(_currentForeground);
}
}
private void WinEventProc(
@@ -87,15 +94,9 @@ namespace BetterLyrics.WinUI3.Helper
uint dwmsEventTime
)
{
if (hwnd == IntPtr.Zero || hwnd == _selfHwnd)
if (hwnd == IntPtr.Zero)
return;
var now = DateTime.Now;
if ((now - _lastEventTime).TotalMilliseconds < ThrottleIntervalMs)
return;
_lastEventTime = now;
if (eventType == User32.EventConstants.EVENT_SYSTEM_FOREGROUND)
{
_currentForeground = hwnd;

View File

@@ -0,0 +1,46 @@
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Windows.System;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.Helper
{
public class GlobalHotKeyHelper
{
private static Dictionary<int, Action> _hotKeyActions = [];
private static int _nextId = 0;
public static void RegisterHotKey(Window window, User32.HotKeyModifiers modifiers, uint key, Action action)
{
HWND hwnd = WindowNative.GetWindowHandle(window);
int id = _nextId++;
User32.RegisterHotKey(hwnd, id, modifiers, key);
_hotKeyActions[id] = action;
}
public static void UnregisterAllHotKeys(Window window)
{
HWND hwnd = WindowNative.GetWindowHandle(window);
foreach (var id in _hotKeyActions.Keys.ToList())
{
User32.UnregisterHotKey(hwnd, id);
_hotKeyActions.Remove(id);
}
}
public static bool TryInvokeAction(int id)
{
if (_hotKeyActions.TryGetValue(id, out var action))
{
action?.Invoke();
return true;
}
return false;
}
}
}

View File

@@ -1,5 +1,15 @@
// 2025/6/23 by Zhe Fang
using CommunityToolkit.WinUI.Helpers;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.IO;
@@ -7,10 +17,6 @@ using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.UI;
@@ -19,157 +25,117 @@ namespace BetterLyrics.WinUI3.Helper
{
public class ImageHelper
{
public const int AccentColorCount = 3;
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
{
var stream = new InMemoryRandomAccessStream();
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
stream.Seek(0);
return stream;
}
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
{
var device = CanvasDevice.GetSharedDevice();
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
using var stream = new InMemoryRandomAccessStream();
using var writer = new DataWriter(stream);
writer.WriteBytes(bytes);
writer.StoreAsync().GetAwaiter().GetResult();
writer.FlushAsync().GetAwaiter().GetResult();
writer.DetachStream();
return RandomAccessStreamReference.CreateFromStream(stream);
}
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
{
using var device = CanvasDevice.GetSharedDevice();
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
// 随机生成渐变色
Windows.UI.Color RandomColor()
{
var rand = new Random(Guid.NewGuid().GetHashCode());
double h = rand.NextDouble() * 360;
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
}
Windows.UI.Color color1 = RandomColor();
Windows.UI.Color color2 = RandomColor();
// 居中绘制文字
using (var ds = renderTarget.CreateDrawingSession())
{
// 背景
ds.Clear(Colors.LightGray);
// 文字格式
var format = new CanvasTextFormat
// 绘制线性渐变背景
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
{
FontSize = Math.Min(width, height) / 6f,
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
HorizontalAlignment = CanvasHorizontalAlignment.Center,
VerticalAlignment = CanvasVerticalAlignment.Center,
WordWrapping = CanvasWordWrapping.Wrap,
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
Options = CanvasDrawTextOptions.Default,
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(width, height)
};
// 设定边距
float margin = Math.Min(width, height) / 12f;
float availableWidth = width - 2 * margin;
float availableHeight = height - 2 * margin;
// 计算合适的字体大小以适应内容区域
float fontSize = format.FontSize;
float minFontSize = 8f;
float maxFontSize = format.FontSize;
CanvasTextLayout layout;
do
{
format.FontSize = fontSize;
layout = new CanvasTextLayout(
ds,
text,
format,
availableWidth,
availableHeight
);
if (
layout.LayoutBounds.Width <= availableWidth
&& layout.LayoutBounds.Height <= availableHeight
)
break;
fontSize -= 1f;
} while (fontSize >= minFontSize);
// 居中绘制文字(在内容区域内居中)
var bounds = layout.LayoutBounds;
var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X;
var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y;
ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray);
ds.FillRectangle(0, 0, width, height, gradientBrush);
}
// 保存为 PNG 并转为 byte[]
using (var stream = new InMemoryRandomAccessStream())
using var stream = new InMemoryRandomAccessStream();
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
var buffer = new byte[stream.Size];
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
{
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
var buffer = new byte[stream.Size];
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
{
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(buffer);
}
return buffer;
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(buffer);
}
return buffer;
}
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes, int count, bool? isDark = null)
{
// 使用 ImageSharp 读取图片
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
var color = image[x, y];
// 可选:忽略透明像素
if (color.A < 32) continue;
if (colorCount.ContainsKey(color))
colorCount[color]++;
else
colorCount[color] = 1;
}
}
// 按出现次数排序,取前 AccentColorCount 个
var topColors = colorCount
.OrderByDescending(kv => kv.Value)
.Take(AccentColorCount)
.Select(kv => kv.Key)
using var image = Image.Load<Rgba32>(bytes);
var colorThief = new ColorThief.ImageSharp.ColorThief();
var mainColor = colorThief.GetColor(image, 10, false);
var palette = colorThief.GetPalette(image, 255, 10, false);
var topColors = palette
.OrderByDescending(x => x.Population)
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
.Select(x => Windows.UI.Color.FromArgb(x.Color.A, x.Color.R, x.Color.G, x.Color.B))
.Take(count)
.ToList();
// 转换为 Windows.UI.Color
return topColors
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
.ToList();
return topColors;
}
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
//{
// var stream = new InMemoryRandomAccessStream();
// await stream.WriteAsync(imageBytes.AsBuffer());
// stream.Seek(0);
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
{
var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageBytes.AsBuffer());
stream.Seek(0);
// var bitmapImage = new BitmapImage();
// await bitmapImage.SetSourceAsync(stream);
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(stream);
// return bitmapImage;
//}
return bitmapImage;
}
//public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
// await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
//public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
//{
// if (imageBytes == null || imageBytes.Length == 0)
// return null;
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
{
if (imageBytes == null || imageBytes.Length == 0)
return null;
// InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
// await stream.WriteAsync(imageBytes.AsBuffer());
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageBytes.AsBuffer());
return stream;
}
// return stream;
//}
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
using var memoryStream = new MemoryStream();
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
using var reader = new DataReader(stream);
await reader.LoadAsync((uint)stream.Size);
byte[] buffer = new byte[stream.Size];
reader.ReadBytes(buffer);
return buffer;
}
public static float GetAverageLuminance(CanvasBitmap bitmap)
@@ -188,5 +154,70 @@ namespace BetterLyrics.WinUI3.Helper
}
return (float)(sum / (pixels.Length / 4));
}
public static byte[] MakeSquareWithThemeColor(byte[] imageBytes)
{
using var image = Image.Load<Rgba32>(imageBytes);
if (image.Width == image.Height)
{
// 已经是正方形,直接返回
return imageBytes;
}
int size = Math.Max(image.Width, image.Height);
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes, 1).FirstOrDefault().ToHex());
using var square = new Image<Rgba32>(size, size, themeColor);
int offsetX = (size - image.Width) / 2;
int offsetY = (size - image.Height) / 2;
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
using var ms = new MemoryStream();
square.Save(ms, new JpegEncoder());
return ms.ToArray();
}
public static byte[] Resize(byte[] imageBytes, int size)
{
using (Image image = Image.Load(imageBytes))
{
var factor = Math.Max((float)size / image.Width, (float)size / image.Height);
int width = (int)(image.Width * factor);
int height = (int)(image.Height * factor);
if (factor > 1)
{
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
}
else
{
image.Mutate(x => x.Resize(width, height, KnownResamplers.NearestNeighbor));
}
using var ms = new MemoryStream();
image.Save(ms, new JpegEncoder());
return ms.ToArray();
}
}
public static byte[] GenerateNoiseBGRA(int width, int height)
{
var random = new Random();
var pixelData = new byte[width * height * 4];
for (int i = 0; i < width * height; i++)
{
byte gray = (byte)random.Next(0, 256);
pixelData[i * 4 + 0] = gray; // B
pixelData[i * 4 + 1] = gray; // G
pixelData[i * 4 + 2] = gray; // R
pixelData[i * 4 + 3] = 255; // A
}
return pixelData;
}
}
}

View File

@@ -0,0 +1,129 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using NTextCat;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using TinyPinyin;
using Windows.Globalization;
namespace BetterLyrics.WinUI3.Services
{
public class LanguageHelper
{
private static readonly RankedLanguageIdentifierFactory _factory = new();
private static readonly RankedLanguageIdentifier _identifier;
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
[
new Models.LanguageInfo("ar", "العربية"),
new Models.LanguageInfo("az", "Azərbaycan dili"),
new Models.LanguageInfo("zh-Hans", "简体中文"),
new Models.LanguageInfo("zh-Hant", "繁體中文"),
new Models.LanguageInfo("cs", "Čeština"),
new Models.LanguageInfo("da", "Dansk"),
new Models.LanguageInfo("nl", "Nederlands"),
new Models.LanguageInfo("en", "English"),
new Models.LanguageInfo("eo", "Esperanto"),
new Models.LanguageInfo("fi", "Suomi"),
new Models.LanguageInfo("fr", "Français"),
new Models.LanguageInfo("de", "Deutsch"),
new Models.LanguageInfo("el", "Ελληνικά"),
new Models.LanguageInfo("he", "עברית"),
new Models.LanguageInfo("hi", "हिन्दी"),
new Models.LanguageInfo("hu", "Magyar"),
new Models.LanguageInfo("id", "Bahasa Indonesia"),
new Models.LanguageInfo("ga", "Gaeilge"),
new Models.LanguageInfo("it", "Italiano"),
new Models.LanguageInfo("ja", "日本語"),
new Models.LanguageInfo("ko", "한국어"),
new Models.LanguageInfo("fa", "فارسی"),
new Models.LanguageInfo("pl", "Polski"),
new Models.LanguageInfo("pt", "Português"),
new Models.LanguageInfo("ru", "Русский"),
new Models.LanguageInfo("sk", "Slovenčina"),
new Models.LanguageInfo("es", "Español"),
new Models.LanguageInfo("sv", "Svenska"),
new Models.LanguageInfo("tr", "Türkçe"),
new Models.LanguageInfo("uk", "Українська"),
new Models.LanguageInfo("vi", "Tiếng Việt"),
];
static LanguageHelper()
{
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
}
public static string? DetectLanguageCode(string? text)
{
if (text == null) return null;
var guessList = _identifier.Identify(text);
string? code = guessList?.FirstOrDefault()?.Item1.Iso639_2T;
code = code switch
{
"simple" => "en",
"zh_classical" => "zh-Hant",
"zh_yue" => "zh-Hant",
"zh" => "zh-Hans",
_ => code
};
return code;
}
public static bool IsCJK(string text)
{
return DetectLanguageCode(text)?.Substring(0, 2) switch
{
"zh" or "ja" or "ko" => true,
_ => false
};
}
public static string ConvertToCountryCode(string? languageCode)
{
if (languageCode == null) return "us";
return languageCode switch
{
"zh" => "cn",
"zh-Hans" => "cn",
"zh-Hant" => "tw",
"ja" => "jp",
"ko" => "kr",
_ => "us"
};
}
public static string GetUserTargetLanguageCode()
{
return SupportedTargetLanguages[_settingsService.SelectedTargetLanguageIndex].Code;
}
public static int GetDefaultTargetLanguageIndex()
{
int found = SupportedTargetLanguages.FindIndex(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
if (found == -1) found = 7; // 默认使用英语
return found;
}
public static string GetOrderChar(string text)
{
if (string.IsNullOrWhiteSpace(text)) return "#";
char c = text.ElementAtOrDefault(0);
if (char.IsLetter(c) && c < 128)
return char.ToUpper(c).ToString();
if (PinyinHelper.IsChinese(c))
{
return PinyinHelper.GetPinyinInitials($"{c}");
}
return "#";
}
}
}

View File

@@ -0,0 +1,43 @@
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class LatestOnlyTaskRunner
{
private readonly AsyncLock _mutex = new();
private CancellationTokenSource _cts;
public async Task RunAsync(Func<CancellationToken, Task> action)
{
CancellationTokenSource oldCts;
// 使用 AsyncLock 保证线程安全
using (await _mutex.LockAsync())
{
// 取消旧的
oldCts = _cts;
_cts = new CancellationTokenSource();
}
oldCts?.Cancel();
oldCts?.Dispose();
CancellationToken token = _cts.Token;
try
{
await action(token);
}
catch (OperationCanceledException)
{
// 可以选择忽略取消异常
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.System;
namespace BetterLyrics.WinUI3.Helper
{
public class LauncherHelper
{
public static async Task SelectAndShowFile(string filePath)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
var folder = await file.GetParentAsync();
var folderOptions = new FolderLauncherOptions();
folderOptions.ItemsToSelect.Add(file);
await Launcher.LaunchFolderAsync(folder, folderOptions);
}
}
}

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using Lyricify.Lyrics.Models;
using System;
using System.Collections.Generic;
@@ -9,29 +10,21 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Windows.Globalization.Fonts;
using LyricsData = BetterLyrics.WinUI3.Models.LyricsData;
namespace BetterLyrics.WinUI3.Helper
{
public class LyricsParser
{
private List<List<LyricsLine>> _multiLangLyricsLines = [];
private List<LyricsData> _lyricsDataArr = [];
public List<List<LyricsLine>> Parse(string? raw, int durationMs)
public List<LyricsData> Parse(string? raw, int? durationMs)
{
_multiLangLyricsLines = [];
durationMs ??= (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
_lyricsDataArr = [];
if (raw == null)
{
_multiLangLyricsLines.Add(
[
new LyricsLine
{
StartMs = 0,
EndMs = durationMs,
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
CharTimings = [],
},
]
);
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder(durationMs.Value));
}
else
{
@@ -42,10 +35,10 @@ namespace BetterLyrics.WinUI3.Helper
ParseLrc(raw);
break;
case LyricsFormat.Qrc:
ParseUsingLyricify(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
break;
case LyricsFormat.Krc:
ParseUsingLyricify(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(raw);
@@ -54,8 +47,8 @@ namespace BetterLyrics.WinUI3.Helper
break;
}
}
PostProcessLyricsLines(durationMs);
return _multiLangLyricsLines;
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
return _lyricsDataArr;
}
private void ParseLrc(string raw)
@@ -116,49 +109,57 @@ namespace BetterLyrics.WinUI3.Helper
// 按时间分组
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
int languageCount = grouped.Max(g => g.Count());
int languageCount = 0;
if (grouped != null && grouped.Count > 0)
{
// 计算最大语言数量
languageCount = grouped.Max(g => g.Count());
}
// 初始化每种语言的歌词列表
_multiLangLyricsLines.Clear();
for (int i = 0; i < languageCount; i++)
_multiLangLyricsLines.Add(new List<LyricsLine>());
_lyricsDataArr.Clear();
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
foreach (var group in grouped)
if (grouped != null)
{
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
foreach (var group in grouped)
{
// 如果该语言有翻译,取对应行,否则用原文(第一行)
var (start, text, syllables) =
langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
var line = new LyricsLine
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
StartMs = start,
EndMs = 0, // 稍后统一修正
OriginalText = text,
CharTimings = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.CharTimings.Add(
new CharTiming
var (start, text, syllables) = linesInGroup[langIdx];
var line = new LyricsLine
{
StartMs = start,
OriginalText = text,
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
StartMs = charStart,
EndMs = 0, // Fixed later
Text = charText ?? "",
StartIndex = startIndex,
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
}
);
currentIndex += charText?.Length ?? 0;
}
_lyricsDataArr[langIdx].LyricsLines.Add(line);
}
// 没有翻译行则不补原文,直接跳过
}
_multiLangLyricsLines[langIdx].Add(line);
}
}
}
@@ -167,8 +168,9 @@ namespace BetterLyrics.WinUI3.Helper
{
try
{
List<LyricsLine> singleLangLyricsLine = [];
var xdoc = XDocument.Parse(raw);
List<LyricsLine> originalLines = [];
List<LyricsLine> translationLines = [];
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
if (body == null) return;
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
@@ -180,49 +182,94 @@ namespace BetterLyrics.WinUI3.Helper
int pStartMs = ParseTtmlTime(pBegin);
int pEndMs = ParseTtmlTime(pEnd);
// 处理分词分时
var spans = p.Elements().Where(s => s.Name.LocalName == "span").ToList();
// 只获取一级span且排除ttm:role="x-bg"的span
var spans = p.Elements()
.Where(s => s.Name.LocalName == "span" &&
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg")
.ToList();
string text = string.Concat(spans.Select(s => s.Value));
var charTimings = new List<CharTiming>();
// 原文和翻译分离
var originalTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-translation")
.ToList();
var translationTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
.ToList();
int startIndex = 0;
for (int i = 0; i < spans.Count; i++)
// 处理原文span后的空白
for (int i = 0; i < originalTextSpans.Count; i++)
{
var span = originalTextSpans[i];
var nextNode = span.NodesAfterSelf().FirstOrDefault();
if (nextNode is XText textNode)
{
span.Value += textNode.Value;
}
}
// 拼接空白字符后的原文
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
var originalCharTimings = new List<LyricsChar>();
int originalStartIndex = 0;
foreach (var span in originalTextSpans)
{
var span = spans[i];
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
if (sStartMs == 0 && sEndMs == 0)
continue;
if (sEndMs == 0)
sEndMs =
(i + 1 < spans.Count)
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
: pEndMs;
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = 0, StartIndex = startIndex, Text = span.Value });
startIndex += span.Value.Length;
originalCharTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = sEndMs,
StartIndex = originalStartIndex,
Text = span.Value
});
originalStartIndex += span.Value.Length;
}
if (originalTextSpans.Count == 0)
originalText = p.Value;
if (spans.Count == 0)
text = p.Value;
originalLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = originalText,
LyricsChars = originalCharTimings,
});
singleLangLyricsLine.Add(
new LyricsLine
// 翻译
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
var translationCharTimings = new List<LyricsChar>();
int translationStartIndex = 0;
foreach (var span in translationTextSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
translationCharTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = sEndMs,
StartIndex = translationStartIndex,
Text = span.Value
});
translationStartIndex += span.Value.Length;
}
if (translationTextSpans.Count > 0)
{
translationLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = 0,
OriginalText = text,
CharTimings = charTimings,
}
);
EndMs = pEndMs,
OriginalText = translationText,
LyricsChars = translationCharTimings,
});
}
}
_multiLangLyricsLines.Add(singleLangLyricsLine);
_lyricsDataArr.Add(new LyricsData(originalLines));
if (translationLines.Count > 0)
_lyricsDataArr.Add(new LyricsData(translationLines));
}
catch
{
@@ -230,7 +277,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
private int ParseTtmlTime(string? t)
private static int ParseTtmlTime(string? t)
{
if (string.IsNullOrWhiteSpace(t))
return 0;
@@ -291,7 +338,7 @@ namespace BetterLyrics.WinUI3.Helper
return 0;
}
private void ParseUsingLyricify(List<ILineInfo>? lines)
private void ParseQQNeteaseKugou(List<ILineInfo>? lines)
{
lines = lines?.Where(x => x.Text != string.Empty).ToList();
List<LyricsLine> lyricsLines = [];
@@ -305,9 +352,9 @@ namespace BetterLyrics.WinUI3.Helper
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = 0,
EndMs = lineRead.EndTime ?? 0,
OriginalText = lineRead.Text,
CharTimings = [],
LyricsChars = [],
};
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
@@ -321,22 +368,14 @@ namespace BetterLyrics.WinUI3.Helper
)
{
var syllable = syllables[syllableIndex];
var charTiming = new CharTiming
var charTiming = new LyricsChar
{
StartMs = syllable.StartTime,
EndMs = 0,
EndMs = syllable.EndTime,
Text = syllable.Text,
StartIndex = startIndex,
};
if (syllableIndex + 1 < syllables.Count)
{
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
}
else
{
charTiming.EndMs = lineWrite.EndMs;
}
lineWrite.CharTimings.Add(charTiming);
lineWrite.LyricsChars.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
@@ -345,56 +384,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
_multiLangLyricsLines.Add(lyricsLines);
}
private void PostProcessLyricsLines(int durationMs)
{
for (int langIdx = 0; langIdx < _multiLangLyricsLines.Count; langIdx++)
{
var linesInSingleLang = _multiLangLyricsLines[langIdx];
for (int i = 0; i < linesInSingleLang.Count; i++)
{
if (i + 1 < linesInSingleLang.Count)
{
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
}
else
{
linesInSingleLang[i].EndMs = durationMs;
}
// 修正 CharTimings 的 EndMs
var timings = linesInSingleLang[i].CharTimings;
if (timings.Count > 0)
{
for (int j = 0; j < timings.Count; j++)
{
if (j + 1 < timings.Count)
{
timings[j].EndMs = timings[j + 1].StartMs;
}
else
{
timings[j].EndMs = linesInSingleLang[i].EndMs;
}
}
}
}
if (linesInSingleLang.Count > 0 && linesInSingleLang[0].StartMs > 0)
{
linesInSingleLang.Insert(
0,
new LyricsLine
{
StartMs = 0,
EndMs = linesInSingleLang[0].StartMs,
OriginalText = "● ● ●",
CharTimings = [],
}
);
}
}
_lyricsDataArr.Add(new LyricsData(lyricsLines));
}
}
}

View File

@@ -0,0 +1,26 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Helper
{
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.FileProperties;
public static class MetadataHelper
{
public static string AppVersion
{
get
{
var version = Package.Current.Id.Version;
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3.Helper
{
public static class MonitorHelper
{
public static IEnumerable<string> GetAllMonitorDeviceNames()
{
var deviceNames = new List<string>();
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
{
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
{
deviceNames.Add(monitorInfoEx.szDevice);
}
return true; // 继续枚举
}, IntPtr.Zero);
return deviceNames;
}
public static User32.MONITORINFOEX GetMonitorInfoExFromDeviceName(string deviceName)
{
User32.MONITORINFOEX? result = null;
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
{
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
{
if (string.Equals(monitorInfoEx.szDevice, deviceName, StringComparison.OrdinalIgnoreCase))
{
result = monitorInfoEx;
return false; // 找到后停止枚举
}
}
return true; // 继续枚举
}, IntPtr.Zero);
return result ?? GetPrimaryMonitorInfoEx();
}
public static User32.MONITORINFOEX GetPrimaryMonitorInfoEx()
{
// (0,0) 总是在主屏
var ptZero = new POINT(0, 0);
HMONITOR hMonitor = User32.MonitorFromPoint(ptZero, User32.MonitorFlags.MONITOR_DEFAULTTOPRIMARY);
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
User32.GetMonitorInfo(hMonitor, ref monitorInfoEx);
return monitorInfoEx;
}
public static string GetPrimaryMonitorDeviceName()
{
var primaryMonitorInfo = GetPrimaryMonitorInfoEx();
return primaryMonitorInfo.szDevice;
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class NetHelper
{
public static async Task<bool> CheckConnectivity(string url)
{
try
{
using var client = new System.Net.Http.HttpClient();
// Try to reach a reliable endpoint
var res = await client.GetAsync(url);
return res.IsSuccessStatusCode;
}
catch
{
return false; // If any exception occurs, assume no connectivity
}
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class ObjectHelper
{
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
public static bool IsDisposed(this IDisposable obj)
{
/*
TIM C: This hacky code is because MSFT does not provide a standard way to interrogate if an object is disposed or not.
I wrote this based upon streams, but it should work for many other types of MSFT objects (maybe).
*/
if (obj == null) { return true; }
var objType = obj.GetType();
//var foo = new System.IO.BufferedStream();
// the _disposed pattern should catch a lot of msft objects.... hopefully
var isDisposedField = objType.GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance) ??
objType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
if (isDisposedField != null) { return Convert.ToBoolean(isDisposedField.GetValue(obj)); }
isDisposedField = objType.GetField("_isOpen", BindingFlags.NonPublic | BindingFlags.Instance);
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
// Windows.Graphics.Imaging.SoftwareBitmap
isDisposedField = objType.GetField("_objRef_global__System_IDisposable", BindingFlags.NonPublic | BindingFlags.Instance);
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
// System.IO.FileStream
var strategyField = objType.GetField("_strategy", BindingFlags.NonPublic | BindingFlags.Instance);
if (strategyField != null)
{
var strategy = strategyField.GetValue(obj);
var isClosedField = strategy.GetType().GetProperty("IsClosed", BindingFlags.NonPublic | BindingFlags.Instance);
if (isClosedField != null) { return Convert.ToBoolean(isClosedField.GetValue(strategy)); }
}
// other streams that use this pattern to determine if they are disposed
if (obj is Stream stream) { return !stream.CanRead && !stream.CanWrite; }
return false;
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
namespace BetterLyrics.WinUI3.Helper
{
public class PathHelper
{
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
//public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Wiki82.profile.xml");
public static string LogoPath => Path.Combine(AssetsFolder, "Logo.ico");
public static string AIMPLogoPath => Path.Combine(AssetsFolder, "AIMP.png");
public static string Foobar2000LogoPath => Path.Combine(AssetsFolder, "foobar2000.png");
public static string MusicBeeLogoPath => Path.Combine(AssetsFolder, "MusicBee.png");
public static string SpotifyLogoPath => Path.Combine(AssetsFolder, "Spotify.png");
public static string AppleMusicLogoPath => Path.Combine(AssetsFolder, "AppleMusic.png");
public static string iTunesLogoPath => Path.Combine(AssetsFolder, "iTunes.png");
public static string KugouMusicLogoPath => Path.Combine(AssetsFolder, "KugouMusic.png");
public static string NetEaseCloudMusicLogoPath => Path.Combine(AssetsFolder, "NetEaseCloudMusic.png");
public static string QQMusicLogoPath => Path.Combine(AssetsFolder, "QQMusic.png");
public static string LXMusicLogoPath => Path.Combine(AssetsFolder, "LXMusic.png");
public static string MediaPlayerWindows11LogoPath => Path.Combine(AssetsFolder, "MediaPlayerWindows11.png");
public static string PotPlayerLogoPath => Path.Combine(AssetsFolder, "PotPlayer.png");
public static string ChromeLogoPath => Path.Combine(AssetsFolder, "Chrome.png");
public static string EdgeLogoPath => Path.Combine(AssetsFolder, "Edge.png");
public static string UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
public static string LyricsCacheDirectory => Path.Combine(CacheFolder, "lyrics");
public static string LrcLibLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "lrclib");
public static string NeteaseLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "netease");
public static string QQLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "qq");
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.json");
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
public static string iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
public static void EnsureDirectories()
{
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
Directory.CreateDirectory(QQLyricsCacheDirectory);
Directory.CreateDirectory(KugouLyricsCacheDirectory);
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(QQTranslationCacheDirectory);
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class StringHelper
{
// 去除空格、括号、下划线、横杠、点、大小写等
public static string Normalize(string s) =>
new string(s
.Where(c => char.IsLetterOrDigit(c))
.ToArray())
.ToLowerInvariant();
public static string NewLine = "\n";
}
}

View File

@@ -0,0 +1,74 @@
using Microsoft.UI.Dispatching;
using System;
using Vanara.Extensions;
using Vanara.PInvoke;
using static Vanara.PInvoke.CoreAudio;
namespace BetterLyrics.WinUI3.Helper
{
public static class SystemVolumeHelper
{
private readonly static IMMDeviceEnumerator _deviceEnumerator = new();
private static IAudioEndpointVolume? _endpointVolume = null;
private static VolumeCallbackImpl? _callbackImpl;
private static int _masterVolume = 0;
private static DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public static event Action<int>? VolumeChanged;
static SystemVolumeHelper()
{
var device = _deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
if (device != null)
{
device.Activate(typeof(IAudioEndpointVolume).GUID, 0, null, out var obj);
if (obj is IAudioEndpointVolume endpointVolume)
{
_endpointVolume = endpointVolume;
_callbackImpl = new VolumeCallbackImpl();
_endpointVolume.RegisterControlChangeNotify(_callbackImpl);
}
}
}
/// <summary>
/// 获取当前系统主音量0~100
/// </summary>
public static int GetMasterVolume()
{
if (_endpointVolume != null)
{
float level = _endpointVolume.GetMasterVolumeLevelScalar();
_masterVolume = (int)(level * 100);
}
return _masterVolume;
}
/// <summary>
/// 设置当前系统主音量0~100
/// </summary>
public static void SetMasterVolume(int volume)
{
if (_masterVolume == volume) return;
_masterVolume = volume;
_endpointVolume?.SetMasterVolumeLevelScalar(_masterVolume / 100f, Guid.Empty);
}
// 内部回调实现
private class VolumeCallbackImpl : IAudioEndpointVolumeCallback
{
HRESULT IAudioEndpointVolumeCallback.OnNotify(nint pNotify)
{
var data = pNotify.ToStructure<AUDIO_VOLUME_NOTIFICATION_DATA>();
_masterVolume = (int)(data.fMasterVolume * 100);
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
VolumeChanged?.Invoke(_masterVolume);
});
return HRESULT.S_OK;
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace BetterLyrics.WinUI3.Helper
{
public class ThrottleHelper
{
private DateTime _lastTriggerTime = DateTime.MinValue;
private readonly TimeSpan _interval;
public ThrottleHelper(TimeSpan interval)
{
_interval = interval;
}
/// <summary>
/// 判断是否可以触发(距离上次触发已超过设定间隔),如果可以则更新时间戳并返回 true否则返回 false。
/// </summary>
public bool CanTrigger()
{
var now = DateTime.Now;
if ((now - _lastTriggerTime) >= _interval)
{
_lastTriggerTime = now;
return true;
}
return false;
}
/// <summary>
/// 重置触发时间
/// </summary>
public void Reset()
{
_lastTriggerTime = DateTime.MinValue;
}
}
}

View File

@@ -6,13 +6,6 @@ using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Helper
{
public class AnimationHelper
{
public const int DebounceDefaultDuration = 200;
public const int StackedNotificationsShowingDuration = 3900;
public const int StoryboardDefaultDuration = 200;
}
public class ValueTransition<T>
where T : struct
{
@@ -52,11 +45,18 @@ namespace BetterLyrics.WinUI3.Helper
}
else
{
_easingType = EasingType.SmoothStep;
_easingType = EasingType.Linear;
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
}
}
public void SetDuration(float seconds)
{
if (seconds <= 0)
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
_durationSeconds = seconds;
}
private void JumpTo(T value)
{
_currentValue = value;
@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
{
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
_progress += (float)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
if (_progress >= 1f)
{
_progress = 1f;

View File

@@ -1,148 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3.Helper
{
public static class WindowColorHelper
{
public static Color GetDominantColor(IntPtr myHwnd, WindowColorSampleMode mode)
{
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return Color.Transparent;
switch (mode)
{
case WindowColorSampleMode.BelowWindow:
{
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
}
case WindowColorSampleMode.WindowArea:
{
int width = myRect.Right - myRect.Left;
int height = myRect.Bottom - myRect.Top;
if (width <= 0 || height <= 0)
return Color.Transparent;
// 采集窗口区域的平均色
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
}
case WindowColorSampleMode.WindowEdge:
{
int width = myRect.Right - myRect.Left;
int height = myRect.Bottom - myRect.Top;
if (width <= 0 || height <= 0)
return Color.Transparent;
var edgeThickness = new Thickness(36, 0, 36, 0);
List<Color> edgeColors = [];
// Top edge
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top,
width,
(int)edgeThickness.Top
)
);
// Bottom edge
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Bottom - (int)edgeThickness.Bottom,
width,
(int)edgeThickness.Bottom
)
);
// Left edge
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Left,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
// Right edge
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Right - (int)edgeThickness.Right,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Right,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
// 合并四边平均色
if (edgeColors.Count == 0)
return Color.Transparent;
long r = 0,
g = 0,
b = 0;
foreach (var c in edgeColors)
{
r += c.R;
g += c.G;
b += c.B;
}
return Color.FromArgb(
255,
(int)(r / edgeColors.Count),
(int)(g / edgeColors.Count),
(int)(b / edgeColors.Count)
);
}
default:
return Color.Transparent;
}
}
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
{
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
using Graphics gDest = Graphics.FromImage(bmp);
IntPtr hdcDest = gDest.GetHdc();
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
gDest.ReleaseHdc(hdcDest);
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
return ComputeAverageColor(bmp);
}
private static Color ComputeAverageColor(Bitmap bmp)
{
long r = 0, g = 0, b = 0;
int count = 0;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color pixel = bmp.GetPixel(x, y);
r += pixel.R;
g += pixel.G;
b += pixel.B;
count++;
}
}
if (count == 0) return Color.Transparent;
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
}
}
}

View File

@@ -1,10 +1,11 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Views;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.Core;
using WinRT.Interop;
using WinUIEx;
@@ -25,17 +26,6 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static void ExitAllWindows()
{
while (_activeWindows.Count > 0)
{
var window = _activeWindows[0];
((Window)window).Close();
_activeWindows.Remove(window);
}
App.Current.Exit();
}
public static T? GetWindowByWindowType<T>()
{
foreach (var window in _activeWindows)
@@ -47,32 +37,33 @@ namespace BetterLyrics.WinUI3.Helper
}
return default;
}
public static void OpenOrShowWindow<T>()
public static void OpenWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
if (window != null)
if (window == null)
{
var castedWindow = (Window)window;
castedWindow.Restore();
}
else
{
object newWindow;
if (typeof(T) == typeof(LyricsWindow))
{
newWindow = new LyricsWindow();
window = new LyricsWindow();
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
}
else if (typeof(T) == typeof(SettingsWindow))
{
newWindow = new SettingsWindow();
window = new SettingsWindow();
}
else if (typeof(T) == typeof(MusicGalleryWindow))
{
window = new MusicGalleryWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
((Window)newWindow).Activate();
TrackWindow(newWindow);
TrackWindow(window);
}
var castedWindow = (Window)window;
castedWindow.Restore();
castedWindow.Activate();
}
public static void RestartApp(string args = "")
@@ -95,10 +86,32 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static void ExitApp()
{
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
DockModeHelper.Disable(lyricsWindow);
}
Environment.Exit(0);
}
private static void TrackWindow(object window)
{
if (!_activeWindows.Contains(window))
{
_activeWindows.Add(window);
var castedWindow = (Window)window;
castedWindow.Closed += WindowHelper_Closed;
}
}
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
{
if (_activeWindows.Contains(sender))
{
_activeWindows.Remove(sender);
}
}
}
}

View File

@@ -0,0 +1,24 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class AlbumArtSearchProviderInfo : ObservableObject
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
[ObservableProperty]
public partial AlbumArtSearchProvider Provider { get; set; }
public AlbumArtSearchProviderInfo() { }
public AlbumArtSearchProviderInfo(AlbumArtSearchProvider provider, bool isEnabled)
{
Provider = provider;
IsEnabled = isEnabled;
}
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class DetectLanguageResult
{
[JsonPropertyName("confidence")]
public double Confidence { get; set; }
[JsonPropertyName("language")]
public string Language { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public partial class GroupInfoList : List<object>
{
public required object Key { get; set; }
public GroupInfoList(IEnumerable<object> items, Func<object, object>? orderSelector = null)
: base(orderSelector != null
? items.OrderBy(orderSelector)
: items)
{
}
public override string ToString()
{
return $"{Key}";
}
}
}

View File

@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class LocalLyricsFolder : ObservableObject
public partial class LocalMediaFolder : ObservableObject
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
@@ -12,9 +12,9 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Path { get; set; }
public LocalLyricsFolder() { }
public LocalMediaFolder() { }
public LocalLyricsFolder(string path, bool isEnabled)
public LocalMediaFolder(string path, bool isEnabled)
{
Path = path;
IsEnabled = isEnabled;

View File

@@ -1,10 +1,12 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Helper;
namespace BetterLyrics.WinUI3.Models
{
public class CharTiming
public class LyricsChar
{
public int EndMs { get; set; }
public int? EndMs { get; set; }
public int StartIndex { get; set; }
public int StartMs { get; set; }
public string Text { get; set; } = string.Empty;

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