Compare commits

..

7 Commits

Author SHA1 Message Date
Zhe Fang
42af22a7e3 Merge pull request #18 from jayfunc/dev
v1.0.11.0
2025-07-11 18:08:49 -04:00
Zhe Fang
509079e8c7 Merge pull request #10 from jayfunc/dev
1.0.9.0
2025-07-07 17:32:48 -04:00
Zhe Fang
c50c180aa0 Merge pull request #9 from jayfunc/dev
v1.0.7.0 release
2025-06-30 20:56:34 -04:00
Zhe Fang
5e74468194 Merge pull request #8 from jayfunc/dev
add multiple online lyrics providers; add desktop mode; improve blur/shadow/scrolling effect performance; fix bugs
2025-06-26 21:51:36 -04:00
Zhe Fang
a93b535667 Merge pull request #7 from jayfunc/dev
update to v1.0.5.0
2025-06-23 13:41:33 -04:00
Zhe Fang
2c55b11e70 Merge pull request #6 from jayfunc/dev
fix
2025-06-18 17:16:06 -04:00
Zhe Fang
7bca1d1205 Merge pull request #5 from jayfunc/dev
Add dock mode, improve glow effect, fix bugs ...
2025-06-17 21:50:22 -04:00
205 changed files with 60286 additions and 340482 deletions

15
.github/FUNDING.yml vendored
View File

@@ -1,15 +0,0 @@
# 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']

View File

@@ -1,18 +0,0 @@
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.

69
.github/workflows/pages.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Deploy Jekyll site to Pages
on:
push:
branches: ["dev", "stable"]
paths:
- "docs/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Build job
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: docs
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
cache-version: 0
working-directory: "${{ github.workspace }}/docs"
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build with Jekyll
# Outputs to the './_site' directory by default
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/_site/
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,28 +0,0 @@
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

@@ -1,22 +0,0 @@
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,4 +406,3 @@ 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,150 +1,149 @@
<?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">
<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" />
<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" />
</Project>

View File

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

View File

@@ -12,7 +12,6 @@
<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>
@@ -48,11 +47,7 @@
<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" />
@@ -83,14 +78,11 @@
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style
x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}"
TargetType="ToggleButton">
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Padding" Value="16,0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
@@ -100,212 +92,11 @@
<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">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -3,33 +3,20 @@
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
{
@@ -46,8 +33,6 @@ namespace BetterLyrics.WinUI3
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
private static Mutex? _instanceMutex;
public App()
{
this.InitializeComponent();
@@ -56,8 +41,6 @@ namespace BetterLyrics.WinUI3
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
ResourceLoader = new ResourceLoader();
EnsureSingleInstance();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
PathHelper.EnsureDirectories();
ConfigureServices();
@@ -70,28 +53,13 @@ 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.OpenWindow<LyricsWindow>();
WindowHelper.OpenOrShowWindow<LyricsWindow>();
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
lyricsWindow.ViewModel.InitLockHotKey();
lyricsWindow.AutoSelectLyricsMode();
}
if (lyricsWindow == null) return;
lyricsWindow.AutoSelectLyricsMode();
}
private static void ConfigureServices()
@@ -111,19 +79,17 @@ namespace BetterLyrics.WinUI3
})
// Services
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
.AddSingleton<IPlaybackService, PlaybackService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.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.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,226 +1,118 @@
<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>
<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>
<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="Assets\Core14.profile.xml" />
<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="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="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<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>
</Project>

View File

@@ -1,15 +0,0 @@
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

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

@@ -1,13 +0,0 @@
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

@@ -1,9 +0,0 @@
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

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

@@ -1,29 +0,0 @@
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

@@ -1,28 +0,0 @@
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

@@ -1,13 +0,0 @@
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

