This commit is contained in:
Zhe Fang
2025-07-16 19:22:47 -04:00
parent df31d03da7
commit fd5e752b43
18 changed files with 165 additions and 77 deletions

View File

@@ -91,7 +91,7 @@ namespace BetterLyrics.WinUI3.Services
public static bool IsCJK(string text)
{
return DetectLanguageCode(text) switch
return DetectLanguageCode(text)?.Substring(0, 2) switch
{
"zh" or "ja" or "ko" => true,
_ => false

View File

@@ -128,7 +128,6 @@ namespace BetterLyrics.WinUI3.Helper
var line = new LyricsLine
{
StartMs = start,
EndMs = 0, // 稍后统一修正
OriginalText = text,
LyricsChars = [],
};
@@ -143,7 +142,6 @@ namespace BetterLyrics.WinUI3.Helper
new LyricsChar
{
StartMs = charStart,
EndMs = 0, // Fixed later
Text = charText ?? "",
StartIndex = startIndex,
}
@@ -170,7 +168,9 @@ namespace BetterLyrics.WinUI3.Helper
{
// 句级时间
string? pBegin = p.Attribute("begin")?.Value;
string? pEnd = p.Attribute("end")?.Value;
int pStartMs = ParseTtmlTime(pBegin);
int pEndMs = ParseTtmlTime(pEnd);
// 只获取一级span且排除ttm:role="x-bg"的span
var spans = p.Elements()
@@ -202,11 +202,13 @@ namespace BetterLyrics.WinUI3.Helper
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 LyricsChar
{
StartMs = sStartMs,
EndMs = 0,
EndMs = sEndMs,
StartIndex = originalStartIndex,
Text = span.Value
});
@@ -218,7 +220,7 @@ namespace BetterLyrics.WinUI3.Helper
originalLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = 0,
EndMs = pEndMs,
OriginalText = originalText,
LyricsChars = originalCharTimings,
});
@@ -230,11 +232,13 @@ namespace BetterLyrics.WinUI3.Helper
foreach (var span in translationTextSpans)
{
string? sBegin = span.Attribute("begin")?.Value;
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
translationCharTimings.Add(new LyricsChar
{
StartMs = sStartMs,
EndMs = 0,
EndMs = sEndMs,
StartIndex = translationStartIndex,
Text = span.Value
});
@@ -245,7 +249,7 @@ namespace BetterLyrics.WinUI3.Helper
translationLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = 0,
EndMs = pEndMs,
OriginalText = translationText,
LyricsChars = translationCharTimings,
});
@@ -336,7 +340,7 @@ namespace BetterLyrics.WinUI3.Helper
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
EndMs = 0,
EndMs = lineRead.EndTime ?? 0,
OriginalText = lineRead.Text,
LyricsChars = [],
};
@@ -355,18 +359,10 @@ namespace BetterLyrics.WinUI3.Helper
var charTiming = new LyricsChar
{
StartMs = syllable.StartTime,
EndMs = 0,
EndMs = syllable.EndTime,
Text = syllable.Text,
StartIndex = startIndex,
};
if (syllableIndex + 1 < syllables.Count)
{
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
}
else
{
charTiming.EndMs = lineWrite.EndMs;
}
lineWrite.LyricsChars.Add(charTiming);
startIndex += syllable.Text.Length;
}
@@ -384,34 +380,6 @@ namespace BetterLyrics.WinUI3.Helper
for (int langIdx = 0; langIdx < _lyricsDataArr.Count; langIdx++)
{
var lines = _lyricsDataArr[langIdx].LyricsLines;
for (int i = 0; i < lines.Count; i++)
{
if (i + 1 < lines.Count)
{
lines[i].EndMs = lines[i + 1].StartMs;
}
else
{
lines[i].EndMs = durationMs;
}
// 修正 CharTimings 的 EndMs
var timings = lines[i].LyricsChars;
if (timings.Count > 0)
{
for (int j = 0; j < timings.Count; j++)
{
if (j + 1 < timings.Count)
{
timings[j].EndMs = timings[j + 1].StartMs;
}
else
{
timings[j].EndMs = lines[i].EndMs;
}
}
}
}
if (lines.Count > 0)
{
if (lines[0].StartMs > 0)

View File

@@ -6,7 +6,7 @@ namespace BetterLyrics.WinUI3.Models
{
public class LyricsChar
{
public int EndMs { get; set; }
public int? EndMs { get; set; }
public int StartIndex { get; set; }
public int StartMs { get; set; }
public string Text { get; set; } = string.Empty;

View File

@@ -23,8 +23,8 @@ namespace BetterLyrics.WinUI3.Models
public List<LyricsChar> LyricsChars { get; set; } = [];
public int DurationMs => EndMs - StartMs;
public int EndMs { get; set; }
public int? DurationMs => EndMs - StartMs;
public int? EndMs { get; set; }
public int StartMs { get; set; }
public string DisplayedText { get; set; } = "";

View File

@@ -85,6 +85,7 @@ namespace BetterLyrics.WinUI3.Services
bool IgnoreFullscreenWindow { get; set; }
bool IsTranslationEnabled { get; set; }
bool ShowTranslationOnly { get; set; }
LyricsDisplayType DisplayType { get; set; }

View File

@@ -72,6 +72,7 @@ namespace BetterLyrics.WinUI3.Services
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
private const string ShowTranslationOnlyKey = "ShowTranslationOnly";
private const string LibreTranslateServerKey = "LibreTranslateServer";
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
@@ -198,7 +199,8 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(IsFanLyricsEnabledKey, false);
SetDefault(LibreTranslateServerKey, "");
SetDefault(IsTranslationEnabledKey, false);
SetDefault(IsTranslationEnabledKey, true);
SetDefault(ShowTranslationOnlyKey, false);
SetDefault(SelectedTargetLanguageIndexKey, LanguageHelper.GetDefaultTargetLanguageIndex());
SetDefault(LXMusicServerKey, "");
@@ -219,6 +221,12 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(DockPlacementKey, (int)DockPlacement.Top);
}
public bool ShowTranslationOnly
{
get => GetValue<bool>(ShowTranslationOnlyKey);
set => SetValue(ShowTranslationOnlyKey, value);
}
public DockPlacement DockPlacement
{
get => (DockPlacement)GetValue<int>(DockPlacementKey);

View File

@@ -759,4 +759,10 @@
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
<value>Bottom</value>
</data>
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
<value>Enable translation</value>
</data>
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>Show translation only</value>
</data>
</root>

View File

@@ -759,4 +759,10 @@
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
<value>底</value>
</data>
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
<value>翻訳を有効にします</value>
</data>
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>翻訳のみを表示します</value>
</data>
</root>

View File

@@ -759,4 +759,10 @@
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
<value>맨 아래</value>
</data>
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
<value>번역 활성화</value>
</data>
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>번역 만 표시하십시오</value>
</data>
</root>

View File

@@ -759,4 +759,10 @@
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
<value>底部</value>
</data>
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
<value>启用翻译</value>
</data>
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>仅显示翻译</value>
</data>
</root>

View File

@@ -759,4 +759,10 @@
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
<value>底部</value>
</data>
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
<value>啟用翻譯</value>
</data>
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
<value>僅顯示翻譯</value>
</data>
</root>

View File

@@ -26,6 +26,8 @@ namespace BetterLyrics.WinUI3.ViewModels
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
PositionOffset = _settingsService.PositionOffset;
IsImmersiveMode = _settingsService.IsImmersiveMode;
ShowTranslationOnly = _settingsService.ShowTranslationOnly;
OnIsImmersiveModeChanged(IsImmersiveMode);
//Volume = SystemVolumeHelper.GetMasterVolume();
@@ -69,7 +71,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsDisplayType DisplayType { get; set; } = LyricsDisplayType.PlaceholderOnly;
public partial LyricsDisplayType DisplayType { get; set; }
[ObservableProperty]
public partial bool IsFirstRun { get; set; }
@@ -88,6 +90,10 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial bool IsTranslationEnabled { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool ShowTranslationOnly { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
@@ -178,6 +184,11 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
partial void OnShowTranslationOnlyChanged(bool value)
{
_settingsService.ShowTranslationOnly = value;
}
//partial void OnVolumeChanged(int value)
//{
// SystemVolumeHelper.SetMasterVolume(value);

View File

@@ -43,6 +43,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
_showTranslationOnly = _settingsService.ShowTranslationOnly;
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();

View File

@@ -68,7 +68,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (currentPlayingLine != null)
{
GetLinePlayingProgress(
currentPlayingLine,
_playingLineIndex,
out int charStartIndex,
out int charLength,
out float charProgress
@@ -368,7 +368,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (i == _playingLineIndex)
{
GetLinePlayingProgress(
line,
i,
out int charStartIndex,
out int charLength,
out float charProgress

View File

@@ -113,6 +113,11 @@ namespace BetterLyrics.WinUI3.ViewModels
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _isTranslationEnabled);
UpdateTranslations();
}
else if (message.PropertyName == nameof(LyricsPageViewModel.ShowTranslationOnly))
{
_showTranslationOnly = message.NewValue;
UpdateTranslations();
}
}
}

View File

@@ -34,6 +34,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private TimeSpan _totalTime = TimeSpan.Zero;
private TimeSpan _positionOffset = TimeSpan.Zero;
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
@@ -140,8 +142,9 @@ namespace BetterLyrics.WinUI3.ViewModels
private List<LyricsData> _lyricsDataArr = [];
private List<string> _translationList = [];
private bool _isTranslationEnabled = false;
private int _targetLanguageIndex = 6;
private bool _isTranslationEnabled;
private bool _showTranslationOnly;
private int _targetLanguageIndex;
private int _timelineSyncThreshold;
@@ -193,28 +196,37 @@ namespace BetterLyrics.WinUI3.ViewModels
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
{
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[i];
if (line == null)
if (line == null) continue;
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i + 1);
var totalMs = _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
{
continue;
return i;
}
if (
line.StartMs <= _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds
&& _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds <= line.EndMs
)
else if (nextLine == null && line.StartMs <= totalMs)
{
return i;
}
}
return -1;
return GetMaxLyricsLineIndexBoundaries().Item2;
}
private void GetLinePlayingProgress(LyricsLine line, out int charStartIndex, out int charLength, out float charProgress)
private void GetLinePlayingProgress(int lineIndex, out int charStartIndex, out int charLength, out float charProgress)
{
charStartIndex = 0;
charLength = 0;
charProgress = 0f;
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return;
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
int lineEndMs;
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
else if (nextLine != null) lineEndMs = nextLine.StartMs;
else lineEndMs = _songDurationMs;
float now = (float)_totalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
// 1. 还没到本句
@@ -224,27 +236,37 @@ namespace BetterLyrics.WinUI3.ViewModels
}
// 2. 已经超过本句
if (now > line.EndMs)
if (now > lineEndMs)
{
charProgress = 1f;
charStartIndex = 0;
charLength = line.OriginalText.Length;
return;
}
// 3. 有逐字时间轴
if (line.LyricsChars != null && line.LyricsChars.Count > 0)
if (line.LyricsChars != null && line.LyricsChars.Count > 1)
{
int charTimingsCount = line.LyricsChars.Count;
for (int i = 0; i < charTimingsCount; i++)
{
var timing = line.LyricsChars[i];
var nextTiming = line.LyricsChars.ElementAtOrDefault(i + 1);
int timingEndMs;
if (timing.EndMs != null) timingEndMs = timing.EndMs.Value;
else if (nextTiming != null) timingEndMs = nextTiming.StartMs;
else timingEndMs = lineEndMs;
charStartIndex = timing.StartIndex;
charLength = timing.Text.Length;
// 当前时间在某个字的高亮区间
if (now >= timing.StartMs && now <= timing.EndMs)
if (now >= timing.StartMs && now <= timingEndMs)
{
charStartIndex = timing.StartIndex;
charLength = timing.Text.Length;
if (timing.EndMs != timing.StartMs)
if (timingEndMs != timing.StartMs)
{
charProgress = (now - timing.StartMs) / (timing.EndMs - timing.StartMs);
charProgress = (now - timing.StartMs) / (timingEndMs - timing.StartMs);
}
else
{
@@ -252,12 +274,17 @@ namespace BetterLyrics.WinUI3.ViewModels
}
return;
}
else if (now > timingEndMs && (nextTiming == null || now < nextTiming?.StartMs))
{
charProgress = 1f;
return;
}
}
}
else
{
// 没有逐字时间轴,直接线性
charProgress = (now - line.StartMs) / line.DurationMs;
charProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
charProgress = Math.Clamp(charProgress, 0f, 1f);
charStartIndex = 0;
charLength = line.OriginalText.Length;
@@ -312,6 +339,8 @@ namespace BetterLyrics.WinUI3.ViewModels
_lastSongArtist = _songArtist;
_songArtist = SongInfo?.Artist;
_songDurationMs = (int)(SongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
@@ -361,6 +390,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else
{
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
_langIndex = 0;
IsTranslating = false;
_isLayoutChanged = true;
}
@@ -384,7 +414,16 @@ namespace BetterLyrics.WinUI3.ViewModels
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
if (found >= 0)
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found]);
if (_showTranslationOnly)
{
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
_langIndex = found;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found]);
_langIndex = 0;
}
}
else
{
@@ -392,7 +431,17 @@ namespace BetterLyrics.WinUI3.ViewModels
{
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
token.ThrowIfCancellationRequested();
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
if (_showTranslationOnly)
{
// TODO
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_langIndex = 0;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_langIndex = 0;
}
}
catch (Exception) { }
}

View File

@@ -246,16 +246,26 @@
</Button>
<!-- Translation -->
<ToggleButton
x:Name="TranslationToggleButton"
<Button
Click="TranslationButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8BD;}"
IsChecked="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}"
Style="{StaticResource GhostToggleButtonStyle}">
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
</ToolTipService.ToolTip>
</ToggleButton>
<Button.DataContext>
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<ToggleSwitch x:Uid="LyricsPageTranslationEnabled" IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="LyricsPageTranslationOnly"
IsEnabled="{x:Bind ViewModel.IsTranslationEnabled, Mode=OneWay}"
IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
<!-- Display type -->
<Button

View File

@@ -71,5 +71,10 @@ namespace BetterLyrics.WinUI3.Views
{
TimelineOffsetFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void TranslationButton_Click(object sender, RoutedEventArgs e)
{
TranslationFlyout.ShowAt(BottomRightCommandStackPanel);
}
}
}