This commit is contained in:
Zhe Fang
2025-11-16 17:54:37 -05:00
parent c471f128c1
commit ecff788c6c
32 changed files with 467 additions and 516 deletions

View File

@@ -12,7 +12,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.121.0" />
Version="1.0.122.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -5,9 +5,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dev="using:DevWinUI"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
@@ -42,7 +42,6 @@
<dev:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}">
<local:ExtendedSlider
Default="12"
Maximum="100"
Minimum="0"
Unit="%"
@@ -52,7 +51,6 @@
<dev:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF5EF;}">
<local:ExtendedSlider
Default="12"
Maximum="64"
Minimum="0"
Value="{x:Bind AlbumArtLayoutSettings.CoverImageShadowAmount, Mode=TwoWay}" />

View File

@@ -133,7 +133,7 @@
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Artist}"
Text="{x:Bind DisplayArtists}"
TextWrapping="Wrap"
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock

View File

@@ -11,65 +11,74 @@
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="12">
<FontIcon
Margin="20"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE8AB;" />
<Button
Margin="12"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="Button_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE73C;}"
Style="{StaticResource GhostButtonStyle}">
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="Escape" />
</Button.KeyboardAccelerators>
</Button>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="ShadowCastGrid" />
<Border
x:Name="ShadowRect"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="12"
Loaded="ShadowRect_Loaded"
Translation="0,0,64">
<Border.Shadow>
<ThemeShadow x:Name="Shadow" />
</Border.Shadow>
</Border>
<Grid Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}" CornerRadius="12">
<TextBlock
x:Uid="SystemTraySwitch"
Margin="20"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Button
Margin="12"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="Button_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE73C;}"
Style="{StaticResource GhostButtonStyle}">
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="Escape" />
</Button.KeyboardAccelerators>
</Button>
<ListView
Margin="48,56"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid
Margin="0,10"
Padding="5"
AllowFocusOnInteraction="True"
CornerRadius="4"
Tapped="Grid_Tapped">
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView
Margin="48,56"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid
Margin="0,10"
Padding="5"
AllowFocusOnInteraction="True"
CornerRadius="4"
Tapped="Grid_Tapped">
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel
Margin="20"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Orientation="Horizontal"
Spacing="6">
<FontIcon
Margin="0,1,0,0"
FontFamily="{StaticResource IconFontFamily}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE946;" />
<TextBlock x:Uid="LyricsWindowSwitchWindowHelp" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
<StackPanel
Margin="20"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Orientation="Horizontal"
Spacing="6">
<HyperlinkButton Click="SettingsHypelinkButton_Click">
<HyperlinkButton.Content>
<TextBlock x:Uid="LyricsWindowSwitchWindowHelp" />
</HyperlinkButton.Content>
</HyperlinkButton>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -40,5 +40,16 @@ namespace BetterLyrics.WinUI3.Controls
await Task.Delay(300);
WindowHook.HideWindow<LyricsWindowSwitchWindow>();
}
private void ShadowRect_Loaded(object sender, RoutedEventArgs e)
{
Shadow.Receivers.Add(ShadowCastGrid);
}
private async void SettingsHypelinkButton_Click(object sender, RoutedEventArgs e)
{
await HideAsync();
WindowHook.OpenOrShowWindow<SettingsWindow>();
}
}
}

View File