@@ -1,13 +0,0 @@
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,9 +14,7 @@
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>
@@ -24,10 +22,7 @@
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

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
public class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{

View File

@@ -3,7 +3,7 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
public partial class CornerRadiusToDoubleConverter : IValueConverter
internal 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
{
public partial class EnumToIntConverter : IValueConverter
internal 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 => "LrcLib",
LyricsSearchProvider.QQ => "QQ 音乐",
LyricsSearchProvider.Netease => "网易云音乐",
LyricsSearchProvider.Kugou => "酷狗音乐",
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
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.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 "N/A";
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -1,47 +0,0 @@
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

@@ -1,47 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,38 +0,0 @@
// 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

@@ -1,15 +0,0 @@
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

@@ -1,27 +0,0 @@
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

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

View File

@@ -1,14 +0,0 @@
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

@@ -61,22 +61,5 @@ 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,15 +0,0 @@
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

@@ -1,22 +0,0 @@
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

@@ -3,7 +3,6 @@
public enum WindowPixelSampleMode
{
BelowWindow,
AboveWindow,
WindowArea,
WindowEdge,
}

View File

@@ -8,11 +8,9 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
public class AlbumArtChangedEventArgs : 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;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
public Color? AlbumArtAccentColor { get; set; } = null;
}
}

View File

@@ -1,17 +0,0 @@
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

@@ -1,18 +0,0 @@
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,9 +4,8 @@ using System;
namespace BetterLyrics.WinUI3.Events
{
public class TimelineChangedEventArgs(TimeSpan position, TimeSpan end) : EventArgs()
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
{
public TimeSpan Position { get; set; } = position;
public TimeSpan End { get; set; } = end;
}
}

View File

@@ -1,19 +0,0 @@
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,51 +0,0 @@
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 ObservableCollection<GroupInfoList> GetGroupedBy<T>(
this IEnumerable<T> items,
Func<T, object> groupKeySelector,
Func<object, object>? orderSelector = null)
{
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

@@ -106,25 +106,18 @@ 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)
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, 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 screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
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);
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.WindowArea:
{

View File

@@ -1,12 +1,10 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services;
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;
@@ -75,12 +73,7 @@ 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>ԭTopMost״̬
@@ -97,7 +90,6 @@ namespace BetterLyrics.WinUI3.Helper
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
if (enable)
{
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ

View File

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

View File

@@ -107,12 +107,6 @@ 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,20 +0,0 @@
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,7 +4,6 @@ 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;
@@ -17,7 +16,6 @@ namespace BetterLyrics.WinUI3.Helper
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
private HWND _currentForeground = HWND.NULL;
private readonly IntPtr _selfHwnd;
private readonly ThrottleHelper _winEventProcThrottle = new(TimeSpan.FromSeconds(1));
public delegate void WindowChangedHandler(HWND hwnd);
private readonly WindowChangedHandler _onWindowChanged;

View File

@@ -1,46 +0,0 @@
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,15 +1,5 @@
// 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;
@@ -17,6 +7,10 @@ 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;
@@ -25,83 +19,125 @@ namespace BetterLyrics.WinUI3.Helper
{
public class ImageHelper
{
private const int _accentColorCount = 1;
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
{
using var stream = new InMemoryRandomAccessStream();
var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
stream.Seek(0);
return stream;
}
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
{
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();
var device = CanvasDevice.GetSharedDevice();
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
// 居中绘制文字
using (var ds = renderTarget.CreateDrawingSession())
{
// 绘制线性渐变背景
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
// 背景
ds.Clear(Colors.LightGray);
// 文字格式
var format = new CanvasTextFormat
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(width, height)
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,
};
ds.FillRectangle(0, 0, width, height, gradientBrush);
// 设定边距
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);
}
// 保存为 PNG 并转为 byte[]
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)))
using (var stream = new InMemoryRandomAccessStream())
{
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(buffer);
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;
}
return buffer;
}
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes, int count, bool? isDark = null)
public static List<Color> GetAccentColorsFromByte(byte[] bytes)
{
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)
// 使用 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)
.ToList();
return topColors;
// 转换为 Windows.UI.Color
return topColors
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
.ToList();
}
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
//{
// var stream = new InMemoryRandomAccessStream();
@@ -131,11 +167,9 @@ namespace BetterLyrics.WinUI3.Helper
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
using var reader = new DataReader(stream);
await reader.LoadAsync((uint)stream.Size);
byte[] buffer = new byte[stream.Size];
reader.ReadBytes(buffer);
return buffer;
using var memoryStream = new MemoryStream();
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
public static float GetAverageLuminance(CanvasBitmap bitmap)
@@ -154,70 +188,5 @@ 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

