mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
Compare commits
23 Commits
77a9bb0a1b
...
v1.2.256.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b3169e5f6 | ||
|
|
9d2e245a99 | ||
|
|
0513c3a128 | ||
|
|
5082c4c245 | ||
|
|
659b4d0e60 | ||
|
|
85f67c2ec6 | ||
|
|
bdbeb391ea | ||
|
|
3357e7aaf4 | ||
|
|
e43461d624 | ||
|
|
3e1907ad8c | ||
|
|
74eeffc8a6 | ||
|
|
c32eb3b877 | ||
|
|
047e53b830 | ||
|
|
fdb7bd16f6 | ||
|
|
4e33444d8e | ||
|
|
8fc67711b6 | ||
|
|
28757d9880 | ||
|
|
e5fb04f577 | ||
|
|
9d03c8f688 | ||
|
|
094fe7b7a1 | ||
|
|
bc32a3f34c | ||
|
|
b23d3c280f | ||
|
|
2738d45b69 |
20
.github/workflows/plugin-registry-check.yml
vendored
Normal file
20
.github/workflows/plugin-registry-check.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Plugin Registry Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Community/plugins-registry.json'
|
||||
|
||||
jobs:
|
||||
check-collision:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Run Hash Collision Check
|
||||
run: node Community/scripts/check-hash-collision.js
|
||||
9
BetterLyrics.Core/BetterLyrics.Core.csproj
Normal file
9
BetterLyrics.Core/BetterLyrics.Core.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
16
BetterLyrics.Core/Interfaces/ILyricsProvider.cs
Normal file
16
BetterLyrics.Core/Interfaces/ILyricsProvider.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using BetterLyrics.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.Core.Interfaces
|
||||
{
|
||||
public interface ILyricsProvider
|
||||
{
|
||||
string Id { get; }
|
||||
string Name { get; }
|
||||
string Author { get; }
|
||||
|
||||
Task<LyricsSearchResult> GetLyricsAsync(string title, string artist, string album, double duration);
|
||||
}
|
||||
}
|
||||
16
BetterLyrics.Core/Models/LyricsSearchResult.cs
Normal file
16
BetterLyrics.Core/Models/LyricsSearchResult.cs
Normal 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);
|
||||
}
|
||||
13
BetterLyrics.Plugins.Demo/BetterLyrics.Plugins.Demo.csproj
Normal file
13
BetterLyrics.Plugins.Demo/BetterLyrics.Plugins.Demo.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
59
BetterLyrics.Plugins.Demo/DemoLyricsProvider.cs
Normal file
59
BetterLyrics.Plugins.Demo/DemoLyricsProvider.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using BetterLyrics.Core.Interfaces;
|
||||
using BetterLyrics.Core.Models;
|
||||
|
||||
namespace BetterLyrics.Plugins.Demo
|
||||
{
|
||||
public class DemoLyricsProvider : ILyricsProvider
|
||||
{
|
||||
public string Id => "f7acc86b-6e3d-42c3-a9a9-8c05c5339412";
|
||||
public string Name => "Demo Plugin";
|
||||
public string Author => "jayfunc";
|
||||
|
||||
public async Task<LyricsSearchResult> GetLyricsAsync(string title, string artist, string album, double duration)
|
||||
{
|
||||
await Task.Delay(300);
|
||||
|
||||
string searchedTitle = "Demo Song";
|
||||
string searchedArtist = "Demo Artist";
|
||||
string searchedAlbum = "Demo Album";
|
||||
double searchedDuration = 25.0;
|
||||
|
||||
string searchedRaw =
|
||||
$"[00:00.00]Welcome to use Demo Plugin\n" +
|
||||
$"[00:05.00]Playing: {title} now\n" +
|
||||
$"[00:10.00]Artist: {artist}\n" +
|
||||
$"[00:15.00]Album: {album}\n" +
|
||||
$"[00:20.00]Duration: {duration}\n" +
|
||||
$"[00:25.00]This is a test lyrics source...";
|
||||
|
||||
string searchedTranslation =
|
||||
$"[00:00.00]欢迎使用演示插件\n" +
|
||||
$"[00:05.00]当前正在播放:{title}\n" +
|
||||
$"[00:10.00]歌手:{artist}\n" +
|
||||
$"[00:15.00]专辑:{album}\n" +
|
||||
$"[00:20.00]时长:{duration}\n" +
|
||||
$"[00:25.00]这是一个测试歌词源...";
|
||||
|
||||
string searchedTransliteration =
|
||||
$"[00:00.00]ˈwɛlkəm tuː juːz ˈdɛmoʊ ˈplʌgɪn\n" +
|
||||
$"[00:05.00]ˈpleɪɪŋ: {title} naʊ\n" +
|
||||
$"[00:10.00]ˈɑːrtɪst: {artist}\n" +
|
||||
$"[00:15.00]ˈælbəm: {album}\n" +
|
||||
$"[00:20.00]dʊˈreɪʃən: {duration}\n" +
|
||||
$"[00:25.00]ðɪs ɪz ə tɛst ˈlɪrɪks sɔːrs...";
|
||||
|
||||
string searchedReference = "https://path.to.lyrics/if.the.lyrics.was.originally.fetched.from.web";
|
||||
|
||||
return new LyricsSearchResult(
|
||||
searchedTitle,
|
||||
searchedArtist,
|
||||
searchedAlbum,
|
||||
searchedDuration,
|
||||
searchedRaw,
|
||||
searchedTranslation,
|
||||
searchedTransliteration,
|
||||
searchedReference);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.2.249.0" />
|
||||
Version="1.2.256.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -248,6 +249,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
|
||||
.AddSingleton<ILyricsCacheService, LyricsCacheService>()
|
||||
.AddSingleton<ISongSearchMapService, SongSearchMapService>()
|
||||
.AddSingleton<IPluginService, PluginService>()
|
||||
|
||||
// ViewModels
|
||||
.AddSingleton<AppSettingsControlViewModel>()
|
||||
|
||||
@@ -96,6 +96,10 @@
|
||||
<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" />
|
||||
@@ -121,6 +125,7 @@
|
||||
<PackageReference Include="z440.atl.core" Version="7.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\BetterLyrics.Core\BetterLyrics.Core.csproj" />
|
||||
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
|
||||
<ProjectReference Include="..\..\Impressionist\Impressionist\Impressionist.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -14,5 +14,6 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
AppleMusic,
|
||||
Plugin = 999,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
ThisMonth,
|
||||
ThisQuarter,
|
||||
ThisYear,
|
||||
AllTime,
|
||||
Custom
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
LibreTranslate,
|
||||
Plugin = 999,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
BetterLyrics,
|
||||
CutletDocker
|
||||
CutletDocker,
|
||||
Plugin = 999,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
// 差距 <= 3秒:100% 相似
|
||||
// 差距 >= 20秒:0% 相似
|
||||
// 差距 <= 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;
|
||||
|
||||
@@ -40,17 +40,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_targetValue = initialValue;
|
||||
_totalDurationForAutoSplit = defaultTotalDuration;
|
||||
|
||||
if (interpolator == null)
|
||||
{
|
||||
// 默认缓动
|
||||
SetEasingType(Enums.EasingType.EaseInOutQuad);
|
||||
}
|
||||
else
|
||||
{
|
||||
_easingType = null;
|
||||
_interpolator = interpolator;
|
||||
}
|
||||
|
||||
if (interpolator != null)
|
||||
{
|
||||
_interpolator = interpolator;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -140,8 +181,8 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
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);
|
||||
@@ -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);
|
||||
|
||||
@@ -215,7 +215,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
lanesEndMs.Add(0);
|
||||
}
|
||||
|
||||
lanesEndMs[assignedLane] = end;
|
||||
lanesEndMs[assignedLane] = end ?? 0;
|
||||
line.LaneIndex = assignedLane;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -47,10 +47,15 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
[NotMapped][JsonIgnore] public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
|
||||
|
||||
[MaxLength(128)]
|
||||
public string? PluginId { get; set; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new LyricsCacheItem()
|
||||
{
|
||||
PluginId = this.PluginId,
|
||||
|
||||
Provider = this.Provider,
|
||||
TranslationProvider = this.TranslationProvider,
|
||||
TransliterationProvider = this.TransliterationProvider,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"),
|
||||
}]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -74,6 +74,10 @@ 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(
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
|
||||
LoadTransliteration(lyricsSearchResult);
|
||||
GenerateTransliterationLyricsData();
|
||||
|
||||
EnsureEndMs(lyricsSearchResult?.Duration);
|
||||
EnsureSyllables();
|
||||
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
{
|
||||
_logger.LogInformation("RefreshLyricsAsync");
|
||||
|
||||
CurrentLyricsSearchResult = null;
|
||||
CurrentLyricsData = LyricsData.GetLoadingPlaceholder();
|
||||
|
||||
if (CurrentSongInfo != SongInfoExtensions.Placeholder)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
if (_pluginService.Providers.Any())
|
||||
{
|
||||
foreach (var plugin in _pluginService.Providers)
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
var pluginResult = await SearchPluginAsync(songInfo, plugin, 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,41 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<LyricsCacheItem> SearchPluginAsync(SongInfo songInfo, ILyricsProvider plugin, CancellationToken token)
|
||||
{
|
||||
var cacheItem = new LyricsCacheItem
|
||||
{
|
||||
Provider = LyricsSearchProvider.Plugin,
|
||||
PluginId = plugin.Id,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using BetterLyrics.Core.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
{
|
||||
public interface IPluginService
|
||||
{
|
||||
IReadOnlyList<ILyricsProvider> Providers { get; }
|
||||
|
||||
void LoadPlugins();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||
}
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
{
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using BetterLyrics.Core.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.PluginService
|
||||
{
|
||||
public class PluginService : IPluginService
|
||||
{
|
||||
private List<ILyricsProvider> _providers = new();
|
||||
|
||||
public IReadOnlyList<ILyricsProvider> Providers => _providers;
|
||||
|
||||
public void LoadPlugins()
|
||||
{
|
||||
// 在涉及加载程序集的地方:
|
||||
// var context = new PluginLoadContext(pluginPath);
|
||||
// 它是本文件夹下的 internal 或者是 public 类,直接用即可。
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -1051,7 +1051,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>
|
||||
@@ -1479,6 +1479,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 +1495,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1138,16 +1138,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>
|
||||
@@ -1479,6 +1479,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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.36105.23
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.1.11312.151
|
||||
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
|
||||
@@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impressionist", "Impression
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorThief.WinUI3", "ColorThief.WinUI3\ColorThief.WinUI3.csproj", "{8F2FE667-2D91-428E-0630-05E6330F9625}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Core", "BetterLyrics.Core\BetterLyrics.Core.csproj", "{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Plugins.Demo", "BetterLyrics.Plugins.Demo\BetterLyrics.Plugins.Demo.csproj", "{87D235CA-4311-4766-8186-AD9B193DFABC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -75,6 +79,30 @@ Global
|
||||
{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
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x86.Build.0 = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
1
Community/plugins-registry.json
Normal file
1
Community/plugins-registry.json
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
40
Community/scripts/check-hash-collision.js
Normal file
40
Community/scripts/check-hash-collision.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// Community/scripts/check-hash-collision.js
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const registryPath = path.join(__dirname, '../plugins-registry.json');
|
||||
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
||||
|
||||
function getStableId(pluginId) {
|
||||
let hash = 23;
|
||||
for (let i = 0; i < pluginId.length; i++) {
|
||||
hash = (hash * 31 + pluginId.charCodeAt(i)) | 0;
|
||||
}
|
||||
return Math.abs(hash) + 1000;
|
||||
}
|
||||
|
||||
const seenIds = new Set();
|
||||
let hasError = false;
|
||||
|
||||
console.log("🔍 Starting to check for plugin ID conflicts...");
|
||||
|
||||
registry.forEach(item => {
|
||||
const stableId = getStableId(item.id);
|
||||
|
||||
console.log(`Checking [${item.id}] -> Hash: ${stableId}`);
|
||||
|
||||
if (seenIds.has(stableId)) {
|
||||
console.error(`⛔ Fatel error! Conflict detected!`);
|
||||
console.error(`The hash value (${stableId}) calculated from the plugin ID [${item.id}] is duplicated with an existing plugin.`);
|
||||
hasError = true;
|
||||
}
|
||||
seenIds.add(stableId);
|
||||
});
|
||||
|
||||
if (hasError) {
|
||||
console.log("⛔ Check failed, please change the plugin ID.");
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("✅ The check passed; no conflicts were found.");
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user