Compare commits

...

5 Commits

Author SHA1 Message Date
Zhe Fang
c0f2b4106a fix: change discord presence dep 2025-11-19 18:38:18 -05:00
Zhe Fang
c704edde19 chores: code cleanup 2025-11-19 17:50:59 -05:00
Zhe Fang
35de1ef7db add: roman parse for ncm and amll-ttml-db lyrics source 2025-11-19 17:49:24 -05:00
Zhe Fang
1c8840e053 fix: search control title missing 2025-11-19 11:40:58 -05:00
Zhe Fang
2d0839d777 fix: apple music metadata fetch missing 2025-11-19 11:32:05 -05:00
54 changed files with 1262 additions and 817 deletions

View File

@@ -143,7 +143,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />

View File

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

View File

@@ -34,6 +34,7 @@
<None Remove="Controls\LyricsWindowSwitchControl.xaml" />
<None Remove="Controls\MediaSettingsControl.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\PropertyRow.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\LyricsSearchWindow.xaml" />
@@ -65,7 +66,6 @@
<PackageReference Include="csharp-kana" Version="1.0.2" />
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
<PackageReference Include="DevWinUI.Controls" Version="9.5.0" />
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.0" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" />
@@ -74,7 +74,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
@@ -91,6 +91,7 @@
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.2.1" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.2.1" />
<PackageReference Include="Vanara.Windows.Shell" Version="4.2.1" />
<PackageReference Include="VCollab.DiscordRichPresence" Version="1.7.0" />
<PackageReference Include="WinUIEx" Version="2.9.0" />
<PackageReference Include="z440.atl.core" Version="7.8.0" />
</ItemGroup>
@@ -333,6 +334,11 @@
<ItemGroup>
<Folder Include="TemplateSelector\" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PropertyRow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AboutControl.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Constants
namespace BetterLyrics.WinUI3.Constants
{
public class PlayerName
{

View File

@@ -136,66 +136,25 @@
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchResult">
<ListViewItem IsEnabled="{x:Bind IsFound}">
<StackPanel Padding="3,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
<HyperlinkButton
Padding="0"
Content="{x:Bind Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}"
NavigateUri="{x:Bind Reference, FallbackValue=about:blank}" />
<!-- Title -->
<Grid ColumnSpacing="12" Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="LyricsSearchControlTitle" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind Title, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Artist -->
<Grid ColumnSpacing="12" Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="LyricsSearchControlArtist" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Album -->
<Grid ColumnSpacing="12" Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="LyricsSearchControlAlbum" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind Album, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Duration -->
<StackPanel
Orientation="Horizontal"
Spacing="6"
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock x:Uid="LyricsSearchControlDurauion" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Duration}" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="s" />
</StackPanel>
<!-- Match percentage -->
<StackPanel
Orientation="Horizontal"
Spacing="6"
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock x:Uid="LyricsPageMatchPercentage" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind MatchPercentage}" />
<StackPanel Padding="0,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
<local:PropertyRow
Margin="-12,0,0,0"
Link="{x:Bind Reference, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind Reference, TargetNullValue=N/A, Mode=OneWay}"
Value="{x:Bind Provider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
<!-- Lyrics search result -->
<StackPanel Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}">
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind Title, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind Album, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsSearchControlDurauion"
Unit="s"
Value="{x:Bind Duration, TargetNullValue=N/A, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsPageMatchPercentage"
Unit="%"
Value="{x:Bind MatchPercentage, Mode=OneWay}" />
</StackPanel>
<!-- NOT FOUND -->
<TextBlock
@@ -266,23 +225,30 @@
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding LanguageCode, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
<DataTemplate x:DataType="models:LyricsData">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{x:Bind LanguageCode, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
<InfoBadge
x:Uid="LyricsSearchControlAutoGenerated"
Margin="6,0,0,0"
Style="{StaticResource StringInfoBadgeStyle}"
Visibility="{x:Bind AutoGenerated, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding LyricsLines, Mode=OneWay}" SelectionChanged="ListView_SelectionChanged">
<DataTemplate x:DataType="models:LyricsData">
<ListView ItemsSource="{x:Bind LyricsLines, Mode=OneWay}" SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate x:DataType="models:LyricsLine">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{Binding StartMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{x:Bind StartMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock
Margin="1,0"
Foreground="{ThemeResource SystemFillColorNeutralBrush}"
Text="-" />
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{Binding EndMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock Margin="6,0" Text="{Binding OriginalText}" />
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{x:Bind EndMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
<TextBlock Margin="6,0" Text="{x:Bind OriginalText, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -2,7 +2,6 @@ using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
@@ -27,7 +26,6 @@ namespace BetterLyrics.WinUI3.Controls
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
public LyricsWindowSettingsControl()
{
@@ -87,7 +85,7 @@ namespace BetterLyrics.WinUI3.Controls
clonedData.IsDefault = false;
var json = System.Text.Json.JsonSerializer.Serialize(clonedData, SourceGenerationContext.Default.LyricsWindowStatus);
File.WriteAllText(file.Path, json);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("ExportSettingsSuccess"));
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
}
}
}
@@ -147,7 +145,7 @@ namespace BetterLyrics.WinUI3.Controls
if (data != null)
{
ViewModel.AppSettings.WindowBoundsRecords.Add(data);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("ImportSettingsSuccess"));
ToastHelper.ShowToast("ImportSettingsSuccess", null, InfoBarSeverity.Success);
}
}
}

View File

