Compare commits

...

25 Commits

Author SHA1 Message Date
Zhe Fang
c0217150c1 Add Chinese translations to index.md
Updated the index.md file to include Chinese translations for section titles and descriptions.
2025-11-24 09:47:14 -05:00
Zhe Fang
0705bde0e2 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-11-24 09:30:25 -05:00
Zhe Fang
3f5122c93f chores: replace parse error placeholde with no lyrics found 2025-11-24 09:30:24 -05:00
Zhe Fang
97f061d887 Update links and headings in index.md 2025-11-24 09:24:05 -05:00
Zhe Fang
77525a62ff chores: ui 2025-11-24 09:23:54 -05:00
Zhe Fang
ad473bbd1d fix: ncm return exception when roman is null 2025-11-24 08:42:57 -05:00
Zhe Fang
b1126026c2 Update Discord notification workflow for releases 2025-11-23 17:25:13 -05:00
Zhe Fang
2f84155e6e Update release event types for Telegram notification 2025-11-23 17:24:38 -05:00
Zhe Fang
1769167811 Add workflow_dispatch trigger to Telegram release workflow 2025-11-23 17:14:53 -05:00
Zhe Fang
551da8c0b0 Add workflow_dispatch trigger to releases workflow 2025-11-23 17:14:36 -05:00
Zhe Fang
a5b3671ce3 chores: bump version code to 147 2025-11-23 14:59:36 -05:00
Zhe Fang
181a06c932 add: support 0 duration for lyrics animation 2025-11-23 09:46:18 -05:00
Zhe Fang
93d567e21d Delete .github/workflows/jekyll-gh-pages.yml 2025-11-22 20:04:09 -05:00
Zhe Fang
9da8510de6 Remove remote_theme from Jekyll GitHub Pages workflow
Removed remote_theme configuration from the workflow.
2025-11-22 20:01:41 -05:00
Zhe Fang
3ed9e599be Add remote theme for Jekyll GitBook deployment 2025-11-22 19:59:24 -05:00
Zhe Fang
e277faea9e Update links in README.CN.md for better navigation 2025-11-22 17:15:38 -05:00
Zhe Fang
21dcd7de4b Update README.md 2025-11-22 17:12:31 -05:00
Zhe Fang
31540beaa0 chores: bump version code to 1.0.146.0 2025-11-22 16:36:27 -05:00
Zhe Fang
63ffe6b661 fix: lyrics matching rule 2025-11-22 14:46:17 -05:00
Zhe Fang
5cb880021c fix: add error parser placeholder 2025-11-22 09:38:49 -05:00
Zhe Fang
b00f2b5865 fix: parse lrc for ncm 2025-11-22 09:27:55 -05:00
Zhe Fang
90a75f1b96 add: globally set lyrics matching threshold 2025-11-21 20:58:28 -05:00
Zhe Fang
735f03542f add: support adjusting matching threshold 2025-11-21 19:53:18 -05:00
Zhe Fang
b7853ded26 fix: ncm transation wrongly tagged with roman 2025-11-21 12:13:10 -05:00
Zhe Fang
88fc0adbec fix: AM album title 2025-11-21 12:08:08 -05:00
45 changed files with 1045 additions and 441 deletions

View File

@@ -2,7 +2,7 @@ name: Notify Telegram on GitHub Release
on:
release:
types: [published]
types: [published, edited]
jobs:
notify:

View File

@@ -2,7 +2,7 @@ name: Notify Discord on GitHub Release
on:
release:
types: [published]
types: [published, edited]
jobs:
github-releases-to-discord:

View File

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

View File

@@ -2,16 +2,17 @@
{
public static class Link
{
public const string MicrosoftStoreUrl = "https://apps.microsoft.com/detail/9p1wcd1p597r";
public const string GitHubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string ShareHubUrl = $"{GitHubUrl}/blob/dev/ShareHub/index.md";
public const string TermsOfServiceUrl = $"{GitHubUrl}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{GitHubUrl}/blob/dev/PrivacyPolicy.md";
public const string WikiUrl = "https://jayfunc.blog/work/betterlyrics";
public const string AppleMusicCfgUrl = $"{WikiUrl}#lyrics-sources-configuration";
public const string FAQUrl = $"{WikiUrl}#faq";
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
public const string MicrosoftStore = "https://apps.microsoft.com/detail/9p1wcd1p597r";
public const string GitHub = "https://github.com/jayfunc/BetterLyrics";
public const string ShareHub = $"{GitHub}/blob/dev/ShareHub/index.md";
public const string TermsOfService = $"{GitHub}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{GitHub}/blob/dev/PrivacyPolicy.md";
public const string UserGuide = $"{GitHub}/wiki/User-Guide";
public const string AppleMusicCfg = $"{UserGuide}#lyrics-source-configuration";
public const string QQGroup = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
public const string Discord = "https://discord.gg/5yAQPnyCKv";
public const string Telegram = "https://t.me/+svhSLZ7awPsxNGY1";
}
}

View File

@@ -5,5 +5,6 @@ namespace BetterLyrics.WinUI3.Constants
public static class Time
{
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(300);
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(300);
}
}

View File

@@ -51,11 +51,10 @@
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHubUrl}" />
<HyperlinkButton Content="Wiki" NavigateUri="{x:Bind const:Link.WikiUrl}" />
<HyperlinkButton Content="FAQ" NavigateUri="{x:Bind const:Link.FAQUrl}" />
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHub}" />
<HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" />
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfServiceUrl}" />
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" />
</StackPanel>
</StackPanel>
@@ -65,9 +64,9 @@
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageFeedback" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton x:Uid="SettingsPageQQGroup" NavigateUri="{x:Bind const:Link.QQGroupUrl}" />
<HyperlinkButton x:Uid="SettingsPageDiscord" NavigateUri="{x:Bind const:Link.DiscordUrl}" />
<HyperlinkButton x:Uid="SettingsPageTelegram" NavigateUri="{x:Bind const:Link.TelegramUrl}" />
<HyperlinkButton x:Uid="SettingsPageQQGroup" NavigateUri="{x:Bind const:Link.QQGroup}" />
<HyperlinkButton x:Uid="SettingsPageDiscord" NavigateUri="{x:Bind const:Link.Discord}" />
<HyperlinkButton x:Uid="SettingsPageTelegram" NavigateUri="{x:Bind const:Link.Telegram}" />
</StackPanel>
</StackPanel>
</dev:SettingsCard>

View File