@@ -5,9 +5,8 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, List<Color> albumArtLightAccentColors, List<Color> albumArtDarkAccentColors) : EventArgs
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, List<Color> albumArtLightAccentColors, List<Color> albumArtDarkAccentColors) : EventArgs
{
public byte[]? Bytes { get; set; } = bytes;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
public List<Color> AlbumArtLightAccentColors { get; set; } = albumArtLightAccentColors;
public List<Color> AlbumArtDarkAccentColors { get; set; } = albumArtDarkAccentColors;

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
@@ -6,7 +7,7 @@ namespace BetterLyrics.WinUI3.Extensions
{
public static class DisposableObjectExtension
{
extension(IDisposable obj)
extension(IDisposable? obj)
{
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
public bool IsDisposed()

View File

@@ -7,6 +7,13 @@ namespace BetterLyrics.WinUI3.Extensions
{
public static class SongInfoExtensions
{
public static SongInfo Placeholder => new SongInfo
{
Title = "N/A",
Album = "N/A",
Artists = ["N/A"],
};
extension(SongInfo songInfo)
{
public SongInfo WithTitle(string value)
@@ -15,9 +22,9 @@ namespace BetterLyrics.WinUI3.Extensions
return songInfo;
}
public SongInfo WithArtist(string value)
public SongInfo WithArtist(string[] value)
{
songInfo.Artist = value;
songInfo.Artists = value;
return songInfo;
}

View File

@@ -6,12 +6,10 @@ using Microsoft.UI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Numerics;
using Vanara.PInvoke;
using Color = Windows.UI.Color;
using Windows.UI;
namespace BetterLyrics.WinUI3.Helper
{
@@ -135,8 +133,8 @@ namespace BetterLyrics.WinUI3.Helper
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
{
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
using Graphics gDest = Graphics.FromImage(bmp);
using System.Drawing.Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
using var gDest = System.Drawing.Graphics.FromImage(bmp);
IntPtr hdcDest = gDest.GetHdc();
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
@@ -149,7 +147,7 @@ namespace BetterLyrics.WinUI3.Helper
return ComputeAverageColor(bmp);
}
private static Color ComputeAverageColor(Bitmap bmp)
private static Color ComputeAverageColor(System.Drawing.Bitmap bmp)
{
long r = 0, g = 0, b = 0;
int count = 0;
@@ -169,5 +167,7 @@ namespace BetterLyrics.WinUI3.Helper
if (count == 0) return Colors.Transparent;
return Color.FromArgb(255, (byte)(r / count), (byte)(g / count), (byte)(b / count));
}
public static Color FromVector3(Vector3 vector3) => Color.FromArgb(255, (byte)vector3.X, (byte)vector3.Y, (byte)vector3.Z);
}
}

View File

@@ -36,9 +36,9 @@ namespace BetterLyrics.WinUI3.Helper
return sb.ToString();
}
public static string? ReadLyricsCache(string title, string artist, string album, LyricsFormat format, string cacheFolderPath)
public static string? ReadLyricsCache(SongInfo songInfo, LyricsFormat format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title} - {album}{format.ToFileExtension()}"));
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.DisplayArtists} - {songInfo.Title} - {songInfo.Album}{format.ToFileExtension()}"));
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
@@ -58,13 +58,13 @@ namespace BetterLyrics.WinUI3.Helper
public static void WriteLyricsCache(SongInfo songInfo, string lyrics, LyricsFormat format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.Artist} - {songInfo.Title} - {songInfo.Album}{format.ToFileExtension()}"));
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.DisplayArtists} - {songInfo.Title} - {songInfo.Album}{format.ToFileExtension()}"));
File.WriteAllText(cacheFilePath, lyrics);
}
public static void WriteAlbumArtCache(string album, string artist, byte[] img, string format, string cacheFolderPath)
public static void WriteAlbumArtCache(SongInfo songInfo, byte[] img, string format, string cacheFolderPath)
{
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{songInfo.DisplayArtists} - {songInfo.Album}{format}"));
File.WriteAllBytes(cacheFilePath, img);
}

View File

@@ -3,8 +3,10 @@
using BetterLyrics.WinUI3.Enums;
using Impressionist.Abstractions;
using Microsoft.Graphics.Canvas;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Numerics;
@@ -18,26 +20,6 @@ namespace BetterLyrics.WinUI3.Helper
{
public class ImageHelper
{
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
{
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
stream.Seek(0);
return stream;
}
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
{
using var stream = new InMemoryRandomAccessStream();
using var writer = new DataWriter(stream);
writer.WriteBytes(bytes);
writer.StoreAsync().GetAwaiter().GetResult();
writer.FlushAsync().GetAwaiter().GetResult();
writer.DetachStream();
return RandomAccessStreamReference.CreateFromStream(stream);
}
public static async Task<IRandomAccessStream> GetAlbumArtPlaceholderAsync()
{
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(PathHelper.AlbumArtPlaceholderPath));
@@ -65,59 +47,6 @@ namespace BetterLyrics.WinUI3.Helper
};
}
public static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
{
var pixelDataProvider = await bitmapDecoder.GetPixelDataAsync();
var pixels = pixelDataProvider.DetachPixelData();
var count = bitmapDecoder.PixelWidth * bitmapDecoder.PixelHeight;
var vector = new Dictionary<Vector3, int>();
for (int i = 0; i < count; i += 10)
{
var offset = i * 4;
var b = pixels[offset];
var g = pixels[offset + 1];
var r = pixels[offset + 2];
var a = pixels[offset + 3];
if (a == 0) continue;
var color = new Vector3(r, g, b);
if (vector.ContainsKey(color))
{
vector[color]++;
}
else
{
vector[color] = 1;
}
}
return vector;
}
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
//{
// var stream = new InMemoryRandomAccessStream();
// await stream.WriteAsync(imageBytes.AsBuffer());
// stream.Seek(0);
// var bitmapImage = new BitmapImage();
// await bitmapImage.SetSourceAsync(stream);
// return bitmapImage;
//}
//public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
// await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
//public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
//{
// if (imageBytes == null || imageBytes.Length == 0)
// return null;
// InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
// await stream.WriteAsync(imageBytes.AsBuffer());
// return stream;
//}
public static async Task<IBuffer> ToBufferAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
@@ -127,26 +56,8 @@ namespace BetterLyrics.WinUI3.Helper
return buffer;
}
public static double GetAverageLuminance(CanvasBitmap bitmap)
{
var pixels = bitmap.GetPixelBytes();
double sum = 0;
for (int i = 0; i < pixels.Length; i += 4)
{
// BGRA
byte b = pixels[i];
byte g = pixels[i + 1];
byte r = pixels[i + 2];
// 忽略A
double y = 0.299 * r + 0.587 * g + 0.114 * b;
sum += y / 255.0;
}
return (double)(sum / (pixels.Length / 4));
}
public static async Task<BitmapDecoder> MakeSquareWithThemeColor(IBuffer buffer, PaletteGeneratorType generatorType)
{
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(buffer);
var decoder = await BitmapDecoder.CreateAsync(stream);
@@ -183,69 +94,6 @@ namespace BetterLyrics.WinUI3.Helper
}
public static async Task<IBuffer> Resize(IBuffer buffer, int size)
{
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(buffer);
var decoder = await BitmapDecoder.CreateAsync(stream);
var factor = Math.Max((double)size / decoder.PixelWidth, (double)size / decoder.PixelHeight);
var width = (uint)(decoder.PixelWidth * factor);
var height = (uint)(decoder.PixelHeight * factor);
if (factor > 1)
{
var transform = new BitmapTransform()
{
ScaledWidth = width,
ScaledHeight = height,
InterpolationMode = BitmapInterpolationMode.Fant
};
var pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
transform, ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.ColorManageToSRgb);
var pixels = pixelData.DetachPixelData();
stream.Seek(0);
stream.Size = 0;
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96, 96, pixels);
await encoder.FlushAsync();
var output = new Windows.Storage.Streams.Buffer((uint)stream.Size);
stream.Seek(0);
await stream.ReadAsync(output, (uint)stream.Size, InputStreamOptions.None);
return output;
}
else
{
var transform = new BitmapTransform()
{
ScaledWidth = (uint)width,
ScaledHeight = (uint)height,
InterpolationMode = BitmapInterpolationMode.NearestNeighbor
};
var pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
transform, ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.ColorManageToSRgb);
var pixels = pixelData.DetachPixelData();
stream.Seek(0);
stream.Size = 0;
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96, 96, pixels);
await encoder.FlushAsync();
var output = new Windows.Storage.Streams.Buffer((uint)stream.Size);
stream.Seek(0);
await stream.ReadAsync(output, (uint)stream.Size, InputStreamOptions.None);
return output;
}
}
public static byte[] GenerateNoiseBGRA(int width, int height)
{
var random = new Random();
@@ -335,5 +183,10 @@ namespace BetterLyrics.WinUI3.Helper
return null;
}
}
public static IRandomAccessStream ToIRandomAccessStream(IBuffer buffer)
{
return buffer.AsStream().AsRandomAccessStream();
}
}
}

View File

