Compare commits

..

15 Commits

Author SHA1 Message Date
Zhe Fang
a207224bbf chores: add zh edition 2025-11-03 12:36:16 -05:00
Zhe Fang
2c80e0815a Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-11-03 11:31:09 -05:00
Zhe Fang
7f65b4addb fix: handle device lost event 2025-11-03 11:31:07 -05:00
Zhe Fang
210df4dd61 Update README.md 2025-11-02 21:33:45 -05:00
Zhe Fang
3c82908890 Update README.md 2025-11-02 21:01:01 -05:00
Zhe Fang
597cc1ec9b Update README.md 2025-11-02 20:59:29 -05:00
Zhe Fang
e354e29ec1 chores: update version code 2025-11-02 19:10:46 -05:00
Zhe Fang
dd5fb91764 chores: add delay when automatically show and hide window 2025-11-02 17:16:40 -05:00
Zhe Fang
041459e564 chores: remove gain 2025-11-02 11:26:27 -05:00
Zhe Fang
7de813b645 fix: audio graph 2025-11-02 11:05:13 -05:00
Zhe Fang
838d663834 chores: add null check in SearchQQNeteaseKugouAsync; fix search page result display issue 2025-11-02 08:47:49 -05:00
Zhe Fang
9add190383 chores: add logo for some music players 2025-11-01 21:20:47 -04:00
Zhe Fang
fa58360884 chores: add pic in faq 2025-11-01 19:27:08 -04:00
Zhe Fang
4dd032655c fix: translation issue caused by mapping 2025-11-01 19:13:10 -04:00
Zhe Fang
80984218ec add: folder filter in music gallery page; chores: add pic screenshoots; 2025-11-01 15:53:15 -04:00
60 changed files with 1058 additions and 500 deletions

View File

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

View File

@@ -59,10 +59,12 @@
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
<converter:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
<converter:BoolNegationToOpacityConverter x:Key="BoolNegationToOpacityConverter" />
<converter:RectToMarginConverter x:Key="RectToMarginConverter" />
<converter:LanguageCodeToDisplayedNameConverter x:Key="LanguageCodeToDisplayedNameConverter" />
<converter:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
<converter:DisplayLanguageCodeToIndexConverter x:Key="DisplayLanguageCodeToIndexConverter" />
<converter:PathToParentFolderConverter x:Key="PathToParentFolderConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
@@ -331,6 +333,16 @@
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
</Style>
<Style
x:Key="FlyoutGhostStyle"
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
TargetType="FlyoutPresenter">
<Setter Property="Padding" Value="0" />
<Setter Property="ScrollViewer.VerticalScrollMode" Value="Disabled" />
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
</Style>
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -117,6 +117,12 @@
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AlbumArtPlaceholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AMLLPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AppleMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@@ -141,6 +147,9 @@
<Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\HyPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\iTunes.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@@ -180,6 +189,9 @@
<Content Update="Assets\Page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PlanetMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@@ -307,6 +319,9 @@
<Generator></Generator>
</PRIResource>
</ItemGroup>
<ItemGroup>
<Folder Include="TemplateSelector\" />
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>

View File

@@ -41,7 +41,7 @@
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}">
<local:ExtendedSlider
Default="12"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -51,7 +51,7 @@
<controls:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF5EF;}">
<local:ExtendedSlider
Default="12"
Frequency="1"
Maximum="64"
Minimum="0"
Value="{x:Bind AlbumArtLayoutSettings.CoverImageShadowAmount, Mode=TwoWay}" />

View File

@@ -64,7 +64,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty FrequencyProperty =
DependencyProperty.Register(nameof(Frequency), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(1));
DependencyProperty.Register(nameof(Frequency), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(1.0));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
public static readonly DependencyProperty MaximumProperty =

View File

@@ -33,7 +33,7 @@
<controls:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsPureColorOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -50,7 +50,7 @@
<controls:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -60,7 +60,7 @@
<controls:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="50"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -70,7 +70,7 @@
<controls:SettingsCard x:Uid="SettingsPageBlurAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
@@ -79,7 +79,7 @@
<controls:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="0"
Frequency="1"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
@@ -95,7 +95,7 @@
<controls:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsFluidOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -117,7 +117,7 @@
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageAmount">
<uc:ExtendedSlider
Frequency="1"
Maximum="100"
Minimum="10"
ResetButtonVisibility="Collapsed"

View File

@@ -27,7 +27,7 @@
<local:ExtendedSlider
x:Uid="SettingsPageLyricsVerticalEdgeOpacitySlider"
Default="0"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -38,7 +38,7 @@
<local:ExtendedSlider
x:Uid="SettingsPageLyricsBlurAmountExtendedSlider"
Default="5"
Frequency="1"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsBlurAmount, Mode=TwoWay}" />
@@ -111,7 +111,7 @@
<controls:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsShadowEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="8"
Frequency="1"
Maximum="20"
Minimum="1"
Value="{x:Bind LyricsEffectSettings.LyricsShadowAmount, Mode=TwoWay}" />
@@ -137,7 +137,7 @@
<controls:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="8"
Frequency="1"
Maximum="20"
Minimum="1"
Value="{x:Bind LyricsEffectSettings.LyricsGlowEffectAmount, Mode=TwoWay}" />
@@ -156,7 +156,7 @@
<controls:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="1"
Frequency="1"
Maximum="4"
Minimum="1"
Value="{x:Bind LyricsEffectSettings.LyricsFloatAmount, Mode=TwoWay}" />
@@ -175,7 +175,7 @@
<controls:SettingsCard IsEnabled="{x:Bind LyricsEffectSettings.IsFanLyricsEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="30"
Frequency="1"
Maximum="90"
Minimum="-90"
Unit="°"
@@ -196,7 +196,7 @@
<controls:SettingsCard Header="X" IsEnabled="{x:Bind LyricsEffectSettings.Is3DLyricsEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="0"
Frequency="1"
Maximum="90"
Minimum="-90"
Unit="°"
@@ -205,7 +205,7 @@
<controls:SettingsCard Header="Y" IsEnabled="{x:Bind LyricsEffectSettings.Is3DLyricsEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="0"
Frequency="1"
Maximum="90"
Minimum="-90"
Unit="°"
@@ -214,7 +214,7 @@
<controls:SettingsCard Header="Z" IsEnabled="{x:Bind LyricsEffectSettings.Is3DLyricsEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="0"
Frequency="1"
Maximum="90"
Minimum="-90"
Unit="°"

View File

@@ -8,6 +8,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
@@ -52,6 +53,19 @@
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsSearchControlAlbum" Description="{x:Bind ViewModel.MappedSongSearchQuery.OriginalAlbum, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedAlbum, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedAlbumCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard>
<CheckBox x:Uid="LyricsSearchControlMarkAsPureMusic" IsChecked="{x:Bind ViewModel.MappedSongSearchQuery.IsMarkedAsPureMusic, Mode=TwoWay}" />
</controls:SettingsCard>
@@ -70,33 +84,42 @@
<Grid Grid.Column="1">
<ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ListViewItem IsEnabled="{Binding IsFound}">
<Grid Opacity="{Binding IsFound, Converter={StaticResource BoolToOpacityConverter}}">
<DataTemplate x:DataType="models:LyricsSearchResult">
<ListViewItem IsEnabled="{x:Bind IsFound}">
<Grid Opacity="{x:Bind IsFound, Converter={StaticResource BoolToOpacityConverter}}">
<RelativePanel Padding="0,6">
<TextBlock
x:Name="SearchedTitle"
RelativePanel.AlignLeftWithPanel="True"
Text="{Binding Title}"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
Text="{x:Bind Title}"
TextWrapping="Wrap"
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedArtists"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="SearchedTitle"
Text="{Binding Artist}"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
Text="{x:Bind Artist}"
TextWrapping="Wrap"
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedAlbum"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.Below="SearchedArtists"
Text="{x:Bind Album}"
TextWrapping="Wrap"
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedProvider"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Text="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
Text="{x:Bind Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
</RelativePanel>
<TextBlock
x:Uid="LyricsSearchControlNotFound"
VerticalAlignment="Center"
Text="Not found"
Visibility="{Binding IsFound, Converter={StaticResource BoolNegationToVisibilityConverter}}" />
Visibility="{x:Bind IsFound, Converter={StaticResource BoolNegationToVisibilityConverter}}" />
</Grid>
</ListViewItem>
</DataTemplate>

View File

@@ -58,7 +58,6 @@
<controls:SettingsCard x:Uid="SettingsPageLyricsBgFontOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB42;}">
<local:ExtendedSlider
Default="30"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
@@ -68,8 +67,7 @@
<controls:SettingsCard x:Uid="SettingsPageLyricsFontStrokeWidth" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC12;}">
<local:ExtendedSlider
Default="0"
Frequency="1"
Maximum="5"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.LyricsFontStrokeWidth, Mode=TwoWay}" />
</controls:SettingsCard>
@@ -184,6 +182,7 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- 字体大小 -->
<controls:SettingsExpander x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E9;}">
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageAutoAdjust">
@@ -192,7 +191,7 @@
<controls:SettingsCard x:Uid="SettingsPagePhoneticText" IsEnabled="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="96"
Maximum="256"
Minimum="12"
ResetButtonVisibility="Collapsed"
Value="{x:Bind LyricsStyleSettings.PhoneticLyricsFontSize, Mode=TwoWay}" />
@@ -200,7 +199,7 @@
<controls:SettingsCard x:Uid="SettingsPageOriginalText" IsEnabled="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="96"
Maximum="256"
Minimum="12"
ResetButtonVisibility="Collapsed"
Value="{x:Bind LyricsStyleSettings.OriginalLyricsFontSize, Mode=TwoWay}" />
@@ -208,7 +207,7 @@
<controls:SettingsCard x:Uid="SettingsPageTranslatedText" IsEnabled="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="96"
Maximum="256"
Minimum="12"
ResetButtonVisibility="Collapsed"
Value="{x:Bind LyricsStyleSettings.TranslatedLyricsFontSize, Mode=TwoWay}" />