@@ -0,0 +1,174 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Controls
{
public static class InstantTip
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.RegisterAttached("Content", typeof(object), typeof(InstantTip), new PropertyMetadata(null, OnContentChanged));
public static void SetContent(DependencyObject element, object value) => element.SetValue(ContentProperty, value);
public static object GetContent(DependencyObject element) => element.GetValue(ContentProperty);
private static Dictionary<int, Popup> _activePopups = [];
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element)
{
element.PointerEntered -= Element_PointerEntered;
element.PointerExited -= Element_PointerExited;
element.Unloaded -= Element_Unloaded;
if (e.NewValue != null)
{
element.PointerEntered += Element_PointerEntered;
element.PointerExited += Element_PointerExited;
element.Unloaded += Element_Unloaded;
}
}
}
private static void Element_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
int hashCode = element.GetHashCode();
if (_activePopups.ContainsKey(hashCode))
{
_activePopups.TryGetValue(hashCode, out var popup);
if (popup != null)
{
popup.IsOpen = true;
}
}
else
{
var rawContent = GetContent(element);
if (rawContent == null) return;
// 创建可视卡片容器 (Visual Card)
var visualCard = new Grid
{
Background = (Brush)Application.Current.Resources["AcrylicBackgroundFillColorDefaultBrush"],
CornerRadius = new CornerRadius(4),
Shadow = new ThemeShadow(),
Translation = new Vector3(0, 0, 32),
Padding = new Thickness(8, 4, 8, 4)
};
var popupContent = new Grid
{
IsHitTestVisible = false,
Opacity = 0,
Padding = new Thickness(16)
};
popupContent.Children.Add(visualCard);
var popup = new Popup
{
Child = popupContent,
IsHitTestVisible = false,
ShouldConstrainToRootBounds = false,
XamlRoot = element.XamlRoot,
};
object finalContent = rawContent;
if (rawContent is ToolTip toolTipWrapper)
{
finalContent = toolTipWrapper.Content;
}
if (finalContent is string text)
{
var textBlock = new TextBlock
{
Text = text,
FontSize = 12,
MaxWidth = 320,
TextWrapping = TextWrapping.Wrap,
Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]
};
visualCard.Children.Add(textBlock);
}
else if (finalContent != null)
{
var textBlock = new TextBlock
{
Text = finalContent.ToString(),
FontSize = 12,
MaxWidth = 320,
TextWrapping = TextWrapping.Wrap,
Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]
};
visualCard.Children.Add(textBlock);
}
var transform = element.TransformToVisual(null);
var point = transform.TransformPoint(new Point(0, element.ActualHeight));
popup.VerticalOffset = point.Y;
popup.HorizontalOffset = point.X - popupContent.Padding.Left;
popup.IsOpen = true;
App.Current.Resources.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
popupContent.OpacityTransition = new ScalarTransition();
popupContent.Opacity = 1;
});
_activePopups.Add(hashCode, popup);
}
}
}
private static void Element_PointerExited(object sender, PointerRoutedEventArgs e)
{
var element = sender as FrameworkElement;
if (element != null)
{
int hashCode = element.GetHashCode();
if (!_activePopups.ContainsKey(hashCode))
{
return;
}
_activePopups.TryGetValue(hashCode, out var popup);
if (popup != null)
{
if (popup.Child is Grid popupContent)
{
popupContent.Opacity = 0;
}
popup.IsOpen = false;
_activePopups.Remove(hashCode);
}
}
}
private static void Element_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
element.PointerEntered -= Element_PointerEntered;
element.PointerExited -= Element_PointerExited;
element.Unloaded -= Element_Unloaded;
}
}
}
}

View File

@@ -224,7 +224,7 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 滚动动画 -->
<!-- 歌词动画 -->
<dev:SettingsExpander x:Uid="SettingsPageScrollEasing" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xECE7;}">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsScrollEasingType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEasingTypeLinear" />
@@ -246,7 +246,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollTopDuration, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -255,7 +255,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollDuration, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -264,7 +264,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollBottomDuration, Mode=TwoWay}" />
</dev:SettingsCard>

View File

@@ -27,6 +27,7 @@
<Grid Grid.Column="0">
<ScrollViewer>
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="LyricsSearchControlSongInfoMapping" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Grid
@@ -34,22 +35,32 @@
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="4">
<StackPanel Spacing="6">
<TextBlock x:Uid="LyricsSearchControlTitle" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.MappedSongSearchQuery.OriginalTitle, Mode=OneWay}"
TextWrapping="Wrap" />
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedTitle, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedTitleCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
<local:PropertyRow x:Uid="LyricsSearchControlTitle" Value="{x:Bind ViewModel.MappedSongSearchQuery.OriginalTitle, Mode=OneWay}" />
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlMappedAs"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox
Grid.Column="1"
Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedTitle, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Grid.Column="2"
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedTitleCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</Grid>
</StackPanel>
</Grid>
@@ -60,33 +71,52 @@
CornerRadius="4">
<StackPanel Spacing="6">
<TextBlock x:Uid="LyricsSearchControlArtist" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.MappedSongSearchQuery.OriginalArtist, Mode=OneWay}"
TextWrapping="Wrap" />
<local:PropertyRow x:Uid="LyricsSearchControlArtist" Value="{x:Bind ViewModel.MappedSongSearchQuery.OriginalArtist, Mode=OneWay}" />
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}" TextWrapping="Wrap" />
<RichTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" TextWrapping="Wrap">
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlMappedAs"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox
Grid.Column="1"
Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Grid.Column="2"
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</Grid>
<RichTextBlock
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap">
<Paragraph>
<Run Text="*" />
<Run x:Uid="ArtistsSplitHint" />
<Run Text=";" />
<Run Text="," />
<Run Text="/" />
<Run Text="" />
<Run Text="、" />
<Run Text="" />
</Paragraph>
</RichTextBlock>
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
<RichTextBlock
FontSize="12"
FontWeight="Bold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Loaded="ArtistsSplitHintRichTextBlock_Loaded"
TextWrapping="Wrap">
<Paragraph>
<Run Text="; , / " />
</Paragraph>
</RichTextBlock>
</StackPanel>
</Grid>
@@ -97,22 +127,32 @@
CornerRadius="4">
<StackPanel Spacing="6">
<TextBlock x:Uid="LyricsSearchControlAlbum" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.MappedSongSearchQuery.OriginalAlbum, Mode=OneWay}"
TextWrapping="Wrap" />
<local:PropertyRow x:Uid="LyricsSearchControlAlbum" Value="{x:Bind ViewModel.MappedSongSearchQuery.OriginalAlbum, Mode=OneWay}" />
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedAlbum, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedAlbumCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlMappedAs"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox
Grid.Column="1"
Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedAlbum, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Grid.Column="2"
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedAlbumCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</Grid>
</StackPanel>
</Grid>
@@ -138,7 +178,7 @@
<ListViewItem IsEnabled="{x:Bind IsFound}">
<StackPanel Padding="0,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
<local:PropertyRow
Margin="-12,0,0,0"
Margin="-8,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}}" />
@@ -155,6 +195,10 @@
x:Uid="LyricsPageMatchPercentage"
Unit="%"
Value="{x:Bind MatchPercentage, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsPageCachePath"
Link="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
<!-- NOT FOUND -->
<TextBlock
@@ -279,31 +323,29 @@
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="1">
<RelativePanel>
<TextBlock
x:Uid="LyricsSearchControlHelp"
Margin="0,0,24,0"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.AlignVerticalCenterWithPanel="True"
RelativePanel.LeftOf="Reset"
TextWrapping="Wrap" />
<Button
x:Name="Reset"
x:Uid="LyricsSearchControlReset"
Margin="0,0,6,0"
Command="{x:Bind ViewModel.ResetCommand}"
RelativePanel.AlignVerticalCenterWithPanel="True"
RelativePanel.LeftOf="SaveChanges" />
<Button
x:Name="SaveChanges"
x:Uid="LyricsSearchControlSaveChanges"
Command="{x:Bind ViewModel.SaveCommand}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource AccentButtonStyle}" />
</RelativePanel>
<Grid Grid.Row="1" ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlHelp"
Grid.Column="1"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Uid="LyricsSearchControlReset"
Grid.Column="2"
Command="{x:Bind ViewModel.ResetCommand}" />
<Button
x:Uid="LyricsSearchControlSaveChanges"
Grid.Column="3"
Command="{x:Bind ViewModel.SaveCommand}"
Style="{StaticResource AccentButtonStyle}" />
</Grid>
</Grid>
</UserControl>