@@ -1,8 +1,10 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models;
using Lyricify.Lyrics.Parsers;
using NTextCat.Commons;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,28 +17,30 @@ namespace BetterLyrics.WinUI3.Helper
{
public List<LyricsData> LyricsDataArr { get; private set; } = [];
public void Parse(List<MappedSongSearchQuery> mappedSongSearchQueries, string title, string artist, string album, string? raw, int? durationMs, LyricsSearchProvider? lyricsSearchProvider)
public void Parse(List<MappedSongSearchQuery> mappedSongSearchQueries, Models.SongInfo songInfo, string? raw, LyricsSearchProvider? lyricsSearchProvider)
{
var overridenTitle = title;
var overridenArtist = artist;
var overridenAlbum = album;
var overridenTitle = songInfo.Title;
var overridenArtist = songInfo.Artists;
var overridenAlbum = songInfo.Album;
var found = mappedSongSearchQueries
.Where(x => x.OriginalTitle == overridenTitle && x.OriginalArtist == overridenArtist && x.OriginalAlbum == overridenAlbum)
.Where(x =>
x.OriginalTitle == overridenTitle &&
x.OriginalArtist == overridenArtist.Join(ATL.Settings.DisplayValueSeparator.ToString()) &&
x.OriginalAlbum == overridenAlbum)
.FirstOrDefault();
if (found != null)
{
overridenTitle = found.MappedTitle;
overridenArtist = found.MappedArtist;
overridenArtist = found.MappedArtist.Split(ATL.Settings.DisplayValueSeparator);
overridenAlbum = found.MappedAlbum;
}
LyricsDataArr = [];
durationMs ??= (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
if (raw == null)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder(durationMs.Value));
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder((int)songInfo.DurationMs));
}
else
{
@@ -60,22 +64,27 @@ namespace BetterLyrics.WinUI3.Helper
}
}
FillRomanizationLyricsData();
FillTranslationFromCache(overridenTitle, overridenArtist, overridenAlbum, lyricsSearchProvider);
FillTranslationFromCache(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtist)
.WithAlbum(overridenAlbum),
lyricsSearchProvider);
}
private void FillTranslationFromCache(string title, string artist, string album, LyricsSearchProvider? provider)
private void FillTranslationFromCache(SongInfo songInfo, LyricsSearchProvider? provider)
{
string? translationRaw = null;
switch (provider)
{
case LyricsSearchProvider.QQ:
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
translationRaw = FileHelper.ReadLyricsCache(songInfo, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
break;
case LyricsSearchProvider.Kugou:
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.KugouTranslationCacheDirectory);
translationRaw = FileHelper.ReadLyricsCache(songInfo, LyricsFormat.Lrc, PathHelper.KugouTranslationCacheDirectory);
break;
case LyricsSearchProvider.Netease:
translationRaw = FileHelper.ReadLyricsCache(title, artist, album, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
translationRaw = FileHelper.ReadLyricsCache(songInfo, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
break;
case LyricsSearchProvider.LrcLib:
break;
@@ -215,12 +224,12 @@ namespace BetterLyrics.WinUI3.Helper
int? lineStartTime = null;
if (bracketMatches.Count > 0)
{
var m = bracketMatches![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[4].Value.PadRight(3, '0'));
lineStartTime = min * 60_000 + sec * 1000 + ms;
content = bracketRegex!.Replace(line, "");
content = bracketRegex!.Replace(line, "").Trim();
if (content == "//") content = "";
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using NTextCat.Commons;
namespace BetterLyrics.WinUI3.Models
{
@@ -9,17 +10,19 @@ namespace BetterLyrics.WinUI3.Models
public string? Raw { get; set; }
public string? Title { get; set; }
public string? Artist { get; set; }
public string[]? Artists { get; set; }
public string? Album { get; set; }
public bool IsFound => !string.IsNullOrEmpty(Raw);
public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
public string? DisplayArtists => Artists?.Join("; ");
public void CopyFromSongInfo(SongInfo songInfo)
{
Title = songInfo.Title;
Artist = songInfo.Artist;
Artists = songInfo.Artists;
Album = songInfo.Album;
}
}

View File

@@ -265,7 +265,7 @@ namespace BetterLyrics.WinUI3.Models
{
Name = _resourceService.GetLocalizedString("FullscreenMode"),
IsBorderless = true,
IsAlwaysOnTop = true,
IsAlwaysOnTop = false,
TitleBarArea = TitleBarArea.None,
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
LyricsStyleSettings = new LyricsStyleSettings

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
using NTextCat.Commons;
using System;
namespace BetterLyrics.WinUI3.Models
@@ -11,7 +12,7 @@ namespace BetterLyrics.WinUI3.Models
public partial string Album { get; set; }
[ObservableProperty]
public partial string Artist { get; set; }
public partial string[] Artists { get; set; }
[ObservableProperty]
public partial double DurationMs { get; set; }
@@ -29,6 +30,8 @@ namespace BetterLyrics.WinUI3.Models
public double Duration => DurationMs / 1000;
public string DisplayArtists => Artists.Join(ATL.Settings.DisplayValueSeparator.ToString());
public SongInfo() { }
public object Clone()
@@ -36,7 +39,7 @@ namespace BetterLyrics.WinUI3.Models
return new SongInfo()
{
Title = this.Title,
Artist = this.Artist,
Artists = this.Artists,
Album = this.Album,
DurationMs = this.DurationMs,
PlayerId = this.PlayerId,
@@ -49,7 +52,7 @@ namespace BetterLyrics.WinUI3.Models
{
return
$"Title: {Title}\n" +
$"Artist: {Artist}\n" +
$"Artist: {Artists}\n" +
$"Album: {Album}\n" +
$"Duration: {Duration} sec\n" +
$"Plauer ID: {PlayerId}\n" +
@@ -57,14 +60,4 @@ namespace BetterLyrics.WinUI3.Models
$"Linked file name: {LinkedFileName}";
}
}
public static class SongInfoExtensions
{
public static SongInfo Placeholder => new()
{
Title = "N/A",
Album = "N/A",
Artist = "N/A",
};
}
}

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -26,20 +27,20 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
private readonly ISettingsService _settingsService;
private readonly ILogger _logger;
public AlbumArtSearchService(ISettingsService settingsService)
public AlbumArtSearchService(ISettingsService settingsService, ILogger<AlbumArtSearchService> logger)
{
_settingsService = settingsService;
_logger = Ioc.Default.GetRequiredService<ILogger<AlbumArtSearchService>>();
_logger = logger;
_iTunesHttpClinet = new();
}
public async Task<IBuffer?> SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token)
public async Task<IBuffer?> SearchAsync(SongInfo songInfo, IBuffer? bufferFromSMTC, CancellationToken token)
{
IBuffer? result = null;
try
{
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.AlbumArtSearchProvidersInfo ?? [])
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == songInfo.PlayerId).FirstOrDefault()?.AlbumArtSearchProvidersInfo ?? [])
{
if (!provider.IsEnabled)
{
@@ -49,7 +50,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
switch (provider.Provider)
{
case AlbumArtSearchProvider.Local:
result = SearchFile(artist, title)?.AsBuffer();
result = SearchFile(songInfo)?.AsBuffer();
break;
case AlbumArtSearchProvider.SMTC:
result = bufferFromSMTC;
@@ -57,7 +58,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
case AlbumArtSearchProvider.iTunes:
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
{
var byteArray = await SearchiTunesAsync(artist, album, title, countryCode);
var byteArray = await SearchiTunesAsync(songInfo, countryCode);
result = byteArray?.AsBuffer();
if (token.IsCancellationRequested) return result;
if (result != null) break;
@@ -77,7 +78,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
return null;
}
private byte[]? SearchFile(string artist, string title)
private byte[]? SearchFile(SongInfo songInfo)
{
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
{
@@ -88,7 +89,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
Track track = new(file);
if ((track.Title == title && track.Artist == artist) || FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), artist, title))
if ((track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists) || FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.DisplayArtists, songInfo.Title))
{
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
if (bytes != null)
@@ -103,13 +104,13 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
return null;
}
private async Task<byte[]?> SearchiTunesAsync(string artist, string album, string title, string countryCode)
private async Task<byte[]?> SearchiTunesAsync(SongInfo songInfo, string countryCode)
{
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
try
{
string format = ".jpg";
var cachedAlbumArt = FileHelper.ReadAlbumArtCache(artist, album, format, PathHelper.iTunesAlbumArtCacheDirectory);
var cachedAlbumArt = FileHelper.ReadAlbumArtCache(songInfo.DisplayArtists, songInfo.Album, format, PathHelper.iTunesAlbumArtCacheDirectory);
if (cachedAlbumArt != null)
{
@@ -117,7 +118,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
}
// Build the iTunes API URL
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{songInfo.Artists} {songInfo.Album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
// Make a request to the API
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
@@ -139,7 +140,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
if (fetched != null && fetched.Length > 0)
{
// Write to cache
FileHelper.WriteAlbumArtCache(artist, album, fetched, format, PathHelper.iTunesAlbumArtCacheDirectory);
FileHelper.WriteAlbumArtCache(songInfo, fetched, format, PathHelper.iTunesAlbumArtCacheDirectory);
return fetched;
}
}
@@ -147,7 +148,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
_logger.LogError(ex, "SearchiTunesAsync");
}
return null;
}

View File

@@ -1,4 +1,5 @@
using System.Threading;
using BetterLyrics.WinUI3.Models;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage.Streams;
@@ -6,6 +7,6 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public interface IAlbumArtSearchService
{
Task<IBuffer?> SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token);
Task<IBuffer?> SearchAsync(SongInfo songInfo, IBuffer? bufferFromSMTC, CancellationToken token);
}
}

View File

@@ -37,7 +37,7 @@ namespace BetterLyrics.WinUI3.Services.DiscordService
SmallImageKey = "logo"
},
Details = songInfo.Title,
State = songInfo.Artist,
State = string.Join("; ", songInfo.Artists),
Timestamps = Timestamps.FromTimeSpan(songInfo.Duration)
});
}

