From 7ddfd1118b60152bc83a6f23789d3a41fcc494c4 Mon Sep 17 00:00:00 2001 From: Zhe Fang Date: Mon, 5 Jan 2026 19:32:30 -0500 Subject: [PATCH] feat: support bg lyrics --- .../Controls/LyricsCanvas.xaml.cs | 40 ++-- .../Logic/LyricsAnimator.cs | 41 ++-- .../Logic/LyricsLayoutManager.cs | 31 +++ .../Logic/LyricsSynchronizer.cs | 49 +++-- .../Models/RenderLyricsLine.cs | 10 + .../Parsers/LyricsParser/LyricsParser.Lrc.cs | 8 +- .../Parsers/LyricsParser/LyricsParser.Ttml.cs | 191 ++++++++++-------- .../Renderer/LyricsRenderer.cs | 10 +- .../Renderer/UnplayingLineRenderer.cs | 11 +- .../LyricsSearchService.cs | 2 + 10 files changed, 255 insertions(+), 138 deletions(-) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/LyricsCanvas.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/LyricsCanvas.xaml.cs index 32aad36..6782661 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/LyricsCanvas.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/LyricsCanvas.xaml.cs @@ -25,6 +25,7 @@ using System.Threading.Tasks; using Windows.Foundation; using Windows.Storage.Streams; using Windows.UI; +using System.Numerics; namespace BetterLyrics.WinUI3.Controls { @@ -398,6 +399,7 @@ namespace BetterLyrics.WinUI3.Controls strokeColor: _albumArtThemeColors.StrokeFontColor, bgColor: _albumArtThemeColors.BgFontColor, fgColor: _albumArtThemeColors.FgFontColor, + currentProgressMs: _songPositionWithOffset.TotalMilliseconds, getPlaybackState: (lineIndex) => { if (_renderLyricsLines == null) return new LinePlaybackState(); @@ -433,19 +435,19 @@ namespace BetterLyrics.WinUI3.Controls ); } -#if DEBUG - //args.DrawingSession.DrawText( - // $"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" + - // $"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" + - // $"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" + - // $"Playing line (idx): {_playingLineIndex}\n" + - // $"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" + - // $"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" + - // $"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" + - // $"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" + - // $"Y offset: {_canvasYScrollTransition.Value}\n" + - // $"User scroll offset: {_mouseYScrollTransition.Value}", - // new Vector2(0, 0), Colors.Red); +#if DEBUG && false + args.DrawingSession.DrawText( + $"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" + + $"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" + + $"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" + + $"Playing line (idx): {_playingLineIndex}\n" + + $"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" + + $"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" + + $"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" + + $"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_gsmtcService.CurrentSongInfo.DurationMs)}\n" + + $"Y offset: {_canvasYScrollTransition.Value}\n" + + $"User scroll offset: {_mouseYScrollTransition.Value}", + new Vector2(0, 0), Colors.Red); #endif } @@ -475,7 +477,7 @@ namespace BetterLyrics.WinUI3.Controls #region UpdatePlayingLineIndex - int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, lyricsData); + int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, _renderLyricsLines); bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex; _playingLineIndex = newPlayingIndex; @@ -536,7 +538,8 @@ namespace BetterLyrics.WinUI3.Controls _isMouseScrolling, _isLayoutChanged, isPlayingLineChanged, - _isMouseScrollingChanged + _isMouseScrollingChanged, + _songPositionWithOffset.TotalMilliseconds ); _isMouseScrollingChanged = false; @@ -667,7 +670,7 @@ namespace BetterLyrics.WinUI3.Controls private void UpdateRenderLyricsLines() { _renderLyricsLines = null; - _renderLyricsLines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine() + var lines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine() { LyricsSyllables = x.LyricsSyllables, StartMs = x.StartMs, @@ -676,6 +679,11 @@ namespace BetterLyrics.WinUI3.Controls OriginalText = x.OriginalText, TranslatedText = x.TranslatedText }).ToList(); + if (lines != null) + { + LyricsLayoutManager.CalculateLanes(lines); + } + _renderLyricsLines = lines; } private async Task ReloadCoverBackgroundResourcesAsync() diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs index 673e65d..9f1217e 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs @@ -17,7 +17,7 @@ namespace BetterLyrics.WinUI3.Logic IList? lines, int startIndex, int endIndex, - int playingLineIndex, + int primaryPlayingLineIndex, double canvasHeight, double targetYScrollOffset, double playingLineTopOffsetFactor, @@ -29,13 +29,14 @@ namespace BetterLyrics.WinUI3.Logic TimeSpan elapsedTime, bool isMouseScrolling, bool isLayoutChanged, - bool isPlayingLineChanged, - bool isMouseScrollingChanged + bool isPrimaryPlayingLineChanged, + bool isMouseScrollingChanged, + double currentProgressMs ) { if (lines == null) return; - var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex); + var currentPlayingLine = lines.ElementAtOrDefault(primaryPlayingLineIndex); if (currentPlayingLine == null) return; var phoneticOpacity = lyricsStyle.PhoneticLyricsOpacity / 100.0; @@ -47,13 +48,17 @@ namespace BetterLyrics.WinUI3.Logic var line = lines.ElementAtOrDefault(i); if (line == null) continue; - if (isLayoutChanged || isPlayingLineChanged || isMouseScrollingChanged) + bool isSecondaryLinePlaying = currentProgressMs >= line.StartMs && currentProgressMs <= line.EndMs; + if (i == primaryPlayingLineIndex) isSecondaryLinePlaying = true; + bool isSecondaryLinePlayingChanged = line.IsPlayingLastFrame != isSecondaryLinePlaying; + line.IsPlayingLastFrame = isSecondaryLinePlaying; + + if (isLayoutChanged || isPrimaryPlayingLineChanged || isMouseScrollingChanged || isSecondaryLinePlayingChanged) { - int lineCountDelta = i - playingLineIndex; - int absLineCountDelta = Math.Abs(lineCountDelta); + int lineCountDelta = i - primaryPlayingLineIndex; double distanceFromPlayingLine = Math.Abs(line.OriginalPosition.Y - currentPlayingLine.OriginalPosition.Y); - double distanceFactor = 0; + double distanceFactor; if (lineCountDelta < 0) { distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight * playingLineTopOffsetFactor), 0, 1); @@ -88,45 +93,53 @@ namespace BetterLyrics.WinUI3.Logic line.BlurAmountTransition.SetDuration(yScrollDuration); line.BlurAmountTransition.SetDelay(yScrollDelay); - line.BlurAmountTransition.StartTransition(isMouseScrolling ? 0 : (lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0)); + line.BlurAmountTransition.StartTransition( + (isMouseScrolling || isSecondaryLinePlaying) ? 0 : + (lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0)); line.ScaleTransition.SetDuration(yScrollDuration); line.ScaleTransition.SetDelay(yScrollDelay); line.ScaleTransition.StartTransition( - lyricsEffect.IsLyricsOutOfSightEffectEnabled ? + isSecondaryLinePlaying ? _highlightedScale : + (lyricsEffect.IsLyricsOutOfSightEffectEnabled ? (_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)) : - _highlightedScale); + _highlightedScale)); line.PhoneticOpacityTransition.SetDuration(yScrollDuration); line.PhoneticOpacityTransition.SetDelay(yScrollDelay); line.PhoneticOpacityTransition.StartTransition( + isSecondaryLinePlaying ? phoneticOpacity : CalculateTargetOpacity(phoneticOpacity, phoneticOpacity, distanceFactor, isMouseScrolling, lyricsEffect)); + // 原文不透明度(已播放) line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration); line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay); line.PlayedOriginalOpacityTransition.StartTransition( + isSecondaryLinePlaying ? 1.0 : CalculateTargetOpacity(originalOpacity, 1.0, distanceFactor, isMouseScrolling, lyricsEffect)); - + // 原文不透明度(未播放) line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration); line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay); line.UnplayedOriginalOpacityTransition.StartTransition( + isSecondaryLinePlaying ? originalOpacity : CalculateTargetOpacity(originalOpacity, originalOpacity, distanceFactor, isMouseScrolling, lyricsEffect)); line.TranslatedOpacityTransition.SetDuration(yScrollDuration); line.TranslatedOpacityTransition.SetDelay(yScrollDelay); line.TranslatedOpacityTransition.StartTransition( + isSecondaryLinePlaying ? translatedOpacity : CalculateTargetOpacity(translatedOpacity, translatedOpacity, distanceFactor, isMouseScrolling, lyricsEffect)); line.ColorTransition.SetDuration(yScrollDuration); line.ColorTransition.SetDelay(yScrollDelay); - line.ColorTransition.StartTransition(absLineCountDelta == 0 ? fgColor : bgColor); + line.ColorTransition.StartTransition(isSecondaryLinePlaying ? fgColor : bgColor); line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType); line.AngleTransition.SetDuration(yScrollDuration); line.AngleTransition.SetDelay(yScrollDelay); line.AngleTransition.StartTransition( (lyricsEffect.IsFanLyricsEnabled && !isMouseScrolling) ? - Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > playingLineIndex ? 1 : -1) : + Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > primaryPlayingLineIndex ? 1 : -1) : 0); line.YOffsetTransition.SetEasingType(canvasYScrollTransition.EasingType); diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs index b499cde..f43d8b5 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs @@ -187,6 +187,37 @@ namespace BetterLyrics.WinUI3.Logic return lines.Last().BottomRightPosition.Y; } + public static void CalculateLanes(IList? lines, int toleranceMs = 50) + { + if (lines == null) return; + var lanesEndMs = new List { 0 }; + + foreach (var line in lines) + { + var start = line.StartMs; + var end = line.EndMs; + + int assignedLane = -1; + for (int i = 0; i < lanesEndMs.Count; i++) + { + if (lanesEndMs[i] <= start + toleranceMs) + { + assignedLane = i; + break; + } + } + + if (assignedLane == -1) + { + assignedLane = lanesEndMs.Count; + lanesEndMs.Add(0); + } + + lanesEndMs[assignedLane] = end ?? 0; + line.LaneIndex = assignedLane; + } + } + public static int FindMouseHoverLineIndex( IList? lines, bool isMouseInLyricsArea, diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs index ee45ea4..773a94c 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsSynchronizer.cs @@ -13,30 +13,53 @@ namespace BetterLyrics.WinUI3.Logic _lastFoundIndex = 0; } - public int GetCurrentLineIndex(double currentTimeMs, LyricsData? lyricsData) + public int GetCurrentLineIndex(double currentTimeMs, IList? lines) { - if (lyricsData == null || lyricsData.LyricsLines.Count == 0) return 0; - var lines = lyricsData.LyricsLines; + if (lines == null || lines.Count == 0) return 0; - // Cache hit - if (IsTimeInLine(currentTimeMs, lines, _lastFoundIndex)) return _lastFoundIndex; - if (_lastFoundIndex + 1 < lines.Count && IsTimeInLine(currentTimeMs, lines, _lastFoundIndex + 1)) + if (_lastFoundIndex >= 0 && _lastFoundIndex < lines.Count) { - _lastFoundIndex++; - return _lastFoundIndex; + var lastLine = lines[_lastFoundIndex]; + if (lastLine.LaneIndex == 0 && IsTimeInLine(currentTimeMs, lines, _lastFoundIndex)) + { + return _lastFoundIndex; + } } - // Cache miss + int bestCandidateIndex = -1; + int bestCandidateLane = int.MaxValue; + for (int i = 0; i < lines.Count; i++) { if (IsTimeInLine(currentTimeMs, lines, i)) { - _lastFoundIndex = i; - return i; + var currentLine = lines[i]; + int currentLane = currentLine.LaneIndex; + + if (currentLane == 0) + { + _lastFoundIndex = i; + return i; + } + + if (currentLane < bestCandidateLane) + { + bestCandidateIndex = i; + bestCandidateLane = currentLane; + } + } + else if (lines[i].StartMs > currentTimeMs + 1000) + { + break; } } - // Default + if (bestCandidateIndex != -1) + { + _lastFoundIndex = bestCandidateIndex; + return bestCandidateIndex; + } + return Math.Min(_lastFoundIndex, lines.Count - 1); } @@ -140,7 +163,7 @@ namespace BetterLyrics.WinUI3.Logic return state; } - private bool IsTimeInLine(double time, IList lines, int index) + private bool IsTimeInLine(double time, IList lines, int index) { if (index < 0 || index >= lines.Count) return false; var line = lines[index]; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs index efcb0c1..430179a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs @@ -57,6 +57,16 @@ namespace BetterLyrics.WinUI3.Models public CanvasGeometry? TranslatedCanvasGeometry { get; private set; } public CanvasGeometry? PhoneticCanvasGeometry { get; private set; } + /// + /// 轨道索引 (0 = 主轨道, 1 = 第一副轨道, etc.) + /// 用于布局计算时的堆叠逻辑 + /// + public int LaneIndex { get; set; } = 0; + /// + /// 是否为背景人声/和声 + /// + public bool IsPlayingLastFrame { get; set; } = false; + public RenderLyricsLine() { AngleTransition = new( diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs index 0e2ba19..2c03821 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Lrc.cs @@ -56,17 +56,19 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser var bracketMatches = bracketRegex.Matches(line); string content = line; - int? lineStartTime = null; + int lineStartMs; if (bracketMatches.Count > 0) { var match = bracketMatches[0]; int min = int.Parse(match.Groups[1].Value); int sec = int.Parse(match.Groups[2].Value); int ms = int.Parse(match.Groups[4].Value.PadRight(3, '0')); - lineStartTime = min * 60_000 + sec * 1000 + ms; + lineStartMs = min * 60_000 + sec * 1000 + ms; + content = bracketRegex!.Replace(line, "").Trim(); if (content == "//") content = ""; - lrcLines.Add(new LyricsLine { StartMs = lineStartTime.Value, OriginalText = content }); + + lrcLines.Add(new LyricsLine { StartMs = lineStartMs, OriginalText = content }); } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs index 70e9c09..cfd94e5 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.Ttml.cs @@ -2,12 +2,15 @@ using BetterLyrics.WinUI3.Models; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Xml.Linq; namespace BetterLyrics.WinUI3.Parsers.LyricsParser { public partial class LyricsParser { + private readonly XNamespace _ttml = "http://www.w3.org/ns/ttml#metadata"; + private void ParseTtml(string raw) { try @@ -19,120 +22,146 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace); var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body"); if (body == null) return; + var ps = body.Descendants().Where(e => e.Name.LocalName == "p"); + foreach (var p in ps) { - // 句级时间 - string? pBegin = p.Attribute("begin")?.Value; - string? pEnd = p.Attribute("end")?.Value; - int pStartMs = ParseTtmlTime(pBegin); - int pEndMs = ParseTtmlTime(pEnd); + ParseTtmlSegment( + container: p, + originalDest: originalLines, + transDest: translationLines, + romanDest: romanLines, + isBackground: false + ); - // 只获取一级span - var spans = p.Elements() - .Where(s => s.Name.LocalName == "span") - .ToList(); + var bgSpans = p.Elements().Where(s => s.Attribute(_ttml + "role")?.Value == "x-bg"); - var originalTextSpans = spans - .Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == null) - .ToList(); - - // 处理原文span后的空白 - for (int i = 0; i < originalTextSpans.Count; i++) + foreach (var bgSpan in bgSpans) { - var span = originalTextSpans[i]; - var nextNode = span.NodesAfterSelf().FirstOrDefault(); - if (nextNode is XText textNode) - { - span.Value += textNode.Value; - } + // 把 span 当作一个容器,再调一次通用解析方法 + ParseTtmlSegment( + container: bgSpan, + originalDest: originalLines, + transDest: translationLines, + romanDest: romanLines, + isBackground: true + ); } - // 拼接空白字符后的原文 - string originalText = string.Concat(originalTextSpans.Select(s => s.Value)); - - var originalCharTimings = new List(); - int originalStartIndex = 0; - foreach (var span in originalTextSpans) - { - string? sBegin = span.Attribute("begin")?.Value; - string? sEnd = span.Attribute("end")?.Value; - int sStartMs = ParseTtmlTime(sBegin); - int sEndMs = ParseTtmlTime(sEnd); - originalCharTimings.Add(new LyricsSyllable - { - StartMs = sStartMs, - EndMs = sEndMs, - StartIndex = originalStartIndex, - Text = span.Value - }); - originalStartIndex += span.Value.Length; - } - if (originalTextSpans.Count == 0) - { - originalText = p.Value; - } - - originalLines.Add(new LyricsLine - { - StartMs = pStartMs, - EndMs = pEndMs, - OriginalText = originalText, - LyricsSyllables = originalCharTimings, - }); - - // 解析 x-role - ParseTtmlXRole(spans, translationLines, "x-translation", pStartMs, pEndMs); - ParseTtmlXRole(spans, romanLines, "x-roman", pStartMs, pEndMs); } _lyricsDataArr.Add(new LyricsData(originalLines)); + if (translationLines.Count > 0) { _lyricsDataArr.Add(new LyricsData(translationLines)); } + if (romanLines.Count > 0) { _lyricsDataArr.Add(new LyricsData(romanLines) { LanguageCode = PhoneticHelper.RomanCode }); } } - catch - { - // 解析失败,忽略 - } + catch { } } - private void ParseTtmlXRole(List sourceSpans, List saveLyricsLines, string xRole, int pStartMs, int? pEndMs) + private void ParseTtmlSegment( + XElement container, + List originalDest, + List transDest, + List romanDest, + bool isBackground) { - var textSpans = sourceSpans - .Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == xRole) + int containerStartMs = ParseTtmlTime(container.Attribute("begin")?.Value); + int containerEndMs = ParseTtmlTime(container.Attribute("end")?.Value); + + var contentSpans = container.Elements() + .Where(s => s.Name.LocalName == "span") + .Where(s => + { + var role = s.Attribute(_ttml + "role")?.Value; + return role == null; + }) .ToList(); - string text = string.Concat(textSpans.Select(s => s.Value)); - var charTimings = new List(); - int startIndex = 0; - foreach (var span in textSpans) + for (int i = 0; i < contentSpans.Count; i++) { - string? sBegin = span.Attribute("begin")?.Value; - string? sEnd = span.Attribute("end")?.Value; - int sStartMs = ParseTtmlTime(sBegin); - int sEndMs = ParseTtmlTime(sEnd); - charTimings.Add(new LyricsSyllable + var span = contentSpans[i]; + var nextNode = span.NodesAfterSelf().FirstOrDefault(); + if (nextNode is XText textNode) + { + span.Value += textNode.Value; + } + } + + var syllables = new List(); + int startIndex = 0; + var sbText = new System.Text.StringBuilder(); + + foreach (var span in contentSpans) + { + int sStartMs = ParseTtmlTime(span.Attribute("begin")?.Value); + int sEndMs = ParseTtmlTime(span.Attribute("end")?.Value); + string text = span.Value; + + syllables.Add(new LyricsSyllable { StartMs = sStartMs, EndMs = sEndMs, StartIndex = startIndex, - Text = span.Value + Text = text }); - startIndex += span.Value.Length; + + sbText.Append(text); + startIndex += text.Length; } - if (textSpans.Count > 0) + + string fullOriginalText = sbText.ToString(); + + if (contentSpans.Count == 0) { - saveLyricsLines.Add(new LyricsLine + fullOriginalText = container.Value; + } + + originalDest.Add(new LyricsLine + { + StartMs = containerStartMs, + EndMs = containerEndMs, + OriginalText = fullOriginalText, + LyricsSyllables = syllables + }); + + var transSpan = container.Elements() + .FirstOrDefault(s => s.Attribute(_ttml + "role")?.Value == "x-translation"); + + AddAuxiliaryLine(transDest, transSpan, containerStartMs, containerEndMs); + + var romanSpan = container.Elements() + .FirstOrDefault(s => s.Attribute(_ttml + "role")?.Value == "x-roman"); + + AddAuxiliaryLine(romanDest, romanSpan, containerStartMs, containerEndMs); + } + + private void AddAuxiliaryLine(List destList, XElement? span, int startMs, int endMs) + { + if (span != null) + { + string text = span.Value; + + destList.Add(new LyricsLine { - StartMs = pStartMs, - EndMs = pEndMs, - OriginalText = text, - LyricsSyllables = charTimings, + StartMs = startMs, + EndMs = endMs, + OriginalText = text + }); + } + else + { + destList.Add(new LyricsLine + { + StartMs = startMs, + EndMs = endMs, + OriginalText = "" }); } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.cs index d6cd827..4ab329a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.cs @@ -41,6 +41,7 @@ namespace BetterLyrics.WinUI3.Renderer Color strokeColor, Color bgColor, Color fgColor, + double currentProgressMs, Func getPlaybackState) { using (var opacityLayer = ds.CreateLayer((float)lyricsOpacity)) @@ -70,6 +71,7 @@ namespace BetterLyrics.WinUI3.Renderer strokeColor, bgColor, fgColor, + currentProgressMs, getPlaybackState); } @@ -101,6 +103,7 @@ namespace BetterLyrics.WinUI3.Renderer strokeColor, bgColor, fgColor, + currentProgressMs, getPlaybackState); } } @@ -125,6 +128,7 @@ namespace BetterLyrics.WinUI3.Renderer Color strokeColor, Color bgColor, Color fgColor, + double currentProgressMs, Func getPlaybackState) { if (lines == null) return; @@ -162,10 +166,12 @@ namespace BetterLyrics.WinUI3.Renderer using (var textOnlyLayer = RenderBaseTextLayer(control, line, styleSettings.LyricsFontStrokeWidth, strokeColor, line.ColorTransition.Value)) { - if (i == playingLineIndex) + bool isPlaying = currentProgressMs >= line.StartMs && currentProgressMs <= line.EndMs; + if (i == playingLineIndex) isPlaying = true; + + if (isPlaying) { var state = getPlaybackState(i); - _playingRenderer.Draw(control, ds, textOnlyLayer, line, state, bgColor, fgColor, effectSettings); } else diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/UnplayingLineRenderer.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/UnplayingLineRenderer.cs index d768198..7b982c8 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/UnplayingLineRenderer.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/UnplayingLineRenderer.cs @@ -2,6 +2,7 @@ using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Effects; using Microsoft.Graphics.Canvas.Text; +using System; using System.Numerics; using Windows.Foundation; @@ -28,15 +29,7 @@ namespace BetterLyrics.WinUI3.Renderer if (line.OriginalCanvasTextLayout != null) { - double opacity; - if (line.PlayedOriginalOpacityTransition.StartValue > line.UnplayedOriginalOpacityTransition.StartValue) - { - opacity = line.PlayedOriginalOpacityTransition.Value; - } - else - { - opacity = line.UnplayedOriginalOpacityTransition.Value; - } + double opacity = Math.Max(line.PlayedOriginalOpacityTransition.Value, line.UnplayedOriginalOpacityTransition.Value); DrawPart(ds, textOnlyLayer, line.OriginalCanvasTextLayout, line.OriginalPosition, diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs index c203486..20f9d08 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs @@ -99,6 +99,8 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService } var lyricsSearchResult = new LyricsSearchResult(); + //lyricsSearchResult.Raw = File.ReadAllText("C:\\Users\\Zhe\\Desktop\\Debug_Complex_3min.ttml"); + //return lyricsSearchResult; string overridenTitle = songInfo.Title; string[] overridenArtists = songInfo.Artists;