fix: lyrics cache was not updated when searching without enabling cache, lyrics animation issue

This commit is contained in:
Zhe Fang
2026-01-08 16:21:44 -05:00
parent efc175668e
commit 6f60952d09
51 changed files with 730 additions and 620 deletions

View File

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

View File

@@ -6,6 +6,7 @@ namespace BetterLyrics.WinUI3.Constants
{
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250);
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350);
public static readonly TimeSpan LongAnimationDuration = TimeSpan.FromMilliseconds(650);
public static readonly TimeSpan WaitingDuration = TimeSpan.FromMilliseconds(300);
}
}

View File

@@ -119,7 +119,7 @@ namespace BetterLyrics.WinUI3.Controls
private bool _isLayoutChanged = true;
private bool _isMouseScrollingChanged = false;
private int _playingLineIndex;
private int _primaryPlayingLineIndex;
private (int Start, int End) _visibleRange;
private double _canvasTargetScrollOffset;
@@ -382,7 +382,6 @@ namespace BetterLyrics.WinUI3.Controls
control: sender,
ds: args.DrawingSession,
lines: _renderLyricsLines,
playingLineIndex: _playingLineIndex,
mouseHoverLineIndex: _mouseHoverLineIndex,
isMousePressing: _isMousePressing,
startVisibleIndex: _visibleRange.Start,
@@ -411,8 +410,6 @@ namespace BetterLyrics.WinUI3.Controls
return _synchronizer.GetLinePlayingProgress(
_songPositionWithOffset.TotalMilliseconds,
line,
nextLine,
songDuration,
isForceWordByWord
);
}
@@ -476,17 +473,17 @@ namespace BetterLyrics.WinUI3.Controls
#region UpdatePlayingLineIndex
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, _renderLyricsLines);
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
_playingLineIndex = newPlayingIndex;
int primaryPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, _renderLyricsLines);
bool isPrimaryPlayingLineChanged = primaryPlayingIndex != _primaryPlayingLineIndex;
_primaryPlayingLineIndex = primaryPlayingIndex;
#endregion
#region UpdateTargetScrollOffset
if (isPlayingLineChanged || _isLayoutChanged)
if (isPrimaryPlayingLineChanged || _isLayoutChanged)
{
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _playingLineIndex);
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _primaryPlayingLineIndex);
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
@@ -524,7 +521,7 @@ namespace BetterLyrics.WinUI3.Controls
_renderLyricsLines,
_isMouseScrolling ? maxRange.Start : _visibleRange.Start,
_isMouseScrolling ? maxRange.End : _visibleRange.End,
_playingLineIndex,
_primaryPlayingLineIndex,
sender.Size.Height,
_canvasTargetScrollOffset,
lyricsStyle.PlayingLineTopOffset / 100.0,
@@ -536,7 +533,7 @@ namespace BetterLyrics.WinUI3.Controls
elapsedTime,
_isMouseScrolling,
_isLayoutChanged,
isPlayingLineChanged,
isPrimaryPlayingLineChanged,
_isMouseScrollingChanged,
_songPositionWithOffset.TotalMilliseconds
);
@@ -669,15 +666,7 @@ namespace BetterLyrics.WinUI3.Controls
private void UpdateRenderLyricsLines()
{
_renderLyricsLines = null;
var lines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine()
{
LyricsSyllables = x.LyricsSyllables,
StartMs = x.StartMs,
EndMs = x.EndMs,
PhoneticText = x.PhoneticText,
OriginalText = x.OriginalText,
TranslatedText = x.TranslatedText
}).ToList();
var lines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine(x)).ToList();
if (lines != null)
{
LyricsLayoutManager.CalculateLanes(lines);

View File

@@ -108,6 +108,14 @@
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsFloatAnimationAmount, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="LyricsEffectSettingsControlAnimationDuration">
<local:ExtendedSlider
Default="450"
Maximum="2000"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsFloatAnimationDuration, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>

View File

@@ -291,7 +291,7 @@
</interactivity:Interaction.Behaviors>
</Button>
</Grid>
<local:PropertyRow Grid.Column="1" Value="{x:Bind OriginalText, Mode=OneWay}" />
<local:PropertyRow Grid.Column="1" Value="{x:Bind PrimaryText, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -447,7 +447,7 @@
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.StartMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
<!-- TODO 原文翻译共同显示 -->
<TextBlock Margin="0,0,0,2" Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.OriginalText, Mode=OneWay}" />
<TextBlock Margin="0,0,0,2" Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.PrimaryText, Mode=OneWay}" />
</StackPanel>
</Grid>
<Grid

View File

@@ -228,6 +228,9 @@
</dev:SettingsExpander.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache">
<CheckBox IsChecked="{Binding IgnoreCacheWhenSearching, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageOverwriteMatchingThreshold">
<ToggleSwitch IsOn="{Binding IsMatchingThresholdOverwritten, Mode=TwoWay}" />
</dev:SettingsCard>

View File

