Files
BetterLyrics/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Parsers/LyricsParser/LyricsParser.cs

358 lines
14 KiB
C#

// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Lyrics;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.TranslationService;
using BetterLyrics.WinUI3.Services.TransliterationService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using Lyricify.Lyrics.Parsers;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
private static readonly ILogger<LyricsParser> _logger = Ioc.Default.GetRequiredService<ILogger<LyricsParser>>();
private List<LyricsData> _lyricsDataArr = [];
public LyricsParser()
{
}
public List<LyricsData> Parse(LyricsCacheItem? lyricsSearchResult)
{
_logger.LogInformation("LyricsParser.Parse");
_lyricsDataArr = [];
if (string.IsNullOrWhiteSpace(lyricsSearchResult?.Raw))
{
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
else
{
switch (lyricsSearchResult.Raw.DetectFormat())
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(lyricsSearchResult.Raw, lyricsSearchResult.Provider.IsRemote());
break;
case LyricsFormat.Qrc:
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Krc:
ParseQrcKrc(KrcParser.Parse(lyricsSearchResult.Raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(lyricsSearchResult.Raw);
break;
default:
break;
}
if (_lyricsDataArr.Count == 0)
{
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
}
LoadTranslation(lyricsSearchResult);
LoadTransliteration(lyricsSearchResult);
GenerateTransliterationLyricsData();
EnsureEndMs(lyricsSearchResult?.Duration);
EnsureSyllables();
return _lyricsDataArr;
}
public async Task<(LyricsData, TransliterationSearchProvider?, TranslationSearchProvider?)> Parse(
ITranslationService translationService,
ITransliterationService transliterationService,
TranslationSettings settings,
LyricsCacheItem? lyricsSearchResult,
CancellationToken token
)
{
TransliterationSearchProvider? transliterationSearchProvider = null;
TranslationSearchProvider? translationSearchProvider = null;
Parse(lyricsSearchResult);
var main = _lyricsDataArr.First();
// 应用音译
LyricsData? phoneticLyricsData = null;
// 已解析歌词内寻找
if (settings.IsChineseRomanizationEnabled && main.LanguageCode == LanguageHelper.ChineseCode)
{
phoneticLyricsData = settings.ChineseRomanization switch
{
ChineseRomanization.Pinyin => _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.PinyinCode),
ChineseRomanization.Jyutping => _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.JyutpingCode),
_ => null,
};
if (phoneticLyricsData != null)
{
main.SetPhoneticText(phoneticLyricsData);
if (phoneticLyricsData.AutoGenerated)
{
transliterationSearchProvider = TransliterationSearchProvider.BetterLyrics;
}
else
{
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
}
}
}
else if (settings.IsJapaneseRomanizationEnabled && main.LanguageCode == LanguageHelper.JapaneseCode)
{
phoneticLyricsData = _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.RomanCode);
if (phoneticLyricsData != null)
{
main.SetPhoneticText(phoneticLyricsData);
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
}
else
{
string romaji = string.Empty;
try
{
romaji = await transliterationService.TransliterateText(main.WrappedOriginalText, PhoneticHelper.RomanCode, token);
_lyricsDataArr.FirstOrDefault()?.SetTransliteration(romaji);
transliterationSearchProvider = TransliterationSearchProvider.CutletDocker;
}
catch (TaskCanceledException) { }
catch (Exception)
{
ToastHelper.ShowToast("CutletDockerFailed", null, InfoBarSeverity.Error);
}
}
}
// 应用翻译
if (settings.IsTranslationEnabled && main.LanguageCode != settings.SelectedTargetLanguageCode)
{
var found = _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == settings.SelectedTargetLanguageCode);
if (found != null)
{
main.SetTranslatedText(found);
translationSearchProvider = lyricsSearchResult?.Provider.ToTranslationSearchProvider();
}
else if (settings.IsLibreTranslateEnabled)
{
string translated = string.Empty;
try
{
translated = await translationService.TranslateTextAsync(main.WrappedOriginalText, settings.SelectedTargetLanguageCode, token);
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated);
translationSearchProvider = TranslationSearchProvider.LibreTranslate;
}
catch (TaskCanceledException) { }
catch (Exception)
{
ToastHelper.ShowToast("LibreTranslateFailed", null, InfoBarSeverity.Error);
}
}
}
// 应用简体中文/繁体中文
if (main.LanguageCode == LanguageHelper.ChineseCode)
{
foreach (var item in main.LyricsLines)
{
item.PrimaryText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.PrimaryText) : ChineseHelper.ToSC(item.PrimaryText);
}
}
if (settings.SelectedTargetLanguageCode == LanguageHelper.ChineseCode)
{
foreach (var item in main.LyricsLines)
{
item.SecondaryText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.SecondaryText) : ChineseHelper.ToSC(item.SecondaryText);
}
}
return (main, transliterationSearchProvider, translationSearchProvider);
}
private void LoadTranslation(LyricsCacheItem? lyricsSearchResult)
{
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Translation))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.QQ:
case LyricsSearchProvider.Kugou:
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Translation, true);
break;
default:
break;
}
}
}
private void LoadTransliteration(LyricsCacheItem? lyricsSearchResult)
{
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Transliteration))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Transliteration, true);
_lyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
break;
default:
break;
}
}
}
/// <summary>
/// 在音译不存在的情况下生成音译歌词
/// </summary>
private void GenerateTransliterationLyricsData()
{
var main = _lyricsDataArr.FirstOrDefault();
if (main != null)
{
string? languageCode = main.LanguageCode;
if (languageCode == LanguageHelper.ChineseCode)
{
if (!_lyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.PinyinCode))
{
_lyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.PinyinCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
PrimaryText = PhoneticHelper.ToPinyin(line.PrimaryText),
PrimarySyllables = line.PrimarySyllables.Select(c => new BaseLyrics
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToPinyin(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
if (!_lyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.JyutpingCode))
{
_lyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.JyutpingCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
PrimaryText = PhoneticHelper.ToJyutping(line.PrimaryText),
PrimarySyllables = line.PrimarySyllables.Select(c => new BaseLyrics
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToJyutping(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
}
}
private void EnsureEndMs(double? duration)
{
foreach (var lyricsData in _lyricsDataArr)
{
var lines = lyricsData.LyricsLines;
// 计算结束时间
for (int i = 0; i < lines.Count; i++)
{
// 计算行结束时间
if (lines[i].EndMs == null)
{
if (i + 1 < lines.Count)
{
lines[i].EndMs = lines[i + 1].StartMs;
}
else
{
lines[i].EndMs = (int)(duration ?? 0) * 1000;
}
}
// 计算音节结束时间
for (int j = 0; j < lines[i].PrimarySyllables.Count; j++)
{
var syllable = lines[i].PrimarySyllables[j];
if (syllable.EndMs == null)
{
if (j < lines[i].PrimarySyllables.Count - 1)
{
syllable.EndMs = lines[i].PrimarySyllables[j + 1].StartMs;
}
else
{
syllable.EndMs = lines[i].EndMs;
}
}
}
}
}
}
/// <summary>
/// Invoke this after <see cref="EnsureEndMs"/>
/// </summary>
private void EnsureSyllables()
{
foreach (var lyricsData in _lyricsDataArr)
{
if (lyricsData == null) continue;
var lines = lyricsData.LyricsLines;
if (lines == null) continue;
foreach (var line in lines)
{
if (line == null) continue;
if (line.IsPrimaryHasRealSyllableInfo) continue;
if (line.PrimarySyllables.Count > 0) continue;
var content = line.PrimaryText;
var length = content.Length;
if (length == 0) continue;
var avgSyllableDuration = line.DurationMs / length;
if (avgSyllableDuration == 0) continue;
for (int j = 0; j < length; j++)
{
line.PrimarySyllables.Add(new BaseLyrics
{
Text = content[j].ToString(),
StartIndex = j,
StartMs = line.StartMs + avgSyllableDuration * j,
EndMs = line.StartMs + avgSyllableDuration * (j + 1),
});
}
}
}
}
}
}