View File

@@ -133,7 +133,7 @@ namespace BetterLyrics.WinUI3.Services.LastFMService
await _client.Track.ScrobbleAsync(new Hqub.Lastfm.Entities.Scrobble
{
Track = songInfo.Title,
Artist = songInfo.Artist,
Artist = songInfo.DisplayArtists,
Date = DateTime.Now,
});
}

View File

@@ -33,10 +33,10 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private readonly ISettingsService _settingsService;
private readonly ILogger _logger;
public LyricsSearchService(ISettingsService settingsService)
public LyricsSearchService(ISettingsService settingsService, ILogger<LyricsSearchService> logger)
{
_settingsService = settingsService;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsSearchService>>();
_logger = logger;
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
@@ -96,20 +96,22 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
var lyricsSearchResult = new LyricsSearchResult();
string overridenTitle = songInfo.Title;
string overridenArtist = songInfo.Artist;
string[] overridenArtists = songInfo.Artists;
string overridenAlbum = songInfo.Album;
_logger.LogInformation("Searching lyrics for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)",
overridenTitle, overridenArtist, overridenAlbum, songInfo.DurationMs);
_logger.LogInformation("SearchSmartlyAsync {SongInfo}", songInfo);
var found = _settingsService.AppSettings.MappedSongSearchQueries
.Where(x => x.OriginalTitle == overridenTitle && x.OriginalArtist == overridenArtist && x.OriginalAlbum == overridenAlbum)
.Where(x =>
x.OriginalTitle == overridenTitle &&
x.OriginalArtist == overridenArtists.Join(ATL.Settings.DisplayValueSeparator.ToString()) &&
x.OriginalAlbum == overridenAlbum)
.FirstOrDefault();
if (found != null)
{
overridenTitle = found.MappedTitle;
overridenArtist = found.MappedArtist;
overridenArtists = found.MappedArtist.Split(ATL.Settings.DisplayValueSeparator);
overridenAlbum = found.MappedAlbum;
_logger.LogInformation("Found mapped song search query: {MappedSongSearchQuery}", found);
@@ -118,7 +120,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (pureMusic)
{
lyricsSearchResult.Title = overridenTitle;
lyricsSearchResult.Artist = overridenArtist;
lyricsSearchResult.Artists = overridenArtists;
lyricsSearchResult.Album = overridenAlbum;
lyricsSearchResult.Raw = "[99:00.000]🎶🎶🎶";
return lyricsSearchResult;
@@ -130,7 +132,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return await SearchSingleAsync(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtist)
.WithArtist(overridenArtists)
.WithAlbum(overridenAlbum),
targetProvider.Value, token);
}
@@ -145,7 +147,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult = await SearchSingleAsync(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtist)
.WithArtist(overridenArtists)
.WithAlbum(overridenAlbum),
provider.Provider, token);
@@ -160,7 +162,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
public async Task<List<LyricsSearchResult>> SearchAllAsync(SongInfo songInfo, CancellationToken token)
{
_logger.LogInformation("Searching all lyrics for: {SongInfo}", songInfo);
_logger.LogInformation("SearchAllAsync {SongInfo}", songInfo);
var results = new List<LyricsSearchResult>();
foreach (var provider in Enum.GetValues<LyricsSearchProvider>())
{
@@ -184,7 +186,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
// Check cache first
if (provider.IsRemote())
{
var cachedLyrics = FileHelper.ReadLyricsCache(songInfo.Title, songInfo.Artist, songInfo.Album, lyricsFormat, provider.GetCacheDirectory());
var cachedLyrics = FileHelper.ReadLyricsCache(songInfo, lyricsFormat, provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
lyricsSearchResult.Raw = cachedLyrics;
@@ -267,7 +269,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
{
var fileName = Path.GetFileNameWithoutExtension(file);
if (FileHelper.IsSwitchableNormalizedMatch(fileName, songInfo.Title, songInfo.Artist) || songInfo.LinkedFileName == fileName)
if (FileHelper.IsSwitchableNormalizedMatch(fileName, songInfo.Title, songInfo.DisplayArtists) || songInfo.LinkedFileName == fileName)
{
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
if (raw != null)
@@ -304,9 +306,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
if ((songInfo.Album != "" && track.Title == songInfo.Title && track.Artist == songInfo.Artist && track.Album == songInfo.Album)
|| (songInfo.Album == "" && track.Title == songInfo.Title && track.Artist == songInfo.Artist)
|| (songInfo.Album == "" && FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.Title, songInfo.Artist)))
if ((songInfo.Album != "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists && track.Album == songInfo.Album)
|| (songInfo.Album == "" && track.Title == songInfo.Title && track.Artist == songInfo.DisplayArtists)
|| (songInfo.Album == "" && FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), songInfo.Title, songInfo.DisplayArtists)))
{
var plain = track.GetRawLyrics();
if (!plain.IsNullOrEmpty())
@@ -367,7 +369,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (musicName == null || artists == null)
continue;
if (FileHelper.IsSwitchableNormalizedMatch($"{artists} - {musicName}", songInfo.Title, songInfo.Artist))
if (FileHelper.IsSwitchableNormalizedMatch($"{artists} - {musicName}", songInfo.Title, songInfo.DisplayArtists))
{
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
{
@@ -397,7 +399,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = lyrics;
lyricsSearchResult.Title = songInfo.Title;
lyricsSearchResult.Artist = songInfo.Artist;
lyricsSearchResult.Artists = songInfo.Artists;
lyricsSearchResult.Album = songInfo.Album;
}
catch
@@ -418,7 +420,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
var url =
$"https://lrclib.net/api/search?" +
$"track_name={Uri.EscapeDataString(songInfo.Title)}&" +
$"artist_name={Uri.EscapeDataString(songInfo.Artist)}&" +
$"artist_name={Uri.EscapeDataString(songInfo.DisplayArtists)}&" +
$"&album_name={Uri.EscapeDataString(songInfo.Album)}" +
$"&durationMs={Uri.EscapeDataString(songInfo.DurationMs.ToString())}";
@@ -451,7 +453,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = original;
lyricsSearchResult.Title = searchedTitle;
lyricsSearchResult.Artist = searchedArtist;
lyricsSearchResult.Artists = searchedArtist?.Split(ATL.Settings.DisplayValueSeparator);
lyricsSearchResult.Album = searchedAlbum;
return lyricsSearchResult;
@@ -481,7 +483,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
ISearchResult? result;
if (searcher == Searchers.Netease && songInfo.SongId != null)
{
result = new NeteaseSearchResult(songInfo.Title, [songInfo.Artist], songInfo.Album, null, (int)songInfo.DurationMs, songInfo.SongId);
result = new NeteaseSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, songInfo.Artists, (int)songInfo.DurationMs, songInfo.SongId);
}
else
{
@@ -489,10 +491,10 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
DurationMs = (int)songInfo.DurationMs,
Album = songInfo.Album,
AlbumArtists = [songInfo.Artist],
Artists = [songInfo.Artist],
AlbumArtists = songInfo.Artists.ToList(),
Artists = songInfo.Artists.ToList(),
Title = songInfo.Title,
}, searcher);
}, searcher, Lyricify.Lyrics.Searchers.Helpers.CompareHelper.MatchType.VeryHigh);
}
if (result != null)
@@ -573,7 +575,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
lyricsSearchResult.Title = result?.Title;
lyricsSearchResult.Artist = result?.Artists.Join(" | ");
lyricsSearchResult.Artists = result?.Artists;
lyricsSearchResult.Album = result?.Album;
return lyricsSearchResult;
@@ -588,11 +590,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (await _appleMusic.InitAsync())
{
var raw = await _appleMusic.GetLyricsAsync(songInfo.Title, songInfo.Artist);
_logger.LogInformation("Apple Music lyrics search result for {SongInfo}: {Raw}", songInfo, raw);
var raw = await _appleMusic.GetLyricsAsync(songInfo.Title, songInfo.DisplayArtists);
_logger.LogInformation("SearchAppleMusicAsync");
lyricsSearchResult.Raw = raw;
lyricsSearchResult.Title = songInfo.Title;
lyricsSearchResult.Artist = songInfo.Artist;
lyricsSearchResult.Artists = songInfo.Artists;
lyricsSearchResult.Album = "";
}

View File

@@ -4,15 +4,18 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public interface IMediaSessionsService : INotifyPropertyChanged
{
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
Task PlayAsync();
@@ -27,11 +30,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
void InitPlaybackShortcuts();
MediaSourceProviderInfo? CurrentMediaSourceProviderInfo { get; }
bool CurrentIsPlaying { get; }
SongInfo? CurrentSongInfo { get; }
TimeSpan CurrentPosition { get; }
LyricsData? CurrentLyricsData { get; }
SoftwareBitmap? SoftwareBitmap { get; }
List<Color> LightAccentColors { get; }
List<Color> DarkAccentColors { get; }
TranslationSearchProvider? TranslationSearchProvider { get; }
LyricsSearchResult? CurrentLyricsSearchResult { get; }
}

View File

@@ -1,12 +1,21 @@
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Helpers;
using DevWinUI;
using Microsoft.Extensions.Logging;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
@@ -14,30 +23,28 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SoftwareBitmap? SoftwareBitmap { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<Color> LightAccentColors { get; set; } = Enumerable.Repeat(Colors.Black, 4).ToList();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<Color> DarkAccentColors { get; set; } = Enumerable.Repeat(Colors.Black, 4).ToList();
private void UpdateAlbumArt()
{
_albumArtRefreshRunner.RunAsync(RefreshArtAlbum);
_ = _albumArtRefreshRunner.RunAsync(RefreshArtAlbum);
}
private async Task RefreshArtAlbum(CancellationToken token)
{
_logger.LogInformation("RefreshArtAlbum");
if (CurrentSongInfo == null)
{
_logger.LogWarning("Cached song info is null, cannot update album art.");
_logger.LogWarning("CurrentSongInfo == null");
return;
}
IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(
CurrentSongInfo?.PlayerId ?? "",
CurrentSongInfo.Title,
CurrentSongInfo.Artist,
CurrentSongInfo.Album,
_SMTCAlbumArtBuffer,
token
), token);
IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(CurrentSongInfo, _SMTCAlbumArtBuffer, token), token);
if (token.IsCancellationRequested) return;
BitmapDecoder? decoder = null;
if (buffer == null)
@@ -45,22 +52,27 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
using var placeHolderStream = await ImageHelper.GetAlbumArtPlaceholderAsync();
var tempBuffer = new Windows.Storage.Streams.Buffer((uint)placeHolderStream.Size);
await placeHolderStream.ReadAsync(tempBuffer, (uint)placeHolderStream.Size, InputStreamOptions.None);
if (token.IsCancellationRequested) return;
buffer = tempBuffer;
token.ThrowIfCancellationRequested();
}
decoder = await ImageHelper.MakeSquareWithThemeColor(buffer, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
token.ThrowIfCancellationRequested();
if (token.IsCancellationRequested) return;
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
if (token.IsCancellationRequested) return;
albumArtSwBitmap.DpiX = 96;
albumArtSwBitmap.DpiY = 96;
token.ThrowIfCancellationRequested();
var albumArtLightAccentColors = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
var lightColorBytes = albumArtLightAccentColors.Palette.Select(t => Windows.UI.Color.FromArgb(255, (byte)t.X, (byte)t.Y, (byte)t.Z)).ToList();
var albumArtDarkAccentColors = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
var darkColorBytes = albumArtDarkAccentColors.Palette.Select(t => Windows.UI.Color.FromArgb(255, (byte)t.X, (byte)t.Y, (byte)t.Z)).ToList();
AlbumArtChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, lightColorBytes, darkColorBytes));
var lightPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
var darkPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
if (token.IsCancellationRequested) return;
SoftwareBitmap = albumArtSwBitmap;
LightAccentColors = lightPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
DarkAccentColors = darkPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
}
}
}

