Compare commits

...

45 Commits

Author SHA1 Message Date
Zhe Fang
b6aac2b968 fix: BetterLyrics.Plugins.Romaji not working as expected 2026-01-11 21:01:48 -05:00
Zhe Fang
290f84cea0 chores: update links in readme 2026-01-11 20:30:30 -05:00
Zhe Fang
3031d298d5 chores 2026-01-11 20:25:33 -05:00
Zhe Fang
ed74e96aeb chores 2026-01-11 20:22:31 -05:00
Zhe Fang
affa55b057 fix: README in lyrics-window-styles 2026-01-11 20:20:37 -05:00
Zhe Fang
305f5e2cac chores 2026-01-11 20:18:24 -05:00
Zhe Fang
b8d4fc4130 chores: refactor folder structure 2026-01-11 20:03:12 -05:00
Zhe Fang
da5fd90d44 chores: update readme 2026-01-11 19:53:10 -05:00
Zhe Fang
f4a6aa78b6 Add BetterLyrics Plugin Development Guide
This document provides a comprehensive guide for developing BetterLyrics plugins, detailing core mechanics, project setup, configuration, and development workflow.
2026-01-11 19:38:09 -05:00
Zhe Fang
52b81cd959 Refactor XML project file structure and properties 2026-01-11 19:34:20 -05:00
Zhe Fang
d79933d41e Create PLUGIN_DEV.CN.md 2026-01-11 19:30:37 -05:00
Zhe Fang
a82f1bdb53 chores: plug-in system has been initially completed. 2026-01-11 18:58:18 -05:00
Zhe Fang
2cac55b55e Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-11 09:47:29 -05:00
Zhe Fang
4efe897ab2 chores: remove unnecessary packages for RomajiConverter.Core 2026-01-11 09:47:27 -05:00
Zhe Fang
173f808a88 Merge pull request #188 from zxbmmmmmmmmm/dev
refactor: animations easing
2026-01-11 09:46:37 -05:00
Betta_Fish
e73c27c705 refactor: animations easing 2026-01-11 22:29:57 +08:00
Zhe Fang
3724ef5f7e chores: add README for BetterLyrics.Plugins.Romaji 2026-01-10 18:19:57 -05:00
Zhe Fang
7ca0ecde16 Delete .github/workflows/plugin-registry-check.yml 2026-01-10 18:08:37 -05:00
Zhe Fang
2e0700dba5 Delete Community directory 2026-01-10 18:08:16 -05:00
Zhe Fang
9ec12e3a08 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-10 18:08:00 -05:00
Zhe Fang
d042993eb7 feat: romaji plugin (without dict) 2026-01-10 18:07:57 -05:00
Zhe Fang
fea7367671 feat: romaji plugin (without dict) 2026-01-10 18:07:54 -05:00
Zhe Fang
2b3169e5f6 Update check-hash-collision.js 2026-01-10 13:32:08 -05:00
Zhe Fang
9d2e245a99 Rename releases-to-discord.yml to release-to-discord.yml 2026-01-10 13:25:17 -05:00
Zhe Fang
0513c3a128 Add GitHub Actions workflow for plugin registry check 2026-01-10 13:22:32 -05:00
Zhe Fang
5082c4c245 Create check-hash-collision.js 2026-01-10 13:20:59 -05:00
Zhe Fang
659b4d0e60 Create plugins-registry.json 2026-01-10 13:19:39 -05:00
Zhe Fang
85f67c2ec6 Merge pull request #186 from jayfunc/plugin
feat: plugin system
2026-01-10 12:43:34 -05:00
Zhe Fang
bdbeb391ea Merge branch 'dev' into plugin 2026-01-10 12:43:27 -05:00
Zhe Fang
3357e7aaf4 chores: bump to 256 2026-01-10 11:46:26 -05:00
Zhe Fang
e43461d624 feat: float and glow effect now can be adapted to auto word-by-word effect 2026-01-10 11:29:22 -05:00
Zhe Fang
3e1907ad8c fix: lyrics parser (line endtime) 2026-01-10 10:06:11 -05:00
Zhe Fang
74eeffc8a6 fix: fan shape lyrics effect 2026-01-10 09:45:23 -05:00
Zhe Fang
c32eb3b877 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-10 07:38:18 -05:00
Zhe Fang
047e53b830 fix: lyrics parser 2026-01-10 07:38:17 -05:00
Zhe Fang
fdb7bd16f6 plugin 2026-01-10 06:21:57 -05:00
Zhe Fang
4e33444d8e Update README.md 2026-01-09 19:26:31 -05:00
Zhe Fang
8fc67711b6 Simplify language selection in README.CN.md
Removed redundant link from Chinese README.
2026-01-09 19:26:16 -05:00
Zhe Fang
28757d9880 chores: bump to 253 2026-01-09 18:09:45 -05:00
Zhe Fang
e5fb04f577 fix: ValueTransition init 2026-01-09 17:54:50 -05:00
Zhe Fang
9d03c8f688 chores: i18n 2026-01-09 17:39:35 -05:00
Zhe Fang
094fe7b7a1 fix: lyrics animation gpu usage 2026-01-09 17:31:07 -05:00
Zhe Fang
bc32a3f34c feat: add all time filter for stats 2026-01-09 15:27:00 -05:00
Zhe Fang
b23d3c280f fix: not found and loading lyrics placeholder was not displayed 2026-01-09 14:48:12 -05:00
Zhe Fang
2738d45b69 fix: fill Duration (via searching NCM) for amll-ttml-db lyrics source; improve matching system 2026-01-09 13:50:46 -05:00
161 changed files with 4687 additions and 1427 deletions

1
.gitignore vendored
View File

@@ -408,3 +408,4 @@ FodyWeavers.xsd
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LastFM.cs
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/Discord.cs
/BetterLyrics.Plugins.Romaji/unidic

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<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>

View File

@@ -0,0 +1,12 @@
using BetterLyrics.Core.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.Core.Interfaces
{
public interface ILyricsSearchPlugin : IPlugin
{
Task<LyricsSearchResult> GetLyricsAsync(string title, string artist, string album, double duration);
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.Core.Interfaces
{
public interface ILyricsTransliterationPlugin : IPlugin
{
Task<string?> GetTransliterationAsync(string text, string targetLangCode);
}
}

View File

@@ -0,0 +1,17 @@
using BetterLyrics.Core.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.Core.Interfaces
{
public interface IPlugin
{
string Id { get; }
string Name { get; }
string Description { get; }
string Author { get; }
void Initialize();
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.Core.Models
{
public record LyricsSearchResult(
string? Title,
string? Artist,
string? Album,
double? Duration,
string Raw,
string? Translation = null,
string? Transliteration = null,
string? Reference = null);
}

View File

@@ -0,0 +1,135 @@
<Project Sdk="Microsoft.NET.Sdk">
<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>
<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>
<ProjectReference Include="..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
<ProjectReference Include="..\RomajiConverter.Core\RomajiConverter.Core.csproj" />
</ItemGroup>
<Target Name="AutoExcludeSharedAssemblies" AfterTargets="ResolveAssemblyReferences">
<PropertyGroup>
<HostOutputDir>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3\bin\x64\$(Configuration)\$(TargetFramework)\</HostOutputDir>
</PropertyGroup>
<Message Text="[Debug] Searching for Host Assemblies in: $(HostOutputDir)" Importance="High" />
<ItemGroup>
<FilesToCopy Include="@(ReferenceCopyLocalPaths)" />
<SharedFiles Include="@(FilesToCopy)"
Condition="Exists('$(HostOutputDir)%(Filename)%(Extension)')" />
<ReferenceCopyLocalPaths Remove="@(SharedFiles)" />
</ItemGroup>
<Message Text="[Smart Trim] Excluded shared assemblies:%0a@(SharedFiles->' -> %(Filename)%(Extension)', '%0a')" Importance="High" Condition="'@(SharedFiles)' != ''" />
</Target>
<Target Name="RunPluginAnalyzer" AfterTargets="Build">
<PropertyGroup>
<AnalyzerPath>..\PluginAnalyzer\bin\Debug\net10.0\PluginAnalyzer.exe</AnalyzerPath>
<ScanDir>$(TargetDir)</ScanDir>
<Ns>BetterLyrics.WinUI3</Ns>
<Prefix>$(ProjectName)</Prefix>
<OutputDir>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3\PluginConfigs\</OutputDir>
</PropertyGroup>
<Message Text="[Analyzer] Delivering configs to Main App..." Importance="High" />
<Exec Command="&quot;$(AnalyzerPath)&quot; &quot;$(ScanDir)\&quot; &quot;$(Ns)&quot; &quot;$(Prefix)&quot; &quot;$(OutputDir)\&quot;" />
</Target>
</Project>

View File

@@ -0,0 +1,6 @@
## Before you build...
- Download `unidic-mecab-2.1.2_bin.zip` from https://clrd.ninjal.ac.jp/unidic_archive/cwj/2.1.2/
- Unzip it and copy the contents of the `dic` folder to `BetterLyrics.Plugins.Romaji\unidic`
See the original info at https://github.com/xyh20180101/RomajiConverter.WinUI/blob/main/doc/README-dev-en.md

View File

@@ -0,0 +1,34 @@
using BetterLyrics.Core.Interfaces;
using RomajiConverter.Core.Helpers;
using System.Reflection;
namespace BetterLyrics.Plugins.Romaji
{
public class RomajiPlugin : ILyricsTransliterationPlugin
{
public string Id => "jayfunc.romaji";
public string Name => "Romaji";
public string Description => "Convert Japanese lyrics to Romaji transliteration.";
public string Author => "jayfunc";
public void Initialize()
{
string? pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
RomajiHelper.Init(pluginPath);
}
public Task<string?> GetTransliterationAsync(string text, string targetLangCode)
{
string? result = null;
if (targetLangCode == "ja-latin")
{
var lines = text.Split("\n");
result = string.Join("\n", lines.Select(p => string.Join(" ", RomajiHelper.ToRomaji(p).FirstOrDefault()?.Units.Select(q => q.Romaji) ?? [""])));
}
return Task.FromResult(result);
}
}
}

View File

@@ -0,0 +1 @@
私 わたし

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Services.LyricsCacheService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.PlayHistoryService;
using BetterLyrics.WinUI3.Services.PluginService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.SMTCService;
using BetterLyrics.WinUI3.Services.SongSearchMapService;
@@ -22,7 +23,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle; // 关键App生命周期管理
using Microsoft.Windows.AppLifecycle;
using Serilog;
using System;
using System.Linq;
@@ -154,6 +155,10 @@ namespace BetterLyrics.WinUI3
}
fileSystemService.StartAllFolderTimers();
// Ensure plugins
var pluginService = Ioc.Default.GetRequiredService<IPluginService>();
pluginService.LoadPlugins();
// Init system tray
m_window = WindowHook.OpenOrShowWindow<SystemTrayWindow>();
@@ -248,6 +253,7 @@ namespace BetterLyrics.WinUI3
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
.AddSingleton<ILyricsCacheService, LyricsCacheService>()
.AddSingleton<ISongSearchMapService, SongSearchMapService>()
.AddSingleton<IPluginService, PluginService>()
// ViewModels
.AddSingleton<AppSettingsControlViewModel>()

View File

@@ -45,6 +45,7 @@
<None Remove="Controls\PatronControl.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\PlayQueue.xaml" />
<None Remove="Controls\PluginManagerControl.xaml" />
<None Remove="Controls\PropertyRow.xaml" />
<None Remove="Controls\RemoteServerConfigControl.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" />
@@ -82,7 +83,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.251219" />
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
<PackageReference Include="DevWinUI.Controls" Version="9.8.1" />
<PackageReference Include="DevWinUI.Controls" Version="9.9.2" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.6" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.1" />
<PackageReference Include="FlaUI.UIA3" Version="5.0.0" />
@@ -96,10 +97,14 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
<PackageReference Include="NTextCat" Version="0.3.65" />
@@ -118,11 +123,12 @@
<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" />
<PackageReference Include="z440.atl.core" Version="7.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
<ProjectReference Include="..\..\Impressionist\Impressionist\Impressionist.csproj" />
<ProjectReference Include="..\..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
<ProjectReference Include="..\..\Impressionist\Impressionist\Impressionist.csproj" />
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
@@ -134,252 +140,245 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--Disable Trimming for Specific Packages-->
<ItemGroup>
<TrimmerRootAssembly Include="FlaUI.UIA3" />
<TrimmerRootAssembly Include="Interop.UIAutomationClient" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Abstractions" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Relational" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Sqlite" />
<TrimmerRootAssembly Include="NAudio.Wasapi" />
<TrimmerRootAssembly Include="TagLibSharp" />
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
<TrimmerRootAssembly Include="Vanara.PInvoke.Gdi32" />
<TrimmerRootAssembly Include="Vanara.PInvoke.Shell32" />
<TrimmerRootAssembly Include="Vanara.PInvoke.User32" />
<TrimmerRootAssembly Include="Vanara.Windows.Shell" />
<TrimmerRootDescriptor Include="PluginConfigs\**\*_TrimmerRoots.xml" />
<TrimmerRootDescriptor Include="Core_TrimmerRoots.xml" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AlbumArtPlaceholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Alipay.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AMLLPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AppleMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Chrome.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Edge.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\FluidEffect.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Empty.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyBox.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyState.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Folder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\HyPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\iTunes.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\KugouMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LastFM.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Leaf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Listen1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LXMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MediaPlayerWindows11.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MoeKoeMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MusicBee.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\OriginalSoundHQPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PlanetMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\RevolvingHearts.gif">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\SaltPlayerForWindows.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Spotify.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\WeChatReward.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Wiki82.profile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AlbumArtPlaceholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Alipay.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AMLLPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AppleMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Chrome.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Edge.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\FluidEffect.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Empty.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyBox.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyState.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Folder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\HyPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\iTunes.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\KugouMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LastFM.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Leaf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Listen1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LXMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MediaPlayerWindows11.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MoeKoeMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MusicBee.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\OriginalSoundHQPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PlanetMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\RevolvingHearts.gif">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\SaltPlayerForWindows.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Spotify.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\WeChatReward.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Wiki82.profile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PatronControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\PluginManagerControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Styles\GhostSliderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\PatronControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Styles\Converters.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\GhostSliderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PlayQueue.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\Converters.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\StatsDashboardControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\PlayQueue.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\FontFamilyAutoSuggestBox.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\StatsDashboardControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsStyleSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\FontFamilyAutoSuggestBox.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\LyricsWindowSwitchWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\LyricsStyleSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsWindowSwitchControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\LyricsWindowSwitchWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\DemoWindowGrid.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\LyricsWindowSwitchControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsWindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\DemoWindowGrid.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\LyricsSearchWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\LyricsWindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsSearchControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\LyricsSearchWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ShortcutTextBox.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\LyricsSearchControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ExtendedSlider.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\ShortcutTextBox.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsEffectSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\ExtendedSlider.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\MediaSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\LyricsEffectSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PlaybackSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\MediaSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AlbumArtAreaStyleSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\PlaybackSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsBackgroundSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\AlbumArtAreaStyleSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AppSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\LyricsBackgroundSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\AppSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\MusicGalleryWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\SettingsWindow.xaml">
@@ -392,54 +391,57 @@
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\RemoteServerConfigControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\RemoteServerConfigControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\NowPlayingBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\NowPlayingBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\SystemTrayWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\SystemTrayWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\WindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\WindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AlbumArtAreaEffectSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\AlbumArtAreaEffectSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ImageSwitcher.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\ImageSwitcher.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ShadowImage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\ShadowImage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PropertyRow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\PropertyRow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AboutControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\AboutControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Styles\InteractiveListViewHeaderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\InteractiveListViewHeaderStyle.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Folder Include="PluginConfigs\" />
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>

View File

@@ -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);
}
}

View File

@@ -196,18 +196,25 @@
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsScrollEasingType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEasingTypeLinear" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeSmoothStep" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutSine" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuad" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutCubic" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuart" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuint" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutExpo" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutCirc" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBack" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutElastic" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBounce" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseSine" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseQuad" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseCubic" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseQuart" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseQuint" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseExpo" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseCirc" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseBack" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseElastic" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseBounce" />
</ComboBox>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageEasingMode">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsScrollEasingMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEasingModeIn" />
<ComboBoxItem x:Uid="SettingsPageEasingModeOut" />
<ComboBoxItem x:Uid="SettingsPageEasingModeInOut" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageScrollTopDuration">
<local:ExtendedSlider
Default="500"

View File