View File

@@ -26,11 +26,9 @@
x:Uid="SettingsPageRecordedWindowStatus"
RelativePanel.AlignLeftWithPanel="True"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Button x:Uid="SettingsPageCreateFromCurrent" Command="{x:Bind ViewModel.CopyLyricsWindowStatusCommand}" />
<Button
x:Uid="SettingsPageCreateFromTemplates"
VerticalAlignment="Center"
RelativePanel.AlignRightWithPanel="True">
<Button x:Uid="SettingsPageCreateFromTemplates">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="SettingsPageStandardMode" Command="{x:Bind ViewModel.CreateStandardLyricsWindowStatusCommand}" />
@@ -213,7 +211,6 @@
<controls:SettingsCard x:Uid="SettingsPageWorkAreaHeight" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<uc:ExtendedSlider
Default="64"
Frequency="1"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
Unit="px"

View File

@@ -0,0 +1,27 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public partial class BoolNegationToOpacityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return boolValue ? 0.0 : 1.0;
}
return 0.0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -7,13 +7,13 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class BoolToOpacityConverter : IValueConverter
public partial class BoolToOpacityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
return boolValue ? 1.0 : 0.3;
return boolValue ? 1.0 : 0.0;
}
return 1.0;
}

View File

@@ -0,0 +1,27 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public partial class PathToParentFolderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string path)
{
return Directory.GetParent(path)?.Name ?? "";
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -10,6 +10,7 @@ namespace BetterLyrics.WinUI3.Enums
{
Title,
Album,
Artist
Artist,
Folder
}
}

View File

@@ -37,9 +37,9 @@ namespace BetterLyrics.WinUI3.Helper
return sb.ToString();
}
public static string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
public static string? ReadLyricsCache(string title, string artist, string album, LyricsFormat format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title} - {album}{format.ToFileExtension()}"));
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
@@ -57,9 +57,9 @@ namespace BetterLyrics.WinUI3.Helper
return null;
}
public static void WriteLyricsCache(string title, string artist, string lyrics, LyricsFormat format, string cacheFolderPath)
public static void WriteLyricsCache(string title, string artist, string album, string lyrics, LyricsFormat format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title} - {album}{format.ToFileExtension()}"));
File.WriteAllText(cacheFilePath, lyrics);
}

View File

@@ -18,6 +18,7 @@ using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI;
using static Vanara.PInvoke.Ole32;
@@ -46,43 +47,14 @@ namespace BetterLyrics.WinUI3.Helper
return RandomAccessStreamReference.CreateFromStream(stream);
}
public static async Task<IRandomAccessStream> CreateTextPlaceholderBytesAsync(int width, int height)
public static async Task<IRandomAccessStream> GetAlbumArtPlaceholderAsync()
{
using var device = CanvasDevice.GetSharedDevice();
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
// 随机生成渐变色
Windows.UI.Color RandomColor()
{
var rand = new Random(Guid.NewGuid().GetHashCode());
double h = rand.NextDouble() * 360;
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
}
Windows.UI.Color color1 = RandomColor();
Windows.UI.Color color2 = RandomColor();
using (var ds = renderTarget.CreateDrawingSession())
{
// 绘制线性渐变背景
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(width, height)
};
ds.FillRectangle(0, 0, width, height, gradientBrush);
}
// 保存为 PNG 并转为 byte[]
var stream = new InMemoryRandomAccessStream();
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
stream.Seek(0);
Uri uri = new Uri($"ms-appx:///Assets/AlbumArtPlaceholder.png");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
return stream;
}
public static Task<ThemeColorResult> GetAccentColorAsync(BitmapDecoder decoder, PaletteGeneratorType generatorType)
{
return generatorType switch

View File

@@ -11,6 +11,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using Vanara.PInvoke;
using LyricsData = BetterLyrics.WinUI3.Models.LyricsData;
namespace BetterLyrics.WinUI3.Helper
@@ -19,10 +20,23 @@ namespace BetterLyrics.WinUI3.Helper
{
public List<LyricsData> LyricsDataArr { get; private set; } = [];
public LyricsData? LibreTranslationLyricsData => LyricsDataArr.LastOrDefault();
public void Parse(string title, string artist, string? raw, int? durationMs, LyricsSearchProvider? lyricsSearchProvider)
public void Parse(List<MappedSongSearchQuery> mappedSongSearchQueries, string title, string artist, string album, string? raw, int? durationMs, LyricsSearchProvider? lyricsSearchProvider)
{
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)
@@ -51,22 +65,22 @@ namespace BetterLyrics.WinUI3.Helper
}
}
FillRomanizationLyricsData();
FillTranslationFromCache(title, artist, lyricsSearchProvider);
FillTranslationFromCache(overridenTitle, overridenArtist, overridenAlbum, lyricsSearchProvider);
}
private void FillTranslationFromCache(string title, string artist, LyricsSearchProvider? provider)
private void FillTranslationFromCache(string title, string artist, string album, LyricsSearchProvider? provider)
{
string? translationRaw = null;
switch (provider)
{
case LyricsSearchProvider.QQ:
translationRaw = FileHelper.ReadLyricsCache(title, artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
break;
case LyricsSearchProvider.Kugou:
translationRaw = FileHelper.ReadLyricsCache(title, artist, LyricsFormat.Lrc, PathHelper.KugouTranslationCacheDirectory);
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.KugouTranslationCacheDirectory);
break;
case LyricsSearchProvider.Netease:
translationRaw = FileHelper.ReadLyricsCache(title, artist, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
break;
case LyricsSearchProvider.LrcLib:
break;
@@ -99,7 +113,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
private async Task FillRomanizationLyricsData()
private void FillRomanizationLyricsData()
{
var chinese = LyricsDataArr.Where(x => x.LanguageCode == "zh").FirstOrDefault();
if (chinese != null)

View File

@@ -150,7 +150,8 @@ namespace BetterLyrics.WinUI3.Helper
{
// 补偿曲线
float[] frequencies = { 20, 50, 100, 200, 500, 1000, 2000, 4000, 8000, 16000, 20000 };
float[] gains = { 0.5f, 0.3f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.1f, 0.9f, 0.8f };
//float[] gains = { 0.5f, 0.3f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.1f, 0.9f, 0.8f };
float[] gains = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
if (freq <= frequencies[0])
{
return gains[0];

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 ?? "";
}
}
}

View File

@@ -6,6 +6,7 @@ using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
@@ -31,6 +32,8 @@ namespace BetterLyrics.WinUI3.Helper
private static readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private static readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private static DispatcherQueueTimer? _setLyricsWindowVisibilityByPlayingStatusTimer;
public static void HideWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
@@ -346,27 +349,36 @@ namespace BetterLyrics.WinUI3.Helper
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
}
public static void SetLyricsWindowVisibilityByPlayingStatus()
/// <summary>
///
/// </summary>
/// <param name="dispatcherQueue">请确保此参数指向同一个对象,建议传值 BaseViewModel._dispatcherQueue</param>
public static void SetLyricsWindowVisibilityByPlayingStatus(DispatcherQueue dispatcherQueue)
{
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
_setLyricsWindowVisibilityByPlayingStatusTimer ??= dispatcherQueue.CreateTimer();
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
_setLyricsWindowVisibilityByPlayingStatusTimer.Debounce(() =>
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
{
SetIsWorkArea<LyricsWindow>(false);
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(false);
}
HideWindow<LyricsWindow>();
}
HideWindow<LyricsWindow>();
}
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
SetIsWorkArea<LyricsWindow>(true);
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(true);
}
OpenOrShowWindow<LyricsWindow>();
}
OpenOrShowWindow<LyricsWindow>();
}
}, Constants.Time.DebounceTimeout);
}
}

View File

@@ -16,5 +16,6 @@ namespace BetterLyrics.WinUI3.Models
public string? Title { get; set; }
public string? Artist { get; set; }
public string? Album { get; set; }
}
}

View File