View File

@@ -4,6 +4,7 @@ using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI;
using Lyricify.Lyrics.Helpers.General;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
@@ -61,7 +62,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (!_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled) return;
_logger.LogInformation("Showing translation for lyrics...");
_logger.LogInformation("SetTranslatedTextAsync");
string targetLangCode = _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode;
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
@@ -74,7 +75,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
_lyricsDataArr[0].ClearTranslatedText();
_lyricsDataArr.FirstOrDefault()?.ClearTranslatedText();
}
else
{
@@ -84,7 +85,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_logger.LogInformation("Found translated text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr[0].SetTranslatedText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
_lyricsDataArr.FirstOrDefault()?.SetTranslatedText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
TranslationSearchProvider = CurrentLyricsSearchResult?.Provider.ToTranslationSearchProvider();
}
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
@@ -97,7 +98,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (token.IsCancellationRequested) return;
if (translated == string.Empty) return;
_lyricsDataArr[0].SetTranslation(translated, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator);
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator);
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
}
@@ -131,7 +132,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (targetPhoneticCode == "")
{
_lyricsDataArr[0].ClearPhoneticText();
_lyricsDataArr.FirstOrDefault()?.ClearPhoneticText();
}
// Try get phonetic text from itself
@@ -139,14 +140,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (found >= 0)
{
_logger.LogInformation("Found phonetic text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr[0].SetPhoneticText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
_lyricsDataArr.FirstOrDefault()?.SetPhoneticText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
}
}
private async Task RefreshLyricsAsync(CancellationToken token)
{
_logger.LogInformation("Refreshing lyrics...");
_logger.LogInformation("RefreshLyricsAsync");
CurrentLyricsSearchResult = null;
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
@@ -158,25 +159,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (CurrentSongInfo != null)
{
_logger.LogInformation("Searching lyrics for: Title={Title}, Artist={Artist}, Album={Album}, DurationMs={DurationMs}",
CurrentSongInfo.Title, CurrentSongInfo.Artist, CurrentSongInfo.Album, CurrentSongInfo.DurationMs);
CurrentLyricsSearchResult = await Task.Run(async () => await _lyrcsSearchService.SearchSmartlyAsync(CurrentSongInfo, token), token);
if (token.IsCancellationRequested) return;
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", CurrentLyricsSearchResult?.IsFound, CurrentLyricsSearchResult?.Provider);
var lyricsParser = new LyricsParser();
lyricsParser.Parse(
_settingsService.AppSettings.MappedSongSearchQueries.ToList(),
CurrentSongInfo.Title, CurrentSongInfo.Artist, CurrentSongInfo.Album, CurrentLyricsSearchResult?.Raw, (int?)CurrentSongInfo?.DurationMs, CurrentLyricsSearchResult?.Provider);
CurrentSongInfo, CurrentLyricsSearchResult?.Raw, CurrentLyricsSearchResult?.Provider);
_lyricsDataArr = lyricsParser.LyricsDataArr;
ApplyChinesePreference();
}
else
{
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
}
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
@@ -206,12 +198,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public void UpdateLyrics()
{
_refreshLyricsRunner.RunAsync(RefreshLyricsAsync);
_ = _refreshLyricsRunner.RunAsync(RefreshLyricsAsync);
}
public void UpdateTranslations()
{
_refreshTranslationRunner.RunAsync(RefreshTranslationAsync);
_ = _refreshTranslationRunner.RunAsync(RefreshTranslationAsync);
}
}
}

