Compare commits

...

34 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
Zhe Fang
a3366422a2 chores: code cleanup 2025-11-20 17:35:36 -05:00
Zhe Fang
8f6e106282 chores: bump version code 2025-11-20 13:02:33 -05:00
Zhe Fang
08d4f4ce90 fix: clear all media props when switching tracks (local player) 2025-11-20 12:30:06 -05:00
Zhe Fang
212041a509 fix: can not find local .lrc lyrics file when metadata is missing in the music file 2025-11-20 10:09:31 -05:00
Zhe Fang
c0f2b4106a fix: change discord presence dep 2025-11-19 18:38:18 -05:00
Zhe Fang
c704edde19 chores: code cleanup 2025-11-19 17:50:59 -05:00
Zhe Fang
35de1ef7db add: roman parse for ncm and amll-ttml-db lyrics source 2025-11-19 17:49:24 -05:00
Zhe Fang
1c8840e053 fix: search control title missing 2025-11-19 11:40:58 -05:00
Zhe Fang
2d0839d777 fix: apple music metadata fetch missing 2025-11-19 11:32:05 -05:00
82 changed files with 2307 additions and 1320 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

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

View File

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

View File

@@ -69,6 +69,7 @@
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
<converter:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
<converter:PictureInfosToImageSourceConverter x:Key="PictureInfosToImageSourceConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />

View File

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

View File

@@ -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

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
using ATL;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BetterLyrics.WinUI3.Converter
{
public partial class PictureInfosToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
BitmapImage bitmapImage = new();
if (value is IList<PictureInfo> list && list.FirstOrDefault()?.PictureData is byte[] pictureData)
{
bitmapImage.SetSource(ImageHelper.ToIRandomAccessStream(pictureData));
}
else
{
bitmapImage.UriSource = new Uri(PathHelper.AlbumArtPlaceholderPath);
}
return bitmapImage;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Extensions
{
@@ -18,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

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

View File

@@ -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

@@ -3,7 +3,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Serialization;
using System;
using System.IO;
@@ -49,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;
@@ -69,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

@@ -185,5 +185,11 @@ namespace BetterLyrics.WinUI3.Helper
{
return buffer.AsStream().AsRandomAccessStream();
}
public static IRandomAccessStream ToIRandomAccessStream(byte[] arr)
{
MemoryStream stream = new MemoryStream(arr);
return stream.AsRandomAccessStream();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

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

View File

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

View File

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

View File

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

View File

@@ -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

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

View File

@@ -14,7 +14,6 @@ using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
using Vanara.Windows.Shell;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using WinRT.Interop;
@@ -119,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

@@ -17,6 +17,7 @@ namespace BetterLyrics.WinUI3.Models
get => field ?? LanguageHelper.DetectLanguageCode(WrappedOriginalText);
set => field = value;
}
public bool AutoGenerated { get; set; } = false;
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
public bool IsWordByWord => LyricsLines.Any(x => x.LyricsChars.Count != 0);
@@ -130,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"),
}]);
}
@@ -148,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;
@@ -10,8 +11,17 @@ namespace BetterLyrics.WinUI3.Models
public LyricsSearchProvider Provider { get; set; }
public string? Raw { get; set; }
/// <summary>
/// 翻译也可能位于 <see cref="Raw"/>
/// </summary>
public string? Translation { get; set; }
/// <summary>
/// 音译也可能位于 <see cref="Raw"/>
/// </summary>
public string? Transliteration { get; set; }
public string? Title { get; set; }
public string[]? Artists { get; set; }
public string? Album { get; set; }
@@ -19,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

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

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

View File

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

View File

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

View File