@@ -91,11 +91,6 @@ namespace BetterLyrics.WinUI3.Models
this.OnPropertyChanged(nameof(AlbumArtLayoutSettings));
}
partial void OnAutoShowOrHideWindowChanged(bool value)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
}
public void UpdateMonitorNameAndBounds()
{
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();

View File

@@ -12,9 +12,11 @@ namespace BetterLyrics.WinUI3.Models
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalTitle { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalArtist { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalAlbum { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedTitle { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedArtist { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedAlbum { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMarkedAsPureMusic { get; set; } = false;
@@ -26,8 +28,10 @@ namespace BetterLyrics.WinUI3.Models
{
OriginalTitle = this.OriginalTitle,
OriginalArtist = this.OriginalArtist,
OriginalAlbum = this.OriginalAlbum,
MappedTitle = this.MappedTitle,
MappedArtist = this.MappedArtist,
MappedAlbum = this.MappedAlbum,
IsMarkedAsPureMusic = this.IsMarkedAsPureMusic,
LyricsSearchProvider = this.LyricsSearchProvider
};

View File

@@ -19,6 +19,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MappedSongSearchQuery> MappedSongSearchQueries { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsWindowStatus> WindowBoundsRecords { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<SongsTabInfo> StarredPlaylists { get; set; } = [];
public AppSettings() { }
}

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using Windows.Graphics.Imaging;
using Windows.UI;
@@ -9,7 +10,7 @@ namespace BetterLyrics.WinUI3.Models
public partial class SongInfo : ObservableObject
{
[ObservableProperty]
public partial string? Album { get; set; }
public partial string Album { get; set; }
[ObservableProperty]
public partial string Artist { get; set; }
@@ -31,4 +32,14 @@ namespace BetterLyrics.WinUI3.Models
public SongInfo() { }
}
public static class SongInfoExtensions
{
public static SongInfo Placeholder => new()
{
Title = "N/A",
Album = "N/A",
Artist = "N/A",
};
}
}

View File

@@ -1,8 +1,10 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public class SongsTabInfo
public partial class SongsTabInfo : BaseViewModel
{
public string Name { get; set; }
@@ -10,6 +12,9 @@ namespace BetterLyrics.WinUI3.Models
public bool IsClosable { get; set; }
[ObservableProperty]
public partial bool IsStarred { get; set; }
public CommonSongProperty FilterProperty { get; set; }
public string FilterValue { get; set; }
@@ -19,15 +24,17 @@ namespace BetterLyrics.WinUI3.Models
Name = string.Empty;
Icon = string.Empty;
IsClosable = true;
IsStarred = false;
FilterProperty = CommonSongProperty.Title;
FilterValue = string.Empty;
}
public SongsTabInfo(string name, string icon, bool isClosable, CommonSongProperty filterProperty, string filterValue)
public SongsTabInfo(string name, string icon, bool isClosable, bool isStarred, CommonSongProperty filterProperty, string filterValue)
{
Name = name;
Icon = icon;
IsClosable = isClosable;
IsStarred = isStarred;
FilterProperty = filterProperty;
FilterValue = filterValue;
}

View File

@@ -14,6 +14,7 @@
<Grid>
<canvas:CanvasAnimatedControl
x:Name="LyricsCanvas"
CreateResources="LyricsCanvas_CreateResources"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
</Grid>

View File

@@ -35,5 +35,10 @@ namespace BetterLyrics.WinUI3.Renderer
LyricsCanvas.RemoveFromVisualTree();
LyricsCanvas = null;
}
private void LyricsCanvas_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
ViewModel.CreateResources(sender, args);
}
}
}

View File

@@ -93,6 +93,9 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
case nameof(LyricsWindowStatus.TitleBarArea):
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
break;
case nameof(LyricsWindowStatus.AutoShowOrHideWindow):
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
break;
default:
break;
}
@@ -134,7 +137,7 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)

View File

@@ -98,17 +98,19 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
string overridenTitle = title;
string overridenArtist = artist;
string overridenAlbum = album;
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
var found = _settingsService.AppSettings.MappedSongSearchQueries
.Where(x => x.OriginalTitle == title && x.OriginalArtist == artist)
.Where(x => x.OriginalTitle == overridenTitle && x.OriginalArtist == overridenArtist && x.OriginalAlbum == overridenAlbum)
.FirstOrDefault();
if (found != null)
{
overridenTitle = found.MappedTitle;
overridenArtist = found.MappedArtist;
overridenAlbum = found.MappedAlbum;
_logger.LogInformation("Found mapped song search query: {MappedSongSearchQuery}", found);
@@ -117,6 +119,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
lyricsSearchResult.Title = overridenTitle;
lyricsSearchResult.Artist = overridenArtist;
lyricsSearchResult.Album = overridenAlbum;
lyricsSearchResult.Raw = "[99:00.000]🎶🎶🎶";
return lyricsSearchResult;
}
@@ -124,7 +127,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
var targetProvider = found.LyricsSearchProvider;
if (targetProvider != null)
{
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, album, durationMs, songId, token);
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, overridenAlbum, durationMs, songId, token);
}
}
@@ -135,7 +138,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
continue;
}
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, album, durationMs, null, token);
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, overridenAlbum, durationMs, null, token);
if (lyricsSearchResult.IsFound)
{
@@ -172,12 +175,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
// Check cache first
if (provider.IsRemote())
{
var cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.GetCacheDirectory());
var cachedLyrics = FileHelper.ReadLyricsCache(title, artist, album, lyricsFormat, provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
lyricsSearchResult.Raw = cachedLyrics;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
lyricsSearchResult.Album = album;
return lyricsSearchResult;
}
}
@@ -186,11 +190,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
if (provider == LyricsSearchProvider.LocalMusicFile)
{
lyricsSearchResult = SearchEmbedded(title, artist);
lyricsSearchResult = SearchEmbedded(title, artist, album);
}
else
{
lyricsSearchResult = await SearchFile(title, artist, lyricsFormat);
lyricsSearchResult = await SearchFile(title, artist, album, lyricsFormat);
}
}
else
@@ -210,7 +214,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
lyricsSearchResult = await SearchAmllTtmlDbAsync(title, artist);
lyricsSearchResult = await SearchAmllTtmlDbAsync(title, artist, album);
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(title, artist, album, (int)durationMs);
@@ -229,7 +233,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
if (provider.IsRemote())
{
FileHelper.WriteLyricsCache(title, artist, lyricsSearchResult.Raw!, lyricsFormat, provider.GetCacheDirectory());
FileHelper.WriteLyricsCache(title, artist, album, lyricsSearchResult.Raw!, lyricsFormat, provider.GetCacheDirectory());
}
}
}
@@ -240,7 +244,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return lyricsSearchResult;
}
private async Task<LyricsSearchResult> SearchFile(string title, string artist, LyricsFormat format)
private async Task<LyricsSearchResult> SearchFile(string title, string artist, string album, LyricsFormat format)
{
var lyricsSearchResult = new LyricsSearchResult
{
@@ -263,6 +267,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
lyricsSearchResult.Album = album;
return lyricsSearchResult;
}
}
}
@@ -275,7 +282,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return lyricsSearchResult;
}
private LyricsSearchResult SearchEmbedded(string title, string artist)
private LyricsSearchResult SearchEmbedded(string title, string artist, string album)
{
var lyricsSearchResult = new LyricsSearchResult
{
@@ -291,14 +298,19 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
if ((track.Title == title && track.Artist == artist) || FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
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)))
{
var plain = TagLib.File.Create(file).Tag.Lyrics;
if (!plain.IsNullOrEmpty())
{
lyricsSearchResult.Raw = plain;
lyricsSearchResult.Title = track.Title;
lyricsSearchResult.Artist = artist;
lyricsSearchResult.Artist = track.Artist;
lyricsSearchResult.Album = track.Album;
return lyricsSearchResult;
}
}
}
@@ -308,7 +320,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return lyricsSearchResult;
}
private async Task<LyricsSearchResult> SearchAmllTtmlDbAsync(string title, string artist)
private async Task<LyricsSearchResult> SearchAmllTtmlDbAsync(string title, string artist, string album)
{
var lyricsSearchResult = new LyricsSearchResult
{
@@ -382,6 +394,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = lyrics;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
lyricsSearchResult.Album = album;
}
catch
{
@@ -421,6 +434,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
string? original = null;
string? searchedTitle = null;
string? searchedArtist = null;
string? searchedAlbum = null;
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
{
@@ -428,11 +442,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
original = first.GetProperty("syncedLyrics").GetString();
searchedTitle = first.GetProperty("trackName").GetString();
searchedArtist = first.GetProperty("artistName").GetString();
searchedAlbum = first.GetProperty("albumName").GetString();
}
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = searchedTitle;
lyricsSearchResult.Artist = searchedArtist;
lyricsSearchResult.Album = searchedAlbum;
return lyricsSearchResult;
}
@@ -475,89 +491,93 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}, searcher);
}
if (result is QQMusicSearchResult qqResult)
if (result != null)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
var original = response?.Lyrics;
var translated = response?.Trans;
if (!string.IsNullOrEmpty(translated))
if (result is QQMusicSearchResult qqResult)
{
FileHelper.WriteLyricsCache(
title,
artist,
translated,
LyricsFormat.Lrc,
PathHelper.QQTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = qqResult.Title;
lyricsSearchResult.Artist = qqResult.Artists.Join(" | ");
}
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,
translated,
LyricsFormat.Lrc,
PathHelper.NeteaseTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = neteaseResult.Title;
lyricsSearchResult.Artist = neteaseResult.Artists.Join(" | ");
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
string? original = null;
var candidate = response?.Candidates.FirstOrDefault();
if (candidate != null)
{
original = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.Id, candidate.AccessKey);
if (original != null)
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
var original = response?.Lyrics;
var translated = response?.Trans;
if (!string.IsNullOrEmpty(translated))
{
var parsedList = Lyricify.Lyrics.Parsers.KrcParser.ParseLyrics(original);
if (parsedList != null)
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.QQTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
}
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;
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
string? original = null;
var candidate = response?.Candidates.FirstOrDefault();
if (candidate != null)
{
original = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.Id, candidate.AccessKey);
if (original != null)
{
string translated = "";
foreach (var item in parsedList)
var parsedList = Lyricify.Lyrics.Parsers.KrcParser.ParseLyrics(original);
if (parsedList != null)
{
if (item is Lyricify.Lyrics.Models.FullSyllableLineInfo fullSyllableLineInfo)
string translated = "";
foreach (var item in parsedList)
{
var startTimeSpan = TimeSpan.FromMilliseconds(fullSyllableLineInfo.StartTime ?? 0);
string startTimeStr = startTimeSpan.ToString(@"mm\:ss\.ff");
string chTranslation = fullSyllableLineInfo.Translations.GetValueOrDefault("zh") ?? "";
translated += $"[{startTimeStr}]{chTranslation}\n";
if (item is Lyricify.Lyrics.Models.FullSyllableLineInfo fullSyllableLineInfo)
{
var startTimeSpan = TimeSpan.FromMilliseconds(fullSyllableLineInfo.StartTime ?? 0);
string startTimeStr = startTimeSpan.ToString(@"mm\:ss\.ff");
string chTranslation = fullSyllableLineInfo.Translations.GetValueOrDefault("zh") ?? "";
translated += $"[{startTimeStr}]{chTranslation}\n";
}
}
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.KugouTranslationCacheDirectory
);
}
}
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
translated,
LyricsFormat.Lrc,
PathHelper.KugouTranslationCacheDirectory
);
}
}
}
}
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = kugouResult.Title;
lyricsSearchResult.Artist = kugouResult.Artists.Join(" | ");
lyricsSearchResult.Raw = original;
}
}
lyricsSearchResult.Title = result?.Title;
lyricsSearchResult.Artist = result?.Artists.Join(" | ");
lyricsSearchResult.Album = result?.Album;
return lyricsSearchResult;
}
@@ -575,6 +595,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
lyricsSearchResult.Album = "";
}
return lyricsSearchResult;