View File

@@ -1,7 +1,10 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -22,5 +25,26 @@ namespace BetterLyrics.WinUI3.Controls
{
ViewModel.SelectedLyricsLine = e.OriginalSource as LyricsLine;
}
private void ArtistsSplitHintRichTextBlock_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (sender is RichTextBlock richTextBlock)
{
TextHighlighter highlighter = new()
{
Background = App.Current.Resources["AccentTextFillColorPrimaryBrush"] as SolidColorBrush,
Ranges =
{
new() { StartIndex = 0, Length = 1 },
new() { StartIndex = 5, Length = 1 },
new() { StartIndex = 10, Length = 1 },
new() { StartIndex = 15, Length = 1 },
new() { StartIndex = 20, Length = 1 },
new() { StartIndex = 25, Length = 1 },
}
};
richTextBlock.TextHighlighters.Add(highlighter);
}
}
}
}

View File

@@ -60,7 +60,7 @@
</Button>
<!-- Sharing hub -->
<HyperlinkButton x:Uid="SettingsPageShareHub" NavigateUri="{x:Bind constants:Link.ShareHubUrl}" />
<HyperlinkButton x:Uid="SettingsPageShareHub" NavigateUri="{x:Bind constants:Link.ShareHub}" />
</StackPanel>

View File

@@ -188,6 +188,14 @@
<ComboBoxItem x:Uid="SettingsPageLyricsSearchBestMatch" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMatchingThreshold">
<local:ExtendedSlider
Default="0"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.MatchingThreshold, Mode=TwoWay}" />
</dev:SettingsCard>
<ListView
x:Name="LyricsSearchProvidersListView"
@@ -209,12 +217,48 @@
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
<dev:SettingsCard Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}">
<dev:SettingsCard.HeaderIcon>
<FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x283F;" />
</dev:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<Grid>
<dev:SettingsExpander Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}" IsExpanded="{Binding IsMatchingThresholdOverwritten, Mode=OneWay}">
<dev:SettingsExpander.HeaderIcon>
<FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x283F;" />
</dev:SettingsExpander.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageOverwriteMatchingThreshold">
<ToggleSwitch IsOn="{Binding IsMatchingThresholdOverwritten, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMatchingThreshold" IsEnabled="{Binding IsMatchingThresholdOverwritten, Mode=OneWay}">
<local:ExtendedSlider
Default="0"
Maximum="100"
Minimum="0"
Unit="%"
Value="{Binding MatchingThreshold, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<Grid
Width="48"
HorizontalAlignment="Left"
Background="{ThemeResource ControlStrokeColorDefaultBrush}"
CornerRadius="4,0,0,4"
Opacity="0">
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageHoldDragSort" />
</ToolTipService.ToolTip>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
@@ -264,9 +308,7 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Playback source -->
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
<!-- Playback source ID -->
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
</Expander>
@@ -277,13 +319,9 @@
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"
@@ -297,30 +335,27 @@
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}" />
<local:PropertyRow
x:Uid="LyricsPageCachePath"
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
</Expander>
@@ -464,7 +499,7 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE897;}"
NavigateUri="{x:Bind constants:Link.AppleMusicCfgUrl}" />
NavigateUri="{x:Bind constants:Link.AppleMusicCfg}" />
<Button
Grid.Column="2"
Command="{x:Bind ViewModel.SaveAppleMusicMediaUserTokenCommand}"

View File

@@ -57,7 +57,7 @@
Click="OnCopyClicked"
Opacity="0">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Copy" />
<ToolTip x:Uid="Copy" />
</ToolTipService.ToolTip>
<Button.OpacityTransition>
<ScalarTransition />

View File

@@ -16,6 +16,10 @@ namespace BetterLyrics.WinUI3.Extensions
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
LyricsSearchProvider.LocalMusicFile => PathHelper.LocalMusicCacheDirectory,
LyricsSearchProvider.LocalLrcFile => PathHelper.LocalLrcCacheDirectory,
LyricsSearchProvider.LocalEslrcFile => PathHelper.LocalEslrcCacheDirectory,
LyricsSearchProvider.LocalTtmlFile => PathHelper.LocalTtmlCacheDirectory,
_ => throw new ArgumentOutOfRangeException(nameof(provider)),
};

View File

@@ -5,6 +5,7 @@ using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
@@ -45,7 +46,7 @@ namespace BetterLyrics.WinUI3.Helper
return new OpacityEffect
{
Source = backgroundFontEffect,
Opacity = (float)(lyricsLine.OpacityTransition.Value * lyricsLayerOpacity),
Opacity = (float)Math.Clamp(lyricsLine.OpacityTransition.Value * lyricsLayerOpacity, 0, 1),
};
}
else
@@ -55,11 +56,11 @@ namespace BetterLyrics.WinUI3.Helper
Source = new GaussianBlurEffect
{
Source = backgroundFontEffect,
BlurAmount = (float)lyricsLine.BlurAmountTransition.Value,
BlurAmount = (float)Math.Max(lyricsLine.BlurAmountTransition.Value, 0),
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
},
Opacity = (float)(lyricsLine.OpacityTransition.Value * lyricsLayerOpacity),
Opacity = (float)Math.Clamp(lyricsLine.OpacityTransition.Value * lyricsLayerOpacity, 0, 1),
};
}
}
@@ -118,7 +119,7 @@ namespace BetterLyrics.WinUI3.Helper
Source = foregroundFontEffect,
AlphaMask = mask,
},
BlurAmount = (float)glowEffectAmount,
BlurAmount = (float)Math.Clamp(glowEffectAmount, 0, 100),
Optimization = EffectOptimization.Speed,
};
}
@@ -357,7 +358,7 @@ namespace BetterLyrics.WinUI3.Helper
Source = foregroundFontEffect,
AlphaMask = mask,
},
Opacity = (float)opacity,
Opacity = (float)Math.Clamp(opacity, 0, 1),
};
}
@@ -371,7 +372,7 @@ namespace BetterLyrics.WinUI3.Helper
AlphaMask = mask,
},
ShadowColor = shadowColor,
BlurAmount = (float)shadowAmount,
BlurAmount = (float)Math.Clamp(shadowAmount, 0, 100),
Optimization = EffectOptimization.Speed,
};
}
@@ -385,7 +386,7 @@ namespace BetterLyrics.WinUI3.Helper
Source = foregroundFontEffect,
AlphaMask = mask,
},
Opacity = (float)opacity,
Opacity = (float)Math.Clamp(opacity, 0, 1),
};
}

View File

