mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad79180e4 | ||
|
|
86118bac02 | ||
|
|
7bfbec4b01 | ||
|
|
a5d6dd1305 | ||
|
|
68f690e1a7 | ||
|
|
42af22a7e3 | ||
|
|
34d7f3f319 | ||
|
|
07b82191d0 | ||
|
|
f8c6060d32 | ||
|
|
bfdb36ff95 | ||
|
|
ce83777c1d | ||
|
|
d709e70fa2 | ||
|
|
8fe4f8fd58 | ||
|
|
b6319e522a | ||
|
|
58d74c1515 | ||
|
|
806f3fdd63 | ||
|
|
90d2055dff | ||
|
|
509079e8c7 | ||
|
|
a29e5c98f8 | ||
|
|
78a6ba8e1f | ||
|
|
352ceca81d | ||
|
|
c50c180aa0 | ||
|
|
2f99d44b86 | ||
|
|
03386e72b2 | ||
|
|
54ba0a0c85 | ||
|
|
7bf8b2894d | ||
|
|
875da76e6b | ||
|
|
547ca6d631 | ||
|
|
60fb088bea | ||
|
|
3a89236af0 | ||
|
|
7d16bdbc88 | ||
|
|
812d23a101 | ||
|
|
4381a34191 | ||
|
|
6e21e5636b |
69
.github/workflows/pages.yml
vendored
Normal file
69
.github/workflows/pages.yml
vendored
Normal 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
|
||||
20
.github/workflows/releases-to-discord.yml
vendored
Normal file
20
.github/workflows/releases-to-discord.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
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: "Release Changelog"
|
||||
avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
|
||||
content: "||@everyone||"
|
||||
footer_title: "Changelog"
|
||||
reduce_headings: true
|
||||
@@ -5,52 +5,62 @@
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18"
|
||||
IgnorableNamespaces="uap rescap uap18">
|
||||
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.6.0" />
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.11.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>BetterLyrics</DisplayName>
|
||||
<PublisherDisplayName>founchoo</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Properties>
|
||||
<DisplayName>BetterLyrics</DisplayName>
|
||||
<PublisherDisplayName>founchoo</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
|
||||
</Dependencies>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="en-US"/>
|
||||
<Resource Language="zh-CN"/>
|
||||
<Resource Language="zh-TW"/>
|
||||
<Resource Language="ja-JP"/>
|
||||
<Resource Language="ko-KR"/>
|
||||
</Resources>
|
||||
<Resources>
|
||||
<Resource Language="en-US"/>
|
||||
<Resource Language="zh-CN"/>
|
||||
<Resource Language="zh-TW"/>
|
||||
<Resource Language="ja-JP"/>
|
||||
<Resource Language="ko-KR"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="BetterLyrics"
|
||||
Description="BetterLyrics.WinUI3 (Package)"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="BetterLyrics"
|
||||
Description="BetterLyrics.WinUI3 (Package)"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap5:Extension
|
||||
Category="windows.startupTask">
|
||||
<uap5:StartupTask
|
||||
TaskId="AutoStartup"
|
||||
Enabled="false"
|
||||
DisplayName="BetterLyrics" />
|
||||
</uap5:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
||||
@@ -47,13 +47,14 @@
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
|
||||
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
|
||||
|
||||
<!-- Style (inc. the correct spacing) of a section header -->
|
||||
<!-- Style -->
|
||||
<Style
|
||||
x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
@@ -63,14 +64,16 @@
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Padding" Value="16,9,16,11" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
@@ -86,11 +89,14 @@
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -15,34 +12,27 @@ using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Serilog;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
using ShadowViewer.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _logger
|
||||
/// </summary>
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
#endregion
|
||||
public static new App Current => (App)Application.Current;
|
||||
public static DispatcherQueue? DispatcherQueue { get; private set; }
|
||||
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
|
||||
public static ResourceLoader? ResourceLoader { get; private set; }
|
||||
|
||||
#region Constructors
|
||||
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
|
||||
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -52,10 +42,10 @@ namespace BetterLyrics.WinUI3
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
AppInfo.EnsureDirectories();
|
||||
PathHelper.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
|
||||
_logger = Ioc.Default.GetService<ILogger<App>>()!;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
@@ -63,51 +53,20 @@ namespace BetterLyrics.WinUI3
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Current
|
||||
/// </summary>
|
||||
public static new App Current => (App)Application.Current;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DispatcherQueue
|
||||
/// </summary>
|
||||
public static DispatcherQueue? DispatcherQueue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DispatcherQueueTimer
|
||||
/// </summary>
|
||||
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ResourceLoader
|
||||
/// </summary>
|
||||
public static ResourceLoader? ResourceLoader { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
WindowHelper.OpenLyricsWindow();
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConfigureServices
|
||||
/// </summary>
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
|
||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
// Register services
|
||||
@@ -121,72 +80,40 @@ namespace BetterLyrics.WinUI3
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IPlaybackService, PlaybackService>()
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
|
||||
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
.AddSingleton<ITranslateService, TranslateService>()
|
||||
// ViewModels
|
||||
.AddTransient<HostWindowViewModel>()
|
||||
.AddSingleton<LyricsWindowViewModel>()
|
||||
.AddSingleton<SettingsWindowViewModel>()
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.AddSingleton<SettingsPageViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
.AddSingleton<LyricsSettingsControlViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The App_UnhandledException
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="object"/></param>
|
||||
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.UnhandledExceptionEventArgs"/></param>
|
||||
private void App_UnhandledException(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
|
||||
)
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
_logger.LogError(e.Exception, "App_UnhandledException");
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CurrentDomain_FirstChanceException
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="object?"/></param>
|
||||
/// <param name="e">The e<see cref="System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs"/></param>
|
||||
private void CurrentDomain_FirstChanceException(
|
||||
object? sender,
|
||||
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
|
||||
)
|
||||
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
|
||||
{
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CurrentDomain_UnhandledException
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="object"/></param>
|
||||
/// <param name="e">The e<see cref="System.UnhandledExceptionEventArgs"/></param>
|
||||
private void CurrentDomain_UnhandledException(
|
||||
object sender,
|
||||
System.UnhandledExceptionEventArgs e
|
||||
)
|
||||
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TaskScheduler_UnobservedTaskException
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="object?"/></param>
|
||||
/// <param name="e">The e<see cref="UnobservedTaskExceptionEventArgs"/></param>
|
||||
private void TaskScheduler_UnobservedTaskException(
|
||||
object? sender,
|
||||
UnobservedTaskExceptionEventArgs e
|
||||
)
|
||||
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
//_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
56094
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Core14.profile.xml
Normal file
56094
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Core14.profile.xml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Discord.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Discord.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/QQ.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/QQ.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
@@ -19,8 +19,9 @@
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\DependenciesSettingsExpander.xaml" />
|
||||
<None Remove="Assets\Core14.profile.xml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
@@ -40,6 +41,7 @@
|
||||
<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" />
|
||||
@@ -47,20 +49,21 @@
|
||||
<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="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<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="6.26.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\AI - 甜度爆表.mp3">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<PackageReference Include="z440.atl.core" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
@@ -77,12 +80,12 @@
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\DependenciesSettingsExpander.xaml">
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.DependenciesSettingsExpander"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="DependenciesSettingsExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard Header="CommunityToolkit.Labs.WinUI.MarqueeText">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.MarqueeText" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.MarqueeText" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.Labs.WinUI.OpacityMaskView">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.OpacityMaskView" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.OpacityMaskView" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.Mvvm">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.Mvvm" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.Mvvm" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Behaviors">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Behaviors" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Behaviors" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Controls.Primitives">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Primitives" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Controls.Segmented">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Segmented" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Segmented" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Controls.SettingsControls">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.SettingsControls" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Converters">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Converters" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Converters" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Extensions">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Extensions" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Extensions" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Helpers">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Helpers" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Helpers" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="CommunityToolkit.WinUI.Media">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Media" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Media" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="H.NotifyIcon.WinUI">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/H.NotifyIcon.WinUI" NavigateUri="https://www.nuget.org/packages/H.NotifyIcon.WinUI" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Lyricify.Lyrics.Helper-NativeAot">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Lyricify.Lyrics.Helper-NativeAot" NavigateUri="https://www.nuget.org/packages/Lyricify.Lyrics.Helper-NativeAot" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Microsoft.Extensions.DependencyInjection">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection" NavigateUri="https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Microsoft.Extensions.Logging">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Extensions.Logging" NavigateUri="https://www.nuget.org/packages/Microsoft.Extensions.Logging" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Microsoft.Graphics.Win2D">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Graphics.Win2D" NavigateUri="https://www.nuget.org/packages/Microsoft.Graphics.Win2D" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Microsoft.Windows.SDK.BuildTools">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools" NavigateUri="https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Microsoft.WindowsAppSDK">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.WindowsAppSDK" NavigateUri="https://www.nuget.org/packages/Microsoft.WindowsAppSDK" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Microsoft.Xaml.Behaviors.WinUI.Managed">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.WinUI.Managed" NavigateUri="https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Serilog.Extensions.Logging">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Serilog.Extensions.Logging" NavigateUri="https://www.nuget.org/packages/Serilog.Extensions.Logging" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Serilog.Sinks.File">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Serilog.Sinks.File" NavigateUri="https://www.nuget.org/packages/Serilog.Sinks.File" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="System.Drawing.Common">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/System.Drawing.Common" NavigateUri="https://www.nuget.org/packages/System.Drawing.Common" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="System.Text.Encoding.CodePages">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/System.Text.Encoding.CodePages" NavigateUri="https://www.nuget.org/packages/System.Text.Encoding.CodePages" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="TagLibSharp">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/TagLibSharp" NavigateUri="https://www.nuget.org/packages/TagLibSharp" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="Ude.NetStandard">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/Ude.NetStandard" NavigateUri="https://www.nuget.org/packages/Ude.NetStandard" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="WinUIEx">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/WinUIEx" NavigateUri="https://www.nuget.org/packages/WinUIEx" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard Header="z440.atl.core">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://www.nuget.org/packages/z440.atl.core" NavigateUri="https://www.nuget.org/packages/z440.atl.core" />
|
||||
</controls:SettingsCard.Description>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class DependenciesSettingsExpander : UserControl
|
||||
{
|
||||
public DependenciesSettingsExpander()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is AlbumArtSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
AlbumArtSearchProvider.Local => App.ResourceLoader!.GetString("AlbumArtSearchLocalProvider"),
|
||||
AlbumArtSearchProvider.SMTC => App.ResourceLoader!.GetString("AlbumArtSearchSMTCProvider"),
|
||||
AlbumArtSearchProvider.iTunes => "iTunes",
|
||||
_ => throw new Exception($"Unknown AlbumArtSearchProvider: {provider}"),
|
||||
};
|
||||
}
|
||||
throw new ArgumentException("Value must be of type AlbumArtSearchProvider", nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,8 @@ using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ColorToBrushConverter" />
|
||||
/// </summary>
|
||||
public partial class ColorToBrushConverter : IValueConverter
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Convert
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Color color)
|
||||
@@ -31,19 +18,9 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
return new SolidColorBrush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
@@ -13,10 +9,9 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
if (value is Microsoft.UI.Xaml.CornerRadius cornerRadius)
|
||||
{
|
||||
// Convert CornerRadius to an integer value, e.g., using the top-left radius
|
||||
return (double)cornerRadius.TopLeft;
|
||||
}
|
||||
return .0; // or handle the case where value is not a CornerRadius
|
||||
return .0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
|
||||
@@ -5,21 +5,8 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="EnumToIntConverter" />
|
||||
/// </summary>
|
||||
internal partial class EnumToIntConverter : IValueConverter
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Convert
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Enum)
|
||||
@@ -29,14 +16,6 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int && targetType.IsEnum)
|
||||
@@ -45,7 +24,5 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
}
|
||||
return Enum.ToObject(targetType, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,21 +5,8 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IntToCornerRadius" />
|
||||
/// </summary>
|
||||
public partial class IntToCornerRadius : IValueConverter
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Convert
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int intValue && parameter is double controlHeight)
|
||||
@@ -29,19 +16,9 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
return new Microsoft.UI.Xaml.CornerRadius(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,73 +6,32 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsSearchProviderToDisplayNameConverter" />
|
||||
/// </summary>
|
||||
public partial class LyricsSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Convert
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLrcLib"
|
||||
),
|
||||
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderQQ"
|
||||
),
|
||||
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderNetease"
|
||||
),
|
||||
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderKugou"
|
||||
),
|
||||
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderAmllTtmlDb"
|
||||
),
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalLrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalMusicFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderEslrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderTtmlFile"
|
||||
),
|
||||
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"),
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,8 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MatchedLocalFilesPathToVisibilityConverter" />
|
||||
/// </summary>
|
||||
public partial class MatchedLocalFilesPathToVisibilityConverter : IValueConverter
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Convert
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string path)
|
||||
@@ -37,19 +24,9 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="object"/></param>
|
||||
/// <param name="targetType">The targetType<see cref="Type"/></param>
|
||||
/// <param name="parameter">The parameter<see cref="object"/></param>
|
||||
/// <param name="language">The language<see cref="string"/></param>
|
||||
/// <returns>The <see cref="object"/></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum AlbumArtSearchProvider
|
||||
{
|
||||
Local,
|
||||
SMTC,
|
||||
iTunes,
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,10 @@
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the AutoStartWindowType
|
||||
/// </summary>
|
||||
public enum AutoStartWindowType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the StandardMode
|
||||
/// </summary>
|
||||
StandardMode,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the DockMode
|
||||
/// </summary>
|
||||
DockMode,
|
||||
DesktopMode,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,45 +1,13 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the BackdropType
|
||||
/// </summary>
|
||||
public enum BackdropType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the None
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Mica
|
||||
/// </summary>
|
||||
Mica = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the MicaAlt
|
||||
/// </summary>
|
||||
MicaAlt = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the DesktopAcrylic
|
||||
/// </summary>
|
||||
DesktopAcrylic = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Transparent
|
||||
/// </summary>
|
||||
Transparent = 4,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -2,38 +2,19 @@
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the EasingType
|
||||
/// </summary>
|
||||
public enum EasingType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the EaseInOutQuad
|
||||
/// </summary>
|
||||
EaseInOutQuad,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the EaseInQuad
|
||||
/// </summary>
|
||||
EaseInQuad,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the EaseOutQuad
|
||||
/// </summary>
|
||||
EaseOutQuad,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Linear
|
||||
/// </summary>
|
||||
Linear,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SmootherStep
|
||||
/// </summary>
|
||||
SmootherStep,
|
||||
SmoothStep,
|
||||
EaseInOutSine,
|
||||
EaseInOutQuad,
|
||||
EaseInOutCubic,
|
||||
EaseInOutQuart,
|
||||
EaseInOutQuint,
|
||||
EaseInOutExpo,
|
||||
EaseInOutCirc,
|
||||
EaseInOutBack,
|
||||
EaseInOutElastic,
|
||||
EaseInOutBounce,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -8,43 +8,13 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Language
|
||||
/// </summary>
|
||||
public enum Language
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the FollowSystem
|
||||
/// </summary>
|
||||
FollowSystem,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the English
|
||||
/// </summary>
|
||||
English,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SimplifiedChinese
|
||||
/// </summary>
|
||||
SimplifiedChinese,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the TraditionalChinese
|
||||
/// </summary>
|
||||
TraditionalChinese,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Japanese
|
||||
/// </summary>
|
||||
Japanese,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Korean
|
||||
/// </summary>
|
||||
Korean,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -2,20 +2,10 @@
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LineMaskType
|
||||
/// </summary>
|
||||
public enum LineRenderingType
|
||||
{
|
||||
UntilCurrentChar,
|
||||
|
||||
/// <summary>
|
||||
/// Current char only
|
||||
/// </summary>
|
||||
CurrentCharOnly,
|
||||
CurrentChar,
|
||||
LineStartToCurrentChar,
|
||||
CurrentLine
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalSearchTargetProps
|
||||
/// </summary>
|
||||
public enum LocalSearchTargetProps
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the LyricsOnly
|
||||
/// </summary>
|
||||
LyricsOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsAndAlbumArt
|
||||
/// </summary>
|
||||
LyricsAndAlbumArt,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsAlignmentType
|
||||
/// </summary>
|
||||
public enum LyricsAlignmentType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the Left
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Center
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Right
|
||||
/// </summary>
|
||||
Right,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -2,33 +2,11 @@
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsDisplayType
|
||||
/// </summary>
|
||||
public enum LyricsDisplayType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the AlbumArtOnly
|
||||
/// </summary>
|
||||
AlbumArtOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsOnly
|
||||
/// </summary>
|
||||
LyricsOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SplitView
|
||||
/// </summary>
|
||||
SplitView,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the PlaceholderOnly
|
||||
/// </summary>
|
||||
PlaceholderOnly,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,30 +1,11 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFontColorType
|
||||
/// </summary>
|
||||
public enum LyricsFontColorType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the Default
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Dominant
|
||||
/// </summary>
|
||||
Dominant,
|
||||
AdaptiveColored,
|
||||
AdaptiveGrayed,
|
||||
Custom,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,92 +1,28 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using Microsoft.UI.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Text;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFontWeight
|
||||
/// </summary>
|
||||
public enum LyricsFontWeight
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the Thin
|
||||
/// </summary>
|
||||
Thin,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ExtraLight
|
||||
/// </summary>
|
||||
ExtraLight,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Light
|
||||
/// </summary>
|
||||
Light,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SemiLight
|
||||
/// </summary>
|
||||
SemiLight,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Normal
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Medium
|
||||
/// </summary>
|
||||
Medium,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SemiBold
|
||||
/// </summary>
|
||||
SemiBold,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Bold
|
||||
/// </summary>
|
||||
Bold,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ExtraBold
|
||||
/// </summary>
|
||||
ExtraBold,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Black
|
||||
/// </summary>
|
||||
Black,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ExtraBlack
|
||||
/// </summary>
|
||||
ExtraBlack,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsFontWeightExtensions" />
|
||||
/// </summary>
|
||||
public static class LyricsFontWeightExtensions
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The ToFontWeight
|
||||
/// </summary>
|
||||
/// <param name="weight">The weight<see cref="LyricsFontWeight"/></param>
|
||||
/// <returns>The <see cref="FontWeight"/></returns>
|
||||
public static FontWeight ToFontWeight(this LyricsFontWeight weight)
|
||||
{
|
||||
return weight switch
|
||||
@@ -102,14 +38,8 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LyricsFontWeight.ExtraBold => FontWeights.ExtraBold,
|
||||
LyricsFontWeight.Black => FontWeights.Black,
|
||||
LyricsFontWeight.ExtraBlack => FontWeights.ExtraBlack,
|
||||
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(
|
||||
nameof(weight),
|
||||
weight,
|
||||
null
|
||||
),
|
||||
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(nameof(weight), weight, null),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,51 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFormat
|
||||
/// </summary>
|
||||
public enum LyricsFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the Lrc
|
||||
/// </summary>
|
||||
Lrc,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Eslrc
|
||||
/// </summary>
|
||||
Eslrc,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Ttml
|
||||
/// </summary>
|
||||
Ttml,
|
||||
Qrc,
|
||||
Krc,
|
||||
NotSpecified,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsFormatExtensions" />
|
||||
/// </summary>
|
||||
public static class LyricsFormatExtensions
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Detect
|
||||
/// </summary>
|
||||
/// <param name="content">The content<see cref="string"/></param>
|
||||
/// <returns>The <see cref="LyricsFormat?"/></returns>
|
||||
public static LyricsFormat? DetectFormat(this string content)
|
||||
{
|
||||
if (
|
||||
content.StartsWith("<?xml")
|
||||
&& System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b")
|
||||
)
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return null;
|
||||
|
||||
// TTML: 检查 <tt ... xmlns="http://www.w3.org/ns/ttml"
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"<tt\b[^>]*\bxmlns\s*=\s*[""']http://www\.w3\.org/ns/ttml[""']",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
return LyricsFormat.Ttml;
|
||||
}
|
||||
// 检测标准LRC和增强型LRC
|
||||
else if (
|
||||
System.Text.RegularExpressions.Regex.IsMatch(content, @"\[\d{1,2}:\d{2}")
|
||||
|| System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"<\d{1,2}:\d{2}\.\d{2,3}>"
|
||||
)
|
||||
)
|
||||
// KRC: 检测主内容格式 [start,duration]<offset,duration,0>字...
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"^\[\d+,\d+\](<\d+,\d+,0>.+)+",
|
||||
System.Text.RegularExpressions.RegexOptions.Multiline))
|
||||
{
|
||||
return LyricsFormat.Krc;
|
||||
}
|
||||
// QRC: 检测主内容格式 [start,duration]字(offset,duration)
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"^\[\d+,\d+\].*?\(\d+,\d+\)",
|
||||
System.Text.RegularExpressions.RegexOptions.Multiline))
|
||||
{
|
||||
return LyricsFormat.Qrc;
|
||||
}
|
||||
// 标准LRC和增强型LRC
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(content, @"\[\d{1,2}:\d{2}") ||
|
||||
System.Text.RegularExpressions.Regex.IsMatch(content, @"<\d{1,2}:\d{2}\.\d{2,3}>"))
|
||||
{
|
||||
return LyricsFormat.Lrc;
|
||||
}
|
||||
@@ -74,11 +55,6 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ToFileExtension
|
||||
/// </summary>
|
||||
/// <param name="format">The format<see cref="LyricsFormat"/></param>
|
||||
/// <returns>The <see cref="string"/></returns>
|
||||
public static string ToFileExtension(this LyricsFormat format)
|
||||
{
|
||||
return format switch
|
||||
@@ -91,7 +67,5 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
_ => ".*",
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsPlayingState
|
||||
/// </summary>
|
||||
public enum LyricsPlayingState
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the NotPlayed
|
||||
/// </summary>
|
||||
NotPlayed,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Playing
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Played
|
||||
/// </summary>
|
||||
Played,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -4,71 +4,30 @@ using BetterLyrics.WinUI3.Helper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsSearchProvider
|
||||
/// </summary>
|
||||
public enum LyricsSearchProvider
|
||||
{
|
||||
QQ,
|
||||
Kugou,
|
||||
Netease,
|
||||
LrcLib,
|
||||
|
||||
AmllTtmlDb,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalMusicFile
|
||||
/// </summary>
|
||||
LocalMusicFile,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalLrcFile
|
||||
/// </summary>
|
||||
LocalLrcFile,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalEslrcFile
|
||||
/// </summary>
|
||||
LocalEslrcFile,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalTtmlFile
|
||||
/// </summary>
|
||||
LocalTtmlFile,
|
||||
}
|
||||
|
||||
public static class LyricsSearchProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The IsLocal
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider<see cref="LyricsSearchProvider"/></param>
|
||||
/// <returns>The <see cref="bool"/></returns>
|
||||
public static bool IsLocal(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider
|
||||
is LyricsSearchProvider.LocalMusicFile
|
||||
or LyricsSearchProvider.LocalLrcFile
|
||||
or LyricsSearchProvider.LocalEslrcFile
|
||||
or LyricsSearchProvider.LocalTtmlFile;
|
||||
}
|
||||
|
||||
public static bool IsRemote(this LyricsSearchProvider provider)
|
||||
{
|
||||
return !provider.IsLocal();
|
||||
}
|
||||
|
||||
public static string GetCacheDirectory(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => AppInfo.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => AppInfo.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => AppInfo.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => AppInfo.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => AppInfo.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
}
|
||||
@@ -88,7 +47,19 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
_ => LyricsFormat.NotSpecified,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
public static bool IsLocal(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider
|
||||
is LyricsSearchProvider.LocalMusicFile
|
||||
or LyricsSearchProvider.LocalLrcFile
|
||||
or LyricsSearchProvider.LocalEslrcFile
|
||||
or LyricsSearchProvider.LocalTtmlFile;
|
||||
}
|
||||
|
||||
public static bool IsRemote(this LyricsSearchProvider provider)
|
||||
{
|
||||
return !provider.IsLocal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsStatus
|
||||
/// </summary>
|
||||
public enum LyricsStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the NotFound
|
||||
/// </summary>
|
||||
NotFound,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Found
|
||||
/// </summary>
|
||||
Found,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Loading
|
||||
/// </summary>
|
||||
Loading,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsType
|
||||
/// </summary>
|
||||
public enum LyricsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the InAppLyrics
|
||||
/// </summary>
|
||||
InAppLyrics,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the DesktopLyrics
|
||||
/// </summary>
|
||||
DesktopLyrics,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the MusicSearchMatchMode
|
||||
/// </summary>
|
||||
public enum MusicSearchMatchMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the TitleAndArtist
|
||||
/// </summary>
|
||||
TitleAndArtist,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the TitleArtistAlbumAndDuration
|
||||
/// </summary>
|
||||
TitleArtistAlbumAndDuration,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum TextAlignmentType
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
public static class LyricsAlignmentTypeExtensions
|
||||
{
|
||||
public static CanvasHorizontalAlignment ToCanvasHorizontalAlignment(this TextAlignmentType alignmentType)
|
||||
{
|
||||
return alignmentType switch
|
||||
{
|
||||
TextAlignmentType.Left => CanvasHorizontalAlignment.Left,
|
||||
TextAlignmentType.Center => CanvasHorizontalAlignment.Center,
|
||||
TextAlignmentType.Right => CanvasHorizontalAlignment.Right,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(alignmentType), alignmentType, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the TitleBarType
|
||||
/// </summary>
|
||||
public enum TitleBarType
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the Compact
|
||||
/// </summary>
|
||||
Compact,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Extended
|
||||
/// </summary>
|
||||
Extended,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="TitleBarTypeExtensions" />
|
||||
/// </summary>
|
||||
public static class TitleBarTypeExtensions
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The GetHeight
|
||||
/// </summary>
|
||||
/// <param name="titleBarType">The titleBarType<see cref="TitleBarType"/></param>
|
||||
/// <returns>The <see cref="double"/></returns>
|
||||
public static double GetHeight(this TitleBarType titleBarType)
|
||||
{
|
||||
return titleBarType switch
|
||||
{
|
||||
TitleBarType.Compact => 32.0,
|
||||
TitleBarType.Extended => 48.0,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(titleBarType),
|
||||
titleBarType,
|
||||
null
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum WindowPixelSampleMode
|
||||
{
|
||||
BelowWindow,
|
||||
WindowArea,
|
||||
WindowEdge,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs : EventArgs
|
||||
{
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
|
||||
public Color? AlbumArtAccentColor { get; set; } = null;
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,8 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IsPlayingChangedEventArgs" />
|
||||
/// </summary>
|
||||
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsPlaying
|
||||
/// </summary>
|
||||
public bool IsPlaying { get; set; } = isPlaying;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,45 +9,10 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LibChangedEventArgs" />
|
||||
/// </summary>
|
||||
public class LibChangedEventArgs : EventArgs
|
||||
public class LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType) : EventArgs
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibChangedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="folder">The folder<see cref="string"/></param>
|
||||
/// <param name="filePath">The filePath<see cref="string"/></param>
|
||||
/// <param name="changeType">The changeType<see cref="WatcherChangeTypes"/></param>
|
||||
public LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType)
|
||||
{
|
||||
Folder = folder;
|
||||
FilePath = filePath;
|
||||
ChangeType = changeType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ChangeType
|
||||
/// </summary>
|
||||
public WatcherChangeTypes ChangeType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FilePath
|
||||
/// </summary>
|
||||
public string FilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Folder
|
||||
/// </summary>
|
||||
public string Folder { get; }
|
||||
|
||||
#endregion
|
||||
public WatcherChangeTypes ChangeType { get; } = changeType;
|
||||
public string FilePath { get; } = filePath;
|
||||
public string Folder { get; } = folder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class MediaSourceProvidersInfoEventArgs(List<MediaSourceProviderInfo> sessionIds):EventArgs
|
||||
{
|
||||
public List<MediaSourceProviderInfo> MediaSourceProviersInfo { get; set; } = sessionIds;
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,8 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PositionChangedEventArgs" />
|
||||
/// </summary>
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Position
|
||||
/// </summary>
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SongInfoChangedEventArgs" />
|
||||
/// </summary>
|
||||
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SongInfo
|
||||
/// </summary>
|
||||
public SongInfo? SongInfo { get; set; } = songInfo;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AnimationHelper" />
|
||||
/// </summary>
|
||||
public class AnimationHelper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Defines the DebounceDefaultDuration
|
||||
/// </summary>
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the StackedNotificationsShowingDuration
|
||||
/// </summary>
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the StoryboardDefaultDuration
|
||||
/// </summary>
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ValueTransition{T}" />
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _currentValue
|
||||
/// </summary>
|
||||
private T _currentValue;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _durationSeconds
|
||||
/// </summary>
|
||||
private float _durationSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _interpolator
|
||||
/// </summary>
|
||||
private Func<T, T, float, T> _interpolator;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _isTransitioning
|
||||
/// </summary>
|
||||
private bool _isTransitioning;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _progress
|
||||
/// </summary>
|
||||
private float _progress;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _startValue
|
||||
/// </summary>
|
||||
private T _startValue;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _targetValue
|
||||
/// </summary>
|
||||
private T _targetValue;
|
||||
|
||||
private EasingType? _easingType;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValueTransition{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initialValue<see cref="T"/></param>
|
||||
/// <param name="durationSeconds">The durationSeconds<see cref="float"/></param>
|
||||
/// <param name="interpolator">The interpolator<see cref="Func{T, T, float, T}"/></param>
|
||||
public ValueTransition(
|
||||
T initialValue,
|
||||
float durationSeconds,
|
||||
Func<T, T, float, T>? interpolator = null,
|
||||
EasingType? easingType = null
|
||||
)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
_targetValue = initialValue;
|
||||
_durationSeconds = durationSeconds;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
|
||||
if (interpolator != null)
|
||||
{
|
||||
_interpolator = interpolator;
|
||||
_easingType = null;
|
||||
}
|
||||
else if (easingType.HasValue)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(easingType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_interpolator = GetInterpolatorByEasingType(EasingType.Linear);
|
||||
_easingType = EasingType.Linear;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether IsTransitioning
|
||||
/// </summary>
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Value
|
||||
/// </summary>
|
||||
public T Value => _currentValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
|
||||
{
|
||||
// 这里只以float为例,实际可根据T类型扩展
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
return (start, end, progress) =>
|
||||
{
|
||||
float s = (float)(object)start;
|
||||
float e = (float)(object)end;
|
||||
float t = progress;
|
||||
switch (type)
|
||||
{
|
||||
case EasingType.EaseInOutQuad:
|
||||
t = EasingHelper.EaseInOutQuad(t);
|
||||
break;
|
||||
case EasingType.EaseInQuad:
|
||||
t = EasingHelper.EaseInQuad(t);
|
||||
break;
|
||||
case EasingType.EaseOutQuad:
|
||||
t = EasingHelper.EaseOutQuad(t);
|
||||
break;
|
||||
case EasingType.Linear:
|
||||
t = EasingHelper.Linear(t);
|
||||
break;
|
||||
case EasingType.SmootherStep:
|
||||
t = EasingHelper.SmootherStep(t);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (T)(object)(s + (e - s) * t);
|
||||
};
|
||||
}
|
||||
throw new NotSupportedException("当前类型未实现默认缓动插值");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Reset
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="T"/></param>
|
||||
public void Reset(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The StartTransition
|
||||
/// </summary>
|
||||
/// <param name="targetValue">The targetValue<see cref="T"/></param>
|
||||
public void StartTransition(T targetValue)
|
||||
{
|
||||
if (!targetValue.Equals(_currentValue))
|
||||
{
|
||||
_startValue = _currentValue;
|
||||
_targetValue = targetValue;
|
||||
_progress = 0f;
|
||||
_isTransitioning = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即跳转到指定值,无动画
|
||||
/// </summary>
|
||||
/// <param name="value">目标值</param>
|
||||
public void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Update
|
||||
/// </summary>
|
||||
/// <param name="elapsedTime">The elapsedTime<see cref="TimeSpan"/></param>
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
if (!_isTransitioning)
|
||||
return;
|
||||
|
||||
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
_currentValue = _targetValue;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using System.IO;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AppInfo" />
|
||||
/// </summary>
|
||||
public static class AppInfo
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Defines the AppAuthor
|
||||
/// </summary>
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the AppDisplayName
|
||||
/// </summary>
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
|
||||
// App Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Defines the AppName
|
||||
/// </summary>
|
||||
public const string AppName = "BetterLyrics";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the GithubUrl
|
||||
/// </summary>
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AppVersion
|
||||
/// </summary>
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AssetsFolder
|
||||
/// </summary>
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CacheFolder
|
||||
/// </summary>
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
|
||||
// Environment Info
|
||||
|
||||
// Data Files
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LogDirectory
|
||||
/// </summary>
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LogFilePattern
|
||||
/// </summary>
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OnlineLyricsCacheDirectory
|
||||
/// </summary>
|
||||
public static string LrcLibLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "lrclib-lyrics");
|
||||
|
||||
public static string AmllTtmlDbLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
|
||||
public static string NeteaseLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "netease-lyrics");
|
||||
|
||||
public static string AmllTtmlDbIndexPath =>
|
||||
Path.Combine(CacheFolder, "amll-ttml-db-index.json");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TestMusicPath
|
||||
/// </summary>
|
||||
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
|
||||
|
||||
// Base Folders
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LocalFolder
|
||||
/// </summary>
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TestMusicFileName
|
||||
/// </summary>
|
||||
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The EnsureDirectories
|
||||
/// </summary>
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="CollectionHelper" />
|
||||
/// </summary>
|
||||
public static class CollectionHelper
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The SafeGet
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="list">The list<see cref="IList{T}"/></param>
|
||||
/// <param name="index">The index<see cref="int"/></param>
|
||||
/// <returns>The <see cref="T?"/></returns>
|
||||
public static T? SafeGet<T>(this IList<T> list, int index)
|
||||
{
|
||||
if (list == null || index < 0 || index >= list.Count)
|
||||
return default;
|
||||
return list[index];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,58 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.UI;
|
||||
|
||||
using Color = Windows.UI.Color;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ColorHelper" />
|
||||
/// </summary>
|
||||
public static class ColorHelper
|
||||
{
|
||||
#region Methods
|
||||
public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor)
|
||||
{
|
||||
// 计算亮度(YIQ公式)
|
||||
double yiq =
|
||||
((backgroundColor.R * 299) + (backgroundColor.G * 587) + (backgroundColor.B * 114))
|
||||
/ 1000.0;
|
||||
return yiq >= 128 ? ElementTheme.Light : ElementTheme.Dark;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetInterpolatedColor
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress<see cref="float"/></param>
|
||||
/// <param name="startColor">The startColor<see cref="Color"/></param>
|
||||
/// <param name="targetColor">The targetColor<see cref="Color"/></param>
|
||||
/// <returns>The <see cref="Color"/></returns>
|
||||
public static Color GetInterpolatedColor(
|
||||
float progress,
|
||||
Color startColor,
|
||||
Color targetColor
|
||||
)
|
||||
public static Color GetForegroundColor(Color background)
|
||||
{
|
||||
// 转为 HSL
|
||||
var hsl = CommunityToolkit.WinUI.Helpers.ColorHelper.ToHsl(background);
|
||||
double h = hsl.H;
|
||||
double s = hsl.S;
|
||||
double l = hsl.L;
|
||||
|
||||
// 目标亮度与背景错开,但不极端
|
||||
double targetL;
|
||||
if (l >= 0.7)
|
||||
targetL = 0.35; // 背景很亮,前景适中偏暗
|
||||
else if (l <= 0.3)
|
||||
targetL = 0.75; // 背景很暗,前景适中偏亮
|
||||
else
|
||||
targetL = l > 0.5 ? l - 0.35 : l + 0.35; // 其余情况适度错开
|
||||
|
||||
// 保持色相,适当提升饱和度
|
||||
double targetS = Math.Min(1.0, s + 0.2);
|
||||
|
||||
// 转回 Color
|
||||
var fg = CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, targetS, targetL);
|
||||
|
||||
// 保持不透明
|
||||
return Color.FromArgb(255, fg.R, fg.G, fg.B);
|
||||
}
|
||||
|
||||
public static Color GetInterpolatedColor(float progress, Color startColor, Color targetColor)
|
||||
{
|
||||
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
|
||||
return Color.FromArgb(
|
||||
@@ -33,16 +63,182 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ToWindowsUIColor
|
||||
/// </summary>
|
||||
/// <param name="color">The color<see cref="System.Drawing.Color"/></param>
|
||||
/// <returns>The <see cref="Windows.UI.Color"/></returns>
|
||||
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
|
||||
public static Color ToColor(this int argb)
|
||||
{
|
||||
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
byte a = (byte)(argb >> 24);
|
||||
byte r = (byte)(argb >> 16);
|
||||
byte g = (byte)(argb >> 8);
|
||||
byte b = (byte)argb;
|
||||
|
||||
// 还原非预乘分量
|
||||
if (a == 0)
|
||||
return Color.FromArgb(0, 0, 0, 0);
|
||||
|
||||
// 预乘解码
|
||||
// 这里 a+1 是编码时的分母
|
||||
int ap1 = a + 1;
|
||||
r = (byte)Math.Min(255, (r * 255 + (ap1 / 2)) / ap1);
|
||||
g = (byte)Math.Min(255, (g * 255 + (ap1 / 2)) / ap1);
|
||||
b = (byte)Math.Min(255, (b * 255 + (ap1 / 2)) / ap1);
|
||||
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
|
||||
public static Color ToColor(this System.Drawing.Color color)
|
||||
{
|
||||
return Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
public static Color WithAlpha(this Color color, byte alpha)
|
||||
{
|
||||
return Color.FromArgb(alpha, color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
public static Color WithBrightness(this Color color, double brightness)
|
||||
{
|
||||
// 确保亮度因子在合理范围内
|
||||
brightness = Math.Max(0, Math.Min(1, brightness));
|
||||
|
||||
var hsl = CommunityToolkit.WinUI.Helpers.ColorHelper.ToHsl(color);
|
||||
double h = hsl.H;
|
||||
double s = hsl.S;
|
||||
|
||||
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
|
||||
}
|
||||
|
||||
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, WindowPixelSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case WindowPixelSampleMode.BelowWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowEdge:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
List<System.Drawing.Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top,
|
||||
width,
|
||||
(int)edgeThickness.Top
|
||||
)
|
||||
);
|
||||
// Bottom edge
|
||||
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Bottom - (int)edgeThickness.Bottom,
|
||||
width,
|
||||
(int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Left edge
|
||||
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Left,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Right edge
|
||||
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Right - (int)edgeThickness.Right,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Right,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
|
||||
// 合并四边平均色
|
||||
if (edgeColors.Count == 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
foreach (var c in edgeColors)
|
||||
{
|
||||
r += c.R;
|
||||
g += c.G;
|
||||
b += c.B;
|
||||
}
|
||||
return System.Drawing.Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return System.Drawing.Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
private static System.Drawing.Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static System.Drawing.Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
System.Drawing.Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return System.Drawing.Color.Transparent;
|
||||
return System.Drawing.Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
@@ -9,65 +14,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DesktopModeHelper
|
||||
{
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
|
||||
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
|
||||
private static readonly Dictionary<
|
||||
IntPtr,
|
||||
(double X, double Y, double Width, double Height)
|
||||
> _originalWindowBounds = [];
|
||||
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><E0BBAF><EFBFBD><EFBFBD>
|
||||
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
|
||||
|
||||
public static void Enable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
|
||||
var windowManager = WindowManager.Get(window);
|
||||
if (!_originalWindowBounds.ContainsKey(hwnd))
|
||||
{
|
||||
_originalWindowBounds[hwnd] = (
|
||||
windowManager.AppWindow.Position.X,
|
||||
windowManager.AppWindow.Position.Y,
|
||||
windowManager.Width,
|
||||
windowManager.Height
|
||||
);
|
||||
}
|
||||
|
||||
// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
var displayArea = Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(
|
||||
windowManager.AppWindow.Id,
|
||||
Microsoft.UI.Windowing.DisplayAreaFallback.Primary
|
||||
);
|
||||
var workArea = displayArea.WorkArea;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
|
||||
int targetWidth = workArea.Width / 3;
|
||||
int targetHeight = workArea.Height / 4;
|
||||
int targetX = workArea.X + (workArea.Width - targetWidth) / 2; // <20><><EFBFBD><EFBFBD>
|
||||
int targetY = workArea.Y + workArea.Height - targetHeight - 64;
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
|
||||
windowManager.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
if (!_originalTopmostStates.ContainsKey(hwnd))
|
||||
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
window.SetIsShownInSwitchers(false);
|
||||
}
|
||||
|
||||
public static void Disable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -80,10 +34,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
|
||||
var windowManager = WindowManager.Get(window);
|
||||
if (_originalWindowBounds.TryGetValue(hwnd, out var bounds))
|
||||
{
|
||||
windowManager.AppWindow.MoveAndResize(
|
||||
window.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(
|
||||
(int)bounds.X,
|
||||
(int)bounds.Y,
|
||||
@@ -94,72 +47,68 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_originalWindowBounds.Remove(hwnd);
|
||||
}
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
}
|
||||
|
||||
public static void Lock(Window window)
|
||||
public static void Enable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
|
||||
SetClickThrough(window, true);
|
||||
}
|
||||
|
||||
public static void Unlock(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD> Disable ʱ<><CAB1><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD>
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
// <20><>¼ԭʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<EFBFBD>С
|
||||
if (!_originalWindowBounds.ContainsKey(hwnd))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowBounds[hwnd] = (
|
||||
window.AppWindow.Position.X,
|
||||
window.AppWindow.Position.Y,
|
||||
window.AppWindow.Size.Width,
|
||||
window.AppWindow.Size.Height
|
||||
);
|
||||
}
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
SetClickThrough(window, false);
|
||||
// <20>Ӵ洢<D3B4><E6B4A2><EFBFBD><EFBFBD>ȡĿ<C8A1><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
|
||||
int targetWidth = _settingsService.DesktopWindowWidth;
|
||||
int targetHeight = _settingsService.DesktopWindowHeight;
|
||||
int targetX = _settingsService.DesktopWindowLeft;
|
||||
int targetY = _settingsService.DesktopWindowTop;
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
|
||||
window.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
if (!_originalTopmostStates.ContainsKey(hwnd))
|
||||
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
window.SetIsShownInSwitchers(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20>л<EFBFBD><D0BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬
|
||||
/// </summary>
|
||||
public static void SetClickThrough(Window window, bool enable)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
|
||||
if (enable)
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
|
||||
_clickThroughStates[hwnd] = true;
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT);
|
||||
_clickThroughStates[hwnd] = false;
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Win32
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_TRANSPARENT = 0x00000020;
|
||||
private const int WS_EX_LAYERED = 0x00080000;
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
@@ -14,9 +15,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static class DockModeHelper
|
||||
{
|
||||
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)
|
||||
@@ -32,14 +31,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
SetWindowPos(
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
rect.Left,
|
||||
rect.Top,
|
||||
rect.Right - rect.Left,
|
||||
rect.Bottom - rect.Top,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
@@ -63,7 +62,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
if (GetWindowRect(hwnd, out var rect))
|
||||
if (User32.GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
_originalPositions[hwnd] = rect;
|
||||
}
|
||||
@@ -71,72 +70,39 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
|
||||
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
||||
SetWindowPos(
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
#region AppBar registration
|
||||
private const uint ABM_NEW = 0x00000000;
|
||||
private const uint ABM_REMOVE = 0x00000001;
|
||||
private const uint ABM_SETPOS = 0x00000003;
|
||||
private const int ABE_TOP = 1;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct APPBARDATA
|
||||
{
|
||||
public int cbSize;
|
||||
public IntPtr hWnd;
|
||||
public uint uCallbackMessage;
|
||||
public uint uEdge;
|
||||
public RECT rc;
|
||||
public int lParam;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
public int left,
|
||||
top,
|
||||
right,
|
||||
bottom;
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
|
||||
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height)
|
||||
{
|
||||
if (_registered.Contains(hwnd))
|
||||
return;
|
||||
if (_registered.Contains(hwnd)) return;
|
||||
|
||||
APPBARDATA abd = new()
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<APPBARDATA>(),
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = ABE_TOP,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = GetSystemMetrics(SM_CXSCREEN),
|
||||
bottom = height,
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = height,
|
||||
},
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_NEW, ref abd);
|
||||
SHAppBarMessage(ABM_SETPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
_registered.Add(hwnd);
|
||||
}
|
||||
@@ -146,73 +112,47 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
APPBARDATA abd = new() { cbSize = Marshal.SizeOf<APPBARDATA>(), hWnd = hwnd };
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_REMOVE, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
|
||||
_registered.Remove(hwnd);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Win32 Helper and Constants
|
||||
|
||||
private const int SWP_NOACTIVATE = 0x0010;
|
||||
private const int SWP_NOOWNERZORDER = 0x0200;
|
||||
private const int SWP_SHOWWINDOW = 0x0040;
|
||||
|
||||
private const int SM_CXSCREEN = 0;
|
||||
private const int SM_CYSCREEN = 0;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(int nIndex);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(
|
||||
IntPtr hWnd,
|
||||
IntPtr hWndInsertAfter,
|
||||
int X,
|
||||
int Y,
|
||||
int cx,
|
||||
int cy,
|
||||
uint uFlags
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 更改已注册 AppBar 的高度。
|
||||
/// </summary>
|
||||
/// <param name="window">目标窗口</param>
|
||||
/// <param name="newHeight">新的高度</param>
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
APPBARDATA abd = new()
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<APPBARDATA>(),
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = ABE_TOP,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = GetSystemMetrics(SM_CXSCREEN),
|
||||
bottom = newHeight,
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = newHeight,
|
||||
},
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_SETPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
SetWindowPos(
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
GetSystemMetrics(SM_CXSCREEN),
|
||||
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
newHeight,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,64 +8,105 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="EasingHelper" />
|
||||
/// </summary>
|
||||
public class EasingHelper
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Acceleration until halfway then deceleration
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="float"/></param>
|
||||
/// <returns>The <see cref="float"/></returns>
|
||||
public static float EaseInOutSine(float t)
|
||||
{
|
||||
return -(MathF.Cos(MathF.PI * t) - 1f) / 2f;
|
||||
}
|
||||
public static float EaseInOutQuad(float t)
|
||||
{
|
||||
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accelerating from 0
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="float"/></param>
|
||||
/// <returns>The <see cref="float"/></returns>
|
||||
public static float EaseInQuad(float t) => t * t;
|
||||
|
||||
/// <summary>
|
||||
/// Decelerating to 0
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="float"/></param>
|
||||
/// <returns>The <see cref="float"/></returns>
|
||||
public static float EaseOutQuad(float t) => t * (2 - t);
|
||||
|
||||
/// <summary>
|
||||
/// No easing
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="float"/></param>
|
||||
/// <returns>The <see cref="float"/></returns>
|
||||
public static float Linear(float t) => t;
|
||||
|
||||
/// <summary>
|
||||
/// Even smoother transition with continuous first and second derivatives
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="float"/></param>
|
||||
/// <returns>The <see cref="float"/></returns>
|
||||
public static float SmootherStep(float t)
|
||||
public static float EaseInOutCubic(float t)
|
||||
{
|
||||
return t * t * t * (t * (6 * t - 15) + 10);
|
||||
return t < 0.5f ? 4 * t * t * t : 1 - MathF.Pow(-2 * t + 2, 3) / 2;
|
||||
}
|
||||
public static float EaseInOutQuart(float t)
|
||||
{
|
||||
return t < 0.5f ? 8 * t * t * t * t : 1 - MathF.Pow(-2 * t + 2, 4) / 2;
|
||||
}
|
||||
|
||||
public static float EaseInOutQuint(float t)
|
||||
{
|
||||
return t < 0.5f ? 16 * t * t * t * t * t : 1 - MathF.Pow(-2 * t + 2, 5) / 2;
|
||||
}
|
||||
|
||||
public static float EaseInOutExpo(float t)
|
||||
{
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: t < 0.5 ? MathF.Pow(2, 20 * t - 10) / 2
|
||||
: (2 - MathF.Pow(2, -20 * t + 10)) / 2;
|
||||
}
|
||||
|
||||
public static float EaseInOutCirc(float t)
|
||||
{
|
||||
return t < 0.5f
|
||||
? (1 - MathF.Sqrt(1 - MathF.Pow(2 * t, 2))) / 2
|
||||
: (MathF.Sqrt(1 - MathF.Pow(-2 * t + 2, 2)) + 1) / 2;
|
||||
}
|
||||
|
||||
public static float EaseInOutBack(float t)
|
||||
{
|
||||
float c1 = 1.70158f;
|
||||
float c2 = c1 * 1.525f;
|
||||
|
||||
return t < 0.5
|
||||
? (MathF.Pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
|
||||
: (MathF.Pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
||||
}
|
||||
|
||||
public static float EaseInOutElastic(float t)
|
||||
{
|
||||
if (t == 0 || t == 1) return t;
|
||||
float p = 0.3f;
|
||||
float s = p / 4;
|
||||
return t < 0.5f
|
||||
? -(MathF.Pow(2, 20 * t - 10) * MathF.Sin((20 * t - 11.125f) * (2 * MathF.PI) / p)) / 2
|
||||
: (MathF.Pow(2, -20 * t + 10) * MathF.Sin((20 * t - 11.125f) * (2 * MathF.PI) / p)) / 2 + 1;
|
||||
}
|
||||
|
||||
private static float EaseOutBounce(float t)
|
||||
{
|
||||
if (t < 4 / 11f)
|
||||
{
|
||||
return (121 * t * t) / 16f;
|
||||
}
|
||||
else if (t < 8 / 11f)
|
||||
{
|
||||
return (363 / 40f * t * t) - (99 / 10f * t) + 17 / 5f;
|
||||
}
|
||||
else if (t < 9 / 10f)
|
||||
{
|
||||
return (4356 / 361f * t * t) - (35442 / 1805f * t) + 16061 / 1805f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (54 / 5f * t * t) - (513 / 25f * t) + 268 / 25f;
|
||||
}
|
||||
}
|
||||
|
||||
public static float EaseInOutBounce(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return (1 - EaseOutBounce(1 - 2 * t)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (1 + EaseOutBounce(2 * t - 1)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoother transition than linear
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="float"/></param>
|
||||
/// <returns>The <see cref="float"/></returns>
|
||||
public static float SmoothStep(float t)
|
||||
{
|
||||
return t * t * (3 - 2 * t);
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
#endregion
|
||||
public static float Linear(float t) => t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ude;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="FileHelper" />
|
||||
/// </summary>
|
||||
public class FileHelper
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The GetEncoding
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename<see cref="string"/></param>
|
||||
/// <returns>The <see cref="Encoding"/></returns>
|
||||
public static Encoding GetEncoding(string filename)
|
||||
{
|
||||
var bytes = File.ReadAllBytes(filename);
|
||||
@@ -36,6 +24,59 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return Encoding.GetEncoding(encoding);
|
||||
}
|
||||
|
||||
#endregion
|
||||
public static string SanitizeFileName(string fileName, char replacement = '_')
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(fileName.Length);
|
||||
foreach (var c in fileName)
|
||||
{
|
||||
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllBytes(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteLyricsCache(string title, string artist, string lyrics, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
public static void WriteAlbumArtCache(string album, string artist, byte[] img, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
File.WriteAllBytes(cacheFilePath, img);
|
||||
}
|
||||
|
||||
public static bool IsSwitchableNormalizedMatch(string fileName, string q1, string q2)
|
||||
{
|
||||
var normFileName = StringHelper.Normalize(fileName.Normalize());
|
||||
var normQ1 = StringHelper.Normalize(q1);
|
||||
var normQ2 = StringHelper.Normalize(q2);
|
||||
|
||||
// 常见两种顺序
|
||||
return normFileName == normQ1 + normQ2
|
||||
|| normFileName == normQ2 + normQ1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ForegroundWindowWatcher
|
||||
{
|
||||
private readonly User32.WinEventProc _winEventDelegate;
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
private HWND _currentForeground = HWND.NULL;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
|
||||
public delegate void WindowChangedHandler(HWND hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
public ForegroundWindowWatcher(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
_onWindowChanged = onWindowChanged;
|
||||
_winEventDelegate = new User32.WinEventProc(WinEventProc);
|
||||
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Interval = TimeSpan.FromSeconds(1);
|
||||
_timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Hook: foreground changes and minimize end
|
||||
_hooks.Add(
|
||||
User32.SetWinEventHook(
|
||||
User32.EventConstants.EVENT_SYSTEM_FOREGROUND,
|
||||
User32.EventConstants.EVENT_SYSTEM_MINIMIZEEND,
|
||||
HINSTANCE.NULL,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
User32.WINEVENT.WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
// Hook: window move/resize (location change)
|
||||
_hooks.Add(
|
||||
User32.SetWinEventHook(
|
||||
User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE,
|
||||
User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE,
|
||||
HINSTANCE.NULL,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
User32.WINEVENT.WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
foreach (var hook in _hooks)
|
||||
User32.UnhookWinEvent(hook);
|
||||
|
||||
_hooks.Clear();
|
||||
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void Timer_Tick(object? sender, object e)
|
||||
{
|
||||
if (_currentForeground != HWND.NULL)
|
||||
{
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
}
|
||||
}
|
||||
|
||||
private void WinEventProc(
|
||||
User32.HWINEVENTHOOK hWinEventHook,
|
||||
uint eventType,
|
||||
HWND hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
)
|
||||
{
|
||||
if (hwnd == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
if (eventType == User32.EventConstants.EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
_currentForeground = hwnd;
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
else if ((eventType == User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE || eventType == User32.EventConstants.EVENT_SYSTEM_MINIMIZEEND) && hwnd == _currentForeground)
|
||||
{
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ForegroundWindowWatcherHelper
|
||||
{
|
||||
private readonly WinEventDelegate _winEventDelegate;
|
||||
private readonly List<IntPtr> _hooks = new();
|
||||
private IntPtr _currentForeground = IntPtr.Zero;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
private const int ThrottleIntervalMs = 100;
|
||||
|
||||
public delegate void WindowChangedHandler(IntPtr hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
private const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
|
||||
private const uint EVENT_SYSTEM_MINIMIZEEND = 0x0017;
|
||||
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
|
||||
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||
|
||||
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
_onWindowChanged = onWindowChanged;
|
||||
_winEventDelegate = new WinEventDelegate(WinEventProc);
|
||||
|
||||
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_pollingTimer.Tick += (_, _) =>
|
||||
{
|
||||
if (_currentForeground != IntPtr.Zero && _currentForeground != _selfHwnd)
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
};
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Hook: foreground changes and minimize end
|
||||
_hooks.Add(
|
||||
SetWinEventHook(
|
||||
EVENT_SYSTEM_FOREGROUND,
|
||||
EVENT_SYSTEM_MINIMIZEEND,
|
||||
IntPtr.Zero,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
// Hook: window move/resize (location change)
|
||||
_hooks.Add(
|
||||
SetWinEventHook(
|
||||
EVENT_OBJECT_LOCATIONCHANGE,
|
||||
EVENT_OBJECT_LOCATIONCHANGE,
|
||||
IntPtr.Zero,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
_pollingTimer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
foreach (var hook in _hooks)
|
||||
UnhookWinEvent(hook);
|
||||
|
||||
_hooks.Clear();
|
||||
_pollingTimer.Stop();
|
||||
}
|
||||
|
||||
private void WinEventProc(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
)
|
||||
{
|
||||
if (hwnd == IntPtr.Zero || hwnd == _selfHwnd)
|
||||
return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastEventTime).TotalMilliseconds < ThrottleIntervalMs)
|
||||
return;
|
||||
|
||||
_lastEventTime = now;
|
||||
|
||||
if (eventType == EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
_currentForeground = hwnd;
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
else if (
|
||||
(eventType == EVENT_OBJECT_LOCATIONCHANGE || eventType == EVENT_SYSTEM_MINIMIZEEND)
|
||||
&& hwnd == _currentForeground
|
||||
)
|
||||
{
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
#region WinAPI
|
||||
private delegate void WinEventDelegate(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetWinEventHook(
|
||||
uint eventMin,
|
||||
uint eventMax,
|
||||
IntPtr hmodWinEventProc,
|
||||
WinEventDelegate lpfnWinEventProc,
|
||||
uint idProcess,
|
||||
uint idThread,
|
||||
uint dwFlags
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -13,43 +7,20 @@ 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;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ImageHelper" />
|
||||
/// </summary>
|
||||
public class ImageHelper
|
||||
{
|
||||
#region Constants
|
||||
private const int _accentColorCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the AccentColorCount
|
||||
/// </summary>
|
||||
public const int AccentColorCount = 3;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _colorThief
|
||||
/// </summary>
|
||||
private static readonly ColorThief _colorThief = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The ByteArrayToStream
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
|
||||
/// <returns>The <see cref="Task{InMemoryRandomAccessStream}"/></returns>
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
@@ -59,18 +30,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CreateTextPlaceholderBytesAsync
|
||||
/// </summary>
|
||||
/// <param name="text">The text<see cref="string"/></param>
|
||||
/// <param name="width">The width<see cref="int"/></param>
|
||||
/// <param name="height">The height<see cref="int"/></param>
|
||||
/// <returns>The <see cref="Task{byte[]}"/></returns>
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(
|
||||
string text,
|
||||
int width,
|
||||
int height
|
||||
)
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
|
||||
{
|
||||
var device = CanvasDevice.GetSharedDevice();
|
||||
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
@@ -142,68 +102,68 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetAccentColorsFromByte
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
|
||||
/// <returns>The <see cref="Task{List{Color}}"/></returns>
|
||||
public static async Task<List<Color>> GetAccentColorsFromByte(byte[] bytes) =>
|
||||
[
|
||||
.. (
|
||||
await _colorThief.GetPalette(await GetDecoderFromByte(bytes), AccentColorCount)
|
||||
).Select(color =>
|
||||
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
|
||||
),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The GetBitmapImageFromBytesAsync
|
||||
/// </summary>
|
||||
/// <param name="imageBytes">The imageBytes<see cref="byte[]"/></param>
|
||||
/// <returns>The <see cref="Task{BitmapImage}"/></returns>
|
||||
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
public static List<Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
// 使用 ImageSharp 读取图片
|
||||
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
|
||||
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
|
||||
|
||||
return bitmapImage;
|
||||
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();
|
||||
|
||||
// 转换为 Windows.UI.Color
|
||||
return topColors
|
||||
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetDecoderFromByte
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
|
||||
/// <returns>The <see cref="Task{BitmapDecoder}"/></returns>
|
||||
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
|
||||
/// <summary>
|
||||
/// The GetStreamFromBytesAsync
|
||||
/// </summary>
|
||||
/// <param name="imageBytes">The imageBytes<see cref="byte[]"/></param>
|
||||
/// <returns>The <see cref="Task{InMemoryRandomAccessStream}"/></returns>
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
|
||||
byte[] imageBytes
|
||||
)
|
||||
{
|
||||
if (imageBytes == null || imageBytes.Length == 0)
|
||||
return null;
|
||||
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// var stream = new InMemoryRandomAccessStream();
|
||||
// await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
// stream.Seek(0);
|
||||
|
||||
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
// var bitmapImage = new BitmapImage();
|
||||
// await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
// return bitmapImage;
|
||||
//}
|
||||
|
||||
//public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
// await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
|
||||
//public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// if (imageBytes == null || imageBytes.Length == 0)
|
||||
// return null;
|
||||
|
||||
// InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
// await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
// return stream;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// The ToByteArrayAsync
|
||||
/// </summary>
|
||||
/// <param name="streamRef">The streamRef<see cref="IRandomAccessStreamReference"/></param>
|
||||
/// <returns>The <see cref="Task{byte[]}"/></returns>
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
@@ -212,6 +172,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
public static float GetAverageLuminance(CanvasBitmap bitmap)
|
||||
{
|
||||
var pixels = bitmap.GetPixelBytes();
|
||||
double sum = 0;
|
||||
for (int i = 0; i < pixels.Length; i += 4)
|
||||
{
|
||||
// BGRA
|
||||
byte b = pixels[i];
|
||||
byte g = pixels[i + 1];
|
||||
byte r = pixels[i + 2];
|
||||
// 忽略A
|
||||
double y = 0.299 * r + 0.587 * g + 0.114 * b;
|
||||
sum += y / 255.0;
|
||||
}
|
||||
return (float)(sum / (pixels.Length / 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LanguageHelper.cs
Normal file
117
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LanguageHelper.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using NTextCat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LanguageHelper
|
||||
{
|
||||
private static readonly RankedLanguageIdentifierFactory _factory = new();
|
||||
private static readonly RankedLanguageIdentifier _identifier;
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
|
||||
[
|
||||
new Models.LanguageInfo("ar", "العربية"),
|
||||
new Models.LanguageInfo("az", "Azərbaycan dili"),
|
||||
new Models.LanguageInfo("zh-Hans", "简体中文"),
|
||||
new Models.LanguageInfo("zh-Hant", "繁體中文"),
|
||||
new Models.LanguageInfo("cs", "Čeština"),
|
||||
new Models.LanguageInfo("da", "Dansk"),
|
||||
new Models.LanguageInfo("nl", "Nederlands"),
|
||||
new Models.LanguageInfo("en", "English"),
|
||||
new Models.LanguageInfo("eo", "Esperanto"),
|
||||
new Models.LanguageInfo("fi", "Suomi"),
|
||||
new Models.LanguageInfo("fr", "Français"),
|
||||
new Models.LanguageInfo("de", "Deutsch"),
|
||||
new Models.LanguageInfo("el", "Ελληνικά"),
|
||||
new Models.LanguageInfo("he", "עברית"),
|
||||
new Models.LanguageInfo("hi", "हिन्दी"),
|
||||
new Models.LanguageInfo("hu", "Magyar"),
|
||||
new Models.LanguageInfo("id", "Bahasa Indonesia"),
|
||||
new Models.LanguageInfo("ga", "Gaeilge"),
|
||||
new Models.LanguageInfo("it", "Italiano"),
|
||||
new Models.LanguageInfo("ja", "日本語"),
|
||||
new Models.LanguageInfo("ko", "한국어"),
|
||||
new Models.LanguageInfo("fa", "فارسی"),
|
||||
new Models.LanguageInfo("pl", "Polski"),
|
||||
new Models.LanguageInfo("pt", "Português"),
|
||||
new Models.LanguageInfo("ru", "Русский"),
|
||||
new Models.LanguageInfo("sk", "Slovenčina"),
|
||||
new Models.LanguageInfo("es", "Español"),
|
||||
new Models.LanguageInfo("sv", "Svenska"),
|
||||
new Models.LanguageInfo("tr", "Türkçe"),
|
||||
new Models.LanguageInfo("uk", "Українська"),
|
||||
new Models.LanguageInfo("vi", "Tiếng Việt"),
|
||||
];
|
||||
|
||||
static LanguageHelper()
|
||||
{
|
||||
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
string? code = ThreeLetterToTwoLetter(_identifier.Identify(text).FirstOrDefault()?.Item1.Iso639_2T);
|
||||
if (code != null && code == "zh")
|
||||
{
|
||||
if (ChineseConverter.ConvertToTraditionalChinese(text) == text)
|
||||
{
|
||||
return "zh-Hant";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "zh-Hans";
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
return DetectLanguageCode(text) switch
|
||||
{
|
||||
"zh" or "ja" or "ko" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static string DetectCountryCode(string? text)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LatestOnlyTaskRunner
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public async Task RunAsync(Func<CancellationToken, Task> func)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
try
|
||||
{
|
||||
await func(token);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,57 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
using Windows.Globalization.Fonts;
|
||||
using LyricsData = BetterLyrics.WinUI3.Models.LyricsData;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsParser" />
|
||||
/// </summary>
|
||||
public class LyricsParser
|
||||
{
|
||||
#region Fields
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _multiLangLyricsLines
|
||||
/// </summary>
|
||||
private List<List<LyricsLine>> _multiLangLyricsLines = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Parse
|
||||
/// </summary>
|
||||
/// <param name="raw">The raw<see cref="string"/></param>
|
||||
/// <param name="lyricsFormat">The lyricsFormat<see cref="LyricsFormat?"/></param>
|
||||
/// <param name="title">The title<see cref="string?"/></param>
|
||||
/// <param name="artist">The artist<see cref="string?"/></param>
|
||||
/// <param name="durationMs">The durationMs<see cref="int"/></param>
|
||||
/// <returns>The <see cref="List{List{LyricsLine}}"/></returns>
|
||||
public List<List<LyricsLine>> Parse(
|
||||
string raw,
|
||||
LyricsFormat? lyricsFormat = null,
|
||||
string? title = null,
|
||||
string? artist = null,
|
||||
int durationMs = 0
|
||||
)
|
||||
public List<LyricsData> Parse(string? raw, int? durationMs)
|
||||
{
|
||||
_multiLangLyricsLines = [];
|
||||
switch (lyricsFormat)
|
||||
durationMs ??= (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
_lyricsDataArr = [];
|
||||
if (raw == null)
|
||||
{
|
||||
case LyricsFormat.Lrc:
|
||||
case LyricsFormat.Eslrc:
|
||||
ParseLrc(raw, durationMs);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseUsingLyricify(
|
||||
Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines,
|
||||
durationMs
|
||||
);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseUsingLyricify(
|
||||
Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines,
|
||||
durationMs
|
||||
);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw, durationMs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder(durationMs.Value));
|
||||
}
|
||||
return _multiLangLyricsLines;
|
||||
else
|
||||
{
|
||||
switch (raw.DetectFormat())
|
||||
{
|
||||
case LyricsFormat.Lrc:
|
||||
case LyricsFormat.Eslrc:
|
||||
ParseLrc(raw);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(durationMs.Value);
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ParseLrc
|
||||
/// </summary>
|
||||
/// <param name="raw">The raw<see cref="string"/></param>
|
||||
/// <param name="durationMs">The durationMs<see cref="int"/></param>
|
||||
private void ParseLrc(string raw, int durationMs)
|
||||
private void ParseLrc(string raw)
|
||||
{
|
||||
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lrcLines =
|
||||
@@ -139,9 +112,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int languageCount = grouped.Max(g => g.Count());
|
||||
|
||||
// 初始化每种语言的歌词列表
|
||||
_multiLangLyricsLines.Clear();
|
||||
_lyricsDataArr.Clear();
|
||||
for (int i = 0; i < languageCount; i++)
|
||||
_multiLangLyricsLines.Add(new List<LyricsLine>());
|
||||
_lyricsDataArr.Add(new LyricsData());
|
||||
|
||||
// 遍历每个时间分组
|
||||
foreach (var group in grouped)
|
||||
@@ -156,7 +129,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
StartMs = start,
|
||||
EndMs = 0, // 稍后统一修正
|
||||
Text = text,
|
||||
OriginalText = text,
|
||||
CharTimings = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
@@ -178,179 +151,109 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
}
|
||||
_multiLangLyricsLines[langIdx].Add(line);
|
||||
_lyricsDataArr[langIdx].LyricsLines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 修正 EndMs
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
{
|
||||
var linesInSingleLang = _multiLangLyricsLines[langIdx];
|
||||
for (int i = 0; i < linesInSingleLang.Count; i++)
|
||||
{
|
||||
if (i + 1 < linesInSingleLang.Count)
|
||||
{
|
||||
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
linesInSingleLang[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = linesInSingleLang[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
{
|
||||
if (j + 1 < timings.Count)
|
||||
{
|
||||
timings[j].EndMs = timings[j + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
timings[j].EndMs = linesInSingleLang[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(linesInSingleLang);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseUsingLyricify(List<ILineInfo>? lines, int durationMs)
|
||||
{
|
||||
List<LyricsLine> lyricsLines = [];
|
||||
|
||||
if (lines != null && lines.Count > 0)
|
||||
{
|
||||
lyricsLines = [];
|
||||
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
|
||||
{
|
||||
var lineRead = lines[lineIndex];
|
||||
var lineWrite = new LyricsLine
|
||||
{
|
||||
StartMs = lineRead.StartTime ?? 0,
|
||||
Text = lineRead.Text,
|
||||
CharTimings = [],
|
||||
};
|
||||
|
||||
if (lineIndex + 1 < lines.Count)
|
||||
{
|
||||
lineWrite.EndMs = lines[lineIndex + 1].StartTime ?? 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineWrite.EndMs = durationMs;
|
||||
}
|
||||
|
||||
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
|
||||
if (syllables != null)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (
|
||||
int syllableIndex = 0;
|
||||
syllableIndex < syllables.Count;
|
||||
syllableIndex++
|
||||
)
|
||||
{
|
||||
var syllable = syllables[syllableIndex];
|
||||
var charTiming = new CharTiming
|
||||
{
|
||||
StartMs = syllable.StartTime,
|
||||
Text = syllable.Text,
|
||||
StartIndex = startIndex,
|
||||
};
|
||||
if (syllableIndex + 1 < syllables.Count)
|
||||
{
|
||||
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
charTiming.EndMs = lineWrite.EndMs;
|
||||
}
|
||||
lineWrite.CharTimings.Add(charTiming);
|
||||
startIndex += syllable.Text.Length;
|
||||
}
|
||||
}
|
||||
|
||||
lyricsLines.Add(lineWrite);
|
||||
}
|
||||
}
|
||||
|
||||
_multiLangLyricsLines.Add(lyricsLines);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ParseTtml
|
||||
/// </summary>
|
||||
/// <param name="raw">The raw<see cref="string"/></param>
|
||||
/// <param name="durationMs">The durationMs<see cref="int"/></param>
|
||||
private void ParseTtml(string raw, int durationMs)
|
||||
private void ParseTtml(string raw)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<LyricsLine> singleLangLyricsLine = [];
|
||||
List<LyricsLine> originalLines = [];
|
||||
List<LyricsLine> translationLines = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null)
|
||||
return;
|
||||
if (body == null) return;
|
||||
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
|
||||
foreach (var p in ps)
|
||||
{
|
||||
// 句级时间
|
||||
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()
|
||||
.Where(s =>
|
||||
s.Name.LocalName == "span"
|
||||
&& s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))
|
||||
== null
|
||||
)
|
||||
.Where(s => s.Name.LocalName == "span" &&
|
||||
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg")
|
||||
.ToList();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s));
|
||||
var charTimings = new List<CharTiming>();
|
||||
// 原文和翻译分离
|
||||
var originalTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-translation")
|
||||
.ToList();
|
||||
var translationTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
// 原文(非 CJK 语言添加空格)
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
if (!LanguageHelper.IsCJK(originalText))
|
||||
{
|
||||
var span = spans[i];
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
|
||||
if (sStartMs == 0 && sEndMs == 0)
|
||||
continue;
|
||||
|
||||
if (sEndMs == 0)
|
||||
sEndMs =
|
||||
(i + 1 < spans.Count)
|
||||
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
|
||||
: pEndMs;
|
||||
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = sEndMs });
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
span.Value += " ";
|
||||
}
|
||||
originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
}
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value;
|
||||
var originalCharTimings = new List<CharTiming>();
|
||||
int originalStartIndex = 0;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
originalCharTimings.Add(new CharTiming
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
StartIndex = originalStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
originalStartIndex += span.Value.Length;
|
||||
}
|
||||
if (originalTextSpans.Count == 0)
|
||||
originalText = p.Value;
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
originalLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
OriginalText = originalText,
|
||||
CharTimings = originalCharTimings,
|
||||
});
|
||||
|
||||
// 翻译
|
||||
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
|
||||
var translationCharTimings = new List<CharTiming>();
|
||||
int translationStartIndex = 0;
|
||||
foreach (var span in translationTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
translationCharTimings.Add(new CharTiming
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
StartIndex = translationStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
translationStartIndex += span.Value.Length;
|
||||
}
|
||||
if (translationTextSpans.Count > 0)
|
||||
{
|
||||
translationLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = pEndMs,
|
||||
Text = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
EndMs = 0,
|
||||
OriginalText = translationText,
|
||||
CharTimings = translationCharTimings,
|
||||
});
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(singleLangLyricsLine);
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
_lyricsDataArr.Add(new LyricsData(originalLines));
|
||||
if (translationLines.Count > 0)
|
||||
_lyricsDataArr.Add(new LyricsData(translationLines));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -358,12 +261,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ParseTtmlTime
|
||||
/// </summary>
|
||||
/// <param name="t">The t<see cref="string?"/></param>
|
||||
/// <returns>The <see cref="int"/></returns>
|
||||
private int ParseTtmlTime(string? t)
|
||||
private static int ParseTtmlTime(string? t)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(t))
|
||||
return 0;
|
||||
@@ -424,27 +322,113 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The PostProcessLyricsLines
|
||||
/// </summary>
|
||||
/// <param name="lines">The lines<see cref="List{LyricsLine}"/></param>
|
||||
private void PostProcessLyricsLines(List<LyricsLine> lines)
|
||||
private void ParseQQNeteaseKugou(List<ILineInfo>? lines)
|
||||
{
|
||||
if (lines.Count > 0 && lines[0].StartMs > 0)
|
||||
lines = lines?.Where(x => x.Text != string.Empty).ToList();
|
||||
List<LyricsLine> lyricsLines = [];
|
||||
|
||||
if (lines != null && lines.Count > 0)
|
||||
{
|
||||
lines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
lyricsLines = [];
|
||||
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
|
||||
{
|
||||
var lineRead = lines[lineIndex];
|
||||
var lineWrite = new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
Text = "● ● ●",
|
||||
StartMs = lineRead.StartTime ?? 0,
|
||||
EndMs = 0,
|
||||
OriginalText = lineRead.Text,
|
||||
CharTimings = [],
|
||||
};
|
||||
|
||||
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
|
||||
if (syllables != null)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (
|
||||
int syllableIndex = 0;
|
||||
syllableIndex < syllables.Count;
|
||||
syllableIndex++
|
||||
)
|
||||
{
|
||||
var syllable = syllables[syllableIndex];
|
||||
var charTiming = new CharTiming
|
||||
{
|
||||
StartMs = syllable.StartTime,
|
||||
EndMs = 0,
|
||||
Text = syllable.Text,
|
||||
StartIndex = startIndex,
|
||||
};
|
||||
if (syllableIndex + 1 < syllables.Count)
|
||||
{
|
||||
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
charTiming.EndMs = lineWrite.EndMs;
|
||||
}
|
||||
lineWrite.CharTimings.Add(charTiming);
|
||||
startIndex += syllable.Text.Length;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
lyricsLines.Add(lineWrite);
|
||||
}
|
||||
}
|
||||
|
||||
_lyricsDataArr.Add(new LyricsData(lyricsLines));
|
||||
}
|
||||
|
||||
#endregion
|
||||
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 = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
public static class MetadataHelper
|
||||
{
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppName = "BetterLyrics";
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs
Normal file
56
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class PathHelper
|
||||
{
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
|
||||
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string LyricsCacheDirectory => Path.Combine(CacheFolder, "lyrics");
|
||||
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "lrclib");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "netease");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "qq");
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.json");
|
||||
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
|
||||
|
||||
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
|
||||
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
|
||||
public static string iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class StringHelper
|
||||
{
|
||||
// 去除空格、括号、下划线、横杠、点、大小写等
|
||||
public static string Normalize(string s) =>
|
||||
new string(s
|
||||
.Where(c => char.IsLetterOrDigit(c))
|
||||
.ToArray())
|
||||
.ToLowerInvariant();
|
||||
public static string NewLine = "\n";
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,8 @@ using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SystemBackdropHelper" />
|
||||
/// </summary>
|
||||
public class SystemBackdropHelper
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The CreateSystemBackdrop
|
||||
/// </summary>
|
||||
/// <param name="backdropType">The backdropType<see cref="BackdropType"/></param>
|
||||
/// <returns>The <see cref="SystemBackdrop?"/></returns>
|
||||
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType)
|
||||
{
|
||||
return backdropType switch
|
||||
@@ -30,7 +20,5 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
{
|
||||
private T _currentValue;
|
||||
private float _durationSeconds;
|
||||
private EasingType? _easingType;
|
||||
private Func<T, T, float, T> _interpolator;
|
||||
private bool _isTransitioning;
|
||||
private float _progress;
|
||||
private T _startValue;
|
||||
private T _targetValue;
|
||||
|
||||
public float DurationSeconds => _durationSeconds;
|
||||
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
public T Value => _currentValue;
|
||||
public T TargetValue => _targetValue;
|
||||
|
||||
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
_targetValue = initialValue;
|
||||
_durationSeconds = durationSeconds;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
|
||||
if (interpolator != null)
|
||||
{
|
||||
_interpolator = interpolator;
|
||||
_easingType = null;
|
||||
}
|
||||
else if (easingType.HasValue)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_easingType = EasingType.Linear;
|
||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDuration(float seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
|
||||
_durationSeconds = seconds;
|
||||
}
|
||||
|
||||
private void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
public void Reset(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
public void StartTransition(T targetValue, bool jumpTo = false)
|
||||
{
|
||||
if (jumpTo)
|
||||
{
|
||||
JumpTo(targetValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetValue.Equals(_currentValue))
|
||||
{
|
||||
_startValue = _currentValue;
|
||||
_targetValue = targetValue;
|
||||
_progress = 0f;
|
||||
_isTransitioning = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Equals(double x, double y, double tolerance)
|
||||
{
|
||||
var diff = Math.Abs(x - y);
|
||||
return diff <= tolerance || diff <= Math.Max(Math.Abs(x), Math.Abs(y)) * tolerance;
|
||||
}
|
||||
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
if (!_isTransitioning) return;
|
||||
|
||||
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
_currentValue = _targetValue;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
||||
}
|
||||
}
|
||||
|
||||
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
|
||||
{
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
return (start, end, progress) =>
|
||||
{
|
||||
float s = (float)(object)start;
|
||||
float e = (float)(object)end;
|
||||
float t = progress;
|
||||
switch (type)
|
||||
{
|
||||
case EasingType.EaseInOutSine:
|
||||
t = EasingHelper.EaseInOutSine(t);
|
||||
break;
|
||||
case EasingType.EaseInOutQuad:
|
||||
t = EasingHelper.EaseInOutQuad(t);
|
||||
break;
|
||||
case EasingType.EaseInOutCubic:
|
||||
t = EasingHelper.EaseInOutCubic(t);
|
||||
break;
|
||||
case EasingType.EaseInOutQuart:
|
||||
t = EasingHelper.EaseInOutQuart(t);
|
||||
break;
|
||||
case EasingType.EaseInOutQuint:
|
||||
t = EasingHelper.EaseInOutQuint(t);
|
||||
break;
|
||||
case EasingType.EaseInOutExpo:
|
||||
t = EasingHelper.EaseInOutExpo(t);
|
||||
break;
|
||||
case EasingType.EaseInOutCirc:
|
||||
t = EasingHelper.EaseInOutCirc(t);
|
||||
break;
|
||||
case EasingType.EaseInOutBack:
|
||||
t = EasingHelper.EaseInOutBack(t);
|
||||
break;
|
||||
case EasingType.EaseInOutElastic:
|
||||
t = EasingHelper.EaseInOutElastic(t);
|
||||
break;
|
||||
case EasingType.EaseInOutBounce:
|
||||
t = EasingHelper.EaseInOutBounce(t);
|
||||
break;
|
||||
case EasingType.SmoothStep:
|
||||
t = EasingHelper.SmoothStep(t);
|
||||
break;
|
||||
case EasingType.Linear:
|
||||
t = EasingHelper.Linear(t);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (T)(object)(s + (e - s) * t);
|
||||
};
|
||||
}
|
||||
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
|
||||
}
|
||||
|
||||
public void SetEasingType(EasingType easingType)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(easingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class WindowColorHelper
|
||||
{
|
||||
public static Color GetDominantColorBelow(IntPtr myHwnd)
|
||||
{
|
||||
if (!GetWindowRect(myHwnd, out RECT myRect))
|
||||
return Color.Transparent;
|
||||
|
||||
int screenWidth = GetSystemMetrics(SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
|
||||
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return Color.Transparent;
|
||||
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
|
||||
#region Win32 Imports & Structs
|
||||
private const int SRCCOPY = 0x00CC0020;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern bool BitBlt(
|
||||
IntPtr hdcDest,
|
||||
int nXDest,
|
||||
int nYDest,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hdcSrc,
|
||||
int nXSrc,
|
||||
int nYSrc,
|
||||
int dwRop
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(SystemMetric smIndex);
|
||||
|
||||
private enum SystemMetric
|
||||
{
|
||||
SM_CXSCREEN = 0,
|
||||
SM_CYSCREEN = 1,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,228 +1,106 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="WindowHelper" />
|
||||
/// </summary>
|
||||
public static class WindowHelper
|
||||
{
|
||||
#region Fields
|
||||
private static List<object> _activeWindows = [];
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _windowCache
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Type, Window> _windowCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _activeWindows
|
||||
/// </summary>
|
||||
private static List<Window> _activeWindows = new List<Window>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ActiveWindows
|
||||
/// </summary>
|
||||
public static List<Window> ActiveWindows
|
||||
public static void CloseWindow<T>()
|
||||
{
|
||||
get { return _activeWindows; }
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window is Window w)
|
||||
{
|
||||
w.Close();
|
||||
_activeWindows.Remove(w);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The GetWindowByFramePageType
|
||||
/// </summary>
|
||||
/// <param name="type">The type<see cref="Type"/></param>
|
||||
/// <returns>The <see cref="Window"/></returns>
|
||||
public static Window GetWindowByFramePageType(Type type)
|
||||
public static void ExitAllWindows()
|
||||
{
|
||||
foreach (var cachedWindow in _windowCache)
|
||||
while (_activeWindows.Count > 0)
|
||||
{
|
||||
if (cachedWindow.Key == type)
|
||||
var window = _activeWindows[0];
|
||||
((Window)window).Close();
|
||||
_activeWindows.Remove(window);
|
||||
}
|
||||
App.Current.Exit();
|
||||
}
|
||||
|
||||
public static T? GetWindowByWindowType<T>()
|
||||
{
|
||||
foreach (var window in _activeWindows)
|
||||
{
|
||||
if (window is T castedWindow)
|
||||
{
|
||||
return cachedWindow.Value;
|
||||
return castedWindow;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetWindowForElement
|
||||
/// </summary>
|
||||
/// <param name="element">The element<see cref="UIElement"/></param>
|
||||
/// <returns>The <see cref="Window"/></returns>
|
||||
public static Window GetWindowForElement(UIElement element)
|
||||
public static void OpenOrShowWindow<T>()
|
||||
{
|
||||
if (element.XamlRoot != null)
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window != null)
|
||||
{
|
||||
foreach (Window window in _activeWindows)
|
||||
{
|
||||
if (element.XamlRoot == window.Content.XamlRoot)
|
||||
{
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HideSystemTitleBar
|
||||
/// </summary>
|
||||
/// <param name="window">The window<see cref="Window"/></param>
|
||||
public static void HideSystemTitleBar(this Window window)
|
||||
{
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HideSystemTitleBarAndSetCustomTitleBar
|
||||
/// </summary>
|
||||
/// <param name="window">The window<see cref="Window"/></param>
|
||||
/// <param name="titleBar">The titleBar<see cref="UIElement"/></param>
|
||||
public static void HideSystemTitleBarAndSetCustomTitleBar(
|
||||
this Window window,
|
||||
UIElement titleBar
|
||||
)
|
||||
{
|
||||
window.HideSystemTitleBar();
|
||||
window.SetTitleBar(titleBar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OpenLyricsWindow
|
||||
/// </summary>
|
||||
public static void OpenLyricsWindow()
|
||||
{
|
||||
OpenOrShowWindow(typeof(LyricsPage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OpenSettingsWindow
|
||||
/// </summary>
|
||||
public static void OpenSettingsWindow()
|
||||
{
|
||||
OpenOrShowWindow(typeof(SettingsPage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TrackWindow
|
||||
/// </summary>
|
||||
/// <param name="window">The window<see cref="Window"/></param>
|
||||
/// <param name="pageType">The pageType<see cref="Type"/></param>
|
||||
public static void TrackWindow(Window window, Type pageType = null)
|
||||
{
|
||||
if (pageType != null)
|
||||
{
|
||||
_windowCache[pageType] = window;
|
||||
}
|
||||
|
||||
if (!_activeWindows.Contains(window))
|
||||
_activeWindows.Add(window);
|
||||
|
||||
window.Closed -= Window_Closed;
|
||||
window.Closed += Window_Closed;
|
||||
}
|
||||
|
||||
private static void Window_Closed(object sender, WindowEventArgs e)
|
||||
{
|
||||
if (sender is Window closedWindow)
|
||||
{
|
||||
_activeWindows.Remove(closedWindow);
|
||||
|
||||
// 从缓存移除
|
||||
foreach (var kvp in _windowCache)
|
||||
{
|
||||
if (kvp.Value == closedWindow)
|
||||
{
|
||||
_windowCache.Remove(kvp.Key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TryHide
|
||||
/// </summary>
|
||||
/// <param name="window">The window<see cref="Window"/></param>
|
||||
public static void TryHide(this Window window)
|
||||
{
|
||||
if (window is not null)
|
||||
{
|
||||
window.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TryShow
|
||||
/// </summary>
|
||||
/// <param name="window">The window<see cref="Window"/></param>
|
||||
public static void TryShow(this Window window)
|
||||
{
|
||||
if (window is not null)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OpenOrShowWindow
|
||||
/// </summary>
|
||||
/// <param name="pageType">The pageType<see cref="Type"/></param>
|
||||
private static void OpenOrShowWindow(Type pageType)
|
||||
{
|
||||
if (_windowCache.TryGetValue(pageType, out var window))
|
||||
{
|
||||
window.TryShow();
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
}
|
||||
else
|
||||
{
|
||||
var newWindow = new HostWindow();
|
||||
TrackWindow(newWindow, pageType);
|
||||
newWindow.ViewModel.FramePageType = pageType;
|
||||
newWindow.Navigate(pageType);
|
||||
newWindow.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
// get dpi for an element
|
||||
|
||||
/// <summary>
|
||||
/// The GetRasterizationScaleForElement
|
||||
/// </summary>
|
||||
/// <param name="element">The element<see cref="UIElement"/></param>
|
||||
/// <returns>The <see cref="double"/></returns>
|
||||
static public double GetRasterizationScaleForElement(UIElement element)
|
||||
{
|
||||
if (element.XamlRoot != null)
|
||||
{
|
||||
foreach (Window window in _activeWindows)
|
||||
object newWindow;
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
if (element.XamlRoot == window.Content.XamlRoot)
|
||||
{
|
||||
return element.XamlRoot.RasterizationScale;
|
||||
}
|
||||
newWindow = new LyricsWindow();
|
||||
((LyricsWindow)newWindow).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
}
|
||||
else if (typeof(T) == typeof(SettingsWindow))
|
||||
{
|
||||
newWindow = new SettingsWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unsupported window type", nameof(T));
|
||||
}
|
||||
((Window)newWindow).Activate();
|
||||
TrackWindow(newWindow);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
public static void RestartApp(string args = "")
|
||||
{
|
||||
// The restart will be executed immediately.
|
||||
AppRestartFailureReason failureReason =
|
||||
Microsoft.Windows.AppLifecycle.AppInstance.Restart(args);
|
||||
|
||||
// If the restart fails, handle it here.
|
||||
switch (failureReason)
|
||||
{
|
||||
case AppRestartFailureReason.RestartPending:
|
||||
break;
|
||||
case AppRestartFailureReason.NotInForeground:
|
||||
break;
|
||||
case AppRestartFailureReason.InvalidUser:
|
||||
break;
|
||||
default: //AppRestartFailureReason.Other
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TrackWindow(object window)
|
||||
{
|
||||
if (!_activeWindows.Contains(window))
|
||||
_activeWindows.Add(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ShowNotificatonMessage" />
|
||||
/// </summary>
|
||||
public class ShowNotificatonMessage(Notification value)
|
||||
: ValueChangedMessage<Notification>(value)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class AlbumArtSearchProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AlbumArtSearchProvider Provider { get; set; }
|
||||
|
||||
public AlbumArtSearchProviderInfo() { }
|
||||
|
||||
public AlbumArtSearchProviderInfo(AlbumArtSearchProvider provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="CharTiming" />
|
||||
/// </summary>
|
||||
public class CharTiming
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EndMs
|
||||
/// </summary>
|
||||
public int EndMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the StartMs
|
||||
/// </summary>
|
||||
public int StartMs { get; set; }
|
||||
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
|
||||
#endregion
|
||||
public int StartMs { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LanguageInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial string Code { get; set; }
|
||||
[ObservableProperty]
|
||||
public partial string Name { get; set; }
|
||||
|
||||
public LanguageInfo(string code, string name)
|
||||
{
|
||||
Code = code;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,47 +4,20 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LocalLyricsFolder" />
|
||||
/// </summary>
|
||||
public partial class LocalLyricsFolder : ObservableObject
|
||||
{
|
||||
#region Constructors
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
|
||||
/// </summary>
|
||||
public LocalLyricsFolder()
|
||||
{
|
||||
}
|
||||
[ObservableProperty]
|
||||
public partial string Path { get; set; }
|
||||
|
||||
public LocalLyricsFolder() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">The path<see cref="string"/></param>
|
||||
/// <param name="isEnabled">The isEnabled<see cref="bool"/></param>
|
||||
public LocalLyricsFolder(string path, bool isEnabled)
|
||||
{
|
||||
Path = path;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsEnabled
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Path
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string Path { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,95 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsData" />
|
||||
/// </summary>
|
||||
public class LyricsData
|
||||
{
|
||||
#region Properties
|
||||
public List<LyricsLine> LyricsLines { get; set; }
|
||||
public string? LanguageCode => LanguageHelper.DetectLanguageCode(WrappedOriginalText);
|
||||
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LanguageIndex
|
||||
/// </summary>
|
||||
public int LanguageIndex { get; set; } = 0;
|
||||
public LyricsData()
|
||||
{
|
||||
LyricsLines = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LyricsLines
|
||||
/// </summary>
|
||||
public List<LyricsLine> LyricsLines => MultiLangLyricsLines[LanguageIndex];
|
||||
public LyricsData(List<LyricsLine> lyricsLines)
|
||||
{
|
||||
LyricsLines = lyricsLines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MultiLangLyricsLines
|
||||
/// </summary>
|
||||
public List<List<LyricsLine>> MultiLangLyricsLines { get; set; } = [];
|
||||
public void SetDisplayedTextAlongWith(LyricsData translationData)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= translationData.LyricsLines.Count)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText; // No translation available, keep original text
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationData.LyricsLines[i].OriginalText})";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
public void SetDisplayedTextAlongWith(string translation)
|
||||
{
|
||||
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= translationArr.Count)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText; // No translation available, keep original text
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationArr[i]})";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDisplayedTextInOriginalText()
|
||||
{
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText;
|
||||
}
|
||||
}
|
||||
|
||||
public static LyricsData GetNotfoundPlaceholder(int durationMs)
|
||||
{
|
||||
return new LyricsData([new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
}]);
|
||||
}
|
||||
|
||||
public static LyricsData GetLoadingPlaceholder()
|
||||
{
|
||||
return new LyricsData([
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
DisplayedText = "● ● ●",
|
||||
CharTimings = [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,68 +7,27 @@ using Microsoft.Graphics.Canvas.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsLine" />
|
||||
/// </summary>
|
||||
public class LyricsLine
|
||||
{
|
||||
#region Properties
|
||||
private const float _animationDuration = 0.3f;
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the BlurAmountTransition
|
||||
/// </summary>
|
||||
public ValueTransition<float> BlurAmountTransition { get; set; } =
|
||||
new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CanvasTextLayout
|
||||
/// </summary>
|
||||
public CanvasTextLayout? CanvasTextLayout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CenterPosition
|
||||
/// </summary>
|
||||
public Vector2 CenterPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CharTimings
|
||||
/// </summary>
|
||||
public List<CharTiming> CharTimings { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DurationMs
|
||||
/// </summary>
|
||||
public int DurationMs => EndMs - StartMs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EndMs
|
||||
/// </summary>
|
||||
public int EndMs { get; set; }
|
||||
|
||||
public ValueTransition<float> HighlightOpacityTransition { get; set; } =
|
||||
new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Position
|
||||
/// </summary>
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ScaleTransition
|
||||
/// </summary>
|
||||
public ValueTransition<float> ScaleTransition { get; set; } =
|
||||
new(initialValue: 0.95f, durationSeconds: 0.3f);
|
||||
public List<CharTiming> CharTimings { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the StartMs
|
||||
/// </summary>
|
||||
public int DurationMs => EndMs - StartMs;
|
||||
public int EndMs { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Text
|
||||
/// </summary>
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
#endregion
|
||||
public string DisplayedText { get; set; } = "";
|
||||
public string OriginalText { get; set; } = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,47 +5,21 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsSearchProviderInfo" />
|
||||
/// </summary>
|
||||
public partial class LyricsSearchProviderInfo : ObservableObject
|
||||
{
|
||||
#region Constructors
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LyricsSearchProviderInfo"/> class.
|
||||
/// </summary>
|
||||
public LyricsSearchProviderInfo()
|
||||
{
|
||||
}
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
|
||||
public LyricsSearchProviderInfo() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LyricsSearchProviderInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider<see cref="LyricsSearchProvider"/></param>
|
||||
/// <param name="isEnabled">The isEnabled<see cref="bool"/></param>
|
||||
public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsEnabled
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Provider
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class MediaSourceProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Provider { get; set; }
|
||||
|
||||
public MediaSourceProviderInfo() { }
|
||||
|
||||
public MediaSourceProviderInfo(string provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Notification" />
|
||||
/// </summary>
|
||||
public partial class Notification : ObservableObject
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Notification"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="string?"/></param>
|
||||
/// <param name="severity">The severity<see cref="InfoBarSeverity"/></param>
|
||||
/// <param name="isForeverDismissable">The isForeverDismissable<see cref="bool"/></param>
|
||||
/// <param name="relatedSettingsKeyName">The relatedSettingsKeyName<see cref="string?"/></param>
|
||||
public Notification(
|
||||
string? message = null,
|
||||
InfoBarSeverity severity = InfoBarSeverity.Informational,
|
||||
bool isForeverDismissable = false,
|
||||
string? relatedSettingsKeyName = null
|
||||
)
|
||||
{
|
||||
Message = message;
|
||||
Severity = severity;
|
||||
IsForeverDismissable = isForeverDismissable;
|
||||
Visibility = IsForeverDismissable ? Visibility.Visible : Visibility.Collapsed;
|
||||
RelatedSettingsKeyName = relatedSettingsKeyName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsForeverDismissable
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial bool IsForeverDismissable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Message
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the RelatedSettingsKeyName
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string? RelatedSettingsKeyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Severity
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial InfoBarSeverity Severity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Visibility
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial Visibility Visibility { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,28 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SongInfo" />
|
||||
/// </summary>
|
||||
public partial class SongInfo : ObservableObject
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SongInfo"/> class.
|
||||
/// </summary>
|
||||
public SongInfo()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Album
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string? Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AlbumArt
|
||||
/// </summary>
|
||||
public byte[]? AlbumArt { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Artist
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string Artist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DurationMs
|
||||
/// In milliseconds
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial double? DurationMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SourceAppUserModelId
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string? SourceAppUserModelId { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Title
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial string Title { get; set; }
|
||||
|
||||
#endregion
|
||||
public SongInfo() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class TranslateResponse
|
||||
{
|
||||
[JsonPropertyName("translatedText")]
|
||||
public string TranslatedText { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Renderer"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
@@ -14,5 +15,15 @@
|
||||
x:Name="LyricsCanvas"
|
||||
Draw="LyricsCanvas_Draw"
|
||||
Update="LyricsCanvas_Update" />
|
||||
<Grid
|
||||
Margin="36"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="{x:Bind ViewModel.IsTranslating, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<FontIcon
|
||||
x:Name="RotatingIcon"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,74 +1,30 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Diagnostics;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsRenderer" />
|
||||
/// </summary>
|
||||
public sealed partial class LyricsRenderer : UserControl
|
||||
{
|
||||
#region Constructors
|
||||
public LyricsRendererViewModel ViewModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LyricsRenderer"/> class.
|
||||
/// </summary>
|
||||
public LyricsRenderer()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ViewModel
|
||||
/// </summary>
|
||||
public LyricsRendererViewModel ViewModel { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The LyricsCanvas_Draw
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl"/></param>
|
||||
/// <param name="args">The args<see cref="Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs"/></param>
|
||||
private void LyricsCanvas_Draw(
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args
|
||||
)
|
||||
private void LyricsCanvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
|
||||
{
|
||||
ViewModel.Draw(sender, args.DrawingSession);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LyricsCanvas_Update
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl"/></param>
|
||||
/// <param name="args">The args<see cref="Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs"/></param>
|
||||
private void LyricsCanvas_Update(
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args
|
||||
)
|
||||
private void LyricsCanvas_Update(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
|
||||
{
|
||||
ViewModel.Update(sender, args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SourceGenerationContext" />
|
||||
/// </summary>
|
||||
|
||||
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(TranslateResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
internal partial class SourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
internal partial class SourceGenerationContext : JsonSerializerContext { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class AlbumArtSearchService : IAlbumArtSearchService
|
||||
{
|
||||
private readonly HttpClient _iTunesHttpClinet;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public AlbumArtSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<AlbumArtSearchService>>();
|
||||
_iTunesHttpClinet = new();
|
||||
}
|
||||
|
||||
public async Task<byte[]?> SearchAsync(string title, string artist, string album, byte[]? bytesFromSMTC = null)
|
||||
{
|
||||
byte[]? result = null;
|
||||
|
||||
foreach (var provider in _settingsService.AlbumArtSearchProvidersInfo)
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case AlbumArtSearchProvider.Local:
|
||||
result = SearchFile(artist, album);
|
||||
break;
|
||||
case AlbumArtSearchProvider.SMTC:
|
||||
result = bytesFromSMTC;
|
||||
break;
|
||||
case AlbumArtSearchProvider.iTunes:
|
||||
result = await SearchiTunesAsync(artist, album);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != null) return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[]? SearchFile(string artist, string album)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), album, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album)
|
||||
{
|
||||
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
|
||||
try
|
||||
{
|
||||
string format = ".jpg";
|
||||
var cachedAlbumArt = FileHelper.ReadAlbumArtCache(artist, album, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
|
||||
if (cachedAlbumArt != null)
|
||||
{
|
||||
return cachedAlbumArt;
|
||||
}
|
||||
|
||||
// Build the iTunes API URL
|
||||
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
|
||||
|
||||
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Parse the JSON response
|
||||
var data = JsonSerializer.Deserialize(responseBody, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
|
||||
if (data.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
|
||||
{
|
||||
// Get the first result
|
||||
var result = results[0];
|
||||
if (result.TryGetProperty("artworkUrl100", out var artworkUrlProp))
|
||||
{
|
||||
string artworkUrl = artworkUrlProp.GetString()?.Replace("100x100bb.jpg", "1200x1200bb.jpg") ?? string.Empty;
|
||||
var fetched = await _iTunesHttpClinet.GetByteArrayAsync(artworkUrl);
|
||||
|
||||
if (fetched != null && fetched.Length > 0)
|
||||
{
|
||||
// Write to cache
|
||||
FileHelper.WriteAlbumArtCache(artist, album, fetched, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
return fetched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface IAlbumArtSearchService
|
||||
{
|
||||
Task<byte[]?> SearchAsync(string title, string artist, string album, byte[]? bytesFromSMTC = null);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,19 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
#region Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ILibWatcherService" />
|
||||
/// </summary>
|
||||
public interface ILibWatcherService
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Defines the MusicLibraryFilesChanged
|
||||
/// </summary>
|
||||
event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The UpdateWatchers
|
||||
/// </summary>
|
||||
/// <param name="folders">The folders<see cref="List{LocalLyricsFolder}"/></param>
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
#region Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IMusicSearchService" />
|
||||
/// </summary>
|
||||
public interface IMusicSearchService
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The SearchAlbumArtAsync
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <returns>The <see cref="byte[]?"/></returns>
|
||||
byte[]? SearchAlbumArtAsync(string title, string artist);
|
||||
|
||||
/// <summary>
|
||||
/// The SearchLyricsAsync
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="album">The album<see cref="string"/></param>
|
||||
/// <param name="durationMs">The durationMs<see cref="double"/></param>
|
||||
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
|
||||
/// <returns>The <see cref="Task{(string?, LyricsFormat?)}"/></returns>
|
||||
Task<(string?, LyricsFormat?)> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album = "",
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,56 +1,17 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
#region Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IPlaybackService" />
|
||||
/// </summary>
|
||||
public interface IPlaybackService
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsPlayingChanged
|
||||
/// </summary>
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the PositionChanged
|
||||
/// </summary>
|
||||
event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SongInfoChanged
|
||||
/// </summary>
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether IsPlaying
|
||||
/// </summary>
|
||||
bool IsPlaying { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Position
|
||||
/// </summary>
|
||||
TimeSpan Position { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SongInfo
|
||||
/// </summary>
|
||||
SongInfo? SongInfo { get; }
|
||||
|
||||
#endregion
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -5,143 +5,87 @@ using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
#region Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ISettingsService" />
|
||||
/// </summary>
|
||||
public interface ISettingsService
|
||||
{
|
||||
#region Properties
|
||||
|
||||
// App behavior
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoStartWindowType
|
||||
/// </summary>
|
||||
AutoStartWindowType AutoStartWindowType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the BackdropType
|
||||
/// </summary>
|
||||
BackdropType BackdropType { get; set; }
|
||||
|
||||
// Album art cover style
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CoverImageRadius
|
||||
/// </summary>
|
||||
int CoverImageRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CoverOverlayBlurAmount
|
||||
/// </summary>
|
||||
int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CoverOverlayOpacity
|
||||
/// </summary>
|
||||
int CoverOverlayOpacity { get; set; }
|
||||
|
||||
// Album art background
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
|
||||
/// </summary>
|
||||
bool IsCoverOverlayEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
|
||||
/// </summary>
|
||||
bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsFirstRun
|
||||
/// </summary>
|
||||
bool IsFanLyricsEnabled { get; set; }
|
||||
bool IsFirstRun { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
|
||||
/// </summary>
|
||||
bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Language
|
||||
/// </summary>
|
||||
Language Language { get; set; }
|
||||
int DesktopWindowLeft { get; set; }
|
||||
int DesktopWindowTop { get; set; }
|
||||
int DesktopWindowWidth { get; set; }
|
||||
int DesktopWindowHeight { get; set; }
|
||||
|
||||
int StandardWindowWidth { get; set; }
|
||||
int StandardWindowHeight { get; set; }
|
||||
int StandardWindowLeft { get; set; }
|
||||
int StandardWindowTop { get; set; }
|
||||
|
||||
bool AutoLockOnDesktopMode { get; set; }
|
||||
|
||||
string LibreTranslateServer { get; set; }
|
||||
int SelectedTargetLanguageIndex { get; set; }
|
||||
// Lyrics lib
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LocalLyricsFolders
|
||||
/// </summary>
|
||||
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
|
||||
// Lyrics style and effetc
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsAlignmentType
|
||||
/// </summary>
|
||||
LyricsAlignmentType LyricsAlignmentType { get; set; }
|
||||
TextAlignmentType LyricsAlignmentType { get; set; }
|
||||
TextAlignmentType SongInfoAlignmentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsBlurAmount
|
||||
/// </summary>
|
||||
int LyricsBlurAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontColorType
|
||||
/// </summary>
|
||||
LyricsFontColorType LyricsFontColorType { get; set; }
|
||||
Color LyricsCustomBgFontColor { get; set; }
|
||||
Color LyricsCustomFgFontColor { get; set; }
|
||||
Color LyricsCustomStrokeFontColor { get; set; }
|
||||
|
||||
LyricsFontColorType LyricsBgFontColorType { get; set; }
|
||||
LyricsFontColorType LyricsFgFontColorType { get; set; }
|
||||
LyricsFontColorType LyricsStrokeFontColorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontSize
|
||||
/// </summary>
|
||||
int LyricsFontSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontWeight
|
||||
/// </summary>
|
||||
ElementTheme LyricsBackgroundTheme { get; set; }
|
||||
|
||||
int LyricsFontStrokeWidth { get; set; }
|
||||
|
||||
LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsGlowEffectScope
|
||||
/// </summary>
|
||||
LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsLineSpacingFactor
|
||||
/// </summary>
|
||||
float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsSearchProvidersInfo
|
||||
/// </summary>
|
||||
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
|
||||
List<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
|
||||
|
||||
EasingType LyricsScrollEasingType { get; set; }
|
||||
int LyricsScrollDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsVerticalEdgeOpacity
|
||||
/// </summary>
|
||||
int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
// App appearance
|
||||
bool IgnoreFullscreenWindow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ThemeType
|
||||
/// </summary>
|
||||
ElementTheme ThemeType { get; set; }
|
||||
bool IsTranslationEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TitleBarType
|
||||
/// </summary>
|
||||
TitleBarType TitleBarType { get; set; }
|
||||
LyricsDisplayType PreferredDisplayType { get; set; }
|
||||
|
||||
#endregion
|
||||
int TimelineSyncThreshold { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ITranslateService
|
||||
{
|
||||
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token);
|
||||
|
||||
int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr);
|
||||
}
|
||||
}
|
||||
@@ -2,136 +2,83 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
using global::BetterLyrics.WinUI3.Events;
|
||||
using global::BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LibWatcherService" />
|
||||
/// </summary>
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
|
||||
|
||||
public LibWatcherService(ISettingsService settingsService)
|
||||
{
|
||||
#region Fields
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _settingsService
|
||||
/// </summary>
|
||||
private readonly ISettingsService _settingsService;
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _watchers
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibWatcherService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
|
||||
public LibWatcherService(ISettingsService settingsService)
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var watcher in _watchers.Values)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
watcher.Dispose();
|
||||
}
|
||||
_watchers.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Defines the MusicLibraryFilesChanged
|
||||
/// </summary>
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Dispose
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders)
|
||||
{
|
||||
// 移除不再监听的
|
||||
foreach (var key in _watchers.Keys.ToList())
|
||||
{
|
||||
foreach (var watcher in _watchers.Values)
|
||||
if (!folders.Any(x => x.Path == key && x.IsEnabled))
|
||||
{
|
||||
watcher.Dispose();
|
||||
}
|
||||
_watchers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The UpdateWatchers
|
||||
/// </summary>
|
||||
/// <param name="folders">The folders<see cref="List{LocalLyricsFolder}"/></param>
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders)
|
||||
{
|
||||
// 移除不再监听的
|
||||
foreach (var key in _watchers.Keys.ToList())
|
||||
{
|
||||
if (!folders.Any(x => x.Path == key && x.IsEnabled))
|
||||
{
|
||||
_watchers[key].Dispose();
|
||||
_watchers.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新的监听
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
if (
|
||||
!_watchers.ContainsKey(folder.Path)
|
||||
&& Directory.Exists(folder.Path)
|
||||
&& folder.IsEnabled
|
||||
)
|
||||
{
|
||||
var watcher = new FileSystemWatcher(folder.Path)
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
watcher.Created += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Changed += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
|
||||
_watchers[folder.Path] = watcher;
|
||||
}
|
||||
_watchers[key].Dispose();
|
||||
_watchers.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnChanged
|
||||
/// </summary>
|
||||
/// <param name="folder">The folder<see cref="string"/></param>
|
||||
/// <param name="e">The e<see cref="FileSystemEventArgs"/></param>
|
||||
private void OnChanged(string folder, FileSystemEventArgs e)
|
||||
// 添加新的监听
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
if (
|
||||
!_watchers.ContainsKey(folder.Path)
|
||||
&& Directory.Exists(folder.Path)
|
||||
&& folder.IsEnabled
|
||||
)
|
||||
{
|
||||
var watcher = new FileSystemWatcher(folder.Path)
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
watcher.Created += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Changed += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
|
||||
_watchers[folder.Path] = watcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
private void OnChanged(string folder, FileSystemEventArgs e)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Providers.Web.Kugou;
|
||||
using Lyricify.Lyrics.Searchers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LyricsSearchService : ILyricsSearchService
|
||||
{
|
||||
private readonly HttpClient _amllTtmlDbHttpClient;
|
||||
private readonly HttpClient _lrcLibHttpClient;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public LyricsSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsSearchService>>();
|
||||
|
||||
_lrcLibHttpClient = new();
|
||||
_lrcLibHttpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{MetadataHelper.AppName} {MetadataHelper.AppVersion} ({MetadataHelper.GithubUrl})"
|
||||
);
|
||||
_amllTtmlDbHttpClient = new();
|
||||
}
|
||||
|
||||
private static bool IsAmllTtmlDbIndexInvalid()
|
||||
{
|
||||
bool existed = File.Exists(PathHelper.AmllTtmlDbIndexPath);
|
||||
|
||||
if (!existed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
long currentTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
string lastUpdatedStr = File.ReadAllText(PathHelper.AmllTtmlDbLastUpdatedPath);
|
||||
long lastUpdated = Convert.ToInt64(lastUpdatedStr);
|
||||
return currentTs - lastUpdated > 1 * 24 * 60 * 60;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
|
||||
{
|
||||
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
|
||||
try
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
if (!response.IsSuccessStatusCode) return false;
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||
await using var fs = new FileStream(
|
||||
PathHelper.AmllTtmlDbIndexPath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None
|
||||
);
|
||||
await stream.CopyToAsync(fs);
|
||||
|
||||
long currentTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
File.WriteAllText(PathHelper.AmllTtmlDbLastUpdatedPath, currentTs.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return cachedLyrics;
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
|
||||
return searchedLyrics;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*{format.ToFileExtension()}", SearchOption.AllDirectories))
|
||||
{
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? SearchEmbedded(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
try
|
||||
{
|
||||
var plain = TagLib.File.Create(file).Tag.Lyrics;
|
||||
if (plain != null && plain != string.Empty)
|
||||
{
|
||||
return plain;
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
|
||||
{
|
||||
if (IsAmllTtmlDbIndexInvalid())
|
||||
{
|
||||
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
|
||||
if (!downloadOk)
|
||||
return null;
|
||||
}
|
||||
|
||||
string? rawLyricFile = null;
|
||||
await foreach (var line in File.ReadLinesAsync(PathHelper.AmllTtmlDbIndexPath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(line);
|
||||
var root = doc.RootElement;
|
||||
if (!root.TryGetProperty("metadata", out var metadataArr))
|
||||
continue;
|
||||
string? musicName = null;
|
||||
string? artists = null;
|
||||
foreach (var meta in metadataArr.EnumerateArray())
|
||||
{
|
||||
if (meta.GetArrayLength() != 2)
|
||||
continue;
|
||||
var key = meta[0].GetString();
|
||||
var valueArr = meta[1];
|
||||
if (key == "musicName" && valueArr.GetArrayLength() > 0)
|
||||
musicName = valueArr[0].GetString();
|
||||
if (key == "artists" && valueArr.GetArrayLength() > 0)
|
||||
artists = valueArr[0].GetString();
|
||||
}
|
||||
if (musicName == null || artists == null)
|
||||
continue;
|
||||
|
||||
if (FileHelper.IsSwitchableNormalizedMatch($"{artists} - {musicName}", title, artist))
|
||||
{
|
||||
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
|
||||
{
|
||||
rawLyricFile = rawLyricFileProp.GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawLyricFile))
|
||||
return null;
|
||||
|
||||
// 下载歌词内容
|
||||
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string?> SearchLrcLibAsync(string title, string artist, string album, int duration)
|
||||
{
|
||||
// Build API query URL
|
||||
var url =
|
||||
$"https://lrclib.net/api/search?" +
|
||||
$"track_name={Uri.EscapeDataString(title)}&" +
|
||||
$"artist_name={Uri.EscapeDataString(artist)}&" +
|
||||
$"&album_name={Uri.EscapeDataString(album)}" +
|
||||
$"&durationMs={Uri.EscapeDataString(duration.ToString())}";
|
||||
|
||||
var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var jArr = JsonSerializer.Deserialize(
|
||||
json,
|
||||
Serialization.SourceGenerationContext.Default.JsonElement
|
||||
);
|
||||
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
|
||||
{
|
||||
var first = jArr[0];
|
||||
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
|
||||
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<string?> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, Searchers searchers)
|
||||
{
|
||||
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs = durationMs,
|
||||
Album = album,
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
);
|
||||
|
||||
if (result is QQMusicSearchResult qqResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
|
||||
var original = response?.Lyrics;
|
||||
return original;
|
||||
}
|
||||
else if (result is NeteaseSearchResult neteaseResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
|
||||
return response?.Lrc.Lyric;
|
||||
}
|
||||
else if (result is KugouSearchResult kugouResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
|
||||
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
|
||||
{
|
||||
return Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(
|
||||
candidate.Id,
|
||||
candidate.AccessKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,671 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MusicSearchService" />
|
||||
/// </summary>
|
||||
public class MusicSearchService : IMusicSearchService
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _httpClient
|
||||
/// </summary>
|
||||
private readonly HttpClient _lrcLibHttpClient;
|
||||
|
||||
private readonly HttpClient _amllTtmlDbHttpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _settingsService
|
||||
/// </summary>
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MusicSearchService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
|
||||
public MusicSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_lrcLibHttpClient = new HttpClient();
|
||||
_lrcLibHttpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
|
||||
);
|
||||
_amllTtmlDbHttpClient = new HttpClient();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The SearchAlbumArtAsync
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <returns>The <see cref="byte[]?"/></returns>
|
||||
public byte[]? SearchAlbumArtAsync(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*.*",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SearchLyricsAsync
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="album">The album<see cref="string"/></param>
|
||||
/// <param name="durationMs">The durationMs<see cref="double"/></param>
|
||||
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
|
||||
/// <returns>The <see cref="Task{(string?, LyricsFormat?)}"/></returns>
|
||||
public async Task<(string?, LyricsFormat?)> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album = "",
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
)
|
||||
{
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
cachedLyrics = ReadCache(
|
||||
title,
|
||||
artist,
|
||||
lyricsFormat,
|
||||
provider.Provider.GetCacheDirectory()
|
||||
);
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, lyricsFormat);
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
|
||||
title,
|
||||
artist,
|
||||
lyricsFormat
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)(durationMs / 1000),
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchKugouAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchNeteaseAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
WriteCache(
|
||||
title,
|
||||
artist,
|
||||
searchedLyrics,
|
||||
lyricsFormat,
|
||||
provider.Provider.GetCacheDirectory()
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
searchedLyrics,
|
||||
lyricsFormat == LyricsFormat.NotSpecified
|
||||
? searchedLyrics.DetectFormat()
|
||||
: lyricsFormat
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private static bool MusicMatch(string fileName, string title, string artist)
|
||||
{
|
||||
return fileName.Contains(title) && fileName.Contains(artist);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SanitizeFileName
|
||||
/// </summary>
|
||||
/// <param name="fileName">The fileName<see cref="string"/></param>
|
||||
/// <param name="replacement">The replacement<see cref="char"/></param>
|
||||
/// <returns>The <see cref="string"/></returns>
|
||||
private static string SanitizeFileName(string fileName, char replacement = '_')
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(fileName.Length);
|
||||
foreach (var c in fileName)
|
||||
{
|
||||
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LocalLyricsSearchInLyricsFiles
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="format">The format<see cref="LyricsFormat"/></param>
|
||||
/// <returns>The <see cref="Task{string?}"/></returns>
|
||||
private async Task<string?> LocalLyricsSearchInLyricsFiles(
|
||||
string title,
|
||||
string artist,
|
||||
LyricsFormat format
|
||||
)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*{format.ToFileExtension()}",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(
|
||||
file,
|
||||
FileHelper.GetEncoding(file)
|
||||
);
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LocalLyricsSearchInMusicFiles
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <returns>The <see cref="string?"/></returns>
|
||||
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*.*",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
//Track track = new(file);
|
||||
//var test1 = track.Lyrics.SynchronizedLyrics;
|
||||
//var test2 = track.Lyrics.UnsynchronizedLyrics;
|
||||
|
||||
try
|
||||
{
|
||||
var plain = TagLib.File.Create(file).Tag.Lyrics;
|
||||
if (plain != null && plain != string.Empty)
|
||||
{
|
||||
return plain;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ReadCache
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="format">The format<see cref="LyricsFormat"/></param>
|
||||
/// <returns>The <see cref="string?"/></returns>
|
||||
private string? ReadCache(
|
||||
string title,
|
||||
string artist,
|
||||
LyricsFormat format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SearchLrcLib
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="album">The album<see cref="string"/></param>
|
||||
/// <param name="duration">The duration<see cref="int"/></param>
|
||||
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
|
||||
/// <returns>The <see cref="Task{string?}"/></returns>
|
||||
private async Task<string?> SearchLrcLibAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int duration,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
{
|
||||
// Build API query URL
|
||||
var url =
|
||||
$"https://lrclib.net/api/search?"
|
||||
+ $"track_name={Uri.EscapeDataString(title)}&"
|
||||
+ $"artist_name={Uri.EscapeDataString(artist)}";
|
||||
|
||||
if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration)
|
||||
{
|
||||
url +=
|
||||
$"&album_name={Uri.EscapeDataString(album)}"
|
||||
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
|
||||
}
|
||||
|
||||
var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var jArr = JsonSerializer.Deserialize(
|
||||
json,
|
||||
Serialization.SourceGenerationContext.Default.JsonElement
|
||||
);
|
||||
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
|
||||
{
|
||||
var first = jArr[0];
|
||||
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
|
||||
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchQQAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int durationMs,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
{
|
||||
string? queryId = (
|
||||
(
|
||||
await new Lyricify.Lyrics.Searchers.QQMusicSearcher().SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? durationMs
|
||||
: null,
|
||||
Album =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? album
|
||||
: null,
|
||||
AlbumArtists = [artist],
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
)
|
||||
) as Lyricify.Lyrics.Searchers.QQMusicSearchResult
|
||||
)?.Id;
|
||||
if (queryId is string id)
|
||||
{
|
||||
return (await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(id))?.Lyrics;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchKugouAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int durationMs,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
{
|
||||
string? queryHash = (
|
||||
(
|
||||
await new Lyricify.Lyrics.Searchers.KugouSearcher().SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? durationMs
|
||||
: null,
|
||||
Album =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? album
|
||||
: null,
|
||||
AlbumArtists = [artist],
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
)
|
||||
) as Lyricify.Lyrics.Searchers.KugouSearchResult
|
||||
)?.Hash;
|
||||
if (queryHash != null)
|
||||
{
|
||||
var candidate = (
|
||||
await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(
|
||||
hash: queryHash
|
||||
)
|
||||
)?.Candidates.FirstOrDefault();
|
||||
if (candidate != null)
|
||||
{
|
||||
return await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(
|
||||
candidate.Id,
|
||||
candidate.AccessKey
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchNeteaseAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int durationMs,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
{
|
||||
string? queryId = (
|
||||
(
|
||||
await new Lyricify.Lyrics.Searchers.NeteaseSearcher().SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? durationMs
|
||||
: null,
|
||||
Album =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? album
|
||||
: null,
|
||||
AlbumArtists = [artist],
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
)
|
||||
) as Lyricify.Lyrics.Searchers.NeteaseSearchResult
|
||||
)?.Id;
|
||||
if (queryId != null)
|
||||
{
|
||||
return (await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(queryId))
|
||||
?.Lrc
|
||||
.Lyric;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地检索 amll-ttml-db 索引并下载歌词内容
|
||||
/// </summary>
|
||||
/// <param name="title">歌曲名</param>
|
||||
/// <param name="artist">歌手名</param>
|
||||
/// <returns>歌词内容字符串,找不到返回 null</returns>
|
||||
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
|
||||
{
|
||||
// 检索本地 JSONL 索引文件,查找 rawLyricFile
|
||||
if (!File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
{
|
||||
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
|
||||
if (!downloadOk || !File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
return null;
|
||||
}
|
||||
|
||||
string? rawLyricFile = null;
|
||||
foreach (var line in File.ReadLines(AppInfo.AmllTtmlDbIndexPath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(line);
|
||||
var root = doc.RootElement;
|
||||
if (!root.TryGetProperty("metadata", out var metadataArr))
|
||||
continue;
|
||||
string? musicName = null;
|
||||
string? artists = null;
|
||||
foreach (var meta in metadataArr.EnumerateArray())
|
||||
{
|
||||
if (meta.GetArrayLength() != 2)
|
||||
continue;
|
||||
var key = meta[0].GetString();
|
||||
var valueArr = meta[1];
|
||||
if (key == "musicName" && valueArr.GetArrayLength() > 0)
|
||||
musicName = valueArr[0].GetString();
|
||||
if (key == "artists" && valueArr.GetArrayLength() > 0)
|
||||
artists = valueArr[0].GetString();
|
||||
}
|
||||
if (musicName == null || artists == null)
|
||||
continue;
|
||||
|
||||
if (MusicMatch($"{artists} - {musicName}", title, artist))
|
||||
{
|
||||
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
|
||||
{
|
||||
rawLyricFile = rawLyricFileProp.GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawLyricFile))
|
||||
return null;
|
||||
|
||||
// 下载歌词内容
|
||||
var url =
|
||||
$"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载 amll-ttml-db 的 JSONL 索引文件到本地缓存目录
|
||||
/// </summary>
|
||||
/// <returns>下载成功返回 true,否则 false</returns>
|
||||
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
|
||||
{
|
||||
const string url =
|
||||
"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
|
||||
try
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(
|
||||
url,
|
||||
HttpCompletionOption.ResponseHeadersRead
|
||||
);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return false;
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||
await using var fs = new FileStream(
|
||||
AppInfo.AmllTtmlDbIndexPath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None
|
||||
);
|
||||
await stream.CopyToAsync(fs);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The WriteCache
|
||||
/// </summary>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="lyrics">The lyrics<see cref="string"/></param>
|
||||
/// <param name="format">The format<see cref="LyricsFormat"/></param>
|
||||
private void WriteCache(
|
||||
string title,
|
||||
string artist,
|
||||
string lyrics,
|
||||
LyricsFormat format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,309 +1,287 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.WinUI;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
using WindowsMediaController;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PlaybackService" />
|
||||
/// </summary>
|
||||
public partial class PlaybackService : IPlaybackService
|
||||
public partial class PlaybackService : BaseViewModel, IPlaybackService,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>>>
|
||||
{
|
||||
#region Fields
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILogger<PlaybackService> _logger;
|
||||
private readonly MediaManager _mediaManager = new();
|
||||
private readonly LatestOnlyTaskRunner _AlbumArtRefreshRunner = new();
|
||||
private readonly LatestOnlyTaskRunner _OnAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _dispatcherQueue
|
||||
/// </summary>
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private SongInfo? _cachedSongInfo;
|
||||
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
|
||||
private byte[]? _SMTCAlbumArtBytes = null;
|
||||
private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new AlbumArtChangedEventArgs();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _musicSearchService
|
||||
/// </summary>
|
||||
private readonly IMusicSearchService _musicSearchService;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _currentSession
|
||||
/// </summary>
|
||||
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _sessionManager
|
||||
/// </summary>
|
||||
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlaybackService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
|
||||
/// <param name="musicSearchService">The musicSearchService<see cref="IMusicSearchService"/></param>
|
||||
public PlaybackService(
|
||||
ISettingsService settingsService,
|
||||
IMusicSearchService musicSearchService
|
||||
)
|
||||
{
|
||||
_musicSearchService = musicSearchService;
|
||||
InitMediaManager().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsPlayingChanged
|
||||
/// </summary>
|
||||
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the PositionChanged
|
||||
/// </summary>
|
||||
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SongInfoChanged
|
||||
/// </summary>
|
||||
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether IsPlaying
|
||||
/// </summary>
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Position
|
||||
/// </summary>
|
||||
public TimeSpan Position { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SongInfo
|
||||
/// </summary>
|
||||
public SongInfo? SongInfo { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Note: this func is invoked by non-UI thread
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private async void CurrentSession_MediaPropertiesChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
MediaPropertiesChangedEventArgs? args
|
||||
)
|
||||
public PlaybackService(ISettingsService settingsService, IAlbumArtSearchService albumArtSearchService) : base(settingsService)
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
|
||||
if (sender == null)
|
||||
_albumArtSearchService = albumArtSearchService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<PlaybackService>>();
|
||||
|
||||
_mediaSourceProvidersInfo = _settingsService.MediaSourceProvidersInfo;
|
||||
InitMediaManager();
|
||||
}
|
||||
|
||||
private bool IsMediaSourceEnabled(string id)
|
||||
{
|
||||
return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
|
||||
}
|
||||
|
||||
private void InitMediaManager()
|
||||
{
|
||||
_mediaManager.Start();
|
||||
|
||||
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
|
||||
_mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;
|
||||
_mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged;
|
||||
_mediaManager.OnAnyMediaPropertyChanged += MediaManager_OnAnyMediaPropertyChanged;
|
||||
_mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged;
|
||||
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
|
||||
|
||||
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
|
||||
}
|
||||
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (mediaSession == null || !IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId))
|
||||
{
|
||||
SongInfo = null;
|
||||
SendNullMessages();
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
_dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
mediaProps = await sender.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
if (mediaProps == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo = new SongInfo
|
||||
try
|
||||
{
|
||||
Title = mediaProps.Title,
|
||||
Artist = mediaProps.Artist,
|
||||
Album = mediaProps?.AlbumTitle ?? string.Empty,
|
||||
DurationMs = _currentSession
|
||||
?.GetTimelineProperties()
|
||||
.EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
|
||||
};
|
||||
|
||||
if (
|
||||
SongInfo.SourceAppUserModelId?.Contains(Package.Current.Id.FamilyName)
|
||||
?? false
|
||||
)
|
||||
{
|
||||
SongInfo.Title = "甜度爆表";
|
||||
SongInfo.Artist = "AI";
|
||||
var props = await mediaSession.ControlSession.TryGetMediaPropertiesAsync();
|
||||
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
|
||||
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
|
||||
}
|
||||
|
||||
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
catch (Exception ex)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
_logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed");
|
||||
SendNullMessages();
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist
|
||||
);
|
||||
|
||||
if (SongInfo.AlbumArt == null)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync(
|
||||
$"{SongInfo.Artist} - {SongInfo.Title}",
|
||||
400,
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
|
||||
{
|
||||
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(timelineProperties.Position));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Non-UI thread
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private void CurrentSession_PlaybackInfoChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
PlaybackInfoChangedEventArgs? args
|
||||
)
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
IsPlaying = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var playbackState = sender.GetPlaybackInfo().PlaybackStatus;
|
||||
// _logger.LogDebug(playbackState.ToString());
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
switch (playbackState)
|
||||
{
|
||||
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed:
|
||||
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Opened:
|
||||
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Changing:
|
||||
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Stopped:
|
||||
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Paused:
|
||||
IsPlaying = false;
|
||||
break;
|
||||
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing:
|
||||
IsPlaying = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
}));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CurrentSession_TimelinePropertiesChanged
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSession?"/></param>
|
||||
/// <param name="args">The args<see cref="TimelinePropertiesChangedEventArgs?"/></param>
|
||||
private void CurrentSession_TimelinePropertiesChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
TimelinePropertiesChangedEventArgs? args
|
||||
)
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
{
|
||||
if (sender == null)
|
||||
_ = _OnAnyMediaPropertyChangedRunner.RunAsync(async token =>
|
||||
{
|
||||
Position = TimeSpan.Zero;
|
||||
}
|
||||
else
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
string id = mediaSession.ControlSession.SourceAppUserModelId;
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
|
||||
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (_mediaManager.CurrentMediaSessions.Count == 0)
|
||||
{
|
||||
Position = sender.GetTimelineProperties().Position;
|
||||
SendNullMessages();
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
}
|
||||
|
||||
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
var id = mediaSession?.ControlSession?.SourceAppUserModelId;
|
||||
if (string.IsNullOrEmpty(id)) return;
|
||||
|
||||
var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
|
||||
if (found == null)
|
||||
{
|
||||
_mediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, true));
|
||||
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
|
||||
}
|
||||
MediaSourceProvidersInfoChanged?.Invoke(this, new MediaSourceProvidersInfoEventArgs(_mediaSourceProvidersInfo));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SendNullMessages()
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
_cachedSongInfo = null;
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false));
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task UpdateAlbumArtRelated(CancellationToken token)
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
{
|
||||
_logger.LogWarning("Cached song info is null, cannot update album art.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? bytes = await _albumArtSearchService.SearchAsync(
|
||||
_cachedSongInfo.Title,
|
||||
_cachedSongInfo.Artist,
|
||||
_cachedSongInfo?.Album ?? string.Empty,
|
||||
_SMTCAlbumArtBytes
|
||||
);
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
/// <summary>
|
||||
/// The InitMediaManager
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Task"/></returns>
|
||||
private async Task InitMediaManager()
|
||||
{
|
||||
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||
_sessionManager.CurrentSessionChanged += SessionManager_CurrentSessionChanged;
|
||||
|
||||
SessionManager_CurrentSessionChanged(_sessionManager, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SessionManager_CurrentSessionChanged
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSessionManager"/></param>
|
||||
/// <param name="args">The args<see cref="CurrentSessionChangedEventArgs?"/></param>
|
||||
private void SessionManager_CurrentSessionChanged(
|
||||
GlobalSystemMediaTransportControlsSessionManager sender,
|
||||
CurrentSessionChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
// _logger.LogDebug("SessionManager_CurrentSessionChanged");
|
||||
// Unregister events associated with the previous session
|
||||
if (_currentSession != null)
|
||||
if (bytes == null)
|
||||
{
|
||||
_currentSession.MediaPropertiesChanged -= CurrentSession_MediaPropertiesChanged;
|
||||
_currentSession.PlaybackInfoChanged -= CurrentSession_PlaybackInfoChanged;
|
||||
_currentSession.TimelinePropertiesChanged -=
|
||||
CurrentSession_TimelinePropertiesChanged;
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{_cachedSongInfo!.Artist} - {_cachedSongInfo.Title}", 400, 400);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
// Record and register events for current session
|
||||
_currentSession = sender.GetCurrentSession();
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (_currentSession != null)
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
_currentSession.MediaPropertiesChanged += CurrentSession_MediaPropertiesChanged;
|
||||
_currentSession.PlaybackInfoChanged += CurrentSession_PlaybackInfoChanged;
|
||||
_currentSession.TimelinePropertiesChanged +=
|
||||
CurrentSession_TimelinePropertiesChanged;
|
||||
}
|
||||
|
||||
CurrentSession_MediaPropertiesChanged(_currentSession, null);
|
||||
CurrentSession_PlaybackInfoChanged(_currentSession, null);
|
||||
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
|
||||
AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.MediaSourceProvidersInfo))
|
||||
{
|
||||
_mediaSourceProvidersInfo = [.. message.NewValue];
|
||||
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
|
||||
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.AlbumArtSearchProvidersInfo))
|
||||
{
|
||||
// Album art search providers info changed, re-fetch album art
|
||||
_logger.LogInformation("Album art search providers info changed, refreshing album art.");
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,157 +1,90 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Storage;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SettingsService" />
|
||||
/// </summary>
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
#region Constants
|
||||
public const string LyricsCustomBgFontColorKey = "LyricsCustomBgFontColor";
|
||||
public const string LyricsCustomFgFontColorKey = "LyricsCustomFgFontColor";
|
||||
public const string LyricsCustomStrokeFontColorKey = "LyricsCustomStrokeFontColor";
|
||||
|
||||
// App behavior
|
||||
|
||||
/// <summary>
|
||||
/// Defines the AutoStartWindowTypeKey
|
||||
/// </summary>
|
||||
private const string AutoStartWindowTypeKey = "AutoStartWindowType";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the BackdropTypeKey
|
||||
/// </summary>
|
||||
private const string BackdropTypeKey = "BackdropType";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the CoverImageRadiusKey
|
||||
/// </summary>
|
||||
private const string CoverImageRadiusKey = "CoverImageRadius";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the CoverOverlayBlurAmountKey
|
||||
/// </summary>
|
||||
private const string CoverImageRadiusKey = "AlbumArtCornerRadius";
|
||||
private const string CoverOverlayBlurAmountKey = "CoverOverlayBlurAmount";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the CoverOverlayOpacityKey
|
||||
/// </summary>
|
||||
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
|
||||
|
||||
// Album art
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsCoverOverlayEnabledKey
|
||||
/// </summary>
|
||||
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsDynamicCoverOverlayEnabledKey
|
||||
/// </summary>
|
||||
private const string DesktopWindowLeftKey = "DesktopWindowLeft";
|
||||
private const string DesktopWindowTopKey = "DesktopWindowTop";
|
||||
private const string DesktopWindowWidthKey = "DesktopWindowWidth";
|
||||
private const string DesktopWindowHeightKey = "DesktopWindowHeight";
|
||||
|
||||
private const string StandardWindowLeftKey = "StandardWindowLeft";
|
||||
private const string StandardWindowTopKey = "StandardWindowTop";
|
||||
private const string StandardWindowWidthKey = "StandardWindowWidth";
|
||||
private const string StandardWindowHeightKey = "StandardWindowHeight";
|
||||
|
||||
private const string AutoLockOnDesktopModeKey = "AutoLockOnDesktopMode";
|
||||
|
||||
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsFirstRunKey
|
||||
/// </summary>
|
||||
private const string IsFanLyricsEnabledKey = "IsFanLyricsEnabled";
|
||||
private const string IsFirstRunKey = "IsFirstRun";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsLyricsGlowEffectEnabledKey
|
||||
/// </summary>
|
||||
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LanguageKey
|
||||
/// </summary>
|
||||
private const string LanguageKey = "Language";
|
||||
|
||||
// Lyrics lib
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalLyricsFoldersKey
|
||||
/// </summary>
|
||||
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsAlignmentTypeKey
|
||||
/// </summary>
|
||||
private const string LyricsAlignmentTypeKey = "LyricsAlignmentType";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsBlurAmountKey
|
||||
/// </summary>
|
||||
private const string LyricsAlignmentTypeKey = "TextAlignmentType";
|
||||
private const string SongInfoAlignmentTypeKey = "SongInfoAlignmentType";
|
||||
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFontColorTypeKey
|
||||
/// </summary>
|
||||
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
|
||||
private const string LyricsBgFontColorTypeKey = "_lyricsBgFontColorType";
|
||||
private const string LyricsFgFontColorTypeKey = "LyricsFgFontColorType";
|
||||
private const string LyricsStrokeFontColorTypeKey = "LyricsStrokeFontColorType";
|
||||
|
||||
private const string LyricsFontStrokeWidthKey = "LyricsFontStrokeWidth";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFontSizeKey
|
||||
/// </summary>
|
||||
private const string LyricsFontSizeKey = "LyricsFontSize";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFontWeightKey
|
||||
/// </summary>
|
||||
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsGlowEffectScopeKey
|
||||
/// </summary>
|
||||
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsLineSpacingFactorKey
|
||||
/// </summary>
|
||||
private const string LyricsHighlightSopeKey = "LyricsHighlightSope";
|
||||
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsSearchProvidersInfoKey
|
||||
/// </summary>
|
||||
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsVerticalEdgeOpacityKey
|
||||
/// </summary>
|
||||
private const string AlbumArtSearchProvidersInfoKey = "AlbumArtSearchProvidersInfo";
|
||||
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
|
||||
|
||||
// App appearance
|
||||
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ThemeTypeKey
|
||||
/// </summary>
|
||||
private const string ThemeTypeKey = "ThemeType";
|
||||
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
|
||||
private const string LibreTranslateServerKey = "LibreTranslateServer";
|
||||
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the TitleBarTypeKey
|
||||
/// </summary>
|
||||
private const string TitleBarTypeKey = "TitleBarType";
|
||||
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
|
||||
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
|
||||
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
|
||||
|
||||
#endregion
|
||||
private const string LyricsScrollEasingTypeKey = "LyricsScrollEasingType";
|
||||
private const string LyricsScrollDurationKey = "LyricsScrollDuration";
|
||||
|
||||
#region Fields
|
||||
public const string TimelineSyncThresholdKey = "TimelineSyncThreshold";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _localSettings
|
||||
/// </summary>
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SettingsService"/> class.
|
||||
/// </summary>
|
||||
public SettingsService()
|
||||
{
|
||||
_localSettings = ApplicationData.Current.LocalSettings;
|
||||
@@ -180,128 +113,222 @@ namespace BetterLyrics.WinUI3.Services
|
||||
))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
SetDefault(
|
||||
AlbumArtSearchProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
Enum.GetValues<AlbumArtSearchProvider>()
|
||||
.Select(p => new AlbumArtSearchProviderInfo(p, true))
|
||||
.ToList(),
|
||||
SourceGenerationContext.Default.ListAlbumArtSearchProviderInfo
|
||||
)
|
||||
);
|
||||
if (AlbumArtSearchProvidersInfo.Count != Enum.GetValues<AlbumArtSearchProvider>().Length)
|
||||
{
|
||||
AlbumArtSearchProvidersInfo = Enum.GetValues<AlbumArtSearchProvider>()
|
||||
.Select(p => new AlbumArtSearchProviderInfo(
|
||||
p,
|
||||
AlbumArtSearchProvidersInfo
|
||||
.Where(x => x.Provider == p)
|
||||
.FirstOrDefault()
|
||||
?.IsEnabled ?? true
|
||||
))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
SetDefault(MediaSourceProvidersInfoKey, "[]");
|
||||
|
||||
// App appearance
|
||||
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
|
||||
SetDefault(LanguageKey, (int)Language.FollowSystem);
|
||||
SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic);
|
||||
|
||||
SetDefault(DesktopWindowHeightKey, 600);
|
||||
SetDefault(DesktopWindowLeftKey, 200);
|
||||
SetDefault(DesktopWindowTopKey, 200);
|
||||
SetDefault(DesktopWindowWidthKey, 1200);
|
||||
|
||||
SetDefault(StandardWindowHeightKey, 800);
|
||||
SetDefault(StandardWindowLeftKey, 200);
|
||||
SetDefault(StandardWindowTopKey, 200);
|
||||
SetDefault(StandardWindowWidthKey, 1600);
|
||||
|
||||
SetDefault(AutoLockOnDesktopModeKey, false);
|
||||
// App behavior
|
||||
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
|
||||
// Album art
|
||||
SetDefault(IsCoverOverlayEnabledKey, true);
|
||||
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
|
||||
SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 200);
|
||||
SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact);
|
||||
SetDefault(CoverImageRadiusKey, 24); // 24 %
|
||||
SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 100);
|
||||
SetDefault(CoverImageRadiusKey, 12); // 12 %
|
||||
// Lyrics
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)LyricsAlignmentType.Center);
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
|
||||
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
|
||||
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
|
||||
SetDefault(LyricsBlurAmountKey, 5);
|
||||
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.Default);
|
||||
|
||||
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Default);
|
||||
|
||||
SetDefault(LyricsBgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
SetDefault(LyricsFgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
SetDefault(LyricsStrokeFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
|
||||
SetDefault(LyricsCustomBgFontColorKey, Colors.White.ToInt());
|
||||
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
|
||||
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
|
||||
|
||||
SetDefault(LyricsFontSizeKey, 28);
|
||||
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
|
||||
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
|
||||
SetDefault(IsLyricsGlowEffectEnabledKey, true);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.UntilCurrentChar);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentChar);
|
||||
SetDefault(LyricsHighlightSopeKey, (int)LineRenderingType.LineStartToCurrentChar);
|
||||
SetDefault(IsFanLyricsEnabledKey, false);
|
||||
|
||||
SetDefault(LibreTranslateServerKey, "");
|
||||
SetDefault(IsTranslationEnabledKey, false);
|
||||
SetDefault(SelectedTargetLanguageIndexKey, 6);
|
||||
|
||||
SetDefault(LyricsFontStrokeWidthKey, 3);
|
||||
SetDefault(IgnoreFullscreenWindowKey, false);
|
||||
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
|
||||
|
||||
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
|
||||
SetDefault(LyricsScrollDurationKey, 500); // 500ms
|
||||
SetDefault(TimelineSyncThresholdKey, 0); // 0ms
|
||||
}
|
||||
|
||||
#endregion
|
||||
public EasingType LyricsScrollEasingType
|
||||
{
|
||||
get => (EasingType)GetValue<int>(LyricsScrollEasingTypeKey);
|
||||
set => SetValue(LyricsScrollEasingTypeKey, (int)value);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
public int LyricsScrollDuration
|
||||
{
|
||||
get => GetValue<int>(LyricsScrollDurationKey);
|
||||
set => SetValue(LyricsScrollDurationKey, value);
|
||||
}
|
||||
|
||||
public LyricsDisplayType PreferredDisplayType
|
||||
{
|
||||
get => (LyricsDisplayType)GetValue<int>(PreferredDisplayTypeKey);
|
||||
set => SetValue(PreferredDisplayTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public ElementTheme LyricsBackgroundTheme
|
||||
{
|
||||
get => (ElementTheme)GetValue<int>(LyricsBackgroundThemeKey);
|
||||
set => SetValue(LyricsBackgroundThemeKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoStartWindowType
|
||||
/// </summary>
|
||||
public AutoStartWindowType AutoStartWindowType
|
||||
{
|
||||
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
|
||||
set => SetValue(AutoStartWindowTypeKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the BackdropType
|
||||
/// </summary>
|
||||
public BackdropType BackdropType
|
||||
public int DesktopWindowLeft
|
||||
{
|
||||
get => (BackdropType)GetValue<int>(BackdropTypeKey);
|
||||
set => SetValue(BackdropTypeKey, (int)value);
|
||||
get => GetValue<int>(DesktopWindowLeftKey);
|
||||
set => SetValue(DesktopWindowLeftKey, value);
|
||||
}
|
||||
|
||||
public int DesktopWindowTop
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowTopKey);
|
||||
set => SetValue(DesktopWindowTopKey, value);
|
||||
}
|
||||
|
||||
public int DesktopWindowWidth
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowWidthKey);
|
||||
set => SetValue(DesktopWindowWidthKey, value);
|
||||
}
|
||||
|
||||
public int DesktopWindowHeight
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowHeightKey);
|
||||
set => SetValue(DesktopWindowHeightKey, value);
|
||||
}
|
||||
|
||||
public int StandardWindowLeft
|
||||
{
|
||||
get => GetValue<int>(StandardWindowLeftKey);
|
||||
set => SetValue(StandardWindowLeftKey, value);
|
||||
}
|
||||
|
||||
public int StandardWindowTop
|
||||
{
|
||||
get => GetValue<int>(StandardWindowTopKey);
|
||||
set => SetValue(StandardWindowTopKey, value);
|
||||
}
|
||||
|
||||
public int StandardWindowWidth
|
||||
{
|
||||
get => GetValue<int>(StandardWindowWidthKey);
|
||||
set => SetValue(StandardWindowWidthKey, value);
|
||||
}
|
||||
|
||||
public int StandardWindowHeight
|
||||
{
|
||||
get => GetValue<int>(StandardWindowHeightKey);
|
||||
set => SetValue(StandardWindowHeightKey, value);
|
||||
}
|
||||
|
||||
public bool AutoLockOnDesktopMode
|
||||
{
|
||||
get => GetValue<bool>(AutoLockOnDesktopModeKey);
|
||||
set => SetValue(AutoLockOnDesktopModeKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CoverImageRadius
|
||||
/// </summary>
|
||||
public int CoverImageRadius
|
||||
{
|
||||
get => GetValue<int>(CoverImageRadiusKey);
|
||||
set => SetValue(CoverImageRadiusKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CoverOverlayBlurAmount
|
||||
/// </summary>
|
||||
public int CoverOverlayBlurAmount
|
||||
{
|
||||
get => GetValue<int>(CoverOverlayBlurAmountKey);
|
||||
set => SetValue(CoverOverlayBlurAmountKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CoverOverlayOpacity
|
||||
/// </summary>
|
||||
public int CoverOverlayOpacity
|
||||
{
|
||||
get => GetValue<int>(CoverOverlayOpacityKey);
|
||||
set => SetValue(CoverOverlayOpacityKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
|
||||
/// </summary>
|
||||
public bool IsCoverOverlayEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsCoverOverlayEnabledKey);
|
||||
set => SetValue(IsCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
|
||||
/// </summary>
|
||||
public bool IsDynamicCoverOverlayEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
|
||||
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsFirstRun
|
||||
/// </summary>
|
||||
public bool IsFanLyricsEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsFanLyricsEnabledKey);
|
||||
set => SetValue(IsFanLyricsEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool IsFirstRun
|
||||
{
|
||||
get => GetValue<bool>(IsFirstRunKey);
|
||||
set => SetValue(IsFirstRunKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
|
||||
/// </summary>
|
||||
public bool IsLyricsGlowEffectEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsLyricsGlowEffectEnabledKey);
|
||||
set => SetValue(IsLyricsGlowEffectEnabledKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Language
|
||||
/// </summary>
|
||||
public Language Language
|
||||
{
|
||||
get => (Language)GetValue<int>(LanguageKey);
|
||||
set => SetValue(LanguageKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LocalLyricsFolders
|
||||
/// </summary>
|
||||
public List<LocalLyricsFolder> LocalLyricsFolders
|
||||
{
|
||||
get =>
|
||||
@@ -319,72 +346,96 @@ namespace BetterLyrics.WinUI3.Services
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsAlignmentType
|
||||
/// </summary>
|
||||
public LyricsAlignmentType LyricsAlignmentType
|
||||
public TextAlignmentType LyricsAlignmentType
|
||||
{
|
||||
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
|
||||
get => (TextAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
|
||||
set => SetValue(LyricsAlignmentTypeKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsBlurAmount
|
||||
/// </summary>
|
||||
public TextAlignmentType SongInfoAlignmentType
|
||||
{
|
||||
get => (TextAlignmentType)GetValue<int>(SongInfoAlignmentTypeKey);
|
||||
set => SetValue(SongInfoAlignmentTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public int LyricsBlurAmount
|
||||
{
|
||||
get => GetValue<int>(LyricsBlurAmountKey);
|
||||
set => SetValue(LyricsBlurAmountKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontColorType
|
||||
/// </summary>
|
||||
public LyricsFontColorType LyricsFontColorType
|
||||
public Color LyricsCustomBgFontColor
|
||||
{
|
||||
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
|
||||
set => SetValue(LyricsFontColorTypeKey, (int)value);
|
||||
get => GetValue<int>(LyricsCustomBgFontColorKey)!.ToColor();
|
||||
set => SetValue(LyricsCustomBgFontColorKey, value.ToInt());
|
||||
}
|
||||
|
||||
public Color LyricsCustomFgFontColor
|
||||
{
|
||||
get => GetValue<int>(LyricsCustomFgFontColorKey)!.ToColor();
|
||||
set => SetValue(LyricsCustomFgFontColorKey, value.ToInt());
|
||||
}
|
||||
|
||||
public Color LyricsCustomStrokeFontColor
|
||||
{
|
||||
get => GetValue<int>(LyricsCustomStrokeFontColorKey)!.ToColor();
|
||||
set => SetValue(LyricsCustomStrokeFontColorKey, value.ToInt());
|
||||
}
|
||||
|
||||
public LyricsFontColorType LyricsBgFontColorType
|
||||
{
|
||||
get => (LyricsFontColorType)GetValue<int>(LyricsBgFontColorTypeKey);
|
||||
set => SetValue(LyricsBgFontColorTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public LyricsFontColorType LyricsFgFontColorType
|
||||
{
|
||||
get => (LyricsFontColorType)GetValue<int>(LyricsFgFontColorTypeKey);
|
||||
set => SetValue(LyricsFgFontColorTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public LyricsFontColorType LyricsStrokeFontColorType
|
||||
{
|
||||
get => (LyricsFontColorType)GetValue<int>(LyricsStrokeFontColorTypeKey);
|
||||
set => SetValue(LyricsStrokeFontColorTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public int LyricsFontStrokeWidth
|
||||
{
|
||||
get => GetValue<int>(LyricsFontStrokeWidthKey);
|
||||
set => SetValue(LyricsFontStrokeWidthKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontSize
|
||||
/// </summary>
|
||||
public int LyricsFontSize
|
||||
{
|
||||
get => GetValue<int>(LyricsFontSizeKey);
|
||||
set => SetValue(LyricsFontSizeKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontWeight
|
||||
/// </summary>
|
||||
public LyricsFontWeight LyricsFontWeight
|
||||
{
|
||||
get => (LyricsFontWeight)GetValue<int>(LyricsFontWeightKey);
|
||||
set => SetValue(LyricsFontWeightKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsGlowEffectScope
|
||||
/// </summary>
|
||||
public LineRenderingType LyricsGlowEffectScope
|
||||
{
|
||||
get => (LineRenderingType)GetValue<int>(LyricsGlowEffectScopeKey);
|
||||
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsLineSpacingFactor
|
||||
/// </summary>
|
||||
public LineRenderingType LyricsHighlightScope
|
||||
{
|
||||
get => (LineRenderingType)GetValue<int>(LyricsHighlightSopeKey);
|
||||
set => SetValue(LyricsHighlightSopeKey, (int)value);
|
||||
}
|
||||
|
||||
public float LyricsLineSpacingFactor
|
||||
{
|
||||
get => GetValue<float>(LyricsLineSpacingFactorKey);
|
||||
set => SetValue(LyricsLineSpacingFactorKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsSearchProvidersInfo
|
||||
/// </summary>
|
||||
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
|
||||
{
|
||||
get =>
|
||||
@@ -402,43 +453,76 @@ namespace BetterLyrics.WinUI3.Services
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsVerticalEdgeOpacity
|
||||
/// </summary>
|
||||
public List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo
|
||||
{
|
||||
get =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(AlbumArtSearchProvidersInfoKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListAlbumArtSearchProviderInfo
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
AlbumArtSearchProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListAlbumArtSearchProviderInfo
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public List<MediaSourceProviderInfo> MediaSourceProvidersInfo
|
||||
{
|
||||
get =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(MediaSourceProvidersInfoKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListMediaSourceProviderInfo
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
MediaSourceProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListMediaSourceProviderInfo
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public int LyricsVerticalEdgeOpacity
|
||||
{
|
||||
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
|
||||
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ThemeType
|
||||
/// </summary>
|
||||
public ElementTheme ThemeType
|
||||
public string LibreTranslateServer
|
||||
{
|
||||
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
|
||||
set => SetValue(ThemeTypeKey, (int)value);
|
||||
get => GetValue<string>(LibreTranslateServerKey)!;
|
||||
set => SetValue(LibreTranslateServerKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TitleBarType
|
||||
/// </summary>
|
||||
public TitleBarType TitleBarType
|
||||
public bool IsTranslationEnabled
|
||||
{
|
||||
get => (TitleBarType)GetValue<int>(TitleBarTypeKey);
|
||||
set => SetValue(TitleBarTypeKey, (int)value);
|
||||
get => GetValue<bool>(IsTranslationEnabledKey);
|
||||
set => SetValue(IsTranslationEnabledKey, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
public int SelectedTargetLanguageIndex
|
||||
{
|
||||
get => GetValue<int>(SelectedTargetLanguageIndexKey);
|
||||
set => SetValue(SelectedTargetLanguageIndexKey, value);
|
||||
}
|
||||
|
||||
#region Methods
|
||||
public bool IgnoreFullscreenWindow
|
||||
{
|
||||
get => GetValue<bool>(IgnoreFullscreenWindowKey);
|
||||
set => SetValue(IgnoreFullscreenWindowKey, value);
|
||||
}
|
||||
|
||||
public int TimelineSyncThreshold
|
||||
{
|
||||
get => GetValue<int>(TimelineSyncThresholdKey);
|
||||
set => SetValue(TimelineSyncThresholdKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetValue
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key">The key<see cref="string"/></param>
|
||||
/// <returns>The <see cref="T?"/></returns>
|
||||
private T? GetValue<T>(string key)
|
||||
{
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value))
|
||||
@@ -448,12 +532,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SetDefault
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key">The key<see cref="string"/></param>
|
||||
/// <param name="value">The value<see cref="T"/></param>
|
||||
private void SetDefault<T>(string key, T value)
|
||||
{
|
||||
if (_localSettings.Values.ContainsKey(key) && _localSettings.Values[key] is T)
|
||||
@@ -461,17 +539,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_localSettings.Values[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SetValue
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key">The key<see cref="string"/></param>
|
||||
/// <param name="value">The value<see cref="T"/></param>
|
||||
private void SetValue<T>(string key, T value)
|
||||
{
|
||||
_localSettings.Values[key] = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class TranslateService : BaseViewModel, ITranslateService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TranslateService(ISettingsService settingsService) :base(settingsService)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text and target language must be provided.");
|
||||
}
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(text);
|
||||
if (string.IsNullOrWhiteSpace(originalLangCode) || originalLangCode == targetLangCode)
|
||||
{
|
||||
return text; // No translation needed
|
||||
}
|
||||
else if (originalLangCode == "zh-Hant" && targetLangCode == "zh-Hans")
|
||||
{
|
||||
return ChineseConverter.ConvertToSimplifiedChinese(text);
|
||||
}
|
||||
else if (originalLangCode == "zh-Hans" && targetLangCode == "zh-Hant")
|
||||
{
|
||||
return ChineseConverter.ConvertToTraditionalChinese(text);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
|
||||
throw new InvalidOperationException("LibreTranslate server URL is not configured.");
|
||||
}
|
||||
|
||||
var url = $"{_settingsService.LibreTranslateServer}/translate";
|
||||
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
|
||||
[
|
||||
new("q", text),
|
||||
new("source", originalLangCode),
|
||||
new("target", targetLangCode),
|
||||
]));
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
|
||||
return result?.TranslatedText ?? string.Empty;
|
||||
}
|
||||
|
||||
public int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr)
|
||||
{
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
if (lyricsDataArr.Count > 1)
|
||||
{
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
{
|
||||
if (lyricsDataArr[i].LanguageCode == targetLangCode)
|
||||
{
|
||||
return i; // Translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // No translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>Local music libraries</value>
|
||||
<value>Local media library</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>Add folders storing music or lyrics</value>
|
||||
@@ -139,13 +139,16 @@
|
||||
<value>Add a folder</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
<value>Lyrics background theme</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>Follow system</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>Adaptive to lyrics background (Colored)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>Adaptive to lyrics background (Colored)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
@@ -196,7 +199,7 @@
|
||||
<value>Transparent</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>Backdrop</value>
|
||||
<value>Lyrics backdrop</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
@@ -210,14 +213,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>The folder has been added. Please do not add it again.</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>Overlay album art background</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>Lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>Dynamic album art background</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>Dynamic lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>Album art background opacity</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>Lyrics background opacity</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>Settings - BetterLyrics</value>
|
||||
@@ -237,8 +240,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>Album art background blur amount</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>Lyrics background blur amount</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>Blur amount</value>
|
||||
@@ -252,9 +255,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Significantly higher GPU usage when blur is enabled (> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>Enabling this feature will slightly increase GPU utilization</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>Top and bottom edge opacity</value>
|
||||
</data>
|
||||
@@ -273,8 +273,8 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Immersive mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>Album background</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>Lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>About</value>
|
||||
@@ -282,7 +282,7 @@
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>Lyrics library</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>App appearance</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>Glow effect scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure lyrics search providers</value>
|
||||
<value>Configure lyrics source</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Drag to sort, the lyrics search order will be in the following order</value>
|
||||
@@ -304,19 +304,19 @@
|
||||
<value>Welcome to BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Let's setup lyrics database now</value>
|
||||
<value>Hover the mouse over the top or bottom area of the app to display more function options</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>No music playing now</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>Developer options</value>
|
||||
<value>Advanced options</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>Play test music</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>Play using system player</value>
|
||||
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>Cache</value>
|
||||
@@ -324,17 +324,20 @@
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>Including log files, network lyrics cache</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>Font color</value>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>Font color (Non-current playback area)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>Font color (Current playback area)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>Album art accent color</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>Adaptive to lyrics background (Grayed)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>Adaptive to lyrics background (Grayed)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>Album art style</value>
|
||||
<value>Album art area style</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>Corner radius</value>
|
||||
@@ -384,19 +387,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Exit picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>Lyrics not found</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>Lyrics effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>Lyrics style</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>This folder is already included in the existing folder and does not need to be added again</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>App behavior</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -406,10 +409,10 @@
|
||||
<value>Activate standard mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>Activate dock mode</value>
|
||||
<value>Activate desktop mode</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>Lyrics not found</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>Activate dock mode</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>System tray - BetterLyrics</value>
|
||||
@@ -456,19 +459,16 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>Extra Black</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>Whole lyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>Current line</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>Current char</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>Loading lyrics...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -531,4 +531,208 @@
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>To unlock after locking, go to the system tray to unlock</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>Fan lyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>Custom</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
|
||||
<value>Custom</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>Lyrics style and effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>App appearance and behavior</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>Auto-lock when activating desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
|
||||
<value>Alignment</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
|
||||
<value>Center</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
|
||||
<value>Album art</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
|
||||
<value>Song title & artist</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>Easing animation type</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>Playback sources</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Playback sources</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Enable or disable lyrics display for a specified media source</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>Log record</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslation.Content" xml:space="preserve">
|
||||
<value>Lyrics translation</value>
|
||||
</data>
|
||||
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
|
||||
<value>Lyrics timeline offset (ms)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>Configure translation services</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>Server address</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>Test server</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
|
||||
<value>Target language</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
|
||||
<value>Translation service powered by LibreTranslate</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>Visit https://github.com/LibreTranslate/LibreTranslate for installation instructions and more information (this software is not affiliated with this translation service in any way)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<value>Server test successful</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<value>Server test failed</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
<value>Lyrics stroke width (Desktop mode only)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>Follow system</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>Automatic startup</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>Lyrics stroke color (Desktop mode only)</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
|
||||
<value>Always stay on top of fullscreen applications</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
|
||||
<value>Force this app to appear on top of full-screen apps when docked or desktop mode is enabled</value>
|
||||
</data>
|
||||
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
|
||||
<value>More</value>
|
||||
</data>
|
||||
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Lyrics timeline offset</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Translate</value>
|
||||
</data>
|
||||
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Display type</value>
|
||||
</data>
|
||||
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>Translate server is not set, please configure it in settings first</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>Will automatically reset to 0 when switching songs</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>The translation in the lyrics will be read first. If there is no match, the machine translation will be requested from the LibreTranslate server</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure album cover source</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Drag to sort, the album art search order will be in the following order</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>Album art source</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>Local music files</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>Music player</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>Media library</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation type</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation duration</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>Linear</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>Smooth step</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>Ease-in-out sine</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>Ease-in-out quad</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>Ease-in-out elastic</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>Ease-in-out back</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>Ease-in-out bounce</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>Ease-in-out circ</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>Ease-in-out expo</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>Ease-in-out quint</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>Ease-in-out quart</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>Ease-in-out cubic</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>Current line start to current char</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>Highlight scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>Lyrics timeline sync threshold</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>If the lyrics progress is jittery, try increasing this threshold; changing this value can cause lyrics synchronization to deviate</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQ feedback & chat group</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>Join now</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>地元の音楽図書館</value>
|
||||
<value>地元のメディア図書館</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>音楽や歌詞を保存するフォルダーを追加します</value>
|
||||
@@ -139,13 +139,16 @@
|
||||
<value>フォルダーを追加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>テーマ</value>
|
||||
<value>歌詞の背景テーマ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>言語</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>システムをフォローします</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>歌詞の背景に適応する(色付き)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>歌詞の背景に適応する(色付き)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>ライト</value>
|
||||
@@ -196,7 +199,7 @@
|
||||
<value>透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>背景</value>
|
||||
<value>歌詞の背景素材</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
@@ -210,14 +213,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>フォルダーが追加されました。二度と追加しないでください。</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>オーバーレイアルバムアートの背景</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>ダイナミックアルバムアートの背景</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>ダイナミックな歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>アルバムアートの背景不透明</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>歌詞の背景不透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>設定 - BetterLyrics</value>
|
||||
@@ -237,8 +240,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>右</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>アルバムアートバックグラウンドブラー量</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>歌詞の背景ぼやけ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>ぼやけの量</value>
|
||||
@@ -252,9 +255,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>この機能を有効にすると、GPUの使用率がわずかに増加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>上端と下端の不透明度</value>
|
||||
</data>
|
||||
@@ -273,16 +273,16 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>没入モード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>アルバムの背景</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>について</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞ライブラリ</value>
|
||||
<value>歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>アプリの外観</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>グローエフェクトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>歌詞検索プロバイダーを構成します</value>
|
||||
<value>歌詞ソースを構成します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>ドラッグしてソートすると、歌詞の検索注文は次の順序で行われます</value>
|
||||
@@ -304,19 +304,19 @@
|
||||
<value>BetterLyrics へようこそ</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>今すぐ歌詞データベースをセットアップしましょう</value>
|
||||
<value>マウスをアプリの上または下部の領域にホバリングして、より多くの機能オプションを表示します</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>今は音楽が再生されていません</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>開発者オプション</value>
|
||||
<value>高度なオプション</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>テスト音楽を再生します</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>システムプレーヤーを使用して再生します</value>
|
||||
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>キャッシュ</value>
|
||||
@@ -324,17 +324,20 @@
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>ログファイル、ネットワーク歌詞キャッシュを含む</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>フォントカラー</value>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>フォントカラー(非電流再生エリア)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>フォントカラー(現在の再生エリア)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>アルバムアートアクセントカラー</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>歌詞の背景に適応する(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>歌詞の背景に適応する(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>アルバムアートスタイル</value>
|
||||
<value>アルバムエリアスタイル</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>コーナー半径</value>
|
||||
@@ -384,19 +387,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモードを終了します</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>歌詞が見つかりません</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌詞効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌詞スタイル</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>アプリの動作</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -406,10 +409,10 @@
|
||||
<value>標準モードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>ドックモードをアクティブにします</value>
|
||||
<value>デスクトップモードを開始します</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>歌詞が見つかりません</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>ドックモードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>システムトレイ - BetterLyrics</value>
|
||||
@@ -456,19 +459,16 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>余分な黒</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>歌詞全体</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>現在の行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>現在の文字</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>歌詞の読み込み...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -531,4 +531,208 @@
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>ファンの歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>カスタマイズ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
|
||||
<value>カスタマイズ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>歌詞のスタイルと効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>アプリの外観と動作</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>デスクトップモードをアクティブにするときの自動ロック</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
|
||||
<value>アライメント</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
|
||||
<value>中心</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
|
||||
<value>左</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
|
||||
<value>右</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
|
||||
<value>アルバムアート</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
|
||||
<value>曲のタイトル&アーティスト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>アニメーションタイプを緩和します</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>再生ソース</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>再生ソース</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>指定されたメディアソースの歌詞ディスプレイを有効または無効にする</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>ログレコード</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslation.Content" xml:space="preserve">
|
||||
<value>歌詞翻訳</value>
|
||||
</data>
|
||||
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
|
||||
<value>歌詞タイムラインオフセット(ミリ秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>翻訳サービスを構成します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>サーバーアドレス</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>テストサーバー</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
|
||||
<value>ターゲット言語</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
|
||||
<value>LibreTranslate を搭載した翻訳サービス</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>https://github.com/LibreTranslate/LibreTranslate にアクセスしてください。</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<value>サーバーテストが成功しました</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<value>サーバーテストに失敗しました</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
<value>歌詞ストローク幅(デスクトップモードのみ)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>システムをフォローします</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>自動起動</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>歌詞ストロークカラー(デスクトップモードのみ)</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
|
||||
<value>常にフルスクリーンアプリケーションを常に把握してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
|
||||
<value>このアプリは、ドッキングまたはデスクトップモードが有効になっているときにフルスクリーンアプリの上に表示されます</value>
|
||||
</data>
|
||||
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
|
||||
<value>もっと</value>
|
||||
</data>
|
||||
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌詞タイムラインオフセット</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
|
||||
<value>翻訳する</value>
|
||||
</data>
|
||||
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
|
||||
<value>表示タイプ</value>
|
||||
</data>
|
||||
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>曲を切り替えると、0 に自動的にリセットされます</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>歌詞の翻訳は最初に読まれます。一致していない場合、機械の翻訳はLibretranslate Serverから要求されます</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>アルバムカバーソースを構成します</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>ドラッグしてソートすると、アルバムアートサーチオーダーは次の順序で行われます</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>アルバムアートソース</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>ローカル音楽ファイル</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>音楽プレーヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>メディアライブラリ</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーションタイプ</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーションの期間</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>リニア</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>スムーズなステップ</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>サインがゆっくりと出入りします</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>セカンダリスローインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>弾力性は内外に遅くなります</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>リバウンドはスローアウトで遅くなります</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>ゆっくりと出入りします</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>丸い、ゆっくりと出入り</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>インデックスは内外に遅くなります</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>5つの遅いインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>4つの遅いインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>3つの遅いインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>現在のラインが現在の文字から始まります</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>ハイライトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌詞タイムライン同期しきい値</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>歌詞の進行が不安定な場合は、このしきい値を増やしてみてください。この値を変更すると、歌詞の同期が逸脱する可能性があります</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQフィードバック&チャットグループ</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>今すぐ参加してください</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>로컬 음악 도서관</value>
|
||||
<value>로컬 미디어 라이브러리</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>음악이나 가사를 저장하는 폴더 추가</value>
|
||||
@@ -139,13 +139,16 @@
|
||||
<value>폴더를 추가하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>주제</value>
|
||||
<value>가사 배경 테마</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>언어</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>시스템을 따르십시오</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>가사 배경 (색상)에 적응</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>가사 배경 (색상)에 적응</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>빛</value>
|
||||
@@ -196,7 +199,7 @@
|
||||
<value>투명한</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>배경</value>
|
||||
<value>가사 배경 자료</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
@@ -210,14 +213,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>폴더가 추가되었습니다. 다시 추가하지 마십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>오버레이 앨범 아트 배경</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>동적 앨범 아트 배경</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>역동적 인 가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>앨범 아트 배경 불투명도</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>가사 배경 불투명도</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>설정 - BetterLyrics</value>
|
||||
@@ -237,8 +240,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>오른쪽</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>앨범 아트 배경 흐림 금액</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>가사 배경 블러</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>흐림 금액</value>
|
||||
@@ -252,9 +255,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>이 기능을 활성화하면 GPU 사용률이 약간 증가합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>상단 및 하단 가장자리 불투명도</value>
|
||||
</data>
|
||||
@@ -273,16 +273,16 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>몰입 형 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>앨범 배경</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>에 대한</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>가사 도서관</value>
|
||||
<value>가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>앱 모양</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>글로우 효과 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>가사 검색 제공 업체를 구성하십시오</value>
|
||||
<value>가사 소스를 구성하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>정렬하기 위해 드래그하면 가사 검색 순서는 다음 순서로됩니다.</value>
|
||||
@@ -304,19 +304,19 @@
|
||||
<value>Betterlyrics에 오신 것을 환영합니다</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>지금 가사 데이터베이스를 설정합시다</value>
|
||||
<value>더 많은 기능 옵션을 표시하려면 앱의 상단 또는 하단 영역 위로 마우스를 가져옵니다.</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>지금 음악이 재생되지 않습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>개발자 옵션</value>
|
||||
<value>고급 옵션</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>테스트 음악을 재생하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>시스템 플레이어를 사용하여 재생하십시오</value>
|
||||
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>은닉처</value>
|
||||
@@ -324,17 +324,20 @@
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>로그 파일, 네트워크 가사 캐시 포함</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>글꼴 색상</value>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>글꼴 색상 (비 전류 재생 영역)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>글꼴 색상 (현재 재생 영역)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>앨범 아트 악센트 색상</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>가사 배경 (회색)에 적응</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>가사 배경 (회색)에 적응</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>앨범 아트 스타일</value>
|
||||
<value>앨범 영역 스타일</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>코너 반경</value>
|
||||
@@ -384,19 +387,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Picture-in-Picture 모드 종료</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>가사를 찾을 수 없습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>가사 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>가사 스타일</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>앱 동작</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -406,10 +409,10 @@
|
||||
<value>표준 모드를 활성화합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>도크 모드를 활성화하십시오</value>
|
||||
<value>데스크탑 모드를 시작하십시오</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>가사를 찾을 수 없습니다</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>도크 모드를 활성화하십시오</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>시스템 트레이 - BetterLyrics</value>
|
||||
@@ -456,19 +459,16 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>여분의 검은 색</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>전체 가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>현재 라인</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>현재 숯</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>설정</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>가사로드 ...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -531,4 +531,208 @@
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>팬 가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>사용자 정의하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
|
||||
<value>사용자 정의하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>가사 스타일과 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>앱 외관과 행동</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>데스크탑 모드를 활성화 할 때 자동 잠금</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
|
||||
<value>조정</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
|
||||
<value>센터</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
|
||||
<value>왼쪽</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
|
||||
<value>오른쪽</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
|
||||
<value>앨범 아트</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
|
||||
<value>노래 제목 및 아티스트</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>애니메이션 유형 완화</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>재생 소스</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>재생 소스</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>지정된 미디어 소스의 가사 디스플레이 활성화 또는 비활성화</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>로그 레코드</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslation.Content" xml:space="preserve">
|
||||
<value>가사 번역</value>
|
||||
</data>
|
||||
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
|
||||
<value>가사 타임 라인 오프셋 (밀리초)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>번역 서비스를 구성하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>서버 주소</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>테스트 서버</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
|
||||
<value>대상 언어</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
|
||||
<value>LibreTranslate 가 구동하는 번역 서비스</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>설치 지침 및 자세한 정보는 https://github.com/LibreTranslate/LibreTranslate 를 방문하십시오 (이 소프트웨어는이 번역 서비스와 제휴하지 않습니다).</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<value>서버 테스트 성공</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<value>서버 테스트가 실패했습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
<value>가사 뇌졸중 너비 (데스크탑 모드 만)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>시스템을 따르십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>자동 시작</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>가사 스트로크 컬러 (데스크탑 모드 만)</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
|
||||
<value>항상 전체 화면 응용 프로그램 위에 머물러 있습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
|
||||
<value>도킹 또는 데스크탑 모드가 활성화 될 때이 앱이 전체 화면 앱 위에 나타나도록 강요</value>
|
||||
</data>
|
||||
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
|
||||
<value>더</value>
|
||||
</data>
|
||||
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
|
||||
<value>가사 타임 라인 오프셋</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
|
||||
<value>번역하다</value>
|
||||
</data>
|
||||
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
|
||||
<value>디스플레이 유형</value>
|
||||
</data>
|
||||
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>설정</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>노래를 전환 할 때 자동으로 0 으로 재설정됩니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>가사의 번역은 먼저 읽습니다. 일치하지 않으면 LibreTranslate 서버에서 기계 번역이 요청됩니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>앨범 표지 소스를 구성합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>분류하기 위해 드래그하면 앨범 아트 검색 순서는 다음 순서로됩니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>앨범 아트 소스</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>로컬 음악 파일</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>음악 플레이어</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>미디어 라이브러리</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>가사 스크롤링 애니메이션 유형</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>가사 스크롤링 애니메이션 지속 시간</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>선의</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>부드러운 단계</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>천천히 입력하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>이차 느린 속도가 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>탄력성이 속도가 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>리바운드는 느리게 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>천천히 안팎으로 튀어 나옵니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>둥글고 느리게 안팎으로</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>인덱스 속도가 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>5 번 느리게 안팎으로</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>4 개의 느린 안팎</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>세 번 느리게 안팎으로</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>현재 라인은 현재 숯으로 시작합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>하이라이트 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>가사 타임 라인 동기화 임계 값</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>가사 진행 상황이 불안하다면이 임계 값을 높이십시오. 이 값을 변경하면 가사가 동기화 될 수 있습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQ 피드백 및 채팅 그룹</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>지금 가입하십시오</value>
|
||||
</data>
|
||||
</root>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user