@@ -0,0 +1,174 @@
// 2025/6/23 by Zhe Fang
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;
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 (string.IsNullOrWhiteSpace(lyricsSearchResult?.Raw))
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
else
{
switch (lyricsSearchResult.Raw.DetectFormat())
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(lyricsSearchResult.Raw, lyricsSearchResult.Provider.IsRemote());
break;
case LyricsFormat.Qrc:
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Krc:
ParseQrcKrc(KrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(lyricsSearchResult.Raw);
break;
default:
break;
}
if (LyricsDataArr.Count == 0)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
}
LoadTranslation(lyricsSearchResult);
LoadTransliteration(lyricsSearchResult);
GenerateTransliterationLyricsData();
}
private void LoadTranslation(LyricsSearchResult? lyricsSearchResult)
{
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Translation))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.QQ:
case LyricsSearchProvider.Kugou:
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Translation, true);
break;
default:
break;
}
}
}
private void LoadTransliteration(LyricsSearchResult? lyricsSearchResult)
{
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Transliteration))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Transliteration, true);
LyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
break;
default:
break;
}
}
}
/// <summary>
/// 在音译不存在的情况下生成音译歌词
/// </summary>
private void GenerateTransliterationLyricsData()
{
var main = LyricsDataArr.FirstOrDefault();
if (main != null)
{
string? languageCode = main.LanguageCode;
if (languageCode == LanguageHelper.ChineseCode)
{
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.PinyinCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.PinyinCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToPinyin(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToPinyin(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.JyutpingCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.JyutpingCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToJyutping(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToJyutping(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
else if (languageCode == LanguageHelper.JapaneseCode)
{
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.RomanCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.RomanCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToRomaji(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToRomaji(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
}
}
}
}

View File

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

View File

@@ -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

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

View File

@@ -18,10 +18,8 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Documents;
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
@@ -42,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();
@@ -146,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;
}
}
}
}
@@ -205,8 +213,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
try
{
LyricsFormat lyricsFormat = provider.GetLyricsFormat();
// Check cache first if allowed
if (checkCache && provider.IsRemote())
{
@@ -218,47 +224,40 @@ 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
{
lyricsSearchResult = await SearchFile(songInfo, lyricsFormat);
}
}
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.LocalLrcFile:
case LyricsSearchProvider.LocalEslrcFile:
case LyricsSearchProvider.LocalTtmlFile:
lyricsSearchResult = await SearchFile(songInfo, provider.GetLyricsFormat());
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(songInfo);
break;
default:
break;
}
if (token.IsCancellationRequested)
{
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
return lyricsSearchResult;
}
@@ -267,14 +266,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
}
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
if (lyricsSearchResult.IsFound)
if (provider.IsRemote())
{
if (provider.IsRemote())
{
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
}
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
}
return lyricsSearchResult;
@@ -282,6 +276,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private async Task<LyricsSearchResult> SearchFile(SongInfo songInfo, LyricsFormat format)
{
int maxScore = 0;
string? bestFile = null;
var lyricsSearchResult = new LyricsSearchResult();
if (format.ToLyricsSearchProvider() is LyricsSearchProvider lyricsSearchProvider)
@@ -297,18 +294,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
{
var fileName = Path.GetFileNameWithoutExtension(file);
if (StringHelper.IsSwitchableNormalizedMatch(fileName, songInfo.Title, songInfo.DisplayArtists) || songInfo.LinkedFileName == fileName)
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult { Reference = file });
if (score > maxScore)
{
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
if (raw != null)
{
lyricsSearchResult.Raw = raw;
lyricsSearchResult.CopyFromSongInfo(songInfo);
lyricsSearchResult.Reference = file;
return lyricsSearchResult;
}
bestFile = file;
maxScore = score;
}
}
}
@@ -317,11 +307,28 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
}
if (bestFile != null)
{
lyricsSearchResult.Reference = bestFile;
lyricsSearchResult.MatchPercentage = maxScore;
string? raw = await File.ReadAllTextAsync(bestFile, FileHelper.GetEncoding(bestFile));
if (raw != null)
{
lyricsSearchResult.Raw = raw;
}
}
return lyricsSearchResult;
}
private LyricsSearchResult SearchEmbedded(SongInfo songInfo)
{
int bestScore = 0;
string? bestFile = null;
string? bestRaw = null;
var lyricsSearchResult = new LyricsSearchResult
{
Provider = LyricsSearchProvider.LocalMusicFile,
@@ -336,24 +343,45 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
if ((songInfo.Album != "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists && track.Album == songInfo.Album)
|| (songInfo.Album == "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists)
|| (songInfo.Album == "" && StringHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.Title, songInfo.DisplayArtists)))
{
var plain = track.GetRawLyrics();
if (!plain.IsNullOrEmpty())
{
lyricsSearchResult.Raw = plain;
lyricsSearchResult.CopyFromSongInfo(songInfo);
lyricsSearchResult.Reference = file;
var raw = track.GetRawLyrics();
return lyricsSearchResult;
if (!string.IsNullOrEmpty(raw))
{
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;
}
}
}
}
}
}
if (bestFile != null)
{
var track = new Track(bestFile);
lyricsSearchResult.Title = track.Title;
lyricsSearchResult.Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator);
lyricsSearchResult.Album = track.Album;
lyricsSearchResult.Duration = track.Duration;
lyricsSearchResult.Raw = bestRaw;
lyricsSearchResult.Reference = bestFile;
lyricsSearchResult.MatchPercentage = bestScore;
}
return lyricsSearchResult;
}
@@ -425,6 +453,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
catch { }
}
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
if (string.IsNullOrWhiteSpace(rawLyricFile))
{
return lyricsSearchResult;
@@ -503,6 +533,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Reference = url;
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
return lyricsSearchResult;
}
@@ -560,6 +592,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = response?.Lrc?.Lyric;
lyricsSearchResult.Translation = response?.Tlyric?.Lyric;
lyricsSearchResult.Transliteration = response?.Romalrc?.Lyric;
lyricsSearchResult.Reference = $"https://music.163.com/song?id={neteaseResult.Id}";
}
else if (result is KugouSearchResult kugouResult)
@@ -602,26 +635,23 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Album = result?.Album;
lyricsSearchResult.Duration = result?.DurationMs / 1000;
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
return lyricsSearchResult;
}
private async Task<LyricsSearchResult> SearchAppleMusicAsync(SongInfo songInfo)
{
var lyricsSearchResult = new LyricsSearchResult
LyricsSearchResult lyricsSearchResult = new()
{
Provider = LyricsSearchProvider.AppleMusic,
Provider = LyricsSearchProvider.AppleMusic
};
_logger.LogInformation("SearchAppleMusicAsync");
if (await _appleMusic.InitAsync())
{
string id = await _appleMusic.SearchSongInfoAsync(songInfo.DisplayArtists, songInfo.Title);
string? raw = await _appleMusic.GetLyricsAsync(id);
_logger.LogInformation("SearchAppleMusicAsync");
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = songInfo.Title;
lyricsSearchResult.Artists = songInfo.Artists;
lyricsSearchResult.Album = "";
lyricsSearchResult.Reference = $"https://music.apple.com/song/{id}";
lyricsSearchResult = await _appleMusic.SearchSongInfoAsync(songInfo);
}
return lyricsSearchResult;

View File

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

View File

@@ -23,17 +23,15 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using DevWinUI;
using EvtSource;
using Lyricify.Lyrics.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Vanara.Windows.Shell;
using Windows.Media.Control;
@@ -185,6 +183,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
case nameof(MediaSourceProviderInfo.LyricsSearchType):
UpdateLyrics();
break;
case nameof(MediaSourceProviderInfo.MatchingThreshold):
UpdateLyrics();
break;
default:
break;
}
@@ -320,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))
{
@@ -342,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();
@@ -526,7 +526,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_logger.LogError("Failed to start SSE connection for LX Music.");
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("FailToStartLXMusicServer"));
ToastHelper.ShowToast("FailToStartLXMusicServer", null, InfoBarSeverity.Error);
});
StopSSE();
}