@@ -48,6 +48,7 @@ namespace BetterLyrics.WinUI3.Helper
{
var json = File.ReadAllText(cacheFilePath);
var data = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LyricsSearchResult);
data?.SelfPath = cacheFilePath;
return data;
}
return null;
@@ -68,6 +69,7 @@ namespace BetterLyrics.WinUI3.Helper
var cacheFilePath = Path.Combine(
lyricsSearchResult.Provider.GetCacheDirectory(),
SanitizeFileName($"{songInfo.ToFileName()}.json"));
lyricsSearchResult.SelfPath = cacheFilePath;
var json = System.Text.Json.JsonSerializer.Serialize(lyricsSearchResult, SourceGenerationContext.Default.LyricsSearchResult);
File.WriteAllText(cacheFilePath, json);
}

View File

@@ -16,52 +16,51 @@ namespace BetterLyrics.WinUI3.Helper
private const double WeightDuration = 0.10;
// JaroWinkler 适合短字符串匹配
private static readonly JaroWinkler _algo = new JaroWinkler();
private static readonly JaroWinkler _algo = new();
public static int CalculateScore(SongInfo local, LyricsSearchResult remote)
{
if (local == null || remote == null) return 0;
double titleScore = GetStringSimilarity(local.Title, remote.Title);
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
double albumScore = GetStringSimilarity(local.Album, remote.Album);
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
double totalScore = 0;
double totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
(albumScore * WeightAlbum) +
(durationScore * WeightDuration);
bool localHasMetadata = !string.IsNullOrWhiteSpace(local.Title);
bool remoteHasMetadata = !string.IsNullOrWhiteSpace(remote.Title);
if (localHasMetadata && remoteHasMetadata)
{
double titleScore = GetStringSimilarity(local.Title, remote.Title);
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
double albumScore = GetStringSimilarity(local.Album, remote.Album);
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
(albumScore * WeightAlbum) +
(durationScore * WeightDuration);
}
else
{
string? localQuery = localHasMetadata
? $"{local.Title} {string.Join(" ", local.Artists ?? [])}"
: Path.GetFileNameWithoutExtension(local.LinkedFileName);
string remoteQuery = remoteHasMetadata
? $"{remote.Title} {string.Join(" ", remote.Artists ?? [])}"
: Path.GetFileNameWithoutExtension(remote.Reference);
string fp1 = CreateSortedFingerprint(localQuery);
string fp2 = CreateSortedFingerprint(remoteQuery);
if (string.IsNullOrWhiteSpace(fp1) || string.IsNullOrWhiteSpace(fp2))
totalScore = 0;
else
totalScore = _algo.Similarity(fp1, fp2);
}
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);
if (string.IsNullOrEmpty(infoRaw) && songInfo.LinkedFileName is string linkedFileName)
infoRaw = linkedFileName;
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() ?? "";
@@ -105,7 +104,7 @@ namespace BetterLyrics.WinUI3.Helper
return 1.0 - ((diff - PerfectTolerance) / (MaxTolerance - PerfectTolerance));
}
private static string CreateSortedFingerprint(string input)
private static string CreateSortedFingerprint(string? input)
{
if (string.IsNullOrWhiteSpace(input)) return "";

View File

@@ -45,6 +45,10 @@ namespace BetterLyrics.WinUI3.Helper
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
public static string AppleMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "apple-music");
public static string LocalMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-music");
public static string LocalLrcCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-lrc");
public static string LocalEslrcCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-eslrc");
public static string LocalTtmlCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-ttml");
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.jsonl");
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
@@ -65,6 +69,10 @@ namespace BetterLyrics.WinUI3.Helper
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(AppleMusicCacheDirectory);
Directory.CreateDirectory(LocalMusicCacheDirectory);
Directory.CreateDirectory(LocalLrcCacheDirectory);
Directory.CreateDirectory(LocalEslrcCacheDirectory);
Directory.CreateDirectory(LocalTtmlCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}

View File

@@ -57,7 +57,7 @@ namespace BetterLyrics.WinUI3.Helper
public void SetDuration(double seconds)
{
if (seconds <= 0)
if (seconds < 0)
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
_durationSeconds = seconds;
}
@@ -145,7 +145,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType type)
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType? type)
{
if (typeof(T) == typeof(double))
{
@@ -193,6 +193,7 @@ namespace BetterLyrics.WinUI3.Helper
t = EasingHelper.Linear(t);
break;
default:
t = EasingHelper.EaseInOutQuad(t);
break;
}
return (T)(object)(s + (e - s) * t);
@@ -201,7 +202,7 @@ namespace BetterLyrics.WinUI3.Helper
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
}
public void SetEasingType(EasingType easingType)
public void SetEasingType(EasingType? easingType)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(easingType);

View File

@@ -118,6 +118,7 @@ namespace BetterLyrics.WinUI3.Hooks
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
TrackWindow(window);
var castedWindow = (Window)window;
castedWindow.Restore();

View File

@@ -131,14 +131,23 @@ namespace BetterLyrics.WinUI3.Models
return result;
}
public static LyricsData GetNotfoundPlaceholder(int durationMs)
public static LyricsData GetNotfoundPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = durationMs,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = _resourceService.GetLocalizedString("LyricsNotFound"),
LyricsChars = [],
}]);
}
public static LyricsData GetParseErrorPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = _resourceService.GetLocalizedString("LyricsParseError"),
}]);
}
@@ -149,10 +158,7 @@ namespace BetterLyrics.WinUI3.Models
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
PhoneticText = "",
OriginalText = "● ● ●",
TranslatedText = "",
LyricsChars = [],
},
]);
}

View File

@@ -3,6 +3,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
@@ -13,37 +14,13 @@ namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
private const double _animationDuration = 0.3;
public ValueTransition<double> AngleTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> BlurAmountTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> HighlightOpacityTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> OpacityTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> ScaleTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> YOffsetTransition { get; set; } = new(
initialValue: 0,
durationSeconds: 0.5,
easingType: EasingType.EaseInOutQuad
);
public double AnimationDuration { get; set; } = 0.3;
public ValueTransition<double> AngleTransition { get; set; }
public ValueTransition<double> BlurAmountTransition { get; set; }
public ValueTransition<double> HighlightOpacityTransition { get; set; }
public ValueTransition<double> OpacityTransition { get; set; }
public ValueTransition<double> ScaleTransition { get; set; }
public ValueTransition<double> YOffsetTransition { get; set; }
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
@@ -86,6 +63,40 @@ namespace BetterLyrics.WinUI3.Models
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public LyricsLine()
{
AngleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutQuad
);
BlurAmountTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutQuad
);
HighlightOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutQuad
);
OpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutQuad
);
ScaleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutQuad
);
YOffsetTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutQuad
);
}
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (OriginalCanvasTextLayout == null)

View File

@@ -8,8 +8,9 @@ namespace BetterLyrics.WinUI3.Models
public partial class LyricsSearchProviderInfo : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMatchingThresholdOverwritten { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 0;
public LyricsSearchProviderInfo() { }

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using CommunityToolkit.Mvvm.ComponentModel;
using NTextCat.Commons;
using System;
@@ -28,6 +29,8 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty] public partial int MatchPercentage { get; set; } = -1;
[ObservableProperty] public partial string Reference { get; set; } = "about:blank";
public string? SelfPath { get; set; }
public bool IsFound => !string.IsNullOrEmpty(Raw);
public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;