@@ -19,7 +19,7 @@ namespace BetterLyrics.WinUI3.Extensions
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = "● ● ●",
PrimaryText = "● ● ●",
},
],
LanguageCode = "N/A",
@@ -37,12 +37,12 @@ namespace BetterLyrics.WinUI3.Extensions
if (transLine != null)
{
// 此处 transLine.OriginalText 指翻译中的“原文”属性
line.TranslatedText = transLine.OriginalText;
line.SecondaryText = transLine.PrimaryText;
}
else
{
// 没有匹配的翻译
line.TranslatedText = "";
line.SecondaryText = "";
}
}
}
@@ -58,12 +58,12 @@ namespace BetterLyrics.WinUI3.Extensions
if (transLine != null)
{
// 此处 transLine.OriginalText 指音译中的“原文”属性
line.PhoneticText = transLine.OriginalText;
line.TertiaryText = transLine.PrimaryText;
}
else
{
// 没有匹配的音译
line.PhoneticText = "";
line.TertiaryText = "";
}
}
}
@@ -76,11 +76,11 @@ namespace BetterLyrics.WinUI3.Extensions
{
if (i >= translationArr.Count)
{
line.TranslatedText = ""; // No translation available, keep empty
line.SecondaryText = ""; // No translation available, keep empty
}
else
{
line.TranslatedText = translationArr[i];
line.SecondaryText = translationArr[i];
}
i++;
}
@@ -94,11 +94,11 @@ namespace BetterLyrics.WinUI3.Extensions
{
if (i >= transliterationArr.Count)
{
line.PhoneticText = ""; // No transliteration available, keep empty
line.TertiaryText = ""; // No transliteration available, keep empty
}
else
{
line.PhoneticText = transliterationArr[i];
line.TertiaryText = transliterationArr[i];
}
i++;
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Entities;
using F23.StringSimilarity;
using System;
using System.IO;
@@ -17,21 +18,31 @@ namespace BetterLyrics.WinUI3.Helper
// JaroWinkler 适合短字符串匹配
private static readonly JaroWinkler _algo = new();
public static int CalculateScore(SongInfo local, LyricsCacheItem remote)
public static int CalculateScore(SongInfo songInfo, LyricsCacheItem remote)
{
if (local == null || remote == null) return 0;
return CalculateScore(songInfo, remote.Title, remote.Artist, remote.Album, remote.Duration * 1000);
}
public static int CalculateScore(SongInfo songInfo, FilesIndexItem local)
{
return CalculateScore(songInfo, local.Title, local.Artist, local.Album, local.Duration * 1000, local.FileName);
}
public static int CalculateScore(
SongInfo songInfo,
string? compareTitle, string? compareArtist, string? compareAlbum, double? compareDurationMs, string? compareFileName = null)
{
double totalScore = 0;
bool localHasMetadata = !string.IsNullOrWhiteSpace(local.Title);
bool remoteHasMetadata = !string.IsNullOrWhiteSpace(remote.Title);
bool localHasMetadata = !string.IsNullOrWhiteSpace(songInfo.Title);
bool remoteHasMetadata = !string.IsNullOrWhiteSpace(compareTitle);
if (localHasMetadata && remoteHasMetadata)
{
double titleScore = GetStringSimilarity(local.Title, remote.Title);
double artistScore = GetStringSimilarity(local.Artist, remote.Artist);
double albumScore = GetStringSimilarity(local.Album, remote.Album);
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
double titleScore = GetStringSimilarity(songInfo.Title, compareTitle);
double artistScore = GetStringSimilarity(songInfo.Artist, compareArtist);
double albumScore = GetStringSimilarity(songInfo.Album, compareAlbum);
double durationScore = GetDurationSimilarity(songInfo.DurationMs, compareDurationMs);
totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
@@ -41,12 +52,12 @@ namespace BetterLyrics.WinUI3.Helper
else
{
string? localQuery = localHasMetadata
? $"{local.Title} {local.Artist}"
: Path.GetFileNameWithoutExtension(local.LinkedFileName);
? $"{songInfo.Title} {songInfo.Artist}"
: Path.GetFileNameWithoutExtension(songInfo.LinkedFileName);
string? remoteQuery = remoteHasMetadata
? $"{remote.Title} {remote.Artist}"
: null;
? $"{compareTitle} {compareArtist}"
: Path.GetFileNameWithoutExtension(compareFileName);
string fp1 = CreateSortedFingerprint(localQuery);
string fp2 = CreateSortedFingerprint(remoteQuery);

View File

@@ -64,6 +64,16 @@ namespace BetterLyrics.WinUI3.Helper
_durationSeconds = seconds;
}
public void SetDurationMs(double millionSeconds)
{
SetDuration(millionSeconds / 1000.0);
}
public void SetDuration(TimeSpan timeSpan)
{
SetDuration(timeSpan.TotalSeconds);
}
public void SetDelay(double seconds)
{
_delaySeconds = seconds;

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models.Lyrics;
using BetterLyrics.WinUI3.Models.Settings;
using DevWinUI;
@@ -49,15 +50,15 @@ namespace BetterLyrics.WinUI3.Logic
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
bool isSecondaryLinePlaying = line.StartMs <= currentPositionMs && currentPositionMs <= line.EndMs;
if (i == primaryPlayingLineIndex) isSecondaryLinePlaying = true;
bool isSecondaryLinePlaying = line.GetIsPlaying(currentPositionMs);
bool isSecondaryLinePlayingChanged = line.IsPlayingLastFrame != isSecondaryLinePlaying;
line.IsPlayingLastFrame = isSecondaryLinePlaying;
// 行动画
if (isLayoutChanged || isPrimaryPlayingLineChanged || isMouseScrollingChanged || isSecondaryLinePlayingChanged)
{
int lineCountDelta = i - primaryPlayingLineIndex;
double distanceFromPlayingLine = Math.Abs(line.OriginalPosition.Y - currentPlayingLine.OriginalPosition.Y);
double distanceFromPlayingLine = Math.Abs(line.PrimaryPosition.Y - currentPlayingLine.PrimaryPosition.Y);
double distanceFactor;
if (lineCountDelta < 0)
@@ -151,61 +152,27 @@ namespace BetterLyrics.WinUI3.Logic
line.YOffsetTransition.StartTransition(targetYScrollOffset);
}
if (line.RenderLyricsOriginalChars != null)
var maxAnimationDurationMs = Math.Max(line.EndMs - currentPositionMs, 0);
// 字符动画
foreach (var renderChar in line.PrimaryRenderChars)
{
foreach (var renderChar in line.RenderLyricsOriginalChars)
{
var syllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= renderChar.Index && renderChar.Index <= x.EndIndex);
if (syllable == null) continue;
renderChar.ProgressPlayed = renderChar.GetPlayProgress(currentPositionMs);
var avgCharDuration = syllable.DurationMs / syllable.Length;
if (avgCharDuration == null || avgCharDuration == 0) continue;
var charStartMs = syllable.StartMs + (renderChar.Index - syllable.StartIndex) * avgCharDuration.Value;
var charEndMs = charStartMs + avgCharDuration;
var progressPlayed = (currentPositionMs - charStartMs) / avgCharDuration.Value;
progressPlayed = Math.Clamp(progressPlayed, 0, 1);
renderChar.ProgressPlayed = progressPlayed;
bool isCharPlaying = charStartMs <= currentPositionMs && currentPositionMs <= charEndMs;
bool isCharPlaying = renderChar.GetIsPlaying(currentPositionMs);
bool isCharPlayingChanged = renderChar.IsPlayingLastFrame != isCharPlaying;
if (isSecondaryLinePlayingChanged || isCharPlayingChanged)
{
if (lyricsEffect.IsLyricsScaleEffectEnabled)
{
double targetScale =
lyricsEffect.IsLyricsScaleEffectAmountAutoAdjust ? 1.15 : lyricsEffect.LyricsScaleEffectAmount / 100.0;
if (isCharPlayingChanged)
{
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
{
renderChar.ScaleTransition.SetDuration((syllable.DurationMs ?? 0) / 1000.0 / 2);
renderChar.ScaleTransition.StartTransition(isCharPlaying ? targetScale : 1);
}
}
}
if (lyricsEffect.IsLyricsGlowEffectEnabled)
{
double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
switch (lyricsEffect.LyricsGlowEffectScope)
{
case Enums.LyricsEffectScope.LongDurationSyllable:
if (isCharPlayingChanged)
{
if (syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration)
{
renderChar.GlowTransition.SetDuration((syllable.DurationMs ?? 0) / 1000.0 / 2);
renderChar.GlowTransition.StartTransition(isCharPlaying ? targetGlow : 0);
}
}
break;
case Enums.LyricsEffectScope.LineStartToCurrentChar:
if (isSecondaryLinePlayingChanged)
{
renderChar.GlowTransition.SetDuration(renderChar.AnimationDuration);
renderChar.GlowTransition.SetDurationMs(Math.Min(Time.AnimationDuration.TotalMilliseconds, maxAnimationDurationMs));
renderChar.GlowTransition.StartTransition(isSecondaryLinePlaying ? targetGlow : 0);
}
break;
@@ -225,18 +192,74 @@ namespace BetterLyrics.WinUI3.Logic
}
if (isCharPlayingChanged)
{
renderChar.FloatTransition.SetDurationMs(Math.Min(lyricsEffect.LyricsFloatAnimationDuration, maxAnimationDurationMs));
renderChar.FloatTransition.StartTransition(0);
}
}
if (isCharPlayingChanged)
{
renderChar.IsPlayingLastFrame = isCharPlaying;
}
}
}
// 音节动画
foreach (var syllable in line.PrimaryRenderSyllables)
{
bool isSyllablePlaying = syllable.GetIsPlaying(currentPositionMs);
bool isSyllablePlayingChanged = syllable.IsPlayingLastFrame != isSyllablePlaying;
if (isSyllablePlayingChanged)
{
var syllableHeight = syllable.ChildrenRenderLyricsChars.FirstOrDefault()?.LayoutRect.Height ?? 0;
if (lyricsEffect.IsLyricsScaleEffectEnabled)
{
double targetScale =
lyricsEffect.IsLyricsScaleEffectAmountAutoAdjust ? 1.15 : lyricsEffect.LyricsScaleEffectAmount / 100.0;
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
{
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
{
renderChar.ScaleTransition.SetDurationMs(Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0);
renderChar.ScaleTransition.StartTransition(isSyllablePlaying ? targetScale : 1);
}
}
}
if (lyricsEffect.IsLyricsGlowEffectEnabled)
{
double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? syllableHeight * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
switch (lyricsEffect.LyricsGlowEffectScope)
{
case Enums.LyricsEffectScope.LongDurationSyllable:
if (syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration)
{
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
{
renderChar.GlowTransition.SetDurationMs(Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0);
renderChar.GlowTransition.StartTransition(isSyllablePlaying ? targetGlow : 0);
}
}
break;
default:
break;
}
}
syllable.IsPlayingLastFrame = isSyllablePlaying;
}
}
// 更新动画
foreach (var renderChar in line.PrimaryRenderChars)
{
renderChar.ScaleTransition.Update(elapsedTime);
renderChar.GlowTransition.Update(elapsedTime);
renderChar.FloatTransition.Update(elapsedTime);
}
}
line.AngleTransition.Update(elapsedTime);
line.ScaleTransition.Update(elapsedTime);

View File

@@ -79,52 +79,52 @@ namespace BetterLyrics.WinUI3.Logic
// 左上角坐标
line.TopLeftPosition = new Vector2(0, (float)currentY);
// 注音层
line.PhoneticPosition = line.TopLeftPosition;
if (line.PhoneticCanvasTextLayout != null)
line.TertiaryPosition = line.TopLeftPosition;
if (line.TertiaryTextLayout != null)
{
currentY += line.PhoneticCanvasTextLayout.LayoutBounds.Height;
currentY += line.TertiaryTextLayout.LayoutBounds.Height;
// 间距
currentY += (line.PhoneticCanvasTextLayout.LayoutBounds.Height / line.PhoneticCanvasTextLayout.LineCount) * 0.1;
currentY += (line.TertiaryTextLayout.LayoutBounds.Height / line.TertiaryTextLayout.LineCount) * 0.1;
actualWidth = Math.Max(actualWidth, line.PhoneticCanvasTextLayout.LayoutBounds.Width);
actualWidth = Math.Max(actualWidth, line.TertiaryTextLayout.LayoutBounds.Width);
}
// 原文层
line.OriginalPosition = new Vector2(0, (float)currentY);
if (line.OriginalCanvasTextLayout != null)
line.PrimaryPosition = new Vector2(0, (float)currentY);
if (line.PrimaryTextLayout != null)
{
currentY += line.OriginalCanvasTextLayout.LayoutBounds.Height;
currentY += line.PrimaryTextLayout.LayoutBounds.Height;
actualWidth = Math.Max(actualWidth, line.OriginalCanvasTextLayout.LayoutBounds.Width);
actualWidth = Math.Max(actualWidth, line.PrimaryTextLayout.LayoutBounds.Width);
}
// 翻译层
if (line.TranslatedCanvasTextLayout != null)
if (line.SecondaryTextLayout != null)
{
// 间距
currentY += (line.TranslatedCanvasTextLayout.LayoutBounds.Height / line.TranslatedCanvasTextLayout.LineCount) * 0.1;
currentY += (line.SecondaryTextLayout.LayoutBounds.Height / line.SecondaryTextLayout.LineCount) * 0.1;
}
line.TranslatedPosition = new Vector2(0, (float)currentY);
if (line.TranslatedCanvasTextLayout != null)
line.SecondaryPosition = new Vector2(0, (float)currentY);
if (line.SecondaryTextLayout != null)
{
currentY += line.TranslatedCanvasTextLayout.LayoutBounds.Height;
currentY += line.SecondaryTextLayout.LayoutBounds.Height;
actualWidth = Math.Max(actualWidth, line.TranslatedCanvasTextLayout.LayoutBounds.Width);
actualWidth = Math.Max(actualWidth, line.SecondaryTextLayout.LayoutBounds.Width);
}
// 右下角坐标
line.BottomRightPosition = new Vector2(0 + (float)actualWidth, (float)currentY);
// 行间距
if (line.OriginalCanvasTextLayout != null)
if (line.PrimaryTextLayout != null)
{
currentY += (line.OriginalCanvasTextLayout.LayoutBounds.Height / line.OriginalCanvasTextLayout.LineCount) * style.LyricsLineSpacingFactor;
currentY += (line.PrimaryTextLayout.LayoutBounds.Height / line.PrimaryTextLayout.LineCount) * style.LyricsLineSpacingFactor;
}
// 更新中心点
line.UpdateCenterPosition(lyricsWidth, style.LyricsAlignmentType);
line.RecalculateCharacterGeometries();
line.RecreateRenderChars();
}
}
@@ -140,9 +140,9 @@ namespace BetterLyrics.WinUI3.Logic
var currentLine = lines.ElementAtOrDefault(playingLineIndex);
var firstLine = lines.FirstOrDefault();
if (currentLine?.OriginalCanvasTextLayout == null || firstLine == null) return null;
if (currentLine?.PrimaryTextLayout == null || firstLine == null) return null;
return -currentLine.OriginalPosition.Y + firstLine.OriginalPosition.Y
return -currentLine.PrimaryPosition.Y + firstLine.PrimaryPosition.Y
- (currentLine.BottomRightPosition.Y - currentLine.TopLeftPosition.Y) / 2.0;
}
@@ -215,7 +215,7 @@ namespace BetterLyrics.WinUI3.Logic
lanesEndMs.Add(0);
}
lanesEndMs[assignedLane] = end ?? 0;
lanesEndMs[assignedLane] = end;
line.LaneIndex = assignedLane;
}
}
@@ -241,7 +241,7 @@ namespace BetterLyrics.WinUI3.Logic
{
int mid = (left + right) / 2;
var line = lines[mid];
if (line.OriginalCanvasTextLayout == null) break;
if (line.PrimaryTextLayout == null) break;
double value = offset + line.BottomRightPosition.Y;
if (value >= mousePosition.Y) { result = mid; right = mid - 1; }
else { left = mid + 1; }
@@ -267,7 +267,7 @@ namespace BetterLyrics.WinUI3.Logic
{
int mid = (left + right) / 2;
var line = lines[mid];
if (line.OriginalCanvasTextLayout == null) break;
if (line.PrimaryTextLayout == null) break;
double value = offset + line.BottomRightPosition.Y;
// 理论上说应该使用下面这一行来精确计算视野内的首个可见行,但是考虑到动画视觉效果,还是注释掉了
//if (value >= lyricsY) { result = mid; right = mid - 1; }
@@ -284,7 +284,7 @@ namespace BetterLyrics.WinUI3.Logic
{
int mid = (left + right) / 2;
var line = lines[mid];
if (line.OriginalCanvasTextLayout == null) break;
if (line.PrimaryTextLayout == null) break;
double value = offset + line.BottomRightPosition.Y;
// 同理
//if (value >= lyricsY + lyricsHeight) { result = mid; right = mid - 1; }

View File

@@ -66,19 +66,14 @@ namespace BetterLyrics.WinUI3.Logic
public LinePlaybackState GetLinePlayingProgress(
double currentTimeMs,
LyricsLine line,
LyricsLine? nextLine,
double songDurationMs,
RenderLyricsLine line,
bool isForceWordByWord)
{
var state = new LinePlaybackState { SyllableStartIndex = 0, SyllableLength = 0, SyllableProgress = 0 };
if (line == null) return state;
double lineEndMs;
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
else if (nextLine != null) lineEndMs = nextLine.StartMs;
else lineEndMs = songDurationMs;
double lineEndMs = line.EndMs;
// 还没到
if (currentTimeMs < line.StartMs) return state;
@@ -87,42 +82,43 @@ namespace BetterLyrics.WinUI3.Logic
if (currentTimeMs > lineEndMs)
{
state.SyllableProgress = 1f;
state.SyllableStartIndex = Math.Max(0, line.OriginalText.Length - 1);
state.SyllableStartIndex = Math.Max(0, line.PrimaryText.Length - 1);
state.SyllableLength = 1;
return state;
}
// 逐字
if (line.LyricsSyllables != null && line.LyricsSyllables.Count > 1)
if (line.PrimaryRenderSyllables != null && line.PrimaryRenderSyllables.Count > 1)
{
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
}
// 强制逐字
if (isForceWordByWord && line.OriginalText.Length > 0)
if (isForceWordByWord && line.PrimaryText.Length > 0)
{
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
}
else
{
// 普通行
state.SyllableStartIndex = line.OriginalText.Length;
state.SyllableStartIndex = line.PrimaryText.Length;
state.SyllableProgress = 1f;
return state;
}
}
private LinePlaybackState CalculateSyllableProgress(double time, LyricsLine line, double lineEndMs)
private LinePlaybackState CalculateSyllableProgress(double time, RenderLyricsLine line, double lineEndMs)
{
var state = new LinePlaybackState();
int count = line.LyricsSyllables.Count;
int count = line.PrimaryRenderSyllables.Count;
for (int i = 0; i < count; i++)
{
var timing = line.LyricsSyllables[i];
var nextTiming = (i + 1 < count) ? line.LyricsSyllables[i + 1] : null;
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 ?? nextTiming?.StartMs ?? lineEndMs;
double timingEndMs = timing.EndMs;
// 在当前字范围内
if (time >= timing.StartMs && time <= timingEndMs)
@@ -146,10 +142,10 @@ namespace BetterLyrics.WinUI3.Logic
return state;
}
private LinePlaybackState CalculateSimulatedProgress(double time, LyricsLine line, double lineEndMs)
private LinePlaybackState CalculateSimulatedProgress(double time, RenderLyricsLine line, double lineEndMs)
{
var state = new LinePlaybackState();
int textLength = line.OriginalText.Length;
int textLength = line.PrimaryText.Length;
double progress = (time - line.StartMs) / (lineEndMs - line.StartMs);
progress = Math.Clamp(progress, 0, 1);

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class BaseLyrics
{
public int StartMs { get; set; }
public int EndMs { get; set; }
public int DurationMs => EndMs - StartMs;
public string Text { get; set; } = "";
public int Length => Text.Length;
public int StartIndex { get; set; }
public int EndIndex => StartIndex + Length - 1;
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class BaseRenderLyrics : BaseLyrics
{
public bool IsPlayingLastFrame { get; set; } = false;
public BaseRenderLyrics(BaseLyrics baseLyrics)
{
this.Text = baseLyrics.Text;
this.StartMs = baseLyrics.StartMs;
this.EndMs = baseLyrics.EndMs;
this.StartIndex = baseLyrics.StartIndex;
}
public bool GetIsPlaying(double currentMs) => this.StartMs <= currentMs && currentMs < this.EndMs;
public double GetPlayProgress(double currentMs) => Math.Clamp((currentMs - this.StartMs) / this.DurationMs, 0, 1);
}
}

View File

@@ -1,12 +0,0 @@
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class LyricsChar
{
public int StartMs { get; set; }
public int EndMs { get; set; }
public int DurationMs => EndMs - StartMs;
public string Text { get; set; } = "";
public int Index { get; set; }
}
}

View File

@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
set => field = value;
}
public bool AutoGenerated { get; set; } = false;
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.PrimaryText));
public LyricsData()
{
@@ -35,7 +35,7 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = _localizationService.GetLocalizedString("LyricsNotFound"),
PrimaryText = _localizationService.GetLocalizedString("LyricsNotFound"),
}]);
}