View File

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

View File

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

View File

@@ -144,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Copy.Content" xml:space="preserve">
<value>Copy</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>Playlist was created successfully</value>
</data>
@@ -180,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">
@@ -228,7 +231,13 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<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>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +246,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>Lyrics Window Management Shortcut Settings</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>Match percentage</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,16 +273,22 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>Show translation only</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<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="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>Auto generated</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>Duration</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -306,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">
@@ -394,43 +409,43 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Clear play queue</value>
</data>
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
<data name="MusicGalleryPageFileAlbum.Header" xml:space="preserve">
<value>Album</value>
</data>
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
<data name="MusicGalleryPageFileArtist.Header" xml:space="preserve">
<value>Artist</value>
</data>
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
<value>File info</value>
</data>
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitDepth.Header" xml:space="preserve">
<value>Bit depth</value>
</data>
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitrate.Header" xml:space="preserve">
<value>Bitrate</value>
</data>
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoDuration.Header" xml:space="preserve">
<value>Duration</value>
</data>
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoEncoder.Header" xml:space="preserve">
<value>Encoder</value>
</data>
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoFormat.Header" xml:space="preserve">
<value>Format</value>
</data>
<data name="MusicGalleryPageFileInfoLyrics.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoLyrics.Header" xml:space="preserve">
<value>Lyrics</value>
</data>
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoPath.Header" xml:space="preserve">
<value>Path</value>
</data>
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoSampleRate.Header" xml:space="preserve">
<value>Sample rate</value>
</data>
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoTitle.Header" xml:space="preserve">
<value>Title</value>
</data>
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoYear.Header" xml:space="preserve">
<value>Year</value>
</data>
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
@@ -565,6 +580,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>Advanced options</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>Album</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>Album art</value>
</data>
@@ -610,6 +628,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>Apply</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>Artist</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>Automatic adjustment</value>
</data>
@@ -847,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>
@@ -1108,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>
@@ -1156,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>
@@ -1189,12 +1225,15 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>Play</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>Playback source</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>Playback source ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>Current playback source</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
</data>
@@ -1219,6 +1258,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>Recorded window status</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>Reference link</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>Original files and folders in this path will not be deleted when removing it from this app</value>
</data>
@@ -1250,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>
@@ -1258,6 +1300,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>The duration of the first line</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>Current lyrics search result</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>Test server</value>
</data>
@@ -1321,6 +1366,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>Current song</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>Title</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[Experimental] Spectrum Layer</value>
</data>
@@ -1456,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,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="Copy.Content" xml:space="preserve">
<value>コピー</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>正常に作成されました</value>
</data>
@@ -180,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">
@@ -228,7 +231,13 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>歌詞が見つかりません</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<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>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +246,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>歌詞ウィンドウ管理ショートカット設定</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>マッチ率</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,16 +273,22 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>翻訳のみを表示します</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<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="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>自動生成されました</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>間隔</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -306,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">
@@ -394,43 +409,43 @@
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>クリアプレイキュー</value>
</data>
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
<data name="MusicGalleryPageFileAlbum.Header" xml:space="preserve">
<value>アルバム</value>
</data>
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
<data name="MusicGalleryPageFileArtist.Header" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
<value>ファイル情報</value>
</data>
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitDepth.Header" xml:space="preserve">
<value>ビットの深さ</value>
</data>
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitrate.Header" xml:space="preserve">
<value>ビットレート</value>
</data>
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoDuration.Header" xml:space="preserve">
<value>間隔</value>
</data>
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoEncoder.Header" xml:space="preserve">
<value>エンコーダー</value>
</data>
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoFormat.Header" xml:space="preserve">
<value>形式</value>
</data>
<data name="MusicGalleryPageFileInfoLyrics.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoLyrics.Header" xml:space="preserve">
<value>歌詞</value>
</data>
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoPath.Header" xml:space="preserve">
<value>パス</value>
</data>
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoSampleRate.Header" xml:space="preserve">
<value>サンプルレート</value>
</data>
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoTitle.Header" xml:space="preserve">
<value>タイトル</value>
</data>
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoYear.Header" xml:space="preserve">
<value>年</value>
</data>
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
@@ -565,6 +580,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>高度なオプション</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>アルバム</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>アルバムアート</value>
</data>
@@ -610,6 +628,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>適用する</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自動調整</value>
</data>
@@ -847,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>
@@ -1108,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>
@@ -1156,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>
@@ -1189,12 +1225,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>遊ぶ</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>再生ソース</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>再生ソースID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>現在の再生ソース</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</value>
</data>
@@ -1219,6 +1258,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>記録されたウィンドウステータス</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>参照リンク</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>このパスの元のファイルとフォルダーは、このアプリから削除するときに削除されません</value>
</data>
@@ -1250,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>
@@ -1258,6 +1300,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>最初の行の期間</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>現在の歌詞検索結果</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>テストサーバー</value>
</data>
@@ -1321,6 +1366,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>現在の曲です</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>タイトル</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[実験的] スペクトラムレイヤー</value>
</data>
@@ -1456,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,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>취소</value>
</data>
<data name="Copy.Content" xml:space="preserve">
<value>접수</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>재생 목록이 성공적으로 생성되었습니다</value>
</data>
@@ -180,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">
@@ -228,7 +231,13 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<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>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +246,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>가사 창 관리 바로 가기 설정</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>매치 퍼센트</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,16 +273,22 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>번역 만 표시하십시오</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>번역 제공자</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<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="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>생성됨</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>지속</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -306,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">
@@ -394,43 +409,43 @@
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>플레이 대기열을 클리어합니다</value>
</data>
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
<data name="MusicGalleryPageFileAlbum.Header" xml:space="preserve">
<value>앨범</value>
</data>
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
<data name="MusicGalleryPageFileArtist.Header" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
<value>파일 정보</value>
</data>
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitDepth.Header" xml:space="preserve">
<value>비트 깊이</value>
</data>
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitrate.Header" xml:space="preserve">
<value>비트 레이트</value>
</data>
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoDuration.Header" xml:space="preserve">
<value>지속</value>
</data>
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoEncoder.Header" xml:space="preserve">
<value>인코더</value>
</data>
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoFormat.Header" xml:space="preserve">
<value>체재</value>
</data>
<data name="MusicGalleryPageFileInfoLyrics.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoLyrics.Header" xml:space="preserve">
<value>가사</value>
</data>
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoPath.Header" xml:space="preserve">
<value>길</value>
</data>
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoSampleRate.Header" xml:space="preserve">
<value>샘플 속도</value>
</data>
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoTitle.Header" xml:space="preserve">
<value>제목</value>
</data>
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoYear.Header" xml:space="preserve">
<value>년도</value>
</data>
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
@@ -565,6 +580,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>고급 옵션</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>앨범</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>앨범 아트</value>
</data>
@@ -610,6 +628,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>적용하다</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>자동 조정</value>
</data>
@@ -847,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>
@@ -1108,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>
@@ -1156,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>
@@ -1189,12 +1225,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>놀다</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>재생 소스</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>재생 소스 ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>현재 재생 소스</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</value>
</data>
@@ -1219,6 +1258,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>기록 된 창 상태</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>참고문헌</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>이 경로의 원본 파일과 폴더는이 앱에서 제거 할 때 삭제되지 않습니다.</value>
</data>
@@ -1250,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>
@@ -1258,6 +1300,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>첫 번째 줄의 기간</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>현재 가사 검색 결과</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>테스트 서버</value>
</data>
@@ -1321,6 +1366,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>현재 노래</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>제목</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[실험] 스펙트럼 레이어</value>
</data>
@@ -1456,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,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Content" xml:space="preserve">
<value>拷贝</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>播放列表创建成功</value>
</data>
@@ -180,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">
@@ -228,7 +231,13 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<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>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +246,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>歌词窗口管理快捷设置</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>匹配百分比</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,16 +273,22 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>仅显示翻译</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻译来源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<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="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>自动生成</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>时长</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -306,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">
@@ -394,43 +409,43 @@
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>清除播放队列</value>
</data>
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
<data name="MusicGalleryPageFileAlbum.Header" xml:space="preserve">
<value>专辑</value>
</data>
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
<data name="MusicGalleryPageFileArtist.Header" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
<value>文件信息</value>
</data>
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitDepth.Header" xml:space="preserve">
<value>位深度</value>
</data>
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitrate.Header" xml:space="preserve">
<value>比特率</value>
</data>
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoDuration.Header" xml:space="preserve">
<value>期间</value>
</data>
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoEncoder.Header" xml:space="preserve">
<value>编码器</value>
</data>
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoFormat.Header" xml:space="preserve">
<value>格式</value>
</data>
<data name="MusicGalleryPageFileInfoLyrics.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoLyrics.Header" xml:space="preserve">
<value>歌词</value>
</data>
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoPath.Header" xml:space="preserve">
<value>路径</value>
</data>
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoSampleRate.Header" xml:space="preserve">
<value>样本率</value>
</data>
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoTitle.Header" xml:space="preserve">
<value>标题</value>
</data>
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoYear.Header" xml:space="preserve">
<value>年</value>
</data>
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
@@ -565,6 +580,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>高级选项</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>专辑</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>专辑</value>
</data>
@@ -610,6 +628,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>应用</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自动调整</value>
</data>
@@ -847,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>
@@ -1108,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>
@@ -1156,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>
@@ -1189,12 +1225,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>播放</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>播放源</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>播放源 ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>当前播放源</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
@@ -1219,6 +1258,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>记录的窗口状态</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>参考链接</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>路径中的原始文件和文件夹不会被删除</value>
</data>
@@ -1250,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>
@@ -1258,6 +1300,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>首行持续时间</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>当前歌词搜索结果</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>测试服务器</value>
</data>
@@ -1321,6 +1366,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>当前歌曲</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>标题</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[实验性] 频谱层</value>
</data>
@@ -1456,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,6 +144,9 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Content" xml:space="preserve">
<value>複製</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>已成功建立播放清單</value>
</data>
@@ -180,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">
@@ -228,7 +231,13 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
<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>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
@@ -237,7 +246,7 @@
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<value>歌詞視窗管理快捷設定</value>
</data>
<data name="LyricsPageMatchPercentage.Text" xml:space="preserve">
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>匹配百分比</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
@@ -264,16 +273,22 @@
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>僅顯示翻譯</value>
</data>
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻譯來源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<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="LyricsSearchControlDurauion.Text" xml:space="preserve">
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
<value>已產生</value>
</data>
<data name="LyricsSearchControlDurauion.Header" xml:space="preserve">
<value>時長</value>
</data>
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
@@ -306,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">
@@ -394,43 +409,43 @@
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>清除播放隊列</value>
</data>
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
<data name="MusicGalleryPageFileAlbum.Header" xml:space="preserve">
<value>專輯</value>
</data>
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
<data name="MusicGalleryPageFileArtist.Header" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
<value>文件信息</value>
</data>
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitDepth.Header" xml:space="preserve">
<value>位深度</value>
</data>
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoBitrate.Header" xml:space="preserve">
<value>比特率</value>
</data>
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoDuration.Header" xml:space="preserve">
<value>期間</value>
</data>
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoEncoder.Header" xml:space="preserve">
<value>編碼器</value>
</data>
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoFormat.Header" xml:space="preserve">
<value>格式</value>
</data>
<data name="MusicGalleryPageFileInfoLyrics.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoLyrics.Header" xml:space="preserve">
<value>歌词</value>
</data>
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoPath.Header" xml:space="preserve">
<value>路徑</value>
</data>
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoSampleRate.Header" xml:space="preserve">
<value>樣本率</value>
</data>
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoTitle.Header" xml:space="preserve">
<value>標題</value>
</data>
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
<data name="MusicGalleryPageFileInfoYear.Header" xml:space="preserve">
<value>年</value>
</data>
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
@@ -565,6 +580,9 @@
<data name="SettingsPageAdvanced.Text" xml:space="preserve">
<value>高級選項</value>
</data>
<data name="SettingsPageAlbum.Header" xml:space="preserve">
<value>專輯</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>專輯</value>
</data>
@@ -610,6 +628,9 @@
<data name="SettingsPageApply.Content" xml:space="preserve">
<value>應用</value>
</data>
<data name="SettingsPageArtist.Header" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自動調整</value>
</data>
@@ -847,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>
@@ -1108,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>
@@ -1156,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>
@@ -1189,12 +1225,15 @@
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>播放</value>
</data>
<data name="SettingsPagePlaybackSource.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>播放源</value>
</data>
<data name="SettingsPagePlaybackSourceID.Text" xml:space="preserve">
<data name="SettingsPagePlaybackSourceID.Header" xml:space="preserve">
<value>播放源 ID</value>
</data>
<data name="SettingsPagePlaybackStatus.Header" xml:space="preserve">
<value>當前播放源</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
@@ -1219,6 +1258,9 @@
<data name="SettingsPageRecordedWindowStatus.Text" xml:space="preserve">
<value>記錄的窗口狀態</value>
</data>
<data name="SettingsPageReference.Header" xml:space="preserve">
<value>參考數據</value>
</data>
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
<value>路徑中的原始檔案和資料夾不會被刪除</value>
</data>
@@ -1250,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>
@@ -1258,6 +1300,9 @@
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
<value>首行持續時間</value>
</data>
<data name="SettingsPageSearchResultStatus.Header" xml:space="preserve">
<value>目前歌詞搜尋結果</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>測試服務器</value>
</data>
@@ -1321,6 +1366,12 @@
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>當前歌曲</value>
</data>
<data name="SettingsPageSongTitle.Header" xml:space="preserve">
<value>標題</value>
</data>
<data name="SettingsPageSpectrumLayer.Header" xml:space="preserve">
<value>[實驗性] 頻譜層</value>
</data>
@@ -1456,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

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

