mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
Compare commits
37 Commits
v1.1.186.0
...
v1.1.203.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa3e79d3ff | ||
|
|
9979474ce1 | ||
|
|
2e7cd93cfe | ||
|
|
bdc31c3e0d | ||
|
|
631d079aa2 | ||
|
|
f76ef87167 | ||
|
|
76aa5ee8d0 | ||
|
|
d7f4978a66 | ||
|
|
0905c46e45 | ||
|
|
d0991c5ddb | ||
|
|
619a3ba196 | ||
|
|
13526bb85c | ||
|
|
61f4f608db | ||
|
|
f690da8501 | ||
|
|
145c13a0e6 | ||
|
|
cea4fbb54d | ||
|
|
1d489c68e9 | ||
|
|
90e7fa42d0 | ||
|
|
29a6879e45 | ||
|
|
58499a2d09 | ||
|
|
580255699b | ||
|
|
9cac7818f1 | ||
|
|
118668a457 | ||
|
|
37621dbf2a | ||
|
|
aa7d56f1cb | ||
|
|
8dbe76e790 | ||
|
|
de6410492e | ||
|
|
26df7c7f67 | ||
|
|
3c411374bd | ||
|
|
99f0b9443b | ||
|
|
a3bc148816 | ||
|
|
cea757702b | ||
|
|
8938a5c798 | ||
|
|
46f4589b64 | ||
|
|
adb02658f4 | ||
|
|
3d7e6061e9 | ||
|
|
a51220c7b9 |
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.1.186.0" />
|
||||
Version="1.1.203.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
<converter:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
|
||||
<converter:DisplayLanguageCodeToIndexConverter x:Key="DisplayLanguageCodeToIndexConverter" />
|
||||
<converter:PathToParentFolderConverter x:Key="PathToParentFolderConverter" />
|
||||
<converter:TrackToLyricsConverter x:Key="TrackToLyricsConverter" />
|
||||
<converter:IntToBoolConverter x:Key="IntToBoolConverter" />
|
||||
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
|
||||
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
|
||||
@@ -75,6 +74,7 @@
|
||||
<converter:TextAlignmentTypeToHorizontalAlignmentConverter x:Key="TextAlignmentTypeToHorizontalAlignmentConverter" />
|
||||
<converter:LyricsLayoutOrientationToOrientationConverter x:Key="LyricsLayoutOrientationToOrientationConverter" />
|
||||
<converter:LyricsLayoutOrientationNegationToOrientationConverter x:Key="LyricsLayoutOrientationNegationToOrientationConverter" />
|
||||
<converter:FileSourceTypeToIconConverter x:Key="FileSourceTypeToIconConverter" />
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
|
||||
@@ -10,7 +10,8 @@ using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
@@ -114,7 +115,8 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
|
||||
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
.AddSingleton<ITranslateService, TranslateService>()
|
||||
.AddSingleton<ITranslationService, TranslationService>()
|
||||
.AddSingleton<ITransliterationService, TransliterationService>()
|
||||
.AddSingleton<ILastFMService, LastFMService>()
|
||||
.AddSingleton<IResourceService, ResourceService>()
|
||||
.AddSingleton<IDiscordService, DiscordService>()
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<None Remove="Controls\NowPlayingBar.xaml" />
|
||||
<None Remove="Controls\PlaybackSettingsControl.xaml" />
|
||||
<None Remove="Controls\PropertyRow.xaml" />
|
||||
<None Remove="Controls\RemoteServerConfigControl.xaml" />
|
||||
<None Remove="Controls\ShortcutTextBox.xaml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Controls\WindowSettingsControl.xaml" />
|
||||
@@ -58,21 +59,21 @@
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" Version="0.1.251021-build.2365" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.251219" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.251219" />
|
||||
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
|
||||
<PackageReference Include="csharp-kana" Version="1.0.2" />
|
||||
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="9.7.1" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="9.8.1" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.6" />
|
||||
<PackageReference Include="F23.StringSimilarity" Version="7.0.1" />
|
||||
<PackageReference Include="FlaUI.UIA3" Version="5.0.0" />
|
||||
<PackageReference Include="FluentFTP" Version="53.0.2" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.4.1" />
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
|
||||
@@ -83,11 +84,10 @@
|
||||
<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" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SMBLibrary" Version="1.5.5.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
@@ -98,6 +98,7 @@
|
||||
<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="WebDav.Client" Version="2.9.0" />
|
||||
<PackageReference Include="WinUIEx" Version="2.9.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.9.0" />
|
||||
</ItemGroup>
|
||||
@@ -342,6 +343,11 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="TemplateSelector\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\RemoteServerConfigControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\NowPlayingBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoPlay, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageStopTrackOnGalleryWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.StopOnWindowClosed, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageExitOnGalleryWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.ExitOnWindowClosed, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
@@ -91,6 +92,47 @@
|
||||
Text="{x:Bind LyricsWindowStatus.Name, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
|
||||
<Grid Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}" Opacity="0">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<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.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="OpenButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="SystemTrayLyrics" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="CloseButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsEnabled="{x:Bind LyricsWindowStatus.IsOpened, Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="SettingsPageCloseStatus" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Linq;
|
||||
using static Vanara.PInvoke.User32.RAWINPUT;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -9,6 +15,8 @@ namespace BetterLyrics.WinUI3.Controls;
|
||||
|
||||
public sealed partial class DemoWindowGrid : UserControl
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public DemoWindowGrid()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -22,4 +30,32 @@ public sealed partial class DemoWindowGrid : UserControl
|
||||
get => (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty);
|
||||
set => SetValue(LyricsWindowStatusProperty, value);
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var data = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
|
||||
var window = WindowHook.GetWindows<NowPlayingWindow>().FirstOrDefault(x => x.LyricsWindowStatus == data);
|
||||
window?.CloseWindow();
|
||||
}
|
||||
|
||||
private void OpenButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var status = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
|
||||
// <20>ģʽ
|
||||
if (_settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
|
||||
}
|
||||
// <20><><EFBFBD><EFBFBD>ģʽ
|
||||
else
|
||||
{
|
||||
var openedWindows = WindowHook.GetWindows<NowPlayingWindow>();
|
||||
foreach (var item in openedWindows.Where(x => x.LyricsWindowStatus != status))
|
||||
{
|
||||
item.CloseWindow();
|
||||
}
|
||||
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,51 @@
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsExpander
|
||||
x:Uid="SettingsPageAlbumArtLayer"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=TwoWay}" />
|
||||
<dev:SettingsExpander.Items>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<uc:ExtendedSlider
|
||||
Default="100"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Unit="%"
|
||||
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<uc:ExtendedSlider
|
||||
Default="50"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Unit="%"
|
||||
Value="{x:Bind LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageBlurAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<uc:ExtendedSlider
|
||||
Default="100"
|
||||
Maximum="200"
|
||||
Minimum="0"
|
||||
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!--<dev:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<uc:ExtendedSlider
|
||||
Default="0"
|
||||
Maximum="10"
|
||||
Minimum="0"
|
||||
Value="{x:Bind LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>-->
|
||||
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsExpander
|
||||
x:Uid="SettingsPageFluidLayer"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
|
||||
@@ -13,15 +13,21 @@ using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Lyricify.Lyrics.Providers.Web.Netease;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
using static Vanara.PInvoke.Ole32;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
@@ -34,7 +40,8 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
|
||||
IRecipient<PropertyChangedMessage<string>>
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<IRandomAccessStream?>>
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
@@ -42,6 +49,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private readonly LyricsRenderer _lyricsRenderer = new();
|
||||
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
||||
private readonly CoverBackgroundRenderer _coverRenderer = new();
|
||||
private readonly PureColorBackgroundRenderer _pureColorRenderer = new();
|
||||
private readonly SnowRenderer _snowRenderer = new();
|
||||
private readonly FogRenderer _fogRenderer = new();
|
||||
@@ -368,8 +376,8 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
lyricsBg.IsPureColorOverlayEnabled
|
||||
);
|
||||
|
||||
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity / 100.0;
|
||||
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
|
||||
_coverRenderer.Draw(sender, args.DrawingSession);
|
||||
|
||||
_fluidRenderer.Draw(sender, args.DrawingSession);
|
||||
|
||||
_snowRenderer.Draw(sender, args.DrawingSession);
|
||||
@@ -540,25 +548,31 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
_isMouseScrollingChanged = false;
|
||||
|
||||
_lyricsRenderer.CalculateLyrics3DMatrix(
|
||||
lyricsStyle: lyricsStyle,
|
||||
lyricsEffect: lyricsEffect,
|
||||
lyricsX: _renderLyricsStartX,
|
||||
lyricsY: _renderLyricsStartY,
|
||||
lyricsWidth: _renderLyricsWidth,
|
||||
canvasHeight: sender.Size.Height
|
||||
lyricsHeight: _renderLyricsHeight
|
||||
);
|
||||
|
||||
_isLayoutChanged = false;
|
||||
|
||||
if (_fluidRenderer.IsEnabled)
|
||||
{
|
||||
_fluidRenderer.UpdateColors(
|
||||
_accentColor1Transition.Value,
|
||||
_accentColor2Transition.Value,
|
||||
_accentColor3Transition.Value,
|
||||
_accentColor4Transition.Value
|
||||
);
|
||||
_fluidRenderer.Update(elapsedTime);
|
||||
}
|
||||
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
|
||||
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity / 100.0;
|
||||
_fluidRenderer.UpdateColors(
|
||||
_accentColor1Transition.Value,
|
||||
_accentColor2Transition.Value,
|
||||
_accentColor3Transition.Value,
|
||||
_accentColor4Transition.Value
|
||||
);
|
||||
_fluidRenderer.Update(elapsedTime);
|
||||
|
||||
_coverRenderer.IsEnabled = lyricsBg.IsCoverOverlayEnabled;
|
||||
_coverRenderer.Opacity = lyricsBg.CoverOverlayOpacity;
|
||||
_coverRenderer.BlurAmount = lyricsBg.CoverOverlayBlurAmount;
|
||||
_coverRenderer.Speed = lyricsBg.CoverOverlaySpeed;
|
||||
_coverRenderer.Update(elapsedTime);
|
||||
|
||||
_snowRenderer.IsEnabled = lyricsBg.IsSnowFlakeOverlayEnabled;
|
||||
_snowRenderer.Amount = lyricsBg.SnowFlakeOverlayAmount / 100f;
|
||||
@@ -586,6 +600,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
private void Canvas_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_fluidRenderer.Dispose();
|
||||
_coverRenderer.Dispose();
|
||||
_snowRenderer.Dispose();
|
||||
_fogRenderer.Dispose();
|
||||
_spectrumRenderer.Dispose();
|
||||
@@ -600,7 +615,13 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private async void Canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
|
||||
{
|
||||
args.TrackAsyncAction(_fluidRenderer.LoadResourcesAsync().AsAsyncAction());
|
||||
var tasks = new Task[]
|
||||
{
|
||||
_fluidRenderer.LoadResourcesAsync(),
|
||||
ReloadCoverBackgroundResourcesAsync()
|
||||
};
|
||||
args.TrackAsyncAction(Task.WhenAll(tasks).AsAsyncAction());
|
||||
|
||||
_snowRenderer.LoadResources();
|
||||
_fogRenderer.LoadResources();
|
||||
|
||||
@@ -682,6 +703,16 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task ReloadCoverBackgroundResourcesAsync()
|
||||
{
|
||||
if (_mediaSessionsService.AlbumArtBitmapStream is IRandomAccessStream stream)
|
||||
{
|
||||
stream.Seek(0);
|
||||
CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(Canvas, stream);
|
||||
_coverRenderer.SetCoverBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
@@ -827,6 +858,14 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsFadeOutEffectEnabled))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsOutOfSightEffectEnabled))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
|
||||
{
|
||||
@@ -874,5 +913,15 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<IRandomAccessStream?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapStream))
|
||||
{
|
||||
_ = ReloadCoverBackgroundResourcesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,16 @@
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsBlurEffectEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 淡出效果 -->
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsFadeOutEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFadeOutEffectEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 远离视野 -->
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsOutOfSightEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsOutOfSightEffectEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 辉光效果 -->
|
||||
<dev:SettingsExpander x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
|
||||
|
||||
@@ -278,18 +278,43 @@
|
||||
</Pivot.HeaderTemplate>
|
||||
<Pivot.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsData">
|
||||
<ListView ItemsSource="{x:Bind LyricsLines, Mode=OneWay}" SelectionChanged="ListView_SelectionChanged">
|
||||
<ListView
|
||||
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
|
||||
ItemsSource="{x:Bind LyricsLines, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsLine">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<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="{x:Bind EndMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
|
||||
<TextBlock Margin="6,0" Text="{x:Bind OriginalText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<Grid Margin="0,6" ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{x:Bind StartMs, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
|
||||
<Button
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Click="PlayLyricsLineButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Opacity="0"
|
||||
Style="{StaticResource AccentButtonStyle}">
|
||||
<Button.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Button.OpacityTransition>
|
||||
<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>
|
||||
</Button>
|
||||
</Grid>
|
||||
<local:PropertyRow Grid.Column="1" Value="{x:Bind OriginalText, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
DataContext = Ioc.Default.GetRequiredService<LyricsSearchControlViewModel>();
|
||||
}
|
||||
|
||||
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
private void PlayLyricsLineButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.SelectedLyricsLine = e.OriginalSource as LyricsLine;
|
||||
var lyricsLine = (LyricsLine)((Button)sender).DataContext;
|
||||
ViewModel.PlayLyricsLine(lyricsLine);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsCenterTopOffset" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<local:ExtendedSlider
|
||||
Default="50"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Maximum="99"
|
||||
Minimum="1"
|
||||
Unit="%"
|
||||
Value="{x:Bind LyricsStyleSettings.PlayingLineTopOffset, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Loaded="UserControl_Loaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
@@ -99,10 +100,7 @@
|
||||
BorderThickness="4"
|
||||
CornerRadius="4"
|
||||
Visibility="{Binding IsOpened, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<uc:DemoWindowGrid
|
||||
Margin="4"
|
||||
LyricsWindowStatus="{Binding}"
|
||||
Tapped="DemoWindowGrid_Tapped" />
|
||||
<uc:DemoWindowGrid Margin="4" LyricsWindowStatus="{Binding}" />
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ToggleButton
|
||||
@@ -116,7 +114,6 @@
|
||||
<Grid ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
@@ -124,13 +121,6 @@
|
||||
Click="ConfigButton_Click">
|
||||
<TextBlock x:Uid="LyricsWindowSettingsControlLyricsWindowConfig" />
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="CloseStatusButton_Click"
|
||||
IsEnabled="{Binding IsOpened, Mode=OneWay}">
|
||||
<TextBlock x:Uid="SettingsPageCloseStatus" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -10,6 +10,7 @@ using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -26,14 +27,14 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public LyricsWindowStatus LyricsWindowStatus
|
||||
public LyricsWindowStatus? LyricsWindowStatus
|
||||
{
|
||||
get { return (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty); }
|
||||
get { return (LyricsWindowStatus?)GetValue(LyricsWindowStatusProperty); }
|
||||
set { SetValue(LyricsWindowStatusProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LyricsWindowStatusProperty =
|
||||
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(LyricsWindowSettingsControl), new PropertyMetadata(default));
|
||||
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(LyricsWindowSettingsControl), new PropertyMetadata(null));
|
||||
|
||||
public LyricsWindowSettingsControl()
|
||||
{
|
||||
@@ -166,52 +167,14 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
ViewModel.OpenConfigPanel();
|
||||
}
|
||||
|
||||
private void DemoWindowGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var status = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
|
||||
// <20>ģʽ
|
||||
if (_settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
|
||||
}
|
||||
// <20><><EFBFBD><EFBFBD>ģʽ
|
||||
else
|
||||
{
|
||||
var openedWindows = WindowHook.GetWindows<NowPlayingWindow>();
|
||||
foreach (var item in openedWindows.Where(x => x.LyricsWindowStatus != status))
|
||||
{
|
||||
item.CloseWindow();
|
||||
}
|
||||
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigSelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
|
||||
{
|
||||
if (sender is SelectorBar bar)
|
||||
{
|
||||
if (bar.SelectedItem is SelectorBarItem item)
|
||||
{
|
||||
ViewModel?.SelectorBarSelectedItemTag = item.Tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseStatusButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
if (element.DataContext is LyricsWindowStatus data)
|
||||
{
|
||||
var window = WindowHook.GetWindows<NowPlayingWindow>().FirstOrDefault(x => x.LyricsWindowStatus == data);
|
||||
window?.CloseWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigSegmented_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
ViewModel.SelectorBarSelectedItemTag = (string)((SegmentedItem)((Segmented)sender).SelectedItem).Tag;
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.CloseConfigPanelCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,22 +29,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private async void Grid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var status = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
|
||||
// <20>ģʽ
|
||||
if (_settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
|
||||
}
|
||||
// <20><><EFBFBD><EFBFBD>ģʽ
|
||||
else
|
||||
{
|
||||
var openedWindows = WindowHook.GetWindows<NowPlayingWindow>();
|
||||
foreach (var item in openedWindows.Where(x => x.LyricsWindowStatus != status))
|
||||
{
|
||||
item.CloseWindow();
|
||||
}
|
||||
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
|
||||
}
|
||||
await HideAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.MediaSettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
@@ -9,6 +8,7 @@
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -49,25 +49,32 @@
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<dev:SettingsExpander>
|
||||
<DataTemplate x:DataType="models:MediaFolder">
|
||||
<dev:SettingsExpander Description="{x:Bind ConnectionSummary, Mode=OneWay}" HeaderIcon="{Binding SourceType, Converter={StaticResource FileSourceTypeToIconConverter}}">
|
||||
|
||||
<dev:SettingsExpander.Header>
|
||||
<HyperlinkButton
|
||||
Padding="0"
|
||||
Click="LocalFolderHyperlinkButton_Click"
|
||||
Content="{Binding Path, Mode=OneWay}"
|
||||
Tag="{Binding Path, Mode=OneWay}" />
|
||||
Content="{x:Bind Path, Mode=OneWay}"
|
||||
Tag="{x:Bind Path, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind ConnectionSummary}" />
|
||||
</dev:SettingsExpander.Header>
|
||||
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
|
||||
|
||||
<ToggleSwitch IsOn="{x:Bind IsEnabled, Mode=TwoWay}" />
|
||||
|
||||
<dev:SettingsExpander.Items>
|
||||
<dev:SettingsCard>
|
||||
<dev:SettingsCard.Header>
|
||||
<HyperlinkButton
|
||||
x:Uid="SettingsPageRemovePath"
|
||||
Padding="0"
|
||||
Click="SettingsPageRemovePathButton_Click"
|
||||
Tag="{Binding}" />
|
||||
</dev:SettingsCard.Header>
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageMusicLibRealTimeWatch">
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageMusicLibRealTimeWatch" IsEnabled="{Binding IsLocal, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{Binding IsRealTimeWatchEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
</dev:SettingsExpander.Items>
|
||||
@@ -76,15 +83,52 @@
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageAddFolder" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<Button
|
||||
x:Uid="SettingsPageAddFolderButton"
|
||||
Command="{x:Bind ViewModel.SelectAndAddFolderCommand}"
|
||||
CommandParameter="{Binding ElementName=RootGrid}" />
|
||||
<dev:SettingsCard Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<DropDownButton x:Uid="SettingsPageAddFolderButton">
|
||||
<DropDownButton.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SettingsPageLocalFolder"
|
||||
Command="{x:Bind ViewModel.SelectAndAddFolderCommand}"
|
||||
CommandParameter="{Binding ElementName=RootGrid}"
|
||||
Icon="Folder" />
|
||||
|
||||
<MenuFlyoutSeparator />
|
||||
|
||||
<MenuFlyoutItem
|
||||
Command="{x:Bind ViewModel.AddRemoteSourceCommand}"
|
||||
CommandParameter="SMB"
|
||||
Text="SMB">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
|
||||
<MenuFlyoutItem
|
||||
Command="{x:Bind ViewModel.AddRemoteSourceCommand}"
|
||||
CommandParameter="FTP"
|
||||
Text="FTP">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
|
||||
<MenuFlyoutItem
|
||||
Command="{x:Bind ViewModel.AddRemoteSourceCommand}"
|
||||
CommandParameter="WebDAV"
|
||||
Text="WebDAV">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
|
||||
</MenuFlyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
</dev:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -22,7 +22,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private void SettingsPageRemovePathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.RemoveFolderAsync((LocalMediaFolder)(sender as HyperlinkButton)!.Tag);
|
||||
ViewModel.RemoveFolderAsync((MediaFolder)(sender as HyperlinkButton)!.Tag);
|
||||
}
|
||||
|
||||
private async void LocalFolderHyperlinkButton_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<Grid
|
||||
x:Name="BottomCommandGrid"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
|
||||
Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}"
|
||||
PointerEntered="BottomCommandGrid_PointerEntered"
|
||||
PointerExited="BottomCommandGrid_PointerExited">
|
||||
@@ -342,7 +342,7 @@
|
||||
|
||||
<Slider
|
||||
x:Name="TimelineSlider"
|
||||
Margin="0,-12,0,0"
|
||||
Margin="0,-14,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
|
||||
@@ -356,7 +356,7 @@
|
||||
Padding="8,4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
|
||||
CornerRadius="6"
|
||||
Opacity="{x:Bind ViewModel.TimelineSliderThumbOpacity, Mode=OneWay}">
|
||||
<Grid.OpacityTransition>
|
||||
|
||||
@@ -190,7 +190,7 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
|
||||
private void TimelineSliderOverlay_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TimelineSliderThumbOpacity = 0.7f;
|
||||
ViewModel.TimelineSliderThumbOpacity = 1f;
|
||||
}
|
||||
|
||||
private void TimelineSliderOverlay_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
@@ -290,10 +290,7 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
TimelineSlider.Value = message.NewValue.TotalSeconds;
|
||||
});
|
||||
TimelineSlider.Value = message.NewValue.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +348,7 @@
|
||||
x:Uid="LyricsSearchControlDurauion"
|
||||
Unit="s"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Duration, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageLanguageCode" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsData.LanguageCode, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageLyricsProviderPrefix"
|
||||
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}"
|
||||
@@ -397,16 +398,23 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<Grid ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
x:Uid="LibreTranslateServerTextBox"
|
||||
Grid.Column="0"
|
||||
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}" />
|
||||
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Uid="SettingsPageServerTestButton"
|
||||
Grid.Column="1"
|
||||
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
|
||||
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</dev:SettingsCard>
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
@@ -424,9 +432,33 @@
|
||||
</dev:SettingsCard>
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
<dev:SettingsCard x:Uid="SettingsPageJapanese">
|
||||
<dev:SettingsExpander x:Uid="SettingsPageJapanese" IsExpanded="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsExpander.Items>
|
||||
<dev:SettingsCard x:Uid="SettingsPageCutletDockerServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=OneWay}">
|
||||
<dev:SettingsCard.Description>
|
||||
<HyperlinkButton Content="https://github.com/jayfunc/cutlet-docker" NavigateUri="https://github.com/jayfunc/cutlet-docker" />
|
||||
</dev:SettingsCard.Description>
|
||||
<Grid ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
x:Uid="CutletServerTextBox"
|
||||
Grid.Column="0"
|
||||
IsEnabled="{x:Bind ViewModel.IsCutletDockerServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.CutletDockerServer, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Uid="SettingsPageServerTestButton"
|
||||
Grid.Column="1"
|
||||
Command="{x:Bind ViewModel.CutletDockerServerTestCommand}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCutletDockerServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</dev:SettingsCard>
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<!-- 中文简体繁体偏好 -->
|
||||
<TextBlock x:Uid="SettingsPageChineseLyrics" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.RemoteServerConfigControl"
|
||||
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"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<StackPanel Width="400" Spacing="16">
|
||||
<ProgressBar
|
||||
x:Name="ProgressBar"
|
||||
IsIndeterminate="True"
|
||||
Visibility="Collapsed" />
|
||||
<InfoBar
|
||||
x:Name="ErrorInfoBar"
|
||||
IsClosable="True"
|
||||
IsOpen="False"
|
||||
Severity="Error" />
|
||||
|
||||
<Grid ColumnDefinitions="*, Auto" ColumnSpacing="12">
|
||||
<TextBox
|
||||
x:Name="HostBox"
|
||||
x:Uid="RemoteServerConfigControlServerAddress"
|
||||
Grid.Column="0"
|
||||
Header="Server Address"
|
||||
InputScope="Url"
|
||||
PlaceholderText="192.168.1.x"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<NumberBox
|
||||
x:Name="PortBox"
|
||||
x:Uid="RemoteServerConfigControlPort"
|
||||
Grid.Column="1"
|
||||
MinWidth="100"
|
||||
Header="Port"
|
||||
LargeChange="10"
|
||||
SmallChange="1"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
ToolTipService.ToolTip="80"
|
||||
Value="80" />
|
||||
</Grid>
|
||||
|
||||
<TextBox
|
||||
x:Name="PathBox"
|
||||
x:Uid="RemoteServerConfigControlPath"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<Grid ColumnDefinitions="*, *" ColumnSpacing="12">
|
||||
<TextBox
|
||||
x:Name="UserBox"
|
||||
x:Uid="RemoteServerConfigControlUsername"
|
||||
Grid.Column="0"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<PasswordBox
|
||||
x:Name="PwdBox"
|
||||
x:Uid="RemoteServerConfigControlPassword"
|
||||
Grid.Column="1"
|
||||
PasswordRevealMode="Peek" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,110 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using DevWinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class RemoteServerConfigControl : UserControl
|
||||
{
|
||||
private readonly string _protocolType;
|
||||
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
|
||||
|
||||
public RemoteServerConfigControl(string protocolType)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
_protocolType = protocolType;
|
||||
|
||||
SetupDefaults();
|
||||
}
|
||||
|
||||
private void SetupDefaults()
|
||||
{
|
||||
switch (_protocolType.ToUpper())
|
||||
{
|
||||
case "SMB":
|
||||
PortBox.Value = 445; // SMB Ĭ<>϶˿<CFB6>
|
||||
PathBox.PlaceholderText = "SharedMusic";
|
||||
break;
|
||||
case "FTP":
|
||||
PortBox.Value = 21; // FTP Ĭ<>϶˿<CFB6>
|
||||
PathBox.PlaceholderText = "/pub/music";
|
||||
break;
|
||||
case "WEBDAV":
|
||||
PortBox.Value = 80; // WebDAV Ĭ<>϶˿<CFB6>
|
||||
PathBox.PlaceholderText = "/dav/music";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public MediaFolder GetConfig()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(HostBox.Text))
|
||||
throw new ArgumentException(_resourceService.GetLocalizedString("RemoteServerConfigControlServerAddressRequired"));
|
||||
|
||||
string name = $"{_protocolType} - {HostBox.Text}";
|
||||
|
||||
Enum.TryParse(_protocolType, true, out FileSourceType sourceType);
|
||||
|
||||
var folder = new MediaFolder
|
||||
{
|
||||
Name = name,
|
||||
Path = HostBox.Text, // <20><><EFBFBD><EFBFBD> Path <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP/Host
|
||||
Port = (int)PortBox.Value,
|
||||
UserName = UserBox.Text,
|
||||
Password = PwdBox.Password, // <20><> PasswordBox <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>
|
||||
SourceType = sourceType,
|
||||
IsRealTimeWatchEnabled = false
|
||||
};
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><E2B4A6>·<EFBFBD><C2B7><EFBFBD><EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA>"Զ<><D4B6>·<EFBFBD><C2B7>"ƴ<>ӵ<EFBFBD> Path <20><EFBFBD><EFA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ֶδ<D6B6>
|
||||
// Ϊ<>˼<CBBC><F2B5A5A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> MediaFolder <20><><EFBFBD>壺
|
||||
// <20><><EFBFBD><EFBFBD> MediaFolder <20><><EFBFBD><EFBFBD><EFBFBD>ټ<EFBFBD>һ<EFBFBD><D2BB> RemotePath <20>ֶΣ<D6B6><CEA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Path <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
// *<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>*<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> MediaFolder <20><><EFBFBD>壬<EFBFBD><E5A3AC><EFBFBD>ǿ<EFBFBD><C7BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD>
|
||||
// Path <20>ֶδ洢<CEB4><E6B4A2>ʽ<EFBFBD><CABD> "192.168.1.5/Music"
|
||||
|
||||
var rawPath = PathBox.Text.Trim().TrimStart('/', '\\'); // ȥ<><C8A5><EFBFBD><EFBFBD>ͷ<EFBFBD><CDB7>б<EFBFBD><D0B1>
|
||||
if (!string.IsNullOrEmpty(rawPath))
|
||||
{
|
||||
// <20><EFBFBD>·<EFBFBD><C2B7>ƴ<EFBFBD><C6B4><EFBFBD><EFBFBD>
|
||||
if (sourceType == FileSourceType.SMB)
|
||||
{
|
||||
// SMBLibrary <20><><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD> Host <20>ֿ<EFBFBD><D6BF><EFBFBD>ShareName <20>ֿ<EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><><EFBFBD><EFBFBD> Path <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA> ShareName ƴ<>ں<EFBFBD><DABA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֶ<EFBFBD>
|
||||
// Ϊ<>˷<EFBFBD><CBB7>㣬<EFBFBD><E3A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><> ShareName ƴ<><C6B4>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Path
|
||||
// <20><><EFBFBD><EFBFBD>: 192.168.1.5/Music
|
||||
folder.Path = $"{HostBox.Text}/{rawPath}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// FTP/WebDAV: 192.168.1.5/pub/music
|
||||
folder.Path = $"{HostBox.Text}/{rawPath}";
|
||||
}
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
public void ShowError(string message)
|
||||
{
|
||||
ErrorInfoBar.Message = message;
|
||||
ErrorInfoBar.IsOpen = true;
|
||||
}
|
||||
|
||||
public void SetProgressBarVisibility(Visibility visibility)
|
||||
{
|
||||
ProgressBar.Visibility = visibility;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
x:Name="TrayIcon"
|
||||
x:FieldModifier="public"
|
||||
ContextMenuMode="SecondWindow"
|
||||
DoubleClickCommand="{x:Bind ViewModel.OpenLyricsWindowSwitchCommand}"
|
||||
IconSource="ms-appx:///Assets/Logo.ico"
|
||||
LeftClickCommand="{x:Bind ViewModel.OpenLyricsWindowSwitchCommand}"
|
||||
NoLeftClickDelay="True"
|
||||
|
||||
@@ -102,6 +102,10 @@
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageAlwaysHideUnlockButton" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysHideUnlockButton, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsExpander
|
||||
x:Uid="SettingsPageAOT"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class FileSourceTypeToIconConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is FileSourceType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
FileSourceType.Local => new FontIcon { Glyph = "\uE8B7" }, // Folder
|
||||
FileSourceType.SMB => new FontIcon { Glyph = "\uE839" }, // Network
|
||||
FileSourceType.FTP => new FontIcon { Glyph = "\uE838" }, // Globe
|
||||
FileSourceType.WebDav => new FontIcon { Glyph = "\uE753" }, // Cloud
|
||||
_ => new FontIcon { Glyph = "\uE8B7" }
|
||||
};
|
||||
}
|
||||
return new FontIcon { Glyph = "\uE8B7" };
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,11 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
if (value is string langCode)
|
||||
{
|
||||
if (PhoneticHelper.IsPhoneticCode(langCode))
|
||||
if (langCode == "N/A")
|
||||
{
|
||||
return langCode;
|
||||
}
|
||||
else if (PhoneticHelper.IsPhoneticCode(langCode))
|
||||
{
|
||||
return PhoneticHelper.GetDisplayName(langCode);
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class TrackToLyricsConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Track track)
|
||||
{
|
||||
return track.GetRawLyrics();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
TransliterationSearchProvider.LocalEslrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderEslrcFile"),
|
||||
TransliterationSearchProvider.LocalTtmlFile => _resourceService.GetLocalizedString("LyricsSearchProviderTtmlFile"),
|
||||
TransliterationSearchProvider.BetterLyrics => "BetterLyrics",
|
||||
TransliterationSearchProvider.CutletDocker => "cutlet-docker",
|
||||
_ => "N/A",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum FileSourceType
|
||||
{
|
||||
Local,
|
||||
SMB,
|
||||
FTP,
|
||||
WebDav
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
BetterLyrics
|
||||
BetterLyrics,
|
||||
CutletDocker
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using ATL;
|
||||
using System.IO;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class TrackExtensions
|
||||
{
|
||||
extension(Track track)
|
||||
{
|
||||
public string GetParentFolderName() => Directory.GetParent(track.Path)?.Name ?? "";
|
||||
|
||||
public string GetParentFolderPath() => Directory.GetParent(track.Path)?.FullName ?? "";
|
||||
|
||||
public string GetRawLyrics()
|
||||
{
|
||||
if (track.Path is string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return TagLib.File.Create(path).Tag.Lyrics;
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public string GetFileName() => Path.GetFileName(track.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static string? DetectLanguageCode(string? text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
if (string.IsNullOrWhiteSpace(text)) return null;
|
||||
var guessList = _identifier.Identify(text);
|
||||
string? code = guessList?.FirstOrDefault()?.Item1.Iso639_2T;
|
||||
code = code switch
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Windows.Security.Credentials;
|
||||
using System;
|
||||
using Windows.Security.Credentials;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
@@ -12,23 +13,13 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <param name="value">要保存的值</param>
|
||||
public static void Save(string resource, string key, string value)
|
||||
{
|
||||
// 删除旧值(避免重复存储)
|
||||
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
|
||||
{
|
||||
// 没有旧值就忽略
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,7 +38,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
credential.RetrievePassword();
|
||||
return credential.Password;
|
||||
}
|
||||
catch
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -65,10 +56,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var credential = vault.Retrieve(resource, key);
|
||||
vault.Remove(credential);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 不存在就忽略
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,11 +32,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToRomaji(string text)
|
||||
{
|
||||
return Kana.Kana.KanaToRomaji(text, Kana.Error.Ignore).ToStr();
|
||||
}
|
||||
|
||||
public static string ToPinyin(string text, Pinyin.ManTone.Style style = Pinyin.ManTone.Style.TONE)
|
||||
{
|
||||
return Pinyin.Pinyin.Instance.HanziToPinyin(text, style).ToStr();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using DevWinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
@@ -45,6 +46,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(IDictionary<string, IList<string>> fileTypeChoices)
|
||||
{
|
||||
var window = WindowHook.GetWindow<T>();
|
||||
|
||||
return await PickSaveFileAsync(window, fileTypeChoices);
|
||||
}
|
||||
|
||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(T? window, IDictionary<string, IList<string>> fileTypeChoices)
|
||||
{
|
||||
if (window == null) return null;
|
||||
|
||||
var picker = new Windows.Storage.Pickers.FileSavePicker();
|
||||
|
||||
@@ -224,10 +224,7 @@ namespace BetterLyrics.WinUI3.Hooks
|
||||
|
||||
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
if (_activeWindows.Contains(sender))
|
||||
{
|
||||
_activeWindows.Remove(sender);
|
||||
}
|
||||
_activeWindows.Remove(sender);
|
||||
}
|
||||
|
||||
public static void SetIsWorkArea(this NowPlayingWindow window, bool enable)
|
||||
|
||||
@@ -92,27 +92,30 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
|
||||
line.ScaleTransition.SetDuration(yScrollDuration);
|
||||
line.ScaleTransition.SetDelay(yScrollDelay);
|
||||
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
|
||||
line.ScaleTransition.StartTransition(
|
||||
lyricsEffect.IsLyricsOutOfSightEffectEnabled ?
|
||||
(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)) :
|
||||
_highlightedScale);
|
||||
|
||||
line.PhoneticOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.PhoneticOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.PhoneticOpacityTransition.StartTransition(
|
||||
absLineCountDelta == 0 ? phoneticOpacity : (isMouseScrolling ? phoneticOpacity : (1 - distanceFactor) * phoneticOpacity));
|
||||
CalculateTargetOpacity(phoneticOpacity, phoneticOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.PlayedOriginalOpacityTransition.StartTransition(
|
||||
absLineCountDelta == 0 ? 1 : (isMouseScrolling ? 1.0 : (1 - distanceFactor) * originalOpacity));
|
||||
CalculateTargetOpacity(originalOpacity, 1.0, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.UnplayedOriginalOpacityTransition.StartTransition(
|
||||
absLineCountDelta == 0 ? originalOpacity : (isMouseScrolling ? originalOpacity : (1 - distanceFactor) * originalOpacity));
|
||||
CalculateTargetOpacity(originalOpacity, originalOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
line.TranslatedOpacityTransition.SetDuration(yScrollDuration);
|
||||
line.TranslatedOpacityTransition.SetDelay(yScrollDelay);
|
||||
line.TranslatedOpacityTransition.StartTransition(
|
||||
absLineCountDelta == 0 ? translatedOpacity : (isMouseScrolling ? translatedOpacity : (1 - distanceFactor) * translatedOpacity));
|
||||
CalculateTargetOpacity(translatedOpacity, translatedOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||
|
||||
line.ColorTransition.SetDuration(yScrollDuration);
|
||||
line.ColorTransition.SetDelay(yScrollDelay);
|
||||
@@ -121,8 +124,10 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.AngleTransition.SetDuration(yScrollDuration);
|
||||
line.AngleTransition.SetDelay(yScrollDelay);
|
||||
line.AngleTransition.StartTransition(lyricsEffect.IsFanLyricsEnabled ?
|
||||
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > playingLineIndex ? 1 : -1) : 0);
|
||||
line.AngleTransition.StartTransition(
|
||||
(lyricsEffect.IsFanLyricsEnabled && !isMouseScrolling) ?
|
||||
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > playingLineIndex ? 1 : -1) :
|
||||
0);
|
||||
|
||||
line.YOffsetTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.YOffsetTransition.SetDuration(yScrollDuration);
|
||||
@@ -143,5 +148,33 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
line.ColorTransition.Update(elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateTargetOpacity(double baseOpacity, double baseOpacityWhenZeroDistanceFactor, double distanceFactor, bool isMouseScrolling, LyricsEffectSettings lyricsEffect)
|
||||
{
|
||||
double targetOpacity;
|
||||
if (distanceFactor == 0)
|
||||
{
|
||||
targetOpacity = baseOpacityWhenZeroDistanceFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isMouseScrolling)
|
||||
{
|
||||
targetOpacity = baseOpacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lyricsEffect.IsLyricsFadeOutEffectEnabled)
|
||||
{
|
||||
targetOpacity = (1 - distanceFactor) * baseOpacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetOpacity = baseOpacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return targetOpacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,17 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
if (value >= mousePosition.Y) { result = mid; right = mid - 1; }
|
||||
else { left = mid + 1; }
|
||||
}
|
||||
|
||||
if (result != -1)
|
||||
{
|
||||
var line = lines[result];
|
||||
double lineTopY = offset + line.TopLeftPosition.Y;
|
||||
if (mousePosition.Y < lineTopY)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class CutletDockerRequest
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class CutletDockerResponse
|
||||
{
|
||||
[JsonPropertyName("romaji")]
|
||||
public string RomajiText { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using BetterLyrics.WinUI3.Models.FileSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class ExtendedTrack : ATL.Track
|
||||
{
|
||||
public new string Path { get; private set; } = "";
|
||||
public string RawLyrics { get; set; } = "";
|
||||
public string ParentFolderName => Directory.GetParent(Path)?.Name ?? "";
|
||||
public string ParentFolderPath => Directory.GetParent(Path)?.FullName ?? "";
|
||||
public string FileName => System.IO.Path.GetFileName(Path);
|
||||
|
||||
public ExtendedTrack() : base() { }
|
||||
|
||||
public ExtendedTrack(string path) : base(path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public ExtendedTrack(string path, Stream stream) : base(stream, System.IO.Path.GetExtension(path))
|
||||
{
|
||||
Path = path;
|
||||
SetRawLyrics(new StreamFileAbstraction(path, stream));
|
||||
}
|
||||
|
||||
private void SetRawLyrics(StreamFileAbstraction streamFileAbstraction)
|
||||
{
|
||||
try
|
||||
{
|
||||
RawLyrics = TagLib.File.Create(streamFileAbstraction).Tag.Lyrics;
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using FluentFTP;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public partial class FTPFileSystem : IUnifiedFileSystem
|
||||
{
|
||||
private readonly AsyncFtpClient _client;
|
||||
private readonly string _rootPath; // 服务器上的根路径 (例如 /pub/music)
|
||||
|
||||
public FTPFileSystem(string host, string user, string pass, int port, string remotePath)
|
||||
{
|
||||
// 如果 path 是 "192.168.1.5/Music",我们需要把 /Music 拆出来
|
||||
// 但为了简单,假设 host 仅仅是 IP,remotePath 才是路径
|
||||
_rootPath = remotePath ?? "/";
|
||||
|
||||
var config = new FtpConfig { ConnectTimeout = 5000 };
|
||||
_client = new AsyncFtpClient(host, user ?? "anonymous", pass ?? "", port > 0 ? port : 21, config);
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
await _client.AutoConnect();
|
||||
return _client.IsConnected;
|
||||
}
|
||||
|
||||
public async Task<List<UnifiedFileItem>> GetFilesAsync(string relativePath)
|
||||
{
|
||||
string targetPath = Path.Combine(_rootPath, relativePath).Replace("\\", "/");
|
||||
|
||||
var items = await _client.GetListing(targetPath);
|
||||
return items.Select(i => new UnifiedFileItem
|
||||
{
|
||||
Name = i.Name,
|
||||
FullPath = i.FullName,
|
||||
IsFolder = i.Type == FtpObjectType.Directory,
|
||||
Size = i.Size,
|
||||
LastModified = i.Modified
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<Stream> OpenReadAsync(string fullPath)
|
||||
{
|
||||
return await _client.OpenRead(fullPath);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync() => await _client.Disconnect();
|
||||
public void Dispose() => _client?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public interface IUnifiedFileSystem : IDisposable
|
||||
{
|
||||
Task<bool> ConnectAsync();
|
||||
Task<List<UnifiedFileItem>> GetFilesAsync(string relativePath);
|
||||
Task<Stream> OpenReadAsync(string fullPath);
|
||||
Task DisconnectAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public partial class LocalFileSystem : IUnifiedFileSystem
|
||||
{
|
||||
private readonly string _rootPath;
|
||||
|
||||
public LocalFileSystem(string rootPath)
|
||||
{
|
||||
_rootPath = rootPath;
|
||||
}
|
||||
|
||||
public Task<bool> ConnectAsync()
|
||||
{
|
||||
return Task.FromResult(Directory.Exists(_rootPath));
|
||||
}
|
||||
|
||||
public async Task<List<UnifiedFileItem>> GetFilesAsync(string relativePath)
|
||||
{
|
||||
var result = new List<UnifiedFileItem>();
|
||||
|
||||
var targetPath = string.IsNullOrWhiteSpace(relativePath)
|
||||
? _rootPath
|
||||
: Path.Combine(_rootPath, relativePath);
|
||||
|
||||
if (!Directory.Exists(targetPath)) return result;
|
||||
|
||||
var dirInfo = new DirectoryInfo(targetPath);
|
||||
|
||||
foreach (var item in dirInfo.GetFileSystemInfos())
|
||||
{
|
||||
bool isDir = (item.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
|
||||
result.Add(new UnifiedFileItem
|
||||
{
|
||||
Name = item.Name,
|
||||
FullPath = item.FullName,
|
||||
IsFolder = isDir,
|
||||
Size = isDir ? 0 : ((FileInfo)item).Length,
|
||||
LastModified = item.LastWriteTime
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Stream> OpenReadAsync(string fullPath)
|
||||
{
|
||||
return new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync() => await Task.CompletedTask;
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using SMBLibrary;
|
||||
using SMBLibrary.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public partial class SMBFileSystem : IUnifiedFileSystem
|
||||
{
|
||||
private SMB2Client _client;
|
||||
private ISMBFileStore _fileStore;
|
||||
|
||||
private readonly string _ip;
|
||||
private readonly string _shareName;
|
||||
private readonly string _pathInsideShare; // 共享里的子路径
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
|
||||
// fullPathInput 例如: "192.168.1.5/Music/Pop"
|
||||
public SMBFileSystem(string fullPathInput, string user, string pass)
|
||||
{
|
||||
_username = user;
|
||||
_password = pass;
|
||||
|
||||
// 解析路径:分离 IP 和 共享名
|
||||
var parts = fullPathInput.Replace("\\", "/").Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length >= 1) _ip = parts[0];
|
||||
if (parts.Length >= 2) _shareName = parts[1];
|
||||
|
||||
// 剩下的部分重新拼起来作为子路径
|
||||
if (parts.Length > 2)
|
||||
_pathInsideShare = string.Join("\\", parts.Skip(2));
|
||||
else
|
||||
_pathInsideShare = "";
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
_client = new SMB2Client();
|
||||
bool connected = _client.Connect(_ip, SMBTransportType.DirectTCPTransport);
|
||||
if (!connected) return false;
|
||||
|
||||
var status = _client.Login(string.Empty, _username, _password);
|
||||
if (status != NTStatus.STATUS_SUCCESS) return false;
|
||||
|
||||
// 连接具体的共享文件夹
|
||||
if (string.IsNullOrEmpty(_shareName)) return true; // 只连了服务器,没连共享
|
||||
|
||||
_fileStore = _client.TreeConnect(_shareName, out status);
|
||||
return status == NTStatus.STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
public async Task<List<UnifiedFileItem>> GetFilesAsync(string relativePath)
|
||||
{
|
||||
var result = new List<UnifiedFileItem>();
|
||||
if (_fileStore == null) return result;
|
||||
|
||||
// 拼接完整路径: Root里面的子路径 + 传入的相对路径
|
||||
string queryPath = Path.Combine(_pathInsideShare, relativePath).Replace("/", "\\").TrimStart('\\');
|
||||
|
||||
// 打开目录
|
||||
var statusRet = _fileStore.CreateFile(out object handle, out FileStatus status, queryPath,
|
||||
AccessMask.GENERIC_READ, SMBLibrary.FileAttributes.Directory, ShareAccess.Read,
|
||||
CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
|
||||
|
||||
if (statusRet != NTStatus.STATUS_SUCCESS) return result;
|
||||
|
||||
List<QueryDirectoryFileInformation> fileInfo;
|
||||
do
|
||||
{
|
||||
statusRet = _fileStore.QueryDirectory(out fileInfo, handle, "*", FileInformationClass.FileDirectoryInformation);
|
||||
|
||||
List<FileDirectoryInformation> list = fileInfo.Select(x => (FileDirectoryInformation)x).ToList();
|
||||
foreach (var item in list)
|
||||
{
|
||||
// 排除当前目录和父目录
|
||||
if (item.FileName == "." || item.FileName == "..") continue;
|
||||
|
||||
result.Add(new UnifiedFileItem
|
||||
{
|
||||
Name = item.FileName,
|
||||
FullPath = Path.Combine(queryPath, item.FileName),
|
||||
IsFolder = (item.FileAttributes & SMBLibrary.FileAttributes.Directory) == SMBLibrary.FileAttributes.Directory,
|
||||
Size = item.AllocationSize,
|
||||
LastModified = item.LastWriteTime
|
||||
});
|
||||
}
|
||||
|
||||
if (statusRet == NTStatus.STATUS_NO_MORE_FILES)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (statusRet != NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
// Log
|
||||
break;
|
||||
}
|
||||
} while (statusRet == NTStatus.STATUS_SUCCESS);
|
||||
|
||||
_fileStore.CloseFile(handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Stream> OpenReadAsync(string fullPath)
|
||||
{
|
||||
var ret = _fileStore.CreateFile(out object handle, out FileStatus status, fullPath,
|
||||
AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, 0, null);
|
||||
|
||||
if (ret != NTStatus.STATUS_SUCCESS) throw new IOException($"SMB Open Error: {ret}");
|
||||
|
||||
return new SMBReadOnlyStream(_fileStore, handle);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
_client?.Disconnect();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using SMBLibrary;
|
||||
using SMBLibrary.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public partial class SMBReadOnlyStream : Stream
|
||||
{
|
||||
private readonly ISMBFileStore _store;
|
||||
private readonly object _handle;
|
||||
private long _position;
|
||||
private long _length; // 新增:缓存文件长度
|
||||
|
||||
public SMBReadOnlyStream(ISMBFileStore store, object handle)
|
||||
{
|
||||
_store = store;
|
||||
_handle = handle;
|
||||
_position = 0;
|
||||
|
||||
var status = _store.GetFileInformation(out FileInformation result, handle, FileInformationClass.FileStandardInformation);
|
||||
if (status == NTStatus.STATUS_SUCCESS && result is FileStandardInformation info)
|
||||
{
|
||||
_length = info.EndOfFile;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果获取失败,这是一个严重问题,意味着无法 Seek 到末尾
|
||||
// 暂时设为 0,但后续读取可能会出问题
|
||||
_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => _length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// 保护:如果位置已经超过文件末尾,直接返回 0 (EOF)
|
||||
if (_position >= _length) return 0;
|
||||
|
||||
// 保护:防止读取越界 (请求读取量不能超过剩余量)
|
||||
long remaining = _length - _position;
|
||||
int bytesToRequest = (int)Math.Min(count, remaining);
|
||||
|
||||
// 为了安全,保留对 remaining 的检查是必须的
|
||||
if (bytesToRequest <= 0) return 0;
|
||||
|
||||
var status = _store.ReadFile(out byte[] data, _handle, _position, bytesToRequest);
|
||||
|
||||
if (status == NTStatus.STATUS_END_OF_FILE) return 0;
|
||||
|
||||
if (status != NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
throw new IOException($"SMB Read failed. Status: {status} (Pos: {_position}, Req: {bytesToRequest})");
|
||||
}
|
||||
|
||||
if (data == null || data.Length == 0) return 0;
|
||||
|
||||
Array.Copy(data, 0, buffer, offset, data.Length);
|
||||
_position += data.Length;
|
||||
return data.Length;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long newPos = _position;
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
newPos = offset;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
newPos = _position + offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
newPos = _length + offset;
|
||||
break;
|
||||
}
|
||||
|
||||
// 允许 Seek 超过 EOF (标准 Stream 行为),但在 Read 时会返回 0
|
||||
if (newPos < 0)
|
||||
{
|
||||
throw new IOException("An attempt was made to move the file pointer before the beginning of the file.");
|
||||
}
|
||||
|
||||
_position = newPos;
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
{
|
||||
try { _store.CloseFile(_handle); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public class StreamFileAbstraction : TagLib.File.IFileAbstraction
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly Stream _stream;
|
||||
private readonly bool _closeStreamOnDispose;
|
||||
|
||||
public StreamFileAbstraction(string path, Stream stream, bool closeStreamOnDispose = false)
|
||||
{
|
||||
_name = Path.GetFileName(path);
|
||||
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
_closeStreamOnDispose = closeStreamOnDispose;
|
||||
}
|
||||
|
||||
public string Name => _name;
|
||||
|
||||
public Stream ReadStream => _stream;
|
||||
|
||||
public Stream WriteStream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stream.CanWrite)
|
||||
{
|
||||
return _stream;
|
||||
}
|
||||
throw new InvalidOperationException("The underlying stream is read-only. Tag saving is not supported for this source.");
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseStream(Stream stream)
|
||||
{
|
||||
if (_closeStreamOnDispose)
|
||||
{
|
||||
stream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public class UnifiedFileItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
public long Size { get; set; }
|
||||
public bool IsFolder { get; set; }
|
||||
public DateTime? LastModified { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WebDav;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.FileSystem
|
||||
{
|
||||
public partial class WebDavFileSystem : IUnifiedFileSystem
|
||||
{
|
||||
private readonly WebDavClient _client;
|
||||
private readonly string _baseUrl;
|
||||
private readonly string _rootPath;
|
||||
|
||||
// host: http://192.168.1.5:5005
|
||||
// path: /music
|
||||
public WebDavFileSystem(string host, string user, string pass, int port, string path)
|
||||
{
|
||||
if (!host.StartsWith("http")) host = $"http://{host}";
|
||||
if (port > 0) host = $"{host}:{port}";
|
||||
|
||||
_baseUrl = host;
|
||||
_rootPath = path ?? "/";
|
||||
|
||||
_client = new WebDavClient(new WebDavClientParams
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Credentials = new System.Net.NetworkCredential(user, pass)
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
// WebDAV 无状态,Propfind 测试根目录连通性
|
||||
var result = await _client.Propfind(_rootPath);
|
||||
return result.IsSuccessful;
|
||||
}
|
||||
|
||||
public async Task<List<UnifiedFileItem>> GetFilesAsync(string relativePath)
|
||||
{
|
||||
var targetPath = Path.Combine(_rootPath, relativePath).Replace("\\", "/");
|
||||
var result = await _client.Propfind(targetPath);
|
||||
|
||||
var list = new List<UnifiedFileItem>();
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
foreach (var res in result.Resources)
|
||||
{
|
||||
if (res == null || res.Uri == null) continue;
|
||||
|
||||
// 排除掉文件夹自身 (WebDAV 通常会把当前请求的文件夹作为第一个结果返回)
|
||||
// 通过判断 URL 结尾是否一致来简单过滤,或者判断 IsCollection 且 Uri 相同
|
||||
// 这里简单处理:只要名字不为空
|
||||
var name = System.Net.WebUtility.UrlDecode(res.Uri.Split('/').LastOrDefault());
|
||||
if (string.IsNullOrEmpty(name)) continue;
|
||||
|
||||
// 如果名字和请求的目录名一样,可能是它自己,跳过 (这需要根据具体服务器响应调整)
|
||||
// 更稳妥的是比较 Uri
|
||||
|
||||
list.Add(new UnifiedFileItem
|
||||
{
|
||||
Name = name,
|
||||
FullPath = res.Uri.ToString(), // WebDAV 需要完整 URI
|
||||
IsFolder = res.IsCollection,
|
||||
Size = res.ContentLength ?? 0,
|
||||
LastModified = res.LastModifiedDate
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task<Stream> OpenReadAsync(string fullPath)
|
||||
{
|
||||
// WebDAV 获取流
|
||||
var res = await _client.GetRawFile(fullPath);
|
||||
if (!res.IsSuccessful) throw new IOException($"WebDAV Error: {res.StatusCode}");
|
||||
return res.Stream;
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync() => await Task.CompletedTask;
|
||||
|
||||
public void Dispose() => _client?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class TranslateResponse
|
||||
public class LibreTranslateResponse
|
||||
{
|
||||
[JsonPropertyName("translatedText")]
|
||||
public string TranslatedText { get; set; }
|
||||
@@ -1,20 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LocalMediaFolder : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsRealTimeWatchEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Path { get; set; }
|
||||
|
||||
public LocalMediaFolder() { }
|
||||
|
||||
public LocalMediaFolder(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
|
||||
|
||||
public List<LyricsLine> LyricsLines { get; set; }
|
||||
public List<LyricsLine> LyricsLines { get; set; } = [];
|
||||
public string? LanguageCode
|
||||
{
|
||||
get => field ?? LanguageHelper.DetectLanguageCode(WrappedOriginalText);
|
||||
@@ -22,7 +22,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public LyricsData()
|
||||
{
|
||||
LyricsLines = [];
|
||||
}
|
||||
|
||||
public LyricsData(List<LyricsLine> lyricsLines)
|
||||
@@ -90,6 +89,24 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTransliteration(string transliteration)
|
||||
{
|
||||
List<string> transliterationArr = transliteration.Split(StringHelper.NewLine).ToList();
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= transliterationArr.Count)
|
||||
{
|
||||
line.PhoneticText = ""; // No transliteration available, keep empty
|
||||
}
|
||||
else
|
||||
{
|
||||
line.PhoneticText = transliterationArr[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public static LyricsData GetNotfoundPlaceholder()
|
||||
{
|
||||
return new LyricsData([new LyricsLine
|
||||
@@ -102,14 +119,18 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public static LyricsData GetLoadingPlaceholder()
|
||||
{
|
||||
return new LyricsData([
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
},
|
||||
]);
|
||||
return new LyricsData()
|
||||
{
|
||||
LyricsLines = [
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
},
|
||||
],
|
||||
LanguageCode = "N/A",
|
||||
};
|
||||
}
|
||||
|
||||
public LyricsLine? GetLyricsLine(double sec)
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysOnTopPolling { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsShownInSwitchers { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLocked { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysHideUnlockButton { get; set; } = false;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPinToTaskbar { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TaskbarPlacement TaskbarPlacement { get; set; } = TaskbarPlacement.Right;
|
||||
@@ -200,6 +201,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
IsAlwaysOnTopPolling = this.IsAlwaysOnTopPolling,
|
||||
IsShownInSwitchers = this.IsShownInSwitchers,
|
||||
IsLocked = this.IsLocked,
|
||||
IsAlwaysHideUnlockButton = this.IsAlwaysHideUnlockButton,
|
||||
|
||||
IsPinToTaskbar = this.IsPinToTaskbar,
|
||||
TaskbarPlacement = this.TaskbarPlacement,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models.FileSystem;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class MediaFolder : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty] public partial string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsRealTimeWatchEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients][NotifyPropertyChangedFor(nameof(ConnectionSummary))] public partial string Path { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
[NotifyPropertyChangedFor(nameof(IsLocal))]
|
||||
[NotifyPropertyChangedFor(nameof(ConnectionSummary))]
|
||||
public partial FileSourceType SourceType { get; set; } = FileSourceType.Local;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Name { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string UserName { get; set; }
|
||||
|
||||
[ObservableProperty] public partial int Port { get; set; } = -1;
|
||||
|
||||
[JsonIgnore] public string Password { get; set; }
|
||||
|
||||
[JsonIgnore] public bool IsLocal => SourceType == FileSourceType.Local;
|
||||
|
||||
[JsonIgnore]
|
||||
public string ConnectionSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsLocal) return Path;
|
||||
return $"{SourceType} - {Path} {(string.IsNullOrEmpty(UserName) ? "" : $"({UserName})")}";
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore] public string VaultKey => $"{Id}-{UserName}";
|
||||
|
||||
public MediaFolder() { }
|
||||
|
||||
public MediaFolder(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public IUnifiedFileSystem? CreateFileSystem()
|
||||
{
|
||||
if (!IsEnabled) return null;
|
||||
if (string.IsNullOrEmpty(Password) && !IsLocal)
|
||||
{
|
||||
Password = PasswordVaultHelper.Get(Constants.App.AppName, VaultKey) ?? "";
|
||||
}
|
||||
|
||||
return SourceType switch
|
||||
{
|
||||
FileSourceType.Local => new LocalFileSystem(Path),
|
||||
FileSourceType.SMB => new SMBFileSystem(Path, UserName, Password),
|
||||
FileSourceType.FTP => new FTPFileSystem(Path, UserName, Password, Port, Path),
|
||||
FileSourceType.WebDav => new WebDavFileSystem(Path, UserName, Password, Port, Path),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class PlayQueueItem
|
||||
{
|
||||
public Track Track { get; set; }
|
||||
public ExtendedTrack Track { get; set; }
|
||||
|
||||
public PlayQueueItem(Track track)
|
||||
public PlayQueueItem(ExtendedTrack track)
|
||||
{
|
||||
Track = track;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial MusicGallerySettings MusicGallerySettings { get; set; } = new MusicGallerySettings();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AdvancedSettings AdvancedSettings { get; set; } = new AdvancedSettings();
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaFolder> LocalMediaFolders { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MappedSongSearchQuery> MappedSongSearchQueries { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsWindowStatus> WindowBoundsRecords { get; set; } = [];
|
||||
|
||||
@@ -12,6 +12,11 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPureColorOverlayEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PureColorOverlayOpacity { get; set; } = 100; // 100 % = 1.0
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsCoverOverlayEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayOpacity { get; set; } = 100; // 100 % = 1.0
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlaySpeed { get; set; } = 50;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayBlurAmount { get; set; } = 100;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFluidOverlayEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int FluidOverlayOpacity { get; set; } = 100;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PaletteGeneratorType PaletteGeneratorType { get; set; } = PaletteGeneratorType.MedianCut;
|
||||
@@ -39,14 +44,25 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
IsPureColorOverlayEnabled = this.IsPureColorOverlayEnabled,
|
||||
PureColorOverlayOpacity = this.PureColorOverlayOpacity,
|
||||
|
||||
IsCoverOverlayEnabled = this.IsCoverOverlayEnabled,
|
||||
CoverOverlayOpacity = this.CoverOverlayOpacity,
|
||||
CoverOverlaySpeed = this.CoverOverlaySpeed,
|
||||
CoverOverlayBlurAmount = this.CoverOverlayBlurAmount,
|
||||
|
||||
IsFluidOverlayEnabled = this.IsFluidOverlayEnabled,
|
||||
FluidOverlayOpacity = this.FluidOverlayOpacity,
|
||||
PaletteGeneratorType = this.PaletteGeneratorType,
|
||||
|
||||
IsSpectrumOverlayEnabled = this.IsSpectrumOverlayEnabled,
|
||||
SpectrumPlacement = this.SpectrumPlacement,
|
||||
SpectrumStyle = this.SpectrumStyle,
|
||||
SpectrumCount = this.SpectrumCount,
|
||||
|
||||
IsSnowFlakeOverlayEnabled = this.IsSnowFlakeOverlayEnabled,
|
||||
SnowFlakeOverlayAmount = this.SnowFlakeOverlayAmount,
|
||||
SnowFlakeOverlaySpeed = this.SnowFlakeOverlaySpeed,
|
||||
|
||||
IsFogOverlayEnabled = this.IsFogOverlayEnabled,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsBlurEffectEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFadeOutEffectEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsOutOfSightEffectEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsGlowEffectEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectScope LyricsGlowEffectScope { get; set; } = LyricsEffectScope.LongDurationSyllable;
|
||||
@@ -52,11 +54,14 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
|
||||
{
|
||||
IsLyricsBlurEffectEnabled = this.IsLyricsBlurEffectEnabled,
|
||||
IsLyricsFadeOutEffectEnabled = this.IsLyricsFadeOutEffectEnabled,
|
||||
IsLyricsOutOfSightEffectEnabled = this.IsLyricsOutOfSightEffectEnabled,
|
||||
|
||||
IsLyricsGlowEffectEnabled = this.IsLyricsGlowEffectEnabled,
|
||||
LyricsGlowEffectLongSyllableDuration = this.LyricsGlowEffectLongSyllableDuration,
|
||||
IsLyricsGlowEffectAmountAutoAdjust = this.IsLyricsGlowEffectAmountAutoAdjust,
|
||||
LyricsGlowEffectAmount = this.LyricsGlowEffectAmount,
|
||||
LyricsGlowEffectScope = this.LyricsGlowEffectScope,
|
||||
|
||||
IsLyricsScaleEffectEnabled = this.IsLyricsScaleEffectEnabled,
|
||||
LyricsScaleEffectLongSyllableDuration = this.LyricsScaleEffectLongSyllableDuration,
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoPlay { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsWindowStatus LyricsWindowStatus { get; set; } = new();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnWindowClosed { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool StopOnWindowClosed { get; set; } = false;
|
||||
|
||||
public MusicGallerySettings() { }
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ChineseRomanization ChineseRomanization { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsChineseRomanizationEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsJapaneseRomanizationEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string CutletDockerServer { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTraditionalChineseEnabled { get; set; } = false;
|
||||
|
||||
public TranslationSettings() { }
|
||||
|
||||
@@ -5,7 +5,8 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Lyricify.Lyrics.Parsers;
|
||||
@@ -70,7 +71,13 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
public async Task<(LyricsData, TransliterationSearchProvider?, TranslationSearchProvider?)> Parse(ITranslateService translateService, TranslationSettings settings, LyricsSearchResult? lyricsSearchResult, CancellationToken token)
|
||||
public async Task<(LyricsData, TransliterationSearchProvider?, TranslationSearchProvider?)> Parse(
|
||||
ITranslationService translationService,
|
||||
ITransliterationService transliterationService,
|
||||
TranslationSettings settings,
|
||||
LyricsSearchResult? lyricsSearchResult,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
TransliterationSearchProvider? transliterationSearchProvider = null;
|
||||
TranslationSearchProvider? translationSearchProvider = null;
|
||||
@@ -81,6 +88,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
|
||||
|
||||
// 应用音译
|
||||
LyricsData? phoneticLyricsData = null;
|
||||
// 已解析歌词内寻找
|
||||
if (settings.IsChineseRomanizationEnabled && main.LanguageCode == LanguageHelper.ChineseCode)
|
||||
{
|
||||
phoneticLyricsData = settings.ChineseRomanization switch
|
||||
@@ -89,21 +97,41 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
|
||||
ChineseRomanization.Jyutping => _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.JyutpingCode),
|
||||
_ => null,
|
||||
};
|
||||
if (phoneticLyricsData != null)
|
||||
{
|
||||
main.SetPhoneticText(phoneticLyricsData);
|
||||
if (phoneticLyricsData.AutoGenerated)
|
||||
{
|
||||
transliterationSearchProvider = TransliterationSearchProvider.BetterLyrics;
|
||||
}
|
||||
else
|
||||
{
|
||||
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (settings.IsJapaneseRomanizationEnabled && main.LanguageCode == LanguageHelper.JapaneseCode)
|
||||
{
|
||||
phoneticLyricsData = _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.RomanCode);
|
||||
}
|
||||
if (phoneticLyricsData != null)
|
||||
{
|
||||
main.SetPhoneticText(phoneticLyricsData);
|
||||
if (phoneticLyricsData.AutoGenerated)
|
||||
if (phoneticLyricsData != null)
|
||||
{
|
||||
transliterationSearchProvider = TransliterationSearchProvider.BetterLyrics;
|
||||
main.SetPhoneticText(phoneticLyricsData);
|
||||
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
|
||||
}
|
||||
else
|
||||
{
|
||||
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
|
||||
string romaji = string.Empty;
|
||||
try
|
||||
{
|
||||
romaji = await transliterationService.TransliterateText(main.WrappedOriginalText, PhoneticHelper.RomanCode, token);
|
||||
_lyricsDataArr.FirstOrDefault()?.SetTransliteration(romaji);
|
||||
transliterationSearchProvider = TransliterationSearchProvider.CutletDocker;
|
||||
}
|
||||
catch (TaskCanceledException) { }
|
||||
catch (Exception)
|
||||
{
|
||||
ToastHelper.ShowToast("CutletDockerFailed", null, InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +149,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
|
||||
string translated = string.Empty;
|
||||
try
|
||||
{
|
||||
translated = await translateService.TranslateTextAsync(main.WrappedOriginalText, settings.SelectedTargetLanguageCode, token);
|
||||
translated = await translationService.TranslateTextAsync(main.WrappedOriginalText, settings.SelectedTargetLanguageCode, token);
|
||||
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated);
|
||||
translationSearchProvider = TranslationSearchProvider.LibreTranslate;
|
||||
}
|
||||
@@ -239,30 +267,6 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
|
||||
});
|
||||
}
|
||||
}
|
||||
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),
|
||||
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
|
||||
{
|
||||
StartMs = c.StartMs,
|
||||
EndMs = c.EndMs,
|
||||
Text = PhoneticHelper.ToRomaji(c.Text),
|
||||
StartIndex = c.StartIndex
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
@@ -13,17 +14,47 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
private CanvasBitmap? _currentBitmap;
|
||||
private CanvasBitmap? _previousBitmap;
|
||||
|
||||
private readonly ValueTransition<double> _crossfadeTransition;
|
||||
private CanvasRenderTarget? _currentTargetCache;
|
||||
private CanvasRenderTarget? _previousTargetCache;
|
||||
|
||||
private Size _lastScreenSize;
|
||||
private bool _lastWasRotating = false;
|
||||
|
||||
private readonly ValueTransition<double> _crossfadeTransition;
|
||||
private float _rotationAngle = 0f;
|
||||
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
||||
public int Opacity { get; set; } = 100;
|
||||
|
||||
public int BlurAmount { get; set; } = 100;
|
||||
private bool _needsCacheUpdate = false;
|
||||
|
||||
public int Speed { get; set; } = 100;
|
||||
private int _blurAmount = 100;
|
||||
public int BlurAmount
|
||||
{
|
||||
get => _blurAmount;
|
||||
set
|
||||
{
|
||||
if (_blurAmount != value)
|
||||
{
|
||||
_blurAmount = value;
|
||||
_needsCacheUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _speed = 100;
|
||||
public int Speed
|
||||
{
|
||||
get => _speed;
|
||||
set
|
||||
{
|
||||
if (_speed != value)
|
||||
{
|
||||
_speed = value;
|
||||
_needsCacheUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CoverBackgroundRenderer()
|
||||
{
|
||||
@@ -34,26 +65,30 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
if (_currentBitmap == newBitmap) return;
|
||||
|
||||
if (_currentBitmap == null)
|
||||
{
|
||||
_currentBitmap = newBitmap;
|
||||
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
|
||||
return;
|
||||
}
|
||||
|
||||
_previousBitmap = _currentBitmap;
|
||||
_previousTargetCache = _currentTargetCache;
|
||||
_currentTargetCache = null;
|
||||
|
||||
_currentBitmap = newBitmap;
|
||||
|
||||
if (newBitmap != null)
|
||||
if (_currentBitmap == null)
|
||||
{
|
||||
_crossfadeTransition.Reset(0.0);
|
||||
_crossfadeTransition.StartTransition(1.0);
|
||||
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previousBitmap = null;
|
||||
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
|
||||
if (_previousBitmap == null)
|
||||
{
|
||||
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_crossfadeTransition.Reset(0.0);
|
||||
_crossfadeTransition.StartTransition(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
_needsCacheUpdate = true;
|
||||
}
|
||||
|
||||
public void Update(TimeSpan deltaTime)
|
||||
@@ -64,17 +99,17 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
|
||||
if (Speed > 0)
|
||||
{
|
||||
float baseSpeed = 0.6f; // 弧度/秒
|
||||
float baseSpeed = 0.6f;
|
||||
float currentSpeed = (Speed / 100.0f) * baseSpeed;
|
||||
|
||||
_rotationAngle += currentSpeed * (float)deltaTime.TotalSeconds;
|
||||
|
||||
_rotationAngle %= (float)(2 * Math.PI);
|
||||
}
|
||||
|
||||
if (_crossfadeTransition.Value >= 1.0 && _previousBitmap != null)
|
||||
{
|
||||
_previousBitmap = null;
|
||||
_previousTargetCache?.Dispose();
|
||||
_previousTargetCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,84 +117,133 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
if (!IsEnabled || Opacity <= 0) return;
|
||||
|
||||
if (_lastScreenSize != control.Size)
|
||||
{
|
||||
_lastScreenSize = control.Size;
|
||||
_needsCacheUpdate = true;
|
||||
}
|
||||
|
||||
bool isRotating = Speed > 0;
|
||||
if (_lastWasRotating != isRotating)
|
||||
{
|
||||
_lastWasRotating = isRotating;
|
||||
_needsCacheUpdate = true;
|
||||
}
|
||||
|
||||
EnsureCachedLayer(control, _currentBitmap, ref _currentTargetCache);
|
||||
|
||||
float baseAlpha = Opacity / 100.0f;
|
||||
float currentBlur = BlurAmount;
|
||||
|
||||
float angle = Speed > 0 ? _rotationAngle : 0f;
|
||||
|
||||
float angle = isRotating ? _rotationAngle : 0f;
|
||||
double fadeProgress = _crossfadeTransition.Value;
|
||||
bool isCrossfading = fadeProgress < 1.0 && _previousBitmap != null;
|
||||
bool isCrossfading = fadeProgress < 1.0 && _previousTargetCache != null;
|
||||
|
||||
Vector2 screenCenter = new Vector2((float)control.Size.Width / 2f, (float)control.Size.Height / 2f);
|
||||
|
||||
if (isCrossfading)
|
||||
{
|
||||
DrawLayer(ds, control.Size, _previousBitmap, angle, currentBlur, baseAlpha);
|
||||
DrawCachedLayer(ds, _previousTargetCache, screenCenter, angle, baseAlpha);
|
||||
|
||||
float newLayerAlpha = baseAlpha * (float)fadeProgress;
|
||||
if (newLayerAlpha > 0.005f)
|
||||
{
|
||||
DrawLayer(ds, control.Size, _currentBitmap, angle, currentBlur, newLayerAlpha);
|
||||
}
|
||||
DrawCachedLayer(ds, _currentTargetCache, screenCenter, angle, newLayerAlpha);
|
||||
}
|
||||
else if (_currentBitmap != null)
|
||||
else if (_currentTargetCache != null)
|
||||
{
|
||||
DrawLayer(ds, control.Size, _currentBitmap, angle, currentBlur, baseAlpha);
|
||||
DrawCachedLayer(ds, _currentTargetCache, screenCenter, angle, baseAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLayer(CanvasDrawingSession ds, Windows.Foundation.Size screenSize, CanvasBitmap? bitmap, float rotationRadians, float blurAmount, float alpha)
|
||||
private void EnsureCachedLayer(ICanvasResourceCreator resourceCreator, CanvasBitmap? sourceBitmap, ref CanvasRenderTarget? targetCache)
|
||||
{
|
||||
if (bitmap == null) return;
|
||||
|
||||
float imgW = bitmap.SizeInPixels.Width;
|
||||
float imgH = bitmap.SizeInPixels.Height;
|
||||
Vector2 screenCenter = new Vector2((float)screenSize.Width / 2f, (float)screenSize.Height / 2f);
|
||||
|
||||
float scale;
|
||||
if (Speed > 0 && Math.Abs(rotationRadians) > 0.001f)
|
||||
if (sourceBitmap == null)
|
||||
{
|
||||
float screenDiagonal = (float)Math.Sqrt(screenSize.Width * screenSize.Width + screenSize.Height * screenSize.Height);
|
||||
|
||||
float scaleX = screenDiagonal / imgW;
|
||||
float scaleY = screenDiagonal / imgH;
|
||||
scale = Math.Max(scaleX, scaleY);
|
||||
}
|
||||
else
|
||||
{
|
||||
float scaleX = (float)screenSize.Width / imgW;
|
||||
float scaleY = (float)screenSize.Height / imgH;
|
||||
scale = Math.Max(scaleX, scaleY);
|
||||
targetCache?.Dispose();
|
||||
targetCache = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 缩放图片 -> 将图片中心移动到 (0,0) 以便旋转 -> 旋转 ->将图片移回屏幕中心
|
||||
Vector2 imgCenterOffset = new Vector2(
|
||||
((float)screenSize.Width - imgW * scale) / 2.0f,
|
||||
((float)screenSize.Height - imgH * scale) / 2.0f
|
||||
);
|
||||
bool deviceMismatch = targetCache != null && targetCache.Device != resourceCreator.Device;
|
||||
|
||||
if (_needsCacheUpdate || targetCache == null || deviceMismatch)
|
||||
{
|
||||
targetCache?.Dispose();
|
||||
|
||||
float imgW = sourceBitmap.SizeInPixels.Width;
|
||||
float imgH = sourceBitmap.SizeInPixels.Height;
|
||||
Size screenSize = _lastScreenSize;
|
||||
|
||||
float scale;
|
||||
if (_lastWasRotating) // Speed > 0
|
||||
{
|
||||
float screenDiagonal = (float)Math.Sqrt(screenSize.Width * screenSize.Width + screenSize.Height * screenSize.Height);
|
||||
scale = Math.Max(screenDiagonal / imgW, screenDiagonal / imgH);
|
||||
}
|
||||
else
|
||||
{
|
||||
float scaleX = (float)screenSize.Width / imgW;
|
||||
float scaleY = (float)screenSize.Height / imgH;
|
||||
scale = Math.Max(scaleX, scaleY);
|
||||
}
|
||||
|
||||
float targetW = imgW * scale;
|
||||
float targetH = imgH * scale;
|
||||
|
||||
targetCache = new CanvasRenderTarget(resourceCreator, targetW, targetH, sourceBitmap.Dpi);
|
||||
|
||||
using (var ds = targetCache.CreateDrawingSession())
|
||||
{
|
||||
ds.Clear(Windows.UI.Color.FromArgb(0, 0, 0, 0));
|
||||
|
||||
using (var transformEffect = new Transform2DEffect())
|
||||
using (var blurEffect = new GaussianBlurEffect())
|
||||
{
|
||||
transformEffect.Source = sourceBitmap;
|
||||
transformEffect.TransformMatrix = Matrix3x2.CreateScale(scale);
|
||||
transformEffect.InterpolationMode = CanvasImageInterpolation.Linear;
|
||||
|
||||
blurEffect.Source = transformEffect;
|
||||
blurEffect.BlurAmount = BlurAmount;
|
||||
blurEffect.BorderMode = EffectBorderMode.Hard;
|
||||
|
||||
ds.DrawImage(blurEffect);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceBitmap == _currentBitmap)
|
||||
{
|
||||
_needsCacheUpdate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCachedLayer(CanvasDrawingSession ds, CanvasRenderTarget? cachedTexture, Vector2 screenCenter, float rotationRadians, float alpha)
|
||||
{
|
||||
if (cachedTexture == null) return;
|
||||
|
||||
Vector2 textureCenter = new Vector2((float)cachedTexture.Size.Width / 2f, (float)cachedTexture.Size.Height / 2f);
|
||||
|
||||
Matrix3x2 transform =
|
||||
Matrix3x2.CreateScale(scale) * Matrix3x2.CreateTranslation(imgCenterOffset) * Matrix3x2.CreateRotation(rotationRadians, screenCenter);
|
||||
Matrix3x2.CreateTranslation(-textureCenter) * Matrix3x2.CreateRotation(rotationRadians) * Matrix3x2.CreateTranslation(screenCenter);
|
||||
|
||||
using (var transformEffect = new Transform2DEffect())
|
||||
using (var blurEffect = new GaussianBlurEffect())
|
||||
{
|
||||
transformEffect.Source = bitmap;
|
||||
transformEffect.TransformMatrix = transform;
|
||||
transformEffect.InterpolationMode = CanvasImageInterpolation.Linear;
|
||||
Matrix3x2 previousTransform = ds.Transform;
|
||||
|
||||
blurEffect.Source = transformEffect;
|
||||
blurEffect.BlurAmount = blurAmount > 0 ? (blurAmount / 2.0f) : 0f;
|
||||
blurEffect.BorderMode = EffectBorderMode.Hard;
|
||||
ds.Transform = transform * previousTransform;
|
||||
ds.DrawImage(cachedTexture, 0, 0, new Rect(0, 0, cachedTexture.Size.Width, cachedTexture.Size.Height), alpha);
|
||||
|
||||
ds.DrawImage(blurEffect, 0, 0, new Windows.Foundation.Rect(0, 0, screenSize.Width, screenSize.Height), alpha);
|
||||
}
|
||||
ds.Transform = previousTransform;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_currentBitmap?.Dispose();
|
||||
_currentBitmap = null;
|
||||
_previousBitmap?.Dispose();
|
||||
|
||||
_currentTargetCache?.Dispose();
|
||||
_previousTargetCache?.Dispose();
|
||||
|
||||
_currentBitmap = null;
|
||||
_previousBitmap = null;
|
||||
_currentTargetCache = null;
|
||||
_previousTargetCache = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,13 +233,15 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
public void CalculateLyrics3DMatrix(LyricsEffectSettings lyricsEffect, double lyricsX, double lyricsY, double lyricsWidth, double canvasHeight)
|
||||
public void CalculateLyrics3DMatrix(LyricsStyleSettings lyricsStyle, LyricsEffectSettings lyricsEffect, double lyricsX, double lyricsY, double lyricsWidth, double lyricsHeight)
|
||||
{
|
||||
if (!lyricsEffect.Is3DLyricsEnabled) return;
|
||||
|
||||
var playingLineTopOffsetFactor = lyricsStyle.PlayingLineTopOffset / 100.0;
|
||||
|
||||
Vector3 center = new(
|
||||
(float)(lyricsX + lyricsWidth / 2),
|
||||
(float)(lyricsY + canvasHeight / 2),
|
||||
(float)(lyricsY + lyricsHeight * playingLineTopOffsetFactor / 2),
|
||||
0);
|
||||
|
||||
float rotationX = (float)(Math.PI * lyricsEffect.Lyrics3DXAngle / 180.0);
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
float blur,
|
||||
float opacity)
|
||||
{
|
||||
if (opacity <= 0) return;
|
||||
if (float.IsNaN(opacity) || opacity <= 0) return;
|
||||
|
||||
var bounds = layout.LayoutBounds;
|
||||
var destRect = new Rect(
|
||||
|
||||
@@ -7,7 +7,9 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Serialization
|
||||
{
|
||||
[JsonSerializable(typeof(TranslateResponse))]
|
||||
[JsonSerializable(typeof(LibreTranslateResponse))]
|
||||
[JsonSerializable(typeof(CutletDockerRequest))]
|
||||
[JsonSerializable(typeof(CutletDockerResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(AppSettings))]
|
||||
[JsonSerializable(typeof(LyricsSearchResult))]
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case AlbumArtSearchProvider.Local:
|
||||
result = SearchFile(songInfo)?.AsBuffer();
|
||||
result = (await SearchFile(songInfo))?.AsBuffer();
|
||||
break;
|
||||
case AlbumArtSearchProvider.SMTC:
|
||||
result = bufferFromSMTC;
|
||||
@@ -77,29 +77,73 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[]? SearchFile(SongInfo songInfo)
|
||||
private async Task<byte[]?> SearchFile(SongInfo songInfo)
|
||||
{
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
if (!folder.IsEnabled) continue;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path))
|
||||
using var fs = folder.CreateFileSystem();
|
||||
if (fs == null) continue;
|
||||
if (!await fs.ConnectAsync()) continue;
|
||||
|
||||
// 递归扫描
|
||||
var foldersToScan = new Queue<string>();
|
||||
foldersToScan.Enqueue(""); // 根目录
|
||||
|
||||
while (foldersToScan.Count > 0)
|
||||
{
|
||||
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
|
||||
var currentPath = foldersToScan.Dequeue();
|
||||
var items = await fs.GetFilesAsync(currentPath);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
Track track = new(file);
|
||||
if ((track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists) || StringHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.DisplayArtists, songInfo.Title))
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
foldersToScan.Enqueue(Path.Combine(currentPath, item.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(item.Name).ToLower();
|
||||
if (FileHelper.MusicExtensions.Contains(ext))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = await fs.OpenReadAsync(item.FullPath))
|
||||
{
|
||||
var track = new ExtendedTrack(item.FullPath, stream);
|
||||
|
||||
bool isMetadataMatch = (track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists);
|
||||
bool isFilenameMatch = StringHelper.IsSwitchableNormalizedMatch(
|
||||
Path.GetFileNameWithoutExtension(item.Name),
|
||||
songInfo.DisplayArtists,
|
||||
songInfo.Title
|
||||
);
|
||||
|
||||
if (isMetadataMatch || isFilenameMatch)
|
||||
{
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null && bytes.Length > 0)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
lyricsSearchResult = await SearchAmllTtmlDbAsync(songInfo);
|
||||
break;
|
||||
case LyricsSearchProvider.LocalMusicFile:
|
||||
lyricsSearchResult = SearchEmbedded(songInfo);
|
||||
lyricsSearchResult = await SearchEmbedded(songInfo);
|
||||
break;
|
||||
case LyricsSearchProvider.LocalLrcFile:
|
||||
case LyricsSearchProvider.LocalEslrcFile:
|
||||
@@ -277,7 +277,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
private async Task<LyricsSearchResult> SearchFile(SongInfo songInfo, LyricsFormat format)
|
||||
{
|
||||
int maxScore = 0;
|
||||
string? bestFile = null;
|
||||
|
||||
MediaFolder? bestFolder = null;
|
||||
string? bestFilePath = null;
|
||||
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
@@ -288,47 +290,97 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
if (!folder.IsEnabled) continue;
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
using var fs = folder.CreateFileSystem();
|
||||
if (fs == null) continue;
|
||||
if (!await fs.ConnectAsync()) continue;
|
||||
|
||||
// 递归扫描
|
||||
var foldersToScan = new Queue<string>();
|
||||
foldersToScan.Enqueue(""); // 从根目录开始
|
||||
|
||||
string targetExt = format.ToFileExtension();
|
||||
|
||||
while (foldersToScan.Count > 0)
|
||||
{
|
||||
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
|
||||
var currentPath = foldersToScan.Dequeue();
|
||||
var items = await fs.GetFilesAsync(currentPath);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult { Reference = file });
|
||||
if (score > maxScore)
|
||||
if (item.IsFolder)
|
||||
{
|
||||
bestFile = file;
|
||||
maxScore = score;
|
||||
foldersToScan.Enqueue(Path.Combine(currentPath, item.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Name.EndsWith(targetExt, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult { Reference = item.FullPath });
|
||||
|
||||
if (score > maxScore)
|
||||
{
|
||||
maxScore = score;
|
||||
bestFilePath = item.FullPath;
|
||||
bestFolder = folder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 日志记录...
|
||||
}
|
||||
}
|
||||
|
||||
if (bestFile != null)
|
||||
// 4. 如果找到了最佳匹配,读取内容
|
||||
if (bestFolder != null && bestFilePath != null)
|
||||
{
|
||||
lyricsSearchResult.Reference = bestFile;
|
||||
lyricsSearchResult.MatchPercentage = maxScore;
|
||||
|
||||
string? raw = await File.ReadAllTextAsync(bestFile, FileHelper.GetEncoding(bestFile));
|
||||
if (raw != null)
|
||||
try
|
||||
{
|
||||
lyricsSearchResult.Raw = raw;
|
||||
// 重新连接以读取文件 (因为之前的 fs 已经在 using 结束时释放)
|
||||
using var fs = bestFolder.CreateFileSystem();
|
||||
if (fs != null && await fs.ConnectAsync())
|
||||
{
|
||||
using var stream = await fs.OpenReadAsync(bestFilePath);
|
||||
|
||||
// 使用 StreamReader 读取文本
|
||||
// 注意:这里简单使用 Default 编码,如果需要探测编码(FileHelper.GetEncoding),
|
||||
// 可能需要先读一部分字节来判断,或者使用带编码探测的库。
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
string raw = await reader.ReadToEndAsync();
|
||||
|
||||
lyricsSearchResult.Reference = bestFilePath;
|
||||
lyricsSearchResult.MatchPercentage = maxScore;
|
||||
lyricsSearchResult.Raw = raw;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 读取失败处理
|
||||
}
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private LyricsSearchResult SearchEmbedded(SongInfo songInfo)
|
||||
private async Task<LyricsSearchResult> SearchEmbedded(SongInfo songInfo)
|
||||
{
|
||||
int bestScore = 0;
|
||||
string? bestFile = null;
|
||||
string? bestFilePath = null;
|
||||
string? bestRaw = null;
|
||||
|
||||
// 用于最后回填 Metadata
|
||||
string? bestTitle = null;
|
||||
string[]? bestArtists = null;
|
||||
string? bestAlbum = null;
|
||||
double bestDuration = 0;
|
||||
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = LyricsSearchProvider.LocalMusicFile,
|
||||
@@ -336,49 +388,89 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
if (!folder.IsEnabled) continue;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path))
|
||||
using var fs = folder.CreateFileSystem();
|
||||
if (fs == null) continue;
|
||||
if (!await fs.ConnectAsync()) continue;
|
||||
|
||||
var foldersToScan = new Queue<string>();
|
||||
foldersToScan.Enqueue("");
|
||||
|
||||
while (foldersToScan.Count > 0)
|
||||
{
|
||||
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
|
||||
var currentPath = foldersToScan.Dequeue();
|
||||
var items = await fs.GetFilesAsync(currentPath);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var track = new Track(file);
|
||||
var raw = track.GetRawLyrics();
|
||||
|
||||
if (!string.IsNullOrEmpty(raw))
|
||||
if (item.IsFolder)
|
||||
{
|
||||
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,
|
||||
});
|
||||
foldersToScan.Enqueue(Path.Combine(currentPath, item.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (score > bestScore)
|
||||
var ext = Path.GetExtension(item.Name).ToLower();
|
||||
if (FileHelper.MusicExtensions.Contains(ext))
|
||||
{
|
||||
try
|
||||
{
|
||||
bestScore = score;
|
||||
bestFile = file;
|
||||
bestRaw = raw;
|
||||
using var stream = await fs.OpenReadAsync(item.FullPath);
|
||||
|
||||
var track = new ExtendedTrack(item.FullPath, stream);
|
||||
var raw = track.RawLyrics;
|
||||
|
||||
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 = item.FullPath,
|
||||
});
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestFilePath = item.FullPath;
|
||||
bestRaw = raw;
|
||||
|
||||
// 缓存当前最佳的元数据,避免最后还需要重新打开文件读一次
|
||||
bestTitle = track.Title;
|
||||
bestArtists = track.Artist?.Split(ATL.Settings.DisplayValueSeparator);
|
||||
bestAlbum = track.Album;
|
||||
bestDuration = track.Duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 单个文件解析失败忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 文件夹扫描失败忽略
|
||||
}
|
||||
}
|
||||
|
||||
if (bestFile != null)
|
||||
if (bestFilePath != 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;
|
||||
// 直接使用缓存的数据,不需要 new Track(bestFile) 了
|
||||
lyricsSearchResult.Title = bestTitle;
|
||||
lyricsSearchResult.Artists = bestArtists;
|
||||
lyricsSearchResult.Album = bestAlbum;
|
||||
lyricsSearchResult.Duration = bestDuration;
|
||||
|
||||
lyricsSearchResult.Raw = bestRaw;
|
||||
lyricsSearchResult.Reference = bestFile;
|
||||
lyricsSearchResult.Reference = bestFilePath;
|
||||
lyricsSearchResult.MatchPercentage = bestScore;
|
||||
}
|
||||
|
||||
@@ -560,13 +652,14 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
|
||||
ISearchResult? result;
|
||||
if (searcher == Searchers.Netease && songInfo.SongId != null)
|
||||
|
||||
if (songInfo.SongId != null && searcher == Searchers.Netease && PlayerIDHelper.IsNeteaseFamily(songInfo.PlayerId))
|
||||
{
|
||||
result = new NeteaseSearchResult("", [], "", [], 0, songInfo.SongId);
|
||||
result = new NeteaseSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId);
|
||||
}
|
||||
else if (searcher == Searchers.QQMusic && songInfo.SongId != null)
|
||||
else if (songInfo.SongId != null && searcher == Searchers.QQMusic && songInfo.PlayerId == Constants.PlayerID.QQMusic)
|
||||
{
|
||||
result = new QQMusicSearchResult("", [], "", [], 0, songInfo.SongId, "");
|
||||
result = new QQMusicSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
@@ -28,6 +29,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
LyricsData? CurrentLyricsData { get; }
|
||||
|
||||
BitmapImage? AlbumArtBitmapImage { get; }
|
||||
IRandomAccessStream? AlbumArtBitmapStream { get; }
|
||||
|
||||
AlbumArtThemeColors CalculateAlbumArtThemeColors(LyricsWindowStatus lyricsWindowStatus, Color backdropAccentColor);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
private BitmapDecoder? _albumArtBitmapDecoder = null;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial BitmapImage? AlbumArtBitmapImage { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial IRandomAccessStream? AlbumArtBitmapStream { get; set; }
|
||||
|
||||
private void UpdateAlbumArt()
|
||||
{
|
||||
@@ -80,6 +81,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
AlbumArtBitmapImage = bitmapImage;
|
||||
AlbumArtBitmapStream = ImageHelper.ToIRandomAccessStream(buffer);
|
||||
}
|
||||
|
||||
public AlbumArtThemeColors CalculateAlbumArtThemeColors(LyricsWindowStatus lyricsWindowStatus, Color backdropAccentColor)
|
||||
|
||||
@@ -40,7 +40,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
(CurrentLyricsData, CurrentLyricsSearchResult.TransliterationProvider, CurrentLyricsSearchResult.TranslationProvider) =
|
||||
await Task.Run(async () => await lyricsParser.Parse(
|
||||
_translateService,
|
||||
_translationService,
|
||||
_transliterationService,
|
||||
_settingsService.AppSettings.TranslationSettings,
|
||||
CurrentLyricsSearchResult,
|
||||
token),
|
||||
|
||||
@@ -14,7 +14,8 @@ using BetterLyrics.WinUI3.Services.DiscordService;
|
||||
using BetterLyrics.WinUI3.Services.LibWatcherService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@@ -48,7 +49,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILyricsSearchService _lyrcsSearchService;
|
||||
private readonly ITranslateService _translateService;
|
||||
private readonly ITranslationService _translationService;
|
||||
private readonly ITransliterationService _transliterationService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly IDiscordService _discordService;
|
||||
@@ -61,7 +63,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool CurrentIsPlaying { get; private set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TimeSpan CurrentPosition { get; private set; } = TimeSpan.Zero;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SongInfo? CurrentSongInfo { get; private set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SongInfo? CurrentSongInfo { get; private set; } = SongInfoExtensions.Placeholder;
|
||||
|
||||
[ObservableProperty] public partial MediaSourceProviderInfo? CurrentMediaSourceProviderInfo { get; set; }
|
||||
|
||||
@@ -71,14 +73,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
ILyricsSearchService lyricsSearchService,
|
||||
ILibWatcherService libWatcherService,
|
||||
IDiscordService discordService,
|
||||
ITranslateService libreTranslateService,
|
||||
ITranslationService libreTranslateService,
|
||||
ITransliterationService transliterationService,
|
||||
ILogger<MediaSessionsService> logger)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_albumArtSearchService = albumArtSearchService;
|
||||
_lyrcsSearchService = lyricsSearchService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_translateService = libreTranslateService;
|
||||
_translationService = libreTranslateService;
|
||||
_transliterationService = transliterationService;
|
||||
_discordService = discordService;
|
||||
_logger = logger;
|
||||
|
||||
@@ -201,28 +205,32 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties? timelineProperties)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null)
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
{
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null)
|
||||
{
|
||||
CurrentPosition = timelineProperties?.Position ?? TimeSpan.Zero;
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
{
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
|
||||
{
|
||||
CurrentPosition = timelineProperties?.Position ?? TimeSpan.Zero;
|
||||
CurrentSongInfo?.DurationMs = timelineProperties?.EndTime.TotalMilliseconds ?? 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo? playbackInfo)
|
||||
@@ -323,9 +331,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
CurrentSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties?.Title ?? "",
|
||||
Artists = fixedArtist?.SplitByCommonSplitter() ?? [],
|
||||
Album = fixedAlbum ?? "",
|
||||
Title = mediaProperties?.Title ?? "N/A",
|
||||
Artists = fixedArtist?.SplitByCommonSplitter() ?? ["N/A"],
|
||||
Album = fixedAlbum ?? "N/A",
|
||||
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds ?? 0,
|
||||
PlayerId = sessionId,
|
||||
SongId = songId,
|
||||
@@ -696,8 +704,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
_logger.LogInformation("Target LibreTranslate language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
|
||||
UpdateLyrics();
|
||||
}
|
||||
else if (message.PropertyName == nameof(TranslationSettings.CutletDockerServer))
|
||||
{
|
||||
UpdateLyrics();
|
||||
}
|
||||
else if (message.PropertyName == nameof(TranslationSettings.LibreTranslateServer))
|
||||
{
|
||||
UpdateLyrics();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ChineseRomanization> message)
|
||||
|
||||
@@ -3,12 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.TranslateService
|
||||
namespace BetterLyrics.WinUI3.Services.TranslationService
|
||||
{
|
||||
public interface ITranslateService
|
||||
public interface ITranslationService
|
||||
{
|
||||
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken token);
|
||||
|
||||
int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr, string targetLangCode);
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.TranslateService
|
||||
namespace BetterLyrics.WinUI3.Services.TranslationService
|
||||
{
|
||||
public partial class TranslateService : BaseViewModel, ITranslateService
|
||||
public partial class TranslationService : BaseViewModel, ITranslationService
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TranslateService(ISettingsService settingsService)
|
||||
public TranslationService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_httpClient = new HttpClient();
|
||||
@@ -51,31 +51,8 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync(token);
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LibreTranslateResponse);
|
||||
return result?.TranslatedText ?? string.Empty;
|
||||
}
|
||||
|
||||
public int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr, string targetLangCode)
|
||||
{
|
||||
int ret = -1;
|
||||
float maxTranslatinRate = 0.0f;
|
||||
|
||||
if (lyricsDataArr.Count > 1)
|
||||
{
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
{
|
||||
if (lyricsDataArr[i].LanguageCode == targetLangCode)
|
||||
{
|
||||
float translationRate = lyricsDataArr[i].LyricsLines.Count / (float)lyricsDataArr[0].LyricsLines.Count;
|
||||
if (translationRate > maxTranslatinRate)
|
||||
{
|
||||
maxTranslatinRate = translationRate;
|
||||
ret = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.TransliterationService
|
||||
{
|
||||
public interface ITransliterationService
|
||||
{
|
||||
Task<string> TransliterateText(string text, string targetLangCode, CancellationToken token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.TransliterationService
|
||||
{
|
||||
public class TransliterationService : ITransliterationService
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TransliterationService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<string> TransliterateText(string text, string targetLangCode, CancellationToken token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new Exception(text + " is empty or null.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_settingsService.AppSettings.TranslationSettings.CutletDockerServer))
|
||||
{
|
||||
throw new Exception("cutlet-docker server URL is not set in settings.");
|
||||
}
|
||||
|
||||
var request = new CutletDockerRequest { Text = text };
|
||||
var reqJson = System.Text.Json.JsonSerializer.Serialize(request, SourceGenerationContext.Default.CutletDockerRequest);
|
||||
|
||||
var url = $"{_settingsService.AppSettings.TranslationSettings.CutletDockerServer}/convert";
|
||||
var response = await _httpClient.PostAsync(url, new StringContent(reqJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var resJson = await response.Content.ReadAsStringAsync(token);
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(resJson, SourceGenerationContext.Default.CutletDockerResponse);
|
||||
return result?.RomajiText ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="ActionCompleted" xml:space="preserve">
|
||||
<value>Operation completed</value>
|
||||
</data>
|
||||
<data name="Add" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>Local music files</value>
|
||||
</data>
|
||||
@@ -150,6 +153,12 @@
|
||||
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
|
||||
<value>Playlist was created successfully</value>
|
||||
</data>
|
||||
<data name="CutletDockerFailed" xml:space="preserve">
|
||||
<value>Failed to request transliteration from cutlet-docker, please check settings or native cutlet-docker configuration</value>
|
||||
</data>
|
||||
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>For example http://localhost:23333</value>
|
||||
</data>
|
||||
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
@@ -237,6 +246,9 @@
|
||||
<data name="LyricsPageCachePath.Value" xml:space="preserve">
|
||||
<value>Cache path</value>
|
||||
</data>
|
||||
<data name="LyricsPageLanguageCode.Header" xml:space="preserve">
|
||||
<value>Lyric language</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>Lyrics provider</value>
|
||||
</data>
|
||||
@@ -537,6 +549,24 @@
|
||||
<data name="PrivacyPolicy.Content" xml:space="preserve">
|
||||
<value>Privacy policy</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPath.Header" xml:space="preserve">
|
||||
<value>Path</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||
<value>Port</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddress.Header" xml:space="preserve">
|
||||
<value>Server Address</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddressRequired" xml:space="preserve">
|
||||
<value>Server address is required</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlUsername.Header" xml:space="preserve">
|
||||
<value>Username</value>
|
||||
</data>
|
||||
<data name="Romaji" xml:space="preserve">
|
||||
<value>Romaji</value>
|
||||
</data>
|
||||
@@ -585,9 +615,6 @@
|
||||
<data name="SettingsPageAdaptEnvColor.Header" xml:space="preserve">
|
||||
<value>Adapt to environmental color</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>Add a folder</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
</data>
|
||||
@@ -630,6 +657,9 @@
|
||||
<data name="SettingsPageAlignment.Header" xml:space="preserve">
|
||||
<value>Alignment</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
|
||||
<value>Always hide unlock button</value>
|
||||
</data>
|
||||
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
|
||||
<value>Base URL</value>
|
||||
</data>
|
||||
@@ -759,6 +789,9 @@
|
||||
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
|
||||
<value>Current lyrics window status</value>
|
||||
</data>
|
||||
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
|
||||
<value>cutlet-docker transliteration service</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>Dark</value>
|
||||
</data>
|
||||
@@ -954,6 +987,9 @@
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>The transliteration of the lyrics will be read first, and if there is no match, the machine transliteration will be requested from the cutlet-docker server</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Japanese annotation</value>
|
||||
</data>
|
||||
@@ -1026,6 +1062,9 @@
|
||||
<data name="SettingsPageListenNewSession.Header" xml:space="preserve">
|
||||
<value>Enable monitoring for new playback sources</value>
|
||||
</data>
|
||||
<data name="SettingsPageLocalFolder.Text" xml:space="preserve">
|
||||
<value>Local Folder</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>Log record</value>
|
||||
</data>
|
||||
@@ -1077,6 +1116,12 @@
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>Extra Light</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Description" xml:space="preserve">
|
||||
<value>Enable fade effect for non-current rows</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Header" xml:space="preserve">
|
||||
<value>Fadeout effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>Current Play Row</value>
|
||||
</data>
|
||||
@@ -1146,6 +1191,12 @@
|
||||
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
|
||||
<value>Font opacity</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Description" xml:space="preserve">
|
||||
<value>Gradually move non-current rows out of view</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Header" xml:space="preserve">
|
||||
<value>Stay out of sight</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
|
||||
<value>Lyrics area height factor</value>
|
||||
</data>
|
||||
@@ -1509,6 +1560,9 @@
|
||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||
<value>Start</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>Stop playing when music library is closed</value>
|
||||
</data>
|
||||
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>Stroke color</value>
|
||||
</data>
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="ActionCompleted" xml:space="preserve">
|
||||
<value>操作が完了しました</value>
|
||||
</data>
|
||||
<data name="Add" xml:space="preserve">
|
||||
<value>追加</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>ローカル音楽ファイル</value>
|
||||
</data>
|
||||
@@ -150,6 +153,12 @@
|
||||
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
|
||||
<value>正常に作成されました</value>
|
||||
</data>
|
||||
<data name="CutletDockerFailed" xml:space="preserve">
|
||||
<value>cutlet - dockerからの音訳のリクエストに失敗しました。設定またはネイティブのcutlet - docker設定を確認してください</value>
|
||||
</data>
|
||||
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>たとえば、http://localhost:23333</value>
|
||||
</data>
|
||||
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
</data>
|
||||
@@ -237,6 +246,9 @@
|
||||
<data name="LyricsPageCachePath.Value" xml:space="preserve">
|
||||
<value>キャッシュパス</value>
|
||||
</data>
|
||||
<data name="LyricsPageLanguageCode.Header" xml:space="preserve">
|
||||
<value>リリックランゲージ</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌詞プロバイダー</value>
|
||||
</data>
|
||||
@@ -537,6 +549,24 @@
|
||||
<data name="PrivacyPolicy.Content" xml:space="preserve">
|
||||
<value>プライバシーポリシー</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||
<value>パスワード</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPath.Header" xml:space="preserve">
|
||||
<value>パス</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||
<value>ポート</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddress.Header" xml:space="preserve">
|
||||
<value>サーバーアドレス</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddressRequired" xml:space="preserve">
|
||||
<value>住所は必須です</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlUsername.Header" xml:space="preserve">
|
||||
<value>ユーザー名</value>
|
||||
</data>
|
||||
<data name="Romaji" xml:space="preserve">
|
||||
<value>ローマン</value>
|
||||
</data>
|
||||
@@ -585,9 +615,6 @@
|
||||
<data name="SettingsPageAdaptEnvColor.Header" xml:space="preserve">
|
||||
<value>環境の色に適応しましょう</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>フォルダーを追加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>追加</value>
|
||||
</data>
|
||||
@@ -630,6 +657,9 @@
|
||||
<data name="SettingsPageAlignment.Header" xml:space="preserve">
|
||||
<value>アライメント</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
|
||||
<value>ロック解除ボタンは常に隠しておきましょう</value>
|
||||
</data>
|
||||
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
|
||||
<value>ベースURL</value>
|
||||
</data>
|
||||
@@ -759,6 +789,9 @@
|
||||
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
|
||||
<value>現在の歌詞ウィンドウステータス</value>
|
||||
</data>
|
||||
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
|
||||
<value>cutlet-docker 音声翻訳サービス</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>暗い</value>
|
||||
</data>
|
||||
@@ -954,6 +987,9 @@
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>歌詞の音訳が最初に読み込まれ、一致しない場合は機械音訳がcutlet - dockerサーバーから要求されます</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>日本の注釈</value>
|
||||
</data>
|
||||
@@ -1026,6 +1062,9 @@
|
||||
<data name="SettingsPageListenNewSession.Header" xml:space="preserve">
|
||||
<value>新しい再生ソースの監視を有効にします</value>
|
||||
</data>
|
||||
<data name="SettingsPageLocalFolder.Text" xml:space="preserve">
|
||||
<value>ローカル フォルダ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>ログレコード</value>
|
||||
</data>
|
||||
@@ -1036,7 +1075,7 @@
|
||||
<value>LX Music Server</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServerInput.PlaceholderText" xml:space="preserve">
|
||||
<value>例)http://127.0.0.1: 23330</value>
|
||||
<value>例)http://127.0.0.1:23330</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>アライメント</value>
|
||||
@@ -1077,6 +1116,12 @@
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>余分な光</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Description" xml:space="preserve">
|
||||
<value>非現在の行にフェード効果を有効にします</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Header" xml:space="preserve">
|
||||
<value>フェード効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>現在のプレイ行</value>
|
||||
</data>
|
||||
@@ -1146,6 +1191,12 @@
|
||||
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
|
||||
<value>フォントの不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Description" xml:space="preserve">
|
||||
<value>現在ではない行を徐々に外に移動してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Header" xml:space="preserve">
|
||||
<value>目につかないように</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
|
||||
<value>歌詞エリアの高さ係数</value>
|
||||
</data>
|
||||
@@ -1509,6 +1560,9 @@
|
||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||
<value>起動</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>音楽ライブラリが閉じられたら、再生を停止してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>ストロークカラー</value>
|
||||
</data>
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="ActionCompleted" xml:space="preserve">
|
||||
<value>작전 완료</value>
|
||||
</data>
|
||||
<data name="Add" xml:space="preserve">
|
||||
<value>추가하다</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>로컬 음악 파일</value>
|
||||
</data>
|
||||
@@ -150,6 +153,12 @@
|
||||
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
|
||||
<value>재생 목록이 성공적으로 생성되었습니다</value>
|
||||
</data>
|
||||
<data name="CutletDockerFailed" xml:space="preserve">
|
||||
<value>커틀릿 도커에서 음역을 요청하지 못했습니다. 설정 또는 기본 커틀릿 도커 구성을 확인하십시오</value>
|
||||
</data>
|
||||
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>예: http://localhost:23333</value>
|
||||
</data>
|
||||
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
</data>
|
||||
@@ -237,6 +246,9 @@
|
||||
<data name="LyricsPageCachePath.Value" xml:space="preserve">
|
||||
<value>캐시</value>
|
||||
</data>
|
||||
<data name="LyricsPageLanguageCode.Header" xml:space="preserve">
|
||||
<value>가사 언어</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>가사 제공자</value>
|
||||
</data>
|
||||
@@ -537,6 +549,24 @@
|
||||
<data name="PrivacyPolicy.Content" xml:space="preserve">
|
||||
<value>개인정보 보호정책</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||
<value>비밀번호</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPath.Header" xml:space="preserve">
|
||||
<value>길</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||
<value>항구</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddress.Header" xml:space="preserve">
|
||||
<value>IP 주소</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddressRequired" xml:space="preserve">
|
||||
<value>서버 주소가 필요합니다</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlUsername.Header" xml:space="preserve">
|
||||
<value>사용자 이름</value>
|
||||
</data>
|
||||
<data name="Romaji" xml:space="preserve">
|
||||
<value>로만</value>
|
||||
</data>
|
||||
@@ -585,9 +615,6 @@
|
||||
<data name="SettingsPageAdaptEnvColor.Header" xml:space="preserve">
|
||||
<value>환경 색상에 적응하세요</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>폴더를 추가하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>추가하다</value>
|
||||
</data>
|
||||
@@ -630,6 +657,9 @@
|
||||
<data name="SettingsPageAlignment.Header" xml:space="preserve">
|
||||
<value>조정</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
|
||||
<value>항상 잠금 해제 버튼을 숨깁니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
|
||||
<value>기본 URL</value>
|
||||
</data>
|
||||
@@ -759,6 +789,9 @@
|
||||
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
|
||||
<value>현재 가사 창 상태</value>
|
||||
</data>
|
||||
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
|
||||
<value>커틀릿 도커 음역 서비스</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>어두운</value>
|
||||
</data>
|
||||
@@ -954,6 +987,9 @@
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>가사의 음역이 먼저 읽히고 일치하지 않으면 커틀릿 도커 서버에서 기계 음역이 요청됩니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>일본 주석</value>
|
||||
</data>
|
||||
@@ -1026,6 +1062,9 @@
|
||||
<data name="SettingsPageListenNewSession.Header" xml:space="preserve">
|
||||
<value>새로운 재생 소스에 대한 모니터링을 활성화하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLocalFolder.Text" xml:space="preserve">
|
||||
<value>로컬 폴더</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>로그 레코드</value>
|
||||
</data>
|
||||
@@ -1036,7 +1075,7 @@
|
||||
<value>LX 음악 서버</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServerInput.PlaceholderText" xml:space="preserve">
|
||||
<value>예: http://127.0.0.1: 23330</value>
|
||||
<value>예: http://127.0.0.1:23330</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>조정</value>
|
||||
@@ -1077,6 +1116,12 @@
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>여분의 빛</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Description" xml:space="preserve">
|
||||
<value>비전류 행에 페이드 효과 활성화</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Header" xml:space="preserve">
|
||||
<value>페이드 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>현재 플레이 행</value>
|
||||
</data>
|
||||
@@ -1146,6 +1191,12 @@
|
||||
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
|
||||
<value>글꼴 불투명도</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Description" xml:space="preserve">
|
||||
<value>현재 행이 아닌 행을 점진적으로 보이지 않게 이동합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Header" xml:space="preserve">
|
||||
<value>눈에 띄지 마세요</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
|
||||
<value>가사 영역 높이 계수</value>
|
||||
</data>
|
||||
@@ -1509,6 +1560,9 @@
|
||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||
<value>시작</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>음악 라이브러리가 닫히면 재생을 중지하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>윤곽선 색상</value>
|
||||
</data>
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="ActionCompleted" xml:space="preserve">
|
||||
<value>操作完成</value>
|
||||
</data>
|
||||
<data name="Add" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>本地音乐文件</value>
|
||||
</data>
|
||||
@@ -150,6 +153,12 @@
|
||||
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
|
||||
<value>播放列表创建成功</value>
|
||||
</data>
|
||||
<data name="CutletDockerFailed" xml:space="preserve">
|
||||
<value>向 cutlet-docker 请求音译失败,请检查设置或本机 cutlet-docker 配置</value>
|
||||
</data>
|
||||
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>例如 http://localhost:23333</value>
|
||||
</data>
|
||||
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
|
||||
<value>默认</value>
|
||||
</data>
|
||||
@@ -237,6 +246,9 @@
|
||||
<data name="LyricsPageCachePath.Value" xml:space="preserve">
|
||||
<value>缓存路径</value>
|
||||
</data>
|
||||
<data name="LyricsPageLanguageCode.Header" xml:space="preserve">
|
||||
<value>歌词语言</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌词来源</value>
|
||||
</data>
|
||||
@@ -537,6 +549,24 @@
|
||||
<data name="PrivacyPolicy.Content" xml:space="preserve">
|
||||
<value>隐私政策</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||
<value>密码</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPath.Header" xml:space="preserve">
|
||||
<value>路径</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||
<value>端口</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddress.Header" xml:space="preserve">
|
||||
<value>服务器地址</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddressRequired" xml:space="preserve">
|
||||
<value>服务器地址为必填项</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlUsername.Header" xml:space="preserve">
|
||||
<value>用户名</value>
|
||||
</data>
|
||||
<data name="Romaji" xml:space="preserve">
|
||||
<value>罗马音</value>
|
||||
</data>
|
||||
@@ -585,9 +615,6 @@
|
||||
<data name="SettingsPageAdaptEnvColor.Header" xml:space="preserve">
|
||||
<value>适应环境色彩</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>添加文件夹</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
</data>
|
||||
@@ -630,6 +657,9 @@
|
||||
<data name="SettingsPageAlignment.Header" xml:space="preserve">
|
||||
<value>对齐方式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
|
||||
<value>总是隐藏解锁按钮</value>
|
||||
</data>
|
||||
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
|
||||
<value>基本 URL</value>
|
||||
</data>
|
||||
@@ -759,6 +789,9 @@
|
||||
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
|
||||
<value>当前歌词窗口状态</value>
|
||||
</data>
|
||||
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
|
||||
<value>cutlet-docker 音译服务</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>深色</value>
|
||||
</data>
|
||||
@@ -954,6 +987,9 @@
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>将优先读取歌词内音译,若无匹配则向 cutlet-docker 服务器请求机器音译</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>日语注音</value>
|
||||
</data>
|
||||
@@ -1026,6 +1062,9 @@
|
||||
<data name="SettingsPageListenNewSession.Header" xml:space="preserve">
|
||||
<value>启用对新播放源的监听</value>
|
||||
</data>
|
||||
<data name="SettingsPageLocalFolder.Text" xml:space="preserve">
|
||||
<value>本地文件夹</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>日志记录</value>
|
||||
</data>
|
||||
@@ -1036,7 +1075,7 @@
|
||||
<value>LX 音乐服务器</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServerInput.PlaceholderText" xml:space="preserve">
|
||||
<value>例如 http://127.0.0.1: 23330</value>
|
||||
<value>例如 http://127.0.0.1:23330</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>对齐方式</value>
|
||||
@@ -1077,6 +1116,12 @@
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>超细</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Description" xml:space="preserve">
|
||||
<value>为非当前行启用淡出效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Header" xml:space="preserve">
|
||||
<value>淡出效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>当前播放行</value>
|
||||
</data>
|
||||
@@ -1146,6 +1191,12 @@
|
||||
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
|
||||
<value>字体不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Description" xml:space="preserve">
|
||||
<value>使非当前行逐渐远离视野</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Header" xml:space="preserve">
|
||||
<value>远离视野</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
|
||||
<value>歌词区域高度因子</value>
|
||||
</data>
|
||||
@@ -1509,6 +1560,9 @@
|
||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||
<value>启动</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>关闭音乐库时停止播放</value>
|
||||
</data>
|
||||
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>描边颜色</value>
|
||||
</data>
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="ActionCompleted" xml:space="preserve">
|
||||
<value>操作完成</value>
|
||||
</data>
|
||||
<data name="Add" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>本地音樂文件</value>
|
||||
</data>
|
||||
@@ -150,6 +153,12 @@
|
||||
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
|
||||
<value>已成功建立播放清單</value>
|
||||
</data>
|
||||
<data name="CutletDockerFailed" xml:space="preserve">
|
||||
<value>向 cutlet-docker 請求音譯失敗,請檢查設定或本機 cutlet-docker 設定</value>
|
||||
</data>
|
||||
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
|
||||
<value>例如 http://localhost:23333</value>
|
||||
</data>
|
||||
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
|
||||
<value>預設</value>
|
||||
</data>
|
||||
@@ -237,6 +246,9 @@
|
||||
<data name="LyricsPageCachePath.Value" xml:space="preserve">
|
||||
<value>檔案路徑緩存</value>
|
||||
</data>
|
||||
<data name="LyricsPageLanguageCode.Header" xml:space="preserve">
|
||||
<value>歌詞語言</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌詞來源</value>
|
||||
</data>
|
||||
@@ -537,6 +549,24 @@
|
||||
<data name="PrivacyPolicy.Content" xml:space="preserve">
|
||||
<value>隱私政策</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||
<value>密碼</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPath.Header" xml:space="preserve">
|
||||
<value>路徑</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||
<value>連接埠</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddress.Header" xml:space="preserve">
|
||||
<value>伺服器位址</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlServerAddressRequired" xml:space="preserve">
|
||||
<value>伺服器位址為必填</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlUsername.Header" xml:space="preserve">
|
||||
<value>使用者名稱</value>
|
||||
</data>
|
||||
<data name="Romaji" xml:space="preserve">
|
||||
<value>羅馬音</value>
|
||||
</data>
|
||||
@@ -585,9 +615,6 @@
|
||||
<data name="SettingsPageAdaptEnvColor.Header" xml:space="preserve">
|
||||
<value>適應環境色彩</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>新增資料夾</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
</data>
|
||||
@@ -630,6 +657,9 @@
|
||||
<data name="SettingsPageAlignment.Header" xml:space="preserve">
|
||||
<value>對齊方式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
|
||||
<value>總是隱藏解鎖按鈕</value>
|
||||
</data>
|
||||
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
|
||||
<value>基本網址</value>
|
||||
</data>
|
||||
@@ -759,6 +789,9 @@
|
||||
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
|
||||
<value>當前歌詞窗口狀態</value>
|
||||
</data>
|
||||
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
|
||||
<value>cutlet-docker 音譯服務</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>深色</value>
|
||||
</data>
|
||||
@@ -954,6 +987,9 @@
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>將優先讀取歌詞內音譯,若無匹配則向 cutlet-docker 伺服器請求機器音譯</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>日語注音</value>
|
||||
</data>
|
||||
@@ -1026,6 +1062,9 @@
|
||||
<data name="SettingsPageListenNewSession.Header" xml:space="preserve">
|
||||
<value>啟用對新播放來源的監聽</value>
|
||||
</data>
|
||||
<data name="SettingsPageLocalFolder.Text" xml:space="preserve">
|
||||
<value>本地資料夾</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>日誌記錄</value>
|
||||
</data>
|
||||
@@ -1036,7 +1075,7 @@
|
||||
<value>LX 音樂服務器</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServerInput.PlaceholderText" xml:space="preserve">
|
||||
<value>例如 http://127.0.0.1: 23330</value>
|
||||
<value>例如 http://127.0.0.1:23330</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>對齊方式</value>
|
||||
@@ -1077,6 +1116,12 @@
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>超細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Description" xml:space="preserve">
|
||||
<value>為非目前行啟用淡出效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFadeOutEffect.Header" xml:space="preserve">
|
||||
<value>淡出效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
|
||||
<value>目前播放行</value>
|
||||
</data>
|
||||
@@ -1146,6 +1191,12 @@
|
||||
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
|
||||
<value>字體不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Description" xml:space="preserve">
|
||||
<value>使非當前行逐漸遠離視野</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsOutOfSightEffect.Header" xml:space="preserve">
|
||||
<value>遠離視野</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
|
||||
<value>歌詞區域高度因子</value>
|
||||
</data>
|
||||
@@ -1509,6 +1560,9 @@
|
||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||
<value>啟動</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>關閉音樂庫時停止播放</value>
|
||||
</data>
|
||||
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>描邊顏色</value>
|
||||
</data>
|
||||
|
||||
@@ -37,9 +37,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LyricsData>? LyricsDataArr { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsLine? SelectedLyricsLine { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial MappedSongSearchQuery? MappedSongSearchQuery { get; set; }
|
||||
|
||||
@@ -99,6 +96,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
return found;
|
||||
}
|
||||
|
||||
public void PlayLyricsLine(LyricsLine? value)
|
||||
{
|
||||
if (value?.StartMs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_mediaSessionsService.ChangePosition(value.StartMs / 1000.0);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Search()
|
||||
{
|
||||
@@ -188,15 +194,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedLyricsLineChanged(LyricsLine? value)
|
||||
{
|
||||
if (value?.StartMs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_mediaSessionsService.ChangePosition(value.StartMs / 1000.0);
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Controls;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
@@ -12,6 +15,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -30,17 +34,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectAndAddFolderAsync(UIElement sender)
|
||||
{
|
||||
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
AddFolderAsync(folder.Path);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFolderAsync(string path)
|
||||
{
|
||||
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
@@ -62,13 +55,93 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
AppSettings.LocalMediaFolders.Add(new LocalMediaFolder(path));
|
||||
AppSettings.LocalMediaFolders.Add(new MediaFolder(path));
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFolderAsync(LocalMediaFolder folder)
|
||||
public void RemoveFolderAsync(MediaFolder folder)
|
||||
{
|
||||
AppSettings.LocalMediaFolders.Remove(folder);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectAndAddFolderAsync(UIElement sender)
|
||||
{
|
||||
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
AddFolderAsync(folder.Path);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddRemoteSourceAsync(string protocolType)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = WindowHook.GetWindow<SettingsWindow>()?.Content.XamlRoot,
|
||||
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
|
||||
Title = protocolType,
|
||||
PrimaryButtonText = _resourceService.GetLocalizedString("Add"),
|
||||
CloseButtonText = _resourceService.GetLocalizedString("Cancel"),
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
Content = new RemoteServerConfigControl(protocolType)
|
||||
};
|
||||
|
||||
dialog.PrimaryButtonClick += async (s, e) =>
|
||||
{
|
||||
var configControl = (RemoteServerConfigControl)dialog.Content;
|
||||
|
||||
try
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
dialog.IsPrimaryButtonEnabled = false;
|
||||
configControl.IsEnabled = false;
|
||||
configControl.SetProgressBarVisibility(Visibility.Visible);
|
||||
|
||||
var tempFolder = configControl.GetConfig();
|
||||
|
||||
var provider = tempFolder.CreateFileSystem();
|
||||
|
||||
bool isConnected = provider != null && await provider.ConnectAsync();
|
||||
|
||||
if (isConnected)
|
||||
{
|
||||
await provider!.DisconnectAsync();
|
||||
|
||||
PasswordVaultHelper.Save(Constants.App.AppName, tempFolder.VaultKey, tempFolder.Password);
|
||||
AppSettings.LocalMediaFolders.Add(tempFolder);
|
||||
|
||||
e.Cancel = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowErrorTip(configControl, _resourceService.GetLocalizedString("SettingsPageServerTestFailedInfo"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowErrorTip(configControl, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dialog.IsPrimaryButtonEnabled = true;
|
||||
configControl.IsEnabled = true;
|
||||
configControl.SetProgressBarVisibility(Visibility.Collapsed);
|
||||
}
|
||||
};
|
||||
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void ShowErrorTip(RemoteServerConfigControl control, string message)
|
||||
{
|
||||
// 你可以在 RemoteServerConfigControl 里加一个 InfoBar 用来显示错误
|
||||
// 假设你在 UserControl 里公开了一个 ShowError 方法
|
||||
control.ShowError(message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private readonly DispatcherQueueTimer _refreshSongsTimer;
|
||||
|
||||
// All songs
|
||||
private List<Track> _tracks = [];
|
||||
private List<ExtendedTrack> _tracks = [];
|
||||
// Songs in current playlist
|
||||
private List<Track> _playlistTracks = [];
|
||||
private List<ExtendedTrack> _playlistTracks = [];
|
||||
// Filtered songs based on search query for current playlist
|
||||
private List<Track> _filteredTracks = [];
|
||||
private List<ExtendedTrack> _filteredTracks = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AppSettings AppSettings { get; set; }
|
||||
@@ -62,7 +62,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial ObservableCollection<GroupInfoList> GroupedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<Track> SelectedTracks { get; set; } = [];
|
||||
public partial List<ExtendedTrack> SelectedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedTracksTotalDuration { get; set; } = 0;
|
||||
@@ -73,7 +73,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public PlayQueueItem? PlayingQueueItem => TrackPlayingQueue.ElementAtOrDefault(AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Track? PlayingTrack { get; set; } = null;
|
||||
public partial ExtendedTrack? PlayingTrack { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial CommonSongProperty SongOrderType { get; set; } = CommonSongProperty.Title;
|
||||
@@ -90,7 +90,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial bool IsDataLoading { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Track TrackRightTapped { get; set; } = new();
|
||||
public partial ExtendedTrack TrackRightTapped { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string SongSearchQuery { get; set; } = string.Empty;
|
||||
@@ -103,7 +103,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_resourceService = resourceService;
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
TrackPlayingQueue = [.. AppSettings.MusicGallerySettings.PlayQueuePaths.Select(x => new PlayQueueItem(new Track(x)))];
|
||||
TrackPlayingQueue = [.. AppSettings.MusicGallerySettings.PlayQueuePaths.Select(x => new PlayQueueItem(new ExtendedTrack(x)))];
|
||||
TrackPlayingQueue.CollectionChanged += TrackPlayingQueue_CollectionChanged;
|
||||
|
||||
SongsTabInfoList.Add(new SongsTabInfo(_resourceService.GetLocalizedString("MusicGalleryPageAllSongs"), "\uE8A9", false, false, CommonSongProperty.Title, string.Empty));
|
||||
@@ -281,28 +281,75 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IsDataLoading = true;
|
||||
_tracks.Clear();
|
||||
|
||||
Task.Run(() =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
if (!folder.IsEnabled) continue;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path))
|
||||
using var fs = folder.CreateFileSystem();
|
||||
|
||||
if (fs == null) continue;
|
||||
|
||||
if (!await fs.ConnectAsync()) continue;
|
||||
|
||||
// 递归扫描队列
|
||||
var foldersToScan = new Queue<string>();
|
||||
foldersToScan.Enqueue(""); // 从根目录开始
|
||||
|
||||
while (foldersToScan.Count > 0)
|
||||
{
|
||||
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
|
||||
var currentPath = foldersToScan.Dequeue();
|
||||
var items = await fs.GetFilesAsync(currentPath);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
Track track = new(file);
|
||||
if (track.Duration <= 0) continue;
|
||||
_tracks.Add(track);
|
||||
if (item.IsFolder)
|
||||
{
|
||||
foldersToScan.Enqueue(Path.Combine(currentPath, item.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(item.Name).ToLower();
|
||||
if (FileHelper.MusicExtensions.Contains(ext))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = await fs.OpenReadAsync(item.FullPath))
|
||||
{
|
||||
ExtendedTrack track = new ExtendedTrack(item.FullPath, stream);
|
||||
|
||||
if (track.Duration > 0)
|
||||
{
|
||||
// 读取专辑图写入内存
|
||||
// 因为此后该流将关闭无法再次访问
|
||||
_ = track.EmbeddedPictures;
|
||||
|
||||
_tracks.Add(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error loading track {item.Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Folder scan error ({folder.Name}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Global scan error: {ex.Message}");
|
||||
}
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
@@ -337,7 +384,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_playlistTracks = _tracks.Where(t => t.Artist.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
case CommonSongProperty.Folder:
|
||||
_playlistTracks = _tracks.Where(t => t.GetParentFolderPath().Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
_playlistTracks = _tracks.Where(t => t.ParentFolderPath.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
case CommonSongProperty.M3UFilePath:
|
||||
if (SelectedSongsTabInfo.FilterValue is string path)
|
||||
@@ -375,9 +422,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
t.Artist.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Album.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
// 文件名(包含后缀)
|
||||
t.GetFileName().Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.FileName.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
// 文件所在文件夹的路径
|
||||
t.GetParentFolderPath().Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
t.ParentFolderPath.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
private void ApplySongOrderType()
|
||||
@@ -387,25 +434,25 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
case CommonSongProperty.Title:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Title),
|
||||
o => ((Track)o).Title
|
||||
o => ((ExtendedTrack)o).Title
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Artist:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Artist),
|
||||
o => ((Track)o).Artist
|
||||
o => ((ExtendedTrack)o).Artist
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Album:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Album),
|
||||
o => ((Track)o).Album
|
||||
o => ((ExtendedTrack)o).Album
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Folder:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.GetParentFolderName()),
|
||||
o => ((Track)o).Album
|
||||
t => LanguageHelper.GetOrderChar(t.ParentFolderName),
|
||||
o => ((ExtendedTrack)o).Album
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
|
||||
@@ -5,7 +5,8 @@ using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Hqub.Lastfm.Entities;
|
||||
@@ -20,10 +21,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial class PlaybackSettingsControlViewModel : BaseViewModel
|
||||
{
|
||||
public IMediaSessionsService MediaSessionsService;
|
||||
private readonly ITranslateService _libreTranslateService;
|
||||
private readonly ITranslationService _translationService;
|
||||
private readonly ILastFMService _lastFMService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IResourceService _resourceService;
|
||||
private readonly ITransliterationService _transliterationService;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AppSettings AppSettings { get; set; }
|
||||
@@ -40,6 +41,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial bool IsLibreTranslateServerTesting { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsCutletDockerServerTesting { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLXMusicServerTesting { get; set; } = false;
|
||||
|
||||
@@ -52,15 +56,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public PlaybackSettingsControlViewModel(
|
||||
ISettingsService settingsService,
|
||||
IMediaSessionsService mediaSessionsService,
|
||||
ITranslateService libreTranslateService,
|
||||
ITranslationService libreTranslationService,
|
||||
ILastFMService lastFMService,
|
||||
IResourceService resourceService)
|
||||
ITransliterationService transliterationService)
|
||||
{
|
||||
MediaSessionsService = mediaSessionsService;
|
||||
|
||||
_settingsService = settingsService;
|
||||
_libreTranslateService = libreTranslateService;
|
||||
_resourceService = resourceService;
|
||||
_translationService = libreTranslationService;
|
||||
_transliterationService = transliterationService;
|
||||
|
||||
_lastFMService = lastFMService;
|
||||
_lastFMService.UserChanged += LastFMService_UserChanged;
|
||||
@@ -107,7 +111,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
string result = await _libreTranslateService.TranslateTextAsync(
|
||||
string result = await _translationService.TranslateTextAsync(
|
||||
"Hello, world!", AppSettings.TranslationSettings.SelectedTargetLanguageCode, new System.Threading.CancellationToken());
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
@@ -128,6 +132,35 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CutletDockerServerTest()
|
||||
{
|
||||
IsCutletDockerServerTesting = true;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string result = await _transliterationService.TransliterateText(
|
||||
"こんにちは", PhoneticHelper.RomanCode, new System.Threading.CancellationToken());
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
ToastHelper.ShowToast("SettingsPageServerTestSuccessInfo", null, InfoBarSeverity.Success);
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
ToastHelper.ShowToast("SettingsPageServerTestFailedInfo", null, InfoBarSeverity.Error);
|
||||
});
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
IsCutletDockerServerTesting = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LastFMAuthAsync()
|
||||
{
|
||||
@@ -171,6 +204,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private void SaveAppleMusicMediaUserToken()
|
||||
{
|
||||
PasswordVaultHelper.Delete(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey);
|
||||
PasswordVaultHelper.Save(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey, AppleMusicMediaUserToken);
|
||||
MediaSessionsService.UpdateLyrics();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.Init("LyricsSearchPageTitle", backdropType: BackdropType.Transparent);
|
||||
this.Init("LyricsSearchPageTitle");
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
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}" />
|
||||
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoLyrics" Value="{x:Bind ViewModel.TrackRightTapped.RawLyrics, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
@@ -381,7 +381,7 @@
|
||||
</MenuBarItemFlyout>
|
||||
</ListView.ContextFlyout>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="atl:Track">
|
||||
<DataTemplate x:DataType="models:ExtendedTrack">
|
||||
<Grid
|
||||
Padding="12"
|
||||
ColumnSpacing="12"
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private async void SongPathHyperlinkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LauncherHelper.SelectAndShowFile(((Track)((HyperlinkButton)sender).DataContext).Path);
|
||||
await LauncherHelper.SelectAndShowFile(((ExtendedTrack)((HyperlinkButton)sender).DataContext).Path);
|
||||
}
|
||||
|
||||
private async void PlayingQueueListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
@@ -104,7 +104,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private async void AddSongToQueueNextMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool startPlaying = ViewModel.TrackPlayingQueue.Count == 0;
|
||||
ViewModel.TrackPlayingQueue.InsertRange(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1, SongListView.SelectedItems.Cast<Track>().Select(x => new PlayQueueItem(x)));
|
||||
ViewModel.TrackPlayingQueue.InsertRange(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1, SongListView.SelectedItems.Cast<ExtendedTrack>().Select(x => new PlayQueueItem(x)));
|
||||
if (startPlaying)
|
||||
{
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1;
|
||||
@@ -115,7 +115,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private async void AddSongToQueueEndMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool startPlaying = ViewModel.TrackPlayingQueue.Count == 0;
|
||||
ViewModel.TrackPlayingQueue.AddRange(SongListView.SelectedItems.Cast<Track>().Select(x => new PlayQueueItem(x)));
|
||||
ViewModel.TrackPlayingQueue.AddRange(SongListView.SelectedItems.Cast<ExtendedTrack>().Select(x => new PlayQueueItem(x)));
|
||||
if (startPlaying)
|
||||
{
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1;
|
||||
@@ -125,7 +125,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void SongListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
ViewModel.SelectedTracks = SongListView.SelectedItems.Cast<Track>().ToList();
|
||||
ViewModel.SelectedTracks = SongListView.SelectedItems.Cast<ExtendedTrack>().ToList();
|
||||
ViewModel.SelectedTracksTotalDuration = ViewModel.SelectedTracks.Select(x => x.Duration).Sum();
|
||||
if (SelectAllCheckBox != null)
|
||||
{
|
||||
@@ -142,22 +142,22 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void ArtistHyperlibkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var artist = ((Track)((FrameworkElement)sender).DataContext).Artist;
|
||||
var artist = ((ExtendedTrack)((FrameworkElement)sender).DataContext).Artist;
|
||||
var playlist = new SongsTabInfo(artist, "\uEFA9", true, false, CommonSongProperty.Artist, artist);
|
||||
ViewModel.UpdateSelectedPlaylist(playlist);
|
||||
}
|
||||
|
||||
private void AlbumHyperlibkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var album = ((Track)((FrameworkElement)sender).DataContext).Album;
|
||||
var album = ((ExtendedTrack)((FrameworkElement)sender).DataContext).Album;
|
||||
var playlist = new SongsTabInfo(album, "\uE93C", true, false, CommonSongProperty.Album, album);
|
||||
ViewModel.UpdateSelectedPlaylist(playlist);
|
||||
}
|
||||
|
||||
private void PathHyperlibkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var track = ((Track)((FrameworkElement)sender).DataContext);
|
||||
var playlist = new SongsTabInfo(track.GetParentFolderName(), "\uE8B7", true, false, CommonSongProperty.Folder, track.GetParentFolderPath());
|
||||
var track = ((ExtendedTrack)((FrameworkElement)sender).DataContext);
|
||||
var playlist = new SongsTabInfo(track.ParentFolderName, "\uE8B7", true, false, CommonSongProperty.Folder, track.ParentFolderPath);
|
||||
ViewModel.UpdateSelectedPlaylist(playlist);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private void Page_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.CancelRefreshSongs();
|
||||
ViewModel.StopTrackCommand.Execute(null);
|
||||
if (ViewModel.AppSettings.MusicGallerySettings.StopOnWindowClosed)
|
||||
{
|
||||
ViewModel.StopTrackCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaylistFavButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -207,7 +210,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void SongListViewItemMoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TrackRightTapped = (Track)((FrameworkElement)sender).DataContext;
|
||||
ViewModel.TrackRightTapped = (ExtendedTrack)((FrameworkElement)sender).DataContext;
|
||||
SongFileInfoFlyout.ShowAt(sender as FrameworkElement);
|
||||
}
|
||||
|
||||
@@ -257,8 +260,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private async void SongListViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
var displayedTracks = SongListView.Items.Cast<Track>();
|
||||
var track = (Track)((FrameworkElement)sender).DataContext;
|
||||
var displayedTracks = SongListView.Items.Cast<ExtendedTrack>();
|
||||
var track = (ExtendedTrack)((FrameworkElement)sender).DataContext;
|
||||
|
||||
// Play all the songs
|
||||
ViewModel.TrackPlayingQueue.Clear();
|
||||
|
||||
@@ -74,6 +74,28 @@
|
||||
ShadowAmount="{x:Bind LyricsWindowStatus.AlbumArtLayoutSettings.CoverImageShadowAmount, Mode=OneWay}"
|
||||
Source="{x:Bind ViewModel.MediaSessionsService.AlbumArtBitmapImage, Mode=OneWay}"
|
||||
SwitchType="{x:Bind LyricsWindowStatus.AlbumArtAreaEffectSettings.ImageSwitchType, Mode=OneWay}" />
|
||||
<Grid.ContextFlyout>
|
||||
<Flyout FlyoutPresenterStyle="{StaticResource FlyoutGhostStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel Margin="16,8,16,4">
|
||||
<uc:PropertyRow
|
||||
x:Uid="SettingsPageWidth"
|
||||
Unit="px"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.AlbumArtBitmapImage.PixelWidth, Mode=OneWay}" />
|
||||
<uc:PropertyRow
|
||||
x:Uid="SettingsPageHeight"
|
||||
Unit="px"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.AlbumArtBitmapImage.PixelHeight, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="SaveAlbumArtButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Grid.ContextFlyout>
|
||||
</Grid>
|
||||
|
||||
<!-- Song info -->
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using BetterLyrics.WinUI3.Controls;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
@@ -17,7 +18,11 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
@@ -652,5 +657,34 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private async void SaveAlbumArtButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var sourceStream = ViewModel.MediaSessionsService.AlbumArtBitmapStream;
|
||||
if (sourceStream == null) return;
|
||||
|
||||
var window = WindowHook.GetWindows<NowPlayingWindow>().FirstOrDefault(x => x.LyricsWindowStatus == LyricsWindowStatus);
|
||||
if (window == null) return;
|
||||
|
||||
IDictionary<string, IList<string>> fileTypeChoices = new Dictionary<string, IList<string>>()
|
||||
{
|
||||
{ "PNG", new List<string>() { ".png" } },
|
||||
{ "JPEG", new List<string>() { ".jpg", ".jpeg" } }
|
||||
};
|
||||
|
||||
var file = await PickerHelper.PickSaveFileAsync(window, fileTypeChoices);
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
using (IRandomAccessStream destStream = await file.OpenAsync(FileAccessMode.ReadWrite))
|
||||
{
|
||||
sourceStream.Seek(0);
|
||||
await RandomAccessStream.CopyAsync(sourceStream, destStream);
|
||||
await destStream.FlushAsync();
|
||||
|
||||
ToastHelper.ShowToast("ActionCompleted", null, InfoBarSeverity.Success);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dev="using:DevWinUI"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media"
|
||||
@@ -28,7 +29,7 @@
|
||||
<Grid
|
||||
x:Name="TopCommandGrid"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
|
||||
Opacity="{x:Bind ViewModel.TopCommandGridOpacity, Mode=OneWay}"
|
||||
PointerEntered="TopCommandGrid_PointerEntered"
|
||||
PointerExited="TopCommandGrid_PointerExited">
|
||||
|
||||
@@ -87,6 +87,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
OnAutoShowOrHideWindowChanged();
|
||||
OnTitleBarAreaChanged();
|
||||
OnIsPinToTaskbarChanged();
|
||||
OnIsAlwaysHideUnlockButtonChanged();
|
||||
|
||||
LyricsWindowStatus.UpdateDemoWindowAndMonitorBounds();
|
||||
}
|
||||
@@ -200,6 +201,11 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIsAlwaysHideUnlockButtonChanged()
|
||||
{
|
||||
UnlockButton.Visibility = LyricsWindowStatus.IsAlwaysHideUnlockButton ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
private void OnIsFullscreenChanged()
|
||||
{
|
||||
if (this.SetIsFullscreen(LyricsWindowStatus.IsFullscreen))
|
||||
@@ -504,6 +510,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
OnIsPinToTaskbarChanged();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowStatus.IsAlwaysHideUnlockButton))
|
||||
{
|
||||
OnIsAlwaysHideUnlockButtonChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user