View File

@@ -32,6 +32,7 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; } = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 0;
public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);

View File

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

View File

@@ -4,7 +4,9 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Parsers;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
@@ -12,14 +14,17 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
private static readonly ILogger<LyricsParser> _logger = Ioc.Default.GetRequiredService<ILogger<LyricsParser>>();
public List<LyricsData> LyricsDataArr { get; private set; } = [];
public void Parse(SongInfo? songInfo, LyricsSearchResult? lyricsSearchResult)
{
_logger.LogInformation("LyricsParser.Parse");
LyricsDataArr = [];
if (lyricsSearchResult?.Raw == null)
if (string.IsNullOrWhiteSpace(lyricsSearchResult?.Raw))
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder((int)(songInfo?.DurationMs ?? 0)));
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
else
{
@@ -27,7 +32,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(lyricsSearchResult.Raw);
ParseLrc(lyricsSearchResult.Raw, lyricsSearchResult.Provider.IsRemote());
break;
case LyricsFormat.Qrc:
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
@@ -41,6 +46,11 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
default:
break;
}
if (LyricsDataArr.Count == 0)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
}
LoadTranslation(lyricsSearchResult);
LoadTransliteration(lyricsSearchResult);
@@ -49,14 +59,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
private void LoadTranslation(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Translation != null)
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Translation))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.QQ:
case LyricsSearchProvider.Kugou:
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Translation);
ParseLrc(lyricsSearchResult.Translation, true);
break;
default:
break;
@@ -66,12 +76,12 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
private void LoadTransliteration(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Transliteration != null)
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Transliteration))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Transliteration);
ParseLrc(lyricsSearchResult.Transliteration, true);
LyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
break;
default:

View File

@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Services.DiscordService
{
StatusDisplay = StatusDisplayType.Details,
Type = ActivityType.Listening,
Buttons = new Button[] { new() { Label = "Get this status", Url = Constants.Link.MicrosoftStoreUrl } },
Buttons = new Button[] { new() { Label = "Get this status", Url = Constants.Link.MicrosoftStore } },
Assets = new Assets
{
LargeImageKey = "banner",

View File

@@ -40,7 +40,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHubUrl})"
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHub})"
);
_amllTtmlDbHttpClient = new();
_appleMusic = new AppleMusic();
@@ -144,32 +144,42 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
List<LyricsSearchResult> lyricsSearchResults = [];
// 曲目没有被映射
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == songInfo.PlayerId)?.LyricsSearchProvidersInfo ?? [])
var mediaSourceProviderInfo = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == songInfo.PlayerId);
if (mediaSourceProviderInfo != null)
{
if (!provider.IsEnabled)
// 曲目没有被映射
foreach (var provider in mediaSourceProviderInfo.LyricsSearchProvidersInfo)
{
continue;
}
lyricsSearchResult = await SearchSingleAsync(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtists)
.WithAlbum(overridenAlbum),
provider.Provider, checkCache, token);
if (lyricsSearchResult.IsFound)
{
switch (lyricsSearchType)
if (!provider.IsEnabled)
{
case LyricsSearchType.Sequential:
return lyricsSearchResult;
case LyricsSearchType.BestMatch:
lyricsSearchResults.Add((LyricsSearchResult)lyricsSearchResult.Clone());
break;
default:
break;
continue;
}
lyricsSearchResult = await SearchSingleAsync(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtists)
.WithAlbum(overridenAlbum),
provider.Provider, checkCache, token);
int matchingThreshold = mediaSourceProviderInfo.MatchingThreshold;
if (provider.IsMatchingThresholdOverwritten)
{
matchingThreshold = provider.MatchingThreshold;
}
if (lyricsSearchResult.IsFound && lyricsSearchResult.MatchPercentage >= matchingThreshold)
{
switch (lyricsSearchType)
{
case LyricsSearchType.Sequential:
return lyricsSearchResult;
case LyricsSearchType.BestMatch:
lyricsSearchResults.Add((LyricsSearchResult)lyricsSearchResult.Clone());
break;
default:
break;
}
}
}
}
@@ -214,42 +224,36 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
if (provider.IsLocal())
switch (provider)
{
if (provider == LyricsSearchProvider.LocalMusicFile)
{
case LyricsSearchProvider.QQ:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Netease);
break;
case LyricsSearchProvider.LrcLib:
lyricsSearchResult = await SearchLrcLibAsync(songInfo);
break;
case LyricsSearchProvider.AmllTtmlDb:
lyricsSearchResult = await SearchAmllTtmlDbAsync(songInfo);
break;
case LyricsSearchProvider.LocalMusicFile:
lyricsSearchResult = SearchEmbedded(songInfo);
}
else
{
break;
case LyricsSearchProvider.LocalLrcFile:
case LyricsSearchProvider.LocalEslrcFile:
case LyricsSearchProvider.LocalTtmlFile:
lyricsSearchResult = await SearchFile(songInfo, provider.GetLyricsFormat());
}
}
else
{
switch (provider)
{
case LyricsSearchProvider.LrcLib:
lyricsSearchResult = await SearchLrcLibAsync(songInfo);
break;
case LyricsSearchProvider.QQ:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
lyricsSearchResult = await SearchAmllTtmlDbAsync(songInfo);
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(songInfo);
break;
default:
break;
}
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(songInfo);
break;
default:
break;
}
if (token.IsCancellationRequested)
@@ -262,12 +266,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
}
if (lyricsSearchResult.IsFound)
if (provider.IsRemote())
{
if (provider.IsRemote())
{
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
}
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
}
return lyricsSearchResult;
@@ -293,7 +294,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
{
int score = MetadataComparer.CalculateScore(songInfo, file);
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult { Reference = file });
if (score > maxScore)
{
bestFile = file;
@@ -324,8 +325,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private LyricsSearchResult SearchEmbedded(SongInfo songInfo)
{
int maxScore = 0;
int bestScore = 0;
string? bestFile = null;
string? bestRaw = null;
var lyricsSearchResult = new LyricsSearchResult
{
@@ -341,18 +343,25 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult
{
Title = track.Title,
Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator),
Album = track.Album,
Duration = track.Duration
});
var raw = track.GetRawLyrics();
if (score > maxScore)
if (!string.IsNullOrEmpty(raw))
{
maxScore = score;
bestFile = file;
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult
{
Title = track.Title,
Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator),
Album = track.Album,
Duration = track.Duration,
Reference = file,
});
if (score > bestScore)
{
bestScore = score;
bestFile = file;
bestRaw = raw;
}
}
}
}
@@ -368,10 +377,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Album = track.Album;
lyricsSearchResult.Duration = track.Duration;
lyricsSearchResult.Raw = track.GetRawLyrics();
lyricsSearchResult.Raw = bestRaw;
lyricsSearchResult.Reference = bestFile;
lyricsSearchResult.MatchPercentage = maxScore;
lyricsSearchResult.MatchPercentage = bestScore;
}
return lyricsSearchResult;
@@ -584,7 +592,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = response?.Lrc?.Lyric;
lyricsSearchResult.Translation = response?.Tlyric?.Lyric;
lyricsSearchResult.Transliteration = response?.Romalrc.Lyric;
lyricsSearchResult.Transliteration = response?.Romalrc?.Lyric;
lyricsSearchResult.Reference = $"https://music.163.com/song?id={neteaseResult.Id}";
}
else if (result is KugouSearchResult kugouResult)