@@ -255,96 +255,74 @@
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<!-- Provider info -->
<!-- Realtime info -->
<TextBlock x:Uid="SettingsPageRealtimeStatus" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard ContentAlignment="Left">
<!-- Playback source info -->
<Expander
x:Uid="SettingsPagePlaybackStatus"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Playback source -->
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="SettingsPagePlaybackSource" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
<!-- Playback source ID -->
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="SettingsPagePlaybackSourceID" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Song title -->
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="LyricsSearchControlTitle" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Song artists -->
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="LyricsSearchControlArtist" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Song album -->
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Uid="LyricsSearchControlAlbum" Grid.Column="0" />
<RichTextBlock Grid.Column="1" Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run Text="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</Grid>
<!-- Lyrics source -->
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock x:Uid="LyricsPageLyricsProviderPrefix" />
<HyperlinkButton
Padding="0"
Content="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}"
IsEnabled="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.IsFound, Mode=OneWay}"
NavigateUri="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}" />
</StackPanel>
<!-- Translation source -->
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock x:Uid="LyricsPageTranslationProviderPrefix" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.MediaSessionsService.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
</StackPanel>
<!-- Match percentage -->
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock x:Uid="LyricsPageMatchPercentage" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
</StackPanel>
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
</dev:SettingsCard>
</Expander>
<!-- Song info -->
<Expander
x:Uid="SettingsPageSongStatus"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Song title -->
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Song artists -->
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Song album -->
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Song duration -->
<local:PropertyRow
x:Uid="LyricsSearchControlDurauion"
Unit="s"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Duration, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
</Expander>
<!-- Search result info -->
<Expander
x:Uid="SettingsPageSearchResultStatus"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Searched title -->
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Searched artists -->
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Searched album -->
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Searched duration -->
<local:PropertyRow
x:Uid="LyricsSearchControlDurauion"
Unit="s"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Duration, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Lyrics source -->
<local:PropertyRow
x:Uid="LyricsPageLyricsProviderPrefix"
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay}"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
<!-- Translation source -->
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
<!-- Match percentage -->
<local:PropertyRow
x:Uid="LyricsPageMatchPercentage"
Unit="%"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
</StackPanel>
</Expander>
<dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" />

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.PropertyRow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid HorizontalAlignment="Left" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
VerticalAlignment="Top"
Text="{x:Bind Header, Mode=OneWay}" />
<Grid
Grid.Column="1"
VerticalAlignment="Top"
PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<RichTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Visibility="{x:Bind TextVisibility, Mode=OneWay}">
<Paragraph>
<Run Text="{x:Bind Value, Mode=OneWay}" />
<Run Text="{x:Bind Unit, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
<HyperlinkButton
Margin="0,-2,0,0"
Padding="0"
HorizontalContentAlignment="Left"
Click="OnLinkClicked"
Content="{x:Bind Value, Mode=OneWay}"
Visibility="{x:Bind LinkVisibility, Mode=OneWay}" />
</Grid>
<Button
x:Name="CopyButton"
Grid.Column="1"
Margin="8,-2,0,0"
Padding="4"
VerticalAlignment="Top"
Click="OnCopyClicked"
Opacity="0">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Copy" />
</ToolTipService.ToolTip>
<Button.OpacityTransition>
<ScalarTransition />
</Button.OpacityTransition>
<Grid>
<FontIcon
x:Name="CopyIcon"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8C8;"
Opacity="1">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<FontIcon
x:Name="CheckIcon"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE73E;"
Opacity="0">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
</Button>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,115 @@
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class PropertyRow : UserControl
{
public PropertyRow()
{
this.InitializeComponent();
}
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register(nameof(Header), typeof(string), typeof(PropertyRow), new PropertyMetadata(string.Empty));
public string Value
{
get => (string)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(string), typeof(PropertyRow), new PropertyMetadata(string.Empty));
public string Link
{
get => (string)GetValue(LinkProperty);
set => SetValue(LinkProperty, value);
}
public static readonly DependencyProperty LinkProperty =
DependencyProperty.Register(nameof(Link), typeof(string), typeof(PropertyRow), new PropertyMetadata(string.Empty));
public string Unit
{
get => (string)GetValue(UnitProperty);
set => SetValue(UnitProperty, value);
}
public static readonly DependencyProperty UnitProperty =
DependencyProperty.Register(nameof(Unit), typeof(string), typeof(PropertyRow), new PropertyMetadata(string.Empty));
private Visibility TextVisibility => Link == string.Empty ? Visibility.Visible : Visibility.Collapsed;
private Visibility LinkVisibility => Link != string.Empty ? Visibility.Visible : Visibility.Collapsed;
private void OnPointerEntered(object sender, PointerRoutedEventArgs e)
{
if (!string.IsNullOrEmpty(Value))
{
CopyButton.Opacity = 1;
}
}
private void OnPointerExited(object sender, PointerRoutedEventArgs e)
{
CopyButton.Opacity = 0;
}
private void OnCopyClicked(object sender, RoutedEventArgs e)
{
var targetValue = string.IsNullOrEmpty(Link) ? Value : Link;
if (string.IsNullOrEmpty(targetValue)) return;
try
{
DataPackage dataPackage = new DataPackage();
dataPackage.SetText(targetValue);
Clipboard.SetContent(dataPackage);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Copy failed: {ex.Message}");
return;
}
CheckIcon.Opacity = 1;
CopyIcon.Opacity = 0;
this.DispatcherQueue.TryEnqueue(async () =>
{
await Task.Delay(1000);
CheckIcon.Opacity = 0;
CopyIcon.Opacity = 1;
});
}
private async void OnLinkClicked(object sender, RoutedEventArgs e)
{
Uri.TryCreate(Link, UriKind.Absolute, out var uri);
if (uri != null)
{
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
await Windows.System.Launcher.LaunchUriAsync(uri);
}
else if (uri.Scheme == Uri.UriSchemeFile)
{
await LauncherHelper.SelectAndShowFile(uri.LocalPath);
}
}
}
}
}

View File

@@ -1,3 +1,4 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Services.ResourceService;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -92,11 +93,11 @@ namespace BetterLyrics.WinUI3.Controls
bool registered = GlobalHotKeyHook.IsHotKeyRegistered(Shortcut);
if (registered)
{
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("SettingsPageShortcutRegSuccessInfo"));
ToastHelper.ShowToast("SettingsPageShortcutRegSuccessInfo", null, InfoBarSeverity.Success);
}
else
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("SettingsPageShortcutRegFailInfo"));
ToastHelper.ShowToast("SettingsPageShortcutRegFailInfo", null, InfoBarSeverity.Error);
}
}
}

View File