@@ -1,5 +1,4 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using NTextCat;
@@ -7,8 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using TinyPinyin;
using Windows.Globalization;
namespace BetterLyrics.WinUI3.Services
{
@@ -58,72 +55,63 @@ namespace BetterLyrics.WinUI3.Services
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
}
private static string? ThreeLetterToTwoLetter(string? threeLetterCode)
{
if (threeLetterCode == null) return null;
foreach (var ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
if (string.Equals(ci.ThreeLetterISOLanguageName, threeLetterCode, StringComparison.OrdinalIgnoreCase))
{
return ci.TwoLetterISOLanguageName;
}
}
return null;
}
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
string? code = ThreeLetterToTwoLetter(_identifier.Identify(text).FirstOrDefault()?.Item1.Iso639_2T);
if (code != null && code == "zh")
{
"simple" => "en",
"zh_classical" => "zh-Hant",
"zh_yue" => "zh-Hant",
"zh" => "zh-Hans",
_ => code
};
if (ChineseConverter.ConvertToTraditionalChinese(text) == text)
{
return "zh-Hant";
}
else
{
return "zh-Hans";
}
}
return code;
}
public static bool IsCJK(string text)
{
return DetectLanguageCode(text)?.Substring(0, 2) switch
return DetectLanguageCode(text) switch
{
"zh" or "ja" or "ko" => true,
_ => false
};
}
public static string ConvertToCountryCode(string? languageCode)
public static string DetectCountryCode(string? text)
{
if (languageCode == null) return "us";
return languageCode switch
{
"zh" => "cn",
"zh-Hans" => "cn",
"zh-Hant" => "tw",
"ja" => "jp",
"ko" => "kr",
_ => "us"
};
if (text == null) return "en";
var code = DetectLanguageCode(text);
if (code == null) return "en";
// 处理中文简体和繁体
if (code == "zh-Hans") return "cn";
if (code == "zh-Hant") return "cn";
// 其他语言直接返回两字母代码
return code;
}
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

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

View File

@@ -1,24 +0,0 @@
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