View File

@@ -1,29 +1,46 @@
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using System.Linq;
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class LyricsLine
public class LyricsLine : BaseLyrics
{
public List<LyricsSyllable> LyricsSyllables { get; set; } = [];
public List<BaseLyrics> PrimarySyllables { get; set; } = [];
public List<BaseLyrics> SecondarySyllables { get; set; } = [];
public List<BaseLyrics> TertiarySyllables { get; set; } = [];
public int? DurationMs => EndMs - StartMs;
public int? EndMs { get; set; }
public int StartMs { get; set; }
public List<BaseLyrics> PrimaryChars { get; private set; } = [];
public List<BaseLyrics> SecondaryChars { get; private set; } = [];
public List<BaseLyrics> TertiaryChars { get; private set; } = [];
/// <summary>
/// 原文
/// </summary>
public string OriginalText { get; set; } = "";
/// <summary>
/// 译文
/// </summary>
public string TranslatedText { get; set; } = "";
/// <summary>
/// 注音
/// </summary>
public string PhoneticText { get; set; } = "";
public string PrimaryText { get; set; } = "";
public string SecondaryText { get; set; } = "";
public string TertiaryText { get; set; } = "";
public LyricsLine()
{
for (int charStartIndex = 0; charStartIndex < PrimaryText.Length; charStartIndex++)
{
var syllable = PrimarySyllables.FirstOrDefault(x => x.StartIndex <= charStartIndex && charStartIndex <= x.EndIndex);
if (syllable == null) continue;
var avgCharDuration = syllable.DurationMs / syllable.Length;
if (avgCharDuration == 0) continue;
var charStartMs = syllable.StartMs + (charStartIndex - syllable.StartIndex) * avgCharDuration;
var charEndMs = charStartMs + avgCharDuration;
PrimaryChars.Add(new BaseLyrics
{
StartIndex = charStartIndex,
StartMs = charStartMs,
EndMs = charEndMs,
Text = PrimaryText[charStartIndex].ToString()
});
}
}
}
}