View File

@@ -1,9 +1,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Shaders;
using CommunityToolkit.WinUI;
using ComputeSharp.D2D1.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
@@ -28,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);
@@ -299,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);
@@ -370,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)
@@ -406,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

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
using ATL;
using BetterLyrics.WinUI3.Collections;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
@@ -14,6 +15,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -350,7 +352,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else
{
_playlistTracks = [];
DevWinUI.Growl.Error(_resourceService.GetLocalizedString("PlaylistViewFailed"), path);
ToastHelper.ShowToast("PlaylistViewFailed", path, InfoBarSeverity.Success);
}
}
break;
@@ -439,15 +441,19 @@ namespace BetterLyrics.WinUI3.ViewModels
else
{
var track = playQueueItem.Track;
var updater = _smtc.DisplayUpdater;
updater.ClearAll();
_smtc.IsEnabled = true;
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));
updater.AppMediaId = Package.Current.Id.FullName;
var storageFile = await StorageFile.GetFileFromPathAsync(track.Path);
await updater.CopyFromFileAsync(MediaPlaybackType.Music, storageFile);
updater.AppMediaId = Package.Current.Id.FullName;
updater.MusicProperties.AlbumTitle = track.Album;
updater.MusicProperties.Genres.Add($"FILENAME-{Path.GetFileNameWithoutExtension(track.Path)}");
updater.MusicProperties.Genres.Add($"{ExtendedGenreFiled.FileName}{Path.GetFileNameWithoutExtension(track.Path)}");
updater.Update();
}
}
@@ -489,7 +495,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (file != null)
{
AddFileToStarredPlaylists(file);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("CreatePlaylistSuccessfully"), file.Path);
ToastHelper.ShowToast("CreatePlaylistSuccessfully", file.Path, InfoBarSeverity.Success);
}
}
@@ -501,7 +507,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (file != null)
{
AddFileToStarredPlaylists(file);
DevWinUI.Growl.Success(_resourceService.GetLocalizedString("ImportPlaylistSuccessfully"), file.Path);
ToastHelper.ShowToast("ImportPlaylistSuccessfully", file.Path, InfoBarSeverity.Success);
}
}