@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Helper
break;
}
}
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
PostProcessLyricsLines(durationMs.Value);
return _lyricsDataArr;
}
@@ -109,57 +109,49 @@ namespace BetterLyrics.WinUI3.Helper
// 按时间分组
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
int languageCount = 0;
if (grouped != null && grouped.Count > 0)
{
// 计算最大语言数量
languageCount = grouped.Max(g => g.Count());
}
int languageCount = grouped.Max(g => g.Count());
// 初始化每种语言的歌词列表
_lyricsDataArr.Clear();
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
for (int i = 0; i < languageCount; i++)
_lyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
if (grouped != null)
foreach (var group in grouped)
{
foreach (var group in grouped)
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
// 如果该语言有翻译,取对应行,否则用原文(第一行)
var (start, text, syllables) =
langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
var line = new LyricsLine
{
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
StartMs = start,
EndMs = 0, // 稍后统一修正
OriginalText = text,
CharTimings = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
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++)
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.CharTimings.Add(
new CharTiming
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
StartMs = charStart,
EndMs = 0, // Fixed later
Text = charText ?? "",
StartIndex = startIndex,
}
}
_lyricsDataArr[langIdx].LyricsLines.Add(line);
);
currentIndex += charText?.Length ?? 0;
}
// 没有翻译行则不补原文,直接跳过
}
_lyricsDataArr[langIdx].LyricsLines.Add(line);
}
}
}
@@ -170,7 +162,7 @@ namespace BetterLyrics.WinUI3.Helper
{
List<LyricsLine> originalLines = [];
List<LyricsLine> translationLines = [];
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
var xdoc = XDocument.Parse(raw);
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
if (body == null) return;
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
@@ -178,9 +170,7 @@ namespace BetterLyrics.WinUI3.Helper
{
// 句级时间
string? pBegin = p.Attribute("begin")?.Value;
string? pEnd = p.Attribute("end")?.Value;
int pStartMs = ParseTtmlTime(pBegin);
int pEndMs = ParseTtmlTime(pEnd);
// 只获取一级span且排除ttm:role="x-bg"的span
var spans = p.Elements()
@@ -196,31 +186,27 @@ namespace BetterLyrics.WinUI3.Helper
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
.ToList();
// 处理原文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;
}
}
// 拼接空白字符后的原文
// 原文(非 CJK 语言添加空格)
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
if (!LanguageHelper.IsCJK(originalText))
{
foreach (var span in originalTextSpans)
{
span.Value += " ";
}
originalText = string.Concat(originalTextSpans.Select(s => s.Value));
}
var originalCharTimings = new List<LyricsChar>();
var originalCharTimings = new List<CharTiming>();
int originalStartIndex = 0;
foreach (var span in originalTextSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
originalCharTimings.Add(new LyricsChar
originalCharTimings.Add(new CharTiming
{
StartMs = sStartMs,
EndMs = sEndMs,
EndMs = 0,
StartIndex = originalStartIndex,
Text = span.Value
});
@@ -232,25 +218,23 @@ namespace BetterLyrics.WinUI3.Helper
originalLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
EndMs = 0,
OriginalText = originalText,
LyricsChars = originalCharTimings,
CharTimings = originalCharTimings,
});
// 翻译
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
var translationCharTimings = new List<LyricsChar>();
var translationCharTimings = new List<CharTiming>();
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
translationCharTimings.Add(new CharTiming
{
StartMs = sStartMs,
EndMs = sEndMs,
EndMs = 0,
StartIndex = translationStartIndex,
Text = span.Value
});
@@ -261,9 +245,9 @@ namespace BetterLyrics.WinUI3.Helper
translationLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
EndMs = 0,
OriginalText = translationText,
LyricsChars = translationCharTimings,
CharTimings = translationCharTimings,
});
}
}
@@ -352,9 +336,9 @@ namespace BetterLyrics.WinUI3.Helper
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = lineRead.EndTime ?? 0,
EndMs = 0,
OriginalText = lineRead.Text,
LyricsChars = [],
CharTimings = [],
};
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
@@ -368,14 +352,22 @@ namespace BetterLyrics.WinUI3.Helper
)
{
var syllable = syllables[syllableIndex];
var charTiming = new LyricsChar
var charTiming = new CharTiming
{
StartMs = syllable.StartTime,
EndMs = syllable.EndTime,
EndMs = 0,
Text = syllable.Text,
StartIndex = startIndex,
};
lineWrite.LyricsChars.Add(charTiming);
if (syllableIndex + 1 < syllables.Count)
{
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
}
else
{
charTiming.EndMs = lineWrite.EndMs;
}
lineWrite.CharTimings.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
@@ -386,5 +378,57 @@ namespace BetterLyrics.WinUI3.Helper
_lyricsDataArr.Add(new LyricsData(lyricsLines));
}
private void PostProcessLyricsLines(int durationMs)
{
for (int langIdx = 0; langIdx < _lyricsDataArr.Count; langIdx++)
{
var lines = _lyricsDataArr[langIdx].LyricsLines;
for (int i = 0; i < lines.Count; i++)
{
if (i + 1 < lines.Count)
{
lines[i].EndMs = lines[i + 1].StartMs;
}
else
{
lines[i].EndMs = durationMs;
}
// 修正 CharTimings 的 EndMs
var timings = lines[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 = lines[i].EndMs;
}
}
}
}
if (lines.Count > 0)
{
if (lines[0].StartMs > 0)
{
lines.Insert(
0,
new LyricsLine
{
StartMs = 0,
EndMs = lines[0].StartMs,
OriginalText = "● ● ●",
CharTimings = [],
}
);
}
}
}
}
}
}

View File

@@ -14,6 +14,9 @@ namespace BetterLyrics.WinUI3.Helper
public static class MetadataHelper
{
public const string AppAuthor = "Zhe Fang";
public const string AppDisplayName = "Better Lyrics";
public const string AppName = "BetterLyrics";
public static string AppVersion
{
get
@@ -22,5 +25,23 @@ namespace BetterLyrics.WinUI3.Helper
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
}
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 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;
}
}
}