View File

@@ -1,19 +0,0 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class LyricsSyllable
{
public string Text { get; set; } = string.Empty;
public int Length => Text.Length;
public int StartIndex { get; set; }
public int EndIndex => StartIndex + Length - 1;
public int StartMs { get; set; }
public int? EndMs { get; set; }
public int? DurationMs => EndMs - StartMs;
public bool IsLongDuration => DurationMs >= 700;
}
}

View File

@@ -1,14 +1,13 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class RenderLyricsChar : LyricsChar
public class RenderLyricsChar : BaseRenderLyrics
{
public Rect LayoutRect { get; set; }
public double AnimationDuration { get; set; } = 0.3;
public Rect LayoutRect { get; private set; }
public ValueTransition<double> ScaleTransition { get; set; }
public ValueTransition<double> GlowTransition { get; set; }
@@ -16,25 +15,24 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
public double ProgressPlayed { get; set; } = 0; // 0~1
public bool IsPlayingLastFrame { get; set; } = false;
public RenderLyricsChar()
public RenderLyricsChar(BaseLyrics lyricsChars, Rect layoutRect) : base(lyricsChars)
{
ScaleTransition = new(
initialValue: 1.0,
durationSeconds: AnimationDuration,
durationSeconds: Time.AnimationDuration.TotalSeconds,
easingType: EasingType.EaseInOutSine
);
GlowTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
durationSeconds: Time.AnimationDuration.TotalSeconds,
easingType: EasingType.EaseInOutSine
);
FloatTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
durationSeconds: Time.LongAnimationDuration.TotalSeconds,
easingType: EasingType.EaseInOutSine
);
LayoutRect = layoutRect;
}
}
}

