diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest
index 71bbdd0..2edf4f4 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest
@@ -11,7 +11,7 @@
+ Version="1.0.5.0" />
@@ -30,6 +30,8 @@
+
+
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml
index 2e54d77..87900e9 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml
@@ -45,6 +45,7 @@
+
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs
index 610ebdc..ae0af5f 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs
@@ -1,12 +1,11 @@
-using System.Text;
+using System;
+using System.Text;
+using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Helper;
-using BetterLyrics.WinUI3.Rendering;
-using BetterLyrics.WinUI3.Services.Database;
-using BetterLyrics.WinUI3.Services.Playback;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
+using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
-using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -14,7 +13,6 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;
-using WinUIEx;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -48,19 +46,46 @@ namespace BetterLyrics.WinUI3
ResourceLoader = new ResourceLoader();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- Helper.AppInfo.EnsureDirectories();
+ AppInfo.EnsureDirectories();
ConfigureServices();
_logger = Ioc.Default.GetService>()!;
UnhandledException += App_UnhandledException;
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
+ TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+ }
+
+ private void CurrentDomain_FirstChanceException(
+ object? sender,
+ System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
+ )
+ {
+ _logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
+ }
+
+ private void TaskScheduler_UnobservedTaskException(
+ object? sender,
+ UnobservedTaskExceptionEventArgs e
+ )
+ {
+ _logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
+ }
+
+ private void CurrentDomain_UnhandledException(
+ object sender,
+ System.UnhandledExceptionEventArgs e
+ )
+ {
+ _logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
}
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
- .WriteTo.File(Helper.AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
+ .WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
.CreateLogger();
// Register services
@@ -73,8 +98,9 @@ namespace BetterLyrics.WinUI3
})
// Services
.AddSingleton()
- .AddSingleton()
.AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
// ViewModels
.AddTransient()
.AddSingleton()
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/CustomTransform.bin b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/CustomTransform.bin
deleted file mode 100644
index 20ae6e7..0000000
Binary files a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/CustomTransform.bin and /dev/null differ
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
index 20c02a8..37e72f5 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
@@ -10,9 +10,6 @@
enable
preview
-
-
-
@@ -21,32 +18,36 @@
-
+
-
+
-
-
-
+
-
+
@@ -67,6 +68,10 @@
+
+
+
+
False
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Converter/LyricsSearchProviderToDisplayNameConverter.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Converter/LyricsSearchProviderToDisplayNameConverter.cs
new file mode 100644
index 0000000..36e0772
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Converter/LyricsSearchProviderToDisplayNameConverter.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BetterLyrics.WinUI3.Enums;
+using Microsoft.UI.Xaml.Data;
+
+namespace BetterLyrics.WinUI3.Converter
+{
+ public class LyricsSearchProviderToDisplayNameConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is LyricsSearchProvider provider)
+ {
+ return provider switch
+ {
+ LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
+ "LyricsSearchProviderLocalLrcFile"
+ ),
+ LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
+ "LyricsSearchProviderLocalMusicFile"
+ ),
+ LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
+ "LyricsSearchProviderLrcLib"
+ ),
+ LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
+ "LyricsSearchProviderEslrcFile"
+ ),
+ LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString(
+ "LyricsSearchProviderTtmlFile"
+ ),
+ _ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null),
+ };
+ }
+ return "";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs
index 7b03a91..8a31c33 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs
@@ -12,5 +12,7 @@ namespace BetterLyrics.WinUI3.Enums
English,
SimplifiedChinese,
TraditionalChinese,
+ Japanese,
+ Korean,
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LocalSearchTargetProps.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LocalSearchTargetProps.cs
new file mode 100644
index 0000000..4372b21
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LocalSearchTargetProps.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Enums
+{
+ public enum LocalSearchTargetProps
+ {
+ LyricsOnly,
+ LyricsAndAlbumArt,
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsFormat.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsFormat.cs
new file mode 100644
index 0000000..17313cb
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsFormat.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Enums
+{
+ public enum LyricsFormat
+ {
+ Lrc,
+ Eslrc,
+ Ttml,
+ }
+
+ public static class LyricsFormatExtensions
+ {
+ public static string ToFileExtension(this LyricsFormat format)
+ {
+ return format switch
+ {
+ LyricsFormat.Lrc => ".lrc",
+ LyricsFormat.Eslrc => ".eslrc",
+ LyricsFormat.Ttml => ".ttml",
+ _ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
+ };
+ }
+
+ public static LyricsFormat? Detect(string content)
+ {
+ if (
+ content.StartsWith(""
+ )
+ )
+ {
+ return LyricsFormat.Lrc;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs
new file mode 100644
index 0000000..97adfa7
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs
@@ -0,0 +1,11 @@
+namespace BetterLyrics.WinUI3.Enums
+{
+ public enum LyricsSearchProvider
+ {
+ LrcLib,
+ LocalMusicFile,
+ LocalLrcFile,
+ LocalEslrcFile,
+ LocalTtmlFile,
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsStatus.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsStatus.cs
new file mode 100644
index 0000000..e9a6418
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsStatus.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Enums
+{
+ public enum LyricsStatus
+ {
+ NotFound,
+ Found,
+ Loading,
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/MusicSearchMatchMode.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/MusicSearchMatchMode.cs
new file mode 100644
index 0000000..874c083
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/MusicSearchMatchMode.cs
@@ -0,0 +1,8 @@
+namespace BetterLyrics.WinUI3.Enums
+{
+ public enum MusicSearchMatchMode
+ {
+ TitleAndArtist,
+ TitleArtistAlbumAndDuration,
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs
index 347fda4..f865dce 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/IsPlayingChangedEventArgs.cs
@@ -9,5 +9,5 @@ namespace BetterLyrics.WinUI3.Events
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
{
public bool IsPlaying { get; set; } = isPlaying;
- }
+}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/LibChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/LibChangedEventArgs.cs
new file mode 100644
index 0000000..7c45539
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/LibChangedEventArgs.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Events
+{
+ public class LibChangedEventArgs : EventArgs
+ {
+ public string Folder { get; }
+ public string FilePath { get; }
+ public WatcherChangeTypes ChangeType { get; }
+
+ public LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType)
+ {
+ Folder = folder;
+ FilePath = filePath;
+ ChangeType = changeType;
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs
index e42add5..ef247af 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/PositionChangedEventArgs.cs
@@ -9,5 +9,5 @@ namespace BetterLyrics.WinUI3.Events
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
{
public TimeSpan Position { get; set; } = position;
- }
+}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs
index 325648d..417ae57 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/SongInfoChangedEventArgs.cs
@@ -10,5 +10,5 @@ namespace BetterLyrics.WinUI3.Events
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
{
public SongInfo? SongInfo { get; set; } = songInfo;
- }
+}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs
index 3ff4df1..d6e06dd 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AnimationHelper.cs
@@ -1,13 +1,79 @@
using System;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Media.Animation;
namespace BetterLyrics.WinUI3.Helper
{
- public static class AnimationHelper
+ public class AnimationHelper
{
public const int StackedNotificationsShowingDuration = 3900;
public const int StoryboardDefaultDuration = 200;
public const int DebounceDefaultDuration = 200;
}
+
+ public class ValueTransition
+ where T : struct
+ {
+ private T _currentValue;
+ private T _startValue;
+ private T _targetValue;
+ private float _progress;
+ private float _durationSeconds;
+ private bool _isTransitioning;
+ private Func _interpolator;
+
+ public ValueTransition(
+ T initialValue,
+ float durationSeconds,
+ Func interpolator
+ )
+ {
+ _currentValue = initialValue;
+ _startValue = initialValue;
+ _targetValue = initialValue;
+ _durationSeconds = durationSeconds;
+ _progress = 1f;
+ _isTransitioning = false;
+ _interpolator = interpolator;
+ }
+
+ public T Value => _currentValue;
+ public bool IsTransitioning => _isTransitioning;
+
+ public void StartTransition(T targetValue)
+ {
+ if (!targetValue.Equals(_currentValue))
+ {
+ _startValue = _currentValue;
+ _targetValue = targetValue;
+ _progress = 0f;
+ _isTransitioning = true;
+ }
+ }
+
+ public void Update(TimeSpan elapsedTime)
+ {
+ if (!_isTransitioning)
+ return;
+
+ _progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
+ if (_progress >= 1f)
+ {
+ _progress = 1f;
+ _currentValue = _targetValue;
+ _isTransitioning = false;
+ }
+ else
+ {
+ _currentValue = _interpolator(_startValue, _targetValue, _progress);
+ }
+ }
+
+ public void Reset(T value)
+ {
+ _currentValue = value;
+ _startValue = value;
+ _targetValue = value;
+ _progress = 0f;
+ _isTransitioning = false;
+ }
+ }
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs
index 3133d8a..9af52b4 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs
@@ -41,22 +41,21 @@ namespace BetterLyrics.WinUI3.Helper
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
// Data Files
- private static string DatabaseFileName => "database.db";
- public static string DatabasePath => Path.Combine(LocalFolder, DatabaseFileName);
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
+ public static string OnlineLyricsCacheDirectory =>
+ Path.Combine(CacheFolder, "online-lyrics");
+
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
- private static string CustomShaderFileName => "CustomTransform.bin";
- public static string CustomShaderPath => Path.Combine(AssetsFolder, CustomShaderFileName);
-
public static void EnsureDirectories()
{
- Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LocalFolder);
+ Directory.CreateDirectory(LogDirectory);
+ Directory.CreateDirectory(OnlineLyricsCacheDirectory);
}
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs
index 49816dc..36c3c25 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ColorThief.cs
@@ -4,6 +4,11 @@ using System.Linq;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
+// This file is a fork of https://github.com/KSemenenko/ColorThief
+// and is used to extract dominant colors from images in WinUI3 application
+// I have modified it to fit the WinUI3 environment and removed unnecessary parts
+// Credits to KSemenenko for the original work.
+
namespace BetterLyrics.WinUI3.Helper
{
///
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs
index e29cc95..6d15847 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.cs
@@ -23,6 +23,7 @@ namespace BetterLyrics.WinUI3.Helper
{
window.SetIsShownInSwitchers(true);
window.ExtendsContentIntoTitleBar = true;
+ window.SetIsAlwaysOnTop(false);
IntPtr hwnd = WindowNative.GetWindowHandle(window);
@@ -43,8 +44,6 @@ namespace BetterLyrics.WinUI3.Helper
_originalPositions.Remove(hwnd);
}
- window.SetIsAlwaysOnTop(false);
-
UnregisterAppBar(hwnd);
}
@@ -52,6 +51,7 @@ namespace BetterLyrics.WinUI3.Helper
{
window.SetIsShownInSwitchers(false);
window.ExtendsContentIntoTitleBar = false;
+ window.SetIsAlwaysOnTop(true);
IntPtr hwnd = WindowNative.GetWindowHandle(window);
@@ -82,8 +82,6 @@ namespace BetterLyrics.WinUI3.Helper
appBarHeight,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
);
-
- window.SetIsAlwaysOnTop(true);
}
[DllImport("user32.dll", SetLastError = true)]
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs
new file mode 100644
index 0000000..1d920dd
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Ude;
+
+namespace BetterLyrics.WinUI3.Helper
+{
+ public class FileHelper
+ {
+ public static Encoding GetEncoding(string filename)
+ {
+ var bytes = File.ReadAllBytes(filename);
+ var cdet = new CharsetDetector();
+ cdet.Feed(bytes, 0, bytes.Length);
+ cdet.DataEnd();
+ var encoding = cdet.Charset;
+ if (encoding == null)
+ {
+ return Encoding.UTF8;
+ }
+ return Encoding.GetEncoding(encoding);
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs
index bf34dbe..8df79d7 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs
@@ -2,8 +2,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.Text;
+using Microsoft.UI;
+using Microsoft.UI.Text;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
@@ -71,5 +76,81 @@ namespace BetterLyrics.WinUI3.Helper
public static async Task GetDecoderFromByte(byte[] bytes) =>
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
+
+ public static async Task CreateTextPlaceholderBytesAsync(
+ string text,
+ int width,
+ int height
+ )
+ {
+ var device = CanvasDevice.GetSharedDevice();
+ var renderTarget = new CanvasRenderTarget(device, width, height, 96);
+
+ // 居中绘制文字
+ using (var ds = renderTarget.CreateDrawingSession())
+ {
+ // 背景色
+ ds.Clear(Colors.LightGray);
+
+ // 文字格式
+ var format = new CanvasTextFormat
+ {
+ FontSize = Math.Min(width, height) / 6f,
+ FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
+ HorizontalAlignment = CanvasHorizontalAlignment.Center,
+ VerticalAlignment = CanvasVerticalAlignment.Center,
+ WordWrapping = CanvasWordWrapping.Wrap,
+ TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
+ Options = CanvasDrawTextOptions.Default,
+ };
+
+ // 设定边距
+ float margin = Math.Min(width, height) / 12f;
+ float availableWidth = width - 2 * margin;
+ float availableHeight = height - 2 * margin;
+
+ // 计算合适的字体大小以适应内容区域
+ float fontSize = format.FontSize;
+ float minFontSize = 8f;
+ float maxFontSize = format.FontSize;
+ CanvasTextLayout layout;
+ do
+ {
+ format.FontSize = fontSize;
+ layout = new CanvasTextLayout(
+ ds,
+ text,
+ format,
+ availableWidth,
+ availableHeight
+ );
+ if (
+ layout.LayoutBounds.Width <= availableWidth
+ && layout.LayoutBounds.Height <= availableHeight
+ )
+ break;
+ fontSize -= 1f;
+ } while (fontSize >= minFontSize);
+
+ // 居中绘制文字(在内容区域内居中)
+ var bounds = layout.LayoutBounds;
+ var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X;
+ var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y;
+ ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray);
+ }
+
+ // 保存为 PNG 并转为 byte[]
+ using (var stream = new InMemoryRandomAccessStream())
+ {
+ await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
+ var buffer = new byte[stream.Size];
+ using (var reader = new DataReader(stream.GetInputStreamAt(0)))
+ {
+ await reader.LoadAsync((uint)stream.Size);
+ reader.ReadBytes(buffer);
+ }
+ return buffer;
+ }
+ }
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
new file mode 100644
index 0000000..e43f094
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Models;
+
+namespace BetterLyrics.WinUI3.Helper
+{
+ public class LyricsParser
+ {
+ private List> _multiLangLyricsLines = [];
+
+ public List> Parse(
+ string raw,
+ LyricsFormat? lyricsFormat = null,
+ string? title = null,
+ string? artist = null,
+ int durationMs = 0
+ )
+ {
+ _multiLangLyricsLines = [];
+ switch (lyricsFormat)
+ {
+ case LyricsFormat.Lrc:
+ case LyricsFormat.Eslrc:
+ ParseLrc(raw, durationMs);
+ break;
+ case LyricsFormat.Ttml:
+ ParseTtml(raw, durationMs);
+ break;
+ default:
+ break;
+ }
+ return _multiLangLyricsLines;
+ }
+
+ private void PostProcessLyricsLines(List lines)
+ {
+ if (lines.Count > 0 && lines[0].StartMs > 0)
+ {
+ lines.Insert(
+ 0,
+ new LyricsLine
+ {
+ StartMs = 0,
+ EndMs = lines[0].StartMs,
+ Text = "",
+ CharTimings = [],
+ }
+ );
+ }
+ }
+
+ private void ParseLrc(string raw, int durationMs)
+ {
+ var lines = raw.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
+ var lrcLines =
+ new List<(int time, string text, List<(int time, string text)> syllables)>();
+
+ // 支持 [mm:ss.xx]字、字,毫秒两位或三位
+ var syllableRegex = new Regex(
+ @"(\[|\<)(\d{2}):(\d{2})\.(\d{2,3})(\]|\>)([^\[\]\<\>]*)"
+ );
+
+ foreach (var line in lines)
+ {
+ var matches = syllableRegex.Matches(line);
+ var syllables = new List<(int, string)>();
+ for (int i = 0; i < matches.Count; i++)
+ {
+ var m = matches[i];
+ int min = int.Parse(m.Groups[2].Value);
+ int sec = int.Parse(m.Groups[3].Value);
+ int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
+ int totalMs = min * 60_000 + sec * 1000 + ms;
+ string text = m.Groups[6].Value;
+
+ syllables.Add((totalMs, text));
+ }
+ if (syllables.Count > 0)
+ {
+ lrcLines.Add(
+ (
+ syllables[0].Item1,
+ string.Concat(syllables.Select(s => s.Item2)),
+ syllables
+ )
+ );
+ }
+ else
+ {
+ // 普通LRC行
+ var bracketRegex = new Regex(@"\[(\d{2}):(\d{2})\.(\d{2,3})\]");
+ var bracketMatches = bracketRegex.Matches(line);
+ string content = line;
+ int? lineStartTime = null;
+ if (bracketMatches.Count > 0)
+ {
+ var m = bracketMatches[0];
+ int min = int.Parse(m.Groups[1].Value);
+ int sec = int.Parse(m.Groups[2].Value);
+ int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0'));
+ lineStartTime = min * 60_000 + sec * 1000 + ms;
+ content = bracketRegex.Replace(line, "").Trim();
+ lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
+ }
+ }
+ }
+
+ // 按时间分组
+ var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
+ int languageCount = grouped.Max(g => g.Count());
+
+ // 初始化每种语言的歌词列表
+ _multiLangLyricsLines.Clear();
+ for (int i = 0; i < languageCount; i++)
+ _multiLangLyricsLines.Add(new List());
+
+ // 遍历每个时间分组
+ foreach (var group in grouped)
+ {
+ var linesInGroup = group.ToList();
+ for (int langIdx = 0; langIdx < languageCount; langIdx++)
+ {
+ // 如果该语言有翻译,取对应行,否则用原文(第一行)
+ var (start, text, syllables) =
+ langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
+ var line = new LyricsLine
+ {
+ StartMs = start,
+ EndMs = 0, // 稍后统一修正
+ Text = text,
+ CharTimings = [],
+ };
+ if (syllables != null && syllables.Count > 0)
+ {
+ for (int j = 0; j < syllables.Count; j++)
+ {
+ var (charStart, charText) = syllables[j];
+ int charEnd = (j + 1 < syllables.Count) ? syllables[j + 1].Item1 : 0;
+ line.CharTimings.Add(
+ new CharTiming { StartMs = charStart, EndMs = charEnd }
+ );
+ }
+ }
+ _multiLangLyricsLines[langIdx].Add(line);
+ }
+ }
+
+ // 修正 EndMs
+ for (int langIdx = 0; langIdx < languageCount; langIdx++)
+ {
+ var linesInSingleLang = _multiLangLyricsLines[langIdx];
+ for (int i = 0; i < linesInSingleLang.Count; i++)
+ {
+ if (i + 1 < linesInSingleLang.Count)
+ linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
+ else
+ linesInSingleLang[i].EndMs = durationMs;
+
+ // 修正 CharTimings 的最后一个 EndMs
+ var timings = linesInSingleLang[i].CharTimings;
+ 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 = linesInSingleLang[i].EndMs;
+ }
+ }
+ }
+ PostProcessLyricsLines(linesInSingleLang);
+ }
+ }
+
+ private void ParseTtml(string raw, int durationMs)
+ {
+ try
+ {
+ List singleLangLyricsLine = [];
+ var xdoc = XDocument.Parse(raw);
+ 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);
+
+ // 处理分词分时
+ var spans = p.Elements()
+ .Where(s =>
+ s.Name.LocalName == "span"
+ && s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))
+ == null
+ )
+ .ToList();
+
+ string text = string.Concat(spans.Select(s => s.Value));
+ var charTimings = new List();
+
+ for (int i = 0; i < spans.Count; i++)
+ {
+ 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 = sEndMs });
+ }
+
+ if (spans.Count == 0)
+ text = p.Value.Trim();
+
+ singleLangLyricsLine.Add(
+ new LyricsLine
+ {
+ StartMs = pStartMs,
+ EndMs = pEndMs,
+ Text = text,
+ CharTimings = charTimings,
+ }
+ );
+ }
+ PostProcessLyricsLines(singleLangLyricsLine);
+ _multiLangLyricsLines.Add(singleLangLyricsLine);
+ }
+ catch
+ {
+ // 解析失败,忽略
+ }
+ }
+
+ private int ParseTtmlTime(string? t)
+ {
+ if (string.IsNullOrWhiteSpace(t))
+ return 0;
+
+ t = t.Trim();
+
+ // 支持 "1.000s"
+ if (t.EndsWith("s"))
+ {
+ if (
+ double.TryParse(
+ t.TrimEnd('s'),
+ System.Globalization.NumberStyles.Float,
+ System.Globalization.CultureInfo.InvariantCulture,
+ out double seconds
+ )
+ )
+ return (int)(seconds * 1000);
+ }
+ else
+ {
+ var parts = t.Split(':');
+ if (parts.Length == 3)
+ {
+ // hh:mm:ss.xxx
+ int h = int.Parse(parts[0]);
+ int m = int.Parse(parts[1]);
+ double s = double.Parse(
+ parts[2],
+ System.Globalization.CultureInfo.InvariantCulture
+ );
+ return (int)((h * 3600 + m * 60 + s) * 1000);
+ }
+ else if (parts.Length == 2)
+ {
+ // mm:ss.xxx
+ int m = int.Parse(parts[0]);
+ double s = double.Parse(
+ parts[1],
+ System.Globalization.CultureInfo.InvariantCulture
+ );
+ return (int)((m * 60 + s) * 1000);
+ }
+ else if (parts.Length == 1)
+ {
+ // ss.xxx
+ if (
+ double.TryParse(
+ parts[0],
+ System.Globalization.NumberStyles.Float,
+ System.Globalization.CultureInfo.InvariantCulture,
+ out double s
+ )
+ )
+ return (int)(s * 1000);
+ }
+ }
+ return 0;
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs
index e720895..5d14b06 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Messages/ShowNotificatonMessage.cs
@@ -11,5 +11,6 @@ using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.Messages
{
public class ShowNotificatonMessage(Notification value)
- : ValueChangedMessage(value) { }
+ : ValueChangedMessage(value)
+ { }
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs
new file mode 100644
index 0000000..d719c94
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Models
+{
+ public class CharTiming
+ {
+ public int StartMs { get; set; }
+ public int EndMs { get; set; }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LocalLyricsFolder.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LocalLyricsFolder.cs
new file mode 100644
index 0000000..3b0b066
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LocalLyricsFolder.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace BetterLyrics.WinUI3.Models
+{
+ public partial class LocalLyricsFolder : ObservableObject
+ {
+ [ObservableProperty]
+ public partial string Path { get; set; }
+
+ [ObservableProperty]
+ public partial bool IsEnabled { get; set; }
+
+ public LocalLyricsFolder() { }
+
+ public LocalLyricsFolder(string path, bool isEnabled)
+ {
+ Path = path;
+ IsEnabled = isEnabled;
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs
new file mode 100644
index 0000000..c176122
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Models
+{
+ public class LyricsData
+ {
+ public int LanguageIndex { get; set; } = 0;
+
+ public List LyricsLines => MultiLangLyricsLines[LanguageIndex];
+ public List> MultiLangLyricsLines { get; set; } = [];
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs
index b6bc69b..82794f8 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs
@@ -1,23 +1,22 @@
using System.Collections.Generic;
using System.Numerics;
using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Helper;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
- public List Texts { get; set; } = [];
+ public string Text { get; set; } = "";
- public int LanguageIndex { get; set; } = 0;
+ public List CharTimings { get; set; } = [];
- public string Text => Texts[LanguageIndex];
-
- public int StartPlayingTimestampMs { get; set; }
- public int EndPlayingTimestampMs { get; set; }
+ public int StartMs { get; set; }
+ public int EndMs { get; set; }
public LyricsPlayingState PlayingState { get; set; }
- public int DurationMs => EndPlayingTimestampMs - StartPlayingTimestampMs;
+ public int DurationMs => EndMs - StartMs;
public float EnteringProgress { get; set; }
@@ -37,10 +36,10 @@ namespace BetterLyrics.WinUI3.Models
{
return new LyricsLine
{
- Texts = new List(this.Texts),
- LanguageIndex = this.LanguageIndex,
- StartPlayingTimestampMs = this.StartPlayingTimestampMs,
- EndPlayingTimestampMs = this.EndPlayingTimestampMs,
+ Text = this.Text,
+ CharTimings = this.CharTimings,
+ StartMs = this.StartMs,
+ EndMs = this.EndMs,
PlayingState = this.PlayingState,
EnteringProgress = this.EnteringProgress,
ExitingProgress = this.ExitingProgress,
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSearchProviderInfo.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSearchProviderInfo.cs
new file mode 100644
index 0000000..9836e07
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSearchProviderInfo.cs
@@ -0,0 +1,22 @@
+using BetterLyrics.WinUI3.Enums;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace BetterLyrics.WinUI3.Models
+{
+ public partial class LyricsSearchProviderInfo : ObservableObject
+ {
+ [ObservableProperty]
+ public partial LyricsSearchProvider Provider { get; set; }
+
+ [ObservableProperty]
+ public partial bool IsEnabled { get; set; }
+
+ public LyricsSearchProviderInfo() { }
+
+ public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
+ {
+ Provider = provider;
+ IsEnabled = isEnabled;
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MetadataIndex.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MetadataIndex.cs
deleted file mode 100644
index 0e2ec9d..0000000
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MetadataIndex.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using SQLite;
-
-namespace BetterLyrics.WinUI3.Models
-{
- public class MetadataIndex
- {
- [PrimaryKey]
- public string? Path { get; set; }
- public string? Title { get; set; }
- public string? Artist { get; set; }
- }
-}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs
index 67cee90..c9dd233 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Notification.cs
@@ -12,19 +12,19 @@ namespace BetterLyrics.WinUI3.Models
public partial class Notification : ObservableObject
{
[ObservableProperty]
- private InfoBarSeverity _severity;
+ public partial InfoBarSeverity Severity { get; set; }
[ObservableProperty]
- private string? _message;
+ public partial string? Message { get; set; }
[ObservableProperty]
- private bool _isForeverDismissable;
+ public partial bool IsForeverDismissable { get; set; }
[ObservableProperty]
- private Visibility _visibility;
+ public partial Visibility Visibility { get; set; }
[ObservableProperty]
- private string? _relatedSettingsKeyName;
+ public partial string? RelatedSettingsKeyName { get; set; }
public Notification(
string? message = null,
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs
index 549fa7d..6c46e33 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs
@@ -1,106 +1,29 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using ATL;
-using BetterLyrics.WinUI3.Helper;
-using CommunityToolkit.Mvvm.ComponentModel;
-using Microsoft.UI;
-using Windows.UI;
-using static ATL.LyricsInfo;
+using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class SongInfo : ObservableObject
{
[ObservableProperty]
- public partial string? Title { get; set; }
+ public partial string Title { get; set; }
[ObservableProperty]
- public partial string? Artist { get; set; }
+ public partial string Artist { get; set; }
[ObservableProperty]
- public partial ObservableCollection? FilesFound { get; set; }
+ public partial string? Album { get; set; }
+ ///
+ /// In milliseconds
+ ///
[ObservableProperty]
- public partial bool IsLyricsExisted { get; set; } = false;
+ public partial double? DurationMs { get; set; }
[ObservableProperty]
public partial string? SourceAppUserModelId { get; set; } = null;
- [ObservableProperty]
- public partial List? LyricsLines { get; set; } = null;
public byte[]? AlbumArt { get; set; } = null;
- [ObservableProperty]
- public partial List? CoverImageDominantColors { get; set; } = null;
-
public SongInfo() { }
-
- ///
- /// Try to parse lyrics from the track, optionally override the raw lyrics string.
- ///
- ///
- ///
- public void ParseLyrics(Track track, string? overrideRaw = null)
- {
- List? result = null;
-
- if (overrideRaw != null)
- track.Lyrics.ParseLRC(overrideRaw);
-
- var lyricsPhrases = track.Lyrics.SynchronizedLyrics;
-
- if (lyricsPhrases?.Count > 0)
- {
- if (lyricsPhrases[0].TimestampMs > 0)
- {
- var placeholder = new LyricsPhrase(0, $"{track.Artist} - {track.Title}");
- lyricsPhrases.Insert(0, placeholder);
- lyricsPhrases.Insert(0, placeholder);
- }
- }
-
- LyricsLine? lyricsLine = null;
-
- for (int i = 0; i < lyricsPhrases?.Count; i++)
- {
- var lyricsPhrase = lyricsPhrases[i];
- int startTimestampMs = lyricsPhrase.TimestampMs;
- int endTimestampMs;
-
- if (i + 1 < lyricsPhrases.Count)
- {
- endTimestampMs = lyricsPhrases[i + 1].TimestampMs;
- }
- else
- {
- endTimestampMs = (int)track.DurationMs;
- }
-
- lyricsLine ??= new LyricsLine { StartPlayingTimestampMs = startTimestampMs };
-
- lyricsLine.Texts.Add(lyricsPhrase.Text);
-
- if (endTimestampMs == startTimestampMs)
- {
- continue;
- }
- else
- {
- lyricsLine.EndPlayingTimestampMs = endTimestampMs;
- result ??= [];
- result.Add(lyricsLine);
- lyricsLine = null;
- }
- }
-
- if (result != null && result.Count == 0)
- {
- result = null;
- }
-
- LyricsLines = result;
- IsLyricsExisted = result != null;
- }
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml
index 4921722..d99aaa8 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml
@@ -14,7 +14,6 @@
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Loaded="LyricsCanvas_Loaded"
- Paused="{x:Bind ViewModel.IsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
Update="LyricsCanvas_Update" />
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs
index 11276a5..2658b6e 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs
@@ -32,7 +32,7 @@ namespace BetterLyrics.WinUI3.Renderer
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args
)
{
- ViewModel.Calculate(sender, args);
+ ViewModel.Update(sender, args);
}
private void LyricsCanvas_Loaded(object sender, RoutedEventArgs e)
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Serialization/SourceGenerationContext.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Serialization/SourceGenerationContext.cs
new file mode 100644
index 0000000..57536a6
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Serialization/SourceGenerationContext.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using BetterLyrics.WinUI3.Models;
+
+namespace BetterLyrics.WinUI3.Serialization
+{
+ [JsonSerializable(typeof(List))]
+ [JsonSerializable(typeof(List))]
+ [JsonSerializable(typeof(List))]
+ [JsonSerializable(typeof(JsonElement))]
+ [JsonSourceGenerationOptions(WriteIndented = true)]
+ internal partial class SourceGenerationContext : JsonSerializerContext { }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/DatabaseService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/DatabaseService.cs
deleted file mode 100644
index fa388fd..0000000
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/DatabaseService.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using ATL;
-using BetterLyrics.WinUI3.Helper;
-using BetterLyrics.WinUI3.Models;
-using SQLite;
-using Ude;
-using Windows.Media.Control;
-using Windows.Storage.Streams;
-
-namespace BetterLyrics.WinUI3.Services.Database
-{
- public class DatabaseService : IDatabaseService
- {
- private readonly SQLiteConnection _connection;
-
- private readonly CharsetDetector _charsetDetector = new();
-
- public DatabaseService()
- {
- _connection = new SQLiteConnection(AppInfo.DatabasePath);
- if (_connection.GetTableInfo("MetadataIndex").Count == 0)
- {
- _connection.CreateTable();
- }
- }
-
- public async Task RebuildDatabaseAsync(IList paths)
- {
- await Task.Run(() =>
- {
- _connection.DeleteAll();
-
- HashSet insertedPaths = new();
-
- foreach (var path in paths)
- {
- if (Directory.Exists(path))
- {
- foreach (
- var file in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
- )
- {
- if (!insertedPaths.Contains(file))
- {
- var track = new Track(file);
- _connection.Insert(
- new MetadataIndex
- {
- Path = file,
- Title = track.Title,
- Artist = track.Artist,
- }
- );
- insertedPaths.Add(file);
- }
- }
- }
- }
- });
- }
-
- public async Task FindSongInfoAsync(
- GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
- )
- {
- if (mediaProps == null || mediaProps.Title == null || mediaProps.Artist == null)
- return new();
-
- var songInfo = new SongInfo { Title = mediaProps?.Title, Artist = mediaProps?.Artist };
-
- // App.ResourceLoader!.GetString("MainPageNoLocalFilesMatched");
-
- if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
- {
- songInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
- }
-
- return await FindSongInfoAsync(songInfo, mediaProps!.Title, mediaProps!.Artist);
- }
-
- public async Task FindSongInfoAsync(
- SongInfo initSongInfo,
- string searchTitle,
- string searchArtist
- )
- {
- var founds = _connection
- .Table()
- // Look up by Title and Artist (these two props were fetched by reading metadata in music file befoe) first
- // then by Path (music file name usually contains song name and artist so this can be a second way to look up for)
- // Please note for .lrc file, only the second way works for it
- .Where(m =>
- (
- m.Title != null
- && m.Artist != null
- && m.Title.Contains(searchTitle)
- && m.Artist.Contains(searchArtist)
- )
- || (
- m.Path != null
- && m.Path.Contains(searchTitle)
- && m.Path.Contains(searchArtist)
- )
- )
- .ToList();
-
- foreach (var found in founds)
- {
- initSongInfo.FilesFound ??= [];
- initSongInfo.FilesFound.Add(found.Path!);
- if (initSongInfo.LyricsLines == null || initSongInfo.AlbumArt == null)
- {
- Track track = new(found.Path);
- initSongInfo.ParseLyrics(track);
- // Successfully parse lyrics info from metadata in music file
- if (initSongInfo.LyricsLines != null)
- {
- // Used as lyrics source
- }
- // Find lyrics file
- if (initSongInfo.LyricsLines == null && found?.Path?.EndsWith(".lrc") == true)
- {
- using (FileStream fs = File.OpenRead(found.Path))
- {
- _charsetDetector.Feed(fs);
- _charsetDetector.DataEnd();
- }
-
- string content;
- if (_charsetDetector.Charset != null)
- {
- Encoding encoding = Encoding.GetEncoding(_charsetDetector.Charset);
- content = File.ReadAllText(found.Path, encoding);
- }
- else
- {
- content = File.ReadAllText(found.Path, Encoding.UTF8);
- }
- initSongInfo.ParseLyrics(track, content);
- // Used as lyrics source
- }
-
- // Finf album art
- if (initSongInfo.AlbumArt == null)
- {
- if (track.EmbeddedPictures.Count > 0)
- {
- initSongInfo.AlbumArt = track.EmbeddedPictures[0].PictureData;
- // Used as album art source
- }
- }
- }
- else
- break;
- }
-
- if (initSongInfo.AlbumArt == null)
- {
- initSongInfo.CoverImageDominantColors = null;
- }
- else
- {
- initSongInfo.CoverImageDominantColors = await ImageHelper.GetAccentColorsFromByte(
- initSongInfo.AlbumArt
- );
- }
-
- if (initSongInfo.LyricsLines == null) { }
-
- return initSongInfo;
- }
- }
-}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/IDatabaseService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/IDatabaseService.cs
deleted file mode 100644
index 91355d4..0000000
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Database/IDatabaseService.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using BetterLyrics.WinUI3.Models;
-using Windows.Media.Control;
-
-namespace BetterLyrics.WinUI3.Services.Database
-{
- public interface IDatabaseService
- {
- Task RebuildDatabaseAsync(IList paths);
-
- Task FindSongInfoAsync(
- GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
- );
-
- Task FindSongInfoAsync(
- SongInfo initSongInfo,
- string searchTitle,
- string searchArtist
- );
- }
-}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ILibWatcherService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ILibWatcherService.cs
new file mode 100644
index 0000000..0457e9b
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ILibWatcherService.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BetterLyrics.WinUI3.Events;
+using BetterLyrics.WinUI3.Models;
+
+namespace BetterLyrics.WinUI3.Services
+{
+ public interface ILibWatcherService
+ {
+ event EventHandler? MusicLibraryFilesChanged;
+ public void UpdateWatchers(List folders);
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IMusicSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IMusicSearchService.cs
new file mode 100644
index 0000000..84f5be3
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IMusicSearchService.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using BetterLyrics.WinUI3.Enums;
+
+namespace BetterLyrics.WinUI3.Services
+{
+ public interface IMusicSearchService
+ {
+ Task<(string?, LyricsFormat?)> SearchLyricsAsync(
+ string title,
+ string artist,
+ string album = "",
+ double durationMs = 0.0,
+ MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
+ );
+
+ byte[]? SearchAlbumArtAsync(string title, string artist);
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/IPlaybackService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs
similarity index 84%
rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/IPlaybackService.cs
rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs
index 658ec2c..9dbb1b6 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/IPlaybackService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs
@@ -2,7 +2,7 @@
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
-namespace BetterLyrics.WinUI3.Services.Playback
+namespace BetterLyrics.WinUI3.Services
{
public interface IPlaybackService
{
@@ -10,7 +10,6 @@ namespace BetterLyrics.WinUI3.Services.Playback
event EventHandler? IsPlayingChanged;
event EventHandler? PositionChanged;
- void ReSendingMessages();
SongInfo? SongInfo { get; }
bool IsPlaying { get; }
TimeSpan Position { get; }
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/ISettingsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs
similarity index 86%
rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/ISettingsService.cs
rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs
index fd3e979..0e8da2a 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/ISettingsService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs
@@ -1,17 +1,19 @@
using System.Collections.Generic;
using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Windows.UI.Text;
-namespace BetterLyrics.WinUI3.Services.Settings
+namespace BetterLyrics.WinUI3.Services
{
public interface ISettingsService
{
bool IsFirstRun { get; set; }
// Lyrics lib
- List MusicLibraries { get; set; }
+ List LocalLyricsFolders { get; set; }
+ List LyricsSearchProvidersInfo { get; set; }
// App appearance
ElementTheme ThemeType { get; set; }
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LibWatcherService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LibWatcherService.cs
new file mode 100644
index 0000000..3891d06
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LibWatcherService.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BetterLyrics.WinUI3.Services
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using global::BetterLyrics.WinUI3.Events;
+ using global::BetterLyrics.WinUI3.Models;
+
+ namespace BetterLyrics.WinUI3.Services
+ {
+ public class LibWatcherService : IDisposable, ILibWatcherService
+ {
+ private readonly ISettingsService _settingsService;
+ private readonly Dictionary _watchers = [];
+
+ public event EventHandler? MusicLibraryFilesChanged;
+
+ public LibWatcherService(ISettingsService settingsService)
+ {
+ _settingsService = settingsService;
+ UpdateWatchers(_settingsService.LocalLyricsFolders);
+ }
+
+ public void UpdateWatchers(List folders)
+ {
+ // 移除不再监听的
+ foreach (var key in _watchers.Keys.ToList())
+ {
+ if (!folders.Any(x => x.Path == key && x.IsEnabled))
+ {
+ _watchers[key].Dispose();
+ _watchers.Remove(key);
+ }
+ }
+
+ // 添加新的监听
+ foreach (var folder in folders)
+ {
+ if (
+ !_watchers.ContainsKey(folder.Path)
+ && Directory.Exists(folder.Path)
+ && folder.IsEnabled
+ )
+ {
+ var watcher = new FileSystemWatcher(folder.Path)
+ {
+ IncludeSubdirectories = true,
+ EnableRaisingEvents = true,
+ };
+ watcher.Created += (s, e) => OnChanged(folder.Path, e);
+ watcher.Changed += (s, e) => OnChanged(folder.Path, e);
+ watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
+ watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
+ _watchers[folder.Path] = watcher;
+ }
+ }
+ }
+
+ private void OnChanged(string folder, FileSystemEventArgs e)
+ {
+ App.DispatcherQueue!.TryEnqueue(
+ Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
+ () =>
+ {
+ MusicLibraryFilesChanged?.Invoke(
+ this,
+ new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
+ );
+ }
+ );
+ }
+
+ public void Dispose()
+ {
+ foreach (var watcher in _watchers.Values)
+ {
+ watcher.Dispose();
+ }
+ _watchers.Clear();
+ }
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs
new file mode 100644
index 0000000..c2707b3
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs
@@ -0,0 +1,354 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using ATL;
+using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Helper;
+using Windows.Storage;
+using Windows.Storage.FileProperties;
+
+namespace BetterLyrics.WinUI3.Services
+{
+ public class MusicSearchService : IMusicSearchService
+ {
+ private readonly HttpClient _httpClient;
+ private readonly ISettingsService _settingsService;
+
+ public MusicSearchService(ISettingsService settingsService)
+ {
+ _settingsService = settingsService;
+ _httpClient = new HttpClient();
+ _httpClient.DefaultRequestHeaders.Add(
+ "User-Agent",
+ $"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
+ );
+ }
+
+ public byte[]? SearchAlbumArtAsync(string title, string artist)
+ {
+ foreach (var folder in _settingsService.LocalLyricsFolders)
+ {
+ if (Directory.Exists(folder.Path) && folder.IsEnabled)
+ {
+ foreach (
+ var file in Directory.GetFiles(
+ folder.Path,
+ $"*.*",
+ SearchOption.AllDirectories
+ )
+ )
+ {
+ if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
+ {
+ Track track = new(file);
+ var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
+ if (bytes != null)
+ {
+ return bytes;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public async Task<(string?, LyricsFormat?)> SearchLyricsAsync(
+ string title,
+ string artist,
+ string album = "",
+ double durationMs = 0.0,
+ MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
+ )
+ {
+ foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
+ {
+ if (!provider.IsEnabled)
+ {
+ continue;
+ }
+
+ switch (provider.Provider)
+ {
+ case LyricsSearchProvider.LrcLib:
+ // Check cache first
+ var cachedLyrics = ReadCache(title, artist, LyricsFormat.Lrc);
+ if (!string.IsNullOrWhiteSpace(cachedLyrics))
+ {
+ return (cachedLyrics, LyricsFormat.Lrc);
+ }
+ break;
+ default:
+ break;
+ }
+
+ string? searchedLyrics = null;
+
+ switch (provider.Provider)
+ {
+ case LyricsSearchProvider.LocalMusicFile:
+ searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist);
+ break;
+ case LyricsSearchProvider.LocalLrcFile:
+ searchedLyrics = await LocalLyricsSearchInLyricsFiles(
+ title,
+ artist,
+ LyricsFormat.Lrc
+ );
+ break;
+ case LyricsSearchProvider.LocalEslrcFile:
+ searchedLyrics = await LocalLyricsSearchInLyricsFiles(
+ title,
+ artist,
+ LyricsFormat.Eslrc
+ );
+ break;
+ case LyricsSearchProvider.LocalTtmlFile:
+ searchedLyrics = await LocalLyricsSearchInLyricsFiles(
+ title,
+ artist,
+ LyricsFormat.Ttml
+ );
+ break;
+ case LyricsSearchProvider.LrcLib:
+ searchedLyrics = await SearchLrcLib(
+ title,
+ artist,
+ album,
+ (int)(durationMs / 1000),
+ matchMode
+ );
+ break;
+ default:
+ break;
+ }
+
+ if (!string.IsNullOrWhiteSpace(searchedLyrics))
+ {
+ switch (provider.Provider)
+ {
+ case LyricsSearchProvider.LrcLib:
+ WriteCache(title, artist, searchedLyrics, LyricsFormat.Lrc);
+ return (searchedLyrics, LyricsFormat.Lrc);
+ case LyricsSearchProvider.LocalMusicFile:
+ return (searchedLyrics, LyricsFormatExtensions.Detect(searchedLyrics));
+ case LyricsSearchProvider.LocalLrcFile:
+ return (searchedLyrics, LyricsFormat.Lrc);
+ case LyricsSearchProvider.LocalEslrcFile:
+ return (searchedLyrics, LyricsFormat.Eslrc);
+ case LyricsSearchProvider.LocalTtmlFile:
+ return (searchedLyrics, LyricsFormat.Ttml);
+ default:
+ break;
+ }
+ }
+ }
+
+ return (null, null);
+ }
+
+ private static int LevenshteinDistance(string a, string b)
+ {
+ if (string.IsNullOrEmpty(a))
+ return b.Length;
+ if (string.IsNullOrEmpty(b))
+ return a.Length;
+ int[,] d = new int[a.Length + 1, b.Length + 1];
+ for (int i = 0; i <= a.Length; i++)
+ d[i, 0] = i;
+ for (int j = 0; j <= b.Length; j++)
+ d[0, j] = j;
+ for (int i = 1; i <= a.Length; i++)
+ for (int j = 1; j <= b.Length; j++)
+ d[i, j] = Math.Min(
+ Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
+ d[i - 1, j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1)
+ );
+ return d[a.Length, b.Length];
+ }
+
+ // 判断相似度
+ private static bool FuzzyMatch(string fileName, string title, string artist)
+ {
+ var normFile = Normalize(fileName);
+ var normTarget1 = Normalize(title + artist);
+ var normTarget2 = Normalize(artist + title);
+
+ int dist1 = LevenshteinDistance(normFile, normTarget1);
+ int dist2 = LevenshteinDistance(normFile, normTarget2);
+
+ return dist1 <= 3 || dist2 <= 3; // 阈值可调整
+ }
+
+ private static string Normalize(string s)
+ {
+ if (string.IsNullOrWhiteSpace(s))
+ return "";
+ var sb = new StringBuilder();
+ foreach (var c in s.ToLowerInvariant())
+ {
+ if (char.IsLetterOrDigit(c))
+ sb.Append(c);
+ }
+ return sb.ToString();
+ }
+
+ private string? LocalLyricsSearchInMusicFiles(string title, string artist)
+ {
+ foreach (var folder in _settingsService.LocalLyricsFolders)
+ {
+ if (Directory.Exists(folder.Path) && folder.IsEnabled)
+ {
+ foreach (
+ var file in Directory.GetFiles(
+ folder.Path,
+ $"*.*",
+ SearchOption.AllDirectories
+ )
+ )
+ {
+ if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
+ {
+ //Track track = new(file);
+ //var plain = track.Lyrics.UnsynchronizedLyrics;
+
+ try
+ {
+ var plain = TagLib.File.Create(file).Tag.Lyrics;
+ if (plain != null && plain != string.Empty)
+ {
+ return plain;
+ }
+ }
+ catch (Exception) { }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private async Task LocalLyricsSearchInLyricsFiles(
+ string title,
+ string artist,
+ LyricsFormat format
+ )
+ {
+ foreach (var folder in _settingsService.LocalLyricsFolders)
+ {
+ if (Directory.Exists(folder.Path) && folder.IsEnabled)
+ {
+ foreach (
+ var file in Directory.GetFiles(
+ folder.Path,
+ $"*{format.ToFileExtension()}",
+ SearchOption.AllDirectories
+ )
+ )
+ {
+ if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
+ {
+ string? raw = await File.ReadAllTextAsync(
+ file,
+ FileHelper.GetEncoding(file)
+ );
+ if (raw != null)
+ {
+ return raw;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private async Task SearchLrcLib(
+ string title,
+ string artist,
+ string album,
+ int duration,
+ MusicSearchMatchMode matchMode
+ )
+ {
+ // Build API query URL
+ var url =
+ $"https://lrclib.net/api/search?"
+ + $"track_name={Uri.EscapeDataString(title)}&"
+ + $"artist_name={Uri.EscapeDataString(artist)}";
+
+ if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration)
+ {
+ url +=
+ $"&album_name={Uri.EscapeDataString(album)}"
+ + $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
+ }
+
+ var response = await _httpClient.GetAsync(url);
+ if (!response.IsSuccessStatusCode)
+ return null;
+
+ var json = await response.Content.ReadAsStringAsync();
+
+ var jArr = JsonSerializer.Deserialize(
+ json,
+ Serialization.SourceGenerationContext.Default.JsonElement
+ );
+ if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
+ {
+ var first = jArr[0];
+ var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
+ var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
+ if (!string.IsNullOrWhiteSpace(result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ private void WriteCache(string title, string artist, string lyrics, LyricsFormat format)
+ {
+ var safeArtist = SanitizeFileName(artist);
+ var safeTitle = SanitizeFileName(title);
+ var cacheFilePath = Path.Combine(
+ AppInfo.OnlineLyricsCacheDirectory,
+ $"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
+ );
+ File.WriteAllText(cacheFilePath, lyrics);
+ }
+
+ private string? ReadCache(string title, string artist, LyricsFormat format)
+ {
+ var safeArtist = SanitizeFileName(artist);
+ var safeTitle = SanitizeFileName(title);
+ var cacheFilePath = Path.Combine(
+ AppInfo.OnlineLyricsCacheDirectory,
+ $"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
+ );
+ if (File.Exists(cacheFilePath))
+ {
+ return File.ReadAllText(cacheFilePath);
+ }
+ return null;
+ }
+
+ private static string SanitizeFileName(string fileName, char replacement = '_')
+ {
+ var invalidChars = Path.GetInvalidFileNameChars();
+ var sb = new StringBuilder(fileName.Length);
+ foreach (var c in fileName)
+ {
+ sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
+ }
+ return sb.ToString();
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/PlaybackService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs
similarity index 61%
rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/PlaybackService.cs
rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs
index 0b0252b..502ca5c 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Playback/PlaybackService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs
@@ -1,15 +1,20 @@
using System;
-using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
using System.Threading.Tasks;
+using ATL;
+using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
-using BetterLyrics.WinUI3.Services.Database;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
+using Windows.ApplicationModel;
using Windows.Media.Control;
+using Windows.Storage.Streams;
-namespace BetterLyrics.WinUI3.Services.Playback
+namespace BetterLyrics.WinUI3.Services
{
public partial class PlaybackService : IPlaybackService
{
@@ -26,23 +31,17 @@ namespace BetterLyrics.WinUI3.Services.Playback
public bool IsPlaying { get; private set; }
public TimeSpan Position { get; private set; }
- private readonly IDatabaseService _databaseService;
+ private readonly IMusicSearchService _musicSearchService;
- public PlaybackService(IDatabaseService databaseService)
+ public PlaybackService(
+ ISettingsService settingsService,
+ IMusicSearchService musicSearchService
+ )
{
- _databaseService = databaseService;
+ _musicSearchService = musicSearchService;
InitMediaManager().ConfigureAwait(true);
}
- private async Task GetSongInfoAsync()
- {
- var songInfo = await _databaseService.FindSongInfoAsync(
- await _currentSession?.TryGetMediaPropertiesAsync()
- );
- songInfo.SourceAppUserModelId = _currentSession?.SourceAppUserModelId;
- return songInfo;
- }
-
private async Task InitMediaManager()
{
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
@@ -51,14 +50,6 @@ namespace BetterLyrics.WinUI3.Services.Playback
SessionManager_CurrentSessionChanged(_sessionManager, null);
}
- public void ReSendingMessages()
- {
- // Re-send messages to update UI
- CurrentSession_MediaPropertiesChanged(_currentSession, null);
- CurrentSession_PlaybackInfoChanged(_currentSession, null);
- CurrentSession_TimelinePropertiesChanged(_currentSession, null);
- }
-
///
/// Note: Non-UI thread
///
@@ -94,10 +85,13 @@ namespace BetterLyrics.WinUI3.Services.Playback
break;
}
}
- _dispatcherQueue.TryEnqueue(() =>
- {
- IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
- });
+ _dispatcherQueue.TryEnqueue(
+ DispatcherQueuePriority.High,
+ () =>
+ {
+ IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
+ }
+ );
}
private void SessionManager_CurrentSessionChanged(
@@ -126,7 +120,9 @@ namespace BetterLyrics.WinUI3.Services.Playback
CurrentSession_TimelinePropertiesChanged;
}
- ReSendingMessages();
+ CurrentSession_MediaPropertiesChanged(_currentSession, null);
+ CurrentSession_PlaybackInfoChanged(_currentSession, null);
+ CurrentSession_TimelinePropertiesChanged(_currentSession, null);
}
///
@@ -134,31 +130,78 @@ namespace BetterLyrics.WinUI3.Services.Playback
///
///
///
- private void CurrentSession_MediaPropertiesChanged(
+ private async void CurrentSession_MediaPropertiesChanged(
GlobalSystemMediaTransportControlsSession? sender,
MediaPropertiesChangedEventArgs? args
)
{
- App.DispatcherQueueTimer!.Debounce(
- async () =>
+ GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
+ if (sender == null)
+ {
+ SongInfo = null;
+ }
+ else
+ {
+ try
{
- // _logger.LogDebug("CurrentSession_MediaPropertiesChanged");
- if (sender == null)
- SongInfo = null;
+ mediaProps = await sender.TryGetMediaPropertiesAsync();
+ }
+ catch (Exception) { }
+
+ if (mediaProps == null)
+ {
+ SongInfo = null;
+ }
+ else
+ {
+ SongInfo = new SongInfo
+ {
+ Title = mediaProps.Title,
+ Artist = mediaProps.Artist,
+ Album = mediaProps?.AlbumTitle ?? string.Empty,
+ DurationMs = _currentSession
+ ?.GetTimelineProperties()
+ .EndTime.TotalMilliseconds,
+ SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
+ };
+
+ if (
+ SongInfo.SourceAppUserModelId?.Contains(Package.Current.Id.FamilyName)
+ ?? false
+ )
+ {
+ SongInfo.Title = "甜度爆表";
+ SongInfo.Artist = "AI";
+ }
+
+ if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
+ {
+ SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
+ }
else
{
- try
+ SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
+ SongInfo.Title,
+ SongInfo.Artist
+ );
+
+ if (SongInfo.AlbumArt == null)
{
- SongInfo = await GetSongInfoAsync();
+ SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync(
+ $"{SongInfo.Artist} - {SongInfo.Title}",
+ 400,
+ 400
+ );
}
- catch (Exception) { }
}
- _dispatcherQueue.TryEnqueue(() =>
- {
- SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
- });
- },
- TimeSpan.FromMilliseconds(AnimationHelper.DebounceDefaultDuration)
+ }
+ }
+ _dispatcherQueue.TryEnqueue(
+ DispatcherQueuePriority.High,
+ () =>
+ {
+ SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
+ }
);
}
@@ -175,10 +218,13 @@ namespace BetterLyrics.WinUI3.Services.Playback
{
Position = sender.GetTimelineProperties().Position;
}
- _dispatcherQueue.TryEnqueue(() =>
- {
- PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
- });
+ _dispatcherQueue.TryEnqueue(
+ DispatcherQueuePriority.High,
+ () =>
+ {
+ PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
+ }
+ );
// _logger.LogDebug(_currentTime);
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/SettingsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs
similarity index 74%
rename from BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/SettingsService.cs
rename to BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs
index 992545e..acdf305 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/Settings/SettingsService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs
@@ -1,15 +1,13 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BetterLyrics.WinUI3.Enums;
-using CommunityToolkit.Mvvm.ComponentModel;
+using BetterLyrics.WinUI3.Models;
+using BetterLyrics.WinUI3.Serialization;
using Microsoft.UI.Xaml;
-using Newtonsoft.Json;
-using Windows.Media;
using Windows.Storage;
-namespace BetterLyrics.WinUI3.Services.Settings
+namespace BetterLyrics.WinUI3.Services
{
public class SettingsService : ISettingsService
{
@@ -17,10 +15,13 @@ namespace BetterLyrics.WinUI3.Services.Settings
private const string IsFirstRunKey = "IsFirstRun";
+ // Lyrics lib
+ private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
+ private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
+
// App appearance
private const string ThemeTypeKey = "ThemeType";
private const string LanguageKey = "Language";
- private const string MusicLibrariesKey = "MusicLibraries";
private const string BackdropTypeKey = "BackdropType";
// App behavior
@@ -49,6 +50,39 @@ namespace BetterLyrics.WinUI3.Services.Settings
get => GetValue(IsFirstRunKey);
set => SetValue(IsFirstRunKey, value);
}
+ public List LocalLyricsFolders
+ {
+ get =>
+ System.Text.Json.JsonSerializer.Deserialize(
+ GetValue(LocalLyricsFoldersKey) ?? "[]",
+ SourceGenerationContext.Default.ListLocalLyricsFolder
+ )!;
+ set =>
+ SetValue(
+ LocalLyricsFoldersKey,
+ System.Text.Json.JsonSerializer.Serialize(
+ value,
+ SourceGenerationContext.Default.ListLocalLyricsFolder
+ )
+ );
+ }
+
+ public List LyricsSearchProvidersInfo
+ {
+ get =>
+ System.Text.Json.JsonSerializer.Deserialize(
+ GetValue(LyricsSearchProvidersInfoKey) ?? "[]",
+ SourceGenerationContext.Default.ListLyricsSearchProviderInfo
+ )!;
+ set =>
+ SetValue(
+ LyricsSearchProvidersInfoKey,
+ System.Text.Json.JsonSerializer.Serialize(
+ value,
+ SourceGenerationContext.Default.ListLyricsSearchProviderInfo
+ )
+ );
+ }
public ElementTheme ThemeType
{
@@ -74,15 +108,6 @@ namespace BetterLyrics.WinUI3.Services.Settings
set => SetValue(AutoStartWindowTypeKey, (int)value);
}
- public List MusicLibraries
- {
- get =>
- JsonConvert.DeserializeObject>(
- GetValue(MusicLibrariesKey) ?? "[]"
- )!;
- set => SetValue(MusicLibrariesKey, JsonConvert.SerializeObject(value));
- }
-
public bool IsCoverOverlayEnabled
{
get => GetValue(IsCoverOverlayEnabledKey);
@@ -178,17 +203,39 @@ namespace BetterLyrics.WinUI3.Services.Settings
_localSettings = ApplicationData.Current.LocalSettings;
SetDefault(IsFirstRunKey, true);
+ // Lyrics lib
+ SetDefault(LocalLyricsFoldersKey, "[]");
+ SetDefault(
+ LyricsSearchProvidersInfoKey,
+ System.Text.Json.JsonSerializer.Serialize(
+ Enum.GetValues()
+ .Select(p => new LyricsSearchProviderInfo(p, true))
+ .ToList(),
+ SourceGenerationContext.Default.ListLyricsSearchProviderInfo
+ )
+ );
+ if (LyricsSearchProvidersInfo.Count != Enum.GetValues().Length)
+ {
+ LyricsSearchProvidersInfo = Enum.GetValues()
+ .Select(p => new LyricsSearchProviderInfo(
+ p,
+ LyricsSearchProvidersInfo
+ .Where(x => x.Provider == p)
+ .FirstOrDefault()
+ ?.IsEnabled ?? true
+ ))
+ .ToList();
+ }
// App appearance
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
SetDefault(LanguageKey, (int)Language.FollowSystem);
- SetDefault(MusicLibrariesKey, "[]");
SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic);
// App behavior
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
// Album art
SetDefault(IsCoverOverlayEnabledKey, true);
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
- SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.1
+ SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
SetDefault(CoverOverlayBlurAmountKey, 200);
SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact);
SetDefault(CoverImageRadiusKey, 24); // 24 %
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw
index 404a7b5..f3f2fa6 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw
@@ -121,10 +121,7 @@
Local music libraries
- Add folders storing music or lyrics to build lyrics index database
-
-
- Open in file explorer
+ Add folders storing music or lyrics
Open in file explorer
@@ -294,18 +291,12 @@
Glow effect scope
-
- Rebuild lyrics index database
-
-
- Rebuild
+
+ Configure lyrics search providers
Add
-
- Rebuilding the database, please wait...
-
Welcome to BetterLyrics
@@ -468,4 +459,31 @@
Settings
+
+ Loading lyrics...
+
+
+ Local .LRC files
+
+
+ Local music files
+
+
+ LRCLIB
+
+
+ 日本語
+
+
+ 한국어
+
+
+ Local .ESLRC files
+
+
+ Local .TTML files
+
+
+ This folder contains added folders, please delete these folders to add the folder
+
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw
new file mode 100644
index 0000000..97251c5
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw
@@ -0,0 +1,489 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 地元の音楽図書館
+
+
+ 音楽や歌詞を保存するフォルダーを追加します
+
+
+ ファイルエクスプローラーで開きます
+
+
+ アプリから削除します
+
+
+ 次のアイテムを削除しても安全です
+
+
+ このパスの元のファイルとフォルダーは、このアプリから削除するときに削除されません
+
+
+ フォルダーを追加します
+
+
+ テーマ
+
+
+ 言語
+
+
+ システムをフォローします
+
+
+ ライト
+
+
+ 暗い
+
+
+ 简体中文
+
+
+ 繁體中文
+
+
+ English
+
+
+ これはオープンソースアプリです
+
+
+ 新しいウィンドウで開きます
+
+
+ GitHubのソースコードを参照してください
+
+
+ バージョン
+
+
+ なし
+
+
+ 雲母
+
+
+ マイカル
+
+
+ デスクトップアクリル
+
+
+ アクリルベース
+
+
+ アクリル薄い
+
+
+ 透明
+
+
+ 背景
+
+
+ デフォルト
+
+
+ 変更を適用するためのアプリを再起動します
+
+
+ パスはコンピューターでは見つかりません
+
+
+ フォルダーが追加されました。二度と追加しないでください。
+
+
+ オーバーレイアルバムアートの背景
+
+
+ ダイナミックアルバムアートの背景
+
+
+ アルバムアートの背景不透明
+
+
+ 設定 - BetterLyrics
+
+
+ BetterLyrics
+
+
+ アライメント
+
+
+ 中心
+
+
+ 左
+
+
+ 右
+
+
+ アルバムアートバックグラウンドブラー量
+
+
+ ぼやけの量
+
+
+ この値を調整すると、アルバム画像のバックグラウンドブラー強度も増加します。
+
+
+ 現在の値:
+
+
+ ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0)
+
+
+ この機能を有効にすると、GPUの使用率がわずかに増加します
+
+
+ 上端と下端の不透明度
+
+
+ ライン間隔
+
+
+ x線の高さ
+
+
+ フォントサイズ
+
+
+ 歌詞のみ
+
+
+ 没入モード
+
+
+ アルバムの背景
+
+
+ について
+
+
+ 歌詞ライブラリ
+
+
+ アプリの外観
+
+
+ グロー効果
+
+
+ グローエフェクトスコープ
+
+
+ 歌詞検索プロバイダーを構成します
+
+
+ 追加
+
+
+ BetterLyrics へようこそ
+
+
+ 今すぐ歌詞データベースをセットアップしましょう
+
+
+ 今は音楽が再生されていません
+
+
+ 開発者オプション
+
+
+ テスト音楽を再生します
+
+
+ システムプレーヤーを使用して再生します
+
+
+ ログ
+
+
+ フォントカラー
+
+
+ デフォルト
+
+
+ アルバムアートアクセントカラー
+
+
+ アルバムアートスタイル
+
+
+ コーナー半径
+
+
+ タイトルバーサイズ
+
+
+ コンパクト
+
+
+ 拡張
+
+
+ 常にトップ
+
+
+ 全画面表示
+
+
+ ESCを押して、フルスクリーンモードを終了します
+
+
+ 再びホバリングして、トグルボタンを表示します
+
+
+ このメッセージを二度と見せないでください
+
+
+ 一致するローカルファイルはありません
+
+
+ アルバムアートのみ
+
+
+ 分割ビュー
+
+
+ 表示タイプを変更します
+
+
+ デスクトップ歌詞モードに切り替えます
+
+
+ ピクチャーインピクチャーモード
+
+
+ ピクチャーインピクチャーモードを終了します
+
+
+ 歌詞が見つかりません
+
+
+ 歌詞効果
+
+
+ 歌詞スタイル
+
+
+ このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません
+
+
+ アプリの動作
+
+
+ アプリを起動するとき
+
+
+ 標準モードをアクティブにします
+
+
+ ドックモードをアクティブにします
+
+
+ 歌詞が見つかりません
+
+
+ システムトレイ - BetterLyrics
+
+
+ ドックモード
+
+
+ フォント重量
+
+
+ 薄い
+
+
+ 余分な光
+
+
+ ライト
+
+
+ 半光
+
+
+ 普通
+
+
+ 中くらい
+
+
+ セミボールド
+
+
+ 大胆な
+
+
+ 余分な太字
+
+
+ 黒
+
+
+ 余分な黒
+
+
+ 歌詞全体
+
+
+ 現在の行
+
+
+ 現在の文字
+
+
+ 設定
+
+
+ 歌詞の読み込み...
+
+
+ ローカル.LRCファイル
+
+
+ ローカル音楽ファイル
+
+
+ LRCLIB
+
+
+ 日本語
+
+
+ 한국어
+
+
+ ローカル.ESLRCファイル
+
+
+ ローカル.TTMLファイル
+
+
+ このフォルダーには追加されたフォルダーが含まれています。これらのフォルダを削除してフォルダーを追加してください
+
+
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw
new file mode 100644
index 0000000..bb6a7b6
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw
@@ -0,0 +1,489 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 로컬 음악 도서관
+
+
+ 음악이나 가사를 저장하는 폴더 추가
+
+
+ 파일 탐색기에서 열립니다
+
+
+ 앱에서 제거하십시오
+
+
+ 다음 항목을 제거하는 것이 안전합니다
+
+
+ 이 경로의 원본 파일과 폴더는이 앱에서 제거 할 때 삭제되지 않습니다.
+
+
+ 폴더를 추가하십시오
+
+
+ 주제
+
+
+ 언어
+
+
+ 시스템을 따르십시오
+
+
+ 빛
+
+
+ 어두운
+
+
+ 简体中文
+
+
+ 繁體中文
+
+
+ English
+
+
+ 이것은 오픈 소스 앱입니다
+
+
+ 새 창에서 열립니다
+
+
+ GitHub의 소스 코드를 참조하십시오
+
+
+ 버전
+
+
+ 없음
+
+
+ 운모
+
+
+ 운모 대체
+
+
+ 데스크탑 아크릴
+
+
+ 아크릴베이스
+
+
+ 아크릴 얇은
+
+
+ 투명한
+
+
+ 배경
+
+
+ 기본
+
+
+ 변경 사항을 적용하려면 앱을 다시 시작하십시오
+
+
+ 경로는 컴퓨터에서 찾을 수 없습니다
+
+
+ 폴더가 추가되었습니다. 다시 추가하지 마십시오.
+
+
+ 오버레이 앨범 아트 배경
+
+
+ 동적 앨범 아트 배경
+
+
+ 앨범 아트 배경 불투명도
+
+
+ 설정 - BetterLyrics
+
+
+ BetterLyrics
+
+
+ 조정
+
+
+ 센터
+
+
+ 왼쪽
+
+
+ 오른쪽
+
+
+ 앨범 아트 배경 흐림 금액
+
+
+ 흐림 금액
+
+
+ 이 값을 조정하면 앨범 이미지의 배경 흐림 강도가 증가합니다.
+
+
+ 현재 가치 :
+
+
+ Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0)
+
+
+ 이 기능을 활성화하면 GPU 사용률이 약간 증가합니다
+
+
+ 상단 및 하단 가장자리 불투명도
+
+
+ 라인 간격
+
+
+ X 라인 높이
+
+
+ 글꼴 크기
+
+
+ 가사 만
+
+
+ 몰입 형 모드
+
+
+ 앨범 배경
+
+
+ 에 대한
+
+
+ 가사 도서관
+
+
+ 앱 모양
+
+
+ 글로우 효과
+
+
+ 글로우 효과 범위
+
+
+ 가사 검색 제공 업체를 구성하십시오
+
+
+ 추가하다
+
+
+ Betterlyrics에 오신 것을 환영합니다
+
+
+ 지금 가사 데이터베이스를 설정합시다
+
+
+ 지금 음악이 재생되지 않습니다
+
+
+ 개발자 옵션
+
+
+ 테스트 음악을 재생하십시오
+
+
+ 시스템 플레이어를 사용하여 재생하십시오
+
+
+ 통나무
+
+
+ 글꼴 색상
+
+
+ 기본
+
+
+ 앨범 아트 악센트 색상
+
+
+ 앨범 아트 스타일
+
+
+ 코너 반경
+
+
+ 제목 바 크기
+
+
+ 콤팩트
+
+
+ 펼친
+
+
+ 항상 위에
+
+
+ 전체 화면
+
+
+ ESC를 눌러 전체 화면 모드를 종료하십시오
+
+
+ 토글 버튼을 표시하려면 다시 다시 가져옵니다
+
+
+ 이 메시지를 다시 표시하지 마십시오
+
+
+ 로컬 파일이 일치하지 않습니다
+
+
+ 앨범 아트 만
+
+
+ 분할보기
+
+
+ 디스플레이 유형을 변경하십시오
+
+
+ 데스크탑 가사 모드로 전환하십시오
+
+
+ 사진 인당 모드
+
+
+ Picture-in-Picture 모드 종료
+
+
+ 가사를 찾을 수 없습니다
+
+
+ 가사 효과
+
+
+ 가사 스타일
+
+
+ 이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.
+
+
+ 앱 동작
+
+
+ 앱을 시작할 때
+
+
+ 표준 모드를 활성화합니다
+
+
+ 도크 모드를 활성화하십시오
+
+
+ 가사를 찾을 수 없습니다
+
+
+ 시스템 트레이 - BetterLyrics
+
+
+ 도크 모드
+
+
+ 글꼴 무게
+
+
+ 얇은
+
+
+ 여분의 빛
+
+
+ 빛
+
+
+ 반 빛
+
+
+ 정상
+
+
+ 중간
+
+
+ 반 대담한
+
+
+ 용감한
+
+
+ 추가 대담한
+
+
+ 검은색
+
+
+ 여분의 검은 색
+
+
+ 전체 가사
+
+
+ 현재 라인
+
+
+ 현재 숯
+
+
+ 설정
+
+
+ 가사로드 ...
+
+
+ 로컬 .LRC 파일
+
+
+ 로컬 음악 파일
+
+
+ LRCLIB
+
+
+ 日本語
+
+
+ 한국어
+
+
+ 로컬 .ESLRC 파일
+
+
+ 로컬 .TTML 파일
+
+
+ 이 폴더에는 추가 된 폴더가 포함되어 있습니다. 폴더를 추가하려면이 폴더를 삭제하십시오.
+
+
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw
index 0c0fcd9..fc94a83 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw
@@ -121,10 +121,7 @@
本地音乐媒体库
- 添加存放音乐或歌词的文件夹以构建歌词索引数据库
-
-
- 在文件资源管理器中打开
+ 添加存放音乐或歌词的文件夹
在文件资源管理器中打开
@@ -294,18 +291,12 @@
辉光效果作用范围
-
- 重构歌词索引数据库
-
-
- 重构
+
+ 配置歌词搜索服务
添加
-
- 重构数据库中,请稍候...
-
欢迎使用 BetterLyrics
@@ -468,4 +459,31 @@
设置
+
+ 加载歌词中...
+
+
+ 本地 .LRC 文件
+
+
+ 本地音乐文件
+
+
+ LRCLIB
+
+
+ 日本語
+
+
+ 한국어
+
+
+ 本地 .ESLRC 文件
+
+
+ 本地 .TTML 文件
+
+
+ 该文件夹包含已添加文件夹,请删除这些文件夹以添加该文件夹
+
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw
index 7bf3dbf..fd74f82 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw
@@ -121,10 +121,7 @@
本地音樂媒體庫
- 新增存放音樂或歌詞的資料夾以建立歌詞索引資料庫
-
-
- 在檔案總管中開啟
+ 新增存放音樂或歌詞的資料夾
在檔案總管中開啟
@@ -294,18 +291,12 @@
輝光效果作用範圍
-
- 重構歌詞索引資料庫
-
-
- 重構
+
+ 配置歌詞搜尋服務
添加
-
- 重構資料庫中,請稍候...
-
歡迎使用 BetterLyrics
@@ -468,4 +459,31 @@
設定
+
+ 載入歌詞中...
+
+
+ 本地 .lRC 文件
+
+
+ 本地音樂文件
+
+
+ LRCLIB
+
+
+ 日本語
+
+
+ 한국어
+
+
+ 本地 .ESLRC 文件
+
+
+ 本地 .TTML 文件
+
+
+ 該文件夾包含已添加文件夾,請刪除這些文件夾以添加該文件夾
+
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseRendererViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseRendererViewModel.cs
deleted file mode 100644
index 0c61d1b..0000000
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseRendererViewModel.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using BetterLyrics.WinUI3.Services.Settings;
-using BetterLyrics.WinUI3.ViewModels;
-using Microsoft.Graphics.Canvas.UI.Xaml;
-
-namespace BetterLyrics.WinUI3.Rendering
-{
- public partial class BaseRendererViewModel(ISettingsService settingsService)
- : BaseViewModel(settingsService)
- {
- public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
- public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
-
- public virtual void Calculate(
- ICanvasAnimatedControl control,
- CanvasAnimatedUpdateEventArgs args
- )
- {
- TotalTime += args.Timing.ElapsedTime;
- ElapsedTime = args.Timing.ElapsedTime;
- }
- }
-}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs
index 093f299..b8f1332 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/BaseViewModel.cs
@@ -1,6 +1,6 @@
using System;
using System.Runtime.CompilerServices;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs
index e5c7d88..c8faa18 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/HostWindowViewModel.cs
@@ -5,13 +5,12 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
-using H.NotifyIcon.Interop;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs
index 9f48f45..7e8e950 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
@@ -6,8 +7,7 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
-using BetterLyrics.WinUI3.Services.Playback;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
@@ -21,7 +21,8 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial class LyricsPageViewModel
: BaseViewModel,
IRecipient>,
- IRecipient>
+ IRecipient>,
+ IRecipient>
{
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToDockMode;
@@ -40,10 +41,16 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; } = null;
+ [ObservableProperty]
+ public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
+
[ObservableProperty]
public partial LyricsDisplayType? PreferredDisplayType { get; set; } =
LyricsDisplayType.SplitView;
+ [ObservableProperty]
+ public partial int LyricsFontSize { get; set; }
+
[ObservableProperty]
public partial bool AboutToUpdateUI { get; set; }
@@ -73,6 +80,7 @@ namespace BetterLyrics.WinUI3.ViewModels
)
: base(settingsService)
{
+ LyricsFontSize = _settingsService.LyricsFontSize;
CoverImageRadius = _settingsService.CoverImageRadius;
_playbackService = playbackService;
@@ -173,13 +181,20 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage message)
{
- if (message.Sender.GetType() == typeof(SettingsViewModel))
+ if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
}
+ if (message.Sender is LyricsSettingsControlViewModel)
+ {
+ if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
+ {
+ LyricsFontSize = message.NewValue;
+ }
+ }
}
public void Receive(PropertyChangedMessage message)
@@ -201,17 +216,15 @@ namespace BetterLyrics.WinUI3.ViewModels
TrySwitchToPreferredDisplayType(SongInfo);
}
}
- else if (message.Sender is SettingsViewModel)
+ }
+
+ public void Receive(PropertyChangedMessage message)
+ {
+ if (message.Sender is LyricsRendererViewModel)
{
- if (
- message.PropertyName
- == nameof(SettingsViewModel.IsRebuildingLyricsIndexDatabase)
- )
+ if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsStatus))
{
- if (!message.NewValue)
- {
- _playbackService.ReSendingMessages();
- }
+ LyricsStatus = message.NewValue;
}
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs
index 0b54bd8..44db46d 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs
@@ -1,20 +1,16 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Diagnostics;
-using System.IO;
using System.Linq;
using System.Numerics;
-using System.Text;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
-using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
-using BetterLyrics.WinUI3.Rendering;
-using BetterLyrics.WinUI3.Services.Playback;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
@@ -24,11 +20,7 @@ using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
-using Microsoft.UI.Dispatching;
-using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Shapes;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.UI;
@@ -36,7 +28,7 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
- : BaseRendererViewModel,
+ : BaseViewModel,
IRecipient>,
IRecipient>,
IRecipient>,
@@ -47,7 +39,9 @@ namespace BetterLyrics.WinUI3.ViewModels
IRecipient>,
IRecipient>,
IRecipient>,
- IRecipient>
+ IRecipient>,
+ IRecipient>>,
+ IRecipient>>
{
private protected CanvasTextFormat _textFormat = new()
{
@@ -55,38 +49,27 @@ namespace BetterLyrics.WinUI3.ViewModels
VerticalAlignment = CanvasVerticalAlignment.Top,
};
+ public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
+ public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
+
public LyricsDisplayType DisplayType { get; set; }
private float _rotateAngle = 0f;
- private byte[] _shaderByteCode = File.ReadAllBytes(AppInfo.CustomShaderPath);
- private Color ActivatedWindowAccentColor { get; set; } = Colors.Transparent;
+ private Color? _albumArtAccentColor = null;
private bool IsDockMode { get; set; } = false;
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; }
+ private List> _multiLangLyrics = [];
+ private int _langIndex = 0;
+
private List? _lyricsForGlowEffect = [];
- private SoftwareBitmap? _lastSoftwareBitmap = null;
- private SoftwareBitmap? _softwareBitmap = null;
- private SoftwareBitmap? SoftwareBitmap
- {
- get => _softwareBitmap;
- set
- {
- if (_softwareBitmap != null)
- {
- _lastSoftwareBitmap = _softwareBitmap;
- _transitionStartTime = DateTimeOffset.Now;
- _isTransitioning = true;
- _transitionAlpha = 0f;
- }
-
- _softwareBitmap = value;
- }
- }
+ private SoftwareBitmap? _lastAlbumArtBitmap = null;
+ private SoftwareBitmap? _albumArtBitmap = null;
public int CoverImageRadius { get; set; }
public bool IsCoverOverlayEnabled { get; set; }
@@ -94,8 +77,11 @@ namespace BetterLyrics.WinUI3.ViewModels
public int CoverOverlayOpacity { get; set; }
public int CoverOverlayBlurAmount { get; set; }
+ private bool _isPlaying = true;
+
+ [NotifyPropertyChangedRecipients]
[ObservableProperty]
- public partial bool IsPlaying { get; set; }
+ public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
private protected Color _fontColor;
@@ -110,24 +96,15 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly int _lineEnteringDurationMs = 800;
private readonly int _lineExitingDurationMs = 800;
- private readonly int _lineScrollDurationMs = 800;
-
- private float _lastTotalYScroll = 0.0f;
- private float _totalYScroll = 0.0f;
private int _startVisibleLineIndex = -1;
private int _endVisibleLineIndex = -1;
- private bool _forceToScroll = false;
-
private readonly float _lyricsGlowEffectAmount = 6f;
private readonly double _rightMargin = 36;
private readonly float _topMargin = 0f;
- [ObservableProperty]
- public partial double LimitedLineWidth { get; set; }
-
private protected bool _isRelayoutNeeded = true;
[ObservableProperty]
@@ -152,26 +129,49 @@ namespace BetterLyrics.WinUI3.ViewModels
public LyricsGlowEffectScope LyricsGlowEffectScope { get; set; }
private protected readonly IPlaybackService _playbackService;
-
- private float _transitionAlpha = 1f;
- private TimeSpan _transitionDuration = TimeSpan.FromMilliseconds(1000);
- private DateTimeOffset _transitionStartTime;
- private bool _isTransitioning = false;
+ private protected readonly IMusicSearchService _musicSearchService;
+ private readonly ILibWatcherService _libWatcherService;
private readonly float _coverRotateSpeed = 0.003f;
- private Color _currentBgColor;
- private Color _targetBgColor;
- private float _colorTransitionProgress = 1f;
- private const float ColorTransitionDuration = 0.3f; // 秒
- private bool _isColorTransitioning = false;
+ private readonly ValueTransition _immersiveBgrTransition = new(
+ initialValue: Colors.Transparent,
+ durationSeconds: 0.3f,
+ interpolator: (from, to, progress) =>
+ Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
+ );
+
+ private readonly ValueTransition _albumArtBgTransition = new(
+ initialValue: 0f,
+ durationSeconds: 1.0f,
+ interpolator: (from, to, progress) => from + (to - from) * progress
+ );
+
+ private readonly ValueTransition _canvasYScrollTransition = new(
+ initialValue: 0f,
+ durationSeconds: 0.8f,
+ interpolator: (from, to, progress) =>
+ from + (to - from) * EasingHelper.SmootherStep(progress)
+ );
+
+ private readonly ValueTransition _limitedLineWidthTransition = new(
+ initialValue: 0f,
+ durationSeconds: 0.8f,
+ interpolator: (from, to, progress) => to
+ );
public LyricsRendererViewModel(
ISettingsService settingsService,
- IPlaybackService playbackService
+ IPlaybackService playbackService,
+ IMusicSearchService musicSearchService,
+ ILibWatcherService libWatcherService
)
: base(settingsService)
{
+ _musicSearchService = musicSearchService;
+ _playbackService = playbackService;
+ _libWatcherService = libWatcherService;
+
CoverImageRadius = _settingsService.CoverImageRadius;
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
@@ -188,16 +188,68 @@ namespace BetterLyrics.WinUI3.ViewModels
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
- _playbackService = playbackService;
+ _libWatcherService.MusicLibraryFilesChanged +=
+ LibWatcherService_MusicLibraryFilesChanged;
+
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
_playbackService.PositionChanged += PlaybackService_PositionChanged;
RefreshPlaybackInfo();
-
UpdateFontColor();
}
+ private void LibWatcherService_MusicLibraryFilesChanged(
+ object? sender,
+ Events.LibChangedEventArgs e
+ )
+ {
+ RefreshLyricsAsync().ConfigureAwait(true);
+ }
+
+ ///
+ /// Should invoke this function when:
+ /// 1. The song info is changed (new song is played).
+ /// 2. Lyrics search provider info is changed (change order, enable or disable any provider).
+ /// 3. Local music/lyrics files are changed (added, removed, renamed).
+ ///
+ ///
+ private async Task RefreshLyricsAsync()
+ {
+ _multiLangLyrics = [];
+ _isRelayoutNeeded = true;
+ LyricsStatus = LyricsStatus.Loading;
+ string? lyricsRaw = null;
+ LyricsFormat? lyricsFormat = null;
+
+ if (SongInfo != null)
+ {
+ (lyricsRaw, lyricsFormat) = await _musicSearchService.SearchLyricsAsync(
+ SongInfo.Title,
+ SongInfo.Artist,
+ SongInfo.Album ?? "",
+ SongInfo.DurationMs ?? 0
+ );
+ }
+
+ if (lyricsRaw == null)
+ {
+ LyricsStatus = LyricsStatus.NotFound;
+ }
+ else if (SongInfo != null)
+ {
+ _multiLangLyrics = new LyricsParser().Parse(
+ lyricsRaw,
+ lyricsFormat,
+ SongInfo.Title,
+ SongInfo.Artist,
+ (int)(SongInfo.DurationMs ?? 0)
+ );
+ _isRelayoutNeeded = true;
+ LyricsStatus = LyricsStatus.Found;
+ }
+ }
+
public void RequestRelayout()
{
_isRelayoutNeeded = true;
@@ -215,29 +267,43 @@ namespace BetterLyrics.WinUI3.ViewModels
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
{
- IsPlaying = e.IsPlaying;
+ _isPlaying = e.IsPlaying;
}
public void RefreshPlaybackInfo()
{
- IsPlaying = _playbackService.IsPlaying;
+ _isPlaying = _playbackService.IsPlaying;
SongInfo = _playbackService.SongInfo;
TotalTime = _playbackService.Position;
}
- partial void OnLimitedLineWidthChanged(double value)
+ async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
{
- _isRelayoutNeeded = true;
- }
+ TotalTime = TimeSpan.Zero;
- async partial void OnSongInfoChanged(SongInfo? value)
- {
- if (value?.AlbumArt is byte[] bytes)
- SoftwareBitmap = await (
+ _lastAlbumArtBitmap = _albumArtBitmap;
+
+ if (newValue?.AlbumArt is byte[] bytes)
+ {
+ _albumArtBitmap = await (
await ImageHelper.GetDecoderFromByte(bytes)
).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
+ _albumArtAccentColor = (
+ await ImageHelper.GetAccentColorsFromByte(bytes)
+ ).FirstOrDefault();
+ }
+ else
+ {
+ _albumArtBitmap = null;
+ _albumArtAccentColor = null;
+ }
+
UpdateFontColor();
- _isRelayoutNeeded = true;
+
+ _albumArtBgTransition.Reset(0f);
+ _albumArtBgTransition.StartTransition(1f);
+
+ await RefreshLyricsAsync();
}
partial void OnLyricsFontSizeChanged(int value)
@@ -267,36 +333,39 @@ namespace BetterLyrics.WinUI3.ViewModels
private protected void UpdateFontColor()
{
- switch (LyricsFontColorType)
+ Color fallback = Colors.Transparent;
+ switch (Theme)
{
- case LyricsFontColorType.Default:
- switch (Theme)
+ case ElementTheme.Default:
+ switch (Application.Current.RequestedTheme)
{
- case ElementTheme.Default:
- switch (Application.Current.RequestedTheme)
- {
- case ApplicationTheme.Light:
- _fontColor = _darkFontColor;
- break;
- case ApplicationTheme.Dark:
- _fontColor = _lightFontColor;
- break;
- default:
- break;
- }
+ case ApplicationTheme.Light:
+ fallback = _darkFontColor;
break;
- case ElementTheme.Light:
- _fontColor = _darkFontColor;
- break;
- case ElementTheme.Dark:
- _fontColor = _lightFontColor;
+ case ApplicationTheme.Dark:
+ fallback = _lightFontColor;
break;
default:
break;
}
break;
+ case ElementTheme.Light:
+ fallback = _darkFontColor;
+ break;
+ case ElementTheme.Dark:
+ fallback = _lightFontColor;
+ break;
+ default:
+ break;
+ }
+
+ switch (LyricsFontColorType)
+ {
+ case LyricsFontColorType.Default:
+ _fontColor = fallback;
+ break;
case LyricsFontColorType.Dominant:
- _fontColor = SongInfo?.CoverImageDominantColors?[0] ?? _lightFontColor;
+ _fontColor = _albumArtAccentColor ?? fallback;
break;
default:
break;
@@ -305,10 +374,10 @@ namespace BetterLyrics.WinUI3.ViewModels
private int GetCurrentPlayingLineIndex()
{
- for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++)
+ for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
{
- var line = SongInfo?.LyricsLines?[i];
- if (line.EndPlayingTimestampMs < TotalTime.TotalMilliseconds)
+ var line = _multiLangLyrics.SafeGet(_langIndex)?[i];
+ if (line?.EndMs < TotalTime.TotalMilliseconds)
{
continue;
}
@@ -326,12 +395,16 @@ namespace BetterLyrics.WinUI3.ViewModels
private Tuple GetMaxLyricsLineIndexBoundaries()
{
- if (SongInfo == null || SongInfo.LyricsLines == null || SongInfo.LyricsLines.Count == 0)
+ if (
+ SongInfo == null
+ || _multiLangLyrics.SafeGet(_langIndex) == null
+ || _multiLangLyrics[_langIndex].Count == 0
+ )
{
return new Tuple(-1, -1);
}
- return new Tuple(0, SongInfo.LyricsLines.Count - 1);
+ return new Tuple(0, _multiLangLyrics[_langIndex].Count - 1);
}
private void DrawLyrics(
@@ -345,8 +418,6 @@ namespace BetterLyrics.WinUI3.ViewModels
var (displayStartLineIndex, displayEndLineIndex) =
GetVisibleLyricsLineIndexBoundaries();
- var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
-
for (
int i = displayStartLineIndex;
source?.Count > 0 && i >= 0 && i < source?.Count && i <= displayEndLineIndex;
@@ -359,7 +430,7 @@ namespace BetterLyrics.WinUI3.ViewModels
control,
line?.Text,
_textFormat,
- (float)LimitedLineWidth,
+ (float)_limitedLineWidthTransition.Value,
(float)control.Size.Height
);
@@ -377,11 +448,11 @@ namespace BetterLyrics.WinUI3.ViewModels
break;
case LyricsAlignmentType.Center:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
- centerX += (float)LimitedLineWidth / 2;
+ centerX += (float)_limitedLineWidthTransition.Value / 2;
break;
case LyricsAlignmentType.Right:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
- centerX += (float)LimitedLineWidth;
+ centerX += (float)_limitedLineWidthTransition.Value;
break;
default:
break;
@@ -463,8 +534,10 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.Transform =
Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY))
* Matrix3x2.CreateTranslation(
- (float)(control.Size.Width - _rightMargin - LimitedLineWidth),
- _totalYScroll + (float)(control.Size.Height / 2)
+ (float)(
+ control.Size.Width - _rightMargin - _limitedLineWidthTransition.Value
+ ),
+ _canvasYScrollTransition.Value + (float)(control.Size.Height / 2)
);
ds.DrawTextLayout(textLayout, position, Colors.Transparent);
@@ -533,13 +606,17 @@ namespace BetterLyrics.WinUI3.ViewModels
Color = withGradient
? Color.FromArgb(
211,
- _currentBgColor.R,
- _currentBgColor.G,
- _currentBgColor.B
+ _immersiveBgrTransition.Value.R,
+ _immersiveBgrTransition.Value.G,
+ _immersiveBgrTransition.Value.B
)
- : _currentBgColor,
+ : _immersiveBgrTransition.Value,
+ },
+ new CanvasGradientStop
+ {
+ Position = 1,
+ Color = _immersiveBgrTransition.Value,
},
- new CanvasGradientStop { Position = 1, Color = _currentBgColor },
]
)
{
@@ -556,14 +633,30 @@ namespace BetterLyrics.WinUI3.ViewModels
var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
- if (_isTransitioning && _lastSoftwareBitmap != null)
+ if (_albumArtBgTransition.IsTransitioning)
{
- DrawImgae(control, overlappedCoversDs, _lastSoftwareBitmap, 1 - _transitionAlpha);
- DrawImgae(control, overlappedCoversDs, SoftwareBitmap, _transitionAlpha);
+ if (_lastAlbumArtBitmap != null)
+ {
+ DrawImgae(
+ control,
+ overlappedCoversDs,
+ _lastAlbumArtBitmap,
+ 1 - _albumArtBgTransition.Value
+ );
+ }
+ if (_albumArtBitmap != null)
+ {
+ DrawImgae(
+ control,
+ overlappedCoversDs,
+ _albumArtBitmap,
+ _albumArtBgTransition.Value
+ );
+ }
}
- else
+ else if (_albumArtBitmap != null)
{
- DrawImgae(control, overlappedCoversDs, SoftwareBitmap, 1);
+ DrawImgae(control, overlappedCoversDs, _albumArtBitmap, 1f);
}
using var coverOverlayEffect = new OpacityEffect
@@ -582,15 +675,14 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
- bool isAlbumArtOverlayDrawn = IsCoverOverlayEnabled && SoftwareBitmap != null;
- if (isAlbumArtOverlayDrawn)
+ if (IsCoverOverlayEnabled)
{
DrawAlbumArtBackground(control, ds);
}
if (IsDockMode)
{
- DrawImmersiveBackground(control, ds, isAlbumArtOverlayDrawn);
+ DrawImmersiveBackground(control, ds, IsCoverOverlayEnabled);
}
// Original lyrics only layer
@@ -607,7 +699,7 @@ namespace BetterLyrics.WinUI3.ViewModels
DrawLyrics(
control,
lyricsDs,
- SongInfo?.LyricsLines,
+ _multiLangLyrics.SafeGet(_langIndex),
_defaultOpacity,
LyricsHighlightType.LineByLine
);
@@ -769,6 +861,10 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush);
}
+ ///
+ /// Reassigns positions (x,y) to lyrics lines based on the current control size and font size.
+ ///
+ ///
private void ReLayout(ICanvasAnimatedControl control)
{
if (control == null)
@@ -779,16 +875,21 @@ namespace BetterLyrics.WinUI3.ViewModels
float y = _topMargin;
// Init Positions
- for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++)
+ for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
{
- var line = SongInfo?.LyricsLines?[i];
+ var line = _multiLangLyrics[_langIndex].SafeGet(i);
+
+ if (line == null)
+ {
+ continue;
+ }
// Calculate layout bounds
using var textLayout = new CanvasTextLayout(
control,
line.Text,
_textFormat,
- (float)LimitedLineWidth,
+ (float)_limitedLineWidthTransition.Value,
(float)control.Size.Height
);
line.Position = new Vector2(0, y);
@@ -800,47 +901,23 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
- public override void Calculate(
- ICanvasAnimatedControl control,
- CanvasAnimatedUpdateEventArgs args
- )
+ public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
- base.Calculate(control, args);
-
- if (_isColorTransitioning)
+ if (_isPlaying)
{
- _colorTransitionProgress +=
- (float)ElapsedTime.TotalSeconds / ColorTransitionDuration;
- if (_colorTransitionProgress >= 1f)
- {
- _colorTransitionProgress = 1f;
- _isColorTransitioning = false;
- _currentBgColor = _targetBgColor;
- }
- else
- {
- _currentBgColor = Helper.ColorHelper.GetInterpolatedColor(
- _colorTransitionProgress,
- _currentBgColor,
- _targetBgColor
- );
- }
+ TotalTime += args.Timing.ElapsedTime;
}
- if (_isTransitioning)
- {
- var elapsed = DateTimeOffset.Now - _transitionStartTime;
- float progress = (float)(
- elapsed.TotalMilliseconds / _transitionDuration.TotalMilliseconds
- );
- _transitionAlpha = Math.Clamp(progress, 0f, 1f);
+ ElapsedTime = args.Timing.ElapsedTime;
- if (_transitionAlpha >= 1f)
- {
- _isTransitioning = false;
- _lastSoftwareBitmap?.Dispose();
- _lastSoftwareBitmap = null;
- }
+ if (_immersiveBgrTransition.IsTransitioning)
+ {
+ _immersiveBgrTransition.Update(ElapsedTime);
+ }
+
+ if (_albumArtBgTransition.IsTransitioning)
+ {
+ _albumArtBgTransition.Update(ElapsedTime);
}
if (IsDynamicCoverOverlayEnabled)
@@ -849,31 +926,37 @@ namespace BetterLyrics.WinUI3.ViewModels
_rotateAngle %= MathF.PI * 2;
}
+ if (_limitedLineWidthTransition.IsTransitioning)
+ {
+ _limitedLineWidthTransition.Update(ElapsedTime);
+ _isRelayoutNeeded = true;
+ }
+
if (_isRelayoutNeeded)
{
ReLayout(control);
_isRelayoutNeeded = false;
- _forceToScroll = true;
}
- int currentPlayingLineIndex = GetCurrentPlayingLineIndex();
-
- CalculateLinesProps(SongInfo?.LyricsLines, currentPlayingLineIndex, _defaultOpacity);
- CalculateCanvasYScrollOffset(control, currentPlayingLineIndex);
+ UpdateLinesProps(_multiLangLyrics.SafeGet(_langIndex), _defaultOpacity);
+ UpdateCanvasYScrollOffset(control);
if (IsLyricsGlowEffectEnabled)
{
// Deep copy lyrics lines for glow effect
- _lyricsForGlowEffect = SongInfo?.LyricsLines?.Select(line => line.Clone()).ToList();
+ _lyricsForGlowEffect = _multiLangLyrics
+ .SafeGet(_langIndex)
+ ?.Select(line => line.Clone())
+ .ToList();
switch (LyricsGlowEffectScope)
{
case LyricsGlowEffectScope.WholeLyrics:
break;
case LyricsGlowEffectScope.CurrentLine:
- CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
+ UpdateLinesProps(_lyricsForGlowEffect, 0);
break;
case LyricsGlowEffectScope.CurrentChar:
- CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
+ UpdateLinesProps(_lyricsForGlowEffect, 0);
break;
default:
break;
@@ -881,17 +964,75 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
- private void CalculateLinesProps(
- List? source,
- int currentPlayingLineIndex,
- float defaultOpacity
- )
+ private float GetLinePlayingProgress(LyricsLine line)
+ {
+ float playProgress = 0f;
+ int now = (int)TotalTime.TotalMilliseconds;
+
+ if (line.CharTimings != null && line.CharTimings.Count > 0)
+ {
+ int charIndex = 0;
+ for (; charIndex < line.CharTimings.Count; charIndex++)
+ {
+ var timing = line.CharTimings[charIndex];
+ if (now < timing.StartMs)
+ {
+ // 当前时间还没到这个字,停在上一个字
+ break;
+ }
+ if (now >= timing.StartMs && now <= timing.EndMs)
+ {
+ float charProgress = 1f;
+ if (timing.EndMs != timing.StartMs)
+ {
+ charProgress =
+ (now - timing.StartMs) / (float)(timing.EndMs - timing.StartMs);
+ }
+ // 当前时间在这个字的高亮区间
+ playProgress = charIndex + charProgress;
+ playProgress /= line.CharTimings.Count;
+ return playProgress;
+ }
+ }
+ // 如果超出最后一个字的结束时间
+ if (now > line.CharTimings[^1].EndMs)
+ {
+ // 如果还没到行尾,保持最后一个字高亮
+ if (now < line.EndMs)
+ {
+ playProgress = 1f; // 全部字高亮
+ }
+ else
+ {
+ playProgress = 1f; // 行已结束
+ }
+ }
+ else if (charIndex == 0)
+ {
+ playProgress = 0f; // 还没到第一个字
+ }
+ }
+ else
+ {
+ playProgress = (now - line.StartMs) / (float)(line.DurationMs);
+ }
+ return playProgress;
+ }
+
+ private void UpdateLinesProps(List? source, float defaultOpacity)
{
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
- for (int i = startLineIndex; source?.Count > 0 && i <= endLineIndex; i++)
+ var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
+
+ for (int i = startLineIndex; i <= endLineIndex; i++)
{
- var line = source?[i];
+ var line = source?.SafeGet(i);
+
+ if (line == null)
+ {
+ continue;
+ }
bool linePlaying = i == currentPlayingLineIndex;
@@ -923,12 +1064,9 @@ namespace BetterLyrics.WinUI3.ViewModels
scale = _highlightedScale;
opacity = _highlightedOpacity;
- playProgress =
- ((float)TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs)
- / line.DurationMs;
+ playProgress = GetLinePlayingProgress(line);
- var durationFromStartMs =
- TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs;
+ var durationFromStartMs = TotalTime.TotalMilliseconds - line.StartMs;
lineEntering = durationFromStartMs <= lineEnteringDurationMs;
if (lineEntering)
{
@@ -948,8 +1086,7 @@ namespace BetterLyrics.WinUI3.ViewModels
line.PlayingState = LyricsPlayingState.Played;
playProgress = 1;
- var durationToEndMs =
- TotalTime.TotalMilliseconds - line.EndPlayingTimestampMs;
+ var durationToEndMs = TotalTime.TotalMilliseconds - line.EndMs;
lineExiting = durationToEndMs <= lineExitingDurationMs;
if (lineExiting)
{
@@ -979,11 +1116,9 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
- private void CalculateCanvasYScrollOffset(
- ICanvasAnimatedControl control,
- int currentPlayingLineIndex
- )
+ private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control)
{
+ var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
if (currentPlayingLineIndex < 0)
{
return;
@@ -997,7 +1132,9 @@ namespace BetterLyrics.WinUI3.ViewModels
}
// Set _scrollOffsetY
- LyricsLine? currentPlayingLine = SongInfo?.LyricsLines?[currentPlayingLineIndex];
+ LyricsLine? currentPlayingLine = _multiLangLyrics
+ .SafeGet(_langIndex)
+ ?[currentPlayingLineIndex];
if (currentPlayingLine == null)
{
@@ -1008,63 +1145,49 @@ namespace BetterLyrics.WinUI3.ViewModels
control,
currentPlayingLine.Text,
_textFormat,
- (float)LimitedLineWidth,
+ (float)_limitedLineWidthTransition.Value,
(float)control.Size.Height
);
- var lineScrollingProgress =
- (TotalTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs)
- / Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs);
-
float targetYScrollOffset =
(float?)(
-currentPlayingLine.Position.Y
- + SongInfo?.LyricsLines?[0].Position.Y
+ + _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y
- playingTextLayout.LayoutBounds.Height / 2
- - _lastTotalYScroll
) ?? 0f;
- var yScrollOffset =
- targetYScrollOffset
- * EasingHelper.SmootherStep((float)Math.Min(1, lineScrollingProgress));
-
- bool isScrollingNow = lineScrollingProgress <= 1;
-
- if (isScrollingNow)
+ if (!_canvasYScrollTransition.IsTransitioning)
{
- _totalYScroll = _lastTotalYScroll + yScrollOffset;
+ _canvasYScrollTransition.StartTransition(targetYScrollOffset);
}
- else
+
+ if (_canvasYScrollTransition.IsTransitioning)
{
- if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1)
- {
- _totalYScroll = _lastTotalYScroll + targetYScrollOffset;
- _forceToScroll = false;
- }
- _lastTotalYScroll = _totalYScroll;
+ _canvasYScrollTransition.Update(ElapsedTime);
}
_startVisibleLineIndex = _endVisibleLineIndex = -1;
// Update visible line indices
- for (
- int i = startLineIndex;
- i >= 0 && i <= endLineIndex && i < SongInfo?.LyricsLines?.Count;
- i++
- )
+ for (int i = startLineIndex; i <= endLineIndex; i++)
{
- var line = SongInfo?.LyricsLines?[i];
+ var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
+
+ if (line == null)
+ {
+ continue;
+ }
using var textLayout = new CanvasTextLayout(
control,
- line.Text,
+ line?.Text,
_textFormat,
- (float)LimitedLineWidth,
+ (float)_limitedLineWidthTransition.Value,
(float)control.Size.Height
);
if (
- _totalYScroll
+ _canvasYScrollTransition.Value
+ (float)(control.Size.Height / 2)
+ line.Position.Y
+ textLayout.LayoutBounds.Height
@@ -1077,7 +1200,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
if (
- _totalYScroll
+ _canvasYScrollTransition.Value
+ (float)(control.Size.Height / 2)
+ line.Position.Y
+ textLayout.LayoutBounds.Height
@@ -1261,7 +1384,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.PropertyName == nameof(LyricsPageViewModel.LimitedLineWidth))
{
- LimitedLineWidth = message.NewValue;
+ _limitedLineWidthTransition.StartTransition((float)message.NewValue);
}
}
}
@@ -1302,17 +1425,33 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor))
{
- _currentBgColor = _isColorTransitioning
- ? Helper.ColorHelper.GetInterpolatedColor(
- _colorTransitionProgress,
- _currentBgColor,
- _targetBgColor
- )
- : ActivatedWindowAccentColor;
- _targetBgColor = message.NewValue;
- _colorTransitionProgress = 0f;
- _isColorTransitioning = true;
- ActivatedWindowAccentColor = message.NewValue;
+ _immersiveBgrTransition.StartTransition(message.NewValue);
+ }
+ }
+ }
+
+ public void Receive(
+ PropertyChangedMessage> message
+ )
+ {
+ if (message.Sender is SettingsViewModel)
+ {
+ if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo))
+ {
+ // Lyrics search providers info changed, re-fetch lyrics
+ RefreshLyricsAsync().ConfigureAwait(true);
+ }
+ }
+ }
+
+ public void Receive(PropertyChangedMessage> message)
+ {
+ if (message.Sender is SettingsViewModel)
+ {
+ if (message.PropertyName == nameof(SettingsViewModel.LocalLyricsFolders))
+ {
+ // Music lib changed, re-fetch lyrics
+ RefreshLyricsAsync().ConfigureAwait(true);
}
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs
index f3303d4..df156db 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs
@@ -1,14 +1,7 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-using BetterLyrics.WinUI3.Enums;
-using BetterLyrics.WinUI3.Helper;
-using BetterLyrics.WinUI3.Services.Playback;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
-using Microsoft.UI;
-using Windows.UI;
namespace BetterInAppLyrics.WinUI3.ViewModels
{
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs
index 2eedff8..9ea8953 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/SettingsViewModel.cs
@@ -2,38 +2,30 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
-using BetterLyrics.WinUI3.Services.Database;
-using BetterLyrics.WinUI3.Services.Playback;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
-using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel.Core;
using Windows.Globalization;
+using Windows.Media;
using Windows.Media.Playback;
using Windows.System;
-using Windows.UI;
using WinRT.Interop;
-using WinUIEx.Messaging;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class SettingsViewModel : ObservableRecipient
{
- [ObservableProperty]
- [NotifyPropertyChangedRecipients]
- public partial bool IsRebuildingLyricsIndexDatabase { get; set; } = false;
-
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeType { get; set; }
@@ -49,9 +41,12 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
+ [ObservableProperty]
+ public partial ObservableCollection LocalLyricsFolders { get; set; }
+
[ObservableProperty]
[NotifyPropertyChangedRecipients]
- public partial ObservableCollection MusicLibraries { get; set; }
+ public partial ObservableCollection LyricsSearchProvidersInfo { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
@@ -73,26 +68,51 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
- partial void OnMusicLibrariesChanged(
- ObservableCollection oldValue,
- ObservableCollection newValue
- )
- {
- if (oldValue != null)
- {
- oldValue.CollectionChanged -= (_, _) =>
- _settingsService.MusicLibraries = [.. MusicLibraries];
- }
- if (newValue != null)
- {
- newValue.CollectionChanged += (_, _) =>
- _settingsService.MusicLibraries = [.. MusicLibraries];
- }
- }
-
[ObservableProperty]
public partial Enums.Language Language { get; set; }
+ public string Version { get; set; } = AppInfo.AppVersion;
+
+ [ObservableProperty]
+ public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
+
+ [ObservableProperty]
+ public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
+
+ private readonly MediaPlayer _mediaPlayer = new();
+ private readonly ISettingsService _settingsService;
+ private readonly ILibWatcherService _libWatcherService;
+ private readonly IPlaybackService _playbackService;
+
+ public SettingsViewModel(
+ ISettingsService settingsService,
+ ILibWatcherService libWatcherService,
+ IPlaybackService playbackService
+ )
+ {
+ _settingsService = settingsService;
+ _libWatcherService = libWatcherService;
+ _playbackService = playbackService;
+
+ RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
+
+ LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
+ LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
+
+ Language = _settingsService.Language;
+ CoverImageRadius = _settingsService.CoverImageRadius;
+ ThemeType = _settingsService.ThemeType;
+ BackdropType = _settingsService.BackdropType;
+ TitleBarType = _settingsService.TitleBarType;
+
+ AutoStartWindowType = _settingsService.AutoStartWindowType;
+
+ IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
+ IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
+ CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
+ CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
+ }
+
partial void OnLanguageChanged(Enums.Language value)
{
switch (value)
@@ -109,52 +129,18 @@ namespace BetterLyrics.WinUI3.ViewModels
case Enums.Language.TraditionalChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
break;
+ case Enums.Language.Japanese:
+ ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
+ break;
+ case Enums.Language.Korean:
+ ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
+ break;
default:
break;
}
_settingsService.Language = Language;
}
- private readonly MediaPlayer _mediaPlayer = new();
-
- private readonly IDatabaseService _databaseService;
- private readonly ISettingsService _settingsService;
-
- public string Version { get; set; } = AppInfo.AppVersion;
-
- [ObservableProperty]
- public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
-
- [ObservableProperty]
- public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
-
- public SettingsViewModel(IDatabaseService databaseService, ISettingsService settingsService)
- {
- _databaseService = databaseService;
- _settingsService = settingsService;
-
- RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
-
- MusicLibraries = [.. _settingsService.MusicLibraries];
- Language = _settingsService.Language;
- CoverImageRadius = _settingsService.CoverImageRadius;
- ThemeType = _settingsService.ThemeType;
- BackdropType = _settingsService.BackdropType;
- TitleBarType = _settingsService.TitleBarType;
-
- AutoStartWindowType = _settingsService.AutoStartWindowType;
-
- IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
- IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
- CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
- CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
- }
-
- partial void OnMusicLibrariesChanged(ObservableCollection value)
- {
- _settingsService.MusicLibraries = [.. value];
- }
-
partial void OnThemeTypeChanged(ElementTheme value)
{
_settingsService.ThemeType = value;
@@ -201,18 +187,22 @@ namespace BetterLyrics.WinUI3.ViewModels
_settingsService.CoverOverlayBlurAmount = value;
}
- [RelayCommand]
- private async Task RebuildLyricsIndexDatabaseAsync()
+ public void RemoveFolderAsync(LocalLyricsFolder folder)
{
- IsRebuildingLyricsIndexDatabase = true;
- await _databaseService.RebuildDatabaseAsync(MusicLibraries);
- IsRebuildingLyricsIndexDatabase = false;
+ LocalLyricsFolders.Remove(folder);
+ _settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
+ _libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
+ Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
- public async Task RemoveFolderAsync(string path)
+ public void OnLyricsSearchProvidersReordered()
{
- MusicLibraries.Remove(path);
- await RebuildLyricsIndexDatabaseAsync();
+ _settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
+ Broadcast(
+ LyricsSearchProvidersInfo,
+ LyricsSearchProvidersInfo,
+ nameof(LyricsSearchProvidersInfo)
+ );
}
[RelayCommand]
@@ -229,27 +219,26 @@ namespace BetterLyrics.WinUI3.ViewModels
if (folder != null)
{
- if (MusicLibraries.Any((item) => folder.Path.StartsWith(item)))
- {
- WeakReferenceMessenger.Default.Send(
- new ShowNotificatonMessage(
- new Notification(
- App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo")
- )
- )
- );
- }
- else
- {
- await AddFolderAsync(folder.Path);
- }
+ AddFolderAsync(folder.Path);
}
}
- private async Task AddFolderAsync(string path)
+ private void AddFolderAsync(string path)
{
- bool existed = MusicLibraries.Any((x) => x == path);
- if (existed)
+ var normalizedPath =
+ Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar)
+ + Path.DirectorySeparatorChar;
+
+ if (
+ LocalLyricsFolders.Any(x =>
+ Path.GetFullPath(x.Path)
+ .TrimEnd(Path.DirectorySeparatorChar)
+ .Equals(
+ normalizedPath.TrimEnd(Path.DirectorySeparatorChar),
+ StringComparison.OrdinalIgnoreCase
+ )
+ )
+ )
{
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
@@ -259,17 +248,55 @@ namespace BetterLyrics.WinUI3.ViewModels
)
);
}
+ else if (
+ LocalLyricsFolders.Any(item =>
+ normalizedPath.StartsWith(
+ Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar)
+ + Path.DirectorySeparatorChar,
+ StringComparison.OrdinalIgnoreCase
+ )
+ )
+ )
+ {
+ // 添加的文件夹是现有文件夹的子文件夹
+ WeakReferenceMessenger.Default.Send(
+ new ShowNotificatonMessage(
+ new Notification(
+ App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo")
+ )
+ )
+ );
+ }
+ else if (
+ LocalLyricsFolders.Any(item =>
+ Path.GetFullPath(item.Path)
+ .TrimEnd(Path.DirectorySeparatorChar)
+ .StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase)
+ )
+ )
+ {
+ // 添加的文件夹是现有文件夹的父文件夹
+ WeakReferenceMessenger.Default.Send(
+ new ShowNotificatonMessage(
+ new Notification(
+ App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo")
+ )
+ )
+ );
+ }
else
{
- MusicLibraries.Add(path);
- await RebuildLyricsIndexDatabaseAsync();
+ LocalLyricsFolders.Add(new LocalLyricsFolder(path, true));
+ _settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
+ _libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
+ Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
}
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
- await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
+ await Launcher.LaunchUriAsync(new Uri(AppInfo.GithubUrl));
}
private void OpenFolderInFileExplorer(string path)
@@ -284,9 +311,9 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
- public void OpenMusicFolder(string path)
+ public void OpenMusicFolder(LocalLyricsFolder folder)
{
- OpenFolderInFileExplorer(path);
+ OpenFolderInFileExplorer(folder.Path);
}
[RelayCommand]
@@ -311,9 +338,9 @@ namespace BetterLyrics.WinUI3.ViewModels
}
[RelayCommand]
- private async Task PlayTestingMusicTask()
+ private void PlayTestingMusicTask()
{
- await AddFolderAsync(AppInfo.AssetsFolder);
+ AddFolderAsync(AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(AppInfo.TestMusicPath));
_mediaPlayer.Play();
}
@@ -323,5 +350,21 @@ namespace BetterLyrics.WinUI3.ViewModels
{
OpenFolderInFileExplorer(AppInfo.LogDirectory);
}
+
+ public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
+ {
+ _settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
+ Broadcast(
+ LyricsSearchProvidersInfo,
+ LyricsSearchProvidersInfo,
+ nameof(LyricsSearchProvidersInfo)
+ );
+ }
+
+ public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
+ {
+ _settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
+ Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
+ }
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml
index 97ae3ad..bd768fe 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml
@@ -12,7 +12,10 @@
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
-
+
+ PointerMoved="TopCommandGrid_PointerMoved">
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs
index e7f68bf..42746cc 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/HostWindow.xaml.cs
@@ -3,7 +3,7 @@ using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
-using BetterLyrics.WinUI3.Services.Settings;
+using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
@@ -162,6 +162,7 @@ namespace BetterLyrics.WinUI3.Views
RestoreButton.Visibility = Visibility.Collapsed;
}
}
+ TopCommandGrid.Opacity = 0;
break;
default:
break;
@@ -220,21 +221,32 @@ namespace BetterLyrics.WinUI3.Views
}
}
- private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
- {
- if (TopCommandGrid.Opacity == 0)
- TopCommandGrid.Opacity = .5;
- }
-
- private void TopCommandGrid_PointerExited(object sender, PointerRoutedEventArgs e)
- {
- if (TopCommandGrid.Opacity == .5)
- TopCommandGrid.Opacity = 0;
- }
-
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
WindowHelper.OpenSettingsWindow();
}
+
+ private void TopCommandGrid_PointerMoved(object sender, PointerRoutedEventArgs e) { }
+
+ private void RootGrid_PointerMoved(object sender, PointerRoutedEventArgs e)
+ {
+ var point = e.GetCurrentPoint(RootGrid);
+ double y = point.Position.Y;
+
+ if (y >= 0 && y <= TopCommandGrid.ActualHeight + 5)
+ {
+ if (TopCommandGrid.Opacity == 0)
+ {
+ TopCommandGrid.Opacity = .5;
+ }
+ }
+ else
+ {
+ if (TopCommandGrid.Opacity == .5)
+ {
+ TopCommandGrid.Opacity = 0;
+ }
+ }
+ }
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml
index b7f1c05..383bb45 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml
@@ -26,7 +26,7 @@
-
+
@@ -45,24 +45,34 @@
-
-
+
+
+
+
+
+
+
+
-
+
@@ -311,42 +321,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -449,34 +423,32 @@
-
-
-
+
+
-
+
-
-
+
+
-
-
-
-
+
-
+
-
+
+
-
+
-
+
-
+
+
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs
index bbafcec..15b11f2 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/LyricsPage.xaml.cs
@@ -64,11 +64,6 @@ namespace BetterLyrics.WinUI3.Views
ViewModel.LimitedLineWidth = e.NewSize.Width;
}
- private void OpenMatchedFileButton_Click(object sender, RoutedEventArgs e)
- {
- ViewModel.OpenMatchedFileFolderInFileExplorer((string)(sender as HyperlinkButton)!.Tag);
- }
-
private void CoverImageGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.CoverImageGridActualHeight = e.NewSize.Height;
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml
index 96c60f5..e02477f 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml
@@ -8,6 +8,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
mc:Ignorable="d">
@@ -73,21 +74,26 @@
+ ItemsSource="{x:Bind ViewModel.LocalLyricsFolders, Mode=OneWay}">
-
-
+
+
+
+
+
@@ -104,13 +110,13 @@
@@ -131,21 +137,38 @@
-
-
-
-
-
-
-
-
+ x:Name="LyricsSearchProvidersSettingsExpander"
+ x:Uid="SettingsPageLyricsSearchProvidersConfig"
+ HeaderIcon="{ui:FontIcon Glyph=}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -166,8 +189,6 @@
-
@@ -188,6 +209,8 @@
+
+
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml.cs
index a593230..b8aa01b 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml.cs
@@ -1,4 +1,8 @@
+using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
+using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Models;
+using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -29,15 +33,15 @@ namespace BetterLyrics.WinUI3.Views
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
- ViewModel.OpenMusicFolder((string)(sender as HyperlinkButton)!.Tag);
+ ViewModel.OpenMusicFolder((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
}
- private async void SettingsPageRemovePathButton_Click(
+ private void SettingsPageRemovePathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
- await ViewModel.RemoveFolderAsync((string)(sender as HyperlinkButton)!.Tag);
+ ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
}
private void NavView_SelectionChanged(
@@ -47,5 +51,35 @@ namespace BetterLyrics.WinUI3.Views
{
ViewModel.NavViewSelectedItemTag = (args.SelectedItem as NavigationViewItem)!.Tag;
}
+
+ private void LyricsSearchProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
+ {
+ if (sender is ToggleSwitch toggleSwitch)
+ {
+ if (toggleSwitch.DataContext is LyricsSearchProviderInfo providerInfo)
+ {
+ ViewModel.ToggleLyricsSearchProvider(providerInfo);
+ }
+ }
+ }
+
+ private void LyricsSearchProvidersListView_DragItemsCompleted(
+ ListViewBase sender,
+ DragItemsCompletedEventArgs args
+ )
+ {
+ ViewModel.OnLyricsSearchProvidersReordered();
+ }
+
+ private void LocalLyricsFolderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
+ {
+ if (sender is ToggleSwitch toggleSwitch)
+ {
+ if (toggleSwitch.DataContext is LocalLyricsFolder localLyricsFolder)
+ {
+ ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
+ }
+ }
+ }
}
}
diff --git a/README.CN.md b/README.CN.md
new file mode 100644
index 0000000..74fadab
--- /dev/null
+++ b/README.CN.md
@@ -0,0 +1,188 @@
+_**Click here to see the English version**_
+
+
+
+
+
+
+BetterLyrics
+
+
+
+基于 WinUI 3 打造的流畅动态本地歌词显示工具
+
+
+---
+
+## 亮点
+
+- 支持将模糊专辑封面设为背景
+- 歌词淡入淡出、缩放等动画流畅自然
+- 切换歌曲时界面无缝过渡
+- 支持每个字符的渐变卡拉OK(发光)效果
+- 沉浸式桌面歌词(Dock 模式)
+
+> 项目仍在开发中,`dev` 分支可能存在 bug。
+
+---
+
+## 支持的歌词源
+
+- 本地歌词:
+ - 音乐文件内嵌歌词(通过 [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) 读取和解析)
+ - `.lrc` 文件
+
+- 在线歌词源:
+ - [LRCLIB](https://lrclib.net/)
+ - QQ 音乐(通过 [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) 获取和解码)
+
+---
+
+## 多种个性化设置选项
+
+提供了丰富的自定义项:
+
+- 主题模式(浅色、深色、跟随系统)
+- 背景样式(无、Mica 云母、Acrylic 亚克力、透明)
+- 专辑封面背景(动态显示、模糊程度、透明度)
+- 歌词样式(对齐方式、字体大小、颜色 **(从专辑封面中提取主题色)**、行间距、透明度、模糊强度、动态**发光**特效)
+- 语言(英文、简体中文、繁体中文)
+
+---
+
+## 软件截图
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## 演示视频
+
+观看我们的介绍视频「BetterLyrics 阶段性开发成果展示」(上传于 2025 年 5 月 31 日):
+[点此观看 B 站视频](https://b23.tv/QjKkYmL)
+
+---
+
+## 立即体验
+
+### 稳定版本
+
+
+
+
+
+> **推荐方式**,**永久免费试用或购买**(免费与付费功能上无差别,若喜欢可购买支持作者)
+
+也可从 Google Drive 下载(详见 [release 页面](https://github.com/jayfunc/BetterLyrics/releases/latest))
+
+> 注意:这是一个 `.zip` 压缩包,请参考[安装指南](How2Install/How2Install.md)进行安装。
+
+### 最新开发版本
+
+可通过 `git clone` 克隆本仓库后自行构建运行。
+
+---
+
+## 播放器适配说明
+
+本项目通过监听 [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols) 获取当前播放歌曲信息。
+理论上,**只要你的播放器支持 SMTC 控件**,加载本地音乐或歌词后即可使用。
+
+兼容性良好的播放器包括但不限于:
+
+- Spotify
+- Groove 音乐
+- Apple Music
+- Windows 媒体播放器
+- VLC
+- QQ 音乐
+- 酷狗音乐
+- 酷我音乐
+
+>(注:未测试全部播放器,如有异常欢迎反馈 issue)
+
+---
+
+## 后续工作
+
+敬请期待。
+
+---
+
+## 特别感谢
+
+- [LRCLIB](https://lrclib.net/)
+ - 在线歌词 API 提供源
+- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
+ - 本地音频元信息读取
+- [WinUIEx](https://github.com/dotMorten/WinUIEx)
+ - 简化 Win32 窗口操作
+- [TagLib#](https://github.com/mono/taglib-sharp)
+ - 曾用作元信息解析库
+- [Stackoverflow - WPF 动画 Margin 属性](https://stackoverflow.com/a/21542882/11048731)
+- [DevWinUI](https://github.com/ghost1372/DevWinUI)
+- [Bilibili -【WinUI3】SystemBackdropController 教程](https://www.bilibili.com/video/BV1PY4FevEkS)
+- [博客园 - .NET App 与 SMTC 交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
+- [Win2D 游戏循环教程](https://www.cnblogs.com/walterlv/p/10236395.html)
+- [Win2D Iris Blur 示例](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
+- [CommunityToolkit - 教程合集](https://mvvm.coldwind.top/)
+
+---
+
+## 灵感来源
+
+- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
+- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
+- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
+- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
+
+---
+
+## 使用的第三方库
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Star 历史
+
+[](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
+
+## 欢迎提出反馈或建议
+
+感谢。
+
+
diff --git a/README.md b/README.md
index b26cc08..10993b2 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+_**点此处查看中文说明**_
+
@@ -17,24 +19,63 @@ Your smooth dynamic local lyrics display built with WinUI 3
- Dynamic blur album art as background
- Smooth lyrics fade in/out, zoom in/out effects
- Smooth user interface change from song to song
-- Gradient Karaoke effect on every single character
-- Immersive desktop lyrics
+- Gradient Karaoke (with glow) effect on every single character
+- Immersive desktop lyrics (dock mode)
> This project is still under development now, bugs and unexpected behaviors may be existed in the latest dev branch.
+## Supported lyrics source
+
+- From your local storage
+ - Music files (with embedded lyrics)
+ - [.lrc](https://en.wikipedia.org/wiki/LRC_(file_format)) files (with both core format and enhanced format)
+ - [.eslrc](https://github.com/ESLyric/release) files
+ - [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) files
+
+- From online lyrics providers
+ - [LRCLIB](https://lrclib.net/)
+
## Customize in your way
We provide more than one setting item to better align with your preference
-- Theme (light, dark, follow system)
+- Theme
+ - Light
+ - Dark
+ - Follow system
-- Backdrop (none, mica, acrylic, transparent)
+- Backdrop
+ - None
+ - Mica
+ - Acrylic
+ - Transparent
-- Album art as background (dynamic, blur amount, opacity)
+- Album art as background
+ - Dynamic
+ - Blur amount
+ - Opacity
-- Lyrics (alignment, font size, font color **(picked from album art accent color)** line spacing, opacity, blur amount, dynamic **glow** effect)
+- Album art as cover
+ - Corner radius
-- Language (English, Simplified Chinese, Traditional Chinese)
+- Lyrics
+ - Alignment
+ - Font size
+ - Font color **(from album art accent color)**
+ - Line spacing
+ - Opacity
+ - Blur amount
+ - Dynamic **glow** effect
+ - Whole lyrics
+ - Line by line
+ - Word by word
+
+- Language
+ - English
+ - Simplified Chinese
+ - Traditional Chinese
+ - Japanese
+ - Korean
## Screenshots
@@ -43,8 +84,14 @@ We provide more than one setting item to better align with your preference

+
+

+
+
+
+


@@ -53,7 +100,7 @@ We provide more than one setting item to better align with your preference
## Demonstration
-Watch our introduction video「BetterLyrics 阶段性开发成果展示」(uploaded on 31 May 2025) on Bilibili [here](https://b23.tv/QjKkYmL).
+Watch our introduction video (uploaded on 31 May 2025) on Bilibili [here](https://b23.tv/QjKkYmL).
## Try it now
@@ -75,38 +122,33 @@ You can `git clone` this project and build it yourself.
## Setup your app
-This project relies on listening messages from [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols).
-So technically, as long as you are using the music apps (like
+This project relies on listening messages from [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols), so most of the music players will work.
-- Spotify
-- Groove Music
-- Apple Music
-- Windows Media Player
-- VLC Media Player
-- QQ 音乐
-- 网易云音乐
-- 酷狗音乐
-- 酷我音乐
+### About lyrics
-) which support SMTC, then possibly (I didn't test all of themif you find one fail to listen to, you can open an issue) all you need to do is just load your local music/lyrics lib and you are good to go.
+For a better experience, you can use [LDDC](https://github.com/chenmozhijin/LDDC) to download lyrics.
## Future work
-- Watching file changes
- When you downloading lyrics (using some other tools or your own scripts) while listening to new musics (non-existed on your local disks), this app can automatically load those new files.
-
-> Please note: we are not planning support directly load lyrics files via some music software APIs due to copyright issues.
+To be added later.
## Many thanks to
+- [LRCLIB](https://lrclib.net/)
+ - Online lyrics API provider
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
-- [DevWinUI](https://github.com/ghost1372/DevWinUI)
-- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
+ - Used for extracting pictures in music files
+- [WinUIEx](https://github.com/dotMorten/WinUIEx)
+ - Provide easy ways to access Win32 API regarding windowing
- [TagLib#](https://github.com/mono/taglib-sharp)
+ - Used for reading original lyrics content
+- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
+- [DevWinUI](https://github.com/ghost1372/DevWinUI)
- [Bilibili -【WinUI3】SystemBackdropController:定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D 中的游戏循环:CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
+- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
## Inspired by
@@ -118,33 +160,24 @@ So technically, as long as you are using the music apps (like
## Third-party libraries that this project uses
```
-CommunityToolkit.Labs.WinUI.MarqueeText
-CommunityToolkit.Labs.WinUI.OpacityMaskView
-CommunityToolkit.Mvvm
-CommunityToolkit.WinUI.Behaviors
-CommunityToolkit.WinUI.Controls.Primitives
-CommunityToolkit.WinUI.Controls.Segmented
-CommunityToolkit.WinUI.Controls.SettingsControls
-CommunityToolkit.WinUI.Converters
-CommunityToolkit.WinUI.Extensions
-CommunityToolkit.WinUI.Helpers
-CommunityToolkit.WinUI.Media
-Microsoft.Extensions.DependencyInjection
-Microsoft.Extensions.Logging
-Microsoft.Graphics.Win2D
-Microsoft.Windows.SDK.BuildTools
-Microsoft.WindowsAppSDK
-Microsoft.Xaml.Behaviors.WinUI.Managed
-Newtonsoft.Json
-Serilog.Extensions.Logging
-Serilog.Sinks.File
-sqlite-net-pcl
-System.Drawing.Common
-System.Text.Encoding.CodePages
-Ude.NetStandard
-WinUIEx
-z440.atl.core
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ S
```
## Star History
@@ -153,4 +186,4 @@ z440.atl.core
## 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.
\ No newline at end of file
+If you find a bug please file it in issues or if you have any ideas feel free to share it here.
diff --git a/Screenshots/dock.gif b/Screenshots/dock.gif
new file mode 100644
index 0000000..39e9781
Binary files /dev/null and b/Screenshots/dock.gif differ
diff --git a/Screenshots/glow.gif b/Screenshots/glow.gif
new file mode 100644
index 0000000..125a4af
Binary files /dev/null and b/Screenshots/glow.gif differ
diff --git a/Screenshots/immersive-dock.gif b/Screenshots/immersive-dock.gif
new file mode 100644
index 0000000..3b30682
Binary files /dev/null and b/Screenshots/immersive-dock.gif differ