@@ -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>
<!-- 中文简体繁体偏好 -->

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.PluginManagerControl"
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:dev="using:DevWinUI"
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">
<Grid Padding="36,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock x:Uid="PluginManagerControlTitle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
</Grid>
<ListView
Grid.Row="1"
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
ItemsSource="{x:Bind Plugins, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:PluginDisplayModel">
<dev:SettingsExpander
Description="{x:Bind Description}"
Header="{x:Bind Name}"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE74C;}">
<TextBlock Text="{x:Bind Version}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<local:PropertyRow Header="Author" Value="{x:Bind Author}" />
<local:PropertyRow Header="ID" Value="{x:Bind Id}" />
</StackPanel>
</dev:SettingsCard>
<dev:SettingsCard>
<Button
x:Uid="PluginManagerControlUninstall"
Click="OnUninstallClick"
Tag="{x:Bind Plugin}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{x:Bind IsListEmpty, Mode=OneWay}">
<FontIcon
FontSize="48"
Glyph="&#xE74C;"
Opacity="0.3" />
<TextBlock
x:Uid="PluginManagerControlNoPluginsInstalled"
Margin="0,12,0,0"
Opacity="0.5" />
</StackPanel>
<StackPanel Grid.Row="2" Margin="0,6,0,20">
<Button
x:Uid="PluginManagerControlInstall"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnInstallPluginClick"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,149 @@
using BetterLyrics.Core.Interfaces;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.PluginService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class PluginManagerControl : UserControl
{
public ObservableCollection<PluginDisplayModel> Plugins { get; } = new();
public Visibility IsListEmpty => Plugins.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
private readonly IPluginService _pluginService;
public PluginManagerControl()
{
this.InitializeComponent();
_pluginService = Ioc.Default.GetRequiredService<IPluginService>();
this.Loaded += (s, e) =>
{
RefreshPluginList();
};
}
private void RefreshPluginList()
{
Plugins.Clear();
var allPlugins = _pluginService.Plugins;
foreach (var plugin in allPlugins)
{
Plugins.Add(new PluginDisplayModel(plugin));
}
Bindings.Update();
}
private async void OnInstallPluginClick(object sender, RoutedEventArgs e)
{
var file = await Helper.PickerHelper.PickSingleFileAsync<SettingsWindow>([".zip"]);
if (file != null)
{
try
{
// 显示加载条...
// 3. 调用我们在上一步写的 InstallPlugin 方法
_pluginService.InstallPlugin(file.Path);
// 4. 重新加载所有插件 (这会触发热重载)
_pluginService.LoadPlugins();
// 5. 刷新界面
RefreshPluginList();
ShowTip("安装成功", $"插件 {file.Name} 已安装。");
}
catch (Exception ex)
{
ShowError("安装失败", ex.Message);
}
}
}
// 卸载按钮点击事件
private async void OnUninstallClick(object sender, RoutedEventArgs e)
{
if (sender is Button btn && btn.Tag is IPlugin plugin)
{
// 二次确认对话框
ContentDialog deleteDialog = new ContentDialog
{
XamlRoot = this.XamlRoot,
Title = "卸载插件",
Content = $"确定要删除 \"{plugin.Name}\" 吗?此操作无法撤销。",
PrimaryButtonText = "删除",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Close
};
var result = await deleteDialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
try
{
_pluginService.UninstallPlugin(plugin.Id);
// 暂时我们只能刷新列表演示
RefreshPluginList();
}
catch (Exception ex)
{
ShowError("卸载失败", ex.Message);
}
}
}
}
// 简单的弹窗辅助方法
private async void ShowTip(string title, string content)
{
ContentDialog dialog = new ContentDialog
{
XamlRoot = this.XamlRoot,
Title = title,
Content = content,
CloseButtonText = "关闭"
};
await dialog.ShowAsync();
}
private async void ShowError(string title, string content)
{
ContentDialog dialog = new ContentDialog
{
XamlRoot = this.XamlRoot,
Title = title,
Content = content,
CloseButtonText = "关闭"
};
await dialog.ShowAsync();
}
}
}

View File

@@ -74,6 +74,7 @@
<ComboBoxItem x:Uid="StatsDashboardControlThisMonth" />
<ComboBoxItem x:Uid="StatsDashboardControlThisQuarter" />
<ComboBoxItem x:Uid="StatsDashboardControlThisYear" />
<ComboBoxItem x:Uid="StatsDashboardControlAllTime" />
<ComboBoxItem x:Uid="StatsDashboardControlCustom" />
</ComboBox>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<linker>
<assembly fullname="FlaUI.UIA3" preserve="all" />
<assembly fullname="Interop.UIAutomationClient" preserve="all" />
<assembly fullname="Microsoft.EntityFrameworkCore" preserve="all" />
<assembly fullname="Microsoft.EntityFrameworkCore.Abstractions" preserve="all" />
<assembly fullname="Microsoft.EntityFrameworkCore.Relational" preserve="all" />
<assembly fullname="Microsoft.EntityFrameworkCore.Sqlite" preserve="all" />
<assembly fullname="NAudio.Wasapi" preserve="all" />
<assembly fullname="TagLibSharp" preserve="all" />
<assembly fullname="Vanara.PInvoke.DwmApi" preserve="all" />
<assembly fullname="Vanara.PInvoke.Gdi32" preserve="all" />
<assembly fullname="Vanara.PInvoke.Shell32" preserve="all" />
<assembly fullname="Vanara.PInvoke.User32" preserve="all" />
<assembly fullname="Vanara.Windows.Shell" preserve="all" />
</linker>

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums;
public enum EaseMode
{
In,
Out,
InOut,
}

View File

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

View File

@@ -14,5 +14,6 @@ namespace BetterLyrics.WinUI3.Enums
LocalEslrcFile,
LocalTtmlFile,
AppleMusic,
Plugin = 999,
}
}

View File

@@ -7,6 +7,7 @@
ThisMonth,
ThisQuarter,
ThisYear,
AllTime,
Custom
}
}

View File

@@ -13,5 +13,6 @@
LocalEslrcFile,
LocalTtmlFile,
LibreTranslate,
Plugin = 999,
}
}

View File

@@ -13,6 +13,7 @@
LocalEslrcFile,
LocalTtmlFile,
BetterLyrics,
CutletDocker
CutletDocker,
Plugin = 999,
}
}

View File

@@ -1,5 +1,6 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models.Lyrics;
using BetterLyrics.WinUI3.Services.LocalizationService;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -18,14 +19,27 @@ namespace BetterLyrics.WinUI3.Extensions
new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
EndMs = (int)TimeSpan.FromSeconds(30).TotalMilliseconds,
PrimaryText = "● ● ●",
PrimarySyllables = [new BaseLyrics { Text = "● ● ●", StartMs = 0, EndMs = (int)TimeSpan.FromSeconds(30).TotalMilliseconds }],
IsPrimaryHasRealSyllableInfo = true,
},
],
LanguageCode = "N/A",
};
}
public static LyricsData GetNotfoundPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
PrimaryText = "N/A",
PrimarySyllables = [new BaseLyrics { Text = "N/A", StartMs = 0, EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds }],
}]);
}
public void SetTranslatedText(LyricsData translationData, int toleranceMs = 50)
{
foreach (var line in lyricsData.LyricsLines)

View File

@@ -36,6 +36,12 @@ namespace BetterLyrics.WinUI3.Extensions
return songInfo;
}
public SongInfo WithSongId(string value)
{
songInfo.SongId = value;
return songInfo;
}
public PlayHistoryItem? ToPlayHistoryItem(double actualPlayedMs)
{
if (songInfo == null) return null;

View File

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

View File

@@ -10,27 +10,27 @@ namespace BetterLyrics.WinUI3.Helper
{
public static partial class MetadataComparer
{
private const double WeightTitle = 0.40;
private const double WeightArtist = 0.40;
private const double WeightTitle = 0.30;
private const double WeightArtist = 0.30;
private const double WeightAlbum = 0.10;
private const double WeightDuration = 0.10;
private const double WeightDuration = 0.30;
// JaroWinkler 适合短字符串匹配
private static readonly JaroWinkler _algo = new();
public static int CalculateScore(SongInfo songInfo, LyricsCacheItem remote)
{
return CalculateScore(songInfo, remote.Title, remote.Artist, remote.Album, remote.Duration * 1000);
return CalculateScore(songInfo, remote.Title, remote.Artist, remote.Album, remote.Duration);
}
public static int CalculateScore(SongInfo songInfo, FilesIndexItem local)
{
return CalculateScore(songInfo, local.Title, local.Artist, local.Album, local.Duration * 1000, local.FileName);
return CalculateScore(songInfo, local.Title, local.Artist, local.Album, local.Duration, local.FileName);
}
public static int CalculateScore(
SongInfo songInfo,
string? compareTitle, string? compareArtist, string? compareAlbum, double? compareDurationMs, string? compareFileName = null)
string? compareTitle, string? compareArtist, string? compareAlbum, double? compareDuration, string? compareFileName = null)
{
double totalScore = 0;
@@ -42,7 +42,7 @@ namespace BetterLyrics.WinUI3.Helper
double titleScore = GetStringSimilarity(songInfo.Title, compareTitle);
double artistScore = GetStringSimilarity(songInfo.Artist, compareArtist);
double albumScore = GetStringSimilarity(songInfo.Album, compareAlbum);
double durationScore = GetDurationSimilarity(songInfo.DurationMs, compareDurationMs);
double durationScore = GetDurationSimilarity(songInfo.Duration, compareDuration);
totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
@@ -94,19 +94,18 @@ namespace BetterLyrics.WinUI3.Helper
return _algo.Similarity(s1, s2);
}
private static double GetDurationSimilarity(double localMs, double? remoteSeconds)
private static double GetDurationSimilarity(double localSeconds, double? remoteSeconds)
{
if (remoteSeconds == null || remoteSeconds == 0) return 0.0; // 远程没有时长数据,不匹配
double localSeconds = localMs / 1000.0;
double diff = Math.Abs(localSeconds - remoteSeconds.Value);
// 差距 <= 3100% 相似
// 差距 >= 200% 相似
// 差距 <= 1 100 % 相似
// 差距 >= 10 0 % 相似
// 中间线性插值
const double PerfectTolerance = 3.0;
const double MaxTolerance = 20.0;
const double PerfectTolerance = 1.0;
const double MaxTolerance = 10.0;
if (diff <= PerfectTolerance) return 1.0;
if (diff >= MaxTolerance) return 0.0;

View File

@@ -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,39 +30,20 @@ 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;
_targetValue = initialValue;
_totalDurationForAutoSplit = defaultTotalDuration;
if (interpolator == null)
{
// 默认缓动
SetEasingType(Enums.EasingType.EaseInOutQuad);
}
else
{
_easingType = null;
_interpolator = interpolator;
}
if (interpolator != null)
{
_interpolator = interpolator;
_easingType = null;
}
else if (defaultEasingType != null)
{
SetEasingType(defaultEasingType);
}
else
{
SetEasingType(Enums.EasingType.EaseInOutQuad);
}
}
@@ -85,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
@@ -246,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))
{
@@ -254,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);
};

View File