View File

@@ -12,9 +12,10 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class RenderLyricsLine : LyricsLine
public class RenderLyricsLine : BaseRenderLyrics
{
public List<RenderLyricsChar> RenderLyricsOriginalChars { get; set; } = [];
public List<RenderLyricsChar> PrimaryRenderChars { get; private set; } = [];
public List<RenderLyricsSyllable> PrimaryRenderSyllables { get; private set; }
public double AnimationDuration { get; set; } = 0.3;
@@ -28,22 +29,22 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
public ValueTransition<double> YOffsetTransition { get; set; }
public ValueTransition<Color> ColorTransition { get; set; }
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
public CanvasTextLayout? PhoneticCanvasTextLayout { get; private set; }
public CanvasTextLayout? PrimaryTextLayout { get; private set; }
public CanvasTextLayout? SecondaryTextLayout { get; private set; }
public CanvasTextLayout? TertiaryTextLayout { get; private set; }
/// <summary>
/// 原文坐标(相对于坐标原点)
/// </summary>
public Vector2 OriginalPosition { get; set; }
public Vector2 PrimaryPosition { get; set; }
/// <summary>
/// 译文坐标(相对于坐标原点)
/// </summary>
public Vector2 TranslatedPosition { get; set; }
public Vector2 SecondaryPosition { get; set; }
/// <summary>
/// 注音坐标(相对于坐标原点)
/// </summary>
public Vector2 PhoneticPosition { get; set; }
public Vector2 TertiaryPosition { get; set; }
/// <summary>
/// 顶部坐标(相对于坐标原点)
@@ -58,21 +59,21 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
/// </summary>
public Vector2 BottomRightPosition { get; set; }
public CanvasGeometry? OriginalCanvasGeometry { get; private set; }
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public CanvasGeometry? PrimaryCanvasGeometry { get; private set; }
public CanvasGeometry? SecondaryCanvasGeometry { get; private set; }
public CanvasGeometry? TertiaryCanvasGeometry { get; private set; }
public string PrimaryText { get; set; } = "";
public string SecondaryText { get; set; } = "";
public string TertiaryText { get; set; } = "";
/// <summary>
/// 轨道索引 (0 = 主轨道, 1 = 第一副轨道, etc.)
/// 用于布局计算时的堆叠逻辑
/// </summary>
public int LaneIndex { get; set; } = 0;
/// <summary>
/// 是否为背景人声/和声
/// </summary>
public bool IsPlayingLastFrame { get; set; } = false;
public RenderLyricsLine()
public RenderLyricsLine(LyricsLine lyricsLine) : base(lyricsLine)
{
AngleTransition = new(
initialValue: 0,
@@ -119,11 +120,18 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
StartMs = lyricsLine.StartMs;
EndMs = lyricsLine.EndMs;
TertiaryText = lyricsLine.TertiaryText;
PrimaryText = lyricsLine.PrimaryText;
SecondaryText = lyricsLine.SecondaryText;
PrimaryRenderSyllables = lyricsLine.PrimarySyllables.Select(x => new RenderLyricsSyllable(x)).ToList();
}
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (OriginalCanvasTextLayout == null)
if (PrimaryTextLayout == null)
{
return;
}
@@ -141,14 +149,14 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
public void DisposeTextLayout()
{
PhoneticCanvasTextLayout?.Dispose();
PhoneticCanvasTextLayout = null;
TertiaryTextLayout?.Dispose();
TertiaryTextLayout = null;
OriginalCanvasTextLayout?.Dispose();
OriginalCanvasTextLayout = null;
PrimaryTextLayout?.Dispose();
PrimaryTextLayout = null;
TranslatedCanvasTextLayout?.Dispose();
TranslatedCanvasTextLayout = null;
SecondaryTextLayout?.Dispose();
SecondaryTextLayout = null;
}
public void RecreateTextLayout(
@@ -161,9 +169,9 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
{
DisposeTextLayout();
if (createPhonetic && PhoneticText != "")
if (createPhonetic && TertiaryText != "")
{
PhoneticCanvasTextLayout = new CanvasTextLayout(control, PhoneticText, new CanvasTextFormat
TertiaryTextLayout = new CanvasTextLayout(control, TertiaryText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
@@ -173,10 +181,10 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
};
PhoneticCanvasTextLayout.SetFontFamily(PhoneticText, fontFamilyCJK, fontFamilyWestern);
TertiaryTextLayout.SetFontFamily(TertiaryText, fontFamilyCJK, fontFamilyWestern);
}
OriginalCanvasTextLayout = new CanvasTextLayout(control, OriginalText, new CanvasTextFormat
PrimaryTextLayout = new CanvasTextLayout(control, PrimaryText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
@@ -186,11 +194,11 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
OriginalCanvasTextLayout.SetFontFamily(OriginalText, fontFamilyCJK, fontFamilyWestern);
PrimaryTextLayout.SetFontFamily(PrimaryText, fontFamilyCJK, fontFamilyWestern);
if (createTranslated && TranslatedText != "")
if (createTranslated && SecondaryText != "")
{
TranslatedCanvasTextLayout = new CanvasTextLayout(control, TranslatedText, new CanvasTextFormat
SecondaryTextLayout = new CanvasTextLayout(control, SecondaryText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
@@ -200,60 +208,77 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
TranslatedCanvasTextLayout.SetFontFamily(TranslatedText, fontFamilyCJK, fontFamilyWestern);
SecondaryTextLayout.SetFontFamily(SecondaryText, fontFamilyCJK, fontFamilyWestern);
}
}
public void DisposeTextGeometry()
{
PhoneticCanvasGeometry?.Dispose();
PhoneticCanvasGeometry = null;
TertiaryCanvasGeometry?.Dispose();
TertiaryCanvasGeometry = null;
OriginalCanvasGeometry?.Dispose();
OriginalCanvasGeometry = null;
PrimaryCanvasGeometry?.Dispose();
PrimaryCanvasGeometry = null;
TranslatedCanvasGeometry?.Dispose();
TranslatedCanvasGeometry = null;
SecondaryCanvasGeometry?.Dispose();
SecondaryCanvasGeometry = null;
}
public void RecreateTextGeometry()
{
DisposeTextGeometry();
if (PhoneticCanvasTextLayout != null)
if (TertiaryTextLayout != null)
{
PhoneticCanvasGeometry = CanvasGeometry.CreateText(PhoneticCanvasTextLayout);
TertiaryCanvasGeometry = CanvasGeometry.CreateText(TertiaryTextLayout);
}
if (OriginalCanvasTextLayout != null)
if (PrimaryTextLayout != null)
{
OriginalCanvasGeometry = CanvasGeometry.CreateText(OriginalCanvasTextLayout);
PrimaryCanvasGeometry = CanvasGeometry.CreateText(PrimaryTextLayout);
}
if (TranslatedCanvasTextLayout != null)
if (SecondaryTextLayout != null)
{
TranslatedCanvasGeometry = CanvasGeometry.CreateText(TranslatedCanvasTextLayout);
SecondaryCanvasGeometry = CanvasGeometry.CreateText(SecondaryTextLayout);
}
}
public void RecalculateCharacterGeometries()
public void RecreateRenderChars()
{
RenderLyricsOriginalChars.Clear();
if (OriginalCanvasTextLayout == null) return;
PrimaryRenderChars.Clear();
if (PrimaryTextLayout == null) return;
var textLength = OriginalText.Length;
for (int i = 0; i < textLength; i++)
foreach (var syllable in PrimaryRenderSyllables)
{
var region = OriginalCanvasTextLayout.GetCharacterRegions(i, 1).FirstOrDefault();
syllable.ChildrenRenderLyricsChars.Clear();
}
var textLength = PrimaryText.Length;
for (int startCharIndex = 0; startCharIndex < textLength; startCharIndex++)
{
var region = PrimaryTextLayout.GetCharacterRegions(startCharIndex, 1).FirstOrDefault();
var bounds = region.LayoutBounds;
RenderLyricsOriginalChars.Add(new RenderLyricsChar()
var syllable = PrimaryRenderSyllables.FirstOrDefault(x => x.StartIndex <= startCharIndex && startCharIndex <= x.EndIndex);
if (syllable == null) continue;
var avgCharDuration = syllable.DurationMs / syllable.Length;
var charStartMs = syllable.StartMs + (startCharIndex - syllable.StartIndex) * avgCharDuration;
var charEndMs = charStartMs + avgCharDuration;
var renderLyricsChar = new RenderLyricsChar(new BaseLyrics
{
Index = i,
LayoutRect = bounds,
Text = OriginalText[i].ToString()
});
StartIndex = startCharIndex,
Text = PrimaryText[startCharIndex].ToString(),
StartMs = charStartMs,
EndMs = charEndMs,
}, bounds);
syllable.ChildrenRenderLyricsChars.Add(renderLyricsChar);
PrimaryRenderChars.Add(renderLyricsChar);
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Lyrics
{
public class RenderLyricsSyllable : BaseRenderLyrics
{
public List<RenderLyricsChar> ChildrenRenderLyricsChars { get; set; } = [];
public RenderLyricsSyllable(BaseLyrics lyricsSyllable) : base(lyricsSyllable) { }
}
}

View File

@@ -24,6 +24,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationAmountAutoAdjust { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAnimationAmount { get; set; } = 8;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAnimationDuration { get; set; } = 450; // 450ms
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EasingType LyricsScrollEasingType { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
@@ -71,6 +72,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
IsLyricsFloatAnimationEnabled = this.IsLyricsFloatAnimationEnabled,
IsLyricsFloatAnimationAmountAutoAdjust = this.IsLyricsFloatAnimationAmountAutoAdjust,
LyricsFloatAnimationAmount = this.LyricsFloatAnimationAmount,
LyricsFloatAnimationDuration = this.LyricsFloatAnimationDuration,
LyricsScrollEasingType = this.LyricsScrollEasingType,
LyricsScrollDuration = this.LyricsScrollDuration,

View File

@@ -11,6 +11,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMatchingThresholdOverwritten { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IgnoreCacheWhenSearching { get; set; } = false;
public LyricsSearchProviderInfo() { }

View File

@@ -24,7 +24,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
foreach (var line in lines)
{
var matches = syllableRegex.Matches(line);
var syllables = new List<LyricsSyllable>();
var syllables = new List<BaseLyrics>();
int startIndex = 0;
for (int i = 0; i < matches.Count; i++)
@@ -36,7 +36,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
int totalMs = min * 60_000 + sec * 1000 + ms;
string text = match.Groups[6].Value;
syllables.Add(new LyricsSyllable { StartMs = totalMs, Text = text, StartIndex = startIndex });
syllables.Add(new BaseLyrics { StartMs = totalMs, Text = text, StartIndex = startIndex });
startIndex += text.Length;
}
@@ -58,8 +58,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = syllables[0].StartMs,
EndMs = lineEndMs,
OriginalText = string.Concat(syllables.Select(s => s.Text)),
LyricsSyllables = syllables
PrimaryText = string.Concat(syllables.Select(s => s.Text)),
PrimarySyllables = syllables
});
}
else
@@ -81,7 +81,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
content = bracketRegex!.Replace(line, "").Trim();
if (content == "//") content = "";
lrcLines.Add(new LyricsLine { StartMs = lineStartMs, OriginalText = content });
lrcLines.Add(new LyricsLine { StartMs = lineStartMs, PrimaryText = content });
}
}
}

View File

@@ -17,12 +17,13 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
{
var lineRead = lines[lineIndex];
var nextLineRead = lines.ElementAtOrDefault(lineIndex + 1);
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = lineRead.EndTime ?? 0,
OriginalText = lineRead.Text,
LyricsSyllables = [],
EndMs = lineRead.EndTime ?? (nextLineRead?.StartTime ?? 0),
PrimaryText = lineRead.Text,
PrimarySyllables = [],
};
var syllables = (lineRead as Lyricify.Lyrics.Models.SyllableLineInfo)?.Syllables;
@@ -36,14 +37,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
)
{
var syllable = syllables[syllableIndex];
var charTiming = new LyricsSyllable
var charTiming = new BaseLyrics
{
StartMs = syllable.StartTime,
EndMs = syllable.EndTime,
Text = syllable.Text,
StartIndex = startIndex,
};
lineWrite.LyricsSyllables.Add(charTiming);
lineWrite.PrimarySyllables.Add(charTiming);
startIndex += syllable.Text.Length;
}
}

View File

@@ -93,7 +93,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
}
}
var syllables = new List<LyricsSyllable>();
var syllables = new List<BaseLyrics>();
int startIndex = 0;
var sbText = new System.Text.StringBuilder();
@@ -103,7 +103,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
int sEndMs = ParseTtmlTime(span.Attribute("end")?.Value);
string text = span.Value;
syllables.Add(new LyricsSyllable
syllables.Add(new BaseLyrics
{
StartMs = sStartMs,
EndMs = sEndMs,
@@ -126,8 +126,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = containerStartMs,
EndMs = containerEndMs,
OriginalText = fullOriginalText,
LyricsSyllables = syllables
PrimaryText = fullOriginalText,
PrimarySyllables = syllables
});
var transSpan = container.Elements()
@@ -151,7 +151,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = startMs,
EndMs = endMs,
OriginalText = text
PrimaryText = text
});
}
else
@@ -160,7 +160,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = startMs,
EndMs = endMs,
OriginalText = ""
PrimaryText = ""
});
}
}