View File

@@ -1,64 +0,0 @@
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

@@ -1,26 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -15,24 +15,7 @@ namespace BetterLyrics.WinUI3.Helper
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 LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
@@ -50,7 +33,6 @@ namespace BetterLyrics.WinUI3.Helper
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");
@@ -67,7 +49,6 @@ namespace BetterLyrics.WinUI3.Helper
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(QQTranslationCacheDirectory);
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}

View File

@@ -1,74 +0,0 @@
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

@@ -1,37 +0,0 @@
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

@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
{
if (!_isTransitioning) return;
_progress += (float)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
if (_progress >= 1f)
{
_progress = 1f;

View File

@@ -26,6 +26,17 @@ 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)
@@ -37,33 +48,33 @@ namespace BetterLyrics.WinUI3.Helper
}
return default;
}
public static void OpenWindow<T>()
public static void OpenOrShowWindow<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))
{
window = new LyricsWindow();
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
newWindow = new LyricsWindow();
((LyricsWindow)newWindow).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
}
else if (typeof(T) == typeof(SettingsWindow))
{
window = new SettingsWindow();
}
else if (typeof(T) == typeof(MusicGalleryWindow))
{
window = new MusicGalleryWindow();
newWindow = new SettingsWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
TrackWindow(window);
((Window)newWindow).Activate();
TrackWindow(newWindow);
}
var castedWindow = (Window)window;
castedWindow.Restore();
castedWindow.Activate();
}
public static void RestartApp(string args = "")
@@ -86,32 +97,10 @@ 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

@@ -1,12 +1,10 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Helper;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsChar
public class CharTiming
{
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;

View File

@@ -1,25 +0,0 @@
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 LocalMediaFolder : ObservableObject
public partial class LocalLyricsFolder : ObservableObject
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
@@ -12,9 +12,9 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Path { get; set; }
public LocalMediaFolder() { }
public LocalLyricsFolder() { }
public LocalMediaFolder(string path, bool isEnabled)
public LocalLyricsFolder(string path, bool isEnabled)
{
Path = path;
IsEnabled = isEnabled;

View File

@@ -1,12 +1,10 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using Lyricify.Lyrics.Helpers.General;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StringHelper = BetterLyrics.WinUI3.Helper.StringHelper;
namespace BetterLyrics.WinUI3.Models
{
@@ -26,43 +24,24 @@ namespace BetterLyrics.WinUI3.Models
LyricsLines = lyricsLines;
}
public void SetDisplayedTextAlongWith(LyricsData translationData, string separator, int toleranceMs = 0)
public void SetDisplayedTextAlongWith(LyricsData translationData)
{
int i = 0;
foreach (var line in LyricsLines)
{
// 在翻译歌词中查找与当前行开始时间最接近且在容忍范围内的行
var transLine = translationData.LyricsLines
.FirstOrDefault(t => Math.Abs(t.StartMs - line.StartMs) <= toleranceMs);
if (transLine != null)
if (i >= translationData.LyricsLines.Count)
{
if (translationData.LanguageCode?.StartsWith("zh") == true)
{
string tmp = "";
if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hant")
{
tmp = ChineseConverter.ConvertToTraditionalChinese(transLine.OriginalText);
}
else if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hans")
{
tmp = ChineseConverter.ConvertToSimplifiedChinese(transLine.OriginalText);
}
line.DisplayedText = $"{line.OriginalText}{separator}{tmp}";
}
else
{
line.DisplayedText = $"{line.OriginalText}{separator}{transLine.OriginalText}";
}
line.DisplayedText = line.OriginalText; // No translation available, keep original text
}
else
{
// 没有匹配的翻译,翻译部分留空
line.DisplayedText = $"{line.OriginalText}";
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationData.LyricsLines[i].OriginalText})";
}
i++;
}
}
public void SetDisplayedTextAlongWith(string translation, string separator)
public void SetDisplayedTextAlongWith(string translation)
{
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
@@ -74,7 +53,7 @@ namespace BetterLyrics.WinUI3.Models
}
else
{
line.DisplayedText = $"{line.OriginalText}{separator}{translationArr[i]}";
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationArr[i]})";
}
i++;
}
@@ -88,30 +67,6 @@ namespace BetterLyrics.WinUI3.Models
}
}
public LyricsData CreateLyricsDataFrom(string translation)
{
var result = new LyricsData(LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
}).ToList());
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
foreach (var line in result.LyricsLines)
{
if (i >= translationArr.Count)
{
break;
}
else
{
line.OriginalText = translationArr[i];
}
i++;
}
return result;
}
public static LyricsData GetNotfoundPlaceholder(int durationMs)
{
return new LyricsData([new LyricsLine
@@ -119,7 +74,7 @@ namespace BetterLyrics.WinUI3.Models
StartMs = 0,
EndMs = durationMs,
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
LyricsChars = [],
CharTimings = [],
}]);
}
@@ -132,7 +87,7 @@ namespace BetterLyrics.WinUI3.Models
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = "● ● ●",
DisplayedText = "● ● ●",
LyricsChars = [],
CharTimings = [],
},
]);
}