View File

@@ -37,7 +37,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
SongInfo?.PlayerId ?? "",
_cachedSongInfo.Title,
_cachedSongInfo.Artist,
_cachedSongInfo?.Album ?? string.Empty,
_cachedSongInfo.Album,
_SMTCAlbumArtBuffer,
token
), token);
@@ -46,7 +46,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (buffer == null)
{
using var placeHolderStream = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
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);
buffer = tempBuffer;

View File

@@ -69,7 +69,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode);
if (originalLangCode == targetLangCode)
{
@@ -83,7 +83,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr, targetLangCode);
if (found >= 0)
{
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
_logger.LogInformation("Found translated text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr[0].SetTranslatedText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
@@ -119,7 +119,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
_logger.LogInformation("Original phonetic code: {OriginalLangCode}", originalLangCode);
if (originalLangCode == "zh" && _settingsService.AppSettings.TranslationSettings.IsChineseRomanizationEnabled)
{
@@ -139,7 +139,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr, targetPhoneticCode);
if (found >= 0)
{
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
_logger.LogInformation("Found phonetic text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr[0].SetPhoneticText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
}
@@ -166,7 +166,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
SongInfo.PlayerId ?? "",
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
SongInfo.Album,
SongInfo.DurationMs ?? 0,
SongInfo.SongId,
token
@@ -174,10 +174,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (token.IsCancellationRequested) return;
LyricsSearchProvider = lyricsSearchResult?.Provider;
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsSearchResult?.IsFound, LyricsSearchProvider?.ToString() ?? "null");
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsSearchResult?.IsFound, LyricsSearchProvider);
var lyricsParser = new LyricsParser();
lyricsParser.Parse(SongInfo.Title, SongInfo.Artist, lyricsSearchResult?.Raw, (int?)SongInfo?.DurationMs, LyricsSearchProvider);
lyricsParser.Parse(
_settingsService.AppSettings.MappedSongSearchQueries.ToList(),
SongInfo.Title, SongInfo.Artist, SongInfo.Album, lyricsSearchResult?.Raw, (int?)SongInfo?.DurationMs, LyricsSearchProvider);
_lyricsDataArr = lyricsParser.LyricsDataArr;
ApplyChinesePreference();
}

View File