@@ -1,7 +1,5 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Helper;
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsSearchProvider

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Enums
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsSearchType
{

View File

@@ -1,7 +1,4 @@
using BetterLyrics.WinUI3.Enums;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Extensions
{

View File

@@ -1,8 +1,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Extensions
{

View File

@@ -1,9 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using NTextCat.Commons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BetterLyrics.WinUI3.Extensions
{

View File

@@ -3,7 +3,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Serialization;
using System;
using System.IO;

View File

@@ -14,6 +14,9 @@ namespace BetterLyrics.WinUI3.Helper
private static readonly RankedLanguageIdentifier _identifier;
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
public const string ChineseCode = "zh";
public const string JapaneseCode = "ja";
public static List<ExtendedLanguage> SupportedTranslationTargetLanguages { get; set; } =
[
new ExtendedLanguage("ar"),
@@ -105,7 +108,6 @@ namespace BetterLyrics.WinUI3.Helper
public static string? DetectLanguageCode(string? text)
{
if (text == null) return null;
var guessList = _identifier.Identify(text);
string? code = guessList?.FirstOrDefault()?.Item1.Iso639_2T;
code = code switch

View File

@@ -1,475 +0,0 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models;
using Lyricify.Lyrics.Parsers;
using NTextCat.Commons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace BetterLyrics.WinUI3.Helper
{
public partial class LyricsParser
{
public List<LyricsData> LyricsDataArr { get; private set; } = [];
public void Parse(SongInfo? songInfo, LyricsSearchResult? lyricsSearchResult)
{
LyricsDataArr = [];
if (lyricsSearchResult?.Raw == null)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder((int)(songInfo?.DurationMs ?? 0)));
}
else
{
switch (lyricsSearchResult.Raw.DetectFormat())
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(lyricsSearchResult.Raw);
break;
case LyricsFormat.Qrc:
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Krc:
ParseQrcKrc(KrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(lyricsSearchResult.Raw);
break;
default:
break;
}
}
FillRomanizationLyricsData();
FillTranslationFromCache(lyricsSearchResult);
}
private void FillTranslationFromCache(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Translation != null)
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.QQ:
case LyricsSearchProvider.Kugou:
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Translation);
break;
default:
break;
}
}
}
private void FillRomanizationLyricsData()
{
var chinese = LyricsDataArr.FirstOrDefault(x => x.LanguageCode == "zh");
if (chinese != null)
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.PinyinCode,
LyricsLines = chinese.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToPinyin(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToPinyin(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.JyutpingCode,
LyricsLines = chinese.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToJyutping(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToJyutping(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
var japanese = LyricsDataArr.FirstOrDefault(x => x.LanguageCode == "ja");
if (japanese != null)
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.RomajiCode,
LyricsLines = japanese.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToRomaji(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToRomaji(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
private void ParseLrc(string raw)
{
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var lrcLines =
new List<(int time, string text, List<(int time, string text)> syllables)>();
// 支持 [mm:ss.xx]字、<mm:ss.xx>字,毫秒两位或三位
var syllableRegex = SyllableRegex();
foreach (var line in lines)
{
var matches = syllableRegex.Matches(line);
var syllables = new List<(int, string)>();
for (int i = 0; i < matches.Count; i++)
{
var m = matches[i];
int min = int.Parse(m.Groups[2].Value);
int sec = int.Parse(m.Groups[3].Value);
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
int totalMs = min * 60_000 + sec * 1000 + ms;
string text = m.Groups[6].Value;
syllables.Add((totalMs, text));
}
if (syllables.Count > 1)
{
lrcLines.Add(
(
syllables[0].Item1,
string.Concat(syllables.Select(s => s.Item2)),
syllables
)
);
}
else
{
// 普通LRC行
Regex? bracketRegex = LrcRegex();
var bracketMatches = bracketRegex.Matches(line);
string content = line;
int? lineStartTime = null;
if (bracketMatches.Count > 0)
{
var m = bracketMatches[0];
int min = int.Parse(m.Groups[1].Value);
int sec = int.Parse(m.Groups[2].Value);
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
lineStartTime = min * 60_000 + sec * 1000 + ms;
content = bracketRegex!.Replace(line, "").Trim();
if (content == "//") content = "";
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
}
}
}
// 按时间分组
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
int languageCount = 0;
if (grouped != null && grouped.Count > 0)
{
// 计算最大语言数量
languageCount = grouped.Max(g => g.Count());
}
// 初始化每种语言的歌词列表
//LyricsDataArr.Clear();
int langStartIndex = LyricsDataArr.Count;
for (int i = 0; i < languageCount; i++) LyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
if (grouped != null)
{
foreach (var group in grouped)
{
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
{
var (start, text, syllables) = linesInGroup[langIdx];
var line = new LyricsLine
{
StartMs = start,
OriginalText = text,
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
}
}
LyricsDataArr[langStartIndex + langIdx].LyricsLines.Add(line);
}
// 没有翻译行则不补原文,直接跳过
}
}
}
}
private void ParseTtml(string raw)
{
try
{
List<LyricsLine> originalLines = [];
List<LyricsLine> translationLines = [];
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
if (body == null) return;
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
foreach (var p in ps)
{
// 句级时间
string? pBegin = p.Attribute("begin")?.Value;
string? pEnd = p.Attribute("end")?.Value;
int pStartMs = ParseTtmlTime(pBegin);
int pEndMs = ParseTtmlTime(pEnd);
// 只获取一级span且排除 ttm:role="x-bg" 的 span 和 ttm:role="x-roman"
var spans = p.Elements()
.Where(s => s.Name.LocalName == "span" &&
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg" &&
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-roman")
.ToList();
// 原文和翻译分离
var originalTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-translation")
.ToList();
var translationTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
.ToList();
// 处理原文span后的空白
for (int i = 0; i < originalTextSpans.Count; i++)
{
var span = originalTextSpans[i];
var nextNode = span.NodesAfterSelf().FirstOrDefault();
if (nextNode is XText textNode)
{
span.Value += textNode.Value;
}
}
// 拼接空白字符后的原文
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
var originalCharTimings = new List<LyricsChar>();
int originalStartIndex = 0;
foreach (var span in originalTextSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
originalCharTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = sEndMs,
StartIndex = originalStartIndex,
Text = span.Value
});
originalStartIndex += span.Value.Length;
}
if (originalTextSpans.Count == 0)
originalText = p.Value;
originalLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = originalText,
LyricsChars = originalCharTimings,
});
// 翻译
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
var translationCharTimings = new List<LyricsChar>();
int translationStartIndex = 0;
foreach (var span in translationTextSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
translationCharTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = sEndMs,
StartIndex = translationStartIndex,
Text = span.Value
});
translationStartIndex += span.Value.Length;
}
if (translationTextSpans.Count > 0)
{
translationLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = translationText,
LyricsChars = translationCharTimings,
});
}
}
LyricsDataArr.Add(new LyricsData(originalLines));
if (translationLines.Count > 0)
LyricsDataArr.Add(new LyricsData(translationLines));
}
catch
{
// 解析失败,忽略
}
}
private static int ParseTtmlTime(string? t)
{
if (string.IsNullOrWhiteSpace(t))
return 0;
t = t.Trim();
// 支持 "1.000s"
if (t.EndsWith("s"))
{
if (
double.TryParse(
t.TrimEnd('s'),
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out double seconds
)
)
return (int)(seconds * 1000);
}
else
{
var parts = t.Split(':');
if (parts.Length == 3)
{
// hh:mm:ss.xxx
int h = int.Parse(parts[0]);
int m = int.Parse(parts[1]);
double s = double.Parse(
parts[2],
System.Globalization.CultureInfo.InvariantCulture
);
return (int)((h * 3600 + m * 60 + s) * 1000);
}
else if (parts.Length == 2)
{
// mm:ss.xxx
int m = int.Parse(parts[0]);
double s = double.Parse(
parts[1],
System.Globalization.CultureInfo.InvariantCulture
);
return (int)((m * 60 + s) * 1000);
}
else if (parts.Length == 1)
{
// ss.xxx
if (
double.TryParse(
parts[0],
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out double s
)
)
return (int)(s * 1000);
}
}
return 0;
}
private void ParseQrcKrc(List<Lyricify.Lyrics.Models.ILineInfo>? lines)
{
lines = lines?.Where(x => x.Text != string.Empty).ToList();
List<LyricsLine> lyricsLines = [];
if (lines != null && lines.Count > 0)
{
lyricsLines = [];
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
{
var lineRead = lines[lineIndex];
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = lineRead.EndTime ?? 0,
OriginalText = lineRead.Text,
LyricsChars = [],
};
var syllables = (lineRead as Lyricify.Lyrics.Models.SyllableLineInfo)?.Syllables;
if (syllables != null)
{
int startIndex = 0;
for (
int syllableIndex = 0;
syllableIndex < syllables.Count;
syllableIndex++
)
{
var syllable = syllables[syllableIndex];
var charTiming = new LyricsChar
{
StartMs = syllable.StartTime,
EndMs = syllable.EndTime,
Text = syllable.Text,
StartIndex = startIndex,
};
lineWrite.LyricsChars.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
lyricsLines.Add(lineWrite);
}
}
LyricsDataArr.Add(new LyricsData(lyricsLines));
}
[GeneratedRegex(@"\[(\d*):(\d*)(\.|\:)(\d*)\]")]
private static partial Regex LrcRegex();
[GeneratedRegex(@"(\[|\<)(\d*):(\d*)\.(\d*)(\]|\>)([^\[\]\<\>]*)")]
private static partial Regex SyllableRegex();
}
}

View File