@@ -36,38 +36,79 @@ namespace BetterLyrics.WinUI3.Logic
double currentPositionMs
)
{
if (lines == null) return;
if (lines == null || lines.Count == 0) return;
var currentPlayingLine = lines.ElementAtOrDefault(primaryPlayingLineIndex);
if (currentPlayingLine == null) return;
if (primaryPlayingLineIndex < 0 || primaryPlayingLineIndex >= lines.Count) return;
var primaryPlayingLine = lines[primaryPlayingLineIndex];
var phoneticOpacity = lyricsStyle.PhoneticLyricsOpacity / 100.0;
var originalOpacity = lyricsStyle.OriginalLyricsOpacity / 100.0;
var translatedOpacity = lyricsStyle.TranslatedLyricsOpacity / 100.0;
for (int i = startIndex; i <= endIndex + 1; i++)
double topHeightFactor = canvasHeight * playingLineTopOffsetFactor;
double bottomHeightFactor = canvasHeight * (1 - playingLineTopOffsetFactor);
double scrollTopDurationSec = lyricsEffect.LyricsScrollTopDuration / 1000.0;
double scrollTopDelaySec = lyricsEffect.LyricsScrollTopDelay / 1000.0;
double scrollBottomDurationSec = lyricsEffect.LyricsScrollBottomDuration / 1000.0;
double scrollBottomDelaySec = lyricsEffect.LyricsScrollBottomDelay / 1000.0;
double canvasTransDuration = canvasYScrollTransition.DurationSeconds;
bool isBlurEnabled = lyricsEffect.IsLyricsBlurEffectEnabled;
bool isOutOfSightEnabled = lyricsEffect.IsLyricsOutOfSightEffectEnabled;
bool isFanEnabled = lyricsEffect.IsFanLyricsEnabled;
double fanAngleRad = Math.PI * (lyricsEffect.FanLyricsAngle / 180.0);
bool isGlowEnabled = lyricsEffect.IsLyricsGlowEffectEnabled;
bool isFloatEnabled = lyricsEffect.IsLyricsFloatAnimationEnabled;
bool isScaleEnabled = lyricsEffect.IsLyricsScaleEffectEnabled;
int safeStart = Math.Max(0, startIndex);
int safeEnd = Math.Min(lines.Count - 1, endIndex + 1);
for (int i = safeStart; i <= safeEnd; i++)
{
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
var line = lines[i];
var lineHeight = line.PrimaryLineHeight;
if (lineHeight == null || lineHeight <= 0) continue;
bool isWordAnimationEnabled = lyricsEffect.WordByWordEffectMode switch
{
Enums.WordByWordEffectMode.Auto => line.IsPrimaryHasRealSyllableInfo,
Enums.WordByWordEffectMode.Always => true,
Enums.WordByWordEffectMode.Never => false,
_ => line.IsPrimaryHasRealSyllableInfo
};
double targetCharFloat = lyricsEffect.IsLyricsFloatAnimationAmountAutoAdjust
? lineHeight.Value * 0.1
: lyricsEffect.LyricsFloatAnimationAmount;
double targetCharGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust
? lineHeight.Value * 0.2
: lyricsEffect.LyricsGlowEffectAmount;
double targetCharScale = lyricsEffect.IsLyricsScaleEffectAmountAutoAdjust
? 1.15
: lyricsEffect.LyricsScaleEffectAmount / 100.0;
var maxAnimationDurationMs = Math.Max(line.EndMs ?? 0 - currentPositionMs, 0);
bool isSecondaryLinePlaying = line.GetIsPlaying(currentPositionMs);
bool isSecondaryLinePlayingChanged = line.IsPlayingLastFrame != isSecondaryLinePlaying;
line.IsPlayingLastFrame = isSecondaryLinePlaying;
// 行动画
if (isLayoutChanged || isPrimaryPlayingLineChanged || isMouseScrollingChanged || isSecondaryLinePlayingChanged)
if (isLayoutChanged || isPrimaryPlayingLineChanged || isMouseScrollingChanged)
{
int lineCountDelta = i - primaryPlayingLineIndex;
double distanceFromPlayingLine = Math.Abs(line.PrimaryPosition.Y - currentPlayingLine.PrimaryPosition.Y);
double distanceFromPlayingLine = Math.Abs(line.PrimaryPosition.Y - primaryPlayingLine.PrimaryPosition.Y);
double distanceFactor;
if (lineCountDelta < 0)
{
distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight * playingLineTopOffsetFactor), 0, 1);
distanceFactor = Math.Clamp(distanceFromPlayingLine / topHeightFactor, 0, 1);
}
else
{
distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight * (1 - playingLineTopOffsetFactor)), 0, 1);
distanceFactor = Math.Clamp(distanceFromPlayingLine / bottomHeightFactor, 0, 1);
}
double yScrollDuration;
@@ -76,34 +117,34 @@ namespace BetterLyrics.WinUI3.Logic
if (lineCountDelta < 0)
{
yScrollDuration =
canvasYScrollTransition.DurationSeconds +
distanceFactor * (lyricsEffect.LyricsScrollTopDuration / 1000.0 - canvasYScrollTransition.DurationSeconds);
yScrollDelay = distanceFactor * lyricsEffect.LyricsScrollTopDelay / 1000.0;
canvasTransDuration +
distanceFactor * (scrollTopDurationSec - canvasTransDuration);
yScrollDelay = distanceFactor * scrollTopDelaySec;
}
else if (lineCountDelta == 0)
{
yScrollDuration = canvasYScrollTransition.DurationSeconds;
yScrollDuration = canvasTransDuration;
yScrollDelay = 0;
}
else
{
yScrollDuration =
canvasYScrollTransition.DurationSeconds +
distanceFactor * (lyricsEffect.LyricsScrollBottomDuration / 1000.0 - canvasYScrollTransition.DurationSeconds);
yScrollDelay = distanceFactor * lyricsEffect.LyricsScrollBottomDelay / 1000.0;
canvasTransDuration +
distanceFactor * (scrollBottomDurationSec - canvasTransDuration);
yScrollDelay = distanceFactor * scrollBottomDelaySec;
}
line.BlurAmountTransition.SetDuration(yScrollDuration);
line.BlurAmountTransition.SetDelay(yScrollDelay);
line.BlurAmountTransition.Start(
(isMouseScrolling || isSecondaryLinePlaying) ? 0 :
(lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0));
(isBlurEnabled ? (5 * distanceFactor) : 0));
line.ScaleTransition.SetDuration(yScrollDuration);
line.ScaleTransition.SetDelay(yScrollDelay);
line.ScaleTransition.Start(
isSecondaryLinePlaying ? _highlightedScale :
(lyricsEffect.IsLyricsOutOfSightEffectEnabled ?
(isOutOfSightEnabled ?
(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)) :
_highlightedScale));
@@ -136,15 +177,15 @@ 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(
(lyricsEffect.IsFanLyricsEnabled && !isMouseScrolling) ?
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > primaryPlayingLineIndex ? 1 : -1) :
(isFanEnabled && !isMouseScrolling) ?
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
@@ -152,128 +193,100 @@ namespace BetterLyrics.WinUI3.Logic
line.YOffsetTransition.Start(targetYScrollOffset);
}
var maxAnimationDurationMs = Math.Max(line.EndMs - currentPositionMs, 0);
// 字符动画
foreach (var renderChar in line.PrimaryRenderChars)
if (isWordAnimationEnabled)
{
renderChar.ProgressPlayed = renderChar.GetPlayProgress(currentPositionMs);
bool isCharPlaying = renderChar.GetIsPlaying(currentPositionMs);
bool isCharPlayingChanged = renderChar.IsPlayingLastFrame != isCharPlaying;
if (isSecondaryLinePlayingChanged || isCharPlayingChanged)
if (isSecondaryLinePlayingChanged)
{
if (lyricsEffect.IsLyricsGlowEffectEnabled)
// 辉光动画
if (isGlowEnabled && lyricsEffect.LyricsGlowEffectScope == Enums.LyricsEffectScope.LineStartToCurrentChar
&& isSecondaryLinePlaying)
{
double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
switch (lyricsEffect.LyricsGlowEffectScope)
foreach (var renderChar in line.PrimaryRenderChars)
{
case Enums.LyricsEffectScope.LineStartToCurrentChar:
if (isSecondaryLinePlayingChanged && isSecondaryLinePlaying)
{
var stepInOutDuration = Math.Min(Time.AnimationDuration.TotalMilliseconds, maxAnimationDurationMs) / 2.0 / 1000.0;
var stepLastingDuration = Math.Max(maxAnimationDurationMs / 1000.0 - stepInOutDuration * 2, 0);
renderChar.GlowTransition.Start(
new Models.Keyframe<double>(targetGlow, stepInOutDuration),
new Models.Keyframe<double>(targetGlow, stepLastingDuration),
new Models.Keyframe<double>(0, stepInOutDuration)
);
}
break;
default:
break;
var stepInOutDuration = Math.Min(Time.AnimationDuration.TotalMilliseconds, maxAnimationDurationMs) / 2.0 / 1000.0;
var stepLastingDuration = Math.Max(maxAnimationDurationMs / 1000.0 - stepInOutDuration * 2, 0);
renderChar.GlowTransition.Start(
new Models.Keyframe<double>(targetCharGlow, stepInOutDuration),
new Models.Keyframe<double>(targetCharGlow, stepLastingDuration),
new Models.Keyframe<double>(0, stepInOutDuration)
);
}
}
if (lyricsEffect.IsLyricsFloatAnimationEnabled)
// 浮动动画
if (isFloatEnabled)
{
double targetFloat =
lyricsEffect.IsLyricsFloatAnimationAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.1 : lyricsEffect.LyricsFloatAnimationAmount;
if (isSecondaryLinePlayingChanged)
foreach (var renderChar in line.PrimaryRenderChars)
{
renderChar.FloatTransition.Start(isSecondaryLinePlaying ? targetFloat : 0);
renderChar.FloatTransition.Start(isSecondaryLinePlaying ? targetCharFloat : 0);
}
if (isCharPlayingChanged)
}
}
// 字符动画
foreach (var renderChar in line.PrimaryRenderChars)
{
renderChar.ProgressPlayed = renderChar.GetPlayProgress(currentPositionMs);
bool isCharPlaying = renderChar.GetIsPlaying(currentPositionMs);
bool isCharPlayingChanged = renderChar.IsPlayingLastFrame != isCharPlaying;
if (isCharPlayingChanged)
{
if (isFloatEnabled)
{
renderChar.FloatTransition.SetDurationMs(Math.Min(lyricsEffect.LyricsFloatAnimationDuration, maxAnimationDurationMs));
renderChar.FloatTransition.Start(0);
}
}
if (isCharPlayingChanged)
{
renderChar.IsPlayingLastFrame = isCharPlaying;
}
}
}
// 音节动画
foreach (var syllable in line.PrimaryRenderSyllables)
{
bool isSyllablePlaying = syllable.GetIsPlaying(currentPositionMs);
bool isSyllablePlayingChanged = syllable.IsPlayingLastFrame != isSyllablePlaying;
if (isSyllablePlayingChanged)
// 音节动画
foreach (var syllable in line.PrimaryRenderSyllables)
{
var syllableHeight = syllable.ChildrenRenderLyricsChars.FirstOrDefault()?.LayoutRect.Height ?? 0;
bool isSyllablePlaying = syllable.GetIsPlaying(currentPositionMs);
bool isSyllablePlayingChanged = syllable.IsPlayingLastFrame != isSyllablePlaying;
if (lyricsEffect.IsLyricsScaleEffectEnabled)
if (isSyllablePlayingChanged)
{
double targetScale =
lyricsEffect.IsLyricsScaleEffectAmountAutoAdjust ? 1.15 : lyricsEffect.LyricsScaleEffectAmount / 100.0;
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
if (isScaleEnabled && isSyllablePlaying)
{
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
{
if (isSyllablePlaying)
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
{
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
renderChar.ScaleTransition.Start(
new Models.Keyframe<double>(targetScale, stepDuration),
new Models.Keyframe<double>(targetCharScale, stepDuration),
new Models.Keyframe<double>(1.0, stepDuration)
);
}
}
}
}
if (lyricsEffect.IsLyricsGlowEffectEnabled)
{
double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? syllableHeight * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
switch (lyricsEffect.LyricsGlowEffectScope)
if (isGlowEnabled && isSyllablePlaying && lyricsEffect.LyricsGlowEffectScope == Enums.LyricsEffectScope.LongDurationSyllable
&& syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration)
{
case Enums.LyricsEffectScope.LongDurationSyllable:
if (syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration)
{
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
{
if (isSyllablePlaying)
{
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
renderChar.GlowTransition.Start(
new Models.Keyframe<double>(targetGlow, stepDuration),
new Models.Keyframe<double>(0, stepDuration)
);
}
}
}
break;
default:
break;
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
{
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
renderChar.GlowTransition.Start(
new Models.Keyframe<double>(targetCharGlow, stepDuration),
new Models.Keyframe<double>(0, stepDuration)
);
}
}
syllable.IsPlayingLastFrame = isSyllablePlaying;
}
syllable.IsPlayingLastFrame = isSyllablePlaying;
}
}
// 更新动画
foreach (var renderChar in line.PrimaryRenderChars)
{
renderChar.Update(elapsedTime);
foreach (var renderChar in line.PrimaryRenderChars)
{
renderChar.Update(elapsedTime);
}
}
line.Update(elapsedTime);

View File

@@ -215,7 +215,7 @@ namespace BetterLyrics.WinUI3.Logic
lanesEndMs.Add(0);
}
lanesEndMs[assignedLane] = end;
lanesEndMs[assignedLane] = end ?? 0;
line.LaneIndex = assignedLane;
}
}

View File

@@ -74,7 +74,7 @@ namespace BetterLyrics.WinUI3.Logic
if (line == null) return state;
double lineEndMs = line.EndMs;
double lineEndMs = line.EndMs ?? 0;
// 还没到
if (currentTimeMs < line.StartMs) return state;
@@ -91,7 +91,7 @@ namespace BetterLyrics.WinUI3.Logic
switch (wordByWordEffectMode)
{
case WordByWordEffectMode.Auto:
if (line.PrimaryRenderSyllables.Count > 1)
if (line.IsPrimaryHasRealSyllableInfo)
{
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
}
@@ -106,7 +106,7 @@ namespace BetterLyrics.WinUI3.Logic
state.SyllableProgress = 1f;
return state;
case WordByWordEffectMode.Always:
if (line.PrimaryRenderSyllables.Count > 1)
if (line.IsPrimaryHasRealSyllableInfo)
{
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
}
@@ -129,8 +129,7 @@ namespace BetterLyrics.WinUI3.Logic
var timing = line.PrimaryRenderSyllables[i];
var nextTiming = (i + 1 < count) ? line.PrimaryRenderSyllables[i + 1] : null;
//double timingEndMs = timing.EndMs ?? nextTiming?.StartMs ?? lineEndMs;
double timingEndMs = timing.EndMs;
double timingEndMs = timing.EndMs ?? 0;
// 在当前字范围内
if (time >= timing.StartMs && time <= timingEndMs)

View File

@@ -7,8 +7,8 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
public class BaseLyrics
{
public int StartMs { get; set; }
public int EndMs { get; set; }
public int DurationMs => EndMs - StartMs;
public int? EndMs { get; set; } = null;
public int DurationMs => Math.Max((EndMs ?? 0) - StartMs, 0);
public string Text { get; set; } = "";
public int Length => Text.Length;

View File

@@ -9,8 +9,6 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class LyricsData
{
private static readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public List<LyricsLine> LyricsLines { get; set; } = [];
public string? LanguageCode
{
@@ -29,15 +27,5 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
LyricsLines = lyricsLines;
}
public static LyricsData GetNotfoundPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
PrimaryText = _localizationService.GetLocalizedString("LyricsNotFound"),
}]);
}
}
}

View File

@@ -19,6 +19,11 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
public string SecondaryText { get; set; } = "";
public string TertiaryText { get; set; } = "";
public new string Text => PrimaryText;
public new int StartIndex = 0;
public bool IsPrimaryHasRealSyllableInfo { get; set; } = false;
public LyricsLine()
{
for (int charStartIndex = 0; charStartIndex < PrimaryText.Length; charStartIndex++)

View File

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

View File

@@ -74,47 +74,51 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
/// </summary>
public int LaneIndex { get; set; } = 0;
public double? PrimaryLineHeight => PrimaryRenderChars.FirstOrDefault()?.LayoutRect.Height;
public bool IsPrimaryHasRealSyllableInfo { get; set; }
public RenderLyricsLine(LyricsLine lyricsLine) : base(lyricsLine)
{
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,
@@ -128,6 +132,7 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
PrimaryText = lyricsLine.PrimaryText;
SecondaryText = lyricsLine.SecondaryText;
PrimaryRenderSyllables = lyricsLine.PrimarySyllables.Select(x => new RenderLyricsSyllable(x)).ToList();
IsPrimaryHasRealSyllableInfo = lyricsLine.IsPrimaryHasRealSyllableInfo;
}
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)

View File

@@ -0,0 +1,25 @@
using BetterLyrics.Core.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models
{
public class PluginDisplayModel
{
public IPlugin Plugin { get; }
public string Name => Plugin.Name;
public string Description => Plugin.Description;
public string Id => Plugin.Id;
public string Author => Plugin.Author;
public string Version => "v1.0.0"; // 如果您的接口有 Version 字段就读接口的
public string Glyph => !string.IsNullOrEmpty(Name) ? Name.Substring(0, 1) : "?";
public PluginDisplayModel(IPlugin plugin)
{
Plugin = plugin;
}
}
}

View File

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

View File

@@ -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();

View File

@@ -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() { }

View File

@@ -40,26 +40,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
startIndex += text.Length;
}
int lineEndMs = 0;
if (syllables.Count > 0)
{
var lastSyllable = syllables[syllables.Count - 1];
if (string.IsNullOrWhiteSpace(lastSyllable.Text))
{
lineEndMs = lastSyllable.StartMs;
syllables.RemoveAt(syllables.Count - 1);
}
}
if (syllables.Count > 0)
if (syllables.Count > 1)
{
lrcLines.Add(new LyricsLine
{
StartMs = syllables[0].StartMs,
EndMs = lineEndMs,
PrimaryText = string.Concat(syllables.Select(s => s.Text)),
PrimarySyllables = syllables
PrimarySyllables = syllables,
IsPrimaryHasRealSyllableInfo = true
});
}
else
@@ -81,7 +69,13 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
content = bracketRegex!.Replace(line, "").Trim();
if (content == "//") content = "";
lrcLines.Add(new LyricsLine { StartMs = lineStartMs, PrimaryText = content });
var lyricsLine = new LyricsLine
{
StartMs = lineStartMs,
PrimaryText = content,
IsPrimaryHasRealSyllableInfo = false
};
lrcLines.Add(lyricsLine);
}
}
}
@@ -125,5 +119,6 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
}
}
}
}
}

View File

@@ -21,9 +21,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = lineRead.EndTime ?? (nextLineRead?.StartTime ?? 0),
PrimaryText = lineRead.Text,
PrimarySyllables = [],
IsPrimaryHasRealSyllableInfo = true,
};
var syllables = (lineRead as Lyricify.Lyrics.Models.SyllableLineInfo)?.Syllables;

View File

@@ -127,7 +127,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = containerStartMs,
EndMs = containerEndMs,
PrimaryText = fullOriginalText,
PrimarySyllables = syllables
PrimarySyllables = syllables,
IsPrimaryHasRealSyllableInfo = true,
});
var transSpan = container.Elements()
@@ -151,7 +152,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = startMs,
EndMs = endMs,
PrimaryText = text
PrimaryText = text,
IsPrimaryHasRealSyllableInfo = false,
});
}
else
@@ -160,7 +162,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = startMs,
EndMs = endMs,
PrimaryText = ""
PrimaryText = "",
IsPrimaryHasRealSyllableInfo = false,
});
}
}

View File