@@ -213,14 +213,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSourceProviderInfo(x.Value));
}
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
private async void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
{
if (!_mediaManager.IsStarted) return;
SendFocusedMessagesAsync();
await SendFocusedMessagesAsync();
}
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties? timelineProperties)
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
@@ -241,16 +241,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
{
_cachedPosition = timelineProperties.Position;
_cachedPosition = timelineProperties?.Position ?? TimeSpan.Zero;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties.EndTime));
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties?.EndTime ?? TimeSpan.Zero));
});
}
}
}
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo? playbackInfo)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
@@ -268,7 +268,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
else
{
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
_cachedIsPlaying = playbackInfo?.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
@@ -279,26 +279,28 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
});
}
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProperties)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
if (mediaSession == null)
{
_cachedSongInfo = SongInfoExtensions.Placeholder;
}
string sessionId = mediaSession.Id;
string? sessionId = mediaSession?.Id;
var desiredSession = GetCurrentSession();
//RecordMediaSourceProviderInfo(mediaSession);
if (mediaSession != desiredSession) return;
if (!IsMediaSourceEnabled(sessionId))
if (sessionId != null && !IsMediaSourceEnabled(sessionId))
{
_cachedSongInfo = null;
_cachedSongInfo = SongInfoExtensions.Placeholder;
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (sessionId == Constants.PlayerID.LXMusic)
{
@@ -315,33 +317,33 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
string fixedArtist = mediaProperties.Artist;
string fixedAlbum = mediaProperties.AlbumTitle;
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? songId = null;
if (sessionId == Constants.PlayerID.AppleMusic || sessionId == Constants.PlayerID.AppleMusicAlternative)
{
fixedArtist = mediaProperties.Artist.Split(" — ").FirstOrDefault() ?? mediaProperties.Artist;
fixedAlbum = mediaProperties.Artist.Split(" — ").LastOrDefault() ?? mediaProperties.AlbumTitle;
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
}
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId))
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId ?? ""))
{
songId = mediaProperties.Genres.FirstOrDefault()?.Replace("NCM-", "");
songId = mediaProperties?.Genres.FirstOrDefault()?.Replace("NCM-", "");
}
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
Title = mediaProperties?.Title ?? "N/A",
Artist = fixedArtist,
Album = fixedAlbum,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds,
PlayerId = sessionId,
SongId = songId
};
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_cachedSongInfo.Duration = (int)((_cachedSongInfo.DurationMs ?? 0) / 1000f);
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (sessionId == Constants.PlayerID.LXMusic)
{
@@ -356,7 +358,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
}
else if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
else if (mediaProperties?.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
}
@@ -438,7 +440,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
_cachedSongInfo = null;
_cachedSongInfo = SongInfoExtensions.Placeholder;
_cachedIsPlaying = false;
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
@@ -448,14 +450,21 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private async Task SendFocusedMessagesAsync()
{
var desiredSession = GetCurrentSession();
if (desiredSession == null || desiredSession.ControlSession == null) return;
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
var mediaProps = await desiredSession.ControlSession.TryGetMediaPropertiesAsync();
if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession.ControlSession.GetTimelineProperties());
var desiredSession = GetCurrentSession();
//if (desiredSession == null || desiredSession.ControlSession == null) return;
try
{
mediaProps = await desiredSession?.ControlSession?.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
//if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession?.ControlSession?.GetTimelineProperties());
MediaManager_OnAnyMediaPropertyChanged(desiredSession, mediaProps);
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession.ControlSession.GetPlaybackInfo());
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession?.ControlSession?.GetPlaybackInfo());
}
private void StartSSE()
@@ -654,7 +663,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageCode))
{
_logger.LogInformation("Target language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
_logger.LogInformation("Target LibreTranslate language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
UpdateTranslations();
}
}

View File

@@ -7,7 +7,6 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using System;
@@ -17,14 +16,17 @@ using Windows.Globalization;
namespace BetterLyrics.WinUI3.Services.SettingsService
{
// TODO 初始化时从文件读取到对象,后续独写操作先操纵对象,写入用 Debounce 写入文件
// 新建一个 AppSettings 类
public partial class SettingsService : BaseViewModel, ISettingsService
{
private DispatcherQueueTimer _writeAppSettingsTimer;
public AppSettings AppSettings { get; set; }
public SettingsService()
{
_writeAppSettingsTimer = _dispatcherQueue.CreateTimer();
AppSettings = ReadAppSettings();
AppSettings.PropertyChanged += AppSettings_PropertyChanged;
@@ -46,6 +48,9 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
AppSettings.WindowBoundsRecords.CollectionChanged += AppSettings_CollectionChanged;
AppSettings.WindowBoundsRecords.ItemPropertyChanged += AppSettings_ItemPropertyChanged;
AppSettings.StarredPlaylists.CollectionChanged += AppSettings_CollectionChanged;
AppSettings.StarredPlaylists.ItemPropertyChanged += AppSettings_ItemPropertyChanged;
AppSettings.Version = MetadataHelper.AppVersion;
EnsureMediaSourceProvidersInfo();
@@ -93,12 +98,12 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
private void AppSettings_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
WriteAppSettingsDebounce();
WriteAppSettings();
}
private void AppSettings_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
WriteAppSettingsDebounce();
WriteAppSettings();
}
private void AppSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -111,7 +116,7 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
default:
break;
}
WriteAppSettingsDebounce();
WriteAppSettings();
}
/// <summary>
@@ -132,7 +137,6 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
/// <returns></returns>
public bool ImportSettings(string importPath)
{
// TODO 导入有问题
if (!File.Exists(importPath))
return false;
@@ -161,9 +165,9 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
return data;
}
private void WriteAppSettingsDebounce()
private void WriteAppSettings()
{
_dispatcherQueueTimer.Debounce(() =>
_writeAppSettingsTimer.Debounce(() =>
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{

View File

@@ -249,6 +249,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>Album</value>
</data>
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>Artist</value>
</data>
@@ -431,7 +434,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<value>Single loop</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>Search songs</value>
<value>Search for song title, artist, album or folder path</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>Album</value>
@@ -439,12 +442,18 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
<value>Artist</value>
</data>
<data name="MusicGalleryPageSortByFolder.Content" xml:space="preserve">
<value>Folder</value>
</data>
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
<value>Title</value>
</data>
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
<value>Sort type</value>
</data>
<data name="MusicGalleryPageStarredPlaylist.Content" xml:space="preserve">
<value>Starred playlists</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>Music gallery - BetterLyrics</value>
</data>

View File

@@ -249,6 +249,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>アルバム</value>
</data>
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>アーティスト</value>
</data>
@@ -431,7 +434,7 @@
<value>シングルループ</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>曲を検索します</value>
<value>曲名、アーティスト、アルバム、フォルダパスを検索してください</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>アルバム</value>
@@ -439,12 +442,18 @@
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="MusicGalleryPageSortByFolder.Content" xml:space="preserve">
<value>フォルダー</value>
</data>
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
<value>タイトル</value>
</data>
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
<value>並べ替えタイプ</value>
</data>
<data name="MusicGalleryPageStarredPlaylist.Content" xml:space="preserve">
<value>スター付きプレイリスト</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音楽ギャラリー - BetterLyrics</value>
</data>

View File

@@ -249,6 +249,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>번역 제공자</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>앨범</value>
</data>
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>아티스트</value>
</data>
@@ -431,7 +434,7 @@
<value>단일 루프</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>노래 검색</value>
<value>노래 제목, 아티스트, 앨범 또는 폴더 경로를 검색하세요</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>앨범</value>
@@ -439,12 +442,18 @@
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="MusicGalleryPageSortByFolder.Content" xml:space="preserve">
<value>폴더</value>
</data>
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
<value>제목</value>
</data>
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
<value>정렬 유형</value>
</data>
<data name="MusicGalleryPageStarredPlaylist.Content" xml:space="preserve">
<value>별표 표시된 재생 목록</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>음악 갤러리 - BetterLyrics</value>
</data>

View File

@@ -249,6 +249,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻译来源</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>专辑</value>
</data>
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>艺术家</value>
</data>
@@ -431,7 +434,7 @@
<value>单曲循环</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>搜索歌曲</value>
<value>搜索歌曲名、艺术家、专辑或所在文件夹路径</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>专辑</value>
@@ -439,12 +442,18 @@
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="MusicGalleryPageSortByFolder.Content" xml:space="preserve">
<value>文件夹</value>
</data>
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
<value>标题</value>
</data>
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
<value>排序类型</value>
</data>
<data name="MusicGalleryPageStarredPlaylist.Content" xml:space="preserve">
<value>已加星标的歌单</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音乐库 - BetterLyrics</value>
</data>

View File

@@ -249,6 +249,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻譯來源</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>專輯</value>
</data>
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>藝術家</value>
</data>
@@ -431,7 +434,7 @@
<value>單曲循環</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>搜歌曲</value>
<value>搜歌曲名稱、藝人、專輯或所在資料夾路徑</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>專輯</value>
@@ -439,12 +442,18 @@
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="MusicGalleryPageSortByFolder.Content" xml:space="preserve">
<value>文件夹</value>
</data>
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
<value>標題</value>
</data>
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
<value>排序類型</value>
</data>
<data name="MusicGalleryPageStarredPlaylist.Content" xml:space="preserve">
<value>已加星號的歌单</value>
</data>
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音樂庫 - BetterLyrics</value>
</data>

View File

@@ -1,25 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.TemplateSelector;
public class SongOrderTemplateSelector : DataTemplateSelector
{
public DataTemplate ByTitleTemplate { get; set; }
public DataTemplate ByAlbumTemplate { get; set; }
public DataTemplate ByArtistTemplate { get; set; }
public CommonSongProperty SongOrderType { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
return SongOrderType switch
{
CommonSongProperty.Title => ByTitleTemplate,
CommonSongProperty.Album => ByAlbumTemplate,
CommonSongProperty.Artist => ByArtistTemplate,
_ => ByTitleTemplate
};
}
}

View File

@@ -11,14 +11,12 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial class BaseViewModel : ObservableRecipient
{
private protected readonly DispatcherQueue _dispatcherQueue;
private protected readonly DispatcherQueueTimer _dispatcherQueueTimer;
public BaseViewModel()
{
IsActive = true;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
public void CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
_logger.LogInformation("Creating resources... Reason: {Reason}", args.Reason);
switch (args.Reason)
{
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.FirstTime:
_isDeviceChanged = true;
break;
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.NewDevice:
_isDeviceChanged = true;
break;
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.DpiChanged:
break;
default:
break;
}
}
}
}

View File

@@ -18,6 +18,7 @@ using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;
using static Vanara.PInvoke.Shell32;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
@@ -128,21 +129,20 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
for (int i = 0; i < _spectrumAnalyzer.BarCount; i++)
{
float x = i * pointSpacing;
float amplitude = _spectrumAnalyzer.SmoothSpectrum.Average() * 10 + _spectrumAnalyzer.SmoothSpectrum[i] * 0.5f;
float y = (float)_canvasHeight - amplitude;
float y = _spectrumAnalyzer.SmoothSpectrum[i];
points[i] = new Vector2(x, y);
}
// 限制最高点高度
var minY = points.OrderBy(p => p.Y).FirstOrDefault().Y;
var limitY = _canvasHeight * (1 - 0.1f);
if (minY < limitY)
var maxY = points.OrderByDescending(p => p.Y).FirstOrDefault().Y;
var limitY = _canvasHeight * 0.2f;
if (maxY > limitY)
{
var num = (float)(limitY / minY);
var num = (float)(limitY / maxY);
points = points.Select(p => new Vector2(p.X, p.Y * num)).ToArray();
}
// 防止越过画布边界
points = points.Select(p => new Vector2(p.X, (float)(Math.Min(_canvasHeight, p.Y)))).ToArray();
points = points.Select(p => new Vector2(p.X, (float)(_canvasHeight - p.Y))).ToArray();
// 用于填充的闭合路径
using var pathBuilder = new CanvasPathBuilder(ds);
@@ -176,7 +176,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
var gradientStops = new CanvasGradientStop[]
{
new() { Position = 0.0f, Color = Colors.Transparent },
new() { Position = 0.8f, Color = Colors.Transparent },
new() { Position = 0.7f, Color = Colors.Transparent },
new() { Position = 1.0f, Color = _adaptiveColoredFontColor ?? _albumArtAccentColor1Transition.Value }
};

View File

@@ -26,6 +26,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
private bool _isLayoutChanged = true;
private bool _isCanvasWidthChanged = false;
private bool _isCanvasHeightChanged = false;
@@ -60,6 +62,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private bool _isLyrics3DMatrixChanged = true;
private bool _isDeviceChanged = true;
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
_elapsedTime = args.Timing.ElapsedTime;
@@ -77,7 +81,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
//_effect?.Properties["iTime"] = Convert.ToSingle(TotalTime.TotalSeconds);
if (_isFluidOverlayEnabledChanged)
if (_isDeviceChanged || _isFluidOverlayEnabledChanged)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsFluidOverlayEnabled)
{
@@ -142,8 +146,11 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_isDebugOverlayEnabledChanged = false;
}
_rotateAngle += _coverRotateBaseSpeed * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlaySpeed / 100.0;
_rotateAngle %= Math.PI * 2;
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlaySpeed > 0)
{
_rotateAngle += _coverRotateBaseSpeed * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlaySpeed / 100.0;
_rotateAngle %= Math.PI * 2;
}
if (_isSpectrumOverlayEnabledChanged)
{
@@ -176,7 +183,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
// }
//}
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
if (_isDeviceChanged || _isCanvasWidthChanged || _isCanvasHeightChanged)
{
UpdateSongInfoFontSize();
@@ -200,7 +207,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
}
}
if (_isDisplayTypeChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
if (_isDeviceChanged || _isDisplayTypeChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
_isSongInfoFontSizeChanged || _isSongTitleVisibilityChanged || _isSongArtistsVisibilityChanged ||
_isCanvasWidthChanged || _isCanvasHeightChanged ||
_isAlbumArtSizeChanged)
@@ -320,20 +327,18 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
// 将当前背景图放到 _lastAlbumArtSwBitmap 中 并设置不透明度为 1
// 将新的背景图放到 _albumArtSwBitmap 中 并设置不透明度为 0
// 这样可以实现背景图的连贯渐变效果
if (_albumArtChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
if (_isDeviceChanged || _albumArtChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
_isCanvasHeightChanged || _isCanvasWidthChanged ||
_lyricsBgBrightnessTransition.IsTransitioning ||
_albumArtBgTransition.IsTransitioning)
{
// 必须先在此处重置动画
if (_albumArtChanged)
if (_isDeviceChanged || _albumArtChanged)
{
// 必须先在此处重置动画
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
}
// 更新 last 和 current
if (_albumArtChanged)
{
// 更新 last 和 current
if (_lastAlbumArtSwBitmap != null)
{
_lastAlbumArtCanvasBitmap?.Dispose();
@@ -360,50 +365,37 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_isLyricsLayoutOrientationChanged = false;
_isAlbumArtSizeChanged = false;
if (_isCoverAcrylicEffectAmountChanged)
if (_isDeviceChanged || _isAlbumArtBgOpacityChanged || _isAlbumArtBgBlurAmountChanged || _isCoverAcrylicEffectAmountChanged)
{
UpdateCoverAcrylicOverlay(control);
if (_isDeviceChanged || _isCoverAcrylicEffectAmountChanged)
{
UpdateCoverAcrylicOverlay(control);
}
DisposeAlbumArtBgRenderTarget();
UpdateAlbumArtBgEffect(control);
_isAlbumArtBgEffectChanged = true;
_isCoverAcrylicEffectAmountChanged = false;
}
if (_isAlbumArtBgOpacityChanged)
{
DisposeAlbumArtBgRenderTarget();
UpdateAlbumArtBgEffect(control);
_isAlbumArtBgEffectChanged = true;
_isAlbumArtBgOpacityChanged = false;
}
if (_isAlbumArtBgBlurAmountChanged)
{
DisposeAlbumArtBgRenderTarget();
UpdateAlbumArtBgEffect(control);
_isAlbumArtBgEffectChanged = true;
_isAlbumArtBgBlurAmountChanged = false;
_isCoverAcrylicEffectAmountChanged = false;
}
_albumArtChanged = false;
if (!_isAlbumArtEffectChanged && _albumArtEffect != null)
if (_isDeviceChanged || (!_isAlbumArtEffectChanged && _albumArtEffect != null))
{
UpdateAlbumArtRenderTarget(control);
DisposeAlbumArtEffect();
}
if (!_isAlbumArtBgEffectChanged && _albumArtBgEffect != null)
if (_isDeviceChanged || (!_isAlbumArtBgEffectChanged && _albumArtBgEffect != null))
{
UpdateAlbumArtBgRenderTarget(control);
DisposeAlbumArtBgEffect();
}
if (_isCanvasHeightChanged || _isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
if (_isDeviceChanged || _isCanvasHeightChanged || _isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
{
_maxLyricsWidth = _canvasWidth - _lyricsXTransition.Value - _rightMargin;
_maxLyricsWidth = Math.Max(_maxLyricsWidth, 0);
@@ -458,6 +450,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_lyricsBgBrightnessTransition.Update(_elapsedTime);
_songInfoOpacityTransition.Update(_elapsedTime);
_canvasYScrollTransition.Update(_elapsedTime);
_isDeviceChanged = false;
}
private string AutoSelectFontFamily(string text)

View File

@@ -130,8 +130,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
[ObservableProperty]
public partial bool IsPlaying { get; set; } = false;
private bool _isLayoutChanged = true;
private int _timelineSyncThreshold = 0;
private int _phoneticLyricsFontSize = 18;

View File

@@ -78,8 +78,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
OriginalTitle = _mediaSessionsService.SongInfo.Title,
OriginalArtist = _mediaSessionsService.SongInfo.Artist,
OriginalAlbum = _mediaSessionsService.SongInfo.Album,
MappedTitle = _mediaSessionsService.SongInfo.Title,
MappedArtist = _mediaSessionsService.SongInfo.Artist,
MappedAlbum = _mediaSessionsService.SongInfo.Album,
};
}
else
@@ -95,9 +97,11 @@ namespace BetterLyrics.WinUI3.ViewModels
{
return null;
}
var found = AppSettings.MappedSongSearchQueries
.Where(x => x.OriginalTitle == _mediaSessionsService.SongInfo.Title && x.OriginalArtist == _mediaSessionsService.SongInfo.Artist).FirstOrDefault();
return found;
.Where(x => x.OriginalTitle == _mediaSessionsService.SongInfo.Title && x.OriginalArtist == _mediaSessionsService.SongInfo.Artist && x.OriginalAlbum == _mediaSessionsService.SongInfo.Album);
return found.FirstOrDefault();
}
[RelayCommand]
@@ -118,7 +122,7 @@ namespace BetterLyrics.WinUI3.ViewModels
return await _lyricsSearchService.SearchAllAsync(
MappedSongSearchQuery.MappedTitle,
MappedSongSearchQuery.MappedArtist,
_mediaSessionsService.SongInfo?.Album ?? "",
MappedSongSearchQuery.MappedAlbum,
_mediaSessionsService.SongInfo?.DurationMs ?? 0, token);
}, token)];
IsSearching = false;
@@ -166,6 +170,12 @@ namespace BetterLyrics.WinUI3.ViewModels
MappedSongSearchQuery?.MappedArtist = MappedSongSearchQuery?.OriginalArtist ?? string.Empty;
}
[RelayCommand]
private void ResetMappedAlbum()
{
MappedSongSearchQuery?.MappedAlbum = MappedSongSearchQuery?.OriginalAlbum ?? string.Empty;
}
partial void OnSelectedLyricsSearchResultChanged(LyricsSearchResult? value)
{
MappedSongSearchQuery?.LyricsSearchProvider = value?.Provider;
@@ -173,8 +183,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
var lyricsParser = new LyricsParser();
lyricsParser.Parse(
MappedSongSearchQuery?.MappedTitle ?? "",
MappedSongSearchQuery?.MappedArtist ?? "",
AppSettings.MappedSongSearchQueries.ToList(),
MappedSongSearchQuery?.OriginalTitle ?? "",
MappedSongSearchQuery?.OriginalArtist ?? "",
MappedSongSearchQuery?.OriginalAlbum ?? "",
value?.Raw, (int?)_mediaSessionsService.SongInfo?.DurationMs, value?.Provider);
LyricsDataArr = [.. lyricsParser.LyricsDataArr];
}

View File

@@ -16,13 +16,13 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using Vanara.PInvoke;
using Windows.Foundation;
using Windows.System;
using Windows.UI;
using WinRT.Interop;
using WinUIEx;
@@ -39,6 +39,7 @@ namespace BetterLyrics.WinUI3
private readonly ILiveStatesService _liveStatesService;
private ForegroundWindowWatcher? _fgWindowWatcher = null;
private DispatcherQueueTimer? _fgWindowWatcherTimer = null;
public LyricsWindowViewModel(ISettingsService settingsService, IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService)
{
@@ -54,7 +55,7 @@ namespace BetterLyrics.WinUI3
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
}
[ObservableProperty] public partial AppSettings AppSettings { get; set; }
@@ -142,11 +143,13 @@ namespace BetterLyrics.WinUI3
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
_fgWindowWatcherTimer = _dispatcherQueue.CreateTimer();
_fgWindowWatcher = new ForegroundWindowWatcher(
hwnd,
fgHwnd =>
{
_dispatcherQueueTimer.Debounce(() =>
_fgWindowWatcherTimer.Debounce(() =>
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsAlwaysOnTop &&
_liveStatesService.LiveStates.LyricsWindowStatus.IsAlwaysOnTopPolling &&

View File

@@ -37,6 +37,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly MediaTimelineController _timelineController = new();
private readonly SystemMediaTransportControls _smtc;
private readonly DispatcherQueueTimer _refreshSongsTimer;
// All songs
private List<Track> _tracks = [];
// Songs in current playlist
@@ -44,6 +46,9 @@ namespace BetterLyrics.WinUI3.ViewModels
// Filtered songs based on search query for current playlist
private List<Track> _filteredTracks = [];
[ObservableProperty]
public partial AppSettings AppSettings { get; set; }
[ObservableProperty]
public partial bool IsLocalMediaNotFound { get; set; }
@@ -92,9 +97,12 @@ namespace BetterLyrics.WinUI3.ViewModels
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService)
{
_settingsService = settingsService;
_refreshSongsTimer = _dispatcherQueue.CreateTimer();
SongsTabInfoList.Add(new SongsTabInfo(App.ResourceLoader!.GetString("MusicGalleryPageAllSongs"), "\uE8A9", false, CommonSongProperty.Title, string.Empty));
_settingsService = settingsService;
AppSettings = _settingsService.AppSettings;
SongsTabInfoList.Add(new SongsTabInfo(App.ResourceLoader!.GetString("MusicGalleryPageAllSongs"), "\uE8A9", false, false, CommonSongProperty.Title, string.Empty));
RefreshSongs();
@@ -259,7 +267,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public void RefreshSongs()
{
_dispatcherQueueTimer.Debounce(() =>
_refreshSongsTimer.Debounce(() =>
{
IsDataLoading = true;
_tracks.Clear();
@@ -319,6 +327,9 @@ namespace BetterLyrics.WinUI3.ViewModels
case CommonSongProperty.Artist:
_playlistTracks = _tracks.Where(t => t.Artist.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
break;
case CommonSongProperty.Folder:
_playlistTracks = _tracks.Where(t => t.GetParentFolderPath().Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
break;
default:
break;
}
@@ -338,7 +349,8 @@ namespace BetterLyrics.WinUI3.ViewModels
_filteredTracks = _playlistTracks.Where(t =>
t.Title.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
t.Artist.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
t.Album.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase)).ToList();
t.Album.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
t.GetParentFolderPath().Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase)).ToList();
}
private void ApplySongOrderType()
@@ -363,6 +375,12 @@ namespace BetterLyrics.WinUI3.ViewModels
o => ((Track)o).Album
);
break;
case CommonSongProperty.Folder:
GroupedTracks = _filteredTracks.GetGroupedBy(
t => LanguageHelper.GetOrderChar(t.GetParentFolderName()),
o => ((Track)o).Album
);
break;
}
}

View File

@@ -29,35 +29,6 @@
<dev:SnowFlakeEffect FlakeCount="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.SnowFlakeOverlayAmount, Mode=OneWay}" Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<!-- No music playing placeholder -->
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
x:Uid="MainPageNoMusicPlaying"
HorizontalAlignment="Center"
FontFamily="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsCJKFontFamily, Mode=OneWay}"
FontSize="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.OriginalLyricsFontSize, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
ComparisonCondition="Equal"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<!-- Bottom command area -->
<Grid
x:Name="BottomCommandGrid"

View File

@@ -13,7 +13,6 @@
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:muxm="using:Microsoft.UI.Xaml.Media"
xmlns:templateselector="using:BetterLyrics.WinUI3.TemplateSelector"
xmlns:ui="using:CommunityToolkit.WinUI"
Unloaded="Page_Unloaded"
mc:Ignorable="d">
@@ -93,111 +92,193 @@
</Flyout>
</Grid.Tag>
<Pivot
VerticalAlignment="Top"
ItemsSource="{x:Bind ViewModel.SongsTabInfoList, Mode=OneWay}"
SelectedIndex="{x:Bind ViewModel.SelectedSongsTabInfoIndex, Mode=TwoWay}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="models:SongsTabInfo">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid
Grid.Column="0"
Background="Transparent"
ColumnSpacing="6"
Tapped="PlaylistGrid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="{x:Bind Icon}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Name}" />
</Grid>
<Button
Grid.Column="1"
Click="PlaylistCloseButton_Click"
Content="{ui:FontIcon FontSize=10,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8BB;}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind IsClosable, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate />
</Pivot.ItemTemplate>
</Pivot>
<StackPanel Spacing="6">
<StackPanel VerticalAlignment="Top" Orientation="Horizontal">
<Button x:Uid="MusicGalleryPageStarredPlaylist">
<Button.Flyout>
<Flyout FlyoutPresenterStyle="{StaticResource FlyoutGhostStyle}">
<Grid>
<ListView
x:Name="StarredPlaylistsListView"
ItemsSource="{x:Bind ViewModel.AppSettings.StarredPlaylists, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:SongsTabInfo">
<Grid Tapped="StarredPlaylistsListViewItemGrid_Tapped">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="{x:Bind Icon}" />
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Name}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="N/A">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.StarredPlaylists.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.StarredPlaylists.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
</Grid>
</Flyout>
</Button.Flyout>
</Button>
<Pivot ItemsSource="{x:Bind ViewModel.SongsTabInfoList, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.SelectedSongsTabInfoIndex, Mode=TwoWay}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="models:SongsTabInfo">
<Grid ColumnSpacing="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
Click="PlaylistFavButton_Click"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind IsClosable, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<Button.Content>
<Grid>
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="10"
Glyph="&#xE734;"
Opacity="{x:Bind IsStarred, Converter={StaticResource BoolNegationToOpacityConverter}, Mode=OneWay}">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="10"
Glyph="&#xE735;"
Opacity="{x:Bind IsStarred, Converter={StaticResource BoolToOpacityConverter}, Mode=OneWay}">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
</Button.Content>
</Button>
<Grid
Grid.Column="1"
Background="Transparent"
ColumnSpacing="6"
Tapped="PlaylistGrid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="{x:Bind Icon}" />
<TextBlock
Grid.Column="1"
Margin="0,0,0,2"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Name}" />
</Grid>
<Button
Grid.Column="2"
Click="PlaylistCloseButton_Click"
Content="{ui:FontIcon FontSize=10,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE711;}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind IsClosable, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate />
</Pivot.ItemTemplate>
</Pivot>
</StackPanel>
<Grid Margin="0,56,0,0" VerticalAlignment="Top">
<AutoSuggestBox
x:Name="SongSearchBox"
x:Uid="MusicGalleryPageSongSearchBox"
HorizontalAlignment="Stretch"
QueryIcon="Find"
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
</Grid>
<Grid Margin="0,96,0,0" VerticalAlignment="Top">
<StackPanel
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="6">
<ToggleButton
x:Name="SelectAllToggleButton"
x:Uid="MusicGalleryPageSelectAll"
Click="SelectAllToggleButton_Click" />
<Button x:Uid="MusicGalleryPagePlayAll" Click="PlayAllButton_Click" />
<Button x:Uid="MusicGalleryPageAddToPlayingQueue">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToNext" Click="AddSongToQueueNextMenuFlyoutItem_Click" />
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToEnd" Click="AddSongToQueueEndMenuFlyoutItem_Click" />
</MenuFlyout>
</Button.Flyout>
</Button>
<Button x:Uid="MusicGalleryPageAddToCustomList" Visibility="Collapsed">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="MusicGalleryPageNewPlaylist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE710;}" />
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
<StackPanel
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="6">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock
x:Uid="MusicGalleryPageSortType"
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<controls:Segmented
x:Name="Segmented"
SelectedIndex="{x:Bind ViewModel.SongOrderType, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}"
SelectionMode="Single">
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByTitle" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC4F;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByAlbum" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE93C;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByArtist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
</controls:Segmented>
<Grid>
<StackPanel
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="6">
<ToggleButton
x:Name="SelectAllToggleButton"
x:Uid="MusicGalleryPageSelectAll"
Click="SelectAllToggleButton_Click" />
<Button x:Uid="MusicGalleryPagePlayAll" Click="PlayAllButton_Click" />
<Button x:Uid="MusicGalleryPageAddToPlayingQueue">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToNext" Click="AddSongToQueueNextMenuFlyoutItem_Click" />
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToEnd" Click="AddSongToQueueEndMenuFlyoutItem_Click" />
</MenuFlyout>
</Button.Flyout>
</Button>
<Button x:Uid="MusicGalleryPageAddToCustomList" Visibility="Collapsed">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="MusicGalleryPageNewPlaylist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE710;}" />
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<StackPanel
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="6">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock
x:Uid="MusicGalleryPageSortType"
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<controls:Segmented
x:Name="Segmented"
SelectedIndex="{x:Bind ViewModel.SongOrderType, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}"
SelectionMode="Single">
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByTitle" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC4F;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByAlbum" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE93C;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByArtist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByFolder" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B7;}" />
</controls:Segmented>
</StackPanel>
</StackPanel>
</Grid>
<SemanticZoom Margin="0,140,0,0">
</StackPanel>
<SemanticZoom Margin="0,120,0,0">
<SemanticZoom.ZoomedInView>
<ListView
x:Name="SongListView"
@@ -214,46 +295,43 @@
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" CornerRadius="6">
<Image Source="{Binding EmbeddedPictures[0].PictureData, Mode=OneWay, Converter={StaticResource ByteArrayToImageConverter}}" Stretch="UniformToFill" />
</Grid>
<!-- 歌曲名 -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 基本信息 -->
<Grid Grid.Column="1">
<StackPanel VerticalAlignment="Center" Spacing="6">
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
<StackPanel Orientation="Horizontal" Spacing="6">
<Grid Background="{ThemeResource AccentAcrylicBackgroundFillColorBaseBrush}" CornerRadius="4">
<TextBlock
Margin="4,2"
FontSize="12"
Text="{Binding AudioFormat.ShortName}" />
</Grid>
<HyperlinkButton Padding="0" Click="ArtistHyperlibkButton_Click">
<TextBlock Text="{Binding Artist}" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</StackPanel>
</Grid>
<!-- 歌手名 -->
<HyperlinkButton
Grid.Column="2"
VerticalAlignment="Center"
Click="ArtistHyperlibkButton_Click"
Tag="{Binding Artist}">
<TextBlock Text="{Binding Artist}" TextWrapping="Wrap" />
</HyperlinkButton>
<!-- 专辑名 -->
<HyperlinkButton
Grid.Column="3"
VerticalAlignment="Center"
Click="AlbumHyperlibkButton_Click"
Tag="{Binding Album}">
<HyperlinkButton Grid.Column="2" Click="AlbumHyperlibkButton_Click">
<TextBlock Text="{Binding Album}" TextWrapping="Wrap" />
</HyperlinkButton>
<!-- 年份 -->
<TextBlock
Grid.Column="4"
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
@@ -261,12 +339,19 @@
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<HyperlinkButton
Grid.Column="5"
VerticalAlignment="Center"
Click="PathHyperlibkButton_Click"
Content="{Binding Path, Converter={StaticResource PathToParentFolderConverter}}" />
</Grid>
</Grid>
</DataTemplate>
@@ -310,7 +395,7 @@
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
<Grid Margin="0,140,0,0" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid Margin="0,120,0,0" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"