@@ -2,42 +2,31 @@
using F23.StringSimilarity;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace BetterLyrics.WinUI3.Helper
{
public static class MetadataComparer
public static partial class MetadataComparer
{
// 权重配置 (总和 1.0)
private const double WeightTitle = 0.40;
private const double WeightArtist = 0.40;
private const double WeightAlbum = 0.10;
private const double WeightDuration = 0.10;
// 实例化算法 (JaroWinkler 适合短字符串匹配)
// JaroWinkler 适合短字符串匹配
private static readonly JaroWinkler _algo = new JaroWinkler();
/// <summary>
/// 计算 SongInfo 和 LyricsSearchResult 的相似度 (0-100)
/// </summary>
public static int CalculateScore(SongInfo local, LyricsSearchResult remote)
{
if (local == null || remote == null) return 0;
// 1. 标题相似度
double titleScore = GetStringSimilarity(local.Title, remote.Title);
// 2. 艺术家相似度 (需要处理数组顺序)
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
// 3. 专辑相似度
double albumScore = GetStringSimilarity(local.Album, remote.Album);
// 4. 时长相似度 (基于毫秒 vs 秒的转换和容差)
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
// 5. 加权汇总
double totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
(albumScore * WeightAlbum) +
@@ -46,9 +35,31 @@ namespace BetterLyrics.WinUI3.Helper
return (int)Math.Round(totalScore * 100);
}
public static int CalculateScore(SongInfo songInfo, string filePathOrName)
{
if (songInfo == null || string.IsNullOrWhiteSpace(filePathOrName)) return 0;
string fileName = Path.GetFileNameWithoutExtension(filePathOrName);
string fileFingerprint = CreateSortedFingerprint(fileName);
var infoParts = new List<string>();
if (!string.IsNullOrEmpty(songInfo.Title))
infoParts.Add(songInfo.Title);
if (songInfo.Artists != null)
infoParts.AddRange(songInfo.Artists);
string infoRaw = string.Join(" ", infoParts);
string infoFingerprint = CreateSortedFingerprint(infoRaw);
double score = _algo.Similarity(infoFingerprint, fileFingerprint);
return (int)Math.Round(score * 100);
}
private static double GetStringSimilarity(string? s1, string? s2)
{
// 归一化:转小写,去空白
s1 = s1?.Trim().ToLowerInvariant() ?? "";
s2 = s2?.Trim().ToLowerInvariant() ?? "";
@@ -63,8 +74,7 @@ namespace BetterLyrics.WinUI3.Helper
if (localArtists == null || localArtists.Length == 0) return 0.0;
if (remoteArtists == null || remoteArtists.Length == 0) return 0.0;
// 技巧:将艺术家数组排序并连接,避免顺序不同导致的不匹配
// 例如: ["Jay-Z", "Linkin Park"] 和 ["Linkin Park", "Jay-Z"] 应该是一样的
// 将艺术家数组排序并连接,避免顺序不同导致的不匹配
var s1 = string.Join(" ", localArtists.OrderBy(a => a).Select(a => a.Trim().ToLowerInvariant()));
var s2 = string.Join(" ", remoteArtists.OrderBy(a => a).Select(a => a.Trim().ToLowerInvariant()));
@@ -78,7 +88,6 @@ namespace BetterLyrics.WinUI3.Helper
double localSeconds = localMs / 1000.0;
double diff = Math.Abs(localSeconds - remoteSeconds.Value);
// 容差逻辑:
// 差距 <= 3秒100% 相似
// 差距 >= 20秒0% 相似
// 中间线性插值
@@ -89,8 +98,24 @@ namespace BetterLyrics.WinUI3.Helper
if (diff <= PerfectTolerance) return 1.0;
if (diff >= MaxTolerance) return 0.0;
// 线性递减公式
return 1.0 - ((diff - PerfectTolerance) / (MaxTolerance - PerfectTolerance));
}
private static string CreateSortedFingerprint(string input)
{
if (string.IsNullOrWhiteSpace(input)) return "";
input = input.ToLowerInvariant();
string cleaned = NonWordCharactersRegex().Replace(input, " ");
var tokens = cleaned.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
.OrderBy(t => t); // 排序
return string.Join(" ", tokens);
}
[GeneratedRegex(@"[\p{P}\p{S}]")]
private static partial Regex NonWordCharactersRegex();
}
}

View File