@@ -69,6 +69,9 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
LoadTransliteration(lyricsSearchResult);
GenerateTransliterationLyricsData();
EnsureEndMs(lyricsSearchResult?.Duration);
EnsureSyllables();
return _lyricsDataArr;
}
@@ -126,7 +129,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
romaji = await transliterationService.TransliterateText(main.WrappedOriginalText, PhoneticHelper.RomanCode, token);
_lyricsDataArr.FirstOrDefault()?.SetTransliteration(romaji);
transliterationSearchProvider = TransliterationSearchProvider.CutletDocker;
transliterationSearchProvider = TransliterationSearchProvider.Plugin;
}
catch (TaskCanceledException) { }
catch (Exception)
@@ -271,5 +274,84 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
}
}
private void EnsureEndMs(double? duration)
{
foreach (var lyricsData in _lyricsDataArr)
{
var lines = lyricsData.LyricsLines;
// 计算结束时间
for (int i = 0; i < lines.Count; i++)
{
// 计算行结束时间
if (lines[i].EndMs == null)
{
if (i + 1 < lines.Count)
{
lines[i].EndMs = lines[i + 1].StartMs;
}
else
{
lines[i].EndMs = (int)(duration ?? 0) * 1000;
}
}
// 计算音节结束时间
for (int j = 0; j < lines[i].PrimarySyllables.Count; j++)
{
var syllable = lines[i].PrimarySyllables[j];
if (syllable.EndMs == null)
{
if (j < lines[i].PrimarySyllables.Count - 1)
{
syllable.EndMs = lines[i].PrimarySyllables[j + 1].StartMs;
}
else
{
syllable.EndMs = lines[i].EndMs;
}
}
}
}
}
}
/// <summary>
/// Invoke this after <see cref="EnsureEndMs"/>
/// </summary>
private void EnsureSyllables()
{
foreach (var lyricsData in _lyricsDataArr)
{
if (lyricsData == null) continue;
var lines = lyricsData.LyricsLines;
if (lines == null) continue;
foreach (var line in lines)
{
if (line == null) continue;
if (line.IsPrimaryHasRealSyllableInfo) continue;
if (line.PrimarySyllables.Count > 0) continue;
var content = line.PrimaryText;
var length = content.Length;
if (length == 0) continue;
var avgSyllableDuration = line.DurationMs / length;
if (avgSyllableDuration == 0) continue;
for (int j = 0; j < length; j++)
{
line.PrimarySyllables.Add(new BaseLyrics
{
Text = content[j].ToString(),
StartIndex = j,
StartMs = line.StartMs + avgSyllableDuration * j,
EndMs = line.StartMs + avgSyllableDuration * (j + 1),
});
}
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
<linker>
<assembly fullname="System.Collections" preserve="all" />
<assembly fullname="System.IO.MemoryMappedFiles" preserve="all" />
<assembly fullname="System.Linq" preserve="all" />
<assembly fullname="System.Memory" preserve="all" />
<assembly fullname="System.ObjectModel" preserve="all" />
<assembly fullname="System.Runtime" preserve="all" />
<assembly fullname="System.Runtime.InteropServices" preserve="all" />
<assembly fullname="System.Text.RegularExpressions" preserve="all" />
<assembly fullname="System.Threading" preserve="all" />
<assembly fullname="BetterLyrics.WinUI3">
<type fullname="BetterLyrics.WinUI3.PluginConfigs.BetterLyrics_Plugins_Romaji_Config" preserve="all" />
</assembly>
</linker>

View File

@@ -0,0 +1,145 @@
// Auto-Generated by PluginAnalyzer
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace BetterLyrics.WinUI3.PluginConfigs;
internal static class BetterLyrics_Plugins_Romaji_Config
{
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.AppContext))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.AppDomain))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ArgumentException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ArgumentNullException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ArgumentOutOfRangeException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Array))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.AsyncCallback))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Boolean))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Buffer))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Byte))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Char))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.Dictionary<,>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.EqualityComparer<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.ICollection<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.IEnumerable<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.IEnumerator<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.IList<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.IReadOnlyCollection<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.IReadOnlyList<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.LinkedList<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.LinkedListNode<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.Generic.List<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.ICollection))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.IEnumerable))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.IEnumerator))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.IList))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.ObjectModel.Collection<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Collections.ObjectModel.ObservableCollection<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ComponentModel.INotifyPropertyChanged))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ComponentModel.PropertyChangedEventArgs))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ComponentModel.PropertyChangedEventHandler))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Delegate))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Diagnostics.DebuggableAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Diagnostics.DebuggableAttribute.DebuggingModes))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Diagnostics.DebuggerBrowsableAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Diagnostics.DebuggerBrowsableState))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Diagnostics.DebuggerHiddenAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Enum))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Environment))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Exception))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Func<,>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.GC))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IAsyncResult))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IComparable<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IDisposable))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IndexOutOfRangeException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Int16))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Int32))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Int64))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.InvalidOperationException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.BinaryReader))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.File))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.FileAccess))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.FileMode))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.FileShare))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.FileStream))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.HandleInheritability))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.MemoryMappedFiles.MemoryMappedFile))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.MemoryMappedFiles.MemoryMappedFileAccess))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.MemoryMappedFiles.MemoryMappedViewAccessor))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.MemoryMappedFiles.MemoryMappedViewStream))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.Path))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.Stream))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.StreamReader))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.TextReader))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.IO.UnmanagedMemoryAccessor))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Linq.Enumerable))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Linq.IGrouping<,>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Math))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.MemoryExtensions))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.MulticastDelegate))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.NotImplementedException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.NotSupportedException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Nullable<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Object))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ObjectDisposedException))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ObsoleteAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ParamArrayAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ReadOnlySpan<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.Assembly))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyCompanyAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyConfigurationAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyCopyrightAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyDescriptionAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyFileVersionAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyInformationalVersionAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyMetadataAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyProductAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyTitleAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.AssemblyTrademarkAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Reflection.DefaultMemberAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.CallerMemberNameAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.CompilationRelaxationsAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.ExtensionAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.IsReadOnlyAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.IteratorStateMachineAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.NullableAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.NullableContextAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.RefSafetyRulesAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.RuntimeCompatibilityAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.CompilerServices.TupleElementNamesAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.InteropServices.InAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.Serialization.SerializationInfo))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.Serialization.StreamingContext))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.Versioning.SupportedOSPlatformAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.Versioning.TargetFrameworkAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Runtime.Versioning.TargetPlatformAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Security.Permissions.SecurityAction))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Security.Permissions.SecurityPermissionAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Security.UnverifiableCodeAttribute))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Single))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.String))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.StringSplitOptions))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.Encoding))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.Capture))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.Group))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.GroupCollection))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.Match))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.MatchCollection))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.Regex))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.RegularExpressions.RegexOptions))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Text.StringBuilder))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Threading.Interlocked))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Threading.Tasks.Task))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Threading.Tasks.Task<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.TimeSpan))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.Type))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.UInt16))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.UInt32))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ValueTuple<,>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(System.ValueType))]
[ModuleInitializer]
internal static void Initialize()
{
// This method runs automatically on startup.
}
}

View File

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

View File

@@ -26,12 +26,12 @@ namespace BetterLyrics.WinUI3.Renderer
Color fgColor,
LyricsEffectSettings settings)
{
DrawPhonetic(ds, textOnlyLayer, line);
DrawOriginalText(control, ds, textOnlyLayer, line, playbackState, bgColor, fgColor, settings);
DrawTranslated(ds, textOnlyLayer, line);
DrawTertiaryText(ds, textOnlyLayer, line);
DrawPrimaryText(control, ds, textOnlyLayer, line, playbackState, bgColor, fgColor, settings);
DrawSecondaryText(ds, textOnlyLayer, line);
}
private void DrawPhonetic(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
private void DrawTertiaryText(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.TertiaryTextLayout == null) return;
@@ -65,7 +65,7 @@ namespace BetterLyrics.WinUI3.Renderer
});
}
private void DrawTranslated(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
private void DrawSecondaryText(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.SecondaryTextLayout == null) return;
@@ -99,7 +99,7 @@ namespace BetterLyrics.WinUI3.Renderer
});
}
private void DrawOriginalText(
private void DrawPrimaryText(
ICanvasResourceCreator resourceCreator,
CanvasDrawingSession ds,
ICanvasImage source,

View File

@@ -22,7 +22,6 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService
{
_logger.LogInformation("RefreshLyricsAsync");
CurrentLyricsSearchResult = null;
CurrentLyricsData = LyricsData.GetLoadingPlaceholder();
if (CurrentSongInfo != SongInfoExtensions.Placeholder)

View File

@@ -11,6 +11,7 @@ using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.DiscordService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.PlayHistoryService;
using BetterLyrics.WinUI3.Services.SettingsService;
@@ -19,6 +20,7 @@ using BetterLyrics.WinUI3.Services.TransliterationService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
@@ -206,7 +208,6 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService
private void InitMediaManager()
{
_mediaManager.Start();
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSession(x.Value.Id));
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
@@ -696,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();

View File

@@ -55,6 +55,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService
existingItem.Title = result.Title;
existingItem.Artist = result.Artist;
existingItem.Album = result.Album;
existingItem.Duration = result.Duration;
existingItem.TransliterationProvider = result.TransliterationProvider;
existingItem.TranslationProvider = result.TranslationProvider;

View File

@@ -1,5 +1,7 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.Core.Interfaces;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
@@ -9,6 +11,7 @@ using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Providers;
using BetterLyrics.WinUI3.Services.FileSystemService;
using BetterLyrics.WinUI3.Services.LyricsCacheService;
using BetterLyrics.WinUI3.Services.PluginService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.SongSearchMapService;
using Lyricify.Lyrics.Helpers;
@@ -29,12 +32,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
private readonly HttpClient _amllTtmlDbHttpClient;
private readonly HttpClient _lrcLibHttpClient;
private readonly AppleMusic _appleMusic;
private readonly Providers.AppleMusic _appleMusic;
private readonly ISettingsService _settingsService;
private readonly IFileSystemService _fileSystemService;
private readonly ILyricsCacheService _lyricsCacheService;
private readonly ISongSearchMapService _songSearchMapService;
private readonly IPluginService _pluginService;
private readonly ILogger _logger;
public LyricsSearchService(
@@ -42,6 +46,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
IFileSystemService fileSystemService,
ILyricsCacheService lyricsCacheService,
ISongSearchMapService songSearchMapService,
IPluginService pluginService,
ILogger<LyricsSearchService> logger
)
{
@@ -49,15 +54,16 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
_fileSystemService = fileSystemService;
_lyricsCacheService = lyricsCacheService;
_songSearchMapService = songSearchMapService;
_pluginService = pluginService;
_logger = logger;
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.BetterLyricsGitHub})"
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Link.BetterLyricsGitHub})"
);
_amllTtmlDbHttpClient = new();
_appleMusic = new AppleMusic();
_appleMusic = new Providers.AppleMusic();
}
private static bool IsAmllTtmlDbIndexInvalid()
@@ -206,11 +212,26 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
_logger.LogInformation("SearchAllAsync {SongInfo}", songInfo);
var results = new List<LyricsCacheItem>();
foreach (var provider in Enum.GetValues<LyricsSearchProvider>())
{
if (provider == LyricsSearchProvider.Plugin) continue;
var searchResult = await SearchSingleAsync(songInfo, provider, checkCache, token);
results.Add(searchResult);
}
foreach (var plugin in _pluginService.Plugins)
{
if (token.IsCancellationRequested) break;
if (plugin is ILyricsSearchPlugin lyricsSearchPlugin)
{
var pluginResult = await SearchPluginAsync(songInfo, lyricsSearchPlugin, token);
results.Add(pluginResult);
}
}
return results;
}
@@ -402,6 +423,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
string? rawLyricFile = null;
string? bestNcmMusicId = null;
await foreach (var line in File.ReadLinesAsync(PathHelper.AmllTtmlDbIndexPath))
{
if (string.IsNullOrWhiteSpace(line))
@@ -416,6 +439,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
string? title = null;
string? artist = null;
string? album = null;
string? ncmMusicId = null;
foreach (var meta in metadataArr.EnumerateArray())
{
@@ -429,6 +453,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
artist = string.Join(" / ", valueArr.EnumerateArray());
if (key == "album" && valueArr.GetArrayLength() > 0)
album = valueArr[0].GetString();
if (key == "ncmMusicId" && valueArr.GetArrayLength() > 0)
ncmMusicId = valueArr[0].GetString();
}
int score = MetadataComparer.CalculateScore(songInfo, new LyricsCacheItem
@@ -436,12 +462,12 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
Title = title,
Artist = artist,
Album = album,
Duration = 0,
});
if (score > lyricsSearchResult.MatchPercentage)
{
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
{
bestNcmMusicId = ncmMusicId;
rawLyricFile = rawLyricFileProp.GetString();
lyricsSearchResult.Title = title;
lyricsSearchResult.Artist = artist;
@@ -458,19 +484,28 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return lyricsSearchResult;
}
// 下载歌词内容
var url = $"{_settingsService.AppSettings.GeneralSettings.AmllTtmlDbBaseUrl}/{Constants.AmllTTmlDB.QueryPrefix}/{rawLyricFile}";
var url = $"{_settingsService.AppSettings.GeneralSettings.AmllTtmlDbBaseUrl}/{AmllTTmlDB.QueryPrefix}/{rawLyricFile}";
lyricsSearchResult.Reference = url;
try
{
// 下载写入歌词
using var response = await _amllTtmlDbHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return lyricsSearchResult;
}
string lyrics = await response.Content.ReadAsStringAsync();
lyricsSearchResult.Raw = lyrics;
// 反查时长
if (bestNcmMusicId != null && lyricsSearchResult.Duration == null)
{
var tmp = await SearchQQNeteaseKugouAsync(
((SongInfo)songInfo.Clone()).WithSongId($"{ExtendedGenreFiled.NetEaseCloudMusicTrackID}{bestNcmMusicId}"),
Searchers.Netease);
lyricsSearchResult.Duration = tmp.Duration;
lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult);
}
}
catch
{
@@ -633,7 +668,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
lyricsSearchResult.Title = result?.Title;
lyricsSearchResult.Artist = string.Join(" / ", result?.Artists ?? []);
lyricsSearchResult.Artist = result?.Artist;
lyricsSearchResult.Album = result?.Album;
lyricsSearchResult.Duration = result?.DurationMs / 1000;
@@ -658,5 +693,40 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return lyricsSearchResult;
}
private async Task<LyricsCacheItem> SearchPluginAsync(SongInfo songInfo, ILyricsSearchPlugin plugin, CancellationToken token)
{
var cacheItem = new LyricsCacheItem
{
Provider = LyricsSearchProvider.Plugin,
};
try
{
var result = await plugin.GetLyricsAsync(songInfo.Title, songInfo.Artist, songInfo.Album, songInfo.Duration);
if (result != null && !string.IsNullOrEmpty(result.Raw))
{
cacheItem.Title = result.Title;
cacheItem.Artist = result.Artist;
cacheItem.Album = result.Album;
cacheItem.Duration = result.Duration;
cacheItem.Raw = result.Raw;
cacheItem.Translation = result.Translation;
cacheItem.Transliteration = result.Transliteration;
cacheItem.Reference = result.Reference ?? "about:blank";
cacheItem.MatchPercentage = MetadataComparer.CalculateScore(songInfo, cacheItem);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Plugin {PluginName} failed to search", plugin.Name);
}
return cacheItem;
}
}
}

View File

@@ -0,0 +1,16 @@
using BetterLyrics.Core.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Services.PluginService
{
public interface IPluginService
{
IReadOnlyList<IPlugin> Plugins { get; }
void LoadPlugins();
void InstallPlugin(string zipPath);
void UninstallPlugin(string pluginId);
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
namespace BetterLyrics.WinUI3.Services.PluginService
{
public class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly? Load(AssemblyName 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)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
// return IntPtr.Zero to use the default AssemblyLoadContext
return IntPtr.Zero;
}
}
}

View File

@@ -0,0 +1,195 @@
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 Windows.Storage;
namespace BetterLyrics.WinUI3.Services.PluginService
{
public class PluginService : IPluginService
{
private List<IPlugin> _plugins = new();
public IReadOnlyList<IPlugin> Plugins => _plugins;
private Dictionary<string, PluginLoadContext> _pluginContexts = new();
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");
if (!Directory.Exists(pluginsRoot)) Directory.CreateDirectory(pluginsRoot);
var pluginFolders = Directory.GetDirectories(pluginsRoot);
foreach (var folder in pluginFolders)
{
var dllFiles = Directory.GetFiles(folder, "*.dll");
foreach (var dllPath in dllFiles)
{
if (_loadedDllPaths.Contains(dllPath)) continue;
TryLoadPlugin(dllPath);
}
}
}
private void TryLoadPlugin(string dllPath)
{
// 1. Create Context
var loadContext = new PluginLoadContext(dllPath);
try
{
var assembly = loadContext.LoadFromAssemblyPath(dllPath);
int loadedCount = 0; // Track successfully loaded plugins
// 2. [Safety Check] Safely retrieve types
IEnumerable<Type> types;
try
{
types = assembly.GetExportedTypes();
}
catch (ReflectionTypeLoadException ex)
{
// If some types fail to load, only keep the usable ones!
types = ex.Types.Where(t => t != null)!;
foreach (var loaderEx in ex.LoaderExceptions)
{
_logger.LogWarning("Partial type loading failure in DLL {path}: {msg}", dllPath, loaderEx?.Message);
}
}
foreach (var type in types)
{
// 3. Check if it is a valid plugin class
if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsAbstract)
{
IPlugin? plugin = null;
try
{
// 4. [Instantiation Guard] Prevent plugin constructor errors from crashing the main app
plugin = (IPlugin?)Activator.CreateInstance(type);
if (plugin == null) continue;
// 5. Check for duplicate IDs
if (_plugins.Any(p => p.Id == plugin.Id))
{
_logger.LogWarning("Skipping duplicate plugin: {id} ({path})", plugin.Id, dllPath);
// Explicitly break reference to aid Unload
plugin = null;
continue;
}
// 6. Initialize
plugin.Initialize();
// 7. Add to collection
_plugins.Add(plugin);
_pluginContexts.Add(plugin.Id, loadContext);
loadedCount++;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize/instantiate plugin {type}", type.FullName);
// If plugin initialization fails, explicitly dispose if possible
if (plugin is IDisposable disposable)
{
try { disposable.Dispose(); } catch { }
}
plugin = null; // Break reference
}
}
}
// 8. Finalize: If no usable plugins were found in this DLL, unload Context
if (loadedCount > 0)
{
_loadedDllPaths.Add(dllPath);
_logger.LogInformation("Successfully loaded {count} plugin(s) from {path}", loadedCount, dllPath);
}
else
{
_logger.LogWarning("No valid plugins found in {path}. Unloading context.", dllPath);
// No plugin instances remain alive at this point, safe to unload
loadContext.Unload();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load assembly: {path}", dllPath);
// Only unload here if the loading process completely crashed
try { loadContext.Unload(); } catch { }
}
}
public void UninstallPlugin(string pluginId)
{
var plugin = _plugins.FirstOrDefault(p => p.Id == pluginId);
if (plugin == null) return;
var dllPath = plugin.GetType().Assembly.Location;
var folderPath = Path.GetDirectoryName(dllPath);
_plugins.Remove(plugin);
_loadedDllPaths.Remove(dllPath);
if (_pluginContexts.TryGetValue(pluginId, out var context))
{
context.Unload();
_pluginContexts.Remove(pluginId);
}
GC.Collect();
GC.WaitForPendingFinalizers();
if (Directory.Exists(folderPath))
{
try
{
Directory.Delete(folderPath, true);
}
catch (IOException ex)
{
_logger.LogError(ex, "Failed to delete plugin folder {}", folderPath);
}
}
}
public void InstallPlugin(string zipPath)
{
string pluginsRoot = Path.Combine(ApplicationData.Current.LocalFolder.Path, "plugins");
string folderName = Path.GetFileNameWithoutExtension(zipPath);
string installDir = Path.Combine(pluginsRoot, folderName);
if (Directory.Exists(installDir))
{
// TODO: 最好是先 Find plugin by path -> UninstallPlugin(id)
// 否则文件被锁住无法 Delete
try
{
Directory.Delete(installDir, true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to delete existing plugin folder {}", installDir);
throw;
}
}
Directory.CreateDirectory(installDir);
ZipFile.ExtractToDirectory(zipPath, installDir);
}
}
}