View File

@@ -142,15 +142,22 @@ namespace BetterLyrics.WinUI3.Views
private void ArtistHyperlibkButton_Click(object sender, RoutedEventArgs e)
{
var artist = (string)((HyperlinkButton)sender).Tag;
var playlist = new SongsTabInfo(artist, "\uEFA9", true, CommonSongProperty.Artist, artist);
var artist = ((Track)((FrameworkElement)sender).DataContext).Artist;
var playlist = new SongsTabInfo(artist, "\uEFA9", true, false, CommonSongProperty.Artist, artist);
ViewModel.UpdateSelectedPlaylist(playlist);
}
private void AlbumHyperlibkButton_Click(object sender, RoutedEventArgs e)
{
var album = (string)((HyperlinkButton)sender).Tag;
var playlist = new SongsTabInfo(album, "\uE93C", true, CommonSongProperty.Album, album);
var album = ((Track)((FrameworkElement)sender).DataContext).Album;
var playlist = new SongsTabInfo(album, "\uE93C", true, false, CommonSongProperty.Album, album);
ViewModel.UpdateSelectedPlaylist(playlist);
}
private void PathHyperlibkButton_Click(object sender, RoutedEventArgs e)
{
var track = ((Track)((FrameworkElement)sender).DataContext);
var playlist = new SongsTabInfo(track.GetParentFolderName(), "\uE8B7", true, false, CommonSongProperty.Folder, track.GetParentFolderPath());
ViewModel.UpdateSelectedPlaylist(playlist);
}
@@ -182,5 +189,29 @@ namespace BetterLyrics.WinUI3.Views
{
ViewModel.CancelRefreshSongs();
}
private void PlaylistFavButton_Click(object sender, RoutedEventArgs e)
{
var playlist = (SongsTabInfo)((FrameworkElement)sender).DataContext;
var targetStatus = !playlist.IsStarred;
if (targetStatus)
{
ViewModel.AppSettings.StarredPlaylists.Add(playlist);
}
else
{
ViewModel.AppSettings.StarredPlaylists.Remove(playlist);
}
playlist.IsStarred = targetStatus;
}
private void StarredPlaylistsListViewItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
{
var songsTabInfo = ((SongsTabInfo)((FrameworkElement)sender).DataContext);
if (!ViewModel.SongsTabInfoList.Contains(songsTabInfo))
{
ViewModel.SongsTabInfoList.Add(songsTabInfo);
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

View File

@@ -1 +1,201 @@
[原页面已更改,点此返回项目主页](https://github.com/jayfunc/BetterLyrics)
![](Promotion/banner.png)
<div align=center>
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="96">
</div>
<h2 align=center>
BetterLyrics
</h2>
<h4 align="center">
🤩 一款优雅且高度自定义的歌词/播放器应用,基于 WinUI3/Win2D 构建
</h4>
<div align="center">
[**_📖 点按此处浏览软件操作说明_**](https://github.com/jayfunc/BetterLyrics/wiki)
</div>
<div align=center>
![Static Badge](https://img.shields.io/badge/Language-C%23-purple) ![Static Badge](https://img.shields.io/badge/License-MIT-red) ![Static Badge](https://img.shields.io/badge/IDE-Visual%20Studio-purple) ![Static Badge](https://img.shields.io/badge/Framework-WinUI%203-blue)
</div>
<div align=center>
[![GitHub Repo stars](https://img.shields.io/github/stars/jayfunc/BetterLyrics)](https://github.com/jayfunc/BetterLyrics/stargazers)
</div>
<div align="center">
<mark>**_💞 BetterLyrics 的发展离不开每一位贡献者、反馈者和用户的全力支持。_**</mark>
</div>
## 🎉 该项目入选少数派推荐文章!
文章链接:[BetterLyrics - 一款专为 Windows 打造的沉浸式流畅歌词显示软件](https://sspai.com/post/101028)
## 🔈 反馈交流群
[QQ 群](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) (1054700388) | [Discord Server](https://discord.gg/5yAQPnyCKv) | [Telegram Group](https://t.me/+svhSLZ7awPsxNGY1)
## 🌟 特色功能
- 🌠 **精美的用户界面**
- 流畅、高度自定义的样式、动画、动效
- 沉浸式流体背景
- 透视/扇形歌词
- 雪花效果
- 多种歌词滚动函数
- ...
- ↔️ **强大的歌词翻译**
- 本地机器翻译 (支持 30 多种语言)
- 自动读取本地音乐文件内嵌歌词
- 🧩 **多种歌词源**
- 💾 本地源
- 音乐文件 (内嵌歌词)
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) 文件 (传统格式、增强格式)
- [.eslrc](https://github.com/ESLyric/release) 文件
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) 文件
- ☁️ 在线源
- QQ 音乐
- 网易云音乐
- 酷狗音乐
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
- [LRCLIB](https://lrclib.net/)
- <details><summary>⚠️ Apple Music (需要额外配置)</summary>
- 浏览器打开 Apple Music打开开发者工具。刷新网页回到开发者工具窗口筛选出 Fetch/XHR选择一个请求在请求标头中找到 media-user-token 并复制其值。
- 打开 BetterLyrics 转到播放源设置。在 Media-User-Token (for Apple Music) 中粘贴复制的值并点按右侧对勾。
- 🎶 **支持众多音乐播放器**
- 点击 [此处](https://github.com/jayfunc/BetterLyrics/wiki/Known-supported-music-players-(configuration-guidance)) 查看详细信息
- 🪟 **多种显示模式**
- **标准模式**
- 标准的歌词窗口样式,沉浸式的音乐歌词体验。
- **停靠模式**
- 停靠在屏幕上/下边缘的轻量歌词窗口,工作休闲互不打扰。
- **桌面模式**
- 悬浮在所有应用上层,不能被选中,但能直击你的使用需求。
- **更多模式...**
- 等你来发现...
- 🧠 **智能化行为**
- 根据歌曲播放状态自动显隐歌词窗口
## 屏幕截图
![](Screenshots/fs2.png)
![](Screenshots/std.png)
![](Screenshots/narrow.png)
![](Screenshots/Snipaste_2025-10-31_19-23-17.png)
![](Screenshots/Snipaste_2025-10-31_19-27-34.png)
![](Screenshots/dock.png)
![](Screenshots/desktop.png)
> ⚠️ 由于 GIF 格式帧率限制,效果仅作展示。请以实机效果为准。
![](Screenshots/PixPin_2025-10-24_18-13-44.gif)
![](Screenshots/PixPin_2025-10-24_18-17-17.gif)
## 演示
在 [哔哩哔哩](https://www.bilibili.com/video/BV1QRstz1EGt/) 上观看于 2025 年 10 月 21 日上传的演示视频
## 即刻体验
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" width="200"/>
</a>
**无限期**免费试用版和付费版**无任何区别**
☕ 如果喜欢该软件,请考虑 [捐赠](#捐赠) 或在 **Microsoft Store** 🧧 购买, 感谢您的支持! 🥰
无法从 Microsoft Store 下载?点按 [此处](https://github.com/jayfunc/BetterLyrics/wiki/Alternative-way-to-download-and-install) 查看其他下载安装方式
## 构建
在构建之前确保替换文件 `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFMTemplate``BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFM.cs`
## 💖 感谢
| 项目/包 | 描述 |
| :--- | :--- |
| [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) | 为 QQ、网易、酷狗在线歌词源提供歌词抓取、解密、解析等一系列方法 |
| [lrclib](https://github.com/tranxuanthang/lrclib) | LRCLIB 歌词 API |
| [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics) | Apple Music 歌词抓取Python 实现) |
| [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) | 从音乐文件提取图片 |
| [WinUIEx](https://github.com/dotMorten/WinUIEx) | 提供有关窗口的开箱即用的 Win32 API |
| [TagLib#](https://github.com/mono/taglib-sharp) | 读取音乐文件内嵌的原始歌词内容 |
| [Vanara](https://github.com/dahall/Vanara) | 提供开箱即用的 Win32 API |
| [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) | 离线翻译核心 |
| [Isolation](https://github.com/Storyteller-Studios/Isolation) | 动态流体背景 |
| [SpectrumVisualization](https://github.com/Johnwikix/SpectrumVisualization) | 频谱图 |
| [DevWinUI](https://github.com/ghost1372/DevWinUI) | 为 WinUI3 提供众多开箱即用的功能 |
| ... | ... |
### 教程、博客等
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
- [Bilibili -【WinUI3】SystemBackdropController定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D 中的游戏循环CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
## 💡 灵感来源
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
## ✍️ 协助翻译
找不到你的语言?有更好的翻译?没关系!😆
现在访问 https://crowdin.com/project/betterlyrics/invite?h=c9bfb28fce061484883c0891e7a26f9b2592556 即刻为本应用提供翻译,成为贡献者!
## 星标记录
<div style="display: flex; justify-content: space-around; align-items: flex-start;">
<img src="https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date" width="100%" >
</div>
## 欢迎反馈问题、提交代码
如果发现 Bug 请在 Issues 内提出,同时也欢迎任何想法、建议。
## 捐赠
如果你喜欢本应用,请考虑捐赠支持开发者。这将有助于本应用的长远发展。
通过以下途径捐赠:
- [PayPal](https://paypal.me/zhefangpay)
- [Buy Me a Coffee](https://buymeacoffee.com/founchoo)
- <details><summary>支付宝</summary>
![](Donate/Alipay.jpg)
</detais>
- <details><summary>微信</summary>
![](Donate/WeChatReward.png)
</details>
## ⚠️ 免责声明
本项目按“原样”提供,不提供任何形式的担保。
所有歌词、字体、图标及其他第三方资源均为其各自版权所有者的财产。
本项目作者不主张对这些资源的所有权。
本项目为非商业用途,不得用于侵犯任何权利。
用户有责任确保其使用符合适用的法律和许可协议。

View File

@@ -9,17 +9,17 @@ BetterLyrics
</h2>
<h4 align="center">
Your dynamic lyrics display tool, built with WinUI 3 and Win2D, works with in-app playback and other players
🤩 An elegant and deeply customizable lyrics & player app, built with WinUI3/Win2D
</h4>
<div align="center">
[**_Click here to view wiki_**](https://github.com/jayfunc/BetterLyrics/wiki)
[**_📖 Click here to view wiki_**](https://github.com/jayfunc/BetterLyrics/wiki)
</div>
<div align=center>
![Static Badge](https://img.shields.io/badge/Language-C%23-purple) ![Static Badge](https://img.shields.io/badge/License-MIT-red) ![Static Badge](https://img.shields.io/badge/IDE-Visual%20Studio-purple) ![Static Badge](https://img.shields.io/badge/Framework-WinUI%203-blue)
</div>
@@ -30,6 +30,18 @@ Your dynamic lyrics display tool, built with WinUI 3 and Win2D, works with in-ap
</div>
<div align="center">
<mark>**_💞 BetterLyrics is made possible by all its contributors, bug reporters and users._**</mark>
</div>
<div align="center">
**_[中文版 README 请点按此处](https://github.com/jayfunc/BetterLyrics/blob/dev/README.CN.md)_**
</div>
## 🎉 This project was featured by SSPAI!
Check out the article: [BetterLyrics An immersive and smooth lyrics display tool designed for Windows](https://sspai.com/post/101028)
@@ -144,7 +156,7 @@ Before you build, make sure that you have already replaced `BetterLyrics\BetterL
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
## Inspired by
## 💡 Inspired by
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)