View File

@@ -183,6 +183,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
case nameof(MediaSourceProviderInfo.LyricsSearchType):
UpdateLyrics();
break;
case nameof(MediaSourceProviderInfo.MatchingThreshold):
UpdateLyrics();
break;
default:
break;
}
@@ -318,14 +321,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? fixedArtist = mediaProperties?.Artist;
string? fixedAlbum = mediaProperties?.AlbumTitle;
string? songId = null;
if (PlayerIDHelper.IsAppleMusic(sessionId))
{
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault();
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault();
fixedAlbum = fixedAlbum?.Replace(" - Single", "");
fixedAlbum = fixedAlbum?.Replace(" - EP", "");
}
else if (PlayerIDHelper.IsNeteaseFamily(sessionId))
{
@@ -340,18 +345,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
CurrentSongInfo = new SongInfo
{
Title = mediaProperties?.Title ?? "N/A",
Artists = fixedArtist.SplitByCommonSplitter(),
Album = fixedAlbum,
Title = mediaProperties?.Title ?? "",
Artists = fixedArtist?.SplitByCommonSplitter() ?? [],
Album = fixedAlbum ?? "",
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds ?? 0,
PlayerId = sessionId,
SongId = songId,
LinkedFileName = linkedFileName
};
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (PlayerIDHelper.IsLXMusic(sessionId))
{
StartSSE();

View File

@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>Copy</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>Music gallery</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>Settings</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>Cache path</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>Cache path</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>Lyrics provider</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>Lyrics parsing failed</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>Album</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>Artist</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>Target lyrics search provider</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>Title</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -859,6 +868,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>Automatically hide/show windows</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>Hold here and drag to sort</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>Import</value>
</data>
@@ -1120,6 +1132,15 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>Lyrics window status switcher shortcut</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>Lyrics window</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Adjusting this value will affect sequential search and best match search results, but will not affect search results in the manual lyrics search interface</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>Minimum matching threshold</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>Media library</value>
</data>
@@ -1168,6 +1189,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>Original text</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>Set the minimum match threshold individually</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>Color picker style</value>
</data>
@@ -1268,7 +1292,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<value>Current line duration</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>Lyrics scrolling animation type</value>
<value>Lyric animation type</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>First line delay</value>
@@ -1483,4 +1507,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics is already running</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>User Guide</value>
</data>
</root>

View File

@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>コピー</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>ミュージックギャラリー</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>歌詞が見つかりません</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>キャッシュパス</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>キャッシュパス</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞プロバイダー</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>歌詞の解析に失敗しました</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>アルバム</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>ターゲット歌詞検索プロバイダー</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>タイトル</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -859,6 +868,9 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>Windowsを自動的に非表示/表示します</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>ここを長押ししてドラッグして並べ替えましょう</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>インポート</value>
</data>
@@ -1120,6 +1132,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>歌詞ウィンドウステータススイッチャーショートカット</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>歌詞ウィンドウ</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>この値を調整すると、シーケンシャル検索と最適な検索結果に影響しますが、手動歌詞検索インターフェースの検索結果には影響しません</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>最小マッチングしきい値</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>メディアライブラリ</value>
</data>
@@ -1168,6 +1189,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>原文</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>最低マッチしきい値を個別に設定してください</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>カラーピックスタイル</value>
</data>
@@ -1268,7 +1292,7 @@
<value>現在のライン期間</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>歌詞スクロールアニメーションタイプ</value>
<value>リリックアニメーションタイプ</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>最初の行の遅延</value>
@@ -1483,4 +1507,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics はすでに実行されています</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>利用案内</value>
</data>
</root>

View File

@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>취소</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>접수</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>음악 갤러리</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>설정</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>캐시</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>캐시</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>가사 제공자</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>번역 제공자</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>가사 구문 분석에 실패했습니다</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>앨범</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>가사 검색 공급자를 타겟팅하십시오</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>제목</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -859,6 +868,9 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>Windows를 자동으로 숨기고 표시합니다</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>여기를 누른 상태에서 드래그하여 정렬하세요</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>가져오기</value>
</data>
@@ -1120,6 +1132,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>가사 창 상태 전환기 바로 가기</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>가사 창</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>이 값을 조정하면 순차 검색 및 검색 결과와 가장 일치하는 검색 결과에 영향을 미치지만 수동 가사 검색 인터페이스의 검색 결과에는 영향을 미치지 않습니다</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>최소 일치 임계값</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>미디어 라이브러리</value>
</data>
@@ -1168,6 +1189,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>원본</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>최소 일치 임계값을 개별적으로 설정하십시오</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>색상 선택 스타일</value>
</data>
@@ -1268,7 +1292,7 @@
<value>현재 라인 기간</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>가사 스크롤링 애니메이션 유형</value>
<value>가사 애니메이션 유형</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>첫 번째 줄 지연</value>
@@ -1483,4 +1507,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics 가 이미 실행 중입니다</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>사용 안내서</value>
</data>
</root>

View File

@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>拷贝</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>音乐库</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>设置</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>缓存路径</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>缓存路径</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌词来源</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻译来源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>歌词解析失败</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>专辑</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>目标歌词搜索提供商</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>标题</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -859,6 +868,9 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>自动隐藏/显示窗口</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>按住此处并拖动以排序</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>导入</value>
</data>
@@ -1120,6 +1132,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>歌词窗口状态切换器快捷方式</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>歌词窗口</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>调整此值将影响顺序搜索和最佳匹配搜索结果,但不会影响手动歌词搜索界面中的搜索结果</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>最小匹配阈值</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>媒体库</value>
</data>
@@ -1168,6 +1189,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>原文</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>单独设置最小匹配阈值</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>取色风格</value>
</data>
@@ -1268,7 +1292,7 @@
<value>当前行持续时间</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>歌词滚动动画类型</value>
<value>歌词动画类型</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>首行延时</value>
@@ -1483,4 +1507,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics 已经在运行</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>使用指南</value>
</data>
</root>

View File

@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>複製</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>音樂庫</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>檔案路徑緩存</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>檔案路徑緩存</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞來源</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻譯來源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>歌詞解析失敗</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>專輯</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>目標歌詞搜尋提供者</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>標題</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -859,6 +868,9 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>自動隱藏/顯示窗口</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>按住這裡並拖曳以進行排序</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>匯入</value>
</data>
@@ -1120,6 +1132,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>歌詞視窗狀態切換器捷徑</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>歌詞視窗</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>調整此值將影響順序搜索和最佳匹配搜索結果,但不會影響手動歌詞搜索界面中的搜索結果</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>最低匹配閾值</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>媒體庫</value>
</data>
@@ -1168,6 +1189,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>原文</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>單獨設置最低匹配閾值</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>取色風格</value>
</data>
@@ -1268,7 +1292,7 @@
<value>當前行持續時間</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>歌詞滾動動畫類型</value>
<value>歌詞動畫類型</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>首行延時</value>
@@ -1483,4 +1507,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics 已經在運作</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>使用手冊</value>
</data>
</root>

View File

@@ -36,7 +36,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Windows.System.Launcher.LaunchUriAsync(new Uri(Constants.Link.GitHubUrl));
await Windows.System.Launcher.LaunchUriAsync(new Uri(Constants.Link.GitHub));
}
[RelayCommand]

View File

@@ -26,6 +26,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
{
DrawBlurredLyrics(control, blurredLyricsDs);
//DrawBlurredLyrics2(control, blurredLyricsDs);
}
using var combined = new CanvasCommandList(control);
@@ -297,17 +298,18 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
//// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition)
* Matrix3x2.CreateRotation((float)line.AngleTransition.Value,
currentPlayingLine.OriginalPosition.WithX(_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.FanLyricsAngle < 0 ? (float)_maxLyricsWidth : 0))
* Matrix3x2.CreateTranslation((float)_lyricsX, (float)yOffset);
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition) *
Matrix3x2.CreateRotation(
(float)line.AngleTransition.Value,
currentPlayingLine.OriginalPosition.WithX(_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.FanLyricsAngle < 0 ? (float)_maxLyricsWidth : 0)) *
Matrix3x2.CreateTranslation((float)_lyricsX, (float)yOffset);
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
using var backgroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
using var backgroundEffect = CanvasHelper.CreateBackgroundEffect(line, backgroundFontEffect, _lyricsOpacityTransition.Value);
combinedDs.DrawImage(backgroundEffect);
@@ -368,7 +370,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
combinedDs.DrawImage(new OpacityEffect
{
Source = effectLayer,
Opacity = (float)(line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value),
Opacity = (float)Math.Clamp(line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value, 0, 1),
});
if (i == _playingLineIndex)
@@ -404,6 +406,125 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
}
}
public void DrawBlurredLyrics2(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine == null) return;
var settings = _liveStatesService.LiveStates.LyricsWindowStatus;
var effectSettings = settings.LyricsEffectSettings;
bool isKaraokeEnabled = effectSettings.IsLyricsLineFadeEnabled;
var styleSettings = settings.LyricsStyleSettings; // 获取样式设置(描边宽等)
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null || line.OriginalCanvasTextLayout == null) continue;
var textLayout = line.OriginalCanvasTextLayout;
// === 1. 恢复您原始的矩阵变换逻辑 ===
// 不要减去 CenterPosition保持和您原始代码一致
double yOffset = line.YOffsetTransition.Value + _canvasHeight / 2 + _lyricsYTransition.Value;
float fanAngleX = effectSettings.FanLyricsAngle < 0 ? (float)_maxLyricsWidth : 0;
ds.Transform =
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition)
* Matrix3x2.CreateRotation((float)line.AngleTransition.Value,
currentPlayingLine.OriginalPosition.WithX(fanAngleX))
* Matrix3x2.CreateTranslation((float)_lyricsX, (float)yOffset);
// === 2. 坐标关键:必须使用 line.OriginalPosition ===
// CanvasHelper 里是画在 OriginalPosition 上的,我们这里必须一致
var drawPos = line.OriginalPosition;
// A. 绘制阴影 (优化:直接绘制,替代 ShadowEffect)
if (effectSettings.IsLyricsShadowEnabled)
{
var shadowColor = _albumArtAccentColor1Transition.Value.WithAlpha((byte)(effectSettings.LyricsShadowAmount * 255));
// 偏移 2px 绘制阴影
ds.DrawTextLayout(textLayout, drawPos.X + 2f, drawPos.Y + 2f, shadowColor);
}
// B. 绘制描边 (如果设置里有)
// 对应 CanvasHelper.CreateFontEffect 里的 stroke 逻辑
if (styleSettings.LyricsFontStrokeWidth > 0)
{
// 注意DrawTextLayout 不支持直接描边,需要用 DrawGeometry
// 如果这一步很卡,可以考虑去掉描边,或者只对当前行描边
if (line.OriginalCanvasGeometry != null)
{
ds.DrawGeometry(line.OriginalCanvasGeometry, drawPos, _strokeFontColor, styleSettings.LyricsFontStrokeWidth);
}
}
// C. 绘制底层文本 (底色)
// 对应 CanvasHelper 里的 DrawTextLayout
var baseColor = _strokeFontColor.WithAlpha((byte)(_strokeFontColor.A * _lyricsOpacityTransition.Value));
ds.DrawTextLayout(textLayout, drawPos, baseColor);
// D. 绘制高亮/卡拉OK效果 (核心优化点)
if (line.HighlightOpacityTransition.Value > 0)
{
GetLinePlayingProgress(i, out int charStartIndex, out int charLength, out double charProgress);
// 整行高亮或非逐字模式
if (charStartIndex >= line.OriginalText.Length || !isKaraokeEnabled)
{
var hlColor = _fgFontColor.WithAlpha((byte)(_fgFontColor.A * line.HighlightOpacityTransition.Value));
ds.DrawTextLayout(textLayout, drawPos, hlColor);
}
else
{
// === 计算裁剪区域 (照搬 CreateCharMask 的逻辑) ===
var regions = textLayout.GetCharacterRegions(charStartIndex, 1);
// 默认裁剪宽度覆盖到当前字之前
double validWidth = 0;
if (regions.Length > 0)
{
var region = regions[0];
// CanvasHelper 里的逻辑region.LayoutBounds.X 是相对于 Layout 左上角的
// highlightWidth = TotalWidth * Progress
double highlightWidth = region.LayoutBounds.Width * charProgress;
// 当前高亮的总右边界 = 当前字左边 + 当前字播放过的宽度
validWidth = region.LayoutBounds.X + highlightWidth;
}
else if (charStartIndex > 0)
{
// 容错:如果取不到当前字区域(例如空格),取上一个字的右边缘
var prevRegions = textLayout.GetCharacterRegions(charStartIndex - 1, 1);
if (prevRegions.Length > 0) validWidth = prevRegions[0].LayoutBounds.Right;
}
if (validWidth > 0)
{
// === 创建 Layer 进行裁剪 ===
// 裁剪矩形的 X/Y 必须加上 drawPos (OriginalPosition)
// 因为 CreateLayer 是基于当前 Transform 的全局坐标
var clipRect = new Rect(
drawPos.X, // 从文字绘制起点的 X 开始
drawPos.Y, // 从文字绘制起点的 Y 开始
validWidth, // 宽度
textLayout.LayoutBounds.Height // 高度
);
using (ds.CreateLayer(1.0f, clipRect))
{
var hlColor = _fgFontColor.WithAlpha((byte)(_fgFontColor.A * line.HighlightOpacityTransition.Value));
ds.DrawTextLayout(textLayout, drawPos, hlColor);
}
}
}
}
// 重置变换,准备画下一行
ds.Transform = Matrix3x2.Identity;
}
}
private void DrawSnowEffect(CanvasDrawingSession ds)
{
if (_snowEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled)

View File

@@ -892,7 +892,27 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
yScrollDelay = distanceFactor * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.LyricsScrollBottomDelay / 1000.0;
}
line.YOffsetTransition.SetEasingType(_canvasYScrollTransition.EasingType ?? EasingType.Linear);
line.AngleTransition.SetEasingType(_canvasYScrollTransition.EasingType);
line.AngleTransition.SetDuration(yScrollDuration);
line.AngleTransition.SetDelay(yScrollDelay);
line.ScaleTransition.SetEasingType(_canvasYScrollTransition.EasingType);
line.ScaleTransition.SetDuration(yScrollDuration);
line.ScaleTransition.SetDelay(yScrollDelay);
line.BlurAmountTransition.SetEasingType(_canvasYScrollTransition.EasingType);
line.BlurAmountTransition.SetDuration(yScrollDuration);
line.BlurAmountTransition.SetDelay(yScrollDelay);
line.OpacityTransition.SetEasingType(_canvasYScrollTransition.EasingType);
line.OpacityTransition.SetDuration(yScrollDuration);
line.OpacityTransition.SetDelay(yScrollDelay);
line.HighlightOpacityTransition.SetEasingType(_canvasYScrollTransition.EasingType);
line.HighlightOpacityTransition.SetDuration(yScrollDuration);
line.HighlightOpacityTransition.SetDelay(yScrollDelay);
line.YOffsetTransition.SetEasingType(_canvasYScrollTransition.EasingType);
line.YOffsetTransition.SetDuration(yScrollDuration);
line.YOffsetTransition.SetDelay(yScrollDelay);
line.YOffsetTransition.StartTransition(_canvasTargetYScrollOffset, _isLayoutChanged);