View File

@@ -1,7 +1,11 @@
using BetterLyrics.WinUI3.Models.Http;
using BetterLyrics.Core.Interfaces;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models.Http;
using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.Services.PluginService;
using BetterLyrics.WinUI3.Services.SettingsService;
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
@@ -12,37 +16,31 @@ namespace BetterLyrics.WinUI3.Services.TransliterationService
public class TransliterationService : ITransliterationService
{
private readonly ISettingsService _settingsService;
private readonly IPluginService _pluginService;
private readonly HttpClient _httpClient;
public TransliterationService(ISettingsService settingsService)
public TransliterationService(ISettingsService settingsService, IPluginService pluginService)
{
_settingsService = settingsService;
_pluginService = pluginService;
_httpClient = new HttpClient();
}
public async Task<string> TransliterateText(string text, string targetLangCode, CancellationToken token)
{
string? result = null;
if (string.IsNullOrWhiteSpace(text))
{
throw new Exception(text + " is empty or null.");
}
if (string.IsNullOrEmpty(_settingsService.AppSettings.TranslationSettings.CutletDockerServer))
var plugin = (ILyricsTransliterationPlugin?)_pluginService.Plugins.FirstOrDefault(x => x is ILyricsTransliterationPlugin);
if (plugin != null)
{
throw new Exception("cutlet-docker server URL is not set in settings.");
result = await plugin.GetTransliterationAsync(text, PhoneticHelper.RomanCode);
}
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;
return result ?? "";
}
}
}

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>مثال: http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>المدة</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>جارٍ تحميل الكلمات...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>لا يمكن عرض المقطوعات لأن المسار غير موجود</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>سياسة الخصوصية</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>منطقة قابلة للسحب</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>اختصار تبديل حالة نافذة الكلمات</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>استراتيجية الرسوم المتحركة كلمة بكلمة</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>دائماً</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>السيارات</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>أبداً</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>سيؤثر ضبط هذه القيمة على نتائج البحث المتسلسل والبحث بأفضل تطابق، ولكنه لن يؤثر على نتائج البحث في واجهة البحث اليدوي عن الكلمات</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>النشاط بالساعة</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>كل الوقت</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>مخصص</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>z. B. http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Dauer</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Songtext wird geladen...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Titel können nicht angezeigt werden, da der Pfad nicht existiert</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Datenschutzrichtlinie</value>
</data>
@@ -798,35 +810,38 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Ziehbereich</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>Back Ease In-Out</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>Bounce Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>Back</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>Circular Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>Bounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>Cubic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>Circular</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>Elastic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>Cubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>Exponential Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>Elastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>Quadratic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>Exponential</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>Quartic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>Quadratic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>Quintic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>Quartic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>Sine Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>Quintic</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>Sine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>Linear</value>
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Tastenkürzel für Songtext-Fensterstatuswechsel</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Wort-für-Wort-Animationsstrategie</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Immer</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Niemals</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Das Anpassen dieses Wertes beeinflusst die sequenzielle Suche und die Suche nach der besten Übereinstimmung, hat jedoch keinen Einfluss auf die Suchergebnisse in der manuellen Songtext-Suchoberfläche</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Aktivität nach Stunden</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Alle Zeiten</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Benutzerdefiniert</value>
</data>

View File

@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Unable to view tracks because the path does not exist</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value>Install plugin</value>
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value>No plugins installed</value>
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value>Plugin Manager</value>
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value>Uninstall plugin</value>
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Privacy Policy</value>
</data>
@@ -798,35 +810,47 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Draggable Area</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>Back Ease In-Out</value>
<data name="SettingsPageEasingMode.Header" xml:space="preserve">
<value>Easing Mode</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>Bounce Ease In-Out</value>
<data name="SettingsPageEasingModeIn.Content" xml:space="preserve">
<value>In</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>Circular Ease In-Out</value>
<data name="SettingsPageEasingModeInOut.Content" xml:space="preserve">
<value>In-Out</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>Cubic Ease In-Out</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value>Out</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>Elastic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>Back</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>Exponential Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>Bounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>Quadratic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>Circular</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>Quartic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>Cubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>Quintic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>Elastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>Sine Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>Exponential</value>
</data>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>Quadratic</value>
</data>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>Quartic</value>
</data>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>Quintic</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>Sine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>Linear</value>
@@ -924,9 +948,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>
@@ -1254,6 +1275,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>
@@ -1479,6 +1503,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Activity by Hour</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>All Time</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Custom</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>ej. http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Duración</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Cargando letra...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>No se pueden ver las pistas porque la ruta no existe</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Política de privacidad</value>
</data>
@@ -798,35 +810,41 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Área arrastrable</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>Back Ease In-Out</value>
<data name="SettingsPageEasingMode.Header" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>Bounce Ease In-Out</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>Circular Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>Back</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>Cubic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>Bounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>Elastic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>Circular</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>Exponential Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>Cubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>Quadratic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>Elastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>Quartic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>Exponential</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>Quintic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>Quadratic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>Sine Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>Quartic</value>
</data>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>Quintic</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>Sine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>Lineal</value>
@@ -924,9 +942,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>
@@ -1138,16 +1153,16 @@
<value>Atajo de cambio de estado de ventana de letras</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Estrategia de animación palabra por palabra</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Siempre</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Nunca</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Ajustar este valor afectará a la búsqueda secuencial y a los resultados de la búsqueda de mejor coincidencia, pero no afectará a los resultados de búsqueda en la interfaz de búsqueda manual de letras</value>
@@ -1254,6 +1269,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>
@@ -1479,6 +1497,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Actividad por horas</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Todos los tiempos</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>A medida</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>ex : http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Durée de l'accord</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Chargement des paroles...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Impossible d'afficher les pistes car le chemin n'existe pas</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Politique de confidentialité</value>
</data>
@@ -798,35 +810,38 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Zone déplaçable</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>Back Ease In-Out</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>Bounce Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>Back</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>Circular Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>Bounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>Cubic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>Circular</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>Elastic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>Cubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>Exponential Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>Elastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>Quadratic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>Exponential</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>Quartic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>Quadratic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>Quintic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>Quartic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>Sine Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>Quintic</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>Sine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>Linéaire</value>
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Raccourci changement état fenêtre paroles</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Stratégie d'animation mot à mot</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Toujours</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Jamais</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>L'ajustement de cette valeur affectera les résultats de la recherche séquentielle et de la meilleure correspondance, mais n'affectera pas les résultats de la recherche manuelle</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Activité par heure</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Tout le temps</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Sur mesure</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>उदाहरण http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>समयांतराल</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>बोल लोड हो रहे हैं...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>ट्रैक देखने में असमर्थ क्योंकि पथ मौजूद नहीं है</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>गोपनीयता नीति</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>खींचने योग्य क्षेत्र</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>बोल विंडो स्थिति स्विच शॉर्टकट</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>शब्द-दर-शब्द एनिमेशन रणनीति</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>हमेशा</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>स्वतः</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>कभी नहीं</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>इस मान को समायोजित करने से अनुक्रमिक खोज और सर्वोत्तम मिलान खोज परिणाम प्रभावित होंगे, लेकिन मैनुअल बोल खोज इंटरफ़ेस में खोज परिणाम प्रभावित नहीं होंगे</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>घंटे के हिसाब से गतिविधि</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>अब तक के सारे</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>कस्टम</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>Contoh: http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Durasi</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Memuat lirik...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Tidak dapat melihat trek karena jalur tidak ada</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Kebijakan Privasi</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Area yang Dapat Diseret</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Pintasan Pengalih Status Jendela Lirik</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Strategi Animasi Kata per Kata</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Selalu</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>Otomatis</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Tidak pernah</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Menyesuaikan nilai ini akan memengaruhi hasil pencarian berurutan dan pencarian kecocokan terbaik, tetapi tidak akan memengaruhi hasil pencarian di antarmuka pencarian manual lirik</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Aktivitas per Jam</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Semua Waktu</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Kustom</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>パスが存在しないため、曲を表示できません</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>個人情報保護方針</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>ドラッグ可能エリア</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>バックイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>バウンスイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>サーキュラーイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>キュービックイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>エラスティックイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>エキスポネンシャルイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>クアドラティックイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>クオートイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>クイントイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>サインイーズ イン アウト</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1051,7 +1063,7 @@
<value>カスタム</value>
</data>
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
<value>フォントファミリー</value>
<value>フォント</value>
</data>
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
<value>フォントサイズ</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>アクティブ時間帯</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>全期間</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>カスタム</value>
</data>
@@ -1492,7 +1510,7 @@
<value>最多アクティブ</value>
</data>
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
<value>再生記録中...</value>
<value>再生履歴記録中...</value>
</data>
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
<value>再生ソース</value>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>예: http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>기간</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>가사 불러오는 중...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>경로가 존재하지 않아 트랙을 볼 수 없습니다</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>개인정보 처리방침</value>
</data>
@@ -798,35 +810,38 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>드래그 가능 영역</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>Back Ease In-Out</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>Bounce Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>Back</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>Circular Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>Bounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>Cubic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>Circular</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>Elastic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>Cubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>Exponential Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>Elastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>Quadratic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>Exponential</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>Quartic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>Quadratic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>Quintic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>Quartic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>Sine Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>Quintic</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>Sine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>Linear (선형)</value>
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>가사 창 상태 전환 단축키</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>단어별 애니메이션 전략</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>항상</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>자동</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>절대로</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>이 값을 조정하면 순차 검색 및 최적 일치 검색 결과에 영향을 미치지만 수동 가사 검색 인터페이스의 검색 결과에는 영향을 미치지 않습니다</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>시간별 활동</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>모든 시간</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>사용자 지정</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>Contoh: http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Durasi</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Memuatkan lirik...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Tidak dapat melihat trek kerana laluan tidak wujud</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Dasar Privasi</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Kawasan Boleh Diseret</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Pintasan Tukar Status Tetingkap Lirik</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Strategi Animasi Perkataan demi Perkataan</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Sentiasa</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>Automatik</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Tak pernah</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Melaraskan nilai ini akan mempengaruhi hasil carian jujukan dan carian padanan terbaik, tetapi tidak akan mempengaruhi hasil carian dalam antara muka carian lirik manual</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Aktiviti mengikut Jam</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Semua</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Tersuai</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>Exemplo: http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Duração</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>A carregar a letra...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Não foi possível ver as faixas porque o caminho não existe</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Política de Privacidade</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Área de Arrastar</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Atalho de Alternância de Estado da Janela</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Estratégia de animação palavra a palavra</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Sempre</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>Automóvel</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Nunca</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Ajustar este valor afetará os resultados da pesquisa sequencial e de melhor correspondência, mas não afetará os resultados na interface de pesquisa manual de letras</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Atividade por hora</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Todo o tempo</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Personalizado</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>например, http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Продолжительность</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Загрузка текста...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Невозможно просмотреть треки, так как путь не существует</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Политика конфиденциальности</value>
</data>
@@ -798,35 +810,38 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Перетаскиваемая область</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>Back Ease In-Out</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>Bounce Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>Back</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>Circular Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>Bounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>Cubic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>Circular</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>Elastic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>Cubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>Exponential Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>Elastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>Quadratic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>Exponential</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>Quartic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>Quadratic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>Quintic Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>Quartic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>Sine Ease In-Out</value>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>Quintic</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>Sine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>Linear</value>
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Горячая клавиша переключения состояния окна</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Стратегия словесной анимации</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Всегда</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>Авто</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Никогда</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Настройка этого значения повлияет на результаты последовательного поиска и поиска лучшего совпадения, но не повлияет на результаты в интерфейсе ручного поиска</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Активность по часам</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Все время</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Пользовательское</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>ตัวอย่าง http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>ระยะเวลา</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>กำลังโหลดเนื้อเพลง...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>ไม่สามารถดูแทร็กได้เนื่องจากเส้นทางไม่มีอยู่</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>นโยบายความเป็นส่วนตัว</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>พื้นที่ลาก</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>ทางลัดสลับสถานะหน้าต่างเนื้อเพลง</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>กลยุทธ์การสร้างแอนิเมชันแบบคำต่อคำ</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>เสมอ</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>ออโต้</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>ไม่เคย</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>การปรับค่านี้จะมีผลกับผลการค้นหาแบบตามลำดับและแบบตรงกันที่สุด แต่จะไม่มีผลกับผลการค้นหาในหน้าค้นหาเนื้อเพลงด้วยตนเอง</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>กิจกรรมตามชั่วโมง</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>ตลอดเวลา</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>กำหนดเอง</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>Ví dụ: http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>Thời gian</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Đang tải lời bài hát...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>Không thể xem các bài hát vì đường dẫn không tồn tại</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>Chính sách bảo mật</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>Vùng có thể kéo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>EaseInOutBack</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>EaseInOutBounce</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>EaseInOutCirc</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>EaseInOutCubic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>EaseInOutElastic</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>EaseInOutExpo</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>EaseInOutQuad</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>EaseInOutQuart</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>EaseInOutQuint</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>EaseInOutSine</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>Phím tắt chuyển trạng thái cửa sổ lời bài hát</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>Chiến lược hoạt hình từng từ</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>Luôn luôn</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>Tự động</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>Không bao giờ</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Điều chỉnh giá trị này sẽ ảnh hưởng đến kết quả tìm kiếm tuần tự và khớp nhất, nhưng sẽ không ảnh hưởng đến kết quả tìm kiếm trong giao diện tìm kiếm lời bài hát thủ công</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>Hoạt động theo giờ</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>Mọi thời đại</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>Tùy chỉnh</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>无法查看曲目,因为路径不存在</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value>安装插件</value>
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value>未安装任何插件</value>
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value>插件管理</value>
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value>卸载插件</value>
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>隐私政策</value>
</data>
@@ -798,35 +810,47 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>可拖动区域</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<value>回弹缓入缓出</value>
<data name="SettingsPageEasingMode.Header" xml:space="preserve">
<value>缓动类型</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<value>弹跳缓入缓出</value>
<data name="SettingsPageEasingModeIn.Content" xml:space="preserve">
<value>缓入</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<value>圆形缓入缓出</value>
<data name="SettingsPageEasingModeInOut.Content" xml:space="preserve">
<value>缓出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<value>三次缓入缓出</value>
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value>缓入缓出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<value>弹性缓入缓出</value>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>弹</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<value>指数缓入缓出</value>
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>弹跳</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<value>二次缓入缓出</value>
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>圆形</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<value>四次缓入缓出</value>
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>三次</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<value>五次缓入缓出</value>
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>弹性</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<value>正弦缓入缓出</value>
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>指数</value>
</data>
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>二次</value>
</data>
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>四次</value>
</data>
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>五次</value>
</data>
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>正弦</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
<value>线性</value>
@@ -924,9 +948,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>
@@ -1254,6 +1275,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>
@@ -1479,6 +1503,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>活跃时段</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>全部时间</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>自定义</value>
</data>

View File