View File

@@ -34,6 +34,8 @@ using Windows.Storage.Streams;
using WindowsMediaController;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Extensions;
using CommunityToolkit.WinUI;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
@@ -62,6 +64,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private double _lxMusicPositionSeconds = 0;
private byte[]? _lxMusicAlbumArtBytes = null;
private readonly DispatcherQueueTimer? _onMediaPropsChangedTimer;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool CurrentIsPlaying { get; private set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TimeSpan CurrentPosition { get; private set; } = TimeSpan.Zero;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SongInfo? CurrentSongInfo { get; private set; }
@@ -76,7 +80,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
ILiveStatesService liveStatesService,
IDiscordService discordService,
ITranslateService libreTranslateService,
IResourceService resourceService)
IResourceService resourceService,
ILogger<MediaSessionsService> logger)
{
_settingsService = settingsService;
_albumArtSearchService = albumArtSearchService;
@@ -86,7 +91,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_liveStatesService = liveStatesService;
_discordService = discordService;
_resourceService = resourceService;
_logger = Ioc.Default.GetRequiredService<ILogger<MediaSessionsService>>();
_logger = logger;
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
@@ -272,107 +279,109 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProperties)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
_onMediaPropsChangedTimer?.Debounce(() =>
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null)
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
{
CurrentSongInfo = SongInfoExtensions.Placeholder;
}
string? sessionId = mediaSession?.Id;
var desiredSession = GetCurrentSession();
if (mediaSession != desiredSession) return;
if (sessionId != null && !IsMediaSourceEnabled(sessionId))
{
CurrentSongInfo = SongInfoExtensions.Placeholder;
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (PlayerIDMatcher.IsLXMusic(sessionId))
if (!_mediaManager.IsStarted) return;
if (mediaSession == null)
{
StopSSE();
CurrentSongInfo = SongInfoExtensions.Placeholder;
}
_SMTCAlbumArtBuffer = null;
}
else
{
var currentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
if (currentMediaSourceProviderInfo?.ResetPositionOffsetOnSongChanged == true)
{
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
string? sessionId = mediaSession?.Id;
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? songId = null;
var desiredSession = GetCurrentSession();
if (PlayerIDMatcher.IsAppleMusic(sessionId))
{
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
}
else if (PlayerIDMatcher.IsNeteaseFamily(sessionId))
{
songId = mediaProperties?.Genres
.Where(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?.FirstOrDefault()?
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
}
if (mediaSession != desiredSession) return;
var linkedFileName = mediaProperties?.Genres
.Where(x => x.StartsWith(ExtendedGenreFiled.FileName))?.FirstOrDefault()?
.Replace(ExtendedGenreFiled.FileName, "");
if (sessionId != null && !IsMediaSourceEnabled(sessionId))
{
CurrentSongInfo = SongInfoExtensions.Placeholder;
CurrentSongInfo = new SongInfo
{
Title = mediaProperties?.Title ?? "N/A",
Artist = fixedArtist,
Album = fixedAlbum,
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds ?? 0,
PlayerId = sessionId,
SongId = songId,
LinkedFileName = linkedFileName
};
if (PlayerIDMatcher.IsLXMusic(sessionId))
{
StopSSE();
}
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (PlayerIDMatcher.IsLXMusic(sessionId))
{
StartSSE();
}
else
{
StopSSE();
}
if (PlayerIDMatcher.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
{
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
}
else if (mediaProperties?.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
}
else
{
_SMTCAlbumArtBuffer = null;
}
}
else
{
var currentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
if (currentMediaSourceProviderInfo?.ResetPositionOffsetOnSongChanged == true)
{
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
CurrentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? songId = null;
UpdateAlbumArt();
UpdateLyrics();
if (PlayerIDMatcher.IsAppleMusic(sessionId))
{
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
}
else if (PlayerIDMatcher.IsNeteaseFamily(sessionId))
{
songId = mediaProperties?.Genres
.Where(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?.FirstOrDefault()?
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
}
UpdateDiscordPresence();
UpdateCurrentMediaSourceProviderInfoPositionOffset();
});
var linkedFileName = mediaProperties?.Genres
.Where(x => x.StartsWith(ExtendedGenreFiled.FileName))?.FirstOrDefault()?
.Replace(ExtendedGenreFiled.FileName, "");
CurrentSongInfo = new SongInfo
{
Title = mediaProperties?.Title ?? "N/A",
Artists = fixedArtist.Split(ATL.Settings.DisplayValueSeparator),
Album = fixedAlbum,
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds ?? 0,
PlayerId = sessionId,
SongId = songId,
LinkedFileName = linkedFileName
};
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (PlayerIDMatcher.IsLXMusic(sessionId))
{
StartSSE();
}
else
{
StopSSE();
}
if (PlayerIDMatcher.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
{
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
}
else if (mediaProperties?.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
}
else
{
_SMTCAlbumArtBuffer = null;
}
}
_logger.LogInformation("MediaManager_OnAnyMediaPropertyChanged {SongInfo}", CurrentSongInfo);
CurrentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
UpdateAlbumArt();
UpdateLyrics();
UpdateDiscordPresence();
UpdateCurrentMediaSourceProviderInfoPositionOffset();
});
}, Time.DebounceTimeout);
}
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)

View File