View File

@@ -1,131 +1,33 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System.Collections.Generic;
using System.Numerics;
using Windows.UI;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Text;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
private const float _animationDuration = 0.3f;
public ValueTransition<float> AngleTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> OpacityTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> ScaleTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> AngleTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> OpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> ScaleTransition { get; set; } = new(initialValue: 0.95f, durationSeconds: _animationDuration);
public CanvasTextLayout? CanvasTextLayout { get; private set; }
public CanvasTextLayout? CanvasTextLayout { get; set; }
public Vector2 CenterPosition { get; private set; }
public Vector2 CenterPosition { get; set; }
public Vector2 Position { get; set; }
public List<LyricsChar> LyricsChars { get; set; } = [];
public List<CharTiming> CharTimings { get; set; } = [];
public int? DurationMs => EndMs - StartMs;
public int? EndMs { get; set; }
public int DurationMs => EndMs - StartMs;
public int EndMs { get; set; }
public int StartMs { get; set; }
public string DisplayedText { get; set; } = "";
public string OriginalText { get; set; } = "";
public CanvasGeometry? TextGeometry { get; private set; }
public CanvasCommandList? BackgroundFontEffect { get; private set; }
public CanvasCommandList? ForegroundFontEffect { get; private set; }
public void UpdateCenterPosition(float maxWidth, TextAlignmentType type)
{
if (CanvasTextLayout == null)
{
return;
}
float centerY = Position.Y + (float)CanvasTextLayout.LayoutBounds.Height;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(Position.X, centerY),
TextAlignmentType.Center => new Vector2(Position.X + maxWidth / 2, centerY),
TextAlignmentType.Right => new Vector2(Position.X + maxWidth, centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void UpdateTextLayout(ICanvasAnimatedControl control, CanvasTextFormat textFormat, float maxWidth, float maxHeight, TextAlignmentType type)
{
CanvasTextLayout?.Dispose();
CanvasTextLayout = null;
CanvasTextLayout = new CanvasTextLayout(control, DisplayedText, textFormat, maxWidth, maxHeight);
CanvasTextLayout.HorizontalAlignment = type.ToCanvasHorizontalAlignment();
}
public void DisposeTextGeometry()
{
TextGeometry?.Dispose();
TextGeometry = null;
}
public void UpdateTextGeometry()
{
DisposeTextGeometry();
if (CanvasTextLayout == null)
{
return;
}
TextGeometry = CanvasGeometry.CreateText(CanvasTextLayout);
}
public void DisposeFontEffects()
{
BackgroundFontEffect?.Dispose();
BackgroundFontEffect = null;
ForegroundFontEffect?.Dispose();
ForegroundFontEffect = null;
}
public void UpdateFontEffect(ICanvasAnimatedControl control, bool drawStroke, Color strokeColor, int strokeWidth, Color fontColor)
{
DisposeFontEffects();
if (TextGeometry == null)
{
return;
}
BackgroundFontEffect = new CanvasCommandList(control);
using var bgFontEffectDs = BackgroundFontEffect.CreateDrawingSession();
ForegroundFontEffect = new CanvasCommandList(control);
using var fgFontEffectDs = ForegroundFontEffect.CreateDrawingSession();
if (drawStroke)
{
bgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
fgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
}
bgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
fgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
}
}
}