@@ -59,46 +59,46 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -244,7 +244,7 @@
<value>例如 http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
<value>時間長度</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>載入歌詞中...</value>
@@ -555,6 +555,18 @@
<data name="PlaylistViewFailed" xml:space="preserve">
<value>無法檢視曲目,因為路徑不存在</value>
</data>
<data name="PluginManagerControlInstall.Content" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlNoPluginsInstalled.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlTitle.Text" xml:space="preserve">
<value />
</data>
<data name="PluginManagerControlUninstall.Content" xml:space="preserve">
<value />
</data>
<data name="PrivacyPolicy.Content" xml:space="preserve">
<value>隱私權政策</value>
</data>
@@ -798,34 +810,37 @@
<data name="SettingsPageDragArea.Header" xml:space="preserve">
<value>可拖曳區域</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
<data name="SettingsPageEasingModeOut.Content" xml:space="preserve">
<value />
</data>
<data name="SettingsPageEasingTypeEaseBack.Content" xml:space="preserve">
<value>回彈緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseBounce.Content" xml:space="preserve">
<value>彈跳緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCirc.Content" xml:space="preserve">
<value>圓形緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseCubic.Content" xml:space="preserve">
<value>三次緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseElastic.Content" xml:space="preserve">
<value>彈性緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseExpo.Content" xml:space="preserve">
<value>指數緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuad.Content" xml:space="preserve">
<value>二次緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuart.Content" xml:space="preserve">
<value>四次緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseQuint.Content" xml:space="preserve">
<value>五次緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
<data name="SettingsPageEasingTypeEaseSine.Content" xml:space="preserve">
<value>正弦緩入緩出</value>
</data>
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
@@ -924,9 +939,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>
@@ -1138,16 +1150,16 @@
<value>歌詞視窗狀態切換快速鍵</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
<value>Word-by-word Animation Strategy</value>
<value>逐字動畫策略</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
<value>Always</value>
<value>永遠</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
<value>Auto</value>
<value>自動</value>
</data>
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
<value>Never</value>
<value>從不</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>調整此值將影響順序搜尋和最佳符合搜尋結果,但不會影響手動歌詞搜尋介面中的搜尋結果</value>
@@ -1254,6 +1266,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>
@@ -1479,6 +1494,9 @@
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
<value>每小時的活動</value>
</data>
<data name="StatsDashboardControlAllTime.Content" xml:space="preserve">
<value>所有時間</value>
</data>
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
<value>自訂</value>
</data>

View File

@@ -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()
{

View File

@@ -186,6 +186,9 @@ namespace BetterLyrics.WinUI3.ViewModels
case StatsRange.ThisYear:
startLocal = new DateTime(nowLocal.Year, 1, 1);
break;
case StatsRange.AllTime:
startLocal = DateTime.MinValue;
break;
}
CustomStartDate = startLocal.Date;

View File

@@ -56,6 +56,12 @@
Glyph=&#xE9D2;}"
Tag="Stats" />
<NavigationViewItem
x:Uid="SettingsPagePlugins"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE74C;}"
Tag="Plugins" />
<NavigationViewItem
x:Uid="SettingsPageAbout"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
@@ -96,6 +102,11 @@
<uc:StatsDashboardControl />
</controls:Case>
<!-- Plugins -->
<controls:Case Value="Plugins">
<uc:PluginManagerControl />
</controls:Case>
<!-- About -->
<controls:Case Value="About">
<uc:AboutControl />

View File

@@ -1,85 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.36105.23
MinimumVisualStudioVersion = 10.0.40219.1
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BetterLyrics.WinUI3 (Package)", "BetterLyrics.WinUI3\BetterLyrics.WinUI3 (Package)\BetterLyrics.WinUI3 (Package).wapproj", "{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.WinUI3", "BetterLyrics.WinUI3\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj", "{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impressionist", "Impressionist\Impressionist\Impressionist.csproj", "{A678BCA5-03DE-71E4-73C1-388B7550E4E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorThief.WinUI3", "ColorThief.WinUI3\ColorThief.WinUI3.csproj", "{8F2FE667-2D91-428E-0630-05E6330F9625}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|ARM64.Build.0 = Debug|ARM64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|ARM64.Deploy.0 = Debug|ARM64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|x64.ActiveCfg = Debug|x64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|x64.Build.0 = Debug|x64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|x64.Deploy.0 = Debug|x64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|x86.ActiveCfg = Debug|x86
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|x86.Build.0 = Debug|x86
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Debug|x86.Deploy.0 = Debug|x86
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|ARM64.ActiveCfg = Release|ARM64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|ARM64.Build.0 = Release|ARM64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|ARM64.Deploy.0 = Release|ARM64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|x64.ActiveCfg = Release|x64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|x64.Build.0 = Release|x64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|x64.Deploy.0 = Release|x64
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|x86.ActiveCfg = Release|x86
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|x86.Build.0 = Release|x86
{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}.Release|x86.Deploy.0 = Release|x86
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Debug|ARM64.ActiveCfg = Debug|ARM64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Debug|ARM64.Build.0 = Debug|ARM64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Debug|x64.ActiveCfg = Debug|x64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Debug|x64.Build.0 = Debug|x64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Debug|x86.ActiveCfg = Debug|x86
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Debug|x86.Build.0 = Debug|x86
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|ARM64.ActiveCfg = Release|ARM64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|ARM64.Build.0 = Release|ARM64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x64.ActiveCfg = Release|x64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x64.Build.0 = Release|x64
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x86.ActiveCfg = Release|x86
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x86.Build.0 = Release|x86
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|ARM64.Build.0 = Debug|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x64.ActiveCfg = Debug|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x64.Build.0 = Debug|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x86.ActiveCfg = Debug|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x86.Build.0 = Debug|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|ARM64.ActiveCfg = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|ARM64.Build.0 = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x64.ActiveCfg = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x64.Build.0 = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x86.ActiveCfg = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x86.Build.0 = Release|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|ARM64.Build.0 = Debug|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x64.ActiveCfg = Debug|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x64.Build.0 = Debug|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x86.ActiveCfg = Debug|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x86.Build.0 = Debug|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Release|ARM64.ActiveCfg = Release|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Release|ARM64.Build.0 = Release|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x64.ActiveCfg = Release|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x64.Build.0 = Release|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x86.ActiveCfg = Release|Any CPU
{8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0FD73F15-ED69-4DC2-9FD3-A4F03AF172A4}
EndGlobalSection
EndGlobal

21
BetterLyrics.slnx Normal file
View File

@@ -0,0 +1,21 @@
<Solution>
<Configurations>
<Platform Name="ARM64" />
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Project Path="BetterLyrics.Core/BetterLyrics.Core.csproj" />
<Project Path="BetterLyrics.Plugins.Romaji/BetterLyrics.Plugins.Romaji.csproj" />
<Project Path="BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package).wapproj" Type="c7167f0d-bc9f-4e6e-afe1-012c56b48db5">
<Deploy />
</Project>
<Project Path="BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
<Platform Solution="*|x86" Project="x86" />
</Project>
<Project Path="ColorThief.WinUI3/ColorThief.WinUI3.csproj" />
<Project Path="Impressionist/Impressionist/Impressionist.csproj" />
<Project Path="PluginAnalyzer/PluginAnalyzer.csproj" Id="8ee58bf7-807d-486c-8d7d-c00bb9ec2b81" />
<Project Path="RomajiConverter.Core/RomajiConverter.Core.csproj" />
</Solution>

View File

@@ -4,11 +4,6 @@
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<RootNamespace>ColorThief.WinUI3</RootNamespace>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
</ItemGroup>
</Project>

254
PluginAnalyzer/Program.cs Normal file
View File

@@ -0,0 +1,254 @@
using Mono.Cecil;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
class Program
{
// Global sets
static HashSet<string> _mergedAssemblies = new HashSet<string>();
static HashSet<string> _mergedSystemTypes = new HashSet<string>();
static string _currentScanDirectory = "";
static void Main(string[] args)
{
// =========================================================
// 🚀 MODE 1: CLI Automation Mode (被 VS 编译调用时)
// =========================================================
if (args.Length > 0)
{
try
{
string scanPath = args[0].Replace("\"", "").Trim();
string targetNs = args.Length > 1 ? args[1] : "BetterLyrics.WinUI3";
string prefix = args.Length > 2 ? args[2] : "Plugin";
// 读取第4个参数作为输出目录如果没传就默认用扫描目录
string outputDir = args.Length > 3 ? args[3].Replace("\"", "").Trim() : scanPath;
Console.WriteLine($"[Analyzer] Scanning: {scanPath}");
Console.WriteLine($"[Analyzer] Output to: {outputDir}");
RunBatch(scanPath, targetNs, prefix, outputDir, silent: true);
}
catch (Exception ex)
{
Console.WriteLine($"[Analyzer] Error: {ex.Message}");
Environment.Exit(1);
}
return;
}
// =========================================================
// 🚀 MODE 2: Interactive Mode (手动双击运行)
// =========================================================
Console.WriteLine("==================================================");
Console.WriteLine(" Plugin Dependency Analyzer (Auto-CLI Ready) ");
Console.WriteLine("==================================================");
while (true)
{
_mergedAssemblies.Clear();
_mergedSystemTypes.Clear();
Console.WriteLine("\n1. Enter Plugin FOLDER path (or 'exit'):");
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input)) continue;
if (input.Trim().ToLower() == "exit") break;
string path = input.Replace("\"", "").Trim();
if (!Directory.Exists(path)) { Console.WriteLine("[ERROR] Folder not found."); continue; }
Console.WriteLine("2. Enter Namespace (Default: BetterLyrics.WinUI3):");
Console.Write("> ");
string ns = Console.ReadLine().Trim();
if (string.IsNullOrWhiteSpace(ns)) ns = "BetterLyrics.WinUI3";
string prefix = new DirectoryInfo(path).Name;
Console.WriteLine($"3. Enter prefix (Default: {prefix}):");
Console.Write("> ");
string p = Console.ReadLine().Trim();
if (!string.IsNullOrWhiteSpace(p)) prefix = p;
// 👇👇👇【修复点在这里】👇👇👇
// 交互模式下,我们默认把文件生成在 exe 旁边的 AnalyzerOutput 文件夹里
string interactiveOutputDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AnalyzerOutput");
// 传入 interactiveOutputDir 以匹配新的方法签名
RunBatch(path, ns, prefix, interactiveOutputDir, silent: false);
}
}
// ---------------------------------------------------------
// 下面的 RunBatch 和 GenerateFiles 签名已更新支持 outputDir
// ---------------------------------------------------------
static void RunBatch(string scanPath, string ns, string prefix, string outputDir, bool silent)
{
_currentScanDirectory = scanPath;
_mergedAssemblies.Clear();
_mergedSystemTypes.Clear();
ProcessDirectory(scanPath);
// 传递 outputDir
GenerateFiles(prefix, ns, outputDir, silent);
}
static void GenerateFiles(string prefix, string rootNamespace, string outputDir, bool silent)
{
Directory.CreateDirectory(outputDir);
string xmlFileName = $"{prefix}_TrimmerRoots.xml";
string csFileName = $"{prefix}_TrimmingConfig.cs";
// 👇 关键修改:区分 Assembly 名和 Namespace 名
string assemblyName = rootNamespace; // DLL 名字 (例如 BetterLyrics.WinUI3)
string codeNamespace = $"{rootNamespace}.PluginConfigs"; // C# 命名空间 (例如 BetterLyrics.WinUI3.PluginConfigs)
// 类名和全名
string configClassName = $"{prefix.Replace(".", "_").Replace(" ", "_")}_Config";
string fullConfigClassName = $"{codeNamespace}.{configClassName}";
// 路径变量
string xmlPath = Path.Combine(outputDir, xmlFileName);
string csPath = Path.Combine(outputDir, csFileName);
// ==========================================
// 1. Generate XML
// ==========================================
var xmlBuilder = new StringBuilder();
xmlBuilder.AppendLine("");
xmlBuilder.AppendLine("<linker>");
// A. 保护插件依赖
if (_mergedAssemblies.Count > 0)
{
foreach (var asm in _mergedAssemblies.OrderBy(n => n))
xmlBuilder.AppendLine($" <assembly fullname=\"{asm}\" preserve=\"all\" />");
}
// B. 保护生成的 Config 类
// ⚠️ 注意assembly fullname 必须是 rootNamespace (DLL名),而不是 codeNamespace
xmlBuilder.AppendLine($" <assembly fullname=\"{assemblyName}\">");
xmlBuilder.AppendLine($" <type fullname=\"{fullConfigClassName}\" preserve=\"all\" />");
xmlBuilder.AppendLine($" </assembly>");
xmlBuilder.AppendLine("</linker>");
// ==========================================
// 2. Generate C#
// ==========================================
var csBuilder = new StringBuilder();
csBuilder.AppendLine("// Auto-Generated by PluginAnalyzer");
csBuilder.AppendLine("using System.Diagnostics.CodeAnalysis;");
csBuilder.AppendLine("using System.Runtime.CompilerServices;");
csBuilder.AppendLine("");
// 👇 这里使用带后缀的命名空间
csBuilder.AppendLine($"namespace {codeNamespace};");
csBuilder.AppendLine($"internal static class {configClassName}");
csBuilder.AppendLine("{");
foreach (var type in _mergedSystemTypes.OrderBy(n => n))
{
csBuilder.AppendLine($" [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof({type}))]");
}
csBuilder.AppendLine(" [ModuleInitializer]");
csBuilder.AppendLine(" internal static void Initialize()");
csBuilder.AppendLine(" {");
csBuilder.AppendLine(" // This method runs automatically on startup.");
csBuilder.AppendLine(" }");
csBuilder.AppendLine("}");
// ==========================================
// 3. Write & Notify
// ==========================================
File.WriteAllText(xmlPath, xmlBuilder.ToString());
File.WriteAllText(csPath, csBuilder.ToString());
if (!silent)
{
Console.WriteLine($"[Analyzer] Generated: {xmlFileName}");
Process.Start("explorer.exe", outputDir);
}
else
{
Console.WriteLine($"[Analyzer] Created: {xmlPath}");
Console.WriteLine($"[Analyzer] Created: {csPath}");
}
}
// ---------------------------------------------------------
// Helpers (逻辑保持不变)
// ---------------------------------------------------------
static void ProcessDirectory(string folderPath)
{
var files = Directory.GetFiles(folderPath, "*.dll");
foreach (var file in files)
{
if (!IsHostOrSystemBinary(Path.GetFileName(file))) AnalyzeDll(file);
}
}
static void AnalyzeDll(string path)
{
try
{
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.GetDirectoryName(path));
using var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { AssemblyResolver = resolver });
foreach (var typeRef in assembly.MainModule.GetTypeReferences())
{
if (typeRef.Scope is AssemblyNameReference asmRef && !IsIgnoredDependency(asmRef.Name) && !IsLocalDll(asmRef.Name))
_mergedAssemblies.Add(asmRef.Name);
if (typeRef.FullName.StartsWith("System.") || typeRef.FullName.StartsWith("Microsoft.Win32"))
_mergedSystemTypes.Add(FormatTypeName(typeRef));
}
}
catch { }
}
static bool IsLocalDll(string asmName) => File.Exists(Path.Combine(_currentScanDirectory, asmName + ".dll"));
static bool IsHostOrSystemBinary(string fileName)
{
fileName = fileName.ToLower();
return fileName.StartsWith("betterlyrics.") && !fileName.Contains("plugin") ||
fileName.StartsWith("microsoft.windows") || fileName.StartsWith("winrt.") || fileName.StartsWith("system.");
}
static bool IsIgnoredDependency(string asmName) => asmName.Contains("BetterLyrics") || asmName.Contains("WindowsAppSDK") || asmName.Contains("Microsoft.Windows") || asmName == "netstandard" || asmName == "mscorlib";
static string FormatTypeName(TypeReference type)
{
string name = type.FullName;
// 1. 处理嵌套类型 (Cecil 用 '/'C# 用 '.')
name = name.Replace("/", ".");
// 2. 处理泛型 (核心修复:根据参数数量动态生成逗号)
// List`1 -> List<>
// Dictionary`2 -> Dictionary<,>
// Tuple`3 -> Tuple<,,>
if (name.Contains("`"))
{
name = Regex.Replace(name, @"`(\d+)", match =>
{
int argsCount = int.Parse(match.Groups[1].Value);
// 逗号数量 = 参数数量 - 1
// 比如 1个参数=0个逗号 (<>)
// 比如 2个参数=1个逗号 (<,>)
return "<" + new string(',', Math.Max(0, argsCount - 1)) + ">";
});
}
return name;
}
}

View File