@@ -5,7 +5,6 @@ using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -25,10 +24,13 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ThrottleHelper _timelineThrottle = new(TimeSpan.FromSeconds(1));
public LyricsPageViewModel(IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService)
public LyricsRendererViewModel.LyricsRendererViewModel LyricsRendererViewModel { get; private set; }
public LyricsPageViewModel(IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService, LyricsRendererViewModel.LyricsRendererViewModel lyricsRendererViewModel)
{
_liveStatesService = liveStatesService;
MediaSessionsService = mediaSessionsService;
LyricsRendererViewModel = lyricsRendererViewModel;
LiveStates = _liveStatesService.LiveStates;
@@ -105,7 +107,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.TotalTime))
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
{
if (_timelineThrottle.CanTrigger())
{

View File

@@ -189,11 +189,11 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
ds.Transform = Matrix3x2.CreateRotation((float)_rotateAngle, control.Size.ToVector2() * 0.5f);
if (_albumArtBgEffect != null)
if (_isAlbumArtBgEffectChanged && _albumArtBgEffect != null)
{
ds.DrawImage(_albumArtBgEffect);
}
else if (_albumArtBgRenderTarget != null)
else if (!_isAlbumArtBgEffectChanged && _albumArtBgRenderTarget != null)
{
double targetSize = Math.Sqrt(Math.Pow(_canvasWidth, 2) + Math.Pow(_canvasHeight, 2));
float offsetX = (float)(_canvasWidth - targetSize) / 2;
@@ -208,6 +208,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
// 专辑图封面正在变动,需实时绘制
if (_isAlbumArtEffectChanged && _albumArtEffect != null)
{
ds.DrawImage(new OpacityEffect
@@ -216,6 +217,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
Opacity = (float)_albumArtOpacityTransition.Value
}, new Vector2((float)_albumArtXTransition.Value, (float)_albumArtYTransition.Value));
}
// 专辑图封面不再变动,使用已保存的绘制
else if (!_isAlbumArtEffectChanged && _albumArtRenderTarget != null)
{
// 这里给一个相反的偏移以恢复位置

View File

@@ -115,12 +115,12 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
using var overlappedCovers = new CanvasCommandList(control);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_lastAlbumArtCanvasBitmap != null)
if (_lastAlbumArtCanvasBitmap != null && !_lastAlbumArtCanvasBitmap.IsDisposed())
{
using var lastBgImageEffect = CreateBgImageEffect(_lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
DrawBackgroundImgae(lastBgImageEffect, overlappedCoversDs, _lastAlbumArtCanvasBitmap);
}
if (_albumArtCanvasBitmap != null)
if (_albumArtCanvasBitmap != null && !_albumArtCanvasBitmap.IsDisposed())
{
using var bgImageEffect = CreateBgImageEffect(_albumArtCanvasBitmap, _albumArtBgTransition.Value);
DrawBackgroundImgae(bgImageEffect, overlappedCoversDs, _albumArtCanvasBitmap);
@@ -181,6 +181,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
float offsetX = -(float)(_canvasWidth - targetSize) / 2;
float offsetY = -(float)(_canvasHeight - targetSize) / 2;
ds.Clear(Colors.Transparent);
ds.DrawImage(_albumArtBgEffect, new Vector2(offsetX, offsetY));
}
@@ -251,6 +252,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_albumArtRenderTarget = new CanvasRenderTarget(control, (float)_canvasWidth, (float)_canvasHeight);
using var ds = _albumArtRenderTarget.CreateDrawingSession();
ds.Clear(Colors.Transparent);
// 给一个偏移,是为了避免绘制时从原点开始,这样会造成阴影被裁切
ds.DrawImage(_albumArtEffect, control.Size.ToVector2() / 2 - new Vector2((float)_albumArtSize, (float)_albumArtSize) / 2);
}

View File

@@ -4,8 +4,13 @@ using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using TagLib.Riff;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
@@ -28,8 +33,46 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
IRecipient<PropertyChangedMessage<AlbumArtLayoutSettings>>,
IRecipient<PropertyChangedMessage<LyricsBackgroundSettings>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
IRecipient<PropertyChangedMessage<SongInfo?>>
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<SoftwareBitmap?>>,
IRecipient<PropertyChangedMessage<List<Color>>>
{
private void OnSongInfoChanged()
{
_lastSongTitle = _songTitle;
_songTitle = _mediaSessionsService.CurrentSongInfo?.Title;
_isSongTitleChanged = true;
_lastSongArtists = _songArtists;
_songArtists = _mediaSessionsService.CurrentSongInfo?.DisplayArtists;
_isSongArtistChanged = true;
_lastSongAlbum = _songAlbum;
_songAlbum = _mediaSessionsService.CurrentSongInfo?.Album;
_isSongAlbumChanged = true;
_songDurationMs = (int)(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
TotalTime = TimeSpan.Zero;
// 处理 Last.fm 追踪
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
private void OnSoftwareBitmapChanged()
{
_lastAlbumArtCanvasBitmap?.Dispose();
_lastAlbumArtCanvasBitmap = null;
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
_albumArtSwBitmap = _mediaSessionsService.SoftwareBitmap;
_albumArtChanged = true;
}
public void Receive(PropertyChangedMessage<bool> message)
{
@@ -425,30 +468,12 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
public void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (_mediaSessionsService.CurrentSongInfo?.Title != _songTitle || _mediaSessionsService.CurrentSongInfo?.Artist != _songArtist)
if (message.Sender is IMediaSessionsService)
{
_lastSongTitle = _songTitle;
_songTitle = _mediaSessionsService.CurrentSongInfo?.Title;
_isSongTitleChanged = true;
_lastSongArtist = _songArtist;
_songArtist = _mediaSessionsService.CurrentSongInfo?.Artist;
_isSongArtistChanged = true;
_lastSongAlbum = _songAlbum;
_songAlbum = _mediaSessionsService.CurrentSongInfo?.Album;
_isSongAlbumChanged = true;
_songDurationMs = (int)(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
TotalTime = TimeSpan.Zero;
// 处理 Last.fm 追踪
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
OnSongInfoChanged();
}
}
}
@@ -477,5 +502,31 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
}
}
}
public void Receive(PropertyChangedMessage<SoftwareBitmap?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.SoftwareBitmap))
{
OnSoftwareBitmapChanged();
}
}
}
public void Receive(PropertyChangedMessage<List<Color>> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.LightAccentColors))
{
UpdateColorConfig();
}
else if (message.PropertyName == nameof(IMediaSessionsService.DarkAccentColors))
{
UpdateColorConfig();
}
}
}
}
}

View File

