mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
@@ -43,6 +43,7 @@
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="iTunesSearch" Version="1.0.44" />
|
||||
<PackageReference Include="LanguageDetection.NETStandard" Version="1.3.1" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return null;
|
||||
|
||||
// TTML
|
||||
if (content.StartsWith("<?xml") && System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b"))
|
||||
// TTML: 检查 <tt ... xmlns="http://www.w3.org/ns/ttml"
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"<tt\b[^>]*\bxmlns\s*=\s*[""']http://www\.w3\.org/ns/ttml[""']",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
return LyricsFormat.Ttml;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using LanguageDetection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LanguageDetectionHelper
|
||||
{
|
||||
private static readonly LanguageDetector _detector = new();
|
||||
|
||||
static LanguageDetectionHelper()
|
||||
{
|
||||
_detector.AddAllLanguages();
|
||||
}
|
||||
|
||||
private static string? ThreeLetterToTwoLetter(string threeLetterCode)
|
||||
{
|
||||
foreach (var ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
|
||||
{
|
||||
if (string.Equals(ci.ThreeLetterISOLanguageName, threeLetterCode, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ci.TwoLetterISOLanguageName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? DetectLanguageCode(string? text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
|
||||
return ThreeLetterToTwoLetter(_detector.Detect(text));
|
||||
}
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
return DetectLanguageCode(text) switch
|
||||
{
|
||||
"zh" or "ja" or "ko" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -167,7 +168,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
try
|
||||
{
|
||||
List<LyricsLine> singleLangLyricsLine = [];
|
||||
List<LyricsLine> originalLines = [];
|
||||
List<LyricsLine> translationLines = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null) return;
|
||||
@@ -176,53 +178,90 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
// 句级时间
|
||||
string? pBegin = p.Attribute("begin")?.Value;
|
||||
string? pEnd = p.Attribute("end")?.Value;
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 处理分词分时
|
||||
var spans = p.Elements().Where(s => s.Name.LocalName == "span").ToList();
|
||||
// 只获取一级span,且排除ttm:role="x-bg"的span
|
||||
var spans = p.Elements()
|
||||
.Where(s => s.Name.LocalName == "span" &&
|
||||
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg")
|
||||
.ToList();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s.Value));
|
||||
var charTimings = new List<CharTiming>();
|
||||
// 原文和翻译分离
|
||||
var originalTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-translation")
|
||||
.ToList();
|
||||
var translationTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
|
||||
.ToList();
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
// 原文(非 CJK 语言添加空格)
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
if (!LanguageDetectionHelper.IsCJK(originalText))
|
||||
{
|
||||
var span = spans[i];
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
|
||||
if (sStartMs == 0 && sEndMs == 0)
|
||||
continue;
|
||||
|
||||
if (sEndMs == 0)
|
||||
sEndMs =
|
||||
(i + 1 < spans.Count)
|
||||
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
|
||||
: pEndMs;
|
||||
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = 0, StartIndex = startIndex, Text = span.Value });
|
||||
startIndex += span.Value.Length;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
span.Value += " ";
|
||||
}
|
||||
originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
}
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value;
|
||||
var originalCharTimings = new List<CharTiming>();
|
||||
int originalStartIndex = 0;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
originalCharTimings.Add(new CharTiming
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
StartIndex = originalStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
originalStartIndex += span.Value.Length;
|
||||
}
|
||||
if (originalTextSpans.Count == 0)
|
||||
originalText = p.Value;
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
originalLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
OriginalText = originalText,
|
||||
CharTimings = originalCharTimings,
|
||||
});
|
||||
|
||||
// 翻译
|
||||
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
|
||||
var translationCharTimings = new List<CharTiming>();
|
||||
int translationStartIndex = 0;
|
||||
foreach (var span in translationTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
translationCharTimings.Add(new CharTiming
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
StartIndex = translationStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
translationStartIndex += span.Value.Length;
|
||||
}
|
||||
if (translationTextSpans.Count > 0)
|
||||
{
|
||||
translationLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
OriginalText = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
OriginalText = translationText,
|
||||
CharTimings = translationCharTimings,
|
||||
});
|
||||
}
|
||||
}
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
_multiLangLyricsLines.Add(originalLines);
|
||||
if (translationLines.Count > 0)
|
||||
_multiLangLyricsLines.Add(translationLines);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILibreTranslateService
|
||||
{
|
||||
Task<string> TranslateAsync(string text, CancellationToken? token);
|
||||
Task<string> TranslateAsync(string text, string targetLangCode, CancellationToken? token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,22 +24,14 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<string> TranslateAsync(string text, CancellationToken? token)
|
||||
public async Task<string> TranslateAsync(string text, string targetLangCode, CancellationToken? token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text and target language must be provided.");
|
||||
}
|
||||
|
||||
string targetLangCode = AppInfo.GetAllTranslationLanguagesInfo()[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
|
||||
string originalLangCode = await DetectLanguageCode(text);
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(originalLangCode) || originalLangCode == targetLangCode)
|
||||
{
|
||||
return text; // No translation needed
|
||||
}
|
||||
string? originalLangCode = LanguageDetectionHelper.DetectLanguageCode(text);
|
||||
|
||||
var url = $"{_settingsService.LibreTranslateServer}/translate";
|
||||
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
|
||||
@@ -57,22 +49,5 @@ namespace BetterLyrics.WinUI3.Services
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
|
||||
return result?.TranslatedText ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task<string> DetectLanguageCode(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text must be provided.");
|
||||
}
|
||||
var url = $"{_settingsService.LibreTranslateServer}/detect";
|
||||
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
|
||||
[
|
||||
new("q", text),
|
||||
]));
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var resultList = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.ListDetectLanguageResult);
|
||||
return resultList?.OrderByDescending(x => x.Confidence).FirstOrDefault()?.Language ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
|
||||
{
|
||||
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-img-index.jsonl";
|
||||
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
|
||||
try
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
@@ -388,7 +388,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
|
||||
// 下载歌词内容
|
||||
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-img/{rawLyricFile}";
|
||||
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private int _langIndex = 0;
|
||||
|
||||
private List<List<LyricsLine>> _multiLangLyrics = [];
|
||||
private List<string> _translations = [];
|
||||
private List<string> _translationList = [];
|
||||
private bool _isTranslationEnabled = false;
|
||||
private int _targetLanguageIndex = 6;
|
||||
|
||||
@@ -346,10 +346,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
|
||||
{
|
||||
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) > 300)
|
||||
{
|
||||
_totalTime = e.Position;
|
||||
}
|
||||
_totalTime = e.Position;
|
||||
}
|
||||
|
||||
private async void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
||||
@@ -439,51 +436,60 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private async Task ShowTranslationsCoreAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Showing translations for lyrics...");
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
string targetLangCode = AppInfo.GetAllTranslationLanguagesInfo()[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
var originalText = string.Join("\n", _multiLangLyrics.FirstOrDefault()?.Select(x => x.OriginalText) ?? []);
|
||||
string? originalLangCode = LanguageDetectionHelper.DetectLanguageCode(originalText);
|
||||
|
||||
if (originalLangCode == targetLangCode)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
|
||||
ShowOriginalsOnly();
|
||||
return;
|
||||
}
|
||||
var text = string.Join("\n", _multiLangLyrics.FirstOrDefault()?.Select(x => x.OriginalText) ?? []);
|
||||
var translated = await _libreTranslateService.TranslateAsync(text, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_translations = translated.Split('\n').ToList();
|
||||
bool totallySame = true;
|
||||
foreach (var langLyrics in _multiLangLyrics)
|
||||
// Try get translation from itself first
|
||||
if (_multiLangLyrics.Count > 1)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in langLyrics)
|
||||
foreach (var langLyrics in _multiLangLyrics.Skip(1))
|
||||
{
|
||||
if (line.OriginalText != _translations[i])
|
||||
var translationList = langLyrics.Select(x => x.OriginalText).ToList();
|
||||
var translation = string.Join("\n", translationList);
|
||||
if (LanguageDetectionHelper.DetectLanguageCode(translation) == targetLangCode)
|
||||
{
|
||||
totallySame = false;
|
||||
_translationList = translationList;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
ShowOriginalsOnly();
|
||||
return;
|
||||
}
|
||||
|
||||
var translated = await _libreTranslateService.TranslateAsync(originalText, targetLangCode, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_translationList = translated.Split('\n').ToList();
|
||||
}
|
||||
|
||||
foreach (var langLyrics in _multiLangLyrics)
|
||||
int i = 0;
|
||||
foreach (var line in _multiLangLyrics.FirstOrDefault() ?? [])
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in langLyrics)
|
||||
{
|
||||
line.DisplayedText = totallySame ? line.OriginalText : $"{line.OriginalText}\n{_translations[i]}";
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
line.DisplayedText = i < _translationList.Count ? $"{line.OriginalText}\n{_translationList[i]}" : line.OriginalText;
|
||||
i++;
|
||||
}
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
@@ -495,7 +501,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void ShowOriginalsOnly()
|
||||
{
|
||||
_logger.LogInformation("Showing original lyrics only, translations disabled.");
|
||||
_logger.LogInformation("Showing original lyrics only, translation disabled.");
|
||||
foreach (var langLyrics in _multiLangLyrics)
|
||||
{
|
||||
foreach (var line in langLyrics)
|
||||
|
||||
@@ -385,7 +385,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
string result = await _libreTranslateService.TranslateAsync("Hello, world!", null);
|
||||
string targetLangCode = AppInfo.GetAllTranslationLanguagesInfo()[SelectedTargetLanguageIndex].Code;
|
||||
string result = await _libreTranslateService.TranslateAsync("Hello, world!", targetLangCode, null);
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
<StackPanel>
|
||||
<Slider
|
||||
x:Uid="MainPagePositionOffsetSlider"
|
||||
Maximum="2000"
|
||||
Minimum="-2000"
|
||||
Maximum="5000"
|
||||
Minimum="-5000"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
@@ -114,7 +114,7 @@
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</RelativePanel>
|
||||
<TextBlock Opacity="0.5" x:Uid="LyricsPagePositionOffsetHint"/>
|
||||
<TextBlock x:Uid="LyricsPagePositionOffsetHint" Opacity="0.5" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
|
||||
15
README.CN.md
15
README.CN.md
@@ -6,11 +6,16 @@
|
||||
|
||||
<h2 align="center">
|
||||
BetterLyrics
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h3 align="center">
|
||||
使用 WinUI 3 构建的流畅动态歌词显示工具
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
---
|
||||
|
||||
- QQ[「BetterLyrics」反馈交流群(简体中文)](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388)
|
||||
- Discord [「BetterLyrics」反馈交流群(繁体中文/英文)](https://discord.gg/5yAQPnyCKv)
|
||||
|
||||
---
|
||||
|
||||
@@ -121,8 +126,4 @@ BetterLyrics
|
||||
|
||||
## 欢迎提出任何问题和 PR
|
||||
|
||||
如果您发现错误,请提交至 issues;如果您有任何想法,请随时在此处分享。
|
||||
|
||||
或者,您也可以加入群聊,分享您的宝贵反馈:
|
||||
- QQ[「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388)
|
||||
- Discord [「BetterLyrics」反馈交流群](https://discord.gg/rbnF556r)
|
||||
如果您发现错误,请提交至 issues;如果您有任何想法,请随时在此处分享。
|
||||
15
README.md
15
README.md
@@ -6,11 +6,16 @@
|
||||
|
||||
<h2 align="center">
|
||||
BetterLyrics
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h3 align="center">
|
||||
Your smooth dynamic lyrics display tool built with WinUI 3
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
---
|
||||
|
||||
- [「BetterLyrics」反馈交流群(简体中文)](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) on QQ
|
||||
- [「BetterLyrics」Feedback Chat Group (Traditional Chinese / English)](https://discord.gg/5yAQPnyCKv) on Discord
|
||||
|
||||
---
|
||||
|
||||
@@ -121,8 +126,4 @@ You can `git clone` this project and build it yourself.
|
||||
|
||||
## Any issues and PRs are welcomed
|
||||
|
||||
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
|
||||
|
||||
Or alternatively join group chat to share your valuable feedback:
|
||||
- [「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) on QQ
|
||||
- [「BetterLyrics」Feedback Chat Group](https://discord.gg/rbnF556r) on Discord
|
||||
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
|
||||
Reference in New Issue
Block a user