View File

@@ -167,14 +167,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
foreach (var item in main.LyricsLines)
{
item.OriginalText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.OriginalText) : ChineseHelper.ToSC(item.OriginalText);
item.PrimaryText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.PrimaryText) : ChineseHelper.ToSC(item.PrimaryText);
}
}
if (settings.SelectedTargetLanguageCode == LanguageHelper.ChineseCode)
{
foreach (var item in main.LyricsLines)
{
item.TranslatedText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.TranslatedText) : ChineseHelper.ToSC(item.TranslatedText);
item.SecondaryText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.SecondaryText) : ChineseHelper.ToSC(item.SecondaryText);
}
}
@@ -235,8 +235,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToPinyin(line.OriginalText),
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
PrimaryText = PhoneticHelper.ToPinyin(line.PrimaryText),
PrimarySyllables = line.PrimarySyllables.Select(c => new BaseLyrics
{
StartMs = c.StartMs,
EndMs = c.EndMs,
@@ -256,8 +256,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToJyutping(line.OriginalText),
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
PrimaryText = PhoneticHelper.ToJyutping(line.PrimaryText),
PrimarySyllables = line.PrimarySyllables.Select(c => new BaseLyrics
{
StartMs = c.StartMs,
EndMs = c.EndMs,

View File

@@ -26,7 +26,6 @@ namespace BetterLyrics.WinUI3.Renderer
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
IList<RenderLyricsLine>? lines,
int playingLineIndex,
int mouseHoverLineIndex,
bool isMousePressing,
int startVisibleIndex,
@@ -57,7 +56,6 @@ namespace BetterLyrics.WinUI3.Renderer
control,
layerDs,
lines,
playingLineIndex,
mouseHoverLineIndex,
isMousePressing,
startVisibleIndex,
@@ -89,7 +87,6 @@ namespace BetterLyrics.WinUI3.Renderer
control,
ds,
lines,
playingLineIndex,
mouseHoverLineIndex,
isMousePressing,
startVisibleIndex,
@@ -114,7 +111,6 @@ namespace BetterLyrics.WinUI3.Renderer
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
IList<RenderLyricsLine>? lines,
int playingLineIndex,
int mouseHoverLineIndex,
bool isMousePressing,
int startVisibleIndex,
@@ -134,9 +130,6 @@ namespace BetterLyrics.WinUI3.Renderer
{
if (lines == null) return;
var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex);
if (currentPlayingLine == null) return;
var effectSettings = windowStatus.LyricsEffectSettings;
var styleSettings = windowStatus.LyricsStyleSettings;
@@ -148,8 +141,8 @@ namespace BetterLyrics.WinUI3.Renderer
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
if (line.OriginalCanvasTextLayout == null) continue;
if (line.OriginalCanvasTextLayout.LayoutBounds.Width <= 0) continue;
if (line.PrimaryTextLayout == null) continue;
if (line.PrimaryTextLayout.LayoutBounds.Width <= 0) continue;
double xOffset = lyricsX;
double yOffset = line.YOffsetTransition.Value + userScrollOffset + lyricsY + lyricsHeight * playingLineTopOffsetFactor;
@@ -167,8 +160,7 @@ namespace BetterLyrics.WinUI3.Renderer
using (var textOnlyLayer = RenderBaseTextLayer(control, line, styleSettings.LyricsFontStrokeWidth, strokeColor, line.ColorTransition.Value))
{
bool isPlaying = currentProgressMs >= line.StartMs && currentProgressMs <= line.EndMs;
if (i == playingLineIndex) isPlaying = true;
bool isPlaying = line.GetIsPlaying(currentProgressMs);
if (isPlaying)
{
@@ -206,14 +198,14 @@ namespace BetterLyrics.WinUI3.Renderer
{
if (strokeWidth > 0)
{
DrawGeometrySafely(clds, line.PhoneticCanvasGeometry, line.PhoneticPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.OriginalCanvasGeometry, line.OriginalPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.TranslatedCanvasGeometry, line.TranslatedPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.TertiaryCanvasGeometry, line.TertiaryPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.PrimaryCanvasGeometry, line.PrimaryPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.SecondaryCanvasGeometry, line.SecondaryPosition, strokeColor, strokeWidth);
}
DrawTextLayoutSafely(clds, line.PhoneticCanvasTextLayout, line.PhoneticPosition, fillColor);
DrawTextLayoutSafely(clds, line.OriginalCanvasTextLayout, line.OriginalPosition, fillColor);
DrawTextLayoutSafely(clds, line.TranslatedCanvasTextLayout, line.TranslatedPosition, fillColor);
DrawTextLayoutSafely(clds, line.TertiaryTextLayout, line.TertiaryPosition, fillColor);
DrawTextLayoutSafely(clds, line.PrimaryTextLayout, line.PrimaryPosition, fillColor);
DrawTextLayoutSafely(clds, line.SecondaryTextLayout, line.SecondaryPosition, fillColor);
}
return commandList;
}

View File

@@ -33,17 +33,17 @@ namespace BetterLyrics.WinUI3.Renderer
private void DrawPhonetic(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.PhoneticCanvasTextLayout == null) return;
if (line.TertiaryTextLayout == null) return;
var opacity = line.PhoneticOpacityTransition.Value;
var blur = line.BlurAmountTransition.Value;
var bounds = line.PhoneticCanvasTextLayout.LayoutBounds;
var bounds = line.TertiaryTextLayout.LayoutBounds;
if (double.IsNaN(opacity)) return;
var destRect = new Rect(
bounds.X + line.PhoneticPosition.X,
bounds.Y + line.PhoneticPosition.Y,
bounds.X + line.TertiaryPosition.X,
bounds.Y + line.TertiaryPosition.Y,
bounds.Width,
bounds.Height
);
@@ -67,17 +67,17 @@ namespace BetterLyrics.WinUI3.Renderer
private void DrawTranslated(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.TranslatedCanvasTextLayout == null) return;
if (line.SecondaryTextLayout == null) return;
var opacity = line.TranslatedOpacityTransition.Value;
var blur = line.BlurAmountTransition.Value;
var bounds = line.TranslatedCanvasTextLayout.LayoutBounds;
var bounds = line.SecondaryTextLayout.LayoutBounds;
if (double.IsNaN(opacity)) return;
var destRect = new Rect(
bounds.X + line.TranslatedPosition.X,
bounds.Y + line.TranslatedPosition.Y,
bounds.X + line.SecondaryPosition.X,
bounds.Y + line.SecondaryPosition.Y,
bounds.Width,
bounds.Height
);
@@ -109,12 +109,12 @@ namespace BetterLyrics.WinUI3.Renderer
Color fgColor,
LyricsEffectSettings settings)
{
if (line.OriginalCanvasTextLayout == null) return;
if (line.PrimaryTextLayout == null) return;
var curCharIndex = state.SyllableStartIndex + state.SyllableLength * state.SyllableProgress;
float fadeWidth = (1f / Math.Max(1, line.OriginalText.Length)) * 0.5f;
float fadeWidth = (1f / Math.Max(1, line.PrimaryText.Length)) * 0.5f;
var lineRegions = line.OriginalCanvasTextLayout.GetCharacterRegions(0, line.OriginalText.Length);
var lineRegions = line.PrimaryTextLayout.GetCharacterRegions(0, line.PrimaryText.Length);
foreach (var subLineRegion in lineRegions)
{
@@ -139,8 +139,8 @@ namespace BetterLyrics.WinUI3.Renderer
var subLineLayoutBounds = subLineRegion.LayoutBounds;
Rect subLineRect = new(
subLineLayoutBounds.X + line.OriginalPosition.X,
subLineLayoutBounds.Y + line.OriginalPosition.Y,
subLineLayoutBounds.X + line.PrimaryPosition.X,
subLineLayoutBounds.Y + line.PrimaryPosition.Y,
subLineLayoutBounds.Width,
subLineLayoutBounds.Height
);
@@ -208,14 +208,14 @@ namespace BetterLyrics.WinUI3.Renderer
int charIndex,
ICanvasImage source)
{
if (charIndex >= line.RenderLyricsOriginalChars.Count) return;
if (charIndex >= line.PrimaryRenderChars.Count) return;
RenderLyricsChar renderChar = line.RenderLyricsOriginalChars[charIndex];
RenderLyricsChar renderChar = line.PrimaryRenderChars[charIndex];
var rect = renderChar.LayoutRect;
var sourceCharRect = new Rect(
rect.X + line.OriginalPosition.X,
rect.Y + line.OriginalPosition.Y,
rect.X + line.PrimaryPosition.X,
rect.Y + line.PrimaryPosition.Y,
rect.Width,
rect.Height
);

View File

@@ -17,32 +17,32 @@ namespace BetterLyrics.WinUI3.Renderer
{
var blurAmount = (float)line.BlurAmountTransition.Value;
if (line.PhoneticCanvasTextLayout != null)
if (line.TertiaryTextLayout != null)
{
var opacity = line.PhoneticOpacityTransition.Value;
DrawPart(ds, textOnlyLayer,
line.PhoneticCanvasTextLayout,
line.PhoneticPosition,
line.TertiaryTextLayout,
line.TertiaryPosition,
blurAmount,
(float)opacity);
}
if (line.OriginalCanvasTextLayout != null)
if (line.PrimaryTextLayout != null)
{
double opacity = Math.Max(line.PlayedOriginalOpacityTransition.Value, line.UnplayedOriginalOpacityTransition.Value);
DrawPart(ds, textOnlyLayer,
line.OriginalCanvasTextLayout,
line.OriginalPosition,
line.PrimaryTextLayout,
line.PrimaryPosition,
blurAmount,
(float)opacity);
}
if (line.TranslatedCanvasTextLayout != null)
if (line.SecondaryTextLayout != null)
{
var opacity = line.TranslatedOpacityTransition.Value;
DrawPart(ds, textOnlyLayer,
line.TranslatedCanvasTextLayout,
line.TranslatedPosition,
line.SecondaryTextLayout,
line.SecondaryPosition,
blurAmount,
(float)opacity);
}

View File

@@ -28,7 +28,7 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService
if (CurrentSongInfo != SongInfoExtensions.Placeholder)
{
CurrentLyricsSearchResult = await Task.Run(async () => await _lyrcsSearchService.SearchSmartlyAsync(
CurrentSongInfo, true, CurrentMediaSourceProviderInfo?.LyricsSearchType, token), token);
CurrentSongInfo, CurrentMediaSourceProviderInfo?.LyricsSearchType, token), token);
if (CurrentLyricsSearchResult != null)
{

View File

@@ -32,7 +32,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService
}
/// <summary>
/// Write cache to DB
/// Write or update cache to DB
/// </summary>
public async Task SaveLyricsAsync(SongInfo songInfo, LyricsCacheItem result)
{
@@ -52,8 +52,18 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService
}
else
{
// No need to handle this case
return;
existingItem.Title = result.Title;
existingItem.Artist = result.Artist;
existingItem.Album = result.Album;
existingItem.TransliterationProvider = result.TransliterationProvider;
existingItem.TranslationProvider = result.TranslationProvider;
existingItem.Raw = result.Raw;
existingItem.Translation = result.Translation;
existingItem.MatchPercentage = result.MatchPercentage;
existingItem.Reference = result.Reference;
}
await context.SaveChangesAsync();

View File

@@ -10,7 +10,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
public interface ILyricsSearchService
{
Task<LyricsCacheItem?> SearchSmartlyAsync(SongInfo songInfo, bool checkCache, LyricsSearchType? lyricsSearchType, CancellationToken token);
Task<LyricsCacheItem?> SearchSmartlyAsync(SongInfo songInfo, LyricsSearchType? lyricsSearchType, CancellationToken token);
Task<List<LyricsCacheItem>> SearchAllAsync(SongInfo songInfo, bool checkCache, CancellationToken token);
}

View File

@@ -104,7 +104,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
public async Task<LyricsCacheItem?> SearchSmartlyAsync(SongInfo songInfo, bool checkCache, LyricsSearchType? lyricsSearchType, CancellationToken token)
public async Task<LyricsCacheItem?> SearchSmartlyAsync(SongInfo songInfo, LyricsSearchType? lyricsSearchType, CancellationToken token)
{
if (lyricsSearchType == null)
{
@@ -112,8 +112,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
var lyricsSearchResult = new LyricsCacheItem();
//lyricsSearchResult.Raw = File.ReadAllText("C:\\Users\\Zhe\\Desktop\\星河回响 (Tech Demo).lrc");
//return lyricsSearchResult;
string overridenTitle = songInfo.Title;
string overridenArtist = songInfo.Artist;
@@ -150,7 +148,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
.WithTitle(overridenTitle)
.WithArtist(overridenArtist)
.WithAlbum(overridenAlbum),
targetProvider.Value, checkCache, token);
targetProvider.Value, true, token);
}
}
@@ -172,7 +170,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
.WithTitle(overridenTitle)
.WithArtist(overridenArtist)
.WithAlbum(overridenAlbum),
provider.Provider, checkCache, token);
provider.Provider, !provider.IgnoreCacheWhenSearching, token);
int matchingThreshold = mediaSourceProviderInfo.MatchingThreshold;
if (provider.IsMatchingThresholdOverwritten)
@@ -288,7 +286,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private async Task<LyricsCacheItem> SearchFile(SongInfo songInfo, LyricsFormat format)
{
int maxScore = 0;
int maxScore = -1;
FilesIndexItem? bestFileEntity = null;
MediaFolder? bestFolderConfig = null;
@@ -316,7 +314,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
if (item.FileName.EndsWith(targetExt, StringComparison.OrdinalIgnoreCase))
{
int score = MetadataComparer.CalculateScore(songInfo, new LyricsCacheItem { Reference = item.FileName });
int score = MetadataComparer.CalculateScore(songInfo, item);
if (score > maxScore)
{
@@ -363,13 +361,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
if (string.IsNullOrEmpty(item.EmbeddedLyrics)) continue;
int score = MetadataComparer.CalculateScore(songInfo, new LyricsCacheItem
{
Title = item.Title,
Artist = item.Artist,
Album = item.Album,
Duration = item.Duration
});
int score = MetadataComparer.CalculateScore(songInfo, item);
if (score > maxScore)
{
@@ -444,6 +436,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
Title = title,
Artist = artist,
Album = album,
Duration = 0,
});
if (score > lyricsSearchResult.MatchPercentage)
{

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -243,6 +243,9 @@
<data name="LibreTranslateServerTextBox.PlaceholderText" xml:space="preserve">
<value>e.g. http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>Duration</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>Loading lyrics...</value>
</data>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>

View File

@@ -59,7 +59,7 @@
: 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: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>
@@ -243,6 +243,9 @@
<data name="LibreTranslateServerTextBox.PlaceholderText" xml:space="preserve">
<value>例如 http://localhost:5000</value>
</data>
<data name="LyricsEffectSettingsControlAnimationDuration.Header" xml:space="preserve">
<value>持续时间</value>
</data>
<data name="LyricsLoading" xml:space="preserve">
<value>加载歌词中...</value>
</data>

View File

@@ -59,7 +59,7 @@
: 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: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>