@@ -12,23 +12,23 @@ namespace BetterLyrics.WinUI3.Helper
/// <param name="value">要保存的值</param>
public static void Save(string resource, string key, string value)
{
var vault = new PasswordVault();
// 删除旧值(避免重复存储)
try
{
var vault = new PasswordVault();
var oldCredential = vault.Retrieve(resource, key);
if (oldCredential != null)
{
vault.Remove(oldCredential);
}
vault.Add(new PasswordCredential(resource, key, value));
}
catch
{
// 没有旧值就忽略
}
vault.Add(new PasswordCredential(resource, key, value));
}
/// <summary>
@@ -39,9 +39,10 @@ namespace BetterLyrics.WinUI3.Helper
/// <returns>存储的值,若不存在则返回 null</returns>
public static string? Get(string resource, string key)
{
var vault = new PasswordVault();
try
{
var vault = new PasswordVault();
var credential = vault.Retrieve(resource, key);
credential.RetrievePassword();
return credential.Password;
@@ -57,9 +58,10 @@ namespace BetterLyrics.WinUI3.Helper
/// </summary>
public static void Delete(string resource, string key)
{
var vault = new PasswordVault();
try
{
var vault = new PasswordVault();
var credential = vault.Retrieve(resource, key);
vault.Remove(credential);
}

View File

@@ -1,7 +1,6 @@
using BetterLyrics.WinUI3.Services.ResourceService;
using CommunityToolkit.Mvvm.DependencyInjection;
using System;
using System.Linq;
namespace BetterLyrics.WinUI3.Helper
{
@@ -9,13 +8,13 @@ namespace BetterLyrics.WinUI3.Helper
{
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
public const string PinyinCode = "zh-pinyin";
public const string JyutpingCode = "zh-jyutping";
public const string RomajiCode = "ja-romaji";
public const string PinyinCode = "zh-cmn-pinyin";
public const string JyutpingCode = "zh-yue-jyutping";
public const string RomanCode = "ja-latin";
public static bool IsPhoneticCode(string code)
{
return code == PinyinCode || code == JyutpingCode || code == RomajiCode;
return code == PinyinCode || code == JyutpingCode || code == RomanCode;
}
public static string GetDisplayName(string code)
@@ -26,7 +25,7 @@ namespace BetterLyrics.WinUI3.Helper
return _resourceService.GetLocalizedString("Pinyin");
case JyutpingCode:
return _resourceService.GetLocalizedString("Jyutping");
case RomajiCode:
case RomanCode:
return _resourceService.GetLocalizedString("Romaji");
default:
throw new ArgumentOutOfRangeException(nameof(code));

View File

@@ -1,5 +1,4 @@
using BetterLyrics.WinUI3.Constants;
using Lyricify.Lyrics.Providers;
using System.Collections.Generic;
using System.Text.RegularExpressions;

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

View File

@@ -0,0 +1,23 @@
using BetterLyrics.WinUI3.Services.ResourceService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
namespace BetterLyrics.WinUI3.Helper
{
public class ToastHelper
{
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
public static void ShowToast(string localizedTitleKey, string? description, InfoBarSeverity severity)
{
AppNotification notification = new AppNotificationBuilder()
.AddText(_resourceService.GetLocalizedString(localizedTitleKey))
.AddText(description)
.BuildNotification();
AppNotificationManager.Default.Show(notification);
}
}
}

View File

@@ -1,14 +1,9 @@
using DevWinUI;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Vanara.Windows.Shell;

View File

@@ -14,7 +14,6 @@ using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
using Vanara.Windows.Shell;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using WinRT.Interop;

View File

@@ -17,6 +17,7 @@ namespace BetterLyrics.WinUI3.Models
get => field ?? LanguageHelper.DetectLanguageCode(WrappedOriginalText);
set => field = value;
}
public bool AutoGenerated { get; set; } = false;
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
public bool IsWordByWord => LyricsLines.Any(x => x.LyricsChars.Count != 0);

View File

@@ -10,8 +10,17 @@ namespace BetterLyrics.WinUI3.Models
public LyricsSearchProvider Provider { get; set; }
public string? Raw { get; set; }
/// <summary>
/// 翻译也可能位于 <see cref="Raw"/>
/// </summary>
public string? Translation { get; set; }
/// <summary>
/// 音译也可能位于 <see cref="Raw"/>
/// </summary>
public string? Transliteration { get; set; }
public string? Title { get; set; }
public string[]? Artists { get; set; }
public string? Album { get; set; }

View File

@@ -1,17 +1,11 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Collections;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{

View File

@@ -0,0 +1,130 @@
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
[GeneratedRegex(@"\[(\d*):(\d*)(\.|\:)(\d*)\]")]
private static partial Regex LrcRegex();
[GeneratedRegex(@"(\[|\<)(\d*):(\d*)\.(\d*)(\]|\>)([^\[\]\<\>]*)")]
private static partial Regex SyllableRegex();
private void ParseLrc(string raw)
{
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var lrcLines =
new List<(int time, string text, List<(int time, string text)> syllables)>();
// 支持 [mm:ss.xx]字、<mm:ss.xx>字,毫秒两位或三位
var syllableRegex = SyllableRegex();
foreach (var line in lines)
{
var matches = syllableRegex.Matches(line);
var syllables = new List<(int, string)>();
for (int i = 0; i < matches.Count; i++)
{
var m = matches[i];
int min = int.Parse(m.Groups[2].Value);
int sec = int.Parse(m.Groups[3].Value);
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
int totalMs = min * 60_000 + sec * 1000 + ms;
string text = m.Groups[6].Value;
syllables.Add((totalMs, text));
}
if (syllables.Count > 1)
{
lrcLines.Add(
(
syllables[0].Item1,
string.Concat(syllables.Select(s => s.Item2)),
syllables
)
);
}
else
{
// 普通LRC行
Regex? bracketRegex = LrcRegex();
var bracketMatches = bracketRegex.Matches(line);
string content = line;
int? lineStartTime = null;
if (bracketMatches.Count > 0)
{
var m = bracketMatches[0];
int min = int.Parse(m.Groups[1].Value);
int sec = int.Parse(m.Groups[2].Value);
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
lineStartTime = min * 60_000 + sec * 1000 + ms;
content = bracketRegex!.Replace(line, "").Trim();
if (content == "//") content = "";
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
}
}
}
// 按时间分组
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
int languageCount = 0;
if (grouped != null && grouped.Count > 0)
{
// 计算最大语言数量
languageCount = grouped.Max(g => g.Count());
}
// 初始化每种语言的歌词列表
int langStartIndex = LyricsDataArr.Count;
for (int i = 0; i < languageCount; i++) LyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
if (grouped != null)
{
foreach (var group in grouped)
{
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
{
var (start, text, syllables) = linesInGroup[langIdx];
var line = new LyricsLine
{
StartMs = start,
OriginalText = text,
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
}
}
LyricsDataArr[langStartIndex + langIdx].LyricsLines.Add(line);
}
// 没有翻译行则不补原文,直接跳过
}
}
}
}
}
}

View File

@@ -0,0 +1,59 @@
using BetterLyrics.WinUI3.Models;
using System.Collections.Generic;
using System.Linq;
namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
private void ParseQrcKrc(List<Lyricify.Lyrics.Models.ILineInfo>? lines)
{
lines = lines?.Where(x => x.Text != string.Empty).ToList();
List<LyricsLine> lyricsLines = [];
if (lines != null && lines.Count > 0)
{
lyricsLines = [];
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
{
var lineRead = lines[lineIndex];
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = lineRead.EndTime ?? 0,
OriginalText = lineRead.Text,
LyricsChars = [],
};
var syllables = (lineRead as Lyricify.Lyrics.Models.SyllableLineInfo)?.Syllables;
if (syllables != null)
{
int startIndex = 0;
for (
int syllableIndex = 0;
syllableIndex < syllables.Count;
syllableIndex++
)
{
var syllable = syllables[syllableIndex];
var charTiming = new LyricsChar
{
StartMs = syllable.StartTime,
EndMs = syllable.EndTime,
Text = syllable.Text,
StartIndex = startIndex,
};
lineWrite.LyricsChars.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
lyricsLines.Add(lineWrite);
}
}
LyricsDataArr.Add(new LyricsData(lyricsLines));
}
}
}

View File

@@ -0,0 +1,208 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
private void ParseTtml(string raw)
{
try
{
List<LyricsLine> originalLines = [];
List<LyricsLine> translationLines = [];
List<LyricsLine> romanLines = [];
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
if (body == null) return;
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
foreach (var p in ps)
{
// 句级时间
string? pBegin = p.Attribute("begin")?.Value;
string? pEnd = p.Attribute("end")?.Value;
int pStartMs = ParseTtmlTime(pBegin);
int pEndMs = ParseTtmlTime(pEnd);
// 只获取一级span
var spans = p.Elements()
.Where(s => s.Name.LocalName == "span")
.ToList();
var romanTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-roman")
.ToList();
var originalTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == null)
.ToList();
var translationTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
.ToList();
// 处理原文span后的空白
for (int i = 0; i < originalTextSpans.Count; i++)
{
var span = originalTextSpans[i];
var nextNode = span.NodesAfterSelf().FirstOrDefault();
if (nextNode is XText textNode)
{
span.Value += textNode.Value;
}
}
// 拼接空白字符后的原文
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
var originalCharTimings = new List<LyricsChar>();
int originalStartIndex = 0;
foreach (var span in originalTextSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
originalCharTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = sEndMs,
StartIndex = originalStartIndex,
Text = span.Value
});
originalStartIndex += span.Value.Length;
}
if (originalTextSpans.Count == 0)
{
originalText = p.Value;
}
originalLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = originalText,
LyricsChars = originalCharTimings,
});
// 解析 x-role
ParseTtmlXRole(spans, translationLines, "x-translation", pStartMs, pEndMs);
ParseTtmlXRole(spans, romanLines, "x-roman", pStartMs, pEndMs);
}
LyricsDataArr.Add(new LyricsData(originalLines));
if (translationLines.Count > 0)
{
LyricsDataArr.Add(new LyricsData(translationLines));
}
if (romanLines.Count > 0)
{
LyricsDataArr.Add(new LyricsData(romanLines) { LanguageCode = PhoneticHelper.RomanCode });
}
}
catch
{
// 解析失败,忽略
}
}
private void ParseTtmlXRole(List<XElement> sourceSpans, List<LyricsLine> saveLyricsLines, string xRole, int pStartMs, int? pEndMs)
{
var textSpans = sourceSpans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == xRole)
.ToList();
string text = string.Concat(textSpans.Select(s => s.Value));
var charTimings = new List<LyricsChar>();
int startIndex = 0;
foreach (var span in textSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
charTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = sEndMs,
StartIndex = startIndex,
Text = span.Value
});
startIndex += span.Value.Length;
}
if (textSpans.Count > 0)
{
saveLyricsLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = text,
LyricsChars = charTimings,
});
}
}
private static int ParseTtmlTime(string? t)
{
if (string.IsNullOrWhiteSpace(t))
return 0;
t = t.Trim();
// 支持 "1.000s"
if (t.EndsWith("s"))
{
if (
double.TryParse(
t.TrimEnd('s'),
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out double seconds
)
)
return (int)(seconds * 1000);
}
else
{
var parts = t.Split(':');
if (parts.Length == 3)
{
// hh:mm:ss.xxx
int h = int.Parse(parts[0]);
int m = int.Parse(parts[1]);
double s = double.Parse(
parts[2],
System.Globalization.CultureInfo.InvariantCulture
);
return (int)((h * 3600 + m * 60 + s) * 1000);
}
else if (parts.Length == 2)
{
// mm:ss.xxx
int m = int.Parse(parts[0]);
double s = double.Parse(
parts[1],
System.Globalization.CultureInfo.InvariantCulture
);
return (int)((m * 60 + s) * 1000);
}
else if (parts.Length == 1)
{
// ss.xxx
if (
double.TryParse(
parts[0],
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out double s
)
)
return (int)(s * 1000);
}
}
return 0;
}
}
}