View File

@@ -65,7 +65,7 @@
Glyph=&#xE713;}"
Style="{StaticResource TitleBarButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="HostWindowSettingsButtonToolTip" />
<ToolTip x:Uid="HostWindowSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>

View File

@@ -564,13 +564,11 @@
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip>
<ToolTip.Content>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip.Content>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip>
</ToolTipService.ToolTip>
<Button.Content>

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
@@ -22,7 +23,7 @@
Glyph=&#xE90B;}"
Style="{StaticResource TitleBarButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SystemTrayLyrics" />
<ToolTip x:Uid="SettingsPageLyricsWindowToolTip" />
</ToolTipService.ToolTip>
</Button>
<Button
@@ -33,7 +34,7 @@
Glyph=&#xE713;}"
Style="{StaticResource TitleBarButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="HostWindowSettingsButtonToolTip" />
<ToolTip x:Uid="HostWindowSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
@@ -21,7 +22,7 @@
Glyph=&#xE90B;}"
Style="{StaticResource TitleBarButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SystemTrayLyrics" />
<ToolTip x:Uid="SettingsPageLyricsWindowToolTip" />
</ToolTipService.ToolTip>
</Button>
<Button

View File

@@ -14,7 +14,7 @@ BetterLyrics
<div align="center">
[操作指南](https://jayfunc.blog/work/betterlyrics-cn) | [隐私政策](PrivacyPolicy.CN.md) | [服务协议](TermsofService.CN.md)
[使用指南](https://github.com/jayfunc/BetterLyrics/wiki/使用指南) | [隐私政策](PrivacyPolicy.CN.md) | [服务协议](TermsofService.CN.md)
</div>
@@ -75,7 +75,7 @@ BetterLyrics
- 🎶 **支持众多音乐播放器**
- 点击 [此处](https://jayfunc.blog/work/betterlyrics-cn#yi-zhi-zhi-chi-de-yin-le-bo-fang-qi-pei-zhi-zhi-nan) 查看详细信息
- 点击 [此处](https://github.com/jayfunc/BetterLyrics/wiki/使用指南#已知支持的音乐播放器配置指南) 查看详细信息
- 🪟 **多种显示模式**
- **标准模式**

View File

@@ -14,7 +14,7 @@ BetterLyrics
<div align="center">
[Wiki](https://jayfunc.blog/work/betterlyrics) | [Privacy Policy](PrivacyPolicy.md) | [Terms of Service](TermsofService.md)
[User Guide](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide) | [Privacy Policy](PrivacyPolicy.md) | [Terms of Service](TermsofService.md)
</div>
@@ -81,7 +81,7 @@ Check out the article: [BetterLyrics An immersive and smooth lyrics display
- 🎶 **Multiple Music Players Supported**
- Check it out [here](https://jayfunc.blog/work/betterlyrics#known-supported-music-players-configuration-guidance)) for detailed info
- Check it out [here](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide#known-supported-music-players-configuration-guide) for detailed info
- 🪟 **Multiple Display Modes**
- **Standard Mode**

View File

@@ -1,47 +1,57 @@
# Welcome to ShareHub
# Welcome to ShareHub | 欢迎来到 ShareHub
## Shared lyrics window status
## Shared lyrics window status | 已分享的歌词窗口状态
Click on the links below to view and download the config files.
Click on the links below to view and download the config files. | 点击以下链接查看和下载配置文件。
Each link is accompanied by a preview image of the lyrics window in that specific status.
Each link is accompanied by a preview image of the lyrics window in that specific status. | 每个链接都附带该状态下歌词窗口的预览图像。
### D
---
- [Desktop](LyricsWindowStatus/desktop.json)
### [Desktop | 桌面](LyricsWindowStatus/desktop.json)
![](LyricsWindowStatus/desktop.png)
- [Docked (Bottom)](LyricsWindowStatus/docked-bottom.json)
---
### [Docked (Bottom) | 停靠(底部)](LyricsWindowStatus/docked-bottom.json)
![](LyricsWindowStatus/docked-bottom.png)
- [Docked (Top)](LyricsWindowStatus/docked-top.json)
---
### [Docked (Top) | 停靠(顶部)](LyricsWindowStatus/docked-top.json)
![](LyricsWindowStatus/docked-top.png)
### F
---
- [Fullscreen (Horizontal)](LyricsWindowStatus/fs-horiz.json)
### [Fullscreen (Horizontal) | 全屏(横屏)](LyricsWindowStatus/fs-horiz.json)
![](LyricsWindowStatus/fs-horiz.png)
- [Fullscreen (Vertical)](LyricsWindowStatus/fs-vert.json)
---
### [Fullscreen (Vertical) | 全屏(竖屏)](LyricsWindowStatus/fs-vert.json)
![](LyricsWindowStatus/fs-vert.png)
### S
---
- [Standard (Horizontal)](LyricsWindowStatus/std-horiz.json)
### [Standard (Horizontal) | 标准(横屏)](LyricsWindowStatus/std-horiz.json)
![](LyricsWindowStatus/std-horiz.png)
- [Standard (Vertical)](LyricsWindowStatus/std-vert.json)
---
### [Standard (Vertical) | 标准(竖屏)](LyricsWindowStatus/std-vert.json)
![](LyricsWindowStatus/std-vert.png)
### T
---
- [Taskbar](LyricsWindowStatus/taskbar.json)
### [Taskbar | 任务栏](LyricsWindowStatus/taskbar.json)
![](LyricsWindowStatus/taskbar.png)
---