View File

@@ -2,10 +2,6 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace BetterLyrics.WinUI3.Models
{
@@ -17,39 +13,12 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Provider { get; set; }
[ObservableProperty]
public partial bool IsLastFMTrackEnabled { get; set; }
[ObservableProperty]
public partial int TimelineSyncThreshold { get; set; }
[ObservableProperty]
public partial int PositionOffset { get; set; }
[ObservableProperty]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
public MediaSourceProviderInfo() { }
public MediaSourceProviderInfo(string provider)
public MediaSourceProviderInfo(string provider, bool isEnabled)
{
Provider = provider;
IsEnabled = true;
IsLastFMTrackEnabled = false;
if (provider == Constants.PlayerID.AppleMusic)
{
TimelineSyncThreshold = PositionOffset = 1000;
}
else
{
TimelineSyncThreshold = 0;
PositionOffset = 0;
}
ResetPositionOffsetOnSongChanged = false;
LyricsSearchProvidersInfo = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
IsEnabled = isEnabled;
}
}

View File

@@ -1,19 +0,0 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class PlayQueueItem
{
public Track Track { get; set; }
public PlayQueueItem(Track track)
{
Track = track;
}
}
}

View File

@@ -14,9 +14,6 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Artist { get; set; }
[ObservableProperty]
public partial int? Duration { get; set; }
[ObservableProperty]
public partial double? DurationMs { get; set; }

View File

@@ -1,35 +0,0 @@
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Models
{
public class SongsTabInfo
{
public string Name { get; set; }
public string Icon { get; set; }
public bool IsClosable { get; set; }
public CommonSongProperty FilterProperty { get; set; }
public string FilterValue { get; set; }
public SongsTabInfo()
{
Name = string.Empty;
Icon = string.Empty;
IsClosable = true;
FilterProperty = CommonSongProperty.Title;
FilterValue = string.Empty;
}
public SongsTabInfo(string name, string icon, bool isClosable, CommonSongProperty filterProperty, string filterValue)
{
Name = name;
Icon = icon;
IsClosable = isClosable;
FilterProperty = filterProperty;
FilterValue = filterValue;
}
}
}

View File

@@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class TrimmedTrack
{
public string Title { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public int? Year { get; set; }
public string Genre { get; set; }
public string FilePath { get; set; }
public int Duration { get; set; }
public byte[]? AlbumArt { get; set; }
}
}

View File

@@ -1,8 +1,9 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
namespace BetterLyrics.WinUI3.Renderer
{

View File

@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Serialization
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
[JsonSerializable(typeof(List<LocalMediaFolder>))]
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(TranslateResponse))]
[JsonSerializable(typeof(JsonElement))]

View File

@@ -1,21 +1,18 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
namespace BetterLyrics.WinUI3.Services
{
public class AlbumArtSearchService : IAlbumArtSearchService
{
@@ -51,11 +48,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
result = bytesFromSMTC;
break;
case AlbumArtSearchProvider.iTunes:
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
{
result = await SearchiTunesAsync(artist, album, title, countryCode);
if (result != null) break;
}
result = await SearchiTunesAsync(artist, album);
break;
default:
break;
@@ -68,7 +61,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
private byte[]? SearchFile(string artist, string album)
{
foreach (var folder in _settingsService.LocalMediaFolders)
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
@@ -89,7 +82,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
return null;
}
private async Task<byte[]?> SearchiTunesAsync(string artist, string album, string title, string countryCode)
private async Task<byte[]?> SearchiTunesAsync(string artist, string album)
{
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
try
@@ -103,10 +96,11 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
}
// Build the iTunes API URL
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + LanguageHelper.DetectCountryCode(album + artist) + "&entity=album";
url.Replace(" ", "-");
// Make a request to the API
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();

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