View File

@@ -0,0 +1,164 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using Lyricify.Lyrics.Parsers;
using System.Collections.Generic;
using System.Linq;
namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
public List<LyricsData> LyricsDataArr { get; private set; } = [];
public void Parse(SongInfo? songInfo, LyricsSearchResult? lyricsSearchResult)
{
LyricsDataArr = [];
if (lyricsSearchResult?.Raw == null)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder((int)(songInfo?.DurationMs ?? 0)));
}
else
{
switch (lyricsSearchResult.Raw.DetectFormat())
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(lyricsSearchResult.Raw);
break;
case LyricsFormat.Qrc:
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Krc:
ParseQrcKrc(KrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(lyricsSearchResult.Raw);
break;
default:
break;
}
}
LoadTranslation(lyricsSearchResult);
LoadTransliteration(lyricsSearchResult);
GenerateTransliterationLyricsData();
}
private void LoadTranslation(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Translation != null)
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.QQ:
case LyricsSearchProvider.Kugou:
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Translation);
break;
default:
break;
}
}
}
private void LoadTransliteration(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Transliteration != null)
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Transliteration);
LyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
break;
default:
break;
}
}
}
/// <summary>
/// 在音译不存在的情况下生成音译歌词
/// </summary>
private void GenerateTransliterationLyricsData()
{
var main = LyricsDataArr.FirstOrDefault();
if (main != null)
{
string? languageCode = main.LanguageCode;
if (languageCode == LanguageHelper.ChineseCode)
{
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.PinyinCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.PinyinCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToPinyin(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToPinyin(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.JyutpingCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.JyutpingCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToJyutping(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToJyutping(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
else if (languageCode == LanguageHelper.JapaneseCode)
{
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.RomanCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.RomanCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToRomaji(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToRomaji(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
}
}
}
}

View File

@@ -1,4 +1,6 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using System;
using System.Net;
using System.Net.Http;
@@ -67,7 +69,7 @@ namespace BetterLyrics.WinUI3.Providers
_client.DefaultRequestHeaders.Add("Accept-Language", $"{_language},en;q=0.9");
}
public async Task<string?> GetLyricsAsync(string id)
private async Task<string?> GetLyricsAsync(string id)
{
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/songs/{id}";
var url = apiUrl + $"?include[songs]=lyrics,syllable-lyrics&l={_language}";
@@ -109,9 +111,14 @@ namespace BetterLyrics.WinUI3.Providers
return null;
}
public async Task<string> SearchSongInfoAsync(string artist, string title)
public async Task<LyricsSearchResult> SearchSongInfoAsync(Models.SongInfo songInfo)
{
var query = $"{artist} {title}";
LyricsSearchResult lyricsSearchResult = new()
{
Provider = Enums.LyricsSearchProvider.AppleMusic
};
var query = $"{songInfo.DisplayArtists} {songInfo.Title}";
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/search";
var url = apiUrl + $"?term={WebUtility.UrlEncode(query)}&types=songs&limit=1&l={_language}";
var resp = await _client.GetStringAsync(url);
@@ -120,9 +127,26 @@ namespace BetterLyrics.WinUI3.Providers
if (results.TryGetProperty("songs", out var songs) && songs.GetProperty("data").GetArrayLength() > 0)
{
var song = songs.GetProperty("data")[0];
return song.GetProperty("id").ToString();
var id = song.GetProperty("id").ToString();
var attr = song.GetProperty("attributes");
lyricsSearchResult.Title = attr.GetProperty("name").ToString();
lyricsSearchResult.Artists = attr.GetProperty("artistName").ToString().SplitByCommonSplitter();
lyricsSearchResult.Album = attr.GetProperty("albumName").ToString();
lyricsSearchResult.Duration = attr.GetProperty("durationInMillis").GetInt32() / 1000.0;
lyricsSearchResult.Reference = $"https://music.apple.com/song/{id}";
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
if (id != null)
{
lyricsSearchResult.Raw = await GetLyricsAsync(id);
}
}
return string.Empty;
return lyricsSearchResult;
}
}
}

View File

@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Services.LastFMService
}
catch (Exception)
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("LastFMAuthFailed") ?? "");
ToastHelper.ShowToast("LastFMAuthFailed", null, InfoBarSeverity.Error);
}
}

View File

