mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
358 lines
14 KiB
C#
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),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|