mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
feat: support bg lyrics
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
IList<RenderLyricsLine>? 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);
|
||||
|
||||
@@ -187,6 +187,37 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
return lines.Last().BottomRightPosition.Y;
|
||||
}
|
||||
|
||||
public static void CalculateLanes(IList<RenderLyricsLine>? lines, int toleranceMs = 50)
|
||||
{
|
||||
if (lines == null) return;
|
||||
var lanesEndMs = new List<int> { 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<RenderLyricsLine>? lines,
|
||||
bool isMouseInLyricsArea,
|
||||
|
||||
@@ -13,30 +13,53 @@ namespace BetterLyrics.WinUI3.Logic
|
||||
_lastFoundIndex = 0;
|
||||
}
|
||||
|
||||
public int GetCurrentLineIndex(double currentTimeMs, LyricsData? lyricsData)
|
||||
public int GetCurrentLineIndex(double currentTimeMs, IList<RenderLyricsLine>? 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<LyricsLine> lines, int index)
|
||||
private bool IsTimeInLine(double time, IList<RenderLyricsLine> lines, int index)
|
||||
{
|
||||
if (index < 0 || index >= lines.Count) return false;
|
||||
var line = lines[index];
|
||||
|
||||
@@ -57,6 +57,16 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
|
||||
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 轨道索引 (0 = 主轨道, 1 = 第一副轨道, etc.)
|
||||
/// 用于布局计算时的堆叠逻辑
|
||||
/// </summary>
|
||||
public int LaneIndex { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 是否为背景人声/和声
|
||||
/// </summary>
|
||||
public bool IsPlayingLastFrame { get; set; } = false;
|
||||
|
||||
public RenderLyricsLine()
|
||||
{
|
||||
AngleTransition = new(
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LyricsSyllable>();
|
||||
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<XElement> sourceSpans, List<LyricsLine> saveLyricsLines, string xRole, int pStartMs, int? pEndMs)
|
||||
private void ParseTtmlSegment(
|
||||
XElement container,
|
||||
List<LyricsLine> originalDest,
|
||||
List<LyricsLine> transDest,
|
||||
List<LyricsLine> 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<LyricsSyllable>();
|
||||
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<LyricsSyllable>();
|
||||
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<LyricsLine> 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 = ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
Color strokeColor,
|
||||
Color bgColor,
|
||||
Color fgColor,
|
||||
double currentProgressMs,
|
||||
Func<int, LinePlaybackState> 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<int, LinePlaybackState> 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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user