@@ -1,4 +1,4 @@
[**中文**](README.CN.md) | [**English**](README.md)
**中文** | [**English**](README.md)
<div align="center">
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="Logo" width="120">
@@ -20,7 +20,7 @@
<br>
<img src="Promotion/banner.png" alt="Banner" width="100%" style="border-radius: 10px;">
<img src="docs/assets/promotion/banner.png" alt="Banner" width="100%" style="border-radius: 10px;">
</div>
@@ -46,7 +46,7 @@
| :---: | :---: |
| <a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="160"/></a><br>无限期免费试用(功能与付费版一致) | [**📦 最新版本 (.zip)**](https://github.com/jayfunc/BetterLyrics/releases/latest)<br>[查看安装指南](https://www.cnblogs.com/jayfunc/p/19212078) |
[📖 用户指南](https://github.com/jayfunc/BetterLyrics/wiki/使用指南) | [🔒 隐私政策](PrivacyPolicy.CN.md) | [⚖️ 服务条款](TermsofService.CN.md)
[📖 用户指南](docs/USER_GUIDE.CN.md) | [🔒 隐私政策](docs/PRIVACY_POLICY.CN.md) | [⚖️ 服务条款](docs/TERMS_OF_SERVICE.CN.md)
</div>
@@ -80,19 +80,19 @@
| 标准视图 | 侧边栏模式 |
| :---: | :---: |
| <img src="Screenshots/std.png" width="100%"> | <img src="Screenshots/narrow.png" width="100%"> |
| <img src="docs/assets/screenshots/std.png" width="100%"> | <img src="docs/assets/screenshots/narrow.png" width="100%"> |
| 歌词视觉特效 | 多模式共存 |
| :---: | :---: |
| <img src="Screenshots/effect.png" width="100%"> | <img src="Screenshots/all-in-one.png" width="100%"> |
| <img src="docs/assets/screenshots/effect.png" width="100%"> | <img src="docs/assets/screenshots/all-in-one.png" width="100%"> |
| 全屏模式 | 全屏模式 |
| :---: | :---: |
| <img src="Screenshots/fs3.png" width="100%"> | <img src="Screenshots/fs2.png" width="100%"> |
| <img src="docs/assets/screenshots/fs3.png" width="100%"> | <img src="docs/assets/screenshots/fs2.png" width="100%"> |
| 音乐库 | 播放统计 |
| :---: | :---: |
| <img src="Screenshots/music-gallery.png" width="100%"> | <img src="Screenshots/stats.png" width="100%"> |
| <img src="docs/assets/screenshots/music-gallery.png" width="100%"> | <img src="docs/assets/screenshots/stats.png" width="100%"> |
</div>
@@ -104,6 +104,8 @@
**协助翻译:** 找不到你的语言?[点此开始翻译](https://github.com/jayfunc/BetterLyrics?tab=contributing-ov-file)。
**开发插件:** 想要扩展功能?[🧩 阅读插件开发指南](docs/PLUGIN_DEV.md)。
**从源码构建:**
> 构建前,请确保已替换 `Constants` 文件夹下的 `DiscordTemplate.cs` 和 `LastFM.cs`。
@@ -115,7 +117,7 @@
| 网页平台 | 支付宝 (扫码) | 微信 (扫码) |
| :---: | :---: | :---: |
| [PayPal](https://paypal.me/zhefangpay)<br><br>[Buy Me a Coffee](https://buymeacoffee.com/founchoo)<br><br>[爱发电 (Afdian)](https://afdian.com/a/jayfunc) | <img src="Donate/Alipay.jpg" width="150"> | <img src="Donate/WeChatReward.png" width="150"> |
| [PayPal](https://paypal.me/zhefangpay)<br><br>[Buy Me a Coffee](https://buymeacoffee.com/founchoo)<br><br>[爱发电 (Afdian)](https://afdian.com/a/jayfunc) | <img src="docs/assets/donate/Alipay.jpg" width="150"> | <img src="docs/assets/donate/WeChatReward.png" width="150"> |
**[查看完整赞助者名单 (Hall of Fame)](SPONSORS.md)**

View File

@@ -1,4 +1,4 @@
[**中文**](README.CN.md) | [**English**](README.md)
[**中文**](README.CN.md) | **English**
<div align="center">
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="Logo" width="120">
@@ -20,7 +20,7 @@
<br>
<img src="Promotion/banner.png" alt="Banner" width="100%" style="border-radius: 10px;">
<img src="docs/assets/promotion/banner.png" alt="Banner" width="100%" style="border-radius: 10px;">
</div>
@@ -46,7 +46,7 @@
| :---: | :---: |
| <a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="160"/></a><br>Unlimited free trial (Same as paid) | [**📦 Latest Release (.zip)**](https://github.com/jayfunc/BetterLyrics/releases/latest)<br>See [Installation Guide](https://jayfunc.blog/blog/how-to-install-zip) |
[📖 User Guide](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide) | [🔒 Privacy Policy](PrivacyPolicy.md) | [⚖️ Terms of Service](TermsofService.md)
[📖 User Guide](docs/USER_GUIDE.md) | [🔒 Privacy Policy](docs/PRIVACY_POLICY.md) | [⚖️ Terms of Service](docs/TERMS_OF_SERVICE.md)
</div>
@@ -80,19 +80,19 @@
| Standard View | Narrow Mode |
| :---: | :---: |
| <img src="Screenshots/std.png" width="100%"> | <img src="Screenshots/narrow.png" width="100%"> |
| <img src="docs/assets/screenshots/std.png" width="100%"> | <img src="docs/assets/screenshots/narrow.png" width="100%"> |
| Lyrics Visual Effects | Coexisting Modes |
| :---: | :---: |
| <img src="Screenshots/effect.png" width="100%"> | <img src="Screenshots/all-in-one.png" width="100%"> |
| <img src="docs/assets/screenshots/effect.png" width="100%"> | <img src="docs/assets/screenshots/all-in-one.png" width="100%"> |
| Fullscreen Mode | Fullscreen Mode |
| :---: | :---: |
| <img src="Screenshots/fs3.png" width="100%"> | <img src="Screenshots/fs2.png" width="100%"> |
| <img src="docs/assets/screenshots/fs3.png" width="100%"> | <img src="docs/assets/screenshots/fs2.png" width="100%"> |
| Music Gallery | Playback Statistics |
| :---: | :---: |
| <img src="Screenshots/music-gallery.png" width="100%"> | <img src="Screenshots/stats.png" width="100%"> |
| <img src="docs/assets/screenshots/music-gallery.png" width="100%"> | <img src="docs/assets/screenshots/stats.png" width="100%"> |
</div>
@@ -104,6 +104,8 @@
**Help us translate:** Cannot find your language? [Start translating here](https://github.com/jayfunc/BetterLyrics?tab=contributing-ov-file).
**Develop Plugins:** Want to extend functionality? [🧩 Read the Developer Guide](docs/PLUGIN_DEV.md).
**Build from source:**
> Before building, ensure you have replaced `DiscordTemplate.cs` and `LastFM.cs` in the `Constants` folder.
@@ -115,7 +117,7 @@ If you like BetterLyrics, please consider supporting it. Your support helps keep
| Web Platforms | Alipay (QR) | WeChat (QR) |
| :---: | :---: | :---: |
| [PayPal](https://paypal.me/zhefangpay)<br><br>[Buy Me a Coffee](https://buymeacoffee.com/founchoo)<br><br>[爱发电 (Afdian)](https://afdian.com/a/jayfunc) | <img src="Donate/Alipay.jpg" width="150"> | <img src="Donate/WeChatReward.png" width="150"> |
| [PayPal](https://paypal.me/zhefangpay)<br><br>[Buy Me a Coffee](https://buymeacoffee.com/founchoo)<br><br>[爱发电 (Afdian)](https://afdian.com/a/jayfunc) | <img src="docs/assets/donate/Alipay.jpg" width="150"> | <img src="docs/assets/donate/WeChatReward.png" width="150"> |
**[View the full Hall of Fame (Sponsors)](SPONSORS.md)**

View File

@@ -0,0 +1,13 @@
using System;
using System.Linq;
namespace RomajiConverter.Core.Extensions
{
public static class StringExtension
{
public static string[] LineToUnits(this string str)
{
return str.Split(new[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
}
}

View File

@@ -0,0 +1,203 @@
using System.Collections.Generic;
using System.Text;
namespace RomajiConverter.Core.Helpers
{
/// <summary>
/// 此类用于片假、平假互转
/// </summary>
public static class KanaHelper
{
/// <summary>
/// 转为片假名
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ToKatakana(string str)
{
var stringBuilder = new StringBuilder();
foreach (var c in str)
{
var bytes = Encoding.Unicode.GetBytes(c.ToString());
if (bytes.Length == 2 && bytes[1] == 0x30 && bytes[0] >= 0x40 && bytes[0] <= 0x9F)
stringBuilder.Append(Encoding.Unicode.GetString(new[] { (byte)(bytes[0] + 0x60), bytes[1] }));
else
stringBuilder.Append(c);
}
return stringBuilder.ToString();
}
/// <summary>
/// 转为平假名
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ToHiragana(string str)
{
var stringBuilder = new StringBuilder();
foreach (var c in str)
{
var bytes = Encoding.Unicode.GetBytes(c.ToString());
if (bytes.Length == 2 && bytes[1] == 0x30 && bytes[0] >= 0xA0 && bytes[0] <= 0xFA)
stringBuilder.Append(Encoding.Unicode.GetString(new[] { (byte)(bytes[0] - 0x60), bytes[1] }));
else
stringBuilder.Append(c);
}
return stringBuilder.ToString();
}
/// <summary>
/// 假名转罗马音
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string KatakanaToRomaji(string str)
{
var result = new StringBuilder();
for (var i = 0; i < str.Length;)
{
if (i < str.Length - 1)
{
var extendedWord = str.Substring(i, 2);
if (ExtendedKanaDictionary.ContainsKey(extendedWord))
{
result.Append(ExtendedKanaDictionary[extendedWord]);
i += 2;
continue;
}
}
var word = str[i].ToString();
if (KanaDictionary.ContainsKey(word))
{
//正常转换
result.Append(KanaDictionary[word]);
}
else if (word == "ー")
{
//长音,取前一个音
result.Append(result.Length > 0 ? result[result.Length - 1].ToString() : word);
}
else
{
//不能识别,保持原样
result.Append(word);
}
i++;
}
//处理促音
for (var i = 0; i < result.Length; i++)
{
if (result[i] == 'っ' || result[i] == 'ッ')
if (i < result.Length - 1)
if (result[i + 1] == 'c')
result[i] = 't';
else
result[i] = result[i + 1];
else
result.Remove(i, 1);
}
return result.ToString();
}
public static Dictionary<string, string> KanaDictionary = new Dictionary<string, string>
{
//平假
{ "あ", "a" }, { "い", "i" }, { "う", "u" }, { "え", "e" }, { "お", "o" },
{ "か", "ka" }, { "き", "ki" }, { "く", "ku" }, { "け", "ke" }, { "こ", "ko" },
{ "さ", "sa" }, { "し", "shi" }, { "す", "su" }, { "せ", "se" }, { "そ", "so" },
{ "た", "ta" }, { "ち", "chi" }, { "つ", "tsu" }, { "て", "te" }, { "と", "to" },
{ "な", "na" }, { "に", "ni" }, { "ぬ", "nu" }, { "ね", "ne" }, { "の", "no" },
{ "は", "ha" }, { "ひ", "hi" }, { "ふ", "fu" }, { "へ", "he" }, { "ほ", "ho" },
{ "ま", "ma" }, { "み", "mi" }, { "む", "mu" }, { "め", "me" }, { "も", "mo" },
{ "や", "ya" }, { "ゆ", "yu" }, { "よ", "yo" },
{ "ら", "ra" }, { "り", "ri" }, { "る", "ru" }, { "れ", "re" }, { "ろ", "ro" },
{ "わ", "wa" }, { "を", "wo" },
{ "が", "ga" }, { "ぎ", "gi" }, { "ぐ", "gu" }, { "げ", "ge" }, { "ご", "go" },
{ "ざ", "za" }, { "じ", "ji" }, { "ず", "zu" }, { "ぜ", "ze" }, { "ぞ", "zo" },
{ "だ", "da" }, { "ぢ", "ji" }, { "づ", "zu" }, { "で", "de" }, { "ど", "do" },
{ "ば", "ba" }, { "び", "bi" }, { "ぶ", "bu" }, { "べ", "be" }, { "ぼ", "bo" },
{ "ぱ", "pa" }, { "ぴ", "pi" }, { "ぷ", "pu" }, { "ぺ", "pe" }, { "ぽ", "po" },
{ "ん", "n" },
//片假
{ "ア", "a" }, { "イ", "i" }, { "ウ", "u" }, { "エ", "e" }, { "オ", "o" },
{ "カ", "ka" }, { "キ", "ki" }, { "ク", "ku" }, { "ケ", "ke" }, { "コ", "ko" },
{ "サ", "sa" }, { "シ", "shi" }, { "ス", "su" }, { "セ", "se" }, { "ソ", "so" },
{ "タ", "ta" }, { "チ", "chi" }, { "ツ", "tsu" }, { "テ", "te" }, { "ト", "to" },
{ "ナ", "na" }, { "ニ", "ni" }, { "ヌ", "nu" }, { "ネ", "ne" }, { "", "no" },
{ "ハ", "ha" }, { "ヒ", "hi" }, { "フ", "fu" }, { "ヘ", "he" }, { "ホ", "ho" },
{ "マ", "ma" }, { "ミ", "mi" }, { "ム", "mu" }, { "メ", "me" }, { "モ", "mo" },
{ "ヤ", "ya" }, { "ユ", "yu" }, { "ヨ", "yo" },
{ "ラ", "ra" }, { "リ", "ri" }, { "ル", "ru" }, { "レ", "re" }, { "ロ", "ro" },
{ "ワ", "wa" }, { "ヲ", "wo" },
{ "ガ", "ga" }, { "ギ", "gi" }, { "グ", "gu" }, { "ゲ", "ge" }, { "ゴ", "go" },
{ "ザ", "za" }, { "ジ", "ji" }, { "ズ", "zu" }, { "ゼ", "ze" }, { "ゾ", "zo" },
{ "ダ", "da" }, { "ヂ", "ji" }, { "ヅ", "zu" }, { "デ", "de" }, { "ド", "do" },
{ "バ", "ba" }, { "ビ", "bi" }, { "ブ", "bu" }, { "ベ", "be" }, { "ボ", "bo" },
{ "パ", "pa" }, { "ピ", "pi" }, { "プ", "pu" }, { "ペ", "pe" }, { "ポ", "po" },
{ "ン", "n" },
//小版本
{ "ぁ", "a" }, { "ぃ", "i" }, { "ぅ", "u" }, { "ぇ", "e" }, { "ぉ", "o" },
{ "ゃ", "ya" }, { "ゅ", "yu" }, { "ょ", "yo" }, { "ゎ", "wa" },
{ "ァ", "a" }, { "ィ", "i" }, { "ゥ", "u" }, { "ェ", "e" }, { "ォ", "o" },
{ "ャ", "ya" }, { "ュ", "yu" }, { "ョ", "yo" }, { "ヮ", "wa" },
};
public static Dictionary<string, string> ExtendedKanaDictionary = new Dictionary<string, string>
{
//平假-拗音
{ "きゃ", "kya" }, { "きゅ", "kyu" }, { "きょ", "kyo" },
{ "しゃ", "sha" }, { "しゅ", "shu" }, { "しょ", "sho" },
{ "ちゃ", "cha" }, { "ちゅ", "chu" }, { "ちょ", "cho" },
{ "にゃ", "nya" }, { "にゅ", "nyu" }, { "にょ", "nyo" },
{ "ひゃ", "hya" }, { "ひゅ", "hyu" }, { "ひょ", "hyo" },
{ "みゃ", "mya" }, { "みゅ", "myu" }, { "みょ", "myo" },
{ "りゃ", "rya" }, { "りゅ", "ryu" }, { "りょ", "ryo" },
{ "ぎゃ", "gya" }, { "ぎゅ", "gyu" }, { "ぎょ", "gyo" },
{ "じゃ", "ja" }, { "じゅ", "ju" }, { "じょ", "jo" },
{ "ぢゃ", "ja" }, { "ぢゅ", "ju" }, { "ぢょ", "jo" },
{ "びゃ", "bya" }, { "びゅ", "byu" }, { "びょ", "byo" },
{ "ぴゃ", "pya" }, { "ぴゅ", "pyu" }, { "ぴょ", "pyo" },
//片假-拗音
{ "キャ", "kya" }, { "キュ", "kyu" }, { "キョ", "kyo" },
{ "シャ", "sha" }, { "シュ", "shu" }, { "ショ", "sho" },
{ "チャ", "cha" }, { "チュ", "chu" }, { "チョ", "cho" },
{ "ニャ", "nya" }, { "ニュ", "nyu" }, { "ニョ", "nyo" },
{ "ヒャ", "hya" }, { "ヒュ", "hyu" }, { "ヒョ", "hyo" },
{ "ミャ", "mya" }, { "ミュ", "myu" }, { "ミョ", "myo" },
{ "リャ", "rya" }, { "リュ", "ryu" }, { "リョ", "ryo" },
{ "ギャ", "gya" }, { "ギュ", "gyu" }, { "ギョ", "gyo" },
{ "ジャ", "ja" }, { "ジュ", "ju" }, { "ジョ", "jo" },
{ "ヂャ", "ja" }, { "ヂュ", "ju" }, { "ヂョ", "jo" },
{ "ビャ", "bya" }, { "ビュ", "byu" }, { "ビョ", "byo" },
{ "ピャ", "pya" }, { "ピュ", "pyu" }, { "ピョ", "pyo" },
//其他语言
{ "イェ", "ye" },
{ "ウィ", "wi" }, { "ウェ", "we" }, { "ウォ", "wo" },
{ "ヴァ", "va" }, { "ヴィ", "vi" }, { "ヴ", "vu" }, { "ヴェ", "ve" }, { "ヴォ", "vo" },
{ "ヴュ", "vyu" },
{ "クァ", "kwa" }, { "クィ", "kwi" }, { "クェ", "kwe" }, { "クォ", "kwo" },
{ "グァ", "gwa" },
{ "シェ", "she" },
{ "ジェ", "je" },
{ "チェ", "che" },
{ "ツァ", "tsa" }, { "ツィ", "tsi" }, { "ツェ", "tse" }, { "ツォ", "tso" },
{ "ティ", "ti" }, { "トゥ", "tu" },
{ "テュ", "tyu" },
{ "ディ", "di" }, { "ドゥ", "du" },
{ "デュ", "dyu" },
{ "ファ", "fa" }, { "フィ", "fi" }, { "フェ", "fe" }, { "フォ", "fo" },
{ "フュ", "fyu" },
};
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace RomajiConverter.Core.Helpers
{
public static class LrcParser
{
public static readonly Regex LrcLineRegex =
new Regex("(\\[(\\d+:)?\\d+:\\d+(\\.\\d+)?\\])+\\s*(?<text>.*?(?=\\[(\\d+:)?\\d+:\\d+(\\.\\d+)?\\]|$|\\r|\\n))",
RegexOptions.Compiled);
public static readonly Regex LrcTimeRegex =
new Regex("\\[((?<hour>\\d+):)?(?<minute>\\d+):(?<second>\\d+)(\\.(?<millisecond>\\d+))?\\]", RegexOptions.Compiled);
public static List<(TimeSpan Time, string Text)> Parse(string lrc)
{
var result = new List<(TimeSpan Time, string Text)>();
var lineMatches = LrcLineRegex.Matches(lrc);
foreach (Match lineMatch in lineMatches)
{
var text = lineMatch.Groups["text"].Value;
var timeMatches = LrcTimeRegex.Matches(lineMatch.Value);
foreach (Match timeMatch in timeMatches)
{
var hour = timeMatch.Groups["hour"].Success ? int.Parse(timeMatch.Groups["hour"].Value) : 0;
var minute = timeMatch.Groups["minute"].Success ? int.Parse(timeMatch.Groups["minute"].Value) : 0;
var second = timeMatch.Groups["second"].Success ? int.Parse(timeMatch.Groups["second"].Value) : 0;
var millisecond = timeMatch.Groups["millisecond"].Success
? int.Parse(timeMatch.Groups["millisecond"].Value)
: 0;
var time = new TimeSpan(0, hour, minute, second, millisecond);
result.Add((time, text));
}
}
return result;
}
}
}

View File

@@ -0,0 +1,372 @@
using MeCab;
using MeCab.Extension.UniDic;
using RomajiConverter.Core.Extensions;
using RomajiConverter.Core.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using RomajiConverter.Core.Options;
namespace RomajiConverter.Core.Helpers
{
public static class RomajiHelper
{
/// <summary>
/// 分词器
/// </summary>
private static MeCabTagger _tagger;
/// <summary>
/// 自定义词典<原文, 假名>
/// </summary>
private static Dictionary<string, string> _customizeDict;
public static void Init(string baseDirectory = null)
{
string rootPath = !string.IsNullOrEmpty(baseDirectory)
? baseDirectory
: AppDomain.CurrentDomain.BaseDirectory;
//词典路径
var dicPath = Path.Combine(rootPath, "unidic");
var parameter = new MeCabParam
{
DicDir = dicPath,
LatticeLevel = MeCabLatticeLevel.Zero
};
_tagger = MeCabTagger.Create(parameter);
var str = File.ReadAllText(Path.Combine(rootPath, "customizeDict.txt"));
var list = str.Split(Environment.NewLine.ToArray());
_customizeDict = new Dictionary<string, string>();
foreach (var item in list)
{
if (string.IsNullOrWhiteSpace(item)) continue;
var array = item.Split(' ');
if (array.Length < 2) continue;
if (!_customizeDict.ContainsKey(array[0]))
_customizeDict.Add(array[0], array[1]);
}
}
#region
/// <summary>
/// 生成转换结果列表
/// </summary>
/// <param name="text"></param>
/// <param name="options"></param>
/// <returns></returns>
public static IEnumerable<ConvertedLine> ToRomaji(string text, ToRomajiOptions options = null)
{
options = options ?? new ToRomajiOptions();
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 lyric = LrcParser.Parse(lineTextList[i]).FirstOrDefault();
timeSpans.Add(lyric.Time);
lineTextList[i] = lyric.Text;
}
else
{
timeSpans.Add(null);
}
}
ushort lineIndex = 0;
for (var index = 0; index < lineTextList.Count; index++)
{
var line = lineTextList[index];
if (IsChinese(line, options.ChineseRate)) continue;
var convertedLine = new ConvertedLine
{
Index = lineIndex,
Time = index < timeSpans.Count ? timeSpans[index] : null,
Japanese = line.Replace("\0", "")
};
foreach (var sentence in convertedLine.Japanese.LineToUnits())
{
if (IsEnglish(sentence))
{
convertedLine.Units.Add(new ConvertedUnit(lineIndex, sentence, sentence, sentence, false));
}
else
foreach (var unit in SentenceToRomaji(lineIndex, sentence, options.IsParticleAsPronunciation))
convertedLine.Units.Add(unit);
}
if (index + 1 < lineTextList.Count && IsChinese(lineTextList[index + 1], options.ChineseRate))
convertedLine.Chinese = lineTextList[index + 1];
lineIndex++;
yield return convertedLine;
}
}
/// <summary>
/// 分句转为罗马音
/// </summary>
/// <param name="lineIndex"></param>
/// <param name="str"></param>
/// <param name="isParticleAsPronunciation"></param>
/// <returns></returns>
public static IEnumerable<ConvertedUnit> SentenceToRomaji(ushort lineIndex, string str, bool isParticleAsPronunciation)
{
foreach (var item in _tagger.ParseToNodes(str))
{
var unit = MeCabNodeToUnit(lineIndex, item, isParticleAsPronunciation);
if (unit != null)
yield return unit;
}
}
public static ConvertedUnit MeCabNodeToUnit(ushort lineIndex, MeCabNode item, bool isParticleAsPronunciation)
{
ConvertedUnit unit = null;
if (item.CharType > 0)
{
var features = CustomSplit(item.Feature);
if (TryCustomConvert(item.Surface, out var customResult))
{
//用户自定义词典
unit = new ConvertedUnit(lineIndex,
item.Surface,
customResult,
KanaHelper.KatakanaToRomaji(customResult),
true);
}
else if (features.Length > 0 && (!isParticleAsPronunciation || item.GetPos1() != "助詞") && IsJapanese(item.Surface))
{
//纯假名
unit = new ConvertedUnit(lineIndex,
item.Surface,
KanaHelper.ToHiragana(item.Surface),
KanaHelper.KatakanaToRomaji(item.Surface),
false);
}
else if (features.Length <= 6 || new[] { "補助記号" }.Contains(item.GetPos1()))
{
//标点符号或无法识别的字
unit = new ConvertedUnit(lineIndex,
item.Surface,
item.Surface,
item.Surface,
false);
}
else if (IsEnglish(item.Surface))
{
//英文
unit = new ConvertedUnit(lineIndex,
item.Surface,
item.Surface,
item.Surface,
false);
}
else
{
//汉字或助词
var kana = GetKana(item);
unit = new ConvertedUnit(lineIndex,
item.Surface,
KanaHelper.ToHiragana(kana),
KanaHelper.KatakanaToRomaji(kana),
!IsJapanese(item.Surface));
var (replaceHiragana, replaceRomaji) = GetReplaceData(item);
unit.ReplaceHiragana = replaceHiragana;
unit.ReplaceRomaji = replaceRomaji;
}
}
else if (item.Stat != MeCabNodeStat.Bos && item.Stat != MeCabNodeStat.Eos)
{
unit = new ConvertedUnit(lineIndex,
item.Surface,
item.Surface,
item.Surface,
false);
}
return unit;
}
#endregion
#region
/// <summary>
/// 自定义分隔方法(Feature可能存在如 a,b,c,"d,e",f 格式的数据,此处不能把双引号中的内容也分隔开)
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private static string[] CustomSplit(string str)
{
var list = new List<string>();
var item = new List<char>();
var haveMark = false;
foreach (var c in str)
if (c == ',' && !haveMark)
{
list.Add(new string(item.ToArray()));
item.Clear();
}
else if (c == '"')
{
item.Add(c);
haveMark = !haveMark;
}
else
{
item.Add(c);
}
return list.ToArray();
}
/// <summary>
/// 获取所有发音
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private static (ObservableCollection<ReplaceString> replaceHiragana, ObservableCollection<ReplaceString>
replaceRomaji) GetReplaceData(MeCabNode node)
{
var length = node.Length;
var replaceNodeList = new List<MeCabNode>();
GetAllReplaceNode(replaceNodeList, node);
void GetAllReplaceNode(List<MeCabNode> list, MeCabNode n)
{
if (n != null && !list.Contains(n) && n.Length == length)
{
list.Add(n);
GetAllReplaceNode(list, n.BNext);
GetAllReplaceNode(list, n.ENext);
}
}
var replaceHiragana = new ObservableCollection<ReplaceString>();
var replaceRomaji = new ObservableCollection<ReplaceString>();
ushort i = 1;
foreach (var meCabNode in replaceNodeList
.GroupBy(GetKana)
.Select(g => g.First()))
{
var kana = GetKana(meCabNode);
if (kana != null)
{
replaceHiragana.Add(new ReplaceString(i, KanaHelper.ToHiragana(kana), true));
replaceRomaji.Add(new ReplaceString(i, KanaHelper.KatakanaToRomaji(kana), true));
i++;
}
}
return (replaceHiragana, replaceRomaji);
}
private static string GetKana(MeCabNode node)
{
return node.GetPos1() == "助詞" ? node.GetPron() : node.GetKana();
}
/// <summary>
/// 自定义转换规则
/// </summary>
/// <param name="str"></param>
/// <param name="result"></param>
/// <returns></returns>
private static bool TryCustomConvert(string str, out string result)
{
if (_customizeDict.ContainsKey(str))
{
result = _customizeDict[str];
return true;
}
result = "";
return false;
}
/// <summary>
/// 判断字符串(句子)是否简体中文
/// </summary>
/// <param name="str"></param>
/// <param name="rate">容错率(0-1)</param>
/// <returns></returns>
public static bool IsChinese(string str, float rate)
{
if (str.Length < 2)
return false;
var wordArray = str.ToCharArray();
var total = wordArray.Length;
var chCount = 0f;
var enCount = 0f;
foreach (var word in wordArray)
{
if (word != 'ー' && IsJapanese(word.ToString()))
//含有日文直接返回否
return false;
var gbBytes = Encoding.Unicode.GetBytes(word.ToString());
if (gbBytes.Length == 2) // double bytes char.
{
if (gbBytes[1] >= 0x4E && gbBytes[1] <= 0x9F) //中文
chCount++;
else
total--;
}
else if (gbBytes.Length == 1)
{
var byteAscii = int.Parse(gbBytes[0].ToString());
if ((byteAscii >= 65 && byteAscii <= 90) || (byteAscii >= 97 && byteAscii <= 122)) //英文字母
enCount++;
else
total--;
}
}
if (chCount == 0) return false; //一个简体中文都没有
return (chCount + enCount) / total >= rate;
}
/// <summary>
/// 判断字符串是否全为单字节
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsEnglish(string str)
{
return new Regex("^[\x20-\x7E]+$", RegexOptions.Compiled).IsMatch(str);
}
/// <summary>
/// 判断字符串是否全为假名
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private static bool IsJapanese(string str)
{
return Regex.IsMatch(str, @"^[\u3040-\u30ff]+$", RegexOptions.Compiled);
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.ObjectModel;
namespace RomajiConverter.Core.Models
{
public class ConvertedLine
{
public ushort Index { get; set; } = 0;
public TimeSpan? Time { get; set; }
public string Chinese { get; set; } = string.Empty;
public string Japanese { get; set; } = string.Empty;
public ObservableCollection<ConvertedUnit> Units { get; set; } = new ObservableCollection<ConvertedUnit>();
}
}

View File

@@ -0,0 +1,125 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RomajiConverter.Core.Models
{
public class ConvertedUnit : INotifyPropertyChanged
{
private string _hiragana;
private bool _isKanji;
private string _japanese;
private ObservableCollection<ReplaceString> _replaceHiragana;
private ObservableCollection<ReplaceString> _replaceRomaji;
private string _romaji;
private ushort _selectId;
private ushort _lineIndex;
public ConvertedUnit(ushort lineIndex, string japanese, string hiragana, string romaji, bool isKanji)
{
LineIndex = lineIndex;
Japanese = japanese;
Romaji = romaji;
Hiragana = hiragana;
IsKanji = isKanji;
SelectId = 1;
ReplaceHiragana = new ObservableCollection<ReplaceString> { new ReplaceString(1, hiragana, true) };
ReplaceRomaji = new ObservableCollection<ReplaceString> { new ReplaceString(1, romaji, true) };
}
public ushort LineIndex
{
get => _lineIndex;
set
{
if (value == _lineIndex) return;
_lineIndex = value;
OnPropertyChanged();
}
}
public string Japanese
{
get => _japanese;
set
{
if (value == _japanese) return;
_japanese = value;
OnPropertyChanged();
}
}
public string Romaji
{
get => _romaji;
set
{
if (value == _romaji) return;
_romaji = value;
OnPropertyChanged();
}
}
public ObservableCollection<ReplaceString> ReplaceRomaji
{
get => _replaceRomaji;
set
{
if (Equals(value, _replaceRomaji)) return;
_replaceRomaji = value;
OnPropertyChanged();
}
}
public string Hiragana
{
get => _hiragana;
set
{
if (value == _hiragana) return;
_hiragana = value;
OnPropertyChanged();
}
}
public ObservableCollection<ReplaceString> ReplaceHiragana
{
get => _replaceHiragana;
set
{
if (Equals(value, _replaceHiragana)) return;
_replaceHiragana = value;
OnPropertyChanged();
}
}
public bool IsKanji
{
get => _isKanji;
set
{
if (value == _isKanji) return;
_isKanji = value;
OnPropertyChanged();
}
}
public ushort SelectId
{
get => _selectId;
set
{
if (value == _selectId) return;
_selectId = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,23 @@
namespace RomajiConverter.Core.Models
{
public class ReplaceString
{
public ReplaceString(ushort id, string value, bool isSystem)
{
Id = id;
Value = value;
IsSystem = isSystem;
}
public ushort Id { get; set; }
public string Value { get; set; }
public bool IsSystem { get; set; }
public override string ToString()
{
return Value;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace RomajiConverter.Core.Options
{
public class ToRomajiOptions
{
public float ChineseRate { get; set; } = 1f;
public bool IsParticleAsPronunciation { get; set; } = true;
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<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>
<RepositoryUrl>https://github.com/xyh20180101/RomajiConverter.WinUI</RepositoryUrl>
<PackageProjectUrl>https://github.com/xyh20180101/RomajiConverter.WinUI</PackageProjectUrl>
<PackageLicenseExpression>GPL-2.0-or-later</PackageLicenseExpression>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReadmeFile>README.md</PackageReadmeFile>
<MeCabUseDefaultDictionary>False</MeCabUseDefaultDictionary>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MeCab.DotNet" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<None Include="docs\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,58 @@
# RomajiConverter.Core
用于[RomajiConverter.WinUI](https://github.com/xyh20180101/RomajiConverter.WinUI)项目的罗马音转换相关逻辑的包
## 使用
```C#
//离线分词器
IEnumerable<ConvertedLine> list = RomajiHelper.ToRomaji(jpnStr);
//AI
var list = new ObservableCollection<ConvertedLine>();
await RomajiAIHelper.ToRomajiStreamingAsync(list, jpnStr, new ToRomajiAIOptions
{
BaseUrl = "",
Model = "",
ApiKey = ""
}); //流式插入
//以下是类结构
public class ConvertedLine
{
public ushort Index { get; set; } = 0;
public string Chinese { get; set; } = string.Empty;
public string Japanese { get; set; } = string.Empty;
public ObservableCollection<ConvertedUnit> Units { get; set; } = new ObservableCollection<ConvertedUnit>();
}
public class ConvertedUnit
{
public ushort LineIndex { get; set; }
public string Japanese { get; set; }
public string Romaji { get; set; }
public ObservableCollection<ReplaceString> ReplaceRomaji { get; set; }
public string Hiragana { get; set; }
public ObservableCollection<ReplaceString> ReplaceHiragana { get; set; }
public bool IsKanji { get; set; }
public ushort SelectId { get; set; }
}
public class ReplaceString
{
public ushort Id { get; set; }
public string Value { get; set; }
public bool IsSystem { get; set; }
}
```

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Some files were not shown because too many files have changed in this diff Show More