mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
Compare commits
4 Commits
3724ef5f7e
...
2cac55b55e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cac55b55e | ||
|
|
4efe897ab2 | ||
|
|
173f808a88 | ||
|
|
e73c27c705 |
@@ -1,9 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,101 +1,108 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="customizeDict.txt">
|
||||
<PackagePath>contentFiles\any\any\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\AUTHORS">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\BSD">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\ChangeLog">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\char.bin">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\COPYING">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\dicrc">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\GPL">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\INSTALL">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\LGPL">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\matrix.bin">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\sys.dic">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\unidic-mecab.pdf">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\unk.dic">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="customizeDict.txt">
|
||||
<PackagePath>contentFiles\any\any\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\AUTHORS">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\BSD">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\ChangeLog">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\char.bin">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\COPYING">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\dicrc">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\GPL">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\INSTALL">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\LGPL">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\matrix.bin">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\sys.dic">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\unidic-mecab.pdf">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
<Content Include="unidic\unk.dic">
|
||||
<PackagePath>contentFiles\any\any\unidic\</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<PackageCopyToOutput>true</PackageCopyToOutput>
|
||||
<Pack>true</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
|
||||
<ProjectReference Include="..\RomajiConverter.Core\RomajiConverter.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.Core\BetterLyrics.Core.csproj" >
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\RomajiConverter.Core\RomajiConverter.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace BetterLyrics.Plugins.Romaji
|
||||
if (targetLangCode == "ja-latin")
|
||||
{
|
||||
var lines = RomajiHelper.ToRomaji(text);
|
||||
result = string.Join("\r\n", lines.Select(p => string.Join(" ", p.Units.Select(q => q.Romaji))));
|
||||
result = string.Join("\n", lines.Select(p => string.Join(" ", p.Units.Select(q => q.Romaji))));
|
||||
}
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.2.256.0" />
|
||||
Version="1.2.265.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
);
|
||||
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
|
||||
initialValue: 1f,
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: 0.3f
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor1Transition = new(
|
||||
@@ -87,13 +88,13 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
);
|
||||
private readonly ValueTransition<double> _canvasYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: 0.3f
|
||||
);
|
||||
private readonly ValueTransition<double> _mouseYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
defaultTotalDuration: 0.3f,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: 0.3f
|
||||
);
|
||||
|
||||
private TimeSpan _songPositionWithOffset;
|
||||
@@ -492,7 +493,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
else
|
||||
{
|
||||
_canvasYScrollTransition.SetDurationMs(lyricsEffect.LyricsScrollDuration);
|
||||
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
|
||||
_canvasYScrollTransition.SetInterpolator(EasingHelper.GetInterpolatorByEasingType<double>(lyricsEffect.LyricsScrollEasingType, lyricsEffect.LyricsScrollEasingMode));
|
||||
_canvasYScrollTransition.Start(_canvasTargetScrollOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,13 @@
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBounce" />
|
||||
</ComboBox>
|
||||
<dev:SettingsExpander.Items>
|
||||
<dev:SettingsCard Header="Easing Mode">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsScrollEasingMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem Content="EaseIn" />
|
||||
<ComboBoxItem Content="EaseOut" />
|
||||
<ComboBoxItem Content="EaseInOut" />
|
||||
</ComboBox>
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageScrollTopDuration">
|
||||
<local:ExtendedSlider
|
||||
Default="500"
|
||||
|
||||
@@ -424,30 +424,6 @@
|
||||
</dev:SettingsExpander>
|
||||
<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: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>
|
||||
|
||||
<!-- 中文简体繁体偏好 -->
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
ContentDialog deleteDialog = new ContentDialog
|
||||
{
|
||||
XamlRoot = this.XamlRoot,
|
||||
Title = "卸载插件?",
|
||||
Title = "卸载插件",
|
||||
Content = $"确定要删除 \"{plugin.Name}\" 吗?此操作无法撤销。",
|
||||
PrimaryButtonText = "删除",
|
||||
CloseButtonText = "取消",
|
||||
@@ -108,9 +108,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 在 PluginService 里加一个 UninstallPlugin 方法
|
||||
// 逻辑:找到插件对应文件夹,Directory.Delete(path, true)
|
||||
// _pluginService.UninstallPlugin(plugin.Id);
|
||||
_pluginService.UninstallPlugin(plugin.Id);
|
||||
|
||||
// 暂时我们只能刷新列表演示
|
||||
RefreshPluginList();
|
||||
@@ -131,7 +129,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
XamlRoot = this.XamlRoot,
|
||||
Title = title,
|
||||
Content = content,
|
||||
CloseButtonText = "好"
|
||||
CloseButtonText = "关闭"
|
||||
};
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterLyrics.WinUI3.Enums;
|
||||
|
||||
public enum EaseMode
|
||||
{
|
||||
In,
|
||||
Out,
|
||||
InOut,
|
||||
}
|
||||
@@ -6,15 +6,15 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
Linear,
|
||||
SmoothStep,
|
||||
EaseInOutSine,
|
||||
EaseInOutQuad,
|
||||
EaseInOutCubic,
|
||||
EaseInOutQuart,
|
||||
EaseInOutQuint,
|
||||
EaseInOutExpo,
|
||||
EaseInOutCirc,
|
||||
EaseInOutBack,
|
||||
EaseInOutElastic,
|
||||
EaseInOutBounce,
|
||||
Sine,
|
||||
Quad,
|
||||
Cubic,
|
||||
Quart,
|
||||
Quint,
|
||||
Expo,
|
||||
Circle,
|
||||
Back,
|
||||
Elastic,
|
||||
Bounce,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +1,170 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class EasingHelper
|
||||
{
|
||||
public static double EaseInOutSine(double t)
|
||||
{
|
||||
return -(Math.Cos(Math.PI * t) - 1f) / 2f;
|
||||
}
|
||||
public static double EaseInOutQuad(double t)
|
||||
{
|
||||
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
}
|
||||
#region Interpolators
|
||||
|
||||
public static double EaseInOutCubic(double t)
|
||||
public static Func<T, T, double, T> GetInterpolatorByEasingType<T>(EasingType? type, EaseMode easingMode = EaseMode.Out)
|
||||
where T : INumber<T>, IFloatingPointIeee754<T>
|
||||
{
|
||||
return t < 0.5f ? 4 * t * t * t : 1 - Math.Pow(-2 * t + 2, 3) / 2;
|
||||
}
|
||||
public static double EaseInOutQuart(double t)
|
||||
{
|
||||
return t < 0.5f ? 8 * t * t * t * t : 1 - Math.Pow(-2 * t + 2, 4) / 2;
|
||||
}
|
||||
|
||||
public static double EaseInOutQuint(double t)
|
||||
{
|
||||
return t < 0.5f ? 16 * t * t * t * t * t : 1 - Math.Pow(-2 * t + 2, 5) / 2;
|
||||
}
|
||||
|
||||
public static double EaseInOutExpo(double t)
|
||||
{
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: t < 0.5 ? Math.Pow(2, 20 * t - 10) / 2
|
||||
: (2 - Math.Pow(2, -20 * t + 10)) / 2;
|
||||
}
|
||||
|
||||
public static double EaseInOutCirc(double t)
|
||||
{
|
||||
return t < 0.5f
|
||||
? (1 - Math.Sqrt(1 - Math.Pow(2 * t, 2))) / 2
|
||||
: (Math.Sqrt(1 - Math.Pow(-2 * t + 2, 2)) + 1) / 2;
|
||||
}
|
||||
|
||||
public static double EaseInOutBack(double t)
|
||||
{
|
||||
double c1 = 1.70158f;
|
||||
double c2 = c1 * 1.525f;
|
||||
|
||||
return t < 0.5
|
||||
? (Math.Pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
|
||||
: (Math.Pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
||||
}
|
||||
|
||||
public static double EaseInOutElastic(double t)
|
||||
{
|
||||
if (t == 0 || t == 1) return t;
|
||||
double p = 0.3f;
|
||||
double s = p / 4;
|
||||
return t < 0.5f
|
||||
? -(Math.Pow(2, 20 * t - 10) * Math.Sin((20 * t - 11.125f) * (2 * Math.PI) / p)) / 2
|
||||
: (Math.Pow(2, -20 * t + 10) * Math.Sin((20 * t - 11.125f) * (2 * Math.PI) / p)) / 2 + 1;
|
||||
}
|
||||
|
||||
private static double EaseOutBounce(double t)
|
||||
{
|
||||
if (t < 4 / 11f)
|
||||
return (start, end, progress) =>
|
||||
{
|
||||
return (121 * t * t) / 16f;
|
||||
Func<T, T> easeInFunc = type switch
|
||||
{
|
||||
EasingType.Sine => EaseInSine,
|
||||
EasingType.Quad => EaseInQuad,
|
||||
EasingType.Cubic => EaseInCubic,
|
||||
EasingType.Quart => EaseInQuart,
|
||||
EasingType.Quint => EaseInQuint,
|
||||
EasingType.Expo => EaseInExpo,
|
||||
EasingType.Circle => EaseInCircle,
|
||||
EasingType.Back => EaseInBack,
|
||||
EasingType.Elastic => EaseInElastic,
|
||||
EasingType.Bounce => EaseInBounce,
|
||||
EasingType.SmoothStep => SmoothStep,
|
||||
EasingType.Linear => Linear,
|
||||
_ => EaseInQuad,
|
||||
};
|
||||
double t = Ease(progress, easingMode, easeInFunc);
|
||||
return start + ((end - start) * T.CreateChecked(t));
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static double Ease<T>(double t, EaseMode mode, Func<T, T> easeIn)
|
||||
where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
t = Math.Clamp(t, 0.0, 1.0);
|
||||
|
||||
T tt = T.CreateChecked(t);
|
||||
T half = T.CreateChecked(0.5);
|
||||
T two = T.CreateChecked(2);
|
||||
T tResult = mode switch
|
||||
{
|
||||
EaseMode.In => easeIn(tt),
|
||||
EaseMode.Out => T.One - easeIn(T.One - tt),
|
||||
EaseMode.InOut => tt < half
|
||||
? easeIn(tt * two) / two
|
||||
: T.One - (easeIn((T.One - tt) * two) / two),
|
||||
_ => easeIn(tt),
|
||||
};
|
||||
|
||||
return double.CreateChecked(tResult);
|
||||
}
|
||||
|
||||
public static T EaseInSine<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
return T.One - T.Cos((t * T.Pi) / T.CreateChecked(2));
|
||||
}
|
||||
|
||||
public static T EaseInQuad<T>(T t) where T : INumber<T>
|
||||
{
|
||||
return t * t;
|
||||
}
|
||||
|
||||
public static T EaseInCubic<T>(T t) where T : INumber<T>
|
||||
{
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
public static T EaseInQuart<T>(T t) where T : INumber<T>
|
||||
{
|
||||
return t * t * t * t;
|
||||
}
|
||||
|
||||
public static T EaseInQuint<T>(T t) where T : INumber<T>
|
||||
{
|
||||
return t * t * t * t * t;
|
||||
}
|
||||
|
||||
public static T EaseInExpo<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
if (t == T.Zero)
|
||||
{
|
||||
return T.Zero;
|
||||
}
|
||||
else if (t < 8 / 11f)
|
||||
|
||||
return T.Pow(T.CreateChecked(2), (T.CreateChecked(10) * t) - T.CreateChecked(10));
|
||||
}
|
||||
|
||||
public static T EaseInCircle<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
return T.One - T.Sqrt(T.One - (t * t));
|
||||
}
|
||||
|
||||
public static T EaseInBack<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
T c1 = T.CreateChecked(1.70158);
|
||||
T c3 = c1 + T.One;
|
||||
|
||||
return (c3 * t * t * t) - (c1 * t * t);
|
||||
}
|
||||
|
||||
public static T EaseInElastic<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
if (t == T.Zero || t == T.One)
|
||||
{
|
||||
return (363 / 40f * t * t) - (99 / 10f * t) + 17 / 5f;
|
||||
return t;
|
||||
}
|
||||
else if (t < 9 / 10f)
|
||||
|
||||
const double springiness = 6;
|
||||
const double oscillations = 1;
|
||||
|
||||
double td = double.CreateChecked(t);
|
||||
|
||||
double expo = (Math.Exp(springiness * td) - 1.0) / (Math.Exp(springiness) - 1.0);
|
||||
double result = 0.7 * expo * Math.Sin((Math.PI * 2.0 * oscillations + (Math.PI * 0.5)) * td);
|
||||
|
||||
return T.CreateChecked(result);
|
||||
}
|
||||
|
||||
private static T EaseOutBounce<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
if (t < T.CreateChecked(4.0 / 11.0))
|
||||
{
|
||||
return (4356 / 361f * t * t) - (35442 / 1805f * t) + 16061 / 1805f;
|
||||
return (T.CreateChecked(121) * t * t) / T.CreateChecked(16);
|
||||
}
|
||||
else if (t < T.CreateChecked(8.0 / 11.0))
|
||||
{
|
||||
return ((T.CreateChecked(363.0 / 40.0) * t * t) - (T.CreateChecked(99.0 / 10.0) * t)) + T.CreateChecked(17.0 / 5.0);
|
||||
}
|
||||
else if (t < T.CreateChecked(9.0 / 10.0))
|
||||
{
|
||||
return ((T.CreateChecked(4356.0 / 361.0) * t * t) - (T.CreateChecked(35442.0 / 1805.0) * t)) + T.CreateChecked(16061.0 / 1805.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (54 / 5f * t * t) - (513 / 25f * t) + 268 / 25f;
|
||||
return ((T.CreateChecked(54.0 / 5.0) * t * t) - (T.CreateChecked(513.0 / 25.0) * t)) + T.CreateChecked(268.0 / 25.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static double EaseInOutBounce(double t)
|
||||
public static T EaseInBounce<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return (1 - EaseOutBounce(1 - 2 * t)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (1 + EaseOutBounce(2 * t - 1)) / 2;
|
||||
}
|
||||
return T.One - EaseOutBounce(T.One - t);
|
||||
}
|
||||
|
||||
public static double SmoothStep(double t)
|
||||
public static T SmoothStep<T>(T t) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
return t * t * (3f - 2f * t);
|
||||
return t * t * (T.CreateChecked(3) - (T.CreateChecked(2) * t));
|
||||
}
|
||||
|
||||
public static double CubicBezier(double t, double p0, double p1, double p2, double p3)
|
||||
public static T CubicBezier<T>(T t, T p0, T p1, T p2, T p3) where T : IFloatingPointIeee754<T>
|
||||
{
|
||||
double u = 1 - t;
|
||||
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
|
||||
T u = T.One - t;
|
||||
|
||||
return (u * u * u * p0)
|
||||
+ (T.CreateChecked(3) * u * u * t * p1)
|
||||
+ (T.CreateChecked(3) * u * t * t * p2)
|
||||
+ (t * t * t * p3);
|
||||
}
|
||||
|
||||
public static double Linear(double t) => t;
|
||||
public static T Linear<T>(T t) where T : INumber<T> => t;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static BetterLyrics.WinUI3.Helper.EasingHelper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
@@ -21,7 +22,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
private double _configuredDelaySeconds; // 配置的延迟时长
|
||||
|
||||
// 动画状态
|
||||
private Enums.EasingType? _easingType;
|
||||
private Func<T, T, double, T> _interpolator;
|
||||
private bool _isTransitioning;
|
||||
private double _progress; // 当前段的进度 (0.0 ~ 1.0)
|
||||
@@ -30,10 +30,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public T Value => _currentValue;
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
public T TargetValue => _targetValue; // 获取当前段的目标值
|
||||
public Enums.EasingType? EasingType => _easingType;
|
||||
public double DurationSeconds => _totalDurationForAutoSplit;
|
||||
|
||||
public ValueTransition(T initialValue, double defaultTotalDuration = 0.3, EasingType? defaultEasingType = null, Func<T, T, double, T>? interpolator = null)
|
||||
public Func<T, T, double, T> Interpolator => _interpolator;
|
||||
|
||||
public ValueTransition(T initialValue, Func<T, T, double, T>? interpolator, double defaultTotalDuration = 0.3)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
@@ -43,15 +44,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
if (interpolator != null)
|
||||
{
|
||||
_interpolator = interpolator;
|
||||
_easingType = null;
|
||||
}
|
||||
else if (defaultEasingType != null)
|
||||
{
|
||||
SetEasingType(defaultEasingType);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetEasingType(Enums.EasingType.EaseInOutQuad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +66,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_configuredDelaySeconds = seconds;
|
||||
}
|
||||
|
||||
public void SetEasingType(Enums.EasingType? easingType)
|
||||
public void SetInterpolator(Func<T, T, double, T> interpolator)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(easingType);
|
||||
_interpolator = interpolator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -235,7 +226,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
#region Interpolators
|
||||
|
||||
private Func<T, T, double, T> GetInterpolatorByEasingType(Enums.EasingType? type)
|
||||
public static Func<T, T, double, T> GetInterpolatorByEasingType(EasingType? type, EaseMode easingMode)
|
||||
{
|
||||
if (typeof(T) == typeof(double))
|
||||
{
|
||||
@@ -243,25 +234,24 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
double s = (double)(object)start;
|
||||
double e = (double)(object)end;
|
||||
double t = progress;
|
||||
|
||||
// 使用 EasingHelper (假设您的项目中已有此辅助类)
|
||||
switch (type)
|
||||
Func<double, double> easeInFunc = type switch
|
||||
{
|
||||
case Enums.EasingType.EaseInOutSine: t = EasingHelper.EaseInOutSine(t); break;
|
||||
case Enums.EasingType.EaseInOutQuad: t = EasingHelper.EaseInOutQuad(t); break;
|
||||
case Enums.EasingType.EaseInOutCubic: t = EasingHelper.EaseInOutCubic(t); break;
|
||||
case Enums.EasingType.EaseInOutQuart: t = EasingHelper.EaseInOutQuart(t); break;
|
||||
case Enums.EasingType.EaseInOutQuint: t = EasingHelper.EaseInOutQuint(t); break;
|
||||
case Enums.EasingType.EaseInOutExpo: t = EasingHelper.EaseInOutExpo(t); break;
|
||||
case Enums.EasingType.EaseInOutCirc: t = EasingHelper.EaseInOutCirc(t); break;
|
||||
case Enums.EasingType.EaseInOutBack: t = EasingHelper.EaseInOutBack(t); break;
|
||||
case Enums.EasingType.EaseInOutElastic: t = EasingHelper.EaseInOutElastic(t); break;
|
||||
case Enums.EasingType.EaseInOutBounce: t = EasingHelper.EaseInOutBounce(t); break;
|
||||
case Enums.EasingType.SmoothStep: t = EasingHelper.SmoothStep(t); break;
|
||||
case Enums.EasingType.Linear: t = EasingHelper.Linear(t); break;
|
||||
default: t = EasingHelper.EaseInOutQuad(t); break;
|
||||
}
|
||||
Enums.EasingType.Sine => EaseInSine,
|
||||
Enums.EasingType.Quad => EaseInQuad,
|
||||
Enums.EasingType.Cubic => EaseInCubic,
|
||||
Enums.EasingType.Quart => EaseInQuart,
|
||||
Enums.EasingType.Quint => EaseInQuint,
|
||||
Enums.EasingType.Expo => EaseInExpo,
|
||||
Enums.EasingType.Circle => EaseInCircle,
|
||||
Enums.EasingType.Back => EaseInBack,
|
||||
Enums.EasingType.Elastic => EaseInElastic,
|
||||
Enums.EasingType.Bounce => EaseInBounce,
|
||||
Enums.EasingType.SmoothStep => SmoothStep,
|
||||
Enums.EasingType.Linear => Linear,
|
||||
_ => EaseInQuad,
|
||||
};
|
||||
double t = Ease(progress, easingMode, easeInFunc);
|
||||
|
||||
return (T)(object)(s + (e - s) * t);
|
||||
};
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
line.ColorTransition.SetDelay(yScrollDelay);
|
||||
line.ColorTransition.Start(isSecondaryLinePlaying ? fgColor : bgColor);
|
||||
|
||||
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.AngleTransition.SetInterpolator(canvasYScrollTransition.Interpolator);
|
||||
line.AngleTransition.SetDuration(yScrollDuration);
|
||||
line.AngleTransition.SetDelay(yScrollDelay);
|
||||
line.AngleTransition.Start(
|
||||
@@ -185,7 +185,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
fanAngleRad * distanceFactor * (i > primaryPlayingLineIndex ? 1 : -1) :
|
||||
0);
|
||||
|
||||
line.YOffsetTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.YOffsetTransition.SetInterpolator(canvasYScrollTransition.Interpolator);
|
||||
line.YOffsetTransition.SetDuration(yScrollDuration);
|
||||
line.YOffsetTransition.SetDelay(yScrollDelay);
|
||||
// 设计之初是当 isLayoutChanged 为真时 jumpTo
|
||||
|
||||
@@ -20,18 +20,18 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
ScaleTransition = new(
|
||||
initialValue: 1.0,
|
||||
defaultTotalDuration: Time.AnimationDuration.TotalSeconds,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: Time.AnimationDuration.TotalSeconds
|
||||
);
|
||||
GlowTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: Time.AnimationDuration.TotalSeconds,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: Time.AnimationDuration.TotalSeconds
|
||||
);
|
||||
FloatTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: Time.LongAnimationDuration.TotalSeconds,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: Time.LongAnimationDuration.TotalSeconds
|
||||
);
|
||||
LayoutRect = layoutRect;
|
||||
}
|
||||
|
||||
@@ -82,43 +82,43 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||
{
|
||||
AngleTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
BlurAmountTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
PhoneticOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
PlayedOriginalOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
UnplayedOriginalOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
TranslatedOpacityTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
ScaleTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
YOffsetTransition = new(
|
||||
initialValue: 0,
|
||||
defaultTotalDuration: AnimationDuration,
|
||||
defaultEasingType: EasingType.EaseInOutSine
|
||||
EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Sine),
|
||||
defaultTotalDuration: AnimationDuration
|
||||
);
|
||||
ColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using static BetterLyrics.WinUI3.Helper.EasingHelper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
@@ -29,6 +30,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAnimationDuration { get; set; } = 450; // 450ms
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EasingType LyricsScrollEasingType { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EaseMode LyricsScrollEasingMode { get; set; } = EaseMode.Out;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollTopDuration { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollBottomDuration { get; set; }
|
||||
@@ -79,6 +81,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
LyricsFloatAnimationDuration = this.LyricsFloatAnimationDuration,
|
||||
|
||||
LyricsScrollEasingType = this.LyricsScrollEasingType,
|
||||
LyricsScrollEasingMode = this.LyricsScrollEasingMode,
|
||||
LyricsScrollDuration = this.LyricsScrollDuration,
|
||||
LyricsScrollTopDuration = this.LyricsScrollTopDuration,
|
||||
LyricsScrollBottomDuration = this.LyricsScrollBottomDuration,
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty] public partial Rect DemoMonitorBounds { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockPlacement DockPlacement { get; set; } = DockPlacement.Top;
|
||||
[ObservableProperty] public partial LyricsStyleSettings LyricsStyleSettings { get; set; } = new();
|
||||
[ObservableProperty] public partial LyricsEffectSettings LyricsEffectSettings { get; set; } = new(500, 500, 500, EasingType.EaseInOutQuad);
|
||||
[ObservableProperty] public partial LyricsEffectSettings LyricsEffectSettings { get; set; } = new(500, 500, 500, EasingType.Quad);
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsBackgroundSettings LyricsBackgroundSettings { get; set; } = new();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtAreaStyleSettings AlbumArtLayoutSettings { get; set; } = new();
|
||||
[ObservableProperty] public partial AlbumArtAreaEffectSettings AlbumArtAreaEffectSettings { get; set; } = new();
|
||||
|
||||
@@ -13,7 +13,6 @@ 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() { }
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
|
||||
public CoverBackgroundRenderer()
|
||||
{
|
||||
_crossfadeTransition = new ValueTransition<double>(1.0, 0.7, defaultEasingType: EasingType.Linear);
|
||||
_crossfadeTransition = new ValueTransition<double>(1.0, EasingHelper.GetInterpolatorByEasingType<double>(EasingType.Linear), 0.7);
|
||||
}
|
||||
|
||||
public void SetCoverBitmap(CanvasBitmap? newBitmap)
|
||||
|
||||
@@ -697,10 +697,6 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
_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();
|
||||
|
||||
@@ -18,37 +18,25 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
var sharedAssemblies = new HashSet<string>
|
||||
{
|
||||
"BetterLyrics.Core",
|
||||
"Microsoft.WindowsAppSDK",
|
||||
"Microsoft.UI",
|
||||
"Microsoft.UI.Xaml",
|
||||
"Microsoft.Graphics",
|
||||
"System.Runtime",
|
||||
"Newtonsoft.Json"
|
||||
};
|
||||
|
||||
if (assemblyName.Name == null || sharedAssemblies.Contains(assemblyName.Name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
{
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
|
||||
// return null to use the default AssemblyLoadContext
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
if (libraryPath != null)
|
||||
{
|
||||
return LoadUnmanagedDllFromPath(libraryPath);
|
||||
}
|
||||
|
||||
// return IntPtr.Zero to use the default AssemblyLoadContext
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
using BetterLyrics.Core.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader; // 必须引用
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
{
|
||||
public class PluginService : IPluginService
|
||||
{
|
||||
// 1. 核心插件列表
|
||||
private List<IPlugin> _plugins = new();
|
||||
public IReadOnlyList<IPlugin> Plugins => _plugins;
|
||||
|
||||
// 2. 新增:上下文管理字典 (Key: 插件ID, Value: 加载上下文)
|
||||
// 我们需要存着它,以便将来执行 Unload
|
||||
private Dictionary<string, PluginLoadContext> _pluginContexts = new();
|
||||
|
||||
// 3. 新增:已加载的文件路径缓存 (防止同一个 DLL 被扫两遍)
|
||||
private HashSet<string> _loadedDllPaths = new();
|
||||
|
||||
private readonly ILogger<PluginService> _logger;
|
||||
|
||||
public PluginService(ILogger<PluginService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void LoadPlugins()
|
||||
{
|
||||
string pluginsRoot = Path.Combine(ApplicationData.Current.LocalFolder.Path, "plugins");
|
||||
@@ -36,8 +36,6 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
|
||||
foreach (var dllPath in dllFiles)
|
||||
{
|
||||
// 🔥 防御 1:基于路径的检查
|
||||
// 如果这个文件已经在内存里了,绝对不要再 Load 一次
|
||||
if (_loadedDllPaths.Contains(dllPath)) continue;
|
||||
|
||||
TryLoadPlugin(dllPath);
|
||||
@@ -49,24 +47,17 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建上下文
|
||||
var loadContext = new PluginLoadContext(dllPath);
|
||||
|
||||
// 加载程序集
|
||||
var assembly = loadContext.LoadFromAssemblyPath(dllPath);
|
||||
|
||||
bool isPluginFound = false;
|
||||
|
||||
foreach (var type in assembly.GetExportedTypes())
|
||||
{
|
||||
if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsAbstract)
|
||||
{
|
||||
// 实例化
|
||||
var plugin = (IPlugin?)Activator.CreateInstance(type);
|
||||
if (plugin == null) continue;
|
||||
|
||||
// 🔥 防御 2:基于 ID 的检查
|
||||
// 防止 "不同 DLL 或者是新版本" 导致 ID 冲突
|
||||
if (_plugins.Any(p => p.Id == plugin.Id))
|
||||
{
|
||||
// 遇到重复 ID,我们选择跳过新的,保留旧的
|
||||
@@ -76,43 +67,36 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化插件 (如果有 Initialize 方法)
|
||||
try
|
||||
{
|
||||
plugin.Initialize();
|
||||
}
|
||||
catch (Exception initEx)
|
||||
{
|
||||
// 如果初始化失败(比如缺字典文件),就不应该把它加到列表里
|
||||
// 记录日志...
|
||||
_logger.LogError(initEx, "Failed to initialize plugin {id} from {path}", plugin.Id, dllPath);
|
||||
loadContext.Unload();
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ 成功入库
|
||||
_plugins.Add(plugin);
|
||||
_pluginContexts.Add(plugin.Id, loadContext); // 记录上下文
|
||||
_pluginContexts.Add(plugin.Id, loadContext);
|
||||
isPluginFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果这个 DLL 里找到了插件,标记路径为已加载
|
||||
if (isPluginFound)
|
||||
{
|
||||
_loadedDllPaths.Add(dllPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果这个 DLL 里一个插件都没找到 (可能是依赖库),
|
||||
// 为了节省内存,我们可以把这个 Context 卸载掉
|
||||
// (前提是其他插件不依赖它,这块比较复杂,简单起见可以先卸载)
|
||||
_logger.LogWarning("No valid plugin types found in assembly {path}", dllPath);
|
||||
loadContext.Unload();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录日志...
|
||||
// throw new Exception($"Failed to load plugin from {dllPath}: {ex.Message}", ex);
|
||||
_logger.LogError(ex, "Failed to load plugin from {path}", dllPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,39 +105,30 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
var plugin = _plugins.FirstOrDefault(p => p.Id == pluginId);
|
||||
if (plugin == null) return;
|
||||
|
||||
// 1. 获取相关信息
|
||||
var dllPath = plugin.GetType().Assembly.Location;
|
||||
var folderPath = Path.GetDirectoryName(dllPath);
|
||||
|
||||
// 2. 从列表中移除插件对象
|
||||
_plugins.Remove(plugin);
|
||||
_loadedDllPaths.Remove(dllPath); // 允许下次重新加载这个路径
|
||||
_loadedDllPaths.Remove(dllPath);
|
||||
|
||||
// 3. 💥 核心:卸载上下文 (释放文件锁的关键)
|
||||
if (_pluginContexts.TryGetValue(pluginId, out var context))
|
||||
{
|
||||
context.Unload();
|
||||
_pluginContexts.Remove(pluginId);
|
||||
}
|
||||
|
||||
// 4. 强制 GC (垃圾回收)
|
||||
// 上下文卸载是“软卸载”,必须等 GC 跑过之后,文件锁才会真正释放
|
||||
// 这几行代码对于“热删除”非常重要
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
// 5. 物理删除文件
|
||||
if (Directory.Exists(folderPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(folderPath, true);
|
||||
}
|
||||
catch (IOException)
|
||||
catch (IOException ex)
|
||||
{
|
||||
// 如果 GC 还没来得及释放锁,可能会报错
|
||||
// 实际生产中,通常是标记为“待删除”,下次重启时删
|
||||
// 或者提示用户重启
|
||||
_logger.LogError(ex, "Failed to delete plugin folder {}", folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,9 +139,6 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
string folderName = Path.GetFileNameWithoutExtension(zipPath);
|
||||
string installDir = Path.Combine(pluginsRoot, folderName);
|
||||
|
||||
// 如果已经存在,说明是更新或者重装
|
||||
// 我们需要先根据文件夹找到旧插件的 ID,然后执行标准的卸载流程
|
||||
// (这里简化处理:直接尝试删文件夹,如果删不掉说明被占用)
|
||||
if (Directory.Exists(installDir))
|
||||
{
|
||||
// TODO: 最好是先 Find plugin by path -> UninstallPlugin(id)
|
||||
@@ -175,14 +147,15 @@ namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
{
|
||||
Directory.Delete(installDir, true);
|
||||
}
|
||||
catch { /* 忽略或报错 */ }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete existing plugin folder {}", installDir);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(installDir);
|
||||
|
||||
ZipFile.ExtractToDirectory(zipPath, installDir);
|
||||
|
||||
// 安装完后,如果不重启软件,你想立即生效的话:
|
||||
// LoadPlugins(); // 因为加了路径去重,这里重新调一次是安全的
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>اختصار التشغيل والإيقاف المؤقت</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>اختصار الأغنية السابقة</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>Die App startet nach erfolgreichem Import automatisch neu</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Priorisiere Transliterationen innerhalb des Songtextes; falls keine Übereinstimmung gefunden wird, fordere maschinelle Transliteration vom cutlet-docker-Server an</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Japanische Lautschrift</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Tastenkürzel für Wiedergabe/Pause</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Tastenkürzel für vorherigen Titel</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>The app will restart automatically after successful import</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Prioritize reading transliteration within lyrics; if no match, request machine transliteration from cutlet-docker server</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Japanese Phonetic</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Play/Pause Shortcut</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value>Plugins</value>
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Previous Track Shortcut</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>La aplicación se reiniciará automáticamente después de una importación exitosa</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Priorizar la lectura de transliteración dentro de la letra; si no hay coincidencia, solicitar transliteración automática al servidor cutlet-docker</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Fonética japonesa</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Atajo de Reproducir/Pausar</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Atajo de pista anterior</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>L'application redémarrera automatiquement après une importation réussie</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Prioriser la lecture de la translittération dans les paroles ; si aucune correspondance, demander une translittération automatique au serveur cutlet-docker</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Phonétique japonaise</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Raccourci Lecture/Pause</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Raccourci piste précédente</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>प्ले और पॉज़ शॉर्टकट</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>पिछला ट्रैक शॉर्टकट</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>Setelah impor berhasil, program akan dimuat ulang secara otomatis</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Akan memprioritaskan membaca transliterasi di dalam lirik, jika tidak ada kecocokan maka akan meminta transliterasi mesin dari server cutlet-docker</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Fonetik Bahasa Jepang</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Pintasan Putar dan Jeda</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Pintasan Lagu Sebelumnya</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>再生/一時停止のショートカットキー</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>次のトラックのショートカットキー</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>재생 및 일시정지 단축키</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>이전 곡 단축키</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>Selepas import berjaya, program akan dimulakan semula secara automatik</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Akan mengutamakan pembacaan transliterasi dalam lirik, jika tiada padanan maka akan meminta transliterasi mesin daripada pelayan cutlet-docker</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Fonetik Bahasa Jepun</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Pintasan Main dan Jeda</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Pintasan Lagu Sebelumnya</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>Após a importação bem-sucedida, o programa será reiniciado automaticamente</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Dará prioridade à leitura da transliteração dentro da letra; se não houver correspondência, solicitará transliteração automática ao servidor cutlet-docker</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Fonética Japonesa</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Atalho para Reproduzir e Pausar</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Atalho para Faixa Anterior</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Горячая клавиша Воспроизведение/Пауза</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Горячая клавиша предыдущего трека</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>ทางลัดเล่นและหยุดชั่วคราว</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>ทางลัดเพลงก่อนหน้า</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.Content" xml:space="preserve">
|
||||
<value>Sau khi nhập thành công, chương trình sẽ tự động khởi động lại</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Description" xml:space="preserve">
|
||||
<value>Sẽ ưu tiên đọc phiên âm trong lời bài hát, nếu không khớp sẽ yêu cầu phiên âm máy từ máy chủ cutlet-docker</value>
|
||||
</data>
|
||||
<data name="SettingsPageJapanese.Header" xml:space="preserve">
|
||||
<value>Phiên âm tiếng Nhật</value>
|
||||
</data>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>Phím tắt phát và tạm dừng</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>Phím tắt bài hát trước</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>播放与暂停快捷键</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value>插件</value>
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>上一曲目快捷键</value>
|
||||
</data>
|
||||
|
||||
@@ -936,9 +936,6 @@
|
||||
<data name="SettingsPageImportSettingsInfo.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>
|
||||
@@ -1266,6 +1263,9 @@
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>播放與暫停快速鍵</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlugins.Content" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>上一曲目快速鍵</value>
|
||||
</data>
|
||||
|
||||
@@ -39,9 +39,6 @@ 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;
|
||||
|
||||
@@ -125,35 +122,6 @@ 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()
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
Tag="Stats" />
|
||||
|
||||
<NavigationViewItem
|
||||
Content="Plugins"
|
||||
x:Uid="SettingsPagePlugins"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="Plugins" />
|
||||
|
||||
@@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorThief.WinUI3", "ColorT
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Core", "BetterLyrics.Core\BetterLyrics.Core.csproj", "{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Plugins.Demo", "BetterLyrics.Plugins.Demo\BetterLyrics.Plugins.Demo.csproj", "{87D235CA-4311-4766-8186-AD9B193DFABC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Plugins.Romaji", "BetterLyrics.Plugins.Romaji\BetterLyrics.Plugins.Romaji.csproj", "{DD2D477F-94CD-4D4B-8B59-C127F2850E34}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RomajiConverter.Core", "RomajiConverter.Core\RomajiConverter.Core.csproj", "{351807DB-CD63-B939-8071-B1FBFF969569}"
|
||||
@@ -95,18 +93,6 @@ Global
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x86.Build.0 = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DD2D477F-94CD-4D4B-8B59-C127F2850E34}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{DD2D477F-94CD-4D4B-8B59-C127F2850E34}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{DD2D477F-94CD-4D4B-8B59-C127F2850E34}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
using RomajiConverter.Core.Models;
|
||||
using RomajiConverter.Core.Options;
|
||||
using System;
|
||||
using System.ClientModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RomajiConverter.Core.Helpers
|
||||
{
|
||||
public static class RomajiAIHelper
|
||||
{
|
||||
public const string Prompt = @"用户将输入一段日文歌词,你需要逐词转换为以下格式:
|
||||
- 每行输出必须严格对应每行输入,禁止额外添加换行,禁止输出空行,不能因为遇到标点符号而换行,换行符必须使用单个\n
|
||||
- 对每行日文进行分词处理,分词应以现代日语常规形态(助词、助动词、词尾变化)为最小单位
|
||||
- 如果一个分词是日文且包含汉字,则需要给出平假名,用小括号在原文后标注,格式为:日文分词(平假名)。禁止在分词中间标注假名(例:x(xx)xx),要么标注整个分词的假名,要么将标注之后的部分拆分为新的分词
|
||||
- 纯假名分词不添加任何假名标注
|
||||
- 遇到仅当 は/へ/を 作为独立分词并起语法助词作用时,在后面添加“|”以及它的口语化假名,非助词情况下只输出原文
|
||||
- 遇到英文单词/字母、数字、标点符号、特殊符号、等非日文的unicode字符时,必须保留且单独作为一个分词,必须只输出原文,不能给出平假名
|
||||
- 每个分词之间必须用半角空格分隔
|
||||
- 如果无法确定某分词是否为助词或其读音,请优先保持原文不转换
|
||||
- 不要包含任何解释、注释、Markdown、额外字段或文本
|
||||
- 示例仅供参考,不能直接输出,任何时候都需要根据上面给出的文本进行转换
|
||||
示例:
|
||||
输入:昨日はColdな夜へ行を歌った
|
||||
输出:昨日(きのう) は|わ Cold な 夜(よる) へ|え 行(い) を|お 歌った(うたった)";
|
||||
|
||||
private static Regex _formatRegex = new Regex(@"^(.*?)(\((.*?)\))*?(\|(.*?))*?$", RegexOptions.Compiled);
|
||||
|
||||
private static ChatCompletionOptions _chatCompletionOptions = new ChatCompletionOptions
|
||||
{
|
||||
Temperature = 0.2f
|
||||
};
|
||||
|
||||
public static async Task LoadRomajiAsync(ICollection<ConvertedLine> convertedLines, string text, ToRomajiAIOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
//预处理为ConvertedLine列表, 其中会包含空行
|
||||
var cacheList = GetCacheList(options, text);
|
||||
|
||||
if (cacheList.Count == 0) return;
|
||||
|
||||
//获取ai结果
|
||||
var client = new ChatClient(
|
||||
model: options.Model,
|
||||
credential: new ApiKeyCredential(options.ApiKey),
|
||||
options: new OpenAIClientOptions
|
||||
{
|
||||
Endpoint = new Uri(options.BaseUrl)
|
||||
}
|
||||
);
|
||||
|
||||
var prompt = string.IsNullOrEmpty(options.Prompt) ? Prompt : options.Prompt;
|
||||
//发送的内容不包含空行
|
||||
var content = string.Join("\n", cacheList.Where(p => !string.IsNullOrWhiteSpace(p.Japanese)).Select(p => p.Japanese));
|
||||
|
||||
var messages = new List<ChatMessage>
|
||||
{
|
||||
new SystemChatMessage(prompt),
|
||||
new UserChatMessage(content)
|
||||
};
|
||||
|
||||
Debug.WriteLine(prompt);
|
||||
Debug.WriteLine(content);
|
||||
|
||||
var completionUpdates = client.CompleteChatStreamingAsync(messages, _chatCompletionOptions, cancellationToken: cancellationToken);
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
ushort lineIndex = 0;
|
||||
var l = 0;
|
||||
var r = 0;
|
||||
|
||||
//插入直到下一个非空行
|
||||
AddNextNotEmptyLine();
|
||||
|
||||
//处理流式返回
|
||||
var enumerator = completionUpdates.GetAsyncEnumerator(cancellationToken);
|
||||
try
|
||||
{
|
||||
while (await enumerator.MoveNextAsync())
|
||||
{
|
||||
var completionUpdate = enumerator.Current;
|
||||
if (completionUpdate.ContentUpdate.Count > 0)
|
||||
{
|
||||
var delta = FixFormat(completionUpdate.ContentUpdate[0].Text);
|
||||
if (string.IsNullOrEmpty(delta)) continue;
|
||||
stringBuilder.Append(delta);
|
||||
Debug.Write(completionUpdate.ContentUpdate[0].Text);
|
||||
|
||||
while (r < stringBuilder.Length)
|
||||
{
|
||||
if (stringBuilder[r] == '\n')
|
||||
{
|
||||
InsertUnit();
|
||||
//插入直到下一个非空行
|
||||
AddNextNotEmptyLine();
|
||||
r++;
|
||||
l = r;
|
||||
}
|
||||
else if (stringBuilder[r] == ' ')
|
||||
{
|
||||
InsertUnit();
|
||||
r++;
|
||||
l = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
r++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await enumerator.DisposeAsync();
|
||||
}
|
||||
|
||||
//处理完成,手动插入最后一个分词
|
||||
if (l != r)
|
||||
{
|
||||
InsertUnit();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void AddNextNotEmptyLine()
|
||||
{
|
||||
do
|
||||
{
|
||||
var newLine = new ConvertedLine
|
||||
{
|
||||
Time = lineIndex >= cacheList.Count ? (TimeSpan?)null : cacheList[lineIndex].Time,
|
||||
Chinese = lineIndex >= cacheList.Count ? string.Empty : cacheList[lineIndex].Chinese,
|
||||
Index = lineIndex,
|
||||
Japanese = lineIndex >= cacheList.Count ? string.Empty : cacheList[lineIndex].Japanese
|
||||
};
|
||||
convertedLines.Add(newLine);
|
||||
lineIndex++;
|
||||
} while (string.IsNullOrWhiteSpace(convertedLines.Last().Japanese) && lineIndex < cacheList.Count);
|
||||
}
|
||||
|
||||
void InsertUnit()
|
||||
{
|
||||
var lastLine = convertedLines.Last();
|
||||
var lastUnitStr = stringBuilder.ToString(l, r - l);
|
||||
if (!string.IsNullOrEmpty(lastUnitStr))
|
||||
lastLine.Units.Add(GetUnit(lastLine.Index, lastUnitStr, options.IsParticleAsPronunciation));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ConvertedLine> GetCacheList(ToRomajiAIOptions options, string text)
|
||||
{
|
||||
var timeSpans = new List<TimeSpan?>();
|
||||
var lineTextList = text.Split(Environment.NewLine.ToArray()).Where(p => !string.IsNullOrWhiteSpace(p)).ToList();
|
||||
|
||||
for (var i = 0; i < lineTextList.Count; i++)
|
||||
{
|
||||
if (LrcParser.LrcLineRegex.IsMatch(lineTextList[i]))
|
||||
{
|
||||
var lyrics = LrcParser.Parse(lineTextList[i]);
|
||||
timeSpans.Add(lyrics.Count > 0 ? lyrics[0].Time : (TimeSpan?)null);
|
||||
lineTextList[i] = lyrics.Count > 0 ? lyrics[0].Text : lineTextList[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
timeSpans.Add(null);
|
||||
}
|
||||
}
|
||||
|
||||
var cacheList = new List<ConvertedLine>();
|
||||
for (var index = 0; index < lineTextList.Count; index++)
|
||||
{
|
||||
var line = lineTextList[index];
|
||||
|
||||
if (RomajiHelper.IsChinese(line, options.ChineseRate)) continue;
|
||||
|
||||
var convertedLine = new ConvertedLine
|
||||
{
|
||||
Time = index < timeSpans.Count ? timeSpans[index] : null,
|
||||
Japanese = line.Replace("\0", "")
|
||||
};
|
||||
|
||||
if (index + 1 < lineTextList.Count &&
|
||||
RomajiHelper.IsChinese(lineTextList[index + 1], options.ChineseRate))
|
||||
convertedLine.Chinese = lineTextList[index + 1];
|
||||
|
||||
convertedLine.Index = (ushort)cacheList.Count;
|
||||
cacheList.Add(convertedLine);
|
||||
}
|
||||
|
||||
return cacheList;
|
||||
}
|
||||
|
||||
private static string FixFormat(string content)
|
||||
{
|
||||
content = content.Replace("\r", "");
|
||||
content = content.Replace("\\n", "\n");
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private static ConvertedUnit GetUnit(ushort lineIndex, string unitString, bool isParticleAsPronunciation)
|
||||
{
|
||||
var match = _formatRegex.Match(unitString);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return new ConvertedUnit(lineIndex, unitString, KanaHelper.ToHiragana(unitString),
|
||||
KanaHelper.KatakanaToRomaji(unitString), false);
|
||||
}
|
||||
|
||||
var origin = match.Groups[1].Value;
|
||||
var kanji_gana = match.Groups[3].Value;
|
||||
var particle_gana = match.Groups[5].Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(kanji_gana))
|
||||
{
|
||||
return new ConvertedUnit(lineIndex, origin, kanji_gana,
|
||||
KanaHelper.KatakanaToRomaji(kanji_gana), true);
|
||||
}
|
||||
else if (isParticleAsPronunciation && !string.IsNullOrEmpty(particle_gana))
|
||||
{
|
||||
return new ConvertedUnit(lineIndex, origin, particle_gana,
|
||||
KanaHelper.KatakanaToRomaji(particle_gana), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ConvertedUnit(lineIndex, origin, KanaHelper.ToHiragana(origin),
|
||||
KanaHelper.KatakanaToRomaji(origin), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace RomajiConverter.Core.Options
|
||||
{
|
||||
public class ToRomajiAIOptions : ToRomajiOptions
|
||||
{
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public string Model { get; set; }
|
||||
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提示词,可以不传,使用默认提示词
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<PackageId>RomajiConverter.Core</PackageId>
|
||||
<Version>2.1.0</Version>
|
||||
<Authors>WL</Authors>
|
||||
@@ -15,7 +16,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MeCab.DotNet" Version="1.2.0" />
|
||||
<PackageReference Include="OpenAI" Version="2.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user