@@ -592,7 +592,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
//double? targetYScrollOffset = -currentPlayingLine!.OriginalPosition.Y + _currentLyricsData?.LyricsLines[0].OriginalPosition.Y - playingTextLayout.LayoutBounds.Height / 2.0;
double? targetYScrollOffset =
-currentPlayingLine!.OriginalPosition.Y
+ _currentLyricsData?.LyricsLines[0].OriginalPosition.Y
+ _currentLyricsData?.LyricsLines.FirstOrDefault()?.OriginalPosition.Y
- (currentPlayingLine.TranslatedPosition.Y + (currentPlayingLine.TranslatedCanvasTextLayout?.LayoutBounds.Height ?? 0) - currentPlayingLine.PhoneticPosition.Y) / 2.0;
if (!targetYScrollOffset.HasValue) return;
@@ -694,20 +694,20 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
_grayedEnvironmentalColor = _lightColor;
_albumArtAccentColor1Transition.StartTransition(_albumArtLightAccentColors.ElementAtOrDefault(0));
_albumArtAccentColor2Transition.StartTransition(_albumArtLightAccentColors.ElementAtOrDefault(1));
_albumArtAccentColor3Transition.StartTransition(_albumArtLightAccentColors.ElementAtOrDefault(2));
_albumArtAccentColor4Transition.StartTransition(_albumArtLightAccentColors.ElementAtOrDefault(3));
_albumArtAccentColor1Transition.StartTransition(_mediaSessionsService.LightAccentColors.ElementAtOrDefault(0));
_albumArtAccentColor2Transition.StartTransition(_mediaSessionsService.LightAccentColors.ElementAtOrDefault(1));
_albumArtAccentColor3Transition.StartTransition(_mediaSessionsService.LightAccentColors.ElementAtOrDefault(2));
_albumArtAccentColor4Transition.StartTransition(_mediaSessionsService.LightAccentColors.ElementAtOrDefault(3));
}
else
{
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
_grayedEnvironmentalColor = _darkColor;
_albumArtAccentColor1Transition.StartTransition(_albumArtDarkAccentColors.ElementAtOrDefault(0));
_albumArtAccentColor2Transition.StartTransition(_albumArtDarkAccentColors.ElementAtOrDefault(1));
_albumArtAccentColor3Transition.StartTransition(_albumArtDarkAccentColors.ElementAtOrDefault(2));
_albumArtAccentColor4Transition.StartTransition(_albumArtDarkAccentColors.ElementAtOrDefault(3));
_albumArtAccentColor1Transition.StartTransition(_mediaSessionsService.DarkAccentColors.ElementAtOrDefault(0));
_albumArtAccentColor2Transition.StartTransition(_mediaSessionsService.DarkAccentColors.ElementAtOrDefault(1));
_albumArtAccentColor3Transition.StartTransition(_mediaSessionsService.DarkAccentColors.ElementAtOrDefault(2));
_albumArtAccentColor4Transition.StartTransition(_mediaSessionsService.DarkAccentColors.ElementAtOrDefault(3));
}
_lyricsBgBrightnessTransition.StartTransition(brightness);
@@ -720,11 +720,11 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
if (isLight)
{
_adaptiveColoredFontColor = _albumArtDarkAccentColors.ElementAtOrDefault(0);
_adaptiveColoredFontColor = _mediaSessionsService.DarkAccentColors.ElementAtOrDefault(0);
}
else
{
_adaptiveColoredFontColor = _albumArtLightAccentColors.ElementAtOrDefault(0);
_adaptiveColoredFontColor = _mediaSessionsService.LightAccentColors.ElementAtOrDefault(0);
}
}
@@ -958,12 +958,12 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_artistTextLayout = null;
_lastArtistTextLayout = new(
control, _lastSongArtist ?? string.Empty,
control, _lastSongArtists ?? string.Empty,
_artistTextFormat, (float)_maxSongInfoWidth, (float)_canvasHeight
);
_artistTextLayout = new(
control, _songArtist ?? string.Empty,
control, _songArtists ?? string.Empty,
_artistTextFormat, (float)_maxSongInfoWidth, (float)_canvasHeight
);
@@ -1320,10 +1320,10 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsCJKFontFamily,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsWesternFontFamily);
_lastArtistTextLayout?.SetFontFamily(_lastSongArtist,
_lastArtistTextLayout?.SetFontFamily(_lastSongArtists,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsCJKFontFamily,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsWesternFontFamily);
_artistTextLayout?.SetFontFamily(_songArtist,
_artistTextLayout?.SetFontFamily(_songArtists,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsCJKFontFamily,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsWesternFontFamily);

View File

@@ -16,6 +16,7 @@ using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using System;
@@ -75,8 +76,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private string? _lastSongTitle;
private string? _songTitle;
private string? _lastSongArtist;
private string? _songArtist;
private string? _lastSongArtists;
private string? _songArtists;
private string? _lastSongAlbum;
private string? _songAlbum;
@@ -111,8 +112,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private Color _adaptiveGrayedFontColor = Colors.Transparent;
private Color? _adaptiveColoredFontColor = null;
private List<Color> _albumArtLightAccentColors = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _albumArtDarkAccentColors = Enumerable.Repeat(Colors.Black, 4).ToList();
private Color _environmentalColor = Colors.Transparent;
private Color _grayedEnvironmentalColor = Colors.Transparent;
@@ -181,6 +180,9 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private Matrix4x4 _lyrics3DMatrix = Matrix4x4.Identity;
private readonly DispatcherQueueTimer? _onSongInfoChangedTimer;
private readonly DispatcherQueueTimer? _onSoftwareBitmapChangedTimer;
public LyricsRendererViewModel(
ISettingsService settingsService,
IMediaSessionsService mediaSessionsService,
@@ -198,7 +200,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
UpdateSongInfoFontSize();
_mediaSessionsService.AlbumArtChanged += MediaSessionsService_AlbumArtChangedChanged;
_mediaSessionsService.LyricsChanged += MediaSessionsService_LyricsChanged;
UpdateColorConfig();
@@ -343,21 +344,5 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
return new Tuple<int, int>(0, _currentLyricsData.LyricsLines.Count - 1);
}
private void MediaSessionsService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
{
_lastAlbumArtCanvasBitmap?.Dispose();
_lastAlbumArtCanvasBitmap = null;
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
_albumArtSwBitmap = e.AlbumArtSwBitmap;
_albumArtChanged = true;
_albumArtLightAccentColors = e.AlbumArtLightAccentColors;
_albumArtDarkAccentColors = e.AlbumArtDarkAccentColors;
UpdateColorConfig();
}
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
@@ -68,10 +69,10 @@ namespace BetterLyrics.WinUI3.ViewModels
MappedSongSearchQuery = new MappedSongSearchQuery
{
OriginalTitle = _mediaSessionsService.CurrentSongInfo.Title,
OriginalArtist = _mediaSessionsService.CurrentSongInfo.Artist,
OriginalArtist = _mediaSessionsService.CurrentSongInfo.DisplayArtists,
OriginalAlbum = _mediaSessionsService.CurrentSongInfo.Album,
MappedTitle = _mediaSessionsService.CurrentSongInfo.Title,
MappedArtist = _mediaSessionsService.CurrentSongInfo.Artist,
MappedArtist = _mediaSessionsService.CurrentSongInfo.DisplayArtists,
MappedAlbum = _mediaSessionsService.CurrentSongInfo.Album,
};
}
@@ -90,7 +91,10 @@ namespace BetterLyrics.WinUI3.ViewModels
}
var found = AppSettings.MappedSongSearchQueries
.Where(x => x.OriginalTitle == _mediaSessionsService.CurrentSongInfo.Title && x.OriginalArtist == _mediaSessionsService.CurrentSongInfo.Artist && x.OriginalAlbum == _mediaSessionsService.CurrentSongInfo.Album);
.Where(x =>
x.OriginalTitle == _mediaSessionsService.CurrentSongInfo.Title &&
x.OriginalArtist == _mediaSessionsService.CurrentSongInfo.DisplayArtists &&
x.OriginalAlbum == _mediaSessionsService.CurrentSongInfo.Album);
return found.FirstOrDefault();
}
@@ -111,12 +115,10 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsSearchResults = [..await Task.Run(async () =>
{
return await _lyricsSearchService.SearchAllAsync(
new SongInfo {
Title = MappedSongSearchQuery.MappedTitle,
Artist = MappedSongSearchQuery.MappedArtist,
Album = MappedSongSearchQuery.MappedAlbum,
DurationMs = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0
}, token);
((SongInfo?)_mediaSessionsService.CurrentSongInfo?.Clone() ?? new())
.WithTitle(MappedSongSearchQuery.MappedTitle)
.WithArtist(MappedSongSearchQuery.MappedArtist.Split(ATL.Settings.DisplayValueSeparator))
.WithAlbum(MappedSongSearchQuery.MappedAlbum), token);
}, token)];
IsSearching = false;
});
@@ -177,10 +179,8 @@ namespace BetterLyrics.WinUI3.ViewModels
var lyricsParser = new LyricsParser();
lyricsParser.Parse(
[MappedSongSearchQuery ?? new()],
MappedSongSearchQuery?.OriginalTitle ?? "",
MappedSongSearchQuery?.OriginalArtist ?? "",
MappedSongSearchQuery?.OriginalAlbum ?? "",
value?.Raw, (int?)_mediaSessionsService.CurrentSongInfo?.DurationMs, value?.Provider);
_mediaSessionsService.CurrentSongInfo,
value?.Raw, value?.Provider);
LyricsDataArr = [.. lyricsParser.LyricsDataArr];
}
else

View File

@@ -10,6 +10,7 @@ using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Numerics;
namespace BetterLyrics.WinUI3.Views