@@ -18,10 +18,8 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Documents;
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
@@ -205,8 +203,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
try
{
LyricsFormat lyricsFormat = provider.GetLyricsFormat();
// Check cache first if allowed
if (checkCache && provider.IsRemote())
{
@@ -226,7 +222,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
else
{
lyricsSearchResult = await SearchFile(songInfo, lyricsFormat);
lyricsSearchResult = await SearchFile(songInfo, provider.GetLyricsFormat());
}
}
else
@@ -258,7 +254,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (token.IsCancellationRequested)
{
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
return lyricsSearchResult;
}
@@ -267,8 +262,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
}
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
if (lyricsSearchResult.IsFound)
{
if (provider.IsRemote())
@@ -282,6 +275,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private async Task<LyricsSearchResult> SearchFile(SongInfo songInfo, LyricsFormat format)
{
int maxScore = 0;
string? bestFile = null;
var lyricsSearchResult = new LyricsSearchResult();
if (format.ToLyricsSearchProvider() is LyricsSearchProvider lyricsSearchProvider)
@@ -297,18 +293,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
{
var fileName = Path.GetFileNameWithoutExtension(file);
if (StringHelper.IsSwitchableNormalizedMatch(fileName, songInfo.Title, songInfo.DisplayArtists) || songInfo.LinkedFileName == fileName)
int score = MetadataComparer.CalculateScore(songInfo, file);
if (score > maxScore)
{
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
if (raw != null)
{
lyricsSearchResult.Raw = raw;
lyricsSearchResult.CopyFromSongInfo(songInfo);
lyricsSearchResult.Reference = file;
return lyricsSearchResult;
}
bestFile = file;
maxScore = score;
}
}
}
@@ -317,11 +306,27 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
}
if (bestFile != null)
{
lyricsSearchResult.Reference = bestFile;
lyricsSearchResult.MatchPercentage = maxScore;
string? raw = await File.ReadAllTextAsync(bestFile, FileHelper.GetEncoding(bestFile));
if (raw != null)
{
lyricsSearchResult.Raw = raw;
}
}
return lyricsSearchResult;
}
private LyricsSearchResult SearchEmbedded(SongInfo songInfo)
{
int maxScore = 0;
string? bestFile = null;
var lyricsSearchResult = new LyricsSearchResult
{
Provider = LyricsSearchProvider.LocalMusicFile,
@@ -336,24 +341,39 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
if ((songInfo.Album != "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists && track.Album == songInfo.Album)
|| (songInfo.Album == "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists)
|| (songInfo.Album == "" && StringHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.Title, songInfo.DisplayArtists)))
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult
{
var plain = track.GetRawLyrics();
if (!plain.IsNullOrEmpty())
{
lyricsSearchResult.Raw = plain;
lyricsSearchResult.CopyFromSongInfo(songInfo);
lyricsSearchResult.Reference = file;
Title = track.Title,
Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator),
Album = track.Album,
Duration = track.Duration
});
return lyricsSearchResult;
}
if (score > maxScore)
{
maxScore = score;
bestFile = file;
}
}
}
}
}
if (bestFile != null)
{
var track = new Track(bestFile);
lyricsSearchResult.Title = track.Title;
lyricsSearchResult.Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator);
lyricsSearchResult.Album = track.Album;
lyricsSearchResult.Duration = track.Duration;
lyricsSearchResult.Raw = track.GetRawLyrics();
lyricsSearchResult.Reference = bestFile;
lyricsSearchResult.MatchPercentage = maxScore;
}
return lyricsSearchResult;
}
@@ -425,6 +445,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
catch { }
}
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
if (string.IsNullOrWhiteSpace(rawLyricFile))
{
return lyricsSearchResult;
@@ -503,6 +525,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Reference = url;
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
return lyricsSearchResult;
}
@@ -560,6 +584,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = response?.Lrc?.Lyric;
lyricsSearchResult.Translation = response?.Tlyric?.Lyric;
lyricsSearchResult.Transliteration = response?.Romalrc.Lyric;
lyricsSearchResult.Reference = $"https://music.163.com/song?id={neteaseResult.Id}";
}
else if (result is KugouSearchResult kugouResult)
@@ -602,26 +627,23 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Album = result?.Album;
lyricsSearchResult.Duration = result?.DurationMs / 1000;
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
return lyricsSearchResult;
}
private async Task<LyricsSearchResult> SearchAppleMusicAsync(SongInfo songInfo)
{
var lyricsSearchResult = new LyricsSearchResult
LyricsSearchResult lyricsSearchResult = new()
{
Provider = LyricsSearchProvider.AppleMusic,
Provider = LyricsSearchProvider.AppleMusic
};
_logger.LogInformation("SearchAppleMusicAsync");
if (await _appleMusic.InitAsync())
{
string id = await _appleMusic.SearchSongInfoAsync(songInfo.DisplayArtists, songInfo.Title);
string? raw = await _appleMusic.GetLyricsAsync(id);
_logger.LogInformation("SearchAppleMusicAsync");
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = songInfo.Title;
lyricsSearchResult.Artists = songInfo.Artists;
lyricsSearchResult.Album = "";
lyricsSearchResult.Reference = $"https://music.apple.com/song/{id}";
lyricsSearchResult = await _appleMusic.SearchSongInfoAsync(songInfo);
}
return lyricsSearchResult;

View File

@@ -3,10 +3,12 @@ using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Parsers.LyricsParser;
using CommunityToolkit.Mvvm.ComponentModel;
using Lyricify.Lyrics.Helpers.General;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -103,7 +105,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
catch (Exception)
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("LibreTranslateFailed")!);
ToastHelper.ShowToast("LibreTranslateFailed", null, InfoBarSeverity.Error);
}
}
}
@@ -126,7 +128,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
else if (originalLangCode == "ja" && _settingsService.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled)
{
targetPhoneticCode = PhoneticHelper.RomajiCode;
targetPhoneticCode = PhoneticHelper.RomanCode;
}
if (targetPhoneticCode == "")

View File

@@ -23,17 +23,15 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using DevWinUI;
using EvtSource;
using Lyricify.Lyrics.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Vanara.Windows.Shell;
using Windows.Media.Control;
@@ -526,7 +524,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_logger.LogError("Failed to start SSE connection for LX Music.");
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("FailToStartLXMusicServer"));
ToastHelper.ShowToast("FailToStartLXMusicServer", null, InfoBarSeverity.Error);
});
StopSSE();
}

View File

@@ -1,8 +1,5 @@
using ComputeSharp;
using ComputeSharp.D2D1;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Shaders
{

View File

@@ -1,6 +1,5 @@
using ComputeSharp;
using ComputeSharp.D2D1;
using static Vanara.PInvoke.Ole32.PROPERTYKEY.System;
namespace BetterLyrics.WinUI3.Shaders
{

View File

@@ -144,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<value>Copy</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>Playlist was created successfully</value>
</data>
@@ -228,7 +231,7 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>Lyrics provider</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +240,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>Lyrics Window Management Shortcut Settings</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>Match percentage</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,7 +267,7 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>Show translation only</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
@@ -273,7 +276,10 @@
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<value>Artist</value>
</data>
<data name="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>Auto generated</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>Duration</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -565,6 +571,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>Advanced options</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>Album</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>Album art</value>
</data>
@@ -610,6 +619,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>Apply</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>Artist</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>Automatic adjustment</value>
</data>
@@ -1189,12 +1201,15 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>Play</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>Playback source</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>Playback source ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>Current playback source</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
</data>
@@ -1219,6 +1234,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>Recorded window status</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>Reference link</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>Original files and folders in this path will not be deleted when removing it from this app</value>
</data>
@@ -1258,6 +1276,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>The duration of the first line</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>Current lyrics search result</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>Test server</value>
</data>
@@ -1321,6 +1342,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>Current song</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>Title</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[Experimental] Spectrum Layer</value>
</data>

View File

@@ -144,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<value>コピー</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>正常に作成されました</value>
</data>
@@ -228,7 +231,7 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>歌詞が見つかりません</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞プロバイダー</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +240,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>歌詞ウィンドウ管理ショートカット設定</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>マッチ率</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,7 +267,7 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>翻訳のみを表示します</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
@@ -273,7 +276,10 @@
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>自動生成されました</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>間隔</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -565,6 +571,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>高度なオプション</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>アルバム</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>アルバムアート</value>
</data>
@@ -610,6 +619,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>適用する</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自動調整</value>
</data>
@@ -1189,12 +1201,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>遊ぶ</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>再生ソース</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>再生ソースID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>現在の再生ソース</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</value>
</data>
@@ -1219,6 +1234,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>記録されたウィンドウステータス</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>参照リンク</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>このパスの元のファイルとフォルダーは、このアプリから削除するときに削除されません</value>
</data>
@@ -1258,6 +1276,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>最初の行の期間</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>現在の歌詞検索結果</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>テストサーバー</value>
</data>
@@ -1321,6 +1342,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>現在の曲です</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>タイトル</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[実験的] スペクトラムレイヤー</value>
</data>

View File

@@ -144,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>취소</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<value>접수</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>재생 목록이 성공적으로 생성되었습니다</value>
</data>
@@ -228,7 +231,7 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>가사 제공자</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +240,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>가사 창 관리 바로 가기 설정</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>매치 퍼센트</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,7 +267,7 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>번역 만 표시하십시오</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>번역 제공자</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
@@ -273,7 +276,10 @@
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>생성됨</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>지속</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -565,6 +571,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>고급 옵션</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>앨범</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>앨범 아트</value>
</data>
@@ -610,6 +619,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>적용하다</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>자동 조정</value>
</data>
@@ -1189,12 +1201,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>놀다</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>재생 소스</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>재생 소스 ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>현재 재생 소스</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</value>
</data>
@@ -1219,6 +1234,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>기록 된 창 상태</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>참고문헌</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>이 경로의 원본 파일과 폴더는이 앱에서 제거 할 때 삭제되지 않습니다.</value>
</data>
@@ -1258,6 +1276,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>첫 번째 줄의 기간</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>현재 가사 검색 결과</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>테스트 서버</value>
</data>
@@ -1321,6 +1342,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>현재 노래</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>제목</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[실험] 스펙트럼 레이어</value>
</data>

View File

@@ -144,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<value>拷贝</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>播放列表创建成功</value>
</data>
@@ -228,7 +231,7 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌词来源</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +240,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>歌词窗口管理快捷设置</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>匹配百分比</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,7 +267,7 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>仅显示翻译</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻译来源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
@@ -273,7 +276,10 @@
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>自动生成</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>时长</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -565,6 +571,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>高级选项</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>专辑</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>专辑</value>
</data>
@@ -610,6 +619,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>应用</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自动调整</value>
</data>
@@ -1189,12 +1201,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>播放</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>播放源</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>播放源 ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>当前播放源</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
@@ -1219,6 +1234,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>记录的窗口状态</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>参考链接</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>路径中的原始文件和文件夹不会被删除</value>
</data>
@@ -1258,6 +1276,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>首行持续时间</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>当前歌词搜索结果</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>测试服务器</value>
</data>
@@ -1321,6 +1342,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>当前歌曲</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>标题</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[实验性] 频谱层</value>
</data>

View File

@@ -144,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<value>複製</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>已成功建立播放清單</value>
</data>
@@ -228,7 +231,7 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞來源</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +240,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>歌詞視窗管理快捷設定</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>匹配百分比</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,7 +267,7 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>僅顯示翻譯</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻譯來源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
@@ -273,7 +276,10 @@
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>已產生</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>時長</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -565,6 +571,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>高級選項</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>專輯</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>專輯</value>
</data>
@@ -610,6 +619,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>應用</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自動調整</value>
</data>
@@ -1189,12 +1201,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>播放</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>播放源</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>播放源 ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>當前播放源</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
@@ -1219,6 +1234,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>記錄的窗口狀態</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>參考數據</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>路徑中的原始檔案和資料夾不會被刪除</value>
</data>
@@ -1258,6 +1276,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>首行持續時間</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>目前歌詞搜尋結果</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>測試服務器</value>
</data>
@@ -1321,6 +1342,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>當前歌曲</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>標題</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[實驗性] 頻譜層</value>
</data>

View File

@@ -7,6 +7,7 @@ using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Threading.Tasks;
@@ -64,7 +65,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("ImportSettingsFailed") ?? "");
ToastHelper.ShowToast("ImportSettingsFailed", null, InfoBarSeverity.Error);
}
}
}
@@ -77,7 +78,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (folder != null)
{
_settingsService.ExportSettings(folder.Path);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("ExportSettingsSuccess") ?? "");
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
}
}
@@ -95,7 +96,7 @@ namespace BetterLyrics.WinUI3.ViewModels
DirectoryHelper.DeleteAllFiles(PathHelper.iTunesAlbumArtCacheDirectory);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("ActionCompleted"));
ToastHelper.ShowToast("ActionCompleted", null, InfoBarSeverity.Success);
}
}

