From e43461d62471733131d88d9cc0cfb67b2b2794d5 Mon Sep 17 00:00:00 2001 From: Zhe Fang Date: Sat, 10 Jan 2026 11:29:22 -0500 Subject: [PATCH] feat: float and glow effect now can be adapted to auto word-by-word effect --- .../Extensions/LyricsDataExtensions.cs | 5 +- .../Logic/LyricsAnimator.cs | 174 +++++++++--------- .../Logic/LyricsSynchronizer.cs | 4 +- .../Models/Lyrics/LyricsLine.cs | 2 + .../Models/Lyrics/RenderLyricsLine.cs | 3 + .../Parsers/LyricsParser/LyricsParser.Lrc.cs | 19 +- .../LyricsParser/LyricsParser.QrcKrc.cs | 2 +- .../Parsers/LyricsParser/LyricsParser.Ttml.cs | 9 +- .../Parsers/LyricsParser/LyricsParser.cs | 40 ++++ 9 files changed, 155 insertions(+), 103 deletions(-) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Extensions/LyricsDataExtensions.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Extensions/LyricsDataExtensions.cs index 8fb4fc0..b66d14b 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Extensions/LyricsDataExtensions.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Extensions/LyricsDataExtensions.cs @@ -19,9 +19,10 @@ 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.FromMinutes(99).TotalMilliseconds }], + PrimarySyllables = [new BaseLyrics { Text = "● ● ●", StartMs = 0, EndMs = (int)TimeSpan.FromSeconds(30).TotalMilliseconds }], + IsPrimaryHasRealSyllableInfo = true, }, ], LanguageCode = "N/A", diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs index 3dcb793..7d0cfce 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs @@ -68,11 +68,17 @@ namespace BetterLyrics.WinUI3.Logic for (int i = safeStart; i <= safeEnd; i++) { 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; @@ -187,98 +193,100 @@ namespace BetterLyrics.WinUI3.Logic line.YOffsetTransition.Start(targetYScrollOffset); } - if (isSecondaryLinePlayingChanged) + if (isWordAnimationEnabled) { - // 辉光动画 - if (isGlowEnabled && lyricsEffect.LyricsGlowEffectScope == Enums.LyricsEffectScope.LineStartToCurrentChar - && isSecondaryLinePlaying) + if (isSecondaryLinePlayingChanged) { - foreach (var renderChar in line.PrimaryRenderChars) + // 辉光动画 + if (isGlowEnabled && lyricsEffect.LyricsGlowEffectScope == Enums.LyricsEffectScope.LineStartToCurrentChar + && 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(targetCharGlow, stepInOutDuration), - new Models.Keyframe(targetCharGlow, stepLastingDuration), - new Models.Keyframe(0, stepInOutDuration) - ); - } - } - - // 浮动动画 - if (isFloatEnabled) - { - foreach (var renderChar in line.PrimaryRenderChars) - { - renderChar.FloatTransition.Start(isSecondaryLinePlaying ? targetCharFloat : 0); - } - } - } - - // 字符动画 - 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); - } - - renderChar.IsPlayingLastFrame = isCharPlaying; - } - } - - // 音节动画 - foreach (var syllable in line.PrimaryRenderSyllables) - { - bool isSyllablePlaying = syllable.GetIsPlaying(currentPositionMs); - bool isSyllablePlayingChanged = syllable.IsPlayingLastFrame != isSyllablePlaying; - - if (isSyllablePlayingChanged) - { - if (isScaleEnabled && isSyllablePlaying) - { - foreach (var renderChar in syllable.ChildrenRenderLyricsChars) + foreach (var renderChar in line.PrimaryRenderChars) { - if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration) - { - var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0; - renderChar.ScaleTransition.Start( - new Models.Keyframe(targetCharScale, stepDuration), - new Models.Keyframe(1.0, stepDuration) - ); - } - } - } - - if (isGlowEnabled && isSyllablePlaying && lyricsEffect.LyricsGlowEffectScope == Enums.LyricsEffectScope.LongDurationSyllable - && syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration) - { - foreach (var renderChar in syllable.ChildrenRenderLyricsChars) - { - var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0; + 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(targetCharGlow, stepDuration), - new Models.Keyframe(0, stepDuration) + new Models.Keyframe(targetCharGlow, stepInOutDuration), + new Models.Keyframe(targetCharGlow, stepLastingDuration), + new Models.Keyframe(0, stepInOutDuration) ); } } - syllable.IsPlayingLastFrame = isSyllablePlaying; + // 浮动动画 + if (isFloatEnabled) + { + foreach (var renderChar in line.PrimaryRenderChars) + { + renderChar.FloatTransition.Start(isSecondaryLinePlaying ? targetCharFloat : 0); + } + } } - } - // 使动画步进一帧 - foreach (var renderChar in line.PrimaryRenderChars) - { - renderChar.Update(elapsedTime); + // 字符动画 + 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); + } + + renderChar.IsPlayingLastFrame = isCharPlaying; + } + } + + // 音节动画 + foreach (var syllable in line.PrimaryRenderSyllables) + { + bool isSyllablePlaying = syllable.GetIsPlaying(currentPositionMs); + bool isSyllablePlayingChanged = syllable.IsPlayingLastFrame != isSyllablePlaying; + + if (isSyllablePlayingChanged) + { + if (isScaleEnabled && isSyllablePlaying) + { + foreach (var renderChar in syllable.ChildrenRenderLyricsChars) + { + if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration) + { + var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0; + renderChar.ScaleTransition.Start( + new Models.Keyframe(targetCharScale, stepDuration), + new Models.Keyframe(1.0, stepDuration) + ); + } + } + } + + if (isGlowEnabled && isSyllablePlaying && lyricsEffect.LyricsGlowEffectScope == Enums.LyricsEffectScope.LongDurationSyllable + && syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration) + { + foreach (var renderChar in syllable.ChildrenRenderLyricsChars) + { + var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0; + renderChar.GlowTransition.Start( + new Models.Keyframe(targetCharGlow, stepDuration), + new Models.Keyframe(0, stepDuration) + ); + } + } + + syllable.IsPlayingLastFrame = isSyllablePlaying; + } + } + + foreach (var renderChar in line.PrimaryRenderChars) + { + renderChar.Update(elapsedTime); + } } line.Update(elapsedTime); diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs index 2c4cedb..e9c1579 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs @@ -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); } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/LyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/LyricsLine.cs index b54a993..81f0a52 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/LyricsLine.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/LyricsLine.cs @@ -22,6 +22,8 @@ namespace BetterLyrics.WinUI3.Models.Lyrics 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++) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/RenderLyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/RenderLyricsLine.cs index 7d0d137..ec858a4 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/RenderLyricsLine.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Lyrics/RenderLyricsLine.cs @@ -76,6 +76,8 @@ namespace BetterLyrics.WinUI3.Models.Lyrics public double? PrimaryLineHeight => PrimaryRenderChars.FirstOrDefault()?.LayoutRect.Height; + public bool IsPrimaryHasRealSyllableInfo { get; set; } + public RenderLyricsLine(LyricsLine lyricsLine) : base(lyricsLine) { AngleTransition = new( @@ -130,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) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs index 0af32e5..53a1d32 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs @@ -46,7 +46,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser { StartMs = syllables[0].StartMs, PrimaryText = string.Concat(syllables.Select(s => s.Text)), - PrimarySyllables = syllables + PrimarySyllables = syllables, + IsPrimaryHasRealSyllableInfo = true }); } else @@ -68,19 +69,13 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser content = bracketRegex!.Replace(line, "").Trim(); if (content == "//") content = ""; - lrcLines.Add(new LyricsLine + var lyricsLine = new LyricsLine { StartMs = lineStartMs, - PrimarySyllables = [ - new BaseLyrics - { - StartIndex = 0, - StartMs = lineStartMs, - Text = content - } - ], - PrimaryText = content - }); + PrimaryText = content, + IsPrimaryHasRealSyllableInfo = false + }; + lrcLines.Add(lyricsLine); } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.QrcKrc.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.QrcKrc.cs index 06a8050..b59046c 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.QrcKrc.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.QrcKrc.cs @@ -22,7 +22,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser { StartMs = lineRead.StartTime ?? 0, PrimaryText = lineRead.Text, - PrimarySyllables = [], + IsPrimaryHasRealSyllableInfo = true, }; var syllables = (lineRead as Lyricify.Lyrics.Models.SyllableLineInfo)?.Syllables; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs index b9ff6f5..f0c6b76 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs @@ -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, }); } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.cs index 5bc61af..42e379c 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.cs @@ -70,6 +70,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser GenerateTransliterationLyricsData(); EnsureEndMs(lyricsSearchResult?.Duration); + EnsureSyllables(); return _lyricsDataArr; } @@ -313,5 +314,44 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser } } + /// + /// Invoke this after + /// + 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), + }); + } + } + } + } + } }