mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
Compare commits
20 Commits
v1.0.120.0
...
v1.0.135.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e6ce08c56 | ||
|
|
28f75fa751 | ||
|
|
051a7af21a | ||
|
|
c8b8853561 | ||
|
|
baab2bcc7a | ||
|
|
9a3e4adc0c | ||
|
|
8d909da139 | ||
|
|
d97f5fec37 | ||
|
|
9a12b06b7e | ||
|
|
3fca90ca72 | ||
|
|
220e940bbb | ||
|
|
dc627bd1ef | ||
|
|
3c852a9d2f | ||
|
|
ecff788c6c | ||
|
|
c471f128c1 | ||
|
|
4a389d5e33 | ||
|
|
ae1fa1c54d | ||
|
|
738a4a0bec | ||
|
|
37ae278a11 | ||
|
|
45094bde31 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -407,3 +407,4 @@ FodyWeavers.xsd
|
||||
*.sln.iml
|
||||
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
|
||||
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LastFM.cs
|
||||
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/Discord.cs
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.121.0" />
|
||||
Version="1.0.135.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -53,8 +53,6 @@
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converter:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
|
||||
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
|
||||
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
|
||||
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
|
||||
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
|
||||
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
|
||||
@@ -69,6 +67,8 @@
|
||||
<converter:TrackToLyricsConverter x:Key="TrackToLyricsConverter" />
|
||||
<converter:IntToBoolConverter x:Key="IntToBoolConverter" />
|
||||
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
|
||||
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
|
||||
<converter:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LibWatcherService;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
@@ -67,10 +69,10 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
WindowHook.OpenOrShowWindow<LyricsWindow>();
|
||||
if (Ioc.Default.GetRequiredService<ISettingsService>().AppSettings.MusicGallerySettings.AutoOpen)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<MusicGalleryWindow>();
|
||||
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +101,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<ITranslateService, TranslateService>()
|
||||
.AddSingleton<ILastFMService, LastFMService>()
|
||||
.AddSingleton<IResourceService, ResourceService>()
|
||||
.AddSingleton<IDiscordService, DiscordService>()
|
||||
// ViewModels
|
||||
.AddSingleton<AppSettingsControlViewModel>()
|
||||
.AddSingleton<PlaybackSettingsControlViewModel>()
|
||||
|
||||
@@ -61,10 +61,13 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.250402" />
|
||||
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
|
||||
<PackageReference Include="csharp-kana" Version="1.0.2" />
|
||||
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="9.5.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="F23.StringSimilarity" Version="7.0.0" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" />
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
@@ -87,8 +90,9 @@
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.Windows.Shell" Version="4.2.1" />
|
||||
<PackageReference Include="WinUIEx" Version="2.9.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.7.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.8.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
|
||||
@@ -106,8 +110,13 @@
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
<TrimmerRootAssembly Include="NAudio.Wasapi" />
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
|
||||
<TrimmerRootAssembly Include="Vanara.PInvoke.Gdi32" />
|
||||
<TrimmerRootAssembly Include="Vanara.PInvoke.Shell32" />
|
||||
<TrimmerRootAssembly Include="Vanara.PInvoke.User32" />
|
||||
<TrimmerRootAssembly Include="Vanara.Windows.Shell" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\AIMP.png">
|
||||
@@ -116,6 +125,9 @@
|
||||
<Content Update="Assets\AlbumArtPlaceholder.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Alipay.jpg">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\AMLLPlayer.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -206,6 +218,9 @@
|
||||
<Content Update="Assets\Spotify.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\WeChatReward.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Wiki82.profile.xml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
namespace BetterLyrics.WinUI3.Collections
|
||||
{
|
||||
// https://stackoverflow.com/a/32013610/11048731
|
||||
public class FullyObservableCollection<T> : ObservableCollection<T>
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
class Discord
|
||||
{
|
||||
public const string AppID = "Your Discord app ID here";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public class ExtendedGenreFiled
|
||||
{
|
||||
public const string NetEaseCloudMusicTrackID = "NCM-";
|
||||
public const string FileName = "FILENAME-";
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,14 @@
|
||||
{
|
||||
public static class Link
|
||||
{
|
||||
public const string MicrosoftStoreUrl = "https://apps.microsoft.com/detail/9p1wcd1p597r";
|
||||
public const string GitHubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public const string ShareHubUrl = $"{GitHubUrl}/blob/dev/ShareHub/index.md";
|
||||
public const string WikiUrl = $"{GitHubUrl}/wiki";
|
||||
public const string AppleMusicCfgUrl = $"{WikiUrl}/%5BEN%5D-Lyrics-provider-configuration#apple-music";
|
||||
public const string FAQUrl = $"{GitHubUrl}/blob/dev/FAQ/index.md";
|
||||
public const string TermsOfServiceUrl = $"{GitHubUrl}/blob/dev/TermsofService.md";
|
||||
public const string PrivacyPolicy = $"{GitHubUrl}/blob/dev/PrivacyPolicy.md";
|
||||
public const string WikiUrl = "https://jayfunc.blog/work/betterlyrics";
|
||||
public const string AppleMusicCfgUrl = $"{WikiUrl}#lyrics-sources-configuration";
|
||||
public const string FAQUrl = $"{WikiUrl}#faq";
|
||||
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
|
||||
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
|
||||
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public class PlayerName
|
||||
{
|
||||
|
||||
@@ -50,10 +50,12 @@
|
||||
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageInstructions" />
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHubUrl}" />
|
||||
<HyperlinkButton Content="Wiki" NavigateUri="{x:Bind const:Link.WikiUrl}" />
|
||||
<HyperlinkButton Content="FAQ" NavigateUri="{x:Bind const:Link.FAQUrl}" />
|
||||
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
|
||||
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfServiceUrl}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
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:dev="using:DevWinUI"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:dev="using:DevWinUI"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
<dev:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<local:ExtendedSlider
|
||||
Default="12"
|
||||
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Unit="%"
|
||||
@@ -52,7 +51,6 @@
|
||||
<dev:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<local:ExtendedSlider
|
||||
Default="12"
|
||||
|
||||
Maximum="64"
|
||||
Minimum="0"
|
||||
Value="{x:Bind AlbumArtLayoutSettings.CoverImageShadowAmount, Mode=TwoWay}" />
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreCacheWhenSearching, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageShowHideHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ShowOrHideLyricsWindowShortcut, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
@@ -128,18 +128,29 @@
|
||||
<dev:SettingsExpander.Items>
|
||||
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled, Mode=OneWay}">
|
||||
<uc:ExtendedSlider
|
||||
Maximum="100"
|
||||
Minimum="10"
|
||||
Maximum="10"
|
||||
Minimum="0"
|
||||
ResetButtonVisibility="Collapsed"
|
||||
Value="{x:Bind LyricsBackgroundSettings.SnowFlakeOverlayAmount, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled, Mode=OneWay}">
|
||||
<uc:ExtendedSlider
|
||||
Maximum="10"
|
||||
Minimum="1"
|
||||
ResetButtonVisibility="Collapsed"
|
||||
Value="{x:Bind LyricsBackgroundSettings.SnowFlakeOverlaySpeed, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageFogLayer" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsFogOverlayEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsExpander
|
||||
x:Uid="SettingsPageSpectrumLayer"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Glyph=}"
|
||||
IsExpanded="{x:Bind LyricsBackgroundSettings.IsSpectrumOverlayEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsSpectrumOverlayEnabled, Mode=TwoWay}" />
|
||||
<dev:SettingsExpander.Items>
|
||||
|
||||
@@ -69,6 +69,17 @@
|
||||
|
||||
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
|
||||
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}" TextWrapping="Wrap" />
|
||||
<RichTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" TextWrapping="Wrap">
|
||||
<Paragraph>
|
||||
<Run x:Uid="ArtistsSplitHint" />
|
||||
<Run Text=";" />
|
||||
<Run Text="," />
|
||||
<Run Text="/" />
|
||||
<Run Text=";" />
|
||||
<Run Text="、" />
|
||||
<Run Text="," />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
|
||||
@@ -126,21 +137,67 @@
|
||||
<DataTemplate x:DataType="models:LyricsSearchResult">
|
||||
<ListViewItem IsEnabled="{x:Bind IsFound}">
|
||||
<StackPanel Padding="3,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
|
||||
<TextBlock Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Text="{x:Bind Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
<TextBlock
|
||||
Text="{x:Bind Title}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind Artist}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind Album}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<HyperlinkButton
|
||||
Padding="0"
|
||||
Content="{x:Bind Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}"
|
||||
NavigateUri="{x:Bind Reference, FallbackValue=about:blank}" />
|
||||
<!-- Title -->
|
||||
<Grid ColumnSpacing="12" Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="LyricsSearchControlTitle" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Artist -->
|
||||
<Grid ColumnSpacing="12" Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="LyricsSearchControlArtist" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Album -->
|
||||
<Grid ColumnSpacing="12" Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="LyricsSearchControlAlbum" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Duration -->
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock x:Uid="LyricsSearchControlDurauion" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Duration}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="s" />
|
||||
</StackPanel>
|
||||
<!-- Match percentage -->
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock x:Uid="LyricsPageMatchPercentage" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind MatchPercentage}" />
|
||||
</StackPanel>
|
||||
<!-- NOT FOUND -->
|
||||
<TextBlock
|
||||
x:Uid="LyricsSearchControlNotFound"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -11,66 +11,74 @@
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
CornerRadius="12">
|
||||
<FontIcon
|
||||
Margin="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="" />
|
||||
<Button
|
||||
Margin="12"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Click="Button_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<Button.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Escape" />
|
||||
</Button.KeyboardAccelerators>
|
||||
</Button>
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Grid x:Name="ShadowCastGrid" />
|
||||
<Border
|
||||
x:Name="ShadowRect"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="12"
|
||||
Loaded="ShadowRect_Loaded"
|
||||
Translation="0,0,64">
|
||||
<Border.Shadow>
|
||||
<ThemeShadow x:Name="Shadow" />
|
||||
</Border.Shadow>
|
||||
</Border>
|
||||
<Grid Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}" CornerRadius="12">
|
||||
<TextBlock
|
||||
x:Uid="SystemTraySwitch"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top" />
|
||||
<Button
|
||||
Margin="12"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Click="Button_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<Button.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Escape" />
|
||||
</Button.KeyboardAccelerators>
|
||||
</Button>
|
||||
|
||||
<ListView
|
||||
Margin="48,56"
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
Margin="0,10"
|
||||
Padding="5"
|
||||
AllowFocusOnInteraction="True"
|
||||
CornerRadius="4"
|
||||
Tapped="Grid_Tapped">
|
||||
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<ListView
|
||||
Margin="48,56"
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid
|
||||
Margin="0,10"
|
||||
Padding="5"
|
||||
AllowFocusOnInteraction="True"
|
||||
CornerRadius="4"
|
||||
Tapped="Grid_Tapped">
|
||||
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<StackPanel
|
||||
Margin="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<FontIcon
|
||||
Margin="0,1,0,0"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="LyricsWindowSwitchWindowHelp" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<HyperlinkButton Click="SettingsHypelinkButton_Click">
|
||||
<HyperlinkButton.Content>
|
||||
<TextBlock x:Uid="LyricsWindowSwitchWindowHelp" />
|
||||
</HyperlinkButton.Content>
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
@@ -34,10 +34,21 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private async Task HideAsync()
|
||||
{
|
||||
var lyricsWindowSwitchWindow = WindowHelper.GetWindowByWindowType<LyricsWindowSwitchWindow>();
|
||||
var lyricsWindowSwitchWindow = WindowHook.GetWindow<LyricsWindowSwitchWindow>();
|
||||
lyricsWindowSwitchWindow?.ViewModel.RootGridOpacity = 0;
|
||||
await Task.Delay(300);
|
||||
WindowHelper.HideWindow<LyricsWindowSwitchWindow>();
|
||||
WindowHook.HideWindow<LyricsWindowSwitchWindow>();
|
||||
}
|
||||
|
||||
private void ShadowRect_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Shadow.Receivers.Add(ShadowCastGrid);
|
||||
}
|
||||
|
||||
private async void SettingsHypelinkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await HideAsync();
|
||||
WindowHook.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,14 +64,22 @@
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{Binding Provider, Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
<FontIcon
|
||||
FontFamily="Segoe UI Symbol"
|
||||
FontSize="12"
|
||||
Glyph="⠿" />
|
||||
<ImageIcon Height="16" Source="{Binding Provider, Converter={StaticResource MediaSourceProviderToLogoUriConverter}, Mode=OneWay}" />
|
||||
<Grid
|
||||
Width="16"
|
||||
Height="16"
|
||||
CornerRadius="4">
|
||||
<ImageIcon Source="{Binding LogoPath}" />
|
||||
</Grid>
|
||||
<TextBlock
|
||||
MaxWidth="200"
|
||||
Text="{Binding Provider, Converter={StaticResource MediaSourceProviderToDisplayedNameConverter}, Mode=OneWay}"
|
||||
Text="{Binding DisplayName, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
@@ -90,6 +98,10 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsLastFMTrackEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageDiscordPresence">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsDiscordPresenceEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- LX music server -->
|
||||
<dev:SettingsCard x:Uid="SettingsPageLXMusicServer" Visibility="{x:Bind ViewModel.SelectedMediaSourceProvider.IsLXMusic, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid ColumnSpacing="6">
|
||||
@@ -170,6 +182,13 @@
|
||||
|
||||
<!-- 歌词源配置 -->
|
||||
<TextBlock x:Uid="SettingsPageLyricsSearchProvidersConfig" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsSearchType">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.SelectedMediaSourceProvider.LyricsSearchType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsSearchSequential" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsSearchBestMatch" />
|
||||
</ComboBox>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<ListView
|
||||
x:Name="LyricsSearchProvidersListView"
|
||||
AllowDrop="True"
|
||||
@@ -238,18 +257,95 @@
|
||||
|
||||
<!-- Provider info -->
|
||||
<TextBlock x:Uid="SettingsPageRealtimeStatus" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
<dev:SettingsCard x:Uid="LyricsPageLyricsProviderPrefix">
|
||||
<HyperlinkButton
|
||||
Content="{x:Bind ViewModel.LyricsSearchProvider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}"
|
||||
IsEnabled="False"
|
||||
NavigateUri="{x:Bind ViewModel.OriginalLyricsRef, Mode=OneWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="LyricsPageTranslationProviderPrefix">
|
||||
<HyperlinkButton
|
||||
Content="{x:Bind ViewModel.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}"
|
||||
IsEnabled="False"
|
||||
NavigateUri="{x:Bind ViewModel.TranslatedLyricsRef, Mode=OneWay}" />
|
||||
<dev:SettingsCard ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<!-- Playback source -->
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="SettingsPagePlaybackSource" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Playback source ID -->
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="SettingsPagePlaybackSourceID" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Song title -->
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="LyricsSearchControlTitle" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Song artists -->
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="LyricsSearchControlArtist" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Song album -->
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Uid="LyricsSearchControlAlbum" Grid.Column="0" />
|
||||
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</Grid>
|
||||
<!-- Lyrics source -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBlock x:Uid="LyricsPageLyricsProviderPrefix" />
|
||||
<HyperlinkButton
|
||||
Padding="0"
|
||||
Content="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}"
|
||||
IsEnabled="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.IsFound, Mode=OneWay}"
|
||||
NavigateUri="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<!-- Translation source -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBlock x:Uid="LyricsPageTranslationProviderPrefix" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.MediaSessionsService.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
|
||||
</StackPanel>
|
||||
<!-- Match percentage -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock x:Uid="LyricsPageMatchPercentage" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
@@ -272,12 +368,7 @@
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageTranslationConfig" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
|
||||
<dev:SettingsCard.Description>
|
||||
<HyperlinkButton Margin="0,6,0,0" NavigateUri="https://github.com/LibreTranslate/LibreTranslate">
|
||||
<TextBlock
|
||||
x:Uid="SettingsPageTranslationInfoLink"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Content="https://github.com/LibreTranslate/LibreTranslate" NavigateUri="https://github.com/LibreTranslate/LibreTranslate" />
|
||||
</dev:SettingsCard.Description>
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Input;
|
||||
@@ -89,7 +89,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private void CheckButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool registered = GlobalHotKeyHelper.IsHotKeyRegistered(Shortcut);
|
||||
bool registered = GlobalHotKeyHook.IsHotKeyRegistered(Shortcut);
|
||||
if (registered)
|
||||
{
|
||||
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("SettingsPageShortcutRegSuccessInfo"));
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class IntToDoubleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int intValue)
|
||||
{
|
||||
return (double)intValue;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class MediaSourceProviderToDisplayedNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
PlayerID.Spotify => PlayerName.Spotify,
|
||||
PlayerID.AppleMusic => PlayerName.AppleMusic,
|
||||
PlayerID.iTunes => PlayerName.iTunes,
|
||||
PlayerID.KugouMusic => PlayerName.KugouMusic,
|
||||
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
||||
PlayerID.QQMusic => PlayerName.QQMusic,
|
||||
PlayerID.LXMusic => PlayerName.LXMusic,
|
||||
PlayerID.LXMusicPortable => PlayerName.LXMusicPortable,
|
||||
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
||||
PlayerID.AIMP => PlayerName.AIMP,
|
||||
PlayerID.Foobar2000 => PlayerName.Foobar2000,
|
||||
PlayerID.MusicBee => PlayerName.MusicBee,
|
||||
PlayerID.PotPlayer => PlayerName.PotPlayer,
|
||||
PlayerID.Chrome => PlayerName.Chrome,
|
||||
PlayerID.Edge => PlayerName.Edge,
|
||||
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
|
||||
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
||||
PlayerID.SaltPlayerForWindows => PlayerName.SaltPlayerForWindows,
|
||||
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
|
||||
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
|
||||
PlayerID.Listen1 => PlayerName.Listen1,
|
||||
_ => provider,
|
||||
};
|
||||
}
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class MediaSourceProviderToLogoUriConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
|
||||
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.iTunes => PathHelper.iTunesLogoPath,
|
||||
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
|
||||
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
||||
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
|
||||
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.LXMusicPortable => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
||||
PlayerID.AIMP => PathHelper.AIMPLogoPath,
|
||||
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
||||
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
|
||||
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
|
||||
PlayerID.Chrome => PathHelper.ChromeLogoPath,
|
||||
PlayerID.Edge => PathHelper.EdgeLogoPath,
|
||||
PlayerID.BetterLyrics => PathHelper.LogoPath,
|
||||
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
|
||||
PlayerID.SaltPlayerForWindows => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerID.Listen1 => PathHelper.Listen1LogoPath,
|
||||
_ => PathHelper.UnknownPlayerLogoPath,
|
||||
};
|
||||
}
|
||||
return PathHelper.UnknownPlayerLogoPath;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class MillisecondsToSecondsConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int intValue)
|
||||
{
|
||||
return intValue / 1000.0;
|
||||
}
|
||||
else if (value is double doubleValue)
|
||||
{
|
||||
return doubleValue / 1000.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
if (value is Track track)
|
||||
{
|
||||
return track.GetLyrics();
|
||||
return track.GetRawLyrics();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum ChineseRomanization
|
||||
{
|
||||
Pinyin,
|
||||
Jyutping,
|
||||
}
|
||||
|
||||
public static class ChineseRomanizationExtensions
|
||||
{
|
||||
public static string ToPhoneticCode(this ChineseRomanization chineseRomanization)
|
||||
{
|
||||
return chineseRomanization switch
|
||||
{
|
||||
ChineseRomanization.Pinyin => PhoneticHelper.PinyinCode,
|
||||
ChineseRomanization.Jyutping => PhoneticHelper.JyutpingCode,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(chineseRomanization))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,72 +11,4 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
Krc,
|
||||
NotSpecified,
|
||||
}
|
||||
|
||||
public static class LyricsFormatExtensions
|
||||
{
|
||||
public static LyricsFormat? DetectFormat(this string content)
|
||||
{
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToFileExtension(this LyricsFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => ".lrc",
|
||||
LyricsFormat.Qrc => ".qrc",
|
||||
LyricsFormat.Krc => ".krc",
|
||||
LyricsFormat.Eslrc => ".eslrc",
|
||||
LyricsFormat.Ttml => ".ttml",
|
||||
_ => ".*",
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsSearchProvider? ToLyricsSearchProvider(this LyricsFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => LyricsSearchProvider.LocalLrcFile,
|
||||
LyricsFormat.Eslrc => LyricsSearchProvider.LocalEslrcFile,
|
||||
LyricsFormat.Ttml => LyricsSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,70 +17,4 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LocalTtmlFile,
|
||||
AppleMusic,
|
||||
}
|
||||
|
||||
public static class LyricsSearchProviderExtensions
|
||||
{
|
||||
public static string GetCacheDirectory(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsFormat GetLyricsFormat(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.QQ => LyricsFormat.Qrc,
|
||||
LyricsSearchProvider.Kugou => LyricsFormat.Krc,
|
||||
LyricsSearchProvider.Netease => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.AmllTtmlDb => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.AppleMusic => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.LocalEslrcFile => LyricsFormat.Eslrc,
|
||||
LyricsSearchProvider.LocalTtmlFile => LyricsFormat.Ttml,
|
||||
_ => LyricsFormat.NotSpecified,
|
||||
};
|
||||
}
|
||||
|
||||
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 TranslationSearchProvider? ToTranslationSearchProvider(this LyricsSearchProvider? provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => TranslationSearchProvider.LrcLib,
|
||||
LyricsSearchProvider.QQ => TranslationSearchProvider.QQ,
|
||||
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
|
||||
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
|
||||
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
|
||||
LyricsSearchProvider.AppleMusic => TranslationSearchProvider.AppleMusic,
|
||||
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
|
||||
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
|
||||
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,
|
||||
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsSearchType
|
||||
{
|
||||
Sequential,
|
||||
BestMatch
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum SpectrumPlacement
|
||||
{
|
||||
|
||||
@@ -5,9 +5,8 @@ using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, List<Color> albumArtLightAccentColors, List<Color> albumArtDarkAccentColors) : EventArgs
|
||||
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, List<Color> albumArtLightAccentColors, List<Color> albumArtDarkAccentColors) : EventArgs
|
||||
{
|
||||
public byte[]? Bytes { get; set; } = bytes;
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
|
||||
public List<Color> AlbumArtLightAccentColors { get; set; } = albumArtLightAccentColors;
|
||||
public List<Color> AlbumArtDarkAccentColors { get; set; } = albumArtDarkAccentColors;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
|
||||
{
|
||||
public bool IsPlaying { get; set; } = isPlaying;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
|
||||
{
|
||||
public SongInfo? SongInfo { get; set; } = songInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class TimelineChangedEventArgs(TimeSpan position, TimeSpan end) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
public TimeSpan End { get; set; } = end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.UI.Windowing;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class AppWindowExtensions
|
||||
{
|
||||
extension(AppWindow appWindow)
|
||||
{
|
||||
public void SetIcons()
|
||||
{
|
||||
appWindow.SetIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTaskbarIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTitleBarIcon(PathHelper.LogoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class CanvasTextLayoutExtensions
|
||||
{
|
||||
extension(CanvasTextLayout? canvasTextLayout)
|
||||
{
|
||||
public void SetFontFamily(string? text, string cjk, string latin)
|
||||
{
|
||||
if (canvasTextLayout == null) return;
|
||||
if (text == null) return;
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
canvasTextLayout.SetFontFamily(i, 1, LanguageHelper.IsCJK(text[i]) ? cjk : latin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class ChineseRomanizationExtensions
|
||||
{
|
||||
extension(ChineseRomanization chineseRomanization)
|
||||
{
|
||||
public string ToPhoneticCode() => chineseRomanization switch
|
||||
{
|
||||
ChineseRomanization.Pinyin => PhoneticHelper.PinyinCode,
|
||||
ChineseRomanization.Jyutping => PhoneticHelper.JyutpingCode,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(chineseRomanization))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class ColorExtensions
|
||||
{
|
||||
extension(Color color)
|
||||
{
|
||||
public Color WithAlpha(byte alpha)
|
||||
{
|
||||
return Color.FromArgb(alpha, color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
public Color WithOpacity(float opacity)
|
||||
{
|
||||
return Color.FromArgb((byte)(opacity * 255), color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
public Color WithBrightness(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 Vector3 ToVector3RGB()
|
||||
{
|
||||
return new Vector3((float)color.R / 0xff, (float)color.G / 0xff, (float)color.B / 0xff);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class DisposableObjectExtension
|
||||
{
|
||||
extension(IDisposable? obj)
|
||||
{
|
||||
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
|
||||
public bool IsDisposed()
|
||||
{
|
||||
/*
|
||||
TIM C: This hacky code is because MSFT does not provide a standard way to interrogate if an object is disposed or not.
|
||||
I wrote this based upon streams, but it should work for many other types of MSFT objects (maybe).
|
||||
*/
|
||||
if (obj == null) { return true; }
|
||||
|
||||
var objType = obj.GetType();
|
||||
//var foo = new System.IO.BufferedStream();
|
||||
|
||||
// the _disposed pattern should catch a lot of msft objects.... hopefully
|
||||
var isDisposedField = objType.GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance) ??
|
||||
objType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
isDisposedField = objType.GetField("_isOpen", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
// Windows.Graphics.Imaging.SoftwareBitmap
|
||||
isDisposedField = objType.GetField("_objRef_global__System_IDisposable", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
// System.IO.FileStream
|
||||
var strategyField = objType.GetField("_strategy", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (strategyField != null)
|
||||
{
|
||||
var strategy = strategyField.GetValue(obj);
|
||||
var isClosedField = strategy.GetType().GetProperty("IsClosed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isClosedField != null) { return Convert.ToBoolean(isClosedField.GetValue(strategy)); }
|
||||
}
|
||||
|
||||
// other streams that use this pattern to determine if they are disposed
|
||||
if (obj is Stream stream) { return !stream.CanRead && !stream.CanWrite; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
extension<T>(T value) where T : struct, Enum
|
||||
{
|
||||
public T GetNext()
|
||||
{
|
||||
T[] values = Enum.GetValues<T>();
|
||||
int currentIndex = Array.IndexOf(values, value);
|
||||
int nextIndex = (currentIndex + 1) % values.Length;
|
||||
return values[nextIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
extension<T>(IEnumerable<T> items)
|
||||
{
|
||||
public ObservableCollection<GroupInfoList> GetGroupedBy(Func<T, object> groupKeySelector, Func<object, object>? orderSelector = null)
|
||||
{
|
||||
var query = from item in items
|
||||
group item by groupKeySelector(item) into g
|
||||
orderby g.Key
|
||||
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
|
||||
|
||||
return new ObservableCollection<GroupInfoList>(query);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class LyricsFormatExtensions
|
||||
{
|
||||
extension(LyricsFormat format)
|
||||
{
|
||||
public string ToFileExtension()
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => ".lrc",
|
||||
LyricsFormat.Qrc => ".qrc",
|
||||
LyricsFormat.Krc => ".krc",
|
||||
LyricsFormat.Eslrc => ".eslrc",
|
||||
LyricsFormat.Ttml => ".ttml",
|
||||
_ => ".*",
|
||||
};
|
||||
}
|
||||
|
||||
public LyricsSearchProvider? ToLyricsSearchProvider()
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => LyricsSearchProvider.LocalLrcFile,
|
||||
LyricsFormat.Eslrc => LyricsSearchProvider.LocalEslrcFile,
|
||||
LyricsFormat.Ttml => LyricsSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class LyricsSearchProviderExtensions
|
||||
{
|
||||
extension(LyricsSearchProvider provider)
|
||||
{
|
||||
public string GetCacheDirectory() => provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
|
||||
public LyricsFormat GetLyricsFormat() => provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.QQ => LyricsFormat.Qrc,
|
||||
LyricsSearchProvider.Kugou => LyricsFormat.Krc,
|
||||
LyricsSearchProvider.Netease => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.AmllTtmlDb => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.AppleMusic => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.LocalEslrcFile => LyricsFormat.Eslrc,
|
||||
LyricsSearchProvider.LocalTtmlFile => LyricsFormat.Ttml,
|
||||
_ => LyricsFormat.NotSpecified,
|
||||
};
|
||||
|
||||
public bool IsLocal() => provider
|
||||
is LyricsSearchProvider.LocalMusicFile
|
||||
or LyricsSearchProvider.LocalLrcFile
|
||||
or LyricsSearchProvider.LocalEslrcFile
|
||||
or LyricsSearchProvider.LocalTtmlFile;
|
||||
|
||||
public bool IsRemote() => !provider.IsLocal();
|
||||
|
||||
public TranslationSearchProvider? ToTranslationSearchProvider() => provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => TranslationSearchProvider.LrcLib,
|
||||
LyricsSearchProvider.QQ => TranslationSearchProvider.QQ,
|
||||
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
|
||||
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
|
||||
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
|
||||
LyricsSearchProvider.AppleMusic => TranslationSearchProvider.AppleMusic,
|
||||
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
|
||||
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
|
||||
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,
|
||||
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class LyricsWindowStatusExtensions
|
||||
{
|
||||
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
|
||||
|
||||
public static LyricsWindowStatus DesktopMode()
|
||||
{
|
||||
return new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("DesktopMode"),
|
||||
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
|
||||
WindowBounds = new Rect(100, 100, 600, 250),
|
||||
IsAlwaysOnTop = true,
|
||||
IsAlwaysOnTopPolling = true,
|
||||
IsBorderless = true,
|
||||
IsClickThrough = true,
|
||||
IsAdaptToEnvironment = true,
|
||||
IsShownInSwitchers = false,
|
||||
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
|
||||
LyricsStyleSettings = new()
|
||||
{
|
||||
LyricsAlignmentType = TextAlignmentType.Center,
|
||||
},
|
||||
LyricsBackgroundSettings = new LyricsBackgroundSettings
|
||||
{
|
||||
IsFluidOverlayEnabled = false,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus DockedMode()
|
||||
{
|
||||
var status = new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("DockedMode"),
|
||||
IsWorkArea = true,
|
||||
IsAlwaysOnTop = true,
|
||||
IsAlwaysOnTopPolling = true,
|
||||
IsBorderless = true,
|
||||
IsAdaptToEnvironment = true,
|
||||
IsShownInSwitchers = false,
|
||||
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
|
||||
EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow,
|
||||
TitleBarArea = TitleBarArea.None,
|
||||
LyricsStyleSettings = new LyricsStyleSettings
|
||||
{
|
||||
LyricsAlignmentType = TextAlignmentType.Center,
|
||||
},
|
||||
LyricsBackgroundSettings = new LyricsBackgroundSettings
|
||||
{
|
||||
IsFluidOverlayEnabled = false,
|
||||
IsPureColorOverlayEnabled = true,
|
||||
}
|
||||
};
|
||||
status.WindowBounds = status.GetWindowBoundsWhenWorkArea();
|
||||
return status;
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus FullscreenMode()
|
||||
{
|
||||
var status = new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("FullscreenMode"),
|
||||
IsBorderless = true,
|
||||
IsAlwaysOnTop = false,
|
||||
TitleBarArea = TitleBarArea.None,
|
||||
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
|
||||
LyricsStyleSettings = new LyricsStyleSettings
|
||||
{
|
||||
LyricsAlignmentType = TextAlignmentType.Center,
|
||||
},
|
||||
};
|
||||
status.WindowBounds = new Rect(
|
||||
status.MonitorBounds.X,
|
||||
status.MonitorBounds.Y - 1,
|
||||
status.MonitorBounds.Width,
|
||||
status.MonitorBounds.Height + 1
|
||||
);
|
||||
return status;
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus StandardMode()
|
||||
{
|
||||
return new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("StandardMode"),
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus NarrowMode()
|
||||
{
|
||||
return new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("NarrowMode"),
|
||||
WindowBounds = new Rect(100, 100, 400, 800),
|
||||
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class ObservableCollectionExtensions
|
||||
{
|
||||
extension<T>(ObservableCollection<T> list)
|
||||
{
|
||||
public void InsertRange(int index, IEnumerable<T> items)
|
||||
{
|
||||
if (list == null) return;
|
||||
if (items == null) return;
|
||||
if (index < 0 || index > list.Count) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Insert(index++, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class PointExtensions
|
||||
{
|
||||
extension(Point point)
|
||||
{
|
||||
public PointInt32 ToPointInt32() => new((int)point.X, (int)point.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class RectExtensions
|
||||
{
|
||||
extension(Rect rect)
|
||||
{
|
||||
public RectInt32 ToRectInt32() => new(
|
||||
(int)rect.X,
|
||||
(int)rect.Y,
|
||||
(int)rect.Width,
|
||||
(int)rect.Height
|
||||
);
|
||||
|
||||
public Rect WithHeight(double height) => new(
|
||||
rect.X,
|
||||
rect.Y,
|
||||
rect.Width,
|
||||
height
|
||||
);
|
||||
|
||||
public Rect WithWidth(double width) => new(
|
||||
rect.X,
|
||||
rect.Y,
|
||||
width,
|
||||
rect.Height
|
||||
);
|
||||
|
||||
public Rect WithX(double x) => new(
|
||||
x,
|
||||
rect.Y,
|
||||
rect.Width,
|
||||
rect.Height
|
||||
);
|
||||
|
||||
public Rect WithY(double y) => new(
|
||||
rect.X,
|
||||
y,
|
||||
rect.Width,
|
||||
rect.Height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class SongInfoExtensions
|
||||
{
|
||||
public static SongInfo Placeholder => new SongInfo
|
||||
{
|
||||
Title = "N/A",
|
||||
Album = "N/A",
|
||||
Artists = ["N/A"],
|
||||
};
|
||||
|
||||
extension(SongInfo songInfo)
|
||||
{
|
||||
public SongInfo WithTitle(string value)
|
||||
{
|
||||
songInfo.Title = value;
|
||||
return songInfo;
|
||||
}
|
||||
|
||||
public SongInfo WithArtist(string[] value)
|
||||
{
|
||||
songInfo.Artists = value;
|
||||
return songInfo;
|
||||
}
|
||||
|
||||
public SongInfo WithAlbum(string value)
|
||||
{
|
||||
songInfo.Album = value;
|
||||
return songInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
private static readonly string[] _splitter =
|
||||
[
|
||||
";"
|
||||
,
|
||||
","
|
||||
,
|
||||
"/"
|
||||
,
|
||||
";"
|
||||
,
|
||||
"、"
|
||||
,
|
||||
","
|
||||
];
|
||||
|
||||
extension(string str)
|
||||
{
|
||||
public string[] SplitByCommonSplitter()
|
||||
{
|
||||
var splitter = _splitter.FirstOrDefault(str.Contains);
|
||||
if (splitter != null)
|
||||
{
|
||||
return str.Split(splitter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return [str];
|
||||
}
|
||||
}
|
||||
|
||||
public LyricsFormat? DetectFormat()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return null;
|
||||
|
||||
// TTML: 检查 <tt ... xmlns="http://www.w3.org/ns/ttml"
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
str,
|
||||
@"<tt\b[^>]*\bxmlns\s*=\s*[""']http://www\.w3\.org/ns/ttml[""']",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
return LyricsFormat.Ttml;
|
||||
}
|
||||
// KRC: 检测主内容格式 [start,duration]<offset,duration,0>字...
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
str,
|
||||
@"^\[\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(
|
||||
str,
|
||||
@"^\[\d+,\d+\].*?\(\d+,\d+\)",
|
||||
System.Text.RegularExpressions.RegexOptions.Multiline))
|
||||
{
|
||||
return LyricsFormat.Qrc;
|
||||
}
|
||||
// 标准LRC和增强型LRC
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(str, @"\[\d{1,2}:\d{2}") ||
|
||||
System.Text.RegularExpressions.Regex.IsMatch(str, @"<\d{1,2}:\d{2}\.\d{2,3}>"))
|
||||
{
|
||||
return LyricsFormat.Lrc;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using ATL;
|
||||
using System.IO;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class TrackExtensions
|
||||
{
|
||||
extension(Track track)
|
||||
{
|
||||
public string GetParentFolderName() => Directory.GetParent(track.Path)?.Name ?? "";
|
||||
|
||||
public string GetParentFolderPath() => Directory.GetParent(track.Path)?.FullName ?? "";
|
||||
|
||||
public string GetRawLyrics()
|
||||
{
|
||||
if (track.Path is string path)
|
||||
{
|
||||
return TagLib.File.Create(path).Tag.Lyrics;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class VectorExtensions
|
||||
{
|
||||
extension(Vector2 vector2)
|
||||
{
|
||||
public Vector2 WithX(float x)
|
||||
{
|
||||
return new Vector2(x, vector2.Y);
|
||||
}
|
||||
|
||||
public Vector2 WithY(float y)
|
||||
{
|
||||
return new Vector2(vector2.X, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class WindowExtensions
|
||||
{
|
||||
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
|
||||
|
||||
extension(Window window)
|
||||
{
|
||||
public void Init(
|
||||
string titleKey,
|
||||
TitleBarHeightOption titleBarHeightOption = TitleBarHeightOption.Standard,
|
||||
BackdropType backdropType = BackdropType.DesktopAcrylic)
|
||||
{
|
||||
window.Title = _resourceService.GetLocalizedString(titleKey);
|
||||
window.AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode;
|
||||
window.AppWindow.SetIcons();
|
||||
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.AppWindow.TitleBar.PreferredHeightOption = titleBarHeightOption;
|
||||
|
||||
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(backdropType);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Microsoft.UI.Windowing;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class AppWindowHelper
|
||||
{
|
||||
public static void SetIcons(this AppWindow appWindow)
|
||||
{
|
||||
appWindow.SetIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTaskbarIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTitleBarIcon(PathHelper.LogoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CanvasTextLayoutExtensions
|
||||
{
|
||||
public static void SetFontFamily(this CanvasTextLayout? layout, string? text, string cjk, string latin)
|
||||
{
|
||||
if (layout == null) return;
|
||||
if (text == null) return;
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
layout.SetFontFamily(i, 1, LanguageHelper.IsCJK(text[i]) ? cjk : latin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static ObservableCollection<GroupInfoList> GetGroupedBy<T>(
|
||||
this IEnumerable<T> items,
|
||||
Func<T, object> groupKeySelector,
|
||||
Func<object, object>? orderSelector = null)
|
||||
{
|
||||
var query = from item in items
|
||||
group item by groupKeySelector(item) into g
|
||||
orderby g.Key
|
||||
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
|
||||
|
||||
return new ObservableCollection<GroupInfoList>(query);
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
|
||||
{
|
||||
if (collection == null) return;
|
||||
if (items == null) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
collection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertRange<T>(this IList<T> list, int index, IEnumerable<T> items)
|
||||
{
|
||||
if (list == null) return;
|
||||
if (items == null) return;
|
||||
if (index < 0 || index > list.Count) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Insert(index++, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Numerics;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
using Color = Windows.UI.Color;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
@@ -62,86 +62,28 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
);
|
||||
}
|
||||
|
||||
public static Color ToColor(this int argb)
|
||||
{
|
||||
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 WithOpacity(this Color color, float opacity)
|
||||
{
|
||||
return Color.FromArgb((byte)(opacity * 255), 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 Vector3 ToVector3RGB(this Color color)
|
||||
{
|
||||
return new Vector3((float)color.R / 0xff, (float)color.G / 0xff, (float)color.B / 0xff);
|
||||
}
|
||||
|
||||
public static Color GetRandomColor()
|
||||
{
|
||||
return Color.FromArgb(255, (byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256));
|
||||
}
|
||||
|
||||
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
|
||||
public static Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return Colors.Transparent;
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
|
||||
var monitorInfo = MonitorHook.GetMonitorInfoExFromDeviceName(monitorDeviceName);
|
||||
int screenWidth = monitorInfo.rcMonitor.Width;
|
||||
switch (mode)
|
||||
{
|
||||
case WindowPixelSampleMode.BelowWindow:
|
||||
{
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Bottom + 2, screenWidth, 1);
|
||||
}
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Bottom + 2, screenWidth, 1);
|
||||
case WindowPixelSampleMode.AboveWindow:
|
||||
{
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - 2, screenWidth, 1);
|
||||
}
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - 2, screenWidth, 1);
|
||||
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;
|
||||
if (width <= 0 || height <= 0) return Colors.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
@@ -150,10 +92,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
return Colors.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 36, 36, 36);
|
||||
List<System.Drawing.Color> edgeColors = [];
|
||||
List<Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0)
|
||||
@@ -169,33 +111,30 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Right, myRect.Top, (int)edgeThickness.Right, height));
|
||||
|
||||
// 合并四边平均色
|
||||
if (edgeColors.Count == 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
if (edgeColors.Count == 0) return Colors.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(
|
||||
return Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
(byte)(r / edgeColors.Count),
|
||||
(byte)(g / edgeColors.Count),
|
||||
(byte)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return System.Drawing.Color.Transparent;
|
||||
return Colors.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
private static System.Drawing.Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
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);
|
||||
using System.Drawing.Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using var gDest = System.Drawing.Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
@@ -208,7 +147,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static System.Drawing.Color ComputeAverageColor(Bitmap bmp)
|
||||
private static Color ComputeAverageColor(System.Drawing.Bitmap bmp)
|
||||
{
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
@@ -225,8 +164,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return System.Drawing.Color.Transparent;
|
||||
return System.Drawing.Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
if (count == 0) return Colors.Transparent;
|
||||
return Color.FromArgb(255, (byte)(r / count), (byte)(g / count), (byte)(b / count));
|
||||
}
|
||||
|
||||
public static Color FromVector3(Vector3 vector3) => Color.FromArgb(255, (byte)vector3.X, (byte)vector3.Y, (byte)vector3.Z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
public static T GetNext<T>(this T value) where T : struct, Enum
|
||||
{
|
||||
T[] values = Enum.GetValues<T>();
|
||||
int currentIndex = Array.IndexOf(values, value);
|
||||
int nextIndex = (currentIndex + 1) % values.Length;
|
||||
return values[nextIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -35,12 +39,17 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string? ReadLyricsCache(string title, string artist, string album, LyricsFormat format, string cacheFolderPath)
|
||||
public static LyricsSearchResult? ReadLyricsCache(SongInfo songInfo, LyricsSearchProvider lyricsSearchProvider)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title} - {album}{format.ToFileExtension()}"));
|
||||
var cacheFilePath = Path.Combine(
|
||||
lyricsSearchProvider.GetCacheDirectory(),
|
||||
SanitizeFileName($"{songInfo.ToFileName()}.json"));
|
||||
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
var json = File.ReadAllText(cacheFilePath);
|
||||
var data = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LyricsSearchResult);
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -55,29 +64,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteLyricsCache(string title, string artist, string album, string lyrics, LyricsFormat format, string cacheFolderPath)
|
||||
public static void WriteLyricsCache(SongInfo songInfo, LyricsSearchResult lyricsSearchResult)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title} - {album}{format.ToFileExtension()}"));
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
var cacheFilePath = Path.Combine(
|
||||
lyricsSearchResult.Provider.GetCacheDirectory(),
|
||||
SanitizeFileName($"{songInfo.ToFileName()}.json"));
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(lyricsSearchResult, SourceGenerationContext.Default.LyricsSearchResult);
|
||||
File.WriteAllText(cacheFilePath, json);
|
||||
}
|
||||
|
||||
public static void WriteAlbumArtCache(string album, string artist, byte[] img, string format, string cacheFolderPath)
|
||||
public static void WriteAlbumArtCache(SongInfo songInfo, byte[] img, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.DisplayArtists} - {songInfo.Album}{format}"));
|
||||
File.WriteAllBytes(cacheFilePath, img);
|
||||
}
|
||||
|
||||
public static bool IsSwitchableNormalizedMatch(string fileName, string q1, string q2)
|
||||
{
|
||||
var normFileName = StringHelper.Normalize(fileName);
|
||||
var normQ1 = StringHelper.Normalize(q1);
|
||||
var normQ2 = StringHelper.Normalize(q2);
|
||||
|
||||
// 常见两种顺序
|
||||
return normFileName == normQ1 + normQ2
|
||||
|| normFileName == normQ2 + normQ1;
|
||||
}
|
||||
|
||||
public static readonly string[] MusicExtensions = {
|
||||
".mp3", ".aac", ".m4a", ".ogg", ".opus", ".wma", ".amr",
|
||||
".flac", ".alac", ".ape", ".wv", ".tak",
|
||||
|
||||
@@ -4,10 +4,9 @@ using BetterLyrics.WinUI3.Enums;
|
||||
using Impressionist.Abstractions;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
@@ -18,29 +17,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
using var writer = new DataWriter(stream);
|
||||
writer.WriteBytes(bytes);
|
||||
writer.StoreAsync().GetAwaiter().GetResult();
|
||||
writer.FlushAsync().GetAwaiter().GetResult();
|
||||
writer.DetachStream();
|
||||
return RandomAccessStreamReference.CreateFromStream(stream);
|
||||
}
|
||||
|
||||
public static async Task<IRandomAccessStream> GetAlbumArtPlaceholderAsync()
|
||||
{
|
||||
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(PathHelper.AlbumArtPlaceholderPath));
|
||||
StorageFile file = await StorageFile.GetFileFromPathAsync(PathHelper.AlbumArtPlaceholderPath);
|
||||
IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
|
||||
return stream;
|
||||
}
|
||||
@@ -65,59 +44,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
{
|
||||
var pixelDataProvider = await bitmapDecoder.GetPixelDataAsync();
|
||||
var pixels = pixelDataProvider.DetachPixelData();
|
||||
var count = bitmapDecoder.PixelWidth * bitmapDecoder.PixelHeight;
|
||||
var vector = new Dictionary<Vector3, int>();
|
||||
for (int i = 0; i < count; i += 10)
|
||||
{
|
||||
var offset = i * 4;
|
||||
var b = pixels[offset];
|
||||
var g = pixels[offset + 1];
|
||||
var r = pixels[offset + 2];
|
||||
var a = pixels[offset + 3];
|
||||
if (a == 0) continue;
|
||||
var color = new Vector3(r, g, b);
|
||||
if (vector.ContainsKey(color))
|
||||
{
|
||||
vector[color]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector[color] = 1;
|
||||
}
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// var stream = new InMemoryRandomAccessStream();
|
||||
// await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
// stream.Seek(0);
|
||||
|
||||
// var bitmapImage = new BitmapImage();
|
||||
// await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
// 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;
|
||||
//}
|
||||
|
||||
public static async Task<IBuffer> ToBufferAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
@@ -127,26 +53,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static double 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 (double)(sum / (pixels.Length / 4));
|
||||
}
|
||||
|
||||
public static async Task<BitmapDecoder> MakeSquareWithThemeColor(IBuffer buffer, PaletteGeneratorType generatorType)
|
||||
{
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(buffer);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
@@ -183,69 +91,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
}
|
||||
|
||||
public static async Task<IBuffer> Resize(IBuffer buffer, int size)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(buffer);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
|
||||
var factor = Math.Max((double)size / decoder.PixelWidth, (double)size / decoder.PixelHeight);
|
||||
|
||||
var width = (uint)(decoder.PixelWidth * factor);
|
||||
var height = (uint)(decoder.PixelHeight * factor);
|
||||
|
||||
if (factor > 1)
|
||||
{
|
||||
var transform = new BitmapTransform()
|
||||
{
|
||||
ScaledWidth = width,
|
||||
ScaledHeight = height,
|
||||
InterpolationMode = BitmapInterpolationMode.Fant
|
||||
};
|
||||
var pixelData = await decoder.GetPixelDataAsync(
|
||||
BitmapPixelFormat.Rgba8,
|
||||
BitmapAlphaMode.Straight,
|
||||
transform, ExifOrientationMode.RespectExifOrientation,
|
||||
ColorManagementMode.ColorManageToSRgb);
|
||||
var pixels = pixelData.DetachPixelData();
|
||||
|
||||
stream.Seek(0);
|
||||
stream.Size = 0;
|
||||
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
|
||||
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96, 96, pixels);
|
||||
await encoder.FlushAsync();
|
||||
var output = new Windows.Storage.Streams.Buffer((uint)stream.Size);
|
||||
stream.Seek(0);
|
||||
await stream.ReadAsync(output, (uint)stream.Size, InputStreamOptions.None);
|
||||
return output;
|
||||
}
|
||||
else
|
||||
{
|
||||
var transform = new BitmapTransform()
|
||||
{
|
||||
ScaledWidth = (uint)width,
|
||||
ScaledHeight = (uint)height,
|
||||
InterpolationMode = BitmapInterpolationMode.NearestNeighbor
|
||||
};
|
||||
var pixelData = await decoder.GetPixelDataAsync(
|
||||
BitmapPixelFormat.Rgba8,
|
||||
BitmapAlphaMode.Straight,
|
||||
transform, ExifOrientationMode.RespectExifOrientation,
|
||||
ColorManagementMode.ColorManageToSRgb);
|
||||
var pixels = pixelData.DetachPixelData();
|
||||
|
||||
stream.Seek(0);
|
||||
stream.Size = 0;
|
||||
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
|
||||
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96, 96, pixels);
|
||||
await encoder.FlushAsync();
|
||||
var output = new Windows.Storage.Streams.Buffer((uint)stream.Size);
|
||||
stream.Seek(0);
|
||||
await stream.ReadAsync(output, (uint)stream.Size, InputStreamOptions.None);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] GenerateNoiseBGRA(int width, int height)
|
||||
{
|
||||
var random = new Random();
|
||||
@@ -335,5 +180,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IRandomAccessStream ToIRandomAccessStream(IBuffer buffer)
|
||||
{
|
||||
return buffer.AsStream().AsRandomAccessStream();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Lyricify.Lyrics.Parsers;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,92 +17,48 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public List<LyricsData> LyricsDataArr { get; private set; } = [];
|
||||
|
||||
public void Parse(List<MappedSongSearchQuery> mappedSongSearchQueries, string title, string artist, string album, string? raw, int? durationMs, LyricsSearchProvider? lyricsSearchProvider)
|
||||
public void Parse(SongInfo? songInfo, LyricsSearchResult? lyricsSearchResult)
|
||||
{
|
||||
var overridenTitle = title;
|
||||
var overridenArtist = artist;
|
||||
var overridenAlbum = album;
|
||||
|
||||
var found = mappedSongSearchQueries
|
||||
.Where(x => x.OriginalTitle == overridenTitle && x.OriginalArtist == overridenArtist && x.OriginalAlbum == overridenAlbum)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
overridenTitle = found.MappedTitle;
|
||||
overridenArtist = found.MappedArtist;
|
||||
overridenAlbum = found.MappedAlbum;
|
||||
}
|
||||
|
||||
LyricsDataArr = [];
|
||||
durationMs ??= (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
if (raw == null)
|
||||
if (lyricsSearchResult?.Raw == null)
|
||||
{
|
||||
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder(durationMs.Value));
|
||||
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder((int)(songInfo?.DurationMs ?? 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (raw.DetectFormat())
|
||||
switch (lyricsSearchResult.Raw.DetectFormat())
|
||||
{
|
||||
case LyricsFormat.Lrc:
|
||||
case LyricsFormat.Eslrc:
|
||||
ParseLrc(raw);
|
||||
ParseLrc(lyricsSearchResult.Raw);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseQrcKrc(QrcParser.Parse(raw).Lines);
|
||||
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseQrcKrc(KrcParser.Parse(raw).Lines);
|
||||
ParseQrcKrc(KrcParser.Parse(lyricsSearchResult.Raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw);
|
||||
ParseTtml(lyricsSearchResult.Raw);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
FillRomanizationLyricsData();
|
||||
FillTranslationFromCache(overridenTitle, overridenArtist, overridenAlbum, lyricsSearchProvider);
|
||||
FillTranslationFromCache(lyricsSearchResult);
|
||||
}
|
||||
|
||||
private void FillTranslationFromCache(string title, string artist, string album, LyricsSearchProvider? provider)
|
||||
private void FillTranslationFromCache(LyricsSearchResult? lyricsSearchResult)
|
||||
{
|
||||
string? translationRaw = null;
|
||||
switch (provider)
|
||||
if (lyricsSearchResult?.Translation != null)
|
||||
{
|
||||
case LyricsSearchProvider.QQ:
|
||||
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.KugouTranslationCacheDirectory);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
|
||||
break;
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalMusicFile:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalLrcFile:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalEslrcFile:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalTtmlFile:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (translationRaw != null)
|
||||
{
|
||||
switch (provider)
|
||||
switch (lyricsSearchResult.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.QQ:
|
||||
case LyricsSearchProvider.Kugou:
|
||||
case LyricsSearchProvider.Netease:
|
||||
ParseLrc(translationRaw);
|
||||
ParseLrc(lyricsSearchResult.Translation);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -110,7 +68,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
private void FillRomanizationLyricsData()
|
||||
{
|
||||
var chinese = LyricsDataArr.Where(x => x.LanguageCode == "zh").FirstOrDefault();
|
||||
var chinese = LyricsDataArr.FirstOrDefault(x => x.LanguageCode == "zh");
|
||||
if (chinese != null)
|
||||
{
|
||||
LyricsDataArr.Add(new LyricsData
|
||||
@@ -148,7 +106,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
var japanese = LyricsDataArr.Where(x => x.LanguageCode == "ja").FirstOrDefault();
|
||||
var japanese = LyricsDataArr.FirstOrDefault(x => x.LanguageCode == "ja");
|
||||
if (japanese != null)
|
||||
{
|
||||
LyricsDataArr.Add(new LyricsData
|
||||
@@ -215,12 +173,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int? lineStartTime = null;
|
||||
if (bracketMatches.Count > 0)
|
||||
{
|
||||
var m = bracketMatches![0];
|
||||
var m = bracketMatches[0];
|
||||
int min = int.Parse(m.Groups[1].Value);
|
||||
int sec = int.Parse(m.Groups[2].Value);
|
||||
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
|
||||
lineStartTime = min * 60_000 + sec * 1000 + ms;
|
||||
content = bracketRegex!.Replace(line, "");
|
||||
content = bracketRegex!.Replace(line, "").Trim();
|
||||
if (content == "//") content = "";
|
||||
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using F23.StringSimilarity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class MetadataComparer
|
||||
{
|
||||
// 权重配置 (总和 1.0)
|
||||
private const double WeightTitle = 0.40;
|
||||
private const double WeightArtist = 0.40;
|
||||
private const double WeightAlbum = 0.10;
|
||||
private const double WeightDuration = 0.10;
|
||||
|
||||
// 实例化算法 (JaroWinkler 适合短字符串匹配)
|
||||
private static readonly JaroWinkler _algo = new JaroWinkler();
|
||||
|
||||
/// <summary>
|
||||
/// 计算 SongInfo 和 LyricsSearchResult 的相似度 (0-100)
|
||||
/// </summary>
|
||||
public static int CalculateScore(SongInfo local, LyricsSearchResult remote)
|
||||
{
|
||||
if (local == null || remote == null) return 0;
|
||||
|
||||
// 1. 标题相似度
|
||||
double titleScore = GetStringSimilarity(local.Title, remote.Title);
|
||||
|
||||
// 2. 艺术家相似度 (需要处理数组顺序)
|
||||
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
|
||||
|
||||
// 3. 专辑相似度
|
||||
double albumScore = GetStringSimilarity(local.Album, remote.Album);
|
||||
|
||||
// 4. 时长相似度 (基于毫秒 vs 秒的转换和容差)
|
||||
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
|
||||
|
||||
// 5. 加权汇总
|
||||
double totalScore = (titleScore * WeightTitle) +
|
||||
(artistScore * WeightArtist) +
|
||||
(albumScore * WeightAlbum) +
|
||||
(durationScore * WeightDuration);
|
||||
|
||||
return (int)Math.Round(totalScore * 100);
|
||||
}
|
||||
|
||||
private static double GetStringSimilarity(string? s1, string? s2)
|
||||
{
|
||||
// 归一化:转小写,去空白
|
||||
s1 = s1?.Trim().ToLowerInvariant() ?? "";
|
||||
s2 = s2?.Trim().ToLowerInvariant() ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty(s1) && string.IsNullOrEmpty(s2)) return 1.0; // 都是空,视为匹配
|
||||
if (string.IsNullOrEmpty(s1) || string.IsNullOrEmpty(s2)) return 0.0; // 其中一个为空
|
||||
|
||||
return _algo.Similarity(s1, s2);
|
||||
}
|
||||
|
||||
private static double GetArtistSimilarity(string[]? localArtists, string[]? remoteArtists)
|
||||
{
|
||||
if (localArtists == null || localArtists.Length == 0) return 0.0;
|
||||
if (remoteArtists == null || remoteArtists.Length == 0) return 0.0;
|
||||
|
||||
// 技巧:将艺术家数组排序并连接,避免顺序不同导致的不匹配
|
||||
// 例如: ["Jay-Z", "Linkin Park"] 和 ["Linkin Park", "Jay-Z"] 应该是一样的
|
||||
var s1 = string.Join(" ", localArtists.OrderBy(a => a).Select(a => a.Trim().ToLowerInvariant()));
|
||||
var s2 = string.Join(" ", remoteArtists.OrderBy(a => a).Select(a => a.Trim().ToLowerInvariant()));
|
||||
|
||||
return _algo.Similarity(s1, s2);
|
||||
}
|
||||
|
||||
private static double GetDurationSimilarity(double localMs, double? remoteSeconds)
|
||||
{
|
||||
if (remoteSeconds == null || remoteSeconds == 0) return 0.0; // 远程没有时长数据,不匹配
|
||||
|
||||
double localSeconds = localMs / 1000.0;
|
||||
double diff = Math.Abs(localSeconds - remoteSeconds.Value);
|
||||
|
||||
// 容差逻辑:
|
||||
// 差距 <= 3秒:100% 相似
|
||||
// 差距 >= 20秒:0% 相似
|
||||
// 中间线性插值
|
||||
|
||||
const double PerfectTolerance = 3.0;
|
||||
const double MaxTolerance = 20.0;
|
||||
|
||||
if (diff <= PerfectTolerance) return 1.0;
|
||||
if (diff >= MaxTolerance) return 0.0;
|
||||
|
||||
// 线性递减公式
|
||||
return 1.0 - ((diff - PerfectTolerance) / (MaxTolerance - PerfectTolerance));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class MetadataHelper
|
||||
{
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
public static string AppVersion => Package.Current.Id.Version.ToFormattedString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ObjectHelper
|
||||
{
|
||||
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
|
||||
public static bool IsDisposed(this IDisposable obj)
|
||||
{
|
||||
/*
|
||||
TIM C: This hacky code is because MSFT does not provide a standard way to interrogate if an object is disposed or not.
|
||||
I wrote this based upon streams, but it should work for many other types of MSFT objects (maybe).
|
||||
*/
|
||||
if (obj == null) { return true; }
|
||||
|
||||
var objType = obj.GetType();
|
||||
//var foo = new System.IO.BufferedStream();
|
||||
|
||||
// the _disposed pattern should catch a lot of msft objects.... hopefully
|
||||
var isDisposedField = objType.GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance) ??
|
||||
objType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
isDisposedField = objType.GetField("_isOpen", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
// Windows.Graphics.Imaging.SoftwareBitmap
|
||||
isDisposedField = objType.GetField("_objRef_global__System_IDisposable", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
// System.IO.FileStream
|
||||
var strategyField = objType.GetField("_strategy", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (strategyField != null)
|
||||
{
|
||||
var strategy = strategyField.GetValue(obj);
|
||||
var isClosedField = strategy.GetType().GetProperty("IsClosed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isClosedField != null) { return Convert.ToBoolean(isClosedField.GetValue(strategy)); }
|
||||
}
|
||||
|
||||
// other streams that use this pattern to determine if they are disposed
|
||||
if (obj is Stream stream) { return !stream.CanRead && !stream.CanWrite; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Wiki82.profile.xml");
|
||||
public static string LogoPath => Path.Combine(AssetsFolder, "Logo.ico");
|
||||
public static string AlbumArtPlaceholderPath => "ms-appx:///Assets/AlbumArtPlaceholder.png";
|
||||
public static string AlbumArtPlaceholderPath => Path.Combine(AssetsFolder, "AlbumArtPlaceholder.png");
|
||||
public static string AIMPLogoPath => Path.Combine(AssetsFolder, "AIMP.png");
|
||||
public static string Foobar2000LogoPath => Path.Combine(AssetsFolder, "foobar2000.png");
|
||||
public static string MusicBeeLogoPath => Path.Combine(AssetsFolder, "MusicBee.png");
|
||||
@@ -45,14 +45,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
|
||||
public static string AppleMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "apple-music");
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.json");
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.jsonl");
|
||||
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
|
||||
|
||||
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
|
||||
public static string KugouTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "kugou");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
public static string iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
|
||||
|
||||
@@ -71,11 +66,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AppleMusicCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(KugouTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using DevWinUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
@@ -10,7 +12,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static async Task<StorageFolder?> PickSingleFolderAsync<T>()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<T>();
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
if (window == null) return null;
|
||||
|
||||
var picker = new Windows.Storage.Pickers.FolderPicker();
|
||||
@@ -26,7 +28,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static async Task<StorageFile?> PickSingleFileAsync<T>(string[] fileTypeFilter)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<T>();
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
if (window == null) return null;
|
||||
|
||||
var picker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
@@ -42,7 +44,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(IDictionary<string, IList<string>> fileTypeChoices)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<T>();
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
if (window == null) return null;
|
||||
|
||||
var picker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using Lyricify.Lyrics.Providers;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PlayerIDHelper
|
||||
{
|
||||
private static readonly List<string> neteaseFamilyRegex =
|
||||
[
|
||||
"cloudmusic.exe", //NetEaseCloudMusic
|
||||
"^17588BrandonWong\\.LyricEase_", //LyricEase
|
||||
"^48848aaaaaaccd\\.HyPlayer_" //HyPlayer
|
||||
];
|
||||
|
||||
public static bool IsNeteaseFamily(string? id)
|
||||
{
|
||||
if (id is null) return false;
|
||||
|
||||
foreach (var regex in neteaseFamilyRegex)
|
||||
{
|
||||
var isMatch = Regex.IsMatch(id, regex);
|
||||
if (isMatch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsLXMusic(string? id) => id is PlayerID.LXMusic or PlayerID.LXMusicPortable;
|
||||
|
||||
public static bool IsAppleMusic(string? id) => id is PlayerID.AppleMusic or PlayerID.AppleMusicAlternative;
|
||||
|
||||
public static string? GetDisplayName(string? id) => id switch
|
||||
{
|
||||
PlayerID.Spotify => PlayerName.Spotify,
|
||||
PlayerID.AppleMusic => PlayerName.AppleMusic,
|
||||
PlayerID.iTunes => PlayerName.iTunes,
|
||||
PlayerID.KugouMusic => PlayerName.KugouMusic,
|
||||
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
||||
PlayerID.QQMusic => PlayerName.QQMusic,
|
||||
PlayerID.LXMusic => PlayerName.LXMusic,
|
||||
PlayerID.LXMusicPortable => PlayerName.LXMusicPortable,
|
||||
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
||||
PlayerID.AIMP => PlayerName.AIMP,
|
||||
PlayerID.Foobar2000 => PlayerName.Foobar2000,
|
||||
PlayerID.MusicBee => PlayerName.MusicBee,
|
||||
PlayerID.PotPlayer => PlayerName.PotPlayer,
|
||||
PlayerID.Chrome => PlayerName.Chrome,
|
||||
PlayerID.Edge => PlayerName.Edge,
|
||||
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
|
||||
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
||||
PlayerID.SaltPlayerForWindows => PlayerName.SaltPlayerForWindows,
|
||||
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
|
||||
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
|
||||
PlayerID.Listen1 => PlayerName.Listen1,
|
||||
_ => id,
|
||||
};
|
||||
|
||||
public static string GetLogoPath(string? id) => id switch
|
||||
{
|
||||
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
|
||||
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.iTunes => PathHelper.iTunesLogoPath,
|
||||
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
|
||||
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
||||
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
|
||||
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.LXMusicPortable => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
||||
PlayerID.AIMP => PathHelper.AIMPLogoPath,
|
||||
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
||||
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
|
||||
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
|
||||
PlayerID.Chrome => PathHelper.ChromeLogoPath,
|
||||
PlayerID.Edge => PathHelper.EdgeLogoPath,
|
||||
PlayerID.BetterLyrics => PathHelper.LogoPath,
|
||||
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
|
||||
PlayerID.SaltPlayerForWindows => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerID.Listen1 => PathHelper.Listen1LogoPath,
|
||||
_ => PathHelper.UnknownPlayerLogoPath,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PlayerIdMatcher
|
||||
{
|
||||
private static readonly List<string> neteaseFamilyRegex =
|
||||
[
|
||||
"cloudmusic.exe", //NetEaseCloudMusic
|
||||
"^17588BrandonWong\\.LyricEase_", //LyricEase
|
||||
"^48848aaaaaaccd\\.HyPlayer_" //HyPlayer
|
||||
];
|
||||
|
||||
public static bool IsNeteaseFamily(string id)
|
||||
{
|
||||
foreach (var regex in neteaseFamilyRegex)
|
||||
{
|
||||
var isMatch = Regex.IsMatch(id, regex);
|
||||
if (isMatch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsLXMusic(string? id)
|
||||
{
|
||||
return id == Constants.PlayerID.LXMusic || id == Constants.PlayerID.LXMusicPortable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PointHelper
|
||||
{
|
||||
public static PointInt32 ToPointInt32(this Point point)
|
||||
{
|
||||
return new PointInt32((int)point.X, (int)point.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class RectHelper
|
||||
{
|
||||
public static RectInt32 ToRectInt32(this Windows.Foundation.Rect rect)
|
||||
{
|
||||
return new RectInt32(
|
||||
(int)rect.X,
|
||||
(int)rect.Y,
|
||||
(int)rect.Width,
|
||||
(int)rect.Height
|
||||
);
|
||||
}
|
||||
|
||||
public static Windows.Foundation.Rect WithHeight(this Windows.Foundation.Rect rect, double height)
|
||||
{
|
||||
return new Windows.Foundation.Rect(
|
||||
rect.X,
|
||||
rect.Y,
|
||||
rect.Width,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
public static Windows.Foundation.Rect WithWidth(this Windows.Foundation.Rect rect, double width)
|
||||
{
|
||||
return new Windows.Foundation.Rect(
|
||||
rect.X,
|
||||
rect.Y,
|
||||
width,
|
||||
rect.Height
|
||||
);
|
||||
}
|
||||
|
||||
public static Windows.Foundation.Rect WithX(this Windows.Foundation.Rect rect, double x)
|
||||
{
|
||||
return new Windows.Foundation.Rect(
|
||||
x,
|
||||
rect.Y,
|
||||
rect.Width,
|
||||
rect.Height
|
||||
);
|
||||
}
|
||||
|
||||
public static Windows.Foundation.Rect WithY(this Windows.Foundation.Rect rect, double y)
|
||||
{
|
||||
return new Windows.Foundation.Rect(
|
||||
rect.X,
|
||||
y,
|
||||
rect.Width,
|
||||
rect.Height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class STATaskHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 在一个专用的后台 STA 线程上运行一个函数。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">返回类型</typeparam>
|
||||
/// <param name="func">要执行的函数</param>
|
||||
/// <returns>一个 Task,其结果是函数的返回值</returns>
|
||||
public static Task<TResult> RunAsSTATask<TResult>(Func<TResult> func)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<TResult>();
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = func();
|
||||
tcs.SetResult(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
tcs.SetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
// (关键) 设置单元状态为 STA
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在一个专用的后台 STA 线程上运行一个 Action。
|
||||
/// </summary>
|
||||
/// <param name="action">要执行的 Action</param>
|
||||
/// <returns>一个 Task</returns>
|
||||
public static Task RunAsSTATask(Action action)
|
||||
{
|
||||
return RunAsSTATask(() =>
|
||||
{
|
||||
action();
|
||||
return true; // 返回一个虚拟结果
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
.ToArray())
|
||||
.ToLowerInvariant();
|
||||
public static string NewLine = "\n";
|
||||
|
||||
public static bool IsSwitchableNormalizedMatch(string source, string q1, string q2)
|
||||
{
|
||||
var normFileName = Normalize(source);
|
||||
var normQ1 = Normalize(q1);
|
||||
var normQ2 = Normalize(q2);
|
||||
|
||||
// 常见两种顺序
|
||||
return normFileName == normQ1 + normQ2
|
||||
|| normFileName == normQ2 + normQ1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class TrackHelper
|
||||
{
|
||||
public static string GetParentFolderName(this ATL.Track track)
|
||||
{
|
||||
return Directory.GetParent(track.Path)?.Name ?? "";
|
||||
}
|
||||
|
||||
public static string GetParentFolderPath(this ATL.Track track)
|
||||
{
|
||||
return Directory.GetParent(track.Path)?.FullName ?? "";
|
||||
}
|
||||
|
||||
public static string GetLyrics(this ATL.Track track)
|
||||
{
|
||||
if (track.Path is string path)
|
||||
{
|
||||
return TagLib.File.Create(path).Tag.Lyrics;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class VectorHelper
|
||||
{
|
||||
public static Vector2 WithX(this Vector2 source, float x)
|
||||
{
|
||||
return new Vector2(x, source.Y);
|
||||
}
|
||||
|
||||
public static Vector2 WithY(this Vector2 source, float y)
|
||||
{
|
||||
return new Vector2(source.X, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Hooks/AppHook.cs
Normal file
82
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Hooks/AppHook.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using DevWinUI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Vanara.Windows.Shell;
|
||||
using static Vanara.PInvoke.Shell32;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Hooks
|
||||
{
|
||||
public class AppHook
|
||||
{
|
||||
public static HICON? GetIcon(ShellItem shellItem, int size = 32)
|
||||
{
|
||||
HICON hIconCopy = HICON.NULL;
|
||||
|
||||
try
|
||||
{
|
||||
using Bitmap baseIcon = shellItem.GetImage(new SIZE { Height = size, Width = size }, ShellItemGetImageOptions.ResizeToFit).ToBitmap();
|
||||
hIconCopy = User32.CopyIcon(baseIcon.GetHicon());
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
if (hIconCopy.IsNull)
|
||||
{
|
||||
User32.DestroyIcon(hIconCopy);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return hIconCopy;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<BitmapImage?> ToBitmapImageAsync(HICON hIcon)
|
||||
{
|
||||
if (hIcon.IsNull)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using Icon icon = Icon.FromHandle(hIcon.DangerousGetHandle());
|
||||
using Bitmap bitmap = icon.ToBitmap();
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
bitmap.Save(memoryStream, ImageFormat.Png);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(memoryStream.AsRandomAccessStream());
|
||||
|
||||
User32.DestroyIcon(hIcon);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
public static ShellItem? GetShellItem(string aumid)
|
||||
{
|
||||
string path = $"shell:AppsFolder\\{aumid}";
|
||||
if (Path.Exists(path))
|
||||
{
|
||||
return new ShellItem(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
var shellFolder = new ShellFolder(KNOWNFOLDERID.FOLDERID_AppsFolder);
|
||||
var found = shellFolder.FirstOrDefault(x => x.ParsingName?.EndsWith(aumid) == true);
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetDisplayName(ShellItem shellItem) => shellItem.GetDisplayName(ShellItemDisplayString.NormalDisplay);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
namespace BetterLyrics.WinUI3.Hooks
|
||||
{
|
||||
public class ForegroundWindowWatcher
|
||||
public class ForegroundWindowHook
|
||||
{
|
||||
private readonly User32.WinEventProc _winEventDelegate;
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
@@ -17,7 +17,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
public ForegroundWindowWatcher(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
public ForegroundWindowHook(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
_onWindowChanged = onWindowChanged;
|
||||
@@ -5,9 +5,9 @@ using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
namespace BetterLyrics.WinUI3.Hooks
|
||||
{
|
||||
public class GlobalHotKeyHelper
|
||||
public class GlobalHotKeyHook
|
||||
{
|
||||
private static Dictionary<int, Action> _actions = [];
|
||||
private static Dictionary<int, List<string>> _keys = [];
|
||||
@@ -23,7 +23,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
if (keys.Count == 0) return;
|
||||
|
||||
var window = WindowHelper.GetWindowByWindowType<T>();
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
if (window == null) return;
|
||||
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -62,7 +62,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
private static void UnregisterHotKey<T>(ShortcutID id)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<T>();
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
if (window == null) return;
|
||||
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -5,9 +5,9 @@ using System.Runtime.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
namespace BetterLyrics.WinUI3.Hooks
|
||||
{
|
||||
public static class MonitorHelper
|
||||
public static class MonitorHook
|
||||
{
|
||||
public static IEnumerable<string> GetAllMonitorDeviceNames()
|
||||
{
|
||||
@@ -2,9 +2,9 @@
|
||||
using NAudio.CoreAudioApi;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
namespace BetterLyrics.WinUI3.Hooks
|
||||
{
|
||||
public static class SystemVolumeHelper
|
||||
public static class SystemVolumeHook
|
||||
{
|
||||
private static MMDeviceEnumerator? _deviceEnumerator;
|
||||
private static MMDevice? _defaultDevice;
|
||||
@@ -15,7 +15,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// </summary>
|
||||
public static event EventHandler<int>? VolumeNotification;
|
||||
|
||||
static SystemVolumeHelper()
|
||||
static SystemVolumeHook()
|
||||
{
|
||||
_deviceEnumerator = new MMDeviceEnumerator();
|
||||
_defaultDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
||||
@@ -1,6 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
@@ -13,14 +14,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
using Vanara.Windows.Shell;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
namespace BetterLyrics.WinUI3.Hooks
|
||||
{
|
||||
public static class WindowHelper
|
||||
public static class WindowHook
|
||||
{
|
||||
private static List<object> _activeWindows = [];
|
||||
private static List<object> _workAreas = [];
|
||||
@@ -54,7 +56,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static T? GetWindowByWindowType<T>()
|
||||
public static T? GetWindow<T>()
|
||||
{
|
||||
foreach (var window in _activeWindows)
|
||||
{
|
||||
@@ -72,7 +74,19 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
return frameworkElement.XamlRoot.ContentIslandEnvironment.AppWindowId.GetWindowHandle();
|
||||
}
|
||||
return null;
|
||||
else if (obj != null)
|
||||
{
|
||||
return WindowNative.GetWindowHandle(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr? GetWindowHandle<T>()
|
||||
{
|
||||
return GetWindowHandle(GetWindow<T>());
|
||||
}
|
||||
|
||||
public static void OpenOrShowWindow<T>()
|
||||
@@ -123,6 +137,17 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
lyricsWindow.ViewModel.InitFgWindowWatcher();
|
||||
|
||||
_mediaSessionsService.InitPlaybackShortcuts();
|
||||
|
||||
//TaskbarList.ThumbBarAddButtons(hwnd,
|
||||
// [
|
||||
// new Shell32.THUMBBUTTON()
|
||||
// {
|
||||
// szTip = "Previous",
|
||||
// dwFlags = Shell32.THUMBBUTTONFLAGS.THBF_ENABLED,
|
||||
// dwMask = Shell32.THUMBBUTTONMASK.THB_TOOLTIP | Shell32.THUMBBUTTONMASK.THB_FLAGS,
|
||||
// }
|
||||
// ]
|
||||
//);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -188,7 +213,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void SetIsClickThrough<T>(bool enable)
|
||||
{
|
||||
Window? window = GetWindowByWindowType<T>() as Window;
|
||||
Window? window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -205,7 +230,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void SetIsWorkArea<T>(bool enable)
|
||||
{
|
||||
Window? window = GetWindowByWindowType<T>() as Window;
|
||||
Window? window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -222,7 +247,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void SetIsBorderless<T>(bool enable)
|
||||
{
|
||||
var window = GetWindowByWindowType<T>() as Window;
|
||||
var window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -239,7 +264,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void SetIsShowInSwitchers<T>(bool enable)
|
||||
{
|
||||
var window = GetWindowByWindowType<T>() as Window;
|
||||
var window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
window.AppWindow.IsShownInSwitchers = enable;
|
||||
@@ -247,7 +272,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void SetIsAlwaysOnTop<T>(bool enable)
|
||||
{
|
||||
var window = GetWindowByWindowType<T>() as Window;
|
||||
var window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
if (window.AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
@@ -258,7 +283,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void MoveAndResize<T>(Rect rect)
|
||||
{
|
||||
var window = GetWindowByWindowType<T>() as Window;
|
||||
var window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
window.AppWindow.Move(new Windows.Graphics.PointInt32((int)rect.X, (int)rect.Y));
|
||||
@@ -269,7 +294,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
LyricsWindow? lyricsWindow = GetWindowByWindowType<LyricsWindow>();
|
||||
LyricsWindow? lyricsWindow = GetWindow<LyricsWindow>();
|
||||
lyricsWindow?.SetTitleBarArea(titleBarArea);
|
||||
}
|
||||
else
|
||||
@@ -326,7 +351,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void UpdateWorkArea<T>()
|
||||
{
|
||||
var window = GetWindowByWindowType<T>() as Window;
|
||||
var window = GetWindow<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -370,10 +395,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
_setLyricsWindowVisibilityByPlayingStatusTimer.Debounce(() =>
|
||||
{
|
||||
var window = GetWindowByWindowType<LyricsWindow>();
|
||||
var window = GetWindow<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.CurrentIsPlaying)
|
||||
{
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
@@ -383,7 +408,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
HideWindow<LyricsWindow>();
|
||||
}
|
||||
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
|
||||
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.CurrentIsPlaying)
|
||||
{
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
@@ -1,6 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
|
||||
@@ -1,16 +1,51 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LyricsSearchResult
|
||||
public partial class LyricsSearchResult : ObservableObject, ICloneable
|
||||
{
|
||||
public bool IsFound => !string.IsNullOrEmpty(Raw);
|
||||
public LyricsSearchProvider? Provider { get; set; }
|
||||
public LyricsSearchProvider Provider { get; set; }
|
||||
|
||||
public string? Raw { get; set; }
|
||||
public string? Translation { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
public string? Artist { get; set; }
|
||||
public string[]? Artists { get; set; }
|
||||
public string? Album { get; set; }
|
||||
public double? Duration { get; set; }
|
||||
[ObservableProperty] public partial int MatchPercentage { get; set; } = -1;
|
||||
[ObservableProperty] public partial string Reference { get; set; } = "about:blank";
|
||||
|
||||
public bool IsFound => !string.IsNullOrEmpty(Raw);
|
||||
|
||||
public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
|
||||
|
||||
public string? DisplayArtists => Artists?.Join("; ");
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new LyricsSearchResult()
|
||||
{
|
||||
Album = this.Album,
|
||||
Duration = this.Duration,
|
||||
Raw = this.Raw,
|
||||
Translation = this.Translation,
|
||||
Title = this.Title,
|
||||
Artists = this.Artists,
|
||||
MatchPercentage = this.MatchPercentage,
|
||||
Provider = this.Provider,
|
||||
Reference = this.Reference
|
||||
};
|
||||
}
|
||||
|
||||
public void CopyFromSongInfo(SongInfo songInfo)
|
||||
{
|
||||
Title = songInfo.Title;
|
||||
Artists = songInfo.Artists;
|
||||
Album = songInfo.Album;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
|
||||
@@ -104,10 +102,10 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public void UpdateMonitorNameAndBounds()
|
||||
{
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
var lyricsWindow = WindowHook.GetWindow<LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
|
||||
var mointor = MonitorHelper.GetMonitorInfoExFromWindow(lyricsWindow);
|
||||
var mointor = MonitorHook.GetMonitorInfoExFromWindow(lyricsWindow);
|
||||
MonitorDeviceName = mointor.szDevice;
|
||||
MonitorBounds = new Rect(
|
||||
mointor.rcMonitor.Left,
|
||||
@@ -119,7 +117,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public void UpdateMonitorBounds()
|
||||
{
|
||||
var mointor = MonitorHelper.GetMonitorInfoExFromDeviceName(MonitorDeviceName);
|
||||
var mointor = MonitorHook.GetMonitorInfoExFromDeviceName(MonitorDeviceName);
|
||||
MonitorBounds = new Rect(
|
||||
mointor.rcMonitor.Left,
|
||||
mointor.rcMonitor.Top,
|
||||
@@ -200,98 +198,4 @@ namespace BetterLyrics.WinUI3.Models
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class LyricsWindowStatusExtensions
|
||||
{
|
||||
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
|
||||
|
||||
public static LyricsWindowStatus DesktopMode()
|
||||
{
|
||||
return new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("DesktopMode"),
|
||||
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
|
||||
WindowBounds = new Rect(100, 100, 600, 250),
|
||||
IsAlwaysOnTop = true,
|
||||
IsAlwaysOnTopPolling = true,
|
||||
IsBorderless = true,
|
||||
IsClickThrough = true,
|
||||
IsAdaptToEnvironment = true,
|
||||
IsShownInSwitchers = false,
|
||||
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
|
||||
LyricsStyleSettings = new()
|
||||
{
|
||||
LyricsAlignmentType = TextAlignmentType.Center,
|
||||
},
|
||||
LyricsBackgroundSettings = new LyricsBackgroundSettings
|
||||
{
|
||||
IsFluidOverlayEnabled = false,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus DockedMode()
|
||||
{
|
||||
var status = new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("DockedMode"),
|
||||
IsWorkArea = true,
|
||||
IsAlwaysOnTop = true,
|
||||
IsAlwaysOnTopPolling = true,
|
||||
IsBorderless = true,
|
||||
IsAdaptToEnvironment = true,
|
||||
IsShownInSwitchers = false,
|
||||
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
|
||||
EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow,
|
||||
TitleBarArea = TitleBarArea.None,
|
||||
LyricsStyleSettings = new LyricsStyleSettings
|
||||
{
|
||||
LyricsAlignmentType = TextAlignmentType.Center,
|
||||
},
|
||||
LyricsBackgroundSettings = new LyricsBackgroundSettings
|
||||
{
|
||||
IsFluidOverlayEnabled = false,
|
||||
IsPureColorOverlayEnabled = true,
|
||||
}
|
||||
};
|
||||
status.WindowBounds = status.GetWindowBoundsWhenWorkArea();
|
||||
return status;
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus FullscreenMode()
|
||||
{
|
||||
var status = new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("FullscreenMode"),
|
||||
IsBorderless = true,
|
||||
IsAlwaysOnTop = true,
|
||||
TitleBarArea = TitleBarArea.None,
|
||||
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
|
||||
LyricsStyleSettings = new LyricsStyleSettings
|
||||
{
|
||||
LyricsAlignmentType = TextAlignmentType.Center,
|
||||
},
|
||||
};
|
||||
status.WindowBounds = status.MonitorBounds;
|
||||
return status;
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus StandardMode()
|
||||
{
|
||||
return new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("StandardMode"),
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsWindowStatus NarrowMode()
|
||||
{
|
||||
return new LyricsWindowStatus
|
||||
{
|
||||
Name = _resourceService.GetLocalizedString("NarrowMode"),
|
||||
WindowBounds = new Rect(100, 100, 400, 800),
|
||||
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Collections;
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
@@ -16,10 +22,14 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Provider { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLastFMTrackEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDiscordPresenceEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTimelineSyncEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TimelineSyncThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unit: ms
|
||||
/// </summary>
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PositionOffset { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ResetPositionOffsetOnSongChanged { get; set; } = false;
|
||||
@@ -27,8 +37,13 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; } = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; } = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
|
||||
|
||||
public bool IsLXMusic => PlayerIdMatcher.IsLXMusic(Provider);
|
||||
public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);
|
||||
|
||||
public string? DisplayName => PlayerIDHelper.GetDisplayName(Provider);
|
||||
|
||||
public bool IsLXMusic => PlayerIDHelper.IsLXMusic(Provider);
|
||||
|
||||
public MediaSourceProviderInfo()
|
||||
{
|
||||
@@ -98,6 +113,5 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
OnPropertyChanged(nameof(LyricsSearchProvidersInfo));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ShowOrHideLyricsWindowShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "H" };
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ListenOnNewPlaybackSource { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IgnoreCacheWhenSearching { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> BorderlessShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "B" };
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ClickThroughShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "C" };
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> LyricsWindowSwitchShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "S" };
|
||||
|
||||
@@ -26,7 +26,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SpectrumPlacement SpectrumPlacement { get; set; } = SpectrumPlacement.Bottom;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsSnowFlakeOverlayEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SnowFlakeOverlayAmount { get; set; } = 50;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SnowFlakeOverlayAmount { get; set; } = 10;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SnowFlakeOverlaySpeed { get; set; } = 1;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFogOverlayEnabled { get; set; } = false;
|
||||
|
||||
|
||||
public LyricsBackgroundSettings() { }
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class SongInfo : ObservableObject
|
||||
public partial class SongInfo : ObservableObject, ICloneable
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial string Album { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Artist { get; set; }
|
||||
public partial string[] Artists { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int? Duration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double? DurationMs { get; set; }
|
||||
public partial double DurationMs { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? PlayerId { get; set; } = null;
|
||||
@@ -27,16 +26,43 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string? SongId { get; set; } = null;
|
||||
|
||||
public SongInfo() { }
|
||||
}
|
||||
public string? LinkedFileName { get; set; } = null;
|
||||
|
||||
public static class SongInfoExtensions
|
||||
{
|
||||
public static SongInfo Placeholder => new()
|
||||
public double Duration => DurationMs / 1000;
|
||||
|
||||
public string DisplayArtists => Artists.Join(ATL.Settings.DisplayValueSeparator.ToString());
|
||||
|
||||
public SongInfo() { }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
Title = "N/A",
|
||||
Album = "N/A",
|
||||
Artist = "N/A",
|
||||
};
|
||||
return new SongInfo()
|
||||
{
|
||||
Title = this.Title,
|
||||
Artists = this.Artists,
|
||||
Album = this.Album,
|
||||
DurationMs = this.DurationMs,
|
||||
PlayerId = this.PlayerId,
|
||||
SongId = this.SongId,
|
||||
LinkedFileName = this.LinkedFileName,
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"Title: {Title}, " +
|
||||
$"Artist: {DisplayArtists}, " +
|
||||
$"Album: {Album}, " +
|
||||
$"Duration: {Duration} sec, " +
|
||||
$"Plauer ID: {PlayerId}, " +
|
||||
$"Song ID: {SongId}, " +
|
||||
$"Linked file name: {LinkedFileName}.";
|
||||
}
|
||||
|
||||
public string ToFileName()
|
||||
{
|
||||
return $"{DisplayArtists} - {Title} - {Album} - {Duration}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
namespace BetterLyrics.WinUI3.Providers
|
||||
{
|
||||
public class AppleMusic
|
||||
{
|
||||
@@ -13,6 +14,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
private string _accessToken = "";
|
||||
private string _storefront = "";
|
||||
private string _language = "";
|
||||
private bool _isInited = false;
|
||||
|
||||
public AppleMusic()
|
||||
{
|
||||
@@ -25,11 +27,18 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public async Task<bool> InitAsync()
|
||||
{
|
||||
await GetAccessTokenAsync();
|
||||
await SetMediaUserTokenAsync();
|
||||
return
|
||||
!string.IsNullOrEmpty(_accessToken) &&
|
||||
!string.IsNullOrEmpty(PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey));
|
||||
if (!_isInited)
|
||||
{
|
||||
var mediaUserToken = PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey);
|
||||
if (!string.IsNullOrEmpty(mediaUserToken))
|
||||
{
|
||||
await GetAccessTokenAsync();
|
||||
await SetMediaUserTokenAsync(mediaUserToken);
|
||||
_isInited = !string.IsNullOrEmpty(_accessToken);
|
||||
}
|
||||
}
|
||||
|
||||
return _isInited;
|
||||
}
|
||||
|
||||
private async Task GetAccessTokenAsync()
|
||||
@@ -46,11 +55,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_accessToken}");
|
||||
}
|
||||
|
||||
private async Task SetMediaUserTokenAsync()
|
||||
private async Task SetMediaUserTokenAsync(string token)
|
||||
{
|
||||
_client.DefaultRequestHeaders.Remove("media-user-token");
|
||||
_client.DefaultRequestHeaders.Add("media-user-token",
|
||||
PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey));
|
||||
_client.DefaultRequestHeaders.Add("media-user-token", token);
|
||||
var resp = await _client.GetStringAsync("https://amp-api.music.apple.com/v1/me/storefront");
|
||||
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
_storefront = json.GetProperty("data")[0].GetProperty("id").ToString();
|
||||
@@ -59,10 +67,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_client.DefaultRequestHeaders.Add("Accept-Language", $"{_language},en;q=0.9");
|
||||
}
|
||||
|
||||
public async Task<string?> GetLyricsAsync(string title, string artist)
|
||||
public async Task<string?> GetLyricsAsync(string id)
|
||||
{
|
||||
string id = await SearchSongInfoAsync(artist, title);
|
||||
|
||||
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/songs/{id}";
|
||||
var url = apiUrl + $"?include[songs]=lyrics,syllable-lyrics&l={_language}";
|
||||
var resp = await _client.GetStringAsync(url);
|
||||
@@ -103,7 +109,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string> SearchSongInfoAsync(string artist, string title)
|
||||
public async Task<string> SearchSongInfoAsync(string artist, string title)
|
||||
{
|
||||
var query = $"{artist} {title}";
|
||||
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/search";
|
||||
@@ -10,6 +10,7 @@ namespace BetterLyrics.WinUI3.Serialization
|
||||
[JsonSerializable(typeof(TranslateResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(AppSettings))]
|
||||
[JsonSerializable(typeof(LyricsSearchResult))]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
internal partial class SourceGenerationContext : JsonSerializerContext { }
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -26,20 +26,20 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public AlbumArtSearchService(ISettingsService settingsService)
|
||||
public AlbumArtSearchService(ISettingsService settingsService, ILogger<AlbumArtSearchService> logger)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<AlbumArtSearchService>>();
|
||||
_logger = logger;
|
||||
_iTunesHttpClinet = new();
|
||||
}
|
||||
|
||||
public async Task<IBuffer?> SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token)
|
||||
public async Task<IBuffer?> SearchAsync(SongInfo songInfo, IBuffer? bufferFromSMTC, CancellationToken token)
|
||||
{
|
||||
IBuffer? result = null;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.AlbumArtSearchProvidersInfo ?? [])
|
||||
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == songInfo.PlayerId)?.AlbumArtSearchProvidersInfo ?? [])
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
@@ -49,7 +49,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case AlbumArtSearchProvider.Local:
|
||||
result = SearchFile(artist, title)?.AsBuffer();
|
||||
result = SearchFile(songInfo)?.AsBuffer();
|
||||
break;
|
||||
case AlbumArtSearchProvider.SMTC:
|
||||
result = bufferFromSMTC;
|
||||
@@ -57,7 +57,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
case AlbumArtSearchProvider.iTunes:
|
||||
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
|
||||
{
|
||||
var byteArray = await SearchiTunesAsync(artist, album, title, countryCode);
|
||||
var byteArray = await SearchiTunesAsync(songInfo, countryCode);
|
||||
result = byteArray?.AsBuffer();
|
||||
if (token.IsCancellationRequested) return result;
|
||||
if (result != null) break;
|
||||
@@ -77,7 +77,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[]? SearchFile(string artist, string title)
|
||||
private byte[]? SearchFile(SongInfo songInfo)
|
||||
{
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
@@ -88,7 +88,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
|
||||
{
|
||||
Track track = new(file);
|
||||
if ((track.Title == title && track.Artist == artist) || FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), artist, title))
|
||||
if ((track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists) || StringHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.DisplayArtists, songInfo.Title))
|
||||
{
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
@@ -103,13 +103,13 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album, string title, string countryCode)
|
||||
private async Task<byte[]?> SearchiTunesAsync(SongInfo songInfo, string countryCode)
|
||||
{
|
||||
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
|
||||
try
|
||||
{
|
||||
string format = ".jpg";
|
||||
var cachedAlbumArt = FileHelper.ReadAlbumArtCache(artist, album, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
var cachedAlbumArt = FileHelper.ReadAlbumArtCache(songInfo.DisplayArtists, songInfo.Album, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
|
||||
if (cachedAlbumArt != null)
|
||||
{
|
||||
@@ -117,7 +117,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
}
|
||||
|
||||
// Build the iTunes API URL
|
||||
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
|
||||
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{songInfo.Artists} {songInfo.Album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
|
||||
|
||||
// Make a request to the API
|
||||
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
@@ -139,7 +139,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
if (fetched != null && fetched.Length > 0)
|
||||
{
|
||||
// Write to cache
|
||||
FileHelper.WriteAlbumArtCache(artist, album, fetched, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
FileHelper.WriteAlbumArtCache(songInfo, fetched, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
return fetched;
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
|
||||
_logger.LogError(ex, "SearchiTunesAsync");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
@@ -6,6 +7,6 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
{
|
||||
public interface IAlbumArtSearchService
|
||||
{
|
||||
Task<IBuffer?> SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token);
|
||||
Task<IBuffer?> SearchAsync(SongInfo songInfo, IBuffer? bufferFromSMTC, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using DiscordRPC;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.DiscordService
|
||||
{
|
||||
public class DiscordService : IDiscordService
|
||||
{
|
||||
private DiscordRpcClient? _client;
|
||||
|
||||
public DiscordService()
|
||||
{
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (_client == null)
|
||||
{
|
||||
_client = new DiscordRpcClient(Constants.Discord.AppID);
|
||||
_client.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRichPresence(SongInfo songInfo)
|
||||
{
|
||||
_client?.SetPresence(new RichPresence
|
||||
{
|
||||
StatusDisplay = StatusDisplayType.Details,
|
||||
Type = ActivityType.Listening,
|
||||
Buttons = new Button[] { new() { Label = "Get this status", Url = Constants.Link.MicrosoftStoreUrl } },
|
||||
Assets = new Assets
|
||||
{
|
||||
LargeImageKey = "banner",
|
||||
SmallImageKey = "logo"
|
||||
},
|
||||
Details = songInfo.Title,
|
||||
State = songInfo.DisplayArtists,
|
||||
Timestamps = Timestamps.FromTimeSpan(songInfo.Duration)
|
||||
});
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_client?.ClearPresence();
|
||||
_client?.Dispose();
|
||||
_client = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.DiscordService
|
||||
{
|
||||
public interface IDiscordService
|
||||
{
|
||||
void Enable();
|
||||
void Disable();
|
||||
void UpdateRichPresence(SongInfo songInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
@@ -59,7 +60,7 @@ namespace BetterLyrics.WinUI3.Services.LastFMService
|
||||
|
||||
public async Task AuthAsync()
|
||||
{
|
||||
var dialogXamlRoot = WindowHelper.GetWindowByWindowType<SettingsWindow>()?.Content.XamlRoot;
|
||||
var dialogXamlRoot = WindowHook.GetWindow<SettingsWindow>()?.Content.XamlRoot;
|
||||
if (dialogXamlRoot == null)
|
||||
{
|
||||
return;
|
||||
@@ -86,7 +87,7 @@ namespace BetterLyrics.WinUI3.Services.LastFMService
|
||||
|
||||
public async Task UnAuthAsync()
|
||||
{
|
||||
var dialogXamlRoot = WindowHelper.GetWindowByWindowType<SettingsWindow>()?.Content.XamlRoot;
|
||||
var dialogXamlRoot = WindowHook.GetWindow<SettingsWindow>()?.Content.XamlRoot;
|
||||
if (dialogXamlRoot == null)
|
||||
{
|
||||
return;
|
||||
@@ -132,7 +133,7 @@ namespace BetterLyrics.WinUI3.Services.LastFMService
|
||||
await _client.Track.ScrobbleAsync(new Hqub.Lastfm.Entities.Scrobble
|
||||
{
|
||||
Track = songInfo.Title,
|
||||
Artist = songInfo.Artist,
|
||||
Artist = songInfo.DisplayArtists,
|
||||
Date = DateTime.Now,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace BetterLyrics.WinUI3.Services.LibWatcherService
|
||||
UpdateWatchers();
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_ItemPropertyChanged(object? sender, Extensions.ItemPropertyChangedEventArgs e)
|
||||
private void LocalMediaFolders_ItemPropertyChanged(object? sender, Collections.ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateWatchers();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
@@ -44,21 +45,21 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
|
||||
LiveStates.LyricsWindowStatus.UpdateMonitorBounds();
|
||||
|
||||
WindowHelper.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
|
||||
WindowHook.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
WindowHelper.UpdateWorkArea<LyricsWindow>();
|
||||
WindowHook.UpdateWorkArea<LyricsWindow>();
|
||||
}
|
||||
await Task.Delay(300);
|
||||
|
||||
WindowHelper.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
|
||||
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
|
||||
WindowHook.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
|
||||
WindowHook.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
|
||||
|
||||
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
|
||||
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
|
||||
WindowHook.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
|
||||
WindowHook.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
|
||||
|
||||
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
|
||||
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
|
||||
WindowHook.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
|
||||
WindowHook.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
|
||||
|
||||
// 下述代码可以删除,但是为了避免给用户造成操作上的疑虑,暂时保留
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
@@ -66,7 +67,7 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
LiveStates.LyricsWindowStatus.WindowBounds = LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea();
|
||||
}
|
||||
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
|
||||
LiveStates.LyricsWindowStatus.WindowX = LiveStates.LyricsWindowStatus.WindowBounds.X;
|
||||
LiveStates.LyricsWindowStatus.WindowY = LiveStates.LyricsWindowStatus.WindowBounds.Y;
|
||||
LiveStates.LyricsWindowStatus.WindowWidth = LiveStates.LyricsWindowStatus.WindowBounds.Width;
|
||||
@@ -83,11 +84,11 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
{
|
||||
case nameof(LyricsWindowStatus.IsWorkArea):
|
||||
LiveStates.IsLyricsWindowStatusRefreshing = true;
|
||||
WindowHelper.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
|
||||
WindowHook.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
|
||||
LiveStates.IsLyricsWindowStatusRefreshing = false;
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
|
||||
}
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.DockHeight):
|
||||
@@ -97,40 +98,40 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
LiveStates.IsLyricsWindowStatusRefreshing = true;
|
||||
WindowHelper.UpdateWorkArea<LyricsWindow>();
|
||||
WindowHook.UpdateWorkArea<LyricsWindow>();
|
||||
LiveStates.IsLyricsWindowStatusRefreshing = false;
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
|
||||
}
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.IsShownInSwitchers):
|
||||
WindowHelper.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
|
||||
WindowHook.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.IsAlwaysOnTop):
|
||||
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
|
||||
WindowHook.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.IsClickThrough):
|
||||
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
|
||||
WindowHook.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.IsBorderless):
|
||||
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
|
||||
WindowHook.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.WindowX):
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithX(LiveStates.LyricsWindowStatus.WindowX));
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithX(LiveStates.LyricsWindowStatus.WindowX));
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.WindowY):
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithY(LiveStates.LyricsWindowStatus.WindowY));
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithY(LiveStates.LyricsWindowStatus.WindowY));
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.WindowWidth):
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithWidth(LiveStates.LyricsWindowStatus.WindowWidth));
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithWidth(LiveStates.LyricsWindowStatus.WindowWidth));
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.WindowHeight):
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithHeight(LiveStates.LyricsWindowStatus.WindowHeight));
|
||||
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithHeight(LiveStates.LyricsWindowStatus.WindowHeight));
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.TitleBarArea):
|
||||
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
|
||||
WindowHook.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.AutoShowOrHideWindow):
|
||||
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
|
||||
WindowHook.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
@@ -9,8 +10,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, string? songId, CancellationToken token);
|
||||
Task<LyricsSearchResult?> SearchSmartlyAsync(SongInfo songInfo, bool checkCache, LyricsSearchType? lyricsSearchType, CancellationToken token);
|
||||
|
||||
Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
Task<List<LyricsSearchResult>> SearchAllAsync(SongInfo songInfo, bool checkCache, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Providers;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers;
|
||||
using Lyricify.Lyrics.Searchers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -17,8 +18,10 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Documents;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
@@ -31,10 +34,10 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public LyricsSearchService(ISettingsService settingsService)
|
||||
public LyricsSearchService(ISettingsService settingsService, ILogger<LyricsSearchService> logger)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsSearchService>>();
|
||||
_logger = logger;
|
||||
|
||||
_lrcLibHttpClient = new();
|
||||
_lrcLibHttpClient.DefaultRequestHeaders.Add(
|
||||
@@ -89,24 +92,32 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, string? songId, CancellationToken token)
|
||||
public async Task<LyricsSearchResult?> SearchSmartlyAsync(SongInfo songInfo, bool checkCache, LyricsSearchType? lyricsSearchType, CancellationToken token)
|
||||
{
|
||||
if (lyricsSearchType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
string overridenTitle = title;
|
||||
string overridenArtist = artist;
|
||||
string overridenAlbum = album;
|
||||
string overridenTitle = songInfo.Title;
|
||||
string[] overridenArtists = songInfo.Artists;
|
||||
string overridenAlbum = songInfo.Album;
|
||||
|
||||
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
_logger.LogInformation("SearchSmartlyAsync {SongInfo}", songInfo);
|
||||
|
||||
// 先检查该曲目是否已被用户映射
|
||||
var found = _settingsService.AppSettings.MappedSongSearchQueries
|
||||
.Where(x => x.OriginalTitle == overridenTitle && x.OriginalArtist == overridenArtist && x.OriginalAlbum == overridenAlbum)
|
||||
.FirstOrDefault();
|
||||
.FirstOrDefault(x =>
|
||||
x.OriginalTitle == overridenTitle &&
|
||||
x.OriginalArtist == overridenArtists.Join(ATL.Settings.DisplayValueSeparator.ToString()) &&
|
||||
x.OriginalAlbum == overridenAlbum);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
overridenTitle = found.MappedTitle;
|
||||
overridenArtist = found.MappedArtist;
|
||||
overridenArtists = found.MappedArtist.Split(ATL.Settings.DisplayValueSeparator);
|
||||
overridenAlbum = found.MappedAlbum;
|
||||
|
||||
_logger.LogInformation("Found mapped song search query: {MappedSongSearchQuery}", found);
|
||||
@@ -115,7 +126,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
if (pureMusic)
|
||||
{
|
||||
lyricsSearchResult.Title = overridenTitle;
|
||||
lyricsSearchResult.Artist = overridenArtist;
|
||||
lyricsSearchResult.Artists = overridenArtists;
|
||||
lyricsSearchResult.Album = overridenAlbum;
|
||||
lyricsSearchResult.Raw = "[99:00.000]🎶🎶🎶";
|
||||
return lyricsSearchResult;
|
||||
@@ -124,41 +135,68 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var targetProvider = found.LyricsSearchProvider;
|
||||
if (targetProvider != null)
|
||||
{
|
||||
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, overridenAlbum, durationMs, songId, token);
|
||||
return await SearchSingleAsync(
|
||||
((SongInfo)songInfo.Clone())
|
||||
.WithTitle(overridenTitle)
|
||||
.WithArtist(overridenArtists)
|
||||
.WithAlbum(overridenAlbum),
|
||||
targetProvider.Value, checkCache, token);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.LyricsSearchProvidersInfo ?? [])
|
||||
List<LyricsSearchResult> lyricsSearchResults = [];
|
||||
|
||||
// 曲目没有被映射
|
||||
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == songInfo.PlayerId)?.LyricsSearchProvidersInfo ?? [])
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, overridenAlbum, durationMs, null, token);
|
||||
lyricsSearchResult = await SearchSingleAsync(
|
||||
((SongInfo)songInfo.Clone())
|
||||
.WithTitle(overridenTitle)
|
||||
.WithArtist(overridenArtists)
|
||||
.WithAlbum(overridenAlbum),
|
||||
provider.Provider, checkCache, token);
|
||||
|
||||
if (lyricsSearchResult.IsFound)
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
switch (lyricsSearchType)
|
||||
{
|
||||
case LyricsSearchType.Sequential:
|
||||
return lyricsSearchResult;
|
||||
case LyricsSearchType.BestMatch:
|
||||
lyricsSearchResults.Add((LyricsSearchResult)lyricsSearchResult.Clone());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
return lyricsSearchType switch
|
||||
{
|
||||
LyricsSearchType.Sequential => lyricsSearchResult,
|
||||
LyricsSearchType.BestMatch => lyricsSearchResults.OrderByDescending(x => x.MatchPercentage).FirstOrDefault(),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
public async Task<List<LyricsSearchResult>> SearchAllAsync(SongInfo songInfo, bool checkCache, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Searching all lyrics for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
_logger.LogInformation("SearchAllAsync {SongInfo}", songInfo);
|
||||
var results = new List<LyricsSearchResult>();
|
||||
foreach (var provider in Enum.GetValues<LyricsSearchProvider>())
|
||||
{
|
||||
var searchResult = await SearchSingleAsync(provider, title, artist, album, durationMs, null, token);
|
||||
var searchResult = await SearchSingleAsync(songInfo, provider, checkCache, token);
|
||||
results.Add(searchResult);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchSingleAsync(LyricsSearchProvider provider, string title, string artist, string album, double durationMs, string? songId, CancellationToken token)
|
||||
private async Task<LyricsSearchResult> SearchSingleAsync(SongInfo songInfo, LyricsSearchProvider provider, bool checkCache, CancellationToken token)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
@@ -169,16 +207,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
LyricsFormat lyricsFormat = provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.IsRemote())
|
||||
// Check cache first if allowed
|
||||
if (checkCache && provider.IsRemote())
|
||||
{
|
||||
var cachedLyrics = FileHelper.ReadLyricsCache(title, artist, album, lyricsFormat, provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
var cached = FileHelper.ReadLyricsCache(songInfo, provider);
|
||||
if (cached != null)
|
||||
{
|
||||
lyricsSearchResult.Raw = cachedLyrics;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
lyricsSearchResult.Album = album;
|
||||
lyricsSearchResult = cached;
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
}
|
||||
@@ -187,11 +222,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
if (provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
lyricsSearchResult = SearchEmbedded(title, artist, album);
|
||||
lyricsSearchResult = SearchEmbedded(songInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
lyricsSearchResult = await SearchFile(title, artist, album, lyricsFormat);
|
||||
lyricsSearchResult = await SearchFile(songInfo, lyricsFormat);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -199,22 +234,22 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
switch (provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
lyricsSearchResult = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
lyricsSearchResult = await SearchLrcLibAsync(songInfo);
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.QQMusic);
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Kugou);
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Netease);
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
lyricsSearchResult = await SearchAmllTtmlDbAsync(title, artist, album);
|
||||
lyricsSearchResult = await SearchAmllTtmlDbAsync(songInfo);
|
||||
break;
|
||||
case LyricsSearchProvider.AppleMusic:
|
||||
lyricsSearchResult = await SearchAppleMusicAsync(title, artist, album, (int)durationMs);
|
||||
lyricsSearchResult = await SearchAppleMusicAsync(songInfo);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -223,30 +258,36 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
if (lyricsSearchResult.IsFound)
|
||||
{
|
||||
if (provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, album, lyricsSearchResult.Raw!, lyricsFormat, provider.GetCacheDirectory());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
|
||||
|
||||
if (lyricsSearchResult.IsFound)
|
||||
{
|
||||
if (provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
|
||||
}
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchFile(string title, string artist, string album, LyricsFormat format)
|
||||
private async Task<LyricsSearchResult> SearchFile(SongInfo songInfo, LyricsFormat format)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
if (format.ToLyricsSearchProvider() is LyricsSearchProvider lyricsSearchProvider)
|
||||
{
|
||||
Provider = format.ToLyricsSearchProvider(),
|
||||
};
|
||||
lyricsSearchResult.Provider = lyricsSearchProvider;
|
||||
}
|
||||
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
@@ -256,15 +297,15 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
|
||||
{
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
var fileName = Path.GetFileNameWithoutExtension(file);
|
||||
if (StringHelper.IsSwitchableNormalizedMatch(fileName, songInfo.Title, songInfo.DisplayArtists) || songInfo.LinkedFileName == fileName)
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
|
||||
if (raw != null)
|
||||
{
|
||||
lyricsSearchResult.Raw = raw;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
lyricsSearchResult.Album = album;
|
||||
lyricsSearchResult.CopyFromSongInfo(songInfo);
|
||||
lyricsSearchResult.Reference = file;
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
@@ -279,7 +320,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private LyricsSearchResult SearchEmbedded(string title, string artist, string album)
|
||||
private LyricsSearchResult SearchEmbedded(SongInfo songInfo)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
@@ -295,17 +336,16 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
|
||||
{
|
||||
var track = new Track(file);
|
||||
if ((album != "" && track.Title == title && track.Artist == artist && track.Album == album)
|
||||
|| (album == "" && track.Title == title && track.Artist == artist)
|
||||
|| (album == "" && FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist)))
|
||||
if ((songInfo.Album != "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists && track.Album == songInfo.Album)
|
||||
|| (songInfo.Album == "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists)
|
||||
|| (songInfo.Album == "" && StringHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.Title, songInfo.DisplayArtists)))
|
||||
{
|
||||
var plain = track.GetLyrics();
|
||||
var plain = track.GetRawLyrics();
|
||||
if (!plain.IsNullOrEmpty())
|
||||
{
|
||||
lyricsSearchResult.Raw = plain;
|
||||
lyricsSearchResult.Title = track.Title;
|
||||
lyricsSearchResult.Artist = track.Artist;
|
||||
lyricsSearchResult.Album = track.Album;
|
||||
lyricsSearchResult.CopyFromSongInfo(songInfo);
|
||||
lyricsSearchResult.Reference = file;
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
@@ -317,7 +357,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchAmllTtmlDbAsync(string title, string artist, string album)
|
||||
private async Task<LyricsSearchResult> SearchAmllTtmlDbAsync(SongInfo songInfo)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
@@ -333,6 +373,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
}
|
||||
|
||||
int bestScore = 0;
|
||||
string? rawLyricFile = null;
|
||||
await foreach (var line in File.ReadLinesAsync(PathHelper.AmllTtmlDbIndexPath))
|
||||
{
|
||||
@@ -344,8 +385,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var root = doc.RootElement;
|
||||
if (!root.TryGetProperty("metadata", out var metadataArr))
|
||||
continue;
|
||||
string? musicName = null;
|
||||
string? artists = null;
|
||||
|
||||
string? title = null;
|
||||
string[]? artists = null;
|
||||
string? album = null;
|
||||
|
||||
foreach (var meta in metadataArr.EnumerateArray())
|
||||
{
|
||||
if (meta.GetArrayLength() != 2)
|
||||
@@ -353,19 +397,28 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var key = meta[0].GetString();
|
||||
var valueArr = meta[1];
|
||||
if (key == "musicName" && valueArr.GetArrayLength() > 0)
|
||||
musicName = valueArr[0].GetString();
|
||||
title = valueArr[0].GetString();
|
||||
if (key == "artists" && valueArr.GetArrayLength() > 0)
|
||||
artists = valueArr[0].GetString();
|
||||
artists = valueArr.EnumerateArray().Select(x => x.GetString() ?? "").ToArray();
|
||||
if (key == "album" && valueArr.GetArrayLength() > 0)
|
||||
album = valueArr[0].GetString();
|
||||
}
|
||||
if (musicName == null || artists == null)
|
||||
continue;
|
||||
|
||||
if (FileHelper.IsSwitchableNormalizedMatch($"{artists} - {musicName}", title, artist))
|
||||
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult
|
||||
{
|
||||
Title = title,
|
||||
Artists = artists,
|
||||
Album = album,
|
||||
});
|
||||
if (score > bestScore)
|
||||
{
|
||||
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
|
||||
{
|
||||
rawLyricFile = rawLyricFileProp.GetString();
|
||||
break;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artists = artists;
|
||||
lyricsSearchResult.Album = album;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,6 +432,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
// 下载歌词内容
|
||||
var url = $"{_settingsService.AppSettings.GeneralSettings.AmllTtmlDbBaseUrl}/{Constants.AmllTTmlDB.QueryPrefix}/{rawLyricFile}";
|
||||
lyricsSearchResult.Reference = url;
|
||||
try
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
@@ -389,9 +443,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
string lyrics = await response.Content.ReadAsStringAsync();
|
||||
|
||||
lyricsSearchResult.Raw = lyrics;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
lyricsSearchResult.Album = album;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -400,7 +451,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchLrcLibAsync(string title, string artist, string album, int duration)
|
||||
private async Task<LyricsSearchResult> SearchLrcLibAsync(SongInfo songInfo)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
@@ -410,10 +461,10 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
// 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())}";
|
||||
$"track_name={Uri.EscapeDataString(songInfo.Title)}&" +
|
||||
$"artist_name={Uri.EscapeDataString(songInfo.DisplayArtists)}&" +
|
||||
$"&album_name={Uri.EscapeDataString(songInfo.Album)}" +
|
||||
$"&durationMs={Uri.EscapeDataString(songInfo.DurationMs.ToString())}";
|
||||
|
||||
using var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
@@ -432,6 +483,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
string? searchedTitle = null;
|
||||
string? searchedArtist = null;
|
||||
string? searchedAlbum = null;
|
||||
double? searchedDuration = null;
|
||||
|
||||
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
|
||||
{
|
||||
@@ -440,17 +492,21 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
searchedTitle = first.GetProperty("trackName").GetString();
|
||||
searchedArtist = first.GetProperty("artistName").GetString();
|
||||
searchedAlbum = first.GetProperty("albumName").GetString();
|
||||
searchedDuration = first.GetProperty("duration").GetDouble();
|
||||
}
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Title = searchedTitle;
|
||||
lyricsSearchResult.Artist = searchedArtist;
|
||||
lyricsSearchResult.Artists = searchedArtist?.SplitByCommonSplitter();
|
||||
lyricsSearchResult.Album = searchedAlbum;
|
||||
lyricsSearchResult.Duration = searchedDuration;
|
||||
|
||||
lyricsSearchResult.Reference = url;
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private static async Task<LyricsSearchResult> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, string? songId, Searchers searcher)
|
||||
private static async Task<LyricsSearchResult> SearchQQNeteaseKugouAsync(SongInfo songInfo, Searchers searcher)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
@@ -472,20 +528,20 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
|
||||
ISearchResult? result;
|
||||
if (searcher == Searchers.Netease && songId != null)
|
||||
if (searcher == Searchers.Netease && songInfo.SongId != null)
|
||||
{
|
||||
result = new NeteaseSearchResult(title, [artist], album, null, durationMs, songId);
|
||||
result = new NeteaseSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, songInfo.Artists, (int)songInfo.DurationMs, songInfo.SongId);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await SearchHelper.Search(new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs = durationMs,
|
||||
Album = album,
|
||||
AlbumArtists = [artist],
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}, searcher);
|
||||
DurationMs = (int)songInfo.DurationMs,
|
||||
Album = songInfo.Album,
|
||||
AlbumArtists = songInfo.Artists.ToList(),
|
||||
Artists = songInfo.Artists.ToList(),
|
||||
Title = songInfo.Title,
|
||||
}, searcher, Lyricify.Lyrics.Searchers.Helpers.CompareHelper.MatchType.NoMatch);
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
@@ -493,45 +549,24 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
if (result is QQMusicSearchResult qqResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
|
||||
var original = response?.Lyrics;
|
||||
var translated = response?.Trans;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.QQTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Raw = response?.Lyrics;
|
||||
lyricsSearchResult.Translation = response?.Trans;
|
||||
lyricsSearchResult.Reference = $"https://y.qq.com/n/ryqq/songDetail/{qqResult.Mid}";
|
||||
}
|
||||
else if (result is NeteaseSearchResult neteaseResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
|
||||
var original = response?.Lrc?.Lyric;
|
||||
var translated = response?.Tlyric?.Lyric;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.NeteaseTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Raw = response?.Lrc?.Lyric;
|
||||
lyricsSearchResult.Translation = response?.Tlyric?.Lyric;
|
||||
lyricsSearchResult.Reference = $"https://music.163.com/song?id={neteaseResult.Id}";
|
||||
}
|
||||
else if (result is KugouSearchResult kugouResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
|
||||
string? original = null;
|
||||
string? translated = null;
|
||||
var candidate = response?.Candidates.FirstOrDefault();
|
||||
if (candidate != null)
|
||||
{
|
||||
@@ -541,7 +576,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var parsedList = Lyricify.Lyrics.Parsers.KrcParser.ParseLyrics(original);
|
||||
if (parsedList != null)
|
||||
{
|
||||
string translated = "";
|
||||
translated = "";
|
||||
foreach (var item in parsedList)
|
||||
{
|
||||
if (item is Lyricify.Lyrics.Models.FullSyllableLineInfo fullSyllableLineInfo)
|
||||
@@ -552,33 +587,25 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
translated += $"[{startTimeStr}]{chTranslation}\n";
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.KugouTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
lyricsSearchResult.Reference = $"https://www.kugou.com/";
|
||||
}
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Translation = translated;
|
||||
}
|
||||
}
|
||||
|
||||
lyricsSearchResult.Title = result?.Title;
|
||||
lyricsSearchResult.Artist = result?.Artists.Join(" | ");
|
||||
lyricsSearchResult.Artists = result?.Artists;
|
||||
lyricsSearchResult.Album = result?.Album;
|
||||
lyricsSearchResult.Duration = result?.DurationMs / 1000;
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchAppleMusicAsync(string title, string artist, string album, int durationMs)
|
||||
private async Task<LyricsSearchResult> SearchAppleMusicAsync(SongInfo songInfo)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
@@ -587,12 +614,14 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
if (await _appleMusic.InitAsync())
|
||||
{
|
||||
var raw = await _appleMusic.GetLyricsAsync(title, artist);
|
||||
_logger.LogInformation("Apple Music lyrics search result for {Title} - {Artist}: {Raw}", title, artist, raw ?? "null");
|
||||
string id = await _appleMusic.SearchSongInfoAsync(songInfo.DisplayArtists, songInfo.Title);
|
||||
string? raw = await _appleMusic.GetLyricsAsync(id);
|
||||
_logger.LogInformation("SearchAppleMusicAsync");
|
||||
lyricsSearchResult.Raw = raw;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
lyricsSearchResult.Title = songInfo.Title;
|
||||
lyricsSearchResult.Artists = songInfo.Artists;
|
||||
lyricsSearchResult.Album = "";
|
||||
lyricsSearchResult.Reference = $"https://music.apple.com/song/{id}";
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
|
||||
@@ -4,18 +4,17 @@ using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
public interface IMediaSessionsService
|
||||
public interface IMediaSessionsService : INotifyPropertyChanged
|
||||
{
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
|
||||
event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
|
||||
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
Task PlayAsync();
|
||||
Task PauseAsync();
|
||||
@@ -23,19 +22,23 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
Task NextAsync();
|
||||
Task ChangePosition(double seconds);
|
||||
|
||||
MediaSourceProviderInfo? GetCurrentMediaSourceProviderInfo();
|
||||
|
||||
void UpdateLyrics();
|
||||
void UpdateTranslations();
|
||||
|
||||
void InitPlaybackShortcuts();
|
||||
|
||||
bool IsPlaying { get; }
|
||||
SongInfo? SongInfo { get; }
|
||||
TimeSpan Position { get; }
|
||||
MediaSourceProviderInfo? CurrentMediaSourceProviderInfo { get; }
|
||||
|
||||
bool CurrentIsPlaying { get; }
|
||||
SongInfo? CurrentSongInfo { get; }
|
||||
TimeSpan CurrentPosition { get; }
|
||||
LyricsData? CurrentLyricsData { get; }
|
||||
|
||||
LyricsSearchProvider? LyricsSearchProvider { get; }
|
||||
SoftwareBitmap? SoftwareBitmap { get; }
|
||||
List<Color> LightAccentColors { get; }
|
||||
List<Color> DarkAccentColors { get; }
|
||||
|
||||
TranslationSearchProvider? TranslationSearchProvider { get; }
|
||||
LyricsSearchResult? CurrentLyricsSearchResult { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
@@ -14,30 +17,28 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
|
||||
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SoftwareBitmap? SoftwareBitmap { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<Color> LightAccentColors { get; set; } = Enumerable.Repeat(Colors.Black, 4).ToList();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<Color> DarkAccentColors { get; set; } = Enumerable.Repeat(Colors.Black, 4).ToList();
|
||||
|
||||
private void UpdateAlbumArt()
|
||||
{
|
||||
_albumArtRefreshRunner.RunAsync(RefreshArtAlbum);
|
||||
_ = _albumArtRefreshRunner.RunAsync(RefreshArtAlbum);
|
||||
}
|
||||
|
||||
private async Task RefreshArtAlbum(CancellationToken token)
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
_logger.LogInformation("RefreshArtAlbum");
|
||||
|
||||
if (CurrentSongInfo == null)
|
||||
{
|
||||
_logger.LogWarning("Cached song info is null, cannot update album art.");
|
||||
_logger.LogWarning("CurrentSongInfo == null");
|
||||
return;
|
||||
}
|
||||
|
||||
IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(
|
||||
SongInfo?.PlayerId ?? "",
|
||||
_cachedSongInfo.Title,
|
||||
_cachedSongInfo.Artist,
|
||||
_cachedSongInfo.Album,
|
||||
_SMTCAlbumArtBuffer,
|
||||
token
|
||||
), token);
|
||||
IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(CurrentSongInfo, _SMTCAlbumArtBuffer, token), token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
BitmapDecoder? decoder = null;
|
||||
|
||||
if (buffer == null)
|
||||
@@ -45,22 +46,27 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
using var placeHolderStream = await ImageHelper.GetAlbumArtPlaceholderAsync();
|
||||
var tempBuffer = new Windows.Storage.Streams.Buffer((uint)placeHolderStream.Size);
|
||||
await placeHolderStream.ReadAsync(tempBuffer, (uint)placeHolderStream.Size, InputStreamOptions.None);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
buffer = tempBuffer;
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
decoder = await ImageHelper.MakeSquareWithThemeColor(buffer, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
albumArtSwBitmap.DpiX = 96;
|
||||
albumArtSwBitmap.DpiY = 96;
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtLightAccentColors = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
|
||||
var lightColorBytes = albumArtLightAccentColors.Palette.Select(t => Windows.UI.Color.FromArgb(255, (byte)t.X, (byte)t.Y, (byte)t.Z)).ToList();
|
||||
var albumArtDarkAccentColors = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
|
||||
var darkColorBytes = albumArtDarkAccentColors.Palette.Select(t => Windows.UI.Color.FromArgb(255, (byte)t.X, (byte)t.Y, (byte)t.Z)).ToList();
|
||||
AlbumArtChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, lightColorBytes, darkColorBytes));
|
||||
var lightPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
|
||||
var darkPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
SoftwareBitmap = albumArtSwBitmap;
|
||||
LightAccentColors = lightPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
|
||||
DarkAccentColors = darkPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user