View File

@@ -1,9 +1,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Shaders;
using CommunityToolkit.WinUI;
using ComputeSharp.D2D1.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;

View File

@@ -16,7 +16,6 @@ using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using System;

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Parsers.LyricsParser;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
@@ -117,8 +118,8 @@ namespace BetterLyrics.WinUI3.ViewModels
((SongInfo?)_mediaSessionsService.CurrentSongInfo?.Clone() ?? new())
.WithTitle(MappedSongSearchQuery.MappedTitle)
.WithArtist(MappedSongSearchQuery.MappedArtist.SplitByCommonSplitter())
.WithAlbum(MappedSongSearchQuery.MappedAlbum),
!_settingsService.AppSettings.GeneralSettings.IgnoreCacheWhenSearching,
.WithAlbum(MappedSongSearchQuery.MappedAlbum),
!_settingsService.AppSettings.GeneralSettings.IgnoreCacheWhenSearching,
token);
return result;
}, token)];

View File

@@ -7,6 +7,7 @@ using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.IO;
using System.Linq;
@@ -46,18 +47,18 @@ namespace BetterLyrics.WinUI3.ViewModels
if (AppSettings.LocalMediaFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
{
DevWinUI.Growl.Warning(_resourceService.GetLocalizedString("SettingsPagePathExistedInfo"));
ToastHelper.ShowToast("SettingsPagePathExistedInfo", null, InfoBarSeverity.Warning);
}
else if (AppSettings.LocalMediaFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
// 添加的文件夹是现有文件夹的子文件夹
DevWinUI.Growl.Warning(_resourceService.GetLocalizedString("SettingsPagePathBeIncludedInfo"));
ToastHelper.ShowToast("SettingsPagePathBeIncludedInfo", null, InfoBarSeverity.Warning);
}
else if (AppSettings.LocalMediaFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
)
{
// 添加的文件夹是现有文件夹的父文件夹
DevWinUI.Growl.Warning(_resourceService.GetLocalizedString("SettingsPagePathIncludingOthersInfo"));
ToastHelper.ShowToast("SettingsPagePathIncludingOthersInfo", null, InfoBarSeverity.Warning);
}
else
{

View File

@@ -14,6 +14,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -350,7 +351,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else
{
_playlistTracks = [];
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("PlaylistViewFailed"), path);
ToastHelper.ShowToast("PlaylistViewFailed", path, InfoBarSeverity.Success);
}
}
break;
@@ -489,7 +490,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (file != null)
{
AddFileToStarredPlaylists(file);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("CreatePlaylistSuccessfully"), file.Path);
ToastHelper.ShowToast("CreatePlaylistSuccessfully", file.Path, InfoBarSeverity.Success);
}
}
@@ -501,7 +502,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (file != null)
{
AddFileToStarredPlaylists(file);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("ImportPlaylistSuccessfully"), file.Path);
ToastHelper.ShowToast("ImportPlaylistSuccessfully", file.Path, InfoBarSeverity.Success);
}
}

View File

@@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Hqub.Lastfm.Entities;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Linq;
using System.Threading.Tasks;
@@ -110,14 +111,14 @@ namespace BetterLyrics.WinUI3.ViewModels
"Hello, world!", AppSettings.TranslationSettings.SelectedTargetLanguageCode, new System.Threading.CancellationToken());
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("SettingsPageServerTestSuccessInfo"));
ToastHelper.ShowToast("SettingsPageServerTestSuccessInfo", null, InfoBarSeverity.Success);
});
}
catch (Exception)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("SettingsPageServerTestFailedInfo"));
ToastHelper.ShowToast("SettingsPageServerTestFailedInfo", null, InfoBarSeverity.Error);
});
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
@@ -156,11 +157,11 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (testResult)
{
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("SettingsPageServerTestSuccessInfo"));
ToastHelper.ShowToast("SettingsPageServerTestSuccessInfo", null, InfoBarSeverity.Success);
}
else
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("SettingsPageServerTestFailedInfo"));
ToastHelper.ShowToast("SettingsPageServerTestFailedInfo", null, InfoBarSeverity.Error);
}
IsLXMusicServerTesting = false;
});

View File

@@ -244,11 +244,11 @@ namespace BetterLyrics.WinUI3.Views
}
}
File.WriteAllText(path, content);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("TracksAddToPlaylistSuccessfully"), path);
ToastHelper.ShowToast("TracksAddToPlaylistSuccessfully", null, InfoBarSeverity.Success);
}
else
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("TracksAddToPlaylistFailed"), path);
ToastHelper.ShowToast("TracksAddToPlaylistFailed", null, InfoBarSeverity.Error);
}
}
}

View File

@@ -7,7 +7,7 @@
<UseWinUI>true</UseWinUI>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
</ItemGroup>