View File

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

View File

@@ -27,13 +27,6 @@
<!-- Lyrics area -->
<renderer:LyricsRenderer />
<ScrollViewer HorizontalAlignment="Center" VerticalScrollBarVisibility="Hidden">
<StackPanel
Margin="40"
VerticalAlignment="Bottom"
dev:Growl.GrowlParent="True" />
</ScrollViewer>
<!-- Bottom command area -->
<Grid
x:Name="BottomCommandGrid"

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

@@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:ext="using:BetterLyrics.WinUI3.Extensions"
xmlns:helper="using:BetterLyrics.WinUI3.Helper"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
xmlns:local="using:BetterLyrics.WinUI3.Views"
@@ -15,6 +16,7 @@
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:muxm="using:Microsoft.UI.Xaml.Media"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="Page_Loaded"
Unloaded="Page_Unloaded"
@@ -45,7 +47,11 @@
ShouldConstrainToRootBounds="False">
<ScrollViewer MaxWidth="300" MaxHeight="600">
<Grid>
<controls:OpacityMaskView MaxHeight="300" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:OpacityMaskView Grid.Row="0" MaxHeight="300">
<controls:OpacityMaskView.OpacityMask>
<Rectangle>
<Rectangle.Fill>
@@ -56,117 +62,27 @@
</Rectangle.Fill>
</Rectangle>
</controls:OpacityMaskView.OpacityMask>
<FlipView ItemsSource="{x:Bind ViewModel.TrackRightTapped.EmbeddedPictures, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding PictureData, Converter={StaticResource ByteArrayToImageConverter}}" />
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
<Image Source="{x:Bind ViewModel.TrackRightTapped.EmbeddedPictures, Mode=OneWay, Converter={StaticResource PictureInfosToImageSourceConverter}}" Stretch="Uniform" />
</controls:OpacityMaskView>
<StackPanel
Margin="0,200,0,0"
Grid.Row="1"
Padding="12"
Spacing="12">
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoTitle" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Title, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileArtist" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Artist, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileAlbum" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Album, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoYear" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Year, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoDuration" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoBitrate" TextWrapping="Wrap" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Bitrate, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoSampleRate" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.SampleRate, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoBitDepth" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.BitDepth, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoFormat" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.AudioFormat.Name, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoEncoder" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Encoder, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoPath" />
<HyperlinkButton Padding="0" Click="SongPathHyperlinkButton_Click">
<HyperlinkButton.Content>
<TextBlock
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped.Path, Mode=OneWay}"
TextWrapping="Wrap" />
</HyperlinkButton.Content>
</HyperlinkButton>
</StackPanel>
<StackPanel>
<TextBlock x:Uid="MusicGalleryPageFileInfoLyrics" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.TrackRightTapped, Converter={StaticResource TrackToLyricsConverter}, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
Spacing="6">
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoTitle" Value="{x:Bind ViewModel.TrackRightTapped.Title, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileArtist" Value="{x:Bind ViewModel.TrackRightTapped.Artist, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileAlbum" Value="{x:Bind ViewModel.TrackRightTapped.Album, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoYear" Value="{x:Bind ViewModel.TrackRightTapped.Year, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoDuration" Value="{x:Bind ViewModel.TrackRightTapped.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoBitrate" Value="{x:Bind ViewModel.TrackRightTapped.Bitrate, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoSampleRate" Value="{x:Bind ViewModel.TrackRightTapped.SampleRate, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoBitDepth" Value="{x:Bind ViewModel.TrackRightTapped.BitDepth, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoFormat" Value="{x:Bind ViewModel.TrackRightTapped.AudioFormat.Name, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoEncoder" Value="{x:Bind ViewModel.TrackRightTapped.Encoder, Mode=OneWay}" />
<uc:PropertyRow
x:Uid="MusicGalleryPageFileInfoPath"
Link="{x:Bind ViewModel.TrackRightTapped.Path, Mode=OneWay}"
Value="{x:Bind ViewModel.TrackRightTapped.Path, Mode=OneWay}" />
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoLyrics" Value="{x:Bind ViewModel.TrackRightTapped, Converter={StaticResource TrackToLyricsConverter}, Mode=OneWay}" />
</StackPanel>
</Grid>
</ScrollViewer>
@@ -482,29 +398,29 @@
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" CornerRadius="6">
<Image Source="{Binding EmbeddedPictures[0].PictureData, Mode=OneWay, Converter={StaticResource ByteArrayToImageConverter}}" Stretch="Uniform" />
<Image Source="{x:Bind EmbeddedPictures, Mode=OneWay, Converter={StaticResource PictureInfosToImageSourceConverter}}" Stretch="Uniform" />
</Grid>
<!-- 基本信息 -->
<Grid Grid.Column="1">
<StackPanel VerticalAlignment="Center" Spacing="6">
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
<TextBlock Text="{x:Bind Title}" TextWrapping="Wrap" />
<StackPanel Orientation="Horizontal" Spacing="6">
<Grid Background="{ThemeResource AccentAcrylicBackgroundFillColorBaseBrush}" CornerRadius="4">
<TextBlock
Margin="4,2"
FontSize="12"
Text="{Binding AudioFormat.ShortName}" />
Text="{x:Bind AudioFormat.ShortName}" />
</Grid>
<HyperlinkButton Padding="0" Click="ArtistHyperlibkButton_Click">
<TextBlock Text="{Binding Artist}" TextWrapping="Wrap" />
<TextBlock Text="{x:Bind Artist}" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</StackPanel>
</Grid>
<HyperlinkButton Grid.Column="2" Click="AlbumHyperlibkButton_Click">
<TextBlock Text="{Binding Album}" TextWrapping="Wrap" />
<TextBlock Text="{x:Bind Album}" TextWrapping="Wrap" />
</HyperlinkButton>
<!-- 年份 -->
@@ -512,7 +428,7 @@
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
Text="{x:Bind Year}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
@@ -520,7 +436,7 @@
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
Text="{x:Bind Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
@@ -528,7 +444,7 @@
Grid.Column="5"
VerticalAlignment="Center"
Click="PathHyperlibkButton_Click"
Content="{Binding Path, Converter={StaticResource PathToParentFolderConverter}}" />
Content="{x:Bind Path, Converter={StaticResource PathToParentFolderConverter}}" />
<Button
Grid.Column="6"
@@ -554,7 +470,7 @@
<TextBlock
AutomationProperties.AccessibilityView="Raw"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{Binding}" />
Text="{x:Bind}" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
@@ -648,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>
@@ -806,13 +720,6 @@
<ProgressRing IsActive="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" />
</Grid>
<ScrollViewer HorizontalAlignment="Center" VerticalScrollBarVisibility="Hidden">
<StackPanel
Margin="20"
VerticalAlignment="Bottom"
dev:Growl.GrowlParent="True" />
</ScrollViewer>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PlaybackOrderState">
<VisualState x:Name="RepeatAll">

View File

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

View File

@@ -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

@@ -94,12 +94,5 @@
</controls:SwitchPresenter>
</NavigationView>
<ScrollViewer HorizontalAlignment="Center" VerticalScrollBarVisibility="Hidden">
<StackPanel
Margin="20"
VerticalAlignment="Bottom"
dev:Growl.GrowlParent="True" />
</ScrollViewer>
</Grid>
</Page>

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

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

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)
---