Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad79180e4 | ||
|
|
86118bac02 | ||
|
|
7bfbec4b01 | ||
|
|
a5d6dd1305 | ||
|
|
68f690e1a7 | ||
|
|
42af22a7e3 | ||
|
|
34d7f3f319 | ||
|
|
07b82191d0 | ||
|
|
f8c6060d32 | ||
|
|
bfdb36ff95 | ||
|
|
ce83777c1d | ||
|
|
d709e70fa2 | ||
|
|
8fe4f8fd58 | ||
|
|
b6319e522a | ||
|
|
58d74c1515 | ||
|
|
806f3fdd63 | ||
|
|
90d2055dff |
69
.github/workflows/pages.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll site to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev", "stable"]
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: true
|
||||
cache-version: 0
|
||||
working-directory: "${{ github.workspace }}/docs"
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Build with Jekyll
|
||||
# Outputs to the './_site' directory by default
|
||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
env:
|
||||
JEKYLL_ENV: production
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
20
.github/workflows/releases-to-discord.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: GitHub Releases to Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1
|
||||
with:
|
||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||
color: "2105893"
|
||||
username: "Release Changelog"
|
||||
avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
|
||||
content: "||@everyone||"
|
||||
footer_title: "Changelog"
|
||||
reduce_headings: true
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.9.0" />
|
||||
Version="1.0.11.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
@@ -64,7 +65,7 @@
|
||||
</Style>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,9,16,11" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace BetterLyrics.WinUI3
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
AppInfo.EnsureDirectories();
|
||||
PathHelper.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
@@ -59,16 +59,6 @@ namespace BetterLyrics.WinUI3
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
|
||||
string[] commandLineArguments = Environment.GetCommandLineArgs();
|
||||
if (commandLineArguments.Length > 1)
|
||||
{
|
||||
commandLineArguments = commandLineArguments.Skip(1).ToArray();
|
||||
if (commandLineArguments.First() == AppInfo.UnlockWindowTag)
|
||||
{
|
||||
lyricsWindow.AutoSelectLyricsMode(AutoStartWindowType.DesktopMode, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
|
||||
@@ -76,7 +66,7 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
|
||||
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
// Register services
|
||||
@@ -90,9 +80,10 @@ namespace BetterLyrics.WinUI3
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IPlaybackService, PlaybackService>()
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
|
||||
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
.AddSingleton<ILibreTranslateService, LibreTranslateService>()
|
||||
.AddSingleton<ITranslateService, TranslateService>()
|
||||
// ViewModels
|
||||
.AddSingleton<LyricsWindowViewModel>()
|
||||
.AddSingleton<SettingsWindowViewModel>()
|
||||
|
||||
56094
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Core14.profile.xml
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Discord.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/QQ.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
@@ -19,6 +19,7 @@
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Core14.profile.xml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
@@ -42,13 +43,14 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="iTunesSearch" Version="1.0.44" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is AlbumArtSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
AlbumArtSearchProvider.Local => App.ResourceLoader!.GetString("AlbumArtSearchLocalProvider"),
|
||||
AlbumArtSearchProvider.SMTC => App.ResourceLoader!.GetString("AlbumArtSearchSMTCProvider"),
|
||||
AlbumArtSearchProvider.iTunes => "iTunes",
|
||||
_ => throw new Exception($"Unknown AlbumArtSearchProvider: {provider}"),
|
||||
};
|
||||
}
|
||||
throw new ArgumentException("Value must be of type AlbumArtSearchProvider", nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 AlbumArtSearchProvider
|
||||
{
|
||||
Local,
|
||||
SMTC,
|
||||
iTunes,
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LineRenderingType
|
||||
{
|
||||
UntilCurrentChar,
|
||||
CurrentCharOnly,
|
||||
CurrentChar,
|
||||
LineStartToCurrentChar,
|
||||
CurrentLine
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return null;
|
||||
|
||||
// TTML
|
||||
if (content.StartsWith("<?xml") && System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b"))
|
||||
// TTML: 检查 <tt ... xmlns="http://www.w3.org/ns/ttml"
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"<tt\b[^>]*\bxmlns\s*=\s*[""']http://www\.w3\.org/ns/ttml[""']",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
{
|
||||
return LyricsFormat.Ttml;
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => AppInfo.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => AppInfo.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => AppInfo.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => AppInfo.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => AppInfo.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.LrcLib => PathHelper.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => PathHelper.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum MusicSearchMatchMode
|
||||
{
|
||||
TitleAndArtist,
|
||||
TitleArtistAlbumAndDuration,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum WindowColorSampleMode
|
||||
public enum WindowPixelSampleMode
|
||||
{
|
||||
BelowWindow,
|
||||
WindowArea,
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs : EventArgs
|
||||
{
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
|
||||
public Color? AlbumArtAccentColor { get; set; } = null;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
public static class AppInfo
|
||||
{
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppName = "BetterLyrics";
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
|
||||
|
||||
public const string UnlockWindowTag = "UnlockWindow";
|
||||
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
|
||||
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
|
||||
|
||||
public static string iTunesAlbumArtCacheDirectory => Path.Combine(CacheFolder, "itunes-album-art");
|
||||
|
||||
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
public static async Task<DateTime> GetBuildDate()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var filePath = assembly.Location;
|
||||
if (!File.Exists(filePath))
|
||||
return DateTime.MinValue;
|
||||
|
||||
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
// 获取文件基本属性
|
||||
BasicProperties props = await file.GetBasicPropertiesAsync();
|
||||
// 返回修改日期
|
||||
return props.DateModified.DateTime;
|
||||
}
|
||||
|
||||
public static List<LanguageInfo> GetAllTranslationLanguagesInfo() =>
|
||||
[
|
||||
new LanguageInfo("ar", "العربية"),
|
||||
new LanguageInfo("az", "Azərbaycan dili"),
|
||||
new LanguageInfo("zh", "中文"),
|
||||
new LanguageInfo("cs", "Čeština"),
|
||||
new LanguageInfo("da", "Dansk"),
|
||||
new LanguageInfo("nl", "Nederlands"),
|
||||
new LanguageInfo("en", "English"),
|
||||
new LanguageInfo("eo", "Esperanto"),
|
||||
new LanguageInfo("fi", "Suomi"),
|
||||
new LanguageInfo("fr", "Français"),
|
||||
new LanguageInfo("de", "Deutsch"),
|
||||
new LanguageInfo("el", "Ελληνικά"),
|
||||
new LanguageInfo("he", "עברית"),
|
||||
new LanguageInfo("hi", "हिन्दी"),
|
||||
new LanguageInfo("hu", "Magyar"),
|
||||
new LanguageInfo("id", "Bahasa Indonesia"),
|
||||
new LanguageInfo("ga", "Gaeilge"),
|
||||
new LanguageInfo("it", "Italiano"),
|
||||
new LanguageInfo("ja", "日本語"),
|
||||
new LanguageInfo("ko", "한국어"),
|
||||
new LanguageInfo("fa", "فارسی"),
|
||||
new LanguageInfo("pl", "Polski"),
|
||||
new LanguageInfo("pt", "Português"),
|
||||
new LanguageInfo("ru", "Русский"),
|
||||
new LanguageInfo("sk", "Slovenčina"),
|
||||
new LanguageInfo("es", "Español"),
|
||||
new LanguageInfo("sv", "Svenska"),
|
||||
new LanguageInfo("tr", "Türkçe"),
|
||||
new LanguageInfo("uk", "Українська"),
|
||||
new LanguageInfo("vi", "Tiếng Việt"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static T? SafeGet<T>(this IList<T> list, int index)
|
||||
{
|
||||
if (list == null || index < 0 || index >= list.Count) return default;
|
||||
return list[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.UI;
|
||||
|
||||
using Color = Windows.UI.Color;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ColorHelper
|
||||
@@ -98,5 +105,140 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
|
||||
}
|
||||
|
||||
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, WindowPixelSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case WindowPixelSampleMode.BelowWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowEdge:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
List<System.Drawing.Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top,
|
||||
width,
|
||||
(int)edgeThickness.Top
|
||||
)
|
||||
);
|
||||
// Bottom edge
|
||||
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Bottom - (int)edgeThickness.Bottom,
|
||||
width,
|
||||
(int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Left edge
|
||||
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Left,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Right edge
|
||||
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Right - (int)edgeThickness.Right,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Right,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
|
||||
// 合并四边平均色
|
||||
if (edgeColors.Count == 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
foreach (var c in edgeColors)
|
||||
{
|
||||
r += c.R;
|
||||
g += c.G;
|
||||
b += c.B;
|
||||
}
|
||||
return System.Drawing.Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return System.Drawing.Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
private static System.Drawing.Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static System.Drawing.Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
System.Drawing.Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return System.Drawing.Color.Transparent;
|
||||
return System.Drawing.Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -15,7 +16,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
|
||||
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
|
||||
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
|
||||
@@ -47,13 +47,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_originalWindowBounds.Remove(hwnd);
|
||||
}
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
}
|
||||
|
||||
@@ -83,10 +76,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
if (!_originalTopmostStates.ContainsKey(hwnd))
|
||||
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
|
||||
@@ -97,48 +86,29 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
window.SetIsShownInSwitchers(false);
|
||||
}
|
||||
|
||||
public static void Lock(Window window)
|
||||
{
|
||||
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD><EFBFBD><CDB8>
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
|
||||
SetClickThrough(window, true);
|
||||
}
|
||||
|
||||
public static void SetClickThrough(Window window, bool enable)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
|
||||
if (enable)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
|
||||
_clickThroughStates[hwnd] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
|
||||
_clickThroughStates[hwnd] = false;
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unlock(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD> Disable ʱ<><CAB1><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD>
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
}
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
SetClickThrough(window, false);
|
||||
|
||||
// To recover the system backdrop, we need to reopen the window
|
||||
WindowHelper.RestartApp(AppInfo.UnlockWindowTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Ude;
|
||||
@@ -21,5 +23,60 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return Encoding.GetEncoding(encoding);
|
||||
}
|
||||
|
||||
public 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();
|
||||
}
|
||||
|
||||
public static string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllBytes(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void WriteLyricsCache(string title, string artist, string lyrics, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {title}{format.ToFileExtension()}"));
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
public static void WriteAlbumArtCache(string album, string artist, byte[] img, string format, string cacheFolderPath)
|
||||
{
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, SanitizeFileName($"{artist} - {album}{format}"));
|
||||
File.WriteAllBytes(cacheFilePath, img);
|
||||
}
|
||||
|
||||
public static bool IsSwitchableNormalizedMatch(string fileName, string q1, string q2)
|
||||
{
|
||||
var normFileName = StringHelper.Normalize(fileName.Normalize());
|
||||
var normQ1 = StringHelper.Normalize(q1);
|
||||
var normQ2 = StringHelper.Normalize(q2);
|
||||
|
||||
// 常见两种顺序
|
||||
return normFileName == normQ1 + normQ2
|
||||
|| normFileName == normQ2 + normQ1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,31 +10,27 @@ using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ForegroundWindowWatcherHelper
|
||||
public class ForegroundWindowWatcher
|
||||
{
|
||||
private readonly User32.WinEventProc _winEventDelegate;
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
private HWND _currentForeground = HWND.NULL;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
private const int ThrottleIntervalMs = 1000;
|
||||
|
||||
public delegate void WindowChangedHandler(HWND hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
public ForegroundWindowWatcher(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
_onWindowChanged = onWindowChanged;
|
||||
_winEventDelegate = new User32.WinEventProc(WinEventProc);
|
||||
|
||||
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_pollingTimer.Tick += (_, _) =>
|
||||
{
|
||||
if (_currentForeground != IntPtr.Zero && _currentForeground != _selfHwnd)
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
};
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Interval = TimeSpan.FromSeconds(1);
|
||||
_timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@@ -65,7 +61,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
)
|
||||
);
|
||||
|
||||
_pollingTimer.Start();
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
@@ -74,7 +70,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
User32.UnhookWinEvent(hook);
|
||||
|
||||
_hooks.Clear();
|
||||
_pollingTimer.Stop();
|
||||
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void Timer_Tick(object? sender, object e)
|
||||
{
|
||||
if (_currentForeground != HWND.NULL)
|
||||
{
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
}
|
||||
}
|
||||
|
||||
private void WinEventProc(
|
||||
@@ -87,15 +92,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
uint dwmsEventTime
|
||||
)
|
||||
{
|
||||
if (hwnd == IntPtr.Zero || hwnd == _selfHwnd)
|
||||
if (hwnd == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastEventTime).TotalMilliseconds < ThrottleIntervalMs)
|
||||
return;
|
||||
|
||||
_lastEventTime = now;
|
||||
|
||||
if (eventType == User32.EventConstants.EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
_currentForeground = hwnd;
|
||||
@@ -19,7 +19,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
public const int AccentColorCount = 3;
|
||||
private const int _accentColorCount = 1;
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
public static List<Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
{
|
||||
// 使用 ImageSharp 读取图片
|
||||
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
|
||||
@@ -127,7 +127,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
// 按出现次数排序,取前 AccentColorCount 个
|
||||
var topColors = colorCount
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.Take(AccentColorCount)
|
||||
.Take(_accentColorCount)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
@@ -138,31 +138,31 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
|
||||
|
||||
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
//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);
|
||||
// var bitmapImage = new BitmapImage();
|
||||
// await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
// return bitmapImage;
|
||||
//}
|
||||
|
||||
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
//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;
|
||||
//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());
|
||||
// InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
// await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
return stream;
|
||||
}
|
||||
// return stream;
|
||||
//}
|
||||
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
|
||||
117
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LanguageHelper.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using NTextCat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LanguageHelper
|
||||
{
|
||||
private static readonly RankedLanguageIdentifierFactory _factory = new();
|
||||
private static readonly RankedLanguageIdentifier _identifier;
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
|
||||
[
|
||||
new Models.LanguageInfo("ar", "العربية"),
|
||||
new Models.LanguageInfo("az", "Azərbaycan dili"),
|
||||
new Models.LanguageInfo("zh-Hans", "简体中文"),
|
||||
new Models.LanguageInfo("zh-Hant", "繁體中文"),
|
||||
new Models.LanguageInfo("cs", "Čeština"),
|
||||
new Models.LanguageInfo("da", "Dansk"),
|
||||
new Models.LanguageInfo("nl", "Nederlands"),
|
||||
new Models.LanguageInfo("en", "English"),
|
||||
new Models.LanguageInfo("eo", "Esperanto"),
|
||||
new Models.LanguageInfo("fi", "Suomi"),
|
||||
new Models.LanguageInfo("fr", "Français"),
|
||||
new Models.LanguageInfo("de", "Deutsch"),
|
||||
new Models.LanguageInfo("el", "Ελληνικά"),
|
||||
new Models.LanguageInfo("he", "עברית"),
|
||||
new Models.LanguageInfo("hi", "हिन्दी"),
|
||||
new Models.LanguageInfo("hu", "Magyar"),
|
||||
new Models.LanguageInfo("id", "Bahasa Indonesia"),
|
||||
new Models.LanguageInfo("ga", "Gaeilge"),
|
||||
new Models.LanguageInfo("it", "Italiano"),
|
||||
new Models.LanguageInfo("ja", "日本語"),
|
||||
new Models.LanguageInfo("ko", "한국어"),
|
||||
new Models.LanguageInfo("fa", "فارسی"),
|
||||
new Models.LanguageInfo("pl", "Polski"),
|
||||
new Models.LanguageInfo("pt", "Português"),
|
||||
new Models.LanguageInfo("ru", "Русский"),
|
||||
new Models.LanguageInfo("sk", "Slovenčina"),
|
||||
new Models.LanguageInfo("es", "Español"),
|
||||
new Models.LanguageInfo("sv", "Svenska"),
|
||||
new Models.LanguageInfo("tr", "Türkçe"),
|
||||
new Models.LanguageInfo("uk", "Українська"),
|
||||
new Models.LanguageInfo("vi", "Tiếng Việt"),
|
||||
];
|
||||
|
||||
static LanguageHelper()
|
||||
{
|
||||
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
|
||||
}
|
||||
|
||||
private static string? ThreeLetterToTwoLetter(string? threeLetterCode)
|
||||
{
|
||||
if (threeLetterCode == null) return null;
|
||||
|
||||
foreach (var ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
|
||||
{
|
||||
if (string.Equals(ci.ThreeLetterISOLanguageName, threeLetterCode, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ci.TwoLetterISOLanguageName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? DetectLanguageCode(string? text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
|
||||
string? code = ThreeLetterToTwoLetter(_identifier.Identify(text).FirstOrDefault()?.Item1.Iso639_2T);
|
||||
if (code != null && code == "zh")
|
||||
{
|
||||
if (ChineseConverter.ConvertToTraditionalChinese(text) == text)
|
||||
{
|
||||
return "zh-Hant";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "zh-Hans";
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
return DetectLanguageCode(text) switch
|
||||
{
|
||||
"zh" or "ja" or "ko" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static string DetectCountryCode(string? text)
|
||||
{
|
||||
if (text == null) return "en";
|
||||
var code = DetectLanguageCode(text);
|
||||
if (code == null) return "en";
|
||||
// 处理中文简体和繁体
|
||||
if (code == "zh-Hans") return "cn";
|
||||
if (code == "zh-Hant") return "cn";
|
||||
// 其他语言直接返回两字母代码
|
||||
return code;
|
||||
}
|
||||
|
||||
public static string GetUserTargetLanguageCode()
|
||||
{
|
||||
return SupportedTargetLanguages[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LatestOnlyTaskRunner
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public async Task RunAsync(Func<CancellationToken, Task> func)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
try
|
||||
{
|
||||
await func(token);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,29 +10,21 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using Windows.Globalization.Fonts;
|
||||
using LyricsData = BetterLyrics.WinUI3.Models.LyricsData;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LyricsParser
|
||||
{
|
||||
private List<List<LyricsLine>> _multiLangLyricsLines = [];
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
public List<List<LyricsLine>> Parse(string? raw, int durationMs)
|
||||
public List<LyricsData> Parse(string? raw, int? durationMs)
|
||||
{
|
||||
_multiLangLyricsLines = [];
|
||||
durationMs ??= (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
_lyricsDataArr = [];
|
||||
if (raw == null)
|
||||
{
|
||||
_multiLangLyricsLines.Add(
|
||||
[
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
},
|
||||
]
|
||||
);
|
||||
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder(durationMs.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -42,10 +35,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
ParseLrc(raw);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseUsingLyricify(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseUsingLyricify(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw);
|
||||
@@ -54,8 +47,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
break;
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(durationMs);
|
||||
return _multiLangLyricsLines;
|
||||
PostProcessLyricsLines(durationMs.Value);
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
private void ParseLrc(string raw)
|
||||
@@ -119,9 +112,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int languageCount = grouped.Max(g => g.Count());
|
||||
|
||||
// 初始化每种语言的歌词列表
|
||||
_multiLangLyricsLines.Clear();
|
||||
_lyricsDataArr.Clear();
|
||||
for (int i = 0; i < languageCount; i++)
|
||||
_multiLangLyricsLines.Add(new List<LyricsLine>());
|
||||
_lyricsDataArr.Add(new LyricsData());
|
||||
|
||||
// 遍历每个时间分组
|
||||
foreach (var group in grouped)
|
||||
@@ -158,7 +151,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
}
|
||||
_multiLangLyricsLines[langIdx].Add(line);
|
||||
_lyricsDataArr[langIdx].LyricsLines.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +160,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
try
|
||||
{
|
||||
List<LyricsLine> singleLangLyricsLine = [];
|
||||
List<LyricsLine> originalLines = [];
|
||||
List<LyricsLine> translationLines = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null) return;
|
||||
@@ -176,53 +170,90 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
// 句级时间
|
||||
string? pBegin = p.Attribute("begin")?.Value;
|
||||
string? pEnd = p.Attribute("end")?.Value;
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 处理分词分时
|
||||
var spans = p.Elements().Where(s => s.Name.LocalName == "span").ToList();
|
||||
// 只获取一级span,且排除ttm:role="x-bg"的span
|
||||
var spans = p.Elements()
|
||||
.Where(s => s.Name.LocalName == "span" &&
|
||||
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg")
|
||||
.ToList();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s.Value));
|
||||
var charTimings = new List<CharTiming>();
|
||||
// 原文和翻译分离
|
||||
var originalTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-translation")
|
||||
.ToList();
|
||||
var translationTextSpans = spans
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
|
||||
.ToList();
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
// 原文(非 CJK 语言添加空格)
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
if (!LanguageHelper.IsCJK(originalText))
|
||||
{
|
||||
var span = spans[i];
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
|
||||
if (sStartMs == 0 && sEndMs == 0)
|
||||
continue;
|
||||
|
||||
if (sEndMs == 0)
|
||||
sEndMs =
|
||||
(i + 1 < spans.Count)
|
||||
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
|
||||
: pEndMs;
|
||||
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = 0, StartIndex = startIndex, Text = span.Value });
|
||||
startIndex += span.Value.Length;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
span.Value += " ";
|
||||
}
|
||||
originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
}
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value;
|
||||
var originalCharTimings = new List<CharTiming>();
|
||||
int originalStartIndex = 0;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
originalCharTimings.Add(new CharTiming
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
StartIndex = originalStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
originalStartIndex += span.Value.Length;
|
||||
}
|
||||
if (originalTextSpans.Count == 0)
|
||||
originalText = p.Value;
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
originalLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
OriginalText = originalText,
|
||||
CharTimings = originalCharTimings,
|
||||
});
|
||||
|
||||
// 翻译
|
||||
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
|
||||
var translationCharTimings = new List<CharTiming>();
|
||||
int translationStartIndex = 0;
|
||||
foreach (var span in translationTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
translationCharTimings.Add(new CharTiming
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
StartIndex = translationStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
translationStartIndex += span.Value.Length;
|
||||
}
|
||||
if (translationTextSpans.Count > 0)
|
||||
{
|
||||
translationLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
OriginalText = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
OriginalText = translationText,
|
||||
CharTimings = translationCharTimings,
|
||||
});
|
||||
}
|
||||
}
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
_lyricsDataArr.Add(new LyricsData(originalLines));
|
||||
if (translationLines.Count > 0)
|
||||
_lyricsDataArr.Add(new LyricsData(translationLines));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -230,7 +261,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
private int ParseTtmlTime(string? t)
|
||||
private static int ParseTtmlTime(string? t)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(t))
|
||||
return 0;
|
||||
@@ -291,7 +322,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ParseUsingLyricify(List<ILineInfo>? lines)
|
||||
private void ParseQQNeteaseKugou(List<ILineInfo>? lines)
|
||||
{
|
||||
lines = lines?.Where(x => x.Text != string.Empty).ToList();
|
||||
List<LyricsLine> lyricsLines = [];
|
||||
@@ -345,27 +376,27 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
_multiLangLyricsLines.Add(lyricsLines);
|
||||
_lyricsDataArr.Add(new LyricsData(lyricsLines));
|
||||
}
|
||||
|
||||
private void PostProcessLyricsLines(int durationMs)
|
||||
{
|
||||
for (int langIdx = 0; langIdx < _multiLangLyricsLines.Count; langIdx++)
|
||||
for (int langIdx = 0; langIdx < _lyricsDataArr.Count; langIdx++)
|
||||
{
|
||||
var linesInSingleLang = _multiLangLyricsLines[langIdx];
|
||||
for (int i = 0; i < linesInSingleLang.Count; i++)
|
||||
var lines = _lyricsDataArr[langIdx].LyricsLines;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (i + 1 < linesInSingleLang.Count)
|
||||
if (i + 1 < lines.Count)
|
||||
{
|
||||
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
|
||||
lines[i].EndMs = lines[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
linesInSingleLang[i].EndMs = durationMs;
|
||||
lines[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = linesInSingleLang[i].CharTimings;
|
||||
var timings = lines[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
@@ -376,23 +407,26 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
else
|
||||
{
|
||||
timings[j].EndMs = linesInSingleLang[i].EndMs;
|
||||
timings[j].EndMs = lines[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (linesInSingleLang.Count > 0 && linesInSingleLang[0].StartMs > 0)
|
||||
if (lines.Count > 0)
|
||||
{
|
||||
linesInSingleLang.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = linesInSingleLang[0].StartMs,
|
||||
OriginalText = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
if (lines[0].StartMs > 0)
|
||||
{
|
||||
lines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
OriginalText = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
public static class MetadataHelper
|
||||
{
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppName = "BetterLyrics";
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
|
||||
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
|
||||
|
||||
public static async Task<DateTime> GetBuildDate()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var filePath = assembly.Location;
|
||||
if (!File.Exists(filePath))
|
||||
return DateTime.MinValue;
|
||||
|
||||
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
// 获取文件基本属性
|
||||
BasicProperties props = await file.GetBasicPropertiesAsync();
|
||||
// 返回修改日期
|
||||
return props.DateModified.DateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class PathHelper
|
||||
{
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
|
||||
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string LyricsCacheDirectory => Path.Combine(CacheFolder, "lyrics");
|
||||
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "lrclib");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "netease");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "qq");
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.json");
|
||||
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
|
||||
|
||||
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
|
||||
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
|
||||
public static string iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class StringHelper
|
||||
{
|
||||
// 去除空格、括号、下划线、横杠、点、大小写等
|
||||
public static string Normalize(string s) =>
|
||||
new string(s
|
||||
.Where(c => char.IsLetterOrDigit(c))
|
||||
.ToArray())
|
||||
.ToLowerInvariant();
|
||||
public static string NewLine = "\n";
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,6 @@ using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class AnimationHelper
|
||||
{
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
}
|
||||
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
{
|
||||
@@ -52,11 +45,18 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
else
|
||||
{
|
||||
_easingType = EasingType.SmoothStep;
|
||||
_easingType = EasingType.Linear;
|
||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDuration(float seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
|
||||
_durationSeconds = seconds;
|
||||
}
|
||||
|
||||
private void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
@@ -1,148 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class WindowColorHelper
|
||||
{
|
||||
public static Color GetDominantColor(IntPtr myHwnd, WindowColorSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return Color.Transparent;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case WindowColorSampleMode.BelowWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowColorSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return Color.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
case WindowColorSampleMode.WindowEdge:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return Color.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
List<Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top,
|
||||
width,
|
||||
(int)edgeThickness.Top
|
||||
)
|
||||
);
|
||||
// Bottom edge
|
||||
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Bottom - (int)edgeThickness.Bottom,
|
||||
width,
|
||||
(int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Left edge
|
||||
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Left,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Right edge
|
||||
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Right - (int)edgeThickness.Right,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Right,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
|
||||
// 合并四边平均色
|
||||
if (edgeColors.Count == 0)
|
||||
return Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
foreach (var c in edgeColors)
|
||||
{
|
||||
r += c.R;
|
||||
g += c.G;
|
||||
b += c.B;
|
||||
}
|
||||
return Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return Color.Transparent;
|
||||
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
@@ -61,6 +62,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
newWindow = new LyricsWindow();
|
||||
((LyricsWindow)newWindow).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
}
|
||||
else if (typeof(T) == typeof(SettingsWindow))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class AlbumArtSearchProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AlbumArtSearchProvider Provider { get; set; }
|
||||
|
||||
public AlbumArtSearchProviderInfo() { }
|
||||
|
||||
public AlbumArtSearchProviderInfo(AlbumArtSearchProvider provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class DetectLanguageResult
|
||||
{
|
||||
[JsonPropertyName("confidence")]
|
||||
public double Confidence { get; set; }
|
||||
|
||||
[JsonPropertyName("language")]
|
||||
public string Language { get; set; }
|
||||
}
|
||||
}
|
||||
95
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LyricsData
|
||||
{
|
||||
public List<LyricsLine> LyricsLines { get; set; }
|
||||
public string? LanguageCode => LanguageHelper.DetectLanguageCode(WrappedOriginalText);
|
||||
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
|
||||
|
||||
public LyricsData()
|
||||
{
|
||||
LyricsLines = [];
|
||||
}
|
||||
|
||||
public LyricsData(List<LyricsLine> lyricsLines)
|
||||
{
|
||||
LyricsLines = lyricsLines;
|
||||
}
|
||||
|
||||
public void SetDisplayedTextAlongWith(LyricsData translationData)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= translationData.LyricsLines.Count)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText; // No translation available, keep original text
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationData.LyricsLines[i].OriginalText})";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDisplayedTextAlongWith(string translation)
|
||||
{
|
||||
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= translationArr.Count)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText; // No translation available, keep original text
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationArr[i]})";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDisplayedTextInOriginalText()
|
||||
{
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText;
|
||||
}
|
||||
}
|
||||
|
||||
public static LyricsData GetNotfoundPlaceholder(int durationMs)
|
||||
{
|
||||
return new LyricsData([new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
}]);
|
||||
}
|
||||
|
||||
public static LyricsData GetLoadingPlaceholder()
|
||||
{
|
||||
return new LyricsData([
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
DisplayedText = "● ● ●",
|
||||
CharTimings = [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,17 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class MediaSourceProviderInfo : ObservableObject
|
||||
public partial class LyricsSearchProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Provider { get; set; }
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
|
||||
public MediaSourceProviderInfo() { }
|
||||
public LyricsSearchProviderInfo() { }
|
||||
|
||||
public MediaSourceProviderInfo(string provider, bool isEnabled)
|
||||
public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -5,17 +5,17 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LyricsSearchProviderInfo : ObservableObject
|
||||
public partial class MediaSourceProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
public partial string Provider { get; set; }
|
||||
|
||||
public LyricsSearchProviderInfo() { }
|
||||
public MediaSourceProviderInfo() { }
|
||||
|
||||
public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
|
||||
public MediaSourceProviderInfo(string provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class Notification : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsForeverDismissable { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? Message { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? RelatedSettingsKeyName { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial InfoBarSeverity Severity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Visibility Visibility { get; set; }
|
||||
|
||||
public Notification(string? message = null, InfoBarSeverity severity = InfoBarSeverity.Informational, bool isForeverDismissable = false, string? relatedSettingsKeyName = null)
|
||||
{
|
||||
Message = message;
|
||||
Severity = severity;
|
||||
IsForeverDismissable = isForeverDismissable;
|
||||
Visibility = IsForeverDismissable ? Visibility.Visible : Visibility.Collapsed;
|
||||
RelatedSettingsKeyName = relatedSettingsKeyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string? Album { get; set; }
|
||||
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
|
||||
|
||||
public Color? AlbumArtAccentColor { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Artist { get; set; }
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ using BetterLyrics.WinUI3.Models;
|
||||
namespace BetterLyrics.WinUI3.Serialization
|
||||
{
|
||||
|
||||
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(List<DetectLanguageResult>))]
|
||||
[JsonSerializable(typeof(TranslateResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class AlbumArtSearchService : IAlbumArtSearchService
|
||||
{
|
||||
private readonly HttpClient _iTunesHttpClinet;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public AlbumArtSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<AlbumArtSearchService>>();
|
||||
_iTunesHttpClinet = new();
|
||||
}
|
||||
|
||||
public async Task<byte[]?> SearchAsync(string title, string artist, string album, byte[]? bytesFromSMTC = null)
|
||||
{
|
||||
byte[]? result = null;
|
||||
|
||||
foreach (var provider in _settingsService.AlbumArtSearchProvidersInfo)
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case AlbumArtSearchProvider.Local:
|
||||
result = SearchFile(artist, album);
|
||||
break;
|
||||
case AlbumArtSearchProvider.SMTC:
|
||||
result = bytesFromSMTC;
|
||||
break;
|
||||
case AlbumArtSearchProvider.iTunes:
|
||||
result = await SearchiTunesAsync(artist, album);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != null) return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[]? SearchFile(string artist, string album)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), album, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album)
|
||||
{
|
||||
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
|
||||
try
|
||||
{
|
||||
string format = ".jpg";
|
||||
var cachedAlbumArt = FileHelper.ReadAlbumArtCache(artist, album, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
|
||||
if (cachedAlbumArt != null)
|
||||
{
|
||||
return cachedAlbumArt;
|
||||
}
|
||||
|
||||
// Build the iTunes API URL
|
||||
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + LanguageHelper.DetectCountryCode(album + artist) + "&entity=album";
|
||||
url.Replace(" ", "-");
|
||||
// Make a request to the API
|
||||
|
||||
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Parse the JSON response
|
||||
var data = JsonSerializer.Deserialize(responseBody, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
|
||||
if (data.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
|
||||
{
|
||||
// Get the first result
|
||||
var result = results[0];
|
||||
if (result.TryGetProperty("artworkUrl100", out var artworkUrlProp))
|
||||
{
|
||||
string artworkUrl = artworkUrlProp.GetString()?.Replace("100x100bb.jpg", "1200x1200bb.jpg") ?? string.Empty;
|
||||
var fetched = await _iTunesHttpClinet.GetByteArrayAsync(artworkUrl);
|
||||
|
||||
if (fetched != null && fetched.Length > 0)
|
||||
{
|
||||
// Write to cache
|
||||
FileHelper.WriteAlbumArtCache(artist, album, fetched, format, PathHelper.iTunesAlbumArtCacheDirectory);
|
||||
return fetched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILibreTranslateService
|
||||
public interface IAlbumArtSearchService
|
||||
{
|
||||
Task<string> TranslateAsync(string text, CancellationToken? token);
|
||||
Task<byte[]?> SearchAsync(string title, string artist, string album, byte[]? bytesFromSMTC = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface IMusicSearchService
|
||||
{
|
||||
Task <byte[]?> SearchAlbumArtAsync(string title, string artist, string album);
|
||||
|
||||
Task<string?> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
double durationMs,
|
||||
CancellationToken token
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
public interface IPlaybackService
|
||||
{
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
|
||||
event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,13 +67,17 @@ namespace BetterLyrics.WinUI3.Services
|
||||
LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
|
||||
List<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
|
||||
|
||||
EasingType LyricsScrollEasingType { get; set; }
|
||||
int LyricsScrollDuration { get; set; }
|
||||
|
||||
int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
bool IgnoreFullscreenWindow { get; set; }
|
||||
@@ -81,5 +85,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
bool IsTranslationEnabled { get; set; }
|
||||
|
||||
LyricsDisplayType PreferredDisplayType { get; set; }
|
||||
|
||||
int TimelineSyncThreshold { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ITranslateService
|
||||
{
|
||||
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token);
|
||||
|
||||
int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LibreTranslateService : ILibreTranslateService
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public LibreTranslateService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<string> TranslateAsync(string text, CancellationToken? token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text and target language must be provided.");
|
||||
}
|
||||
|
||||
string targetLangCode = AppInfo.GetAllTranslationLanguagesInfo()[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
|
||||
string originalLangCode = await DetectLanguageCode(text);
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(originalLangCode) || originalLangCode == targetLangCode)
|
||||
{
|
||||
return text; // No translation needed
|
||||
}
|
||||
|
||||
var url = $"{_settingsService.LibreTranslateServer}/translate";
|
||||
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
|
||||
[
|
||||
new("q", text),
|
||||
new("source", originalLangCode),
|
||||
new("target", targetLangCode),
|
||||
]));
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
|
||||
return result?.TranslatedText ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task<string> DetectLanguageCode(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text must be provided.");
|
||||
}
|
||||
var url = $"{_settingsService.LibreTranslateServer}/detect";
|
||||
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
|
||||
[
|
||||
new("q", text),
|
||||
]));
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var resultList = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.ListDetectLanguageResult);
|
||||
return resultList?.OrderByDescending(x => x.Confidence).FirstOrDefault()?.Language ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using iTunesSearch.Library;
|
||||
using Lyricify.Lyrics.Providers.Web.Kugou;
|
||||
using Lyricify.Lyrics.Searchers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -16,35 +15,51 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class MusicSearchService : IMusicSearchService
|
||||
public class LyricsSearchService : ILyricsSearchService
|
||||
{
|
||||
private readonly HttpClient _amllTtmlDbHttpClient;
|
||||
private readonly HttpClient _lrcLibHttpClient;
|
||||
private readonly HttpClient _iTunesHttpClinet;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MusicSearchService(ISettingsService settingsService)
|
||||
public LyricsSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<MusicSearchService>>();
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsSearchService>>();
|
||||
|
||||
_lrcLibHttpClient = new();
|
||||
_lrcLibHttpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
|
||||
$"{MetadataHelper.AppName} {MetadataHelper.AppVersion} ({MetadataHelper.GithubUrl})"
|
||||
);
|
||||
_amllTtmlDbHttpClient = new();
|
||||
_iTunesHttpClinet = new();
|
||||
}
|
||||
|
||||
private static bool IsAmllTtmlDbIndexInvalid()
|
||||
{
|
||||
bool existed = File.Exists(PathHelper.AmllTtmlDbIndexPath);
|
||||
|
||||
if (!existed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
long currentTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
string lastUpdatedStr = File.ReadAllText(PathHelper.AmllTtmlDbLastUpdatedPath);
|
||||
long lastUpdated = Convert.ToInt64(lastUpdatedStr);
|
||||
return currentTs - lastUpdated > 1 * 24 * 60 * 60;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
|
||||
{
|
||||
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-img-index.jsonl";
|
||||
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
|
||||
try
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
@@ -52,13 +67,16 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||
await using var fs = new FileStream(
|
||||
AppInfo.AmllTtmlDbIndexPath,
|
||||
PathHelper.AmllTtmlDbIndexPath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None
|
||||
);
|
||||
await stream.CopyToAsync(fs);
|
||||
|
||||
long currentTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
File.WriteAllText(PathHelper.AmllTtmlDbLastUpdatedPath, currentTs.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -67,96 +85,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
}
|
||||
|
||||
private static string GuessCountryCode(string album, string artist)
|
||||
{
|
||||
string s = album + artist;
|
||||
if (s.Any(c => c >= 0x4e00 && c <= 0x9fff)) // 中文
|
||||
return "cn";
|
||||
if (s.Any(c => (c >= 0x3040 && c <= 0x30ff) || (c >= 0x31f0 && c <= 0x31ff))) // 日文
|
||||
return "jp";
|
||||
if (s.Any(c => c >= 0xac00 && c <= 0xd7af)) // 韩文
|
||||
return "kr";
|
||||
if (s.Any(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) // 英文
|
||||
return "us";
|
||||
// 其他情况
|
||||
return "us";
|
||||
}
|
||||
|
||||
public async Task<byte[]?> SearchAlbumArtAsync(string title, string artist, string album)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await SearchiTunesAlbumArtAsync(artist, album);
|
||||
}
|
||||
|
||||
private async Task<byte[]?> SearchiTunesAlbumArtAsync(string artist, string album)
|
||||
{
|
||||
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
|
||||
try
|
||||
{
|
||||
string format = ".jpg";
|
||||
var cachedAlbumArt = ReadAlbumArtCache(artist, album, format, AppInfo.iTunesAlbumArtCacheDirectory);
|
||||
|
||||
if (cachedAlbumArt != null)
|
||||
{
|
||||
return cachedAlbumArt;
|
||||
}
|
||||
|
||||
// Build the iTunes API URL
|
||||
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + GuessCountryCode(album, artist) + "&entity=album";
|
||||
url.Replace(" ", "-");
|
||||
// Make a request to the API
|
||||
|
||||
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Parse the JSON response
|
||||
var data = JsonSerializer.Deserialize(responseBody, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
|
||||
if (data.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
|
||||
{
|
||||
// Get the first result
|
||||
var result = results[0];
|
||||
if (result.TryGetProperty("artworkUrl100", out var artworkUrlProp))
|
||||
{
|
||||
string artworkUrl = artworkUrlProp.GetString()?.Replace("100x100bb.jpg", "1200x1200bb.jpg") ?? string.Empty;
|
||||
var fetched = await _iTunesHttpClinet.GetByteArrayAsync(artworkUrl);
|
||||
|
||||
if (fetched != null && fetched.Length > 0)
|
||||
{
|
||||
// Write to cache
|
||||
WriteAlbumArtCache(artist, album, fetched, format, AppInfo.iTunesAlbumArtCacheDirectory);
|
||||
return fetched;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<string?> SearchLyricsAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
public async Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
|
||||
@@ -173,7 +102,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
cachedLyrics = ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return cachedLyrics;
|
||||
@@ -186,11 +115,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist);
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await LocalLyricsSearchInLyricsFiles(title, artist, lyricsFormat);
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -201,13 +130,13 @@ namespace BetterLyrics.WinUI3.Services
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
@@ -223,7 +152,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
|
||||
return searchedLyrics;
|
||||
@@ -233,36 +162,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool MusicMatch(string fileName, string title, string artist)
|
||||
{
|
||||
var normFileName = Normalize(fileName);
|
||||
var normTitle = Normalize(title);
|
||||
var normArtist = Normalize(artist);
|
||||
|
||||
// 常见两种顺序
|
||||
return normFileName == normTitle + normArtist
|
||||
|| normFileName == normArtist + normTitle;
|
||||
}
|
||||
|
||||
// 预处理:去除空格、括号、下划线、横杠、点、大小写等
|
||||
static string Normalize(string s) =>
|
||||
new string(s
|
||||
.Where(c => char.IsLetterOrDigit(c))
|
||||
.ToArray())
|
||||
.ToLowerInvariant();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private async Task<string?> LocalLyricsSearchInLyricsFiles(string title, string artist, LyricsFormat format)
|
||||
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
@@ -270,7 +170,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*{format.ToFileExtension()}", SearchOption.AllDirectories))
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
|
||||
if (raw != null)
|
||||
@@ -284,7 +184,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
|
||||
private string? SearchEmbedded(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
@@ -292,7 +192,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -311,42 +211,17 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeTitle}{format.ToFileExtension()}");
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeAlbum = SanitizeFileName(album);
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeAlbum}{format}");
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllBytes(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
|
||||
{
|
||||
// 检索本地 JSONL 索引文件,查找 rawLyricFile
|
||||
if (!File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
if (IsAmllTtmlDbIndexInvalid())
|
||||
{
|
||||
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
|
||||
if (!downloadOk || !File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
if (!downloadOk)
|
||||
return null;
|
||||
}
|
||||
|
||||
string? rawLyricFile = null;
|
||||
await foreach (var line in File.ReadLinesAsync(AppInfo.AmllTtmlDbIndexPath))
|
||||
await foreach (var line in File.ReadLinesAsync(PathHelper.AmllTtmlDbIndexPath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
@@ -372,7 +247,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
if (musicName == null || artists == null)
|
||||
continue;
|
||||
|
||||
if (MusicMatch($"{artists} - {musicName}", title, artist))
|
||||
if (FileHelper.IsSwitchableNormalizedMatch($"{artists} - {musicName}", title, artist))
|
||||
{
|
||||
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
|
||||
{
|
||||
@@ -388,7 +263,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
|
||||
// 下载歌词内容
|
||||
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-img/{rawLyricFile}";
|
||||
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
@@ -436,13 +311,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchUsingLyricifyAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int durationMs,
|
||||
Searchers searchers
|
||||
)
|
||||
private static async Task<string?> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, Searchers searchers)
|
||||
{
|
||||
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
@@ -479,39 +348,5 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void WriteLyricsCache(
|
||||
string title,
|
||||
string artist,
|
||||
string lyrics,
|
||||
LyricsFormat format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
private void WriteAlbumArtCache(
|
||||
string album,
|
||||
string artist,
|
||||
byte[] img,
|
||||
string format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeAlbum = SanitizeFileName(album);
|
||||
var cacheFilePath = Path.Combine(
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeAlbum}{format}"
|
||||
);
|
||||
File.WriteAllBytes(cacheFilePath, img);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -22,25 +23,30 @@ using WindowsMediaController;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public partial class PlaybackService : BaseViewModel, IPlaybackService, IRecipient<PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>>>
|
||||
public partial class PlaybackService : BaseViewModel, IPlaybackService,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>>>
|
||||
{
|
||||
private readonly IMusicSearchService _musicSearchService;
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILogger<PlaybackService> _logger;
|
||||
|
||||
private readonly MediaManager _mediaManager = new();
|
||||
private readonly LatestOnlyTaskRunner _AlbumArtRefreshRunner = new();
|
||||
private readonly LatestOnlyTaskRunner _OnAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
private CancellationTokenSource? _mediaPropsCts;
|
||||
|
||||
private SongInfo? _cachedSongInfo;
|
||||
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
|
||||
private byte[]? _SMTCAlbumArtBytes = null;
|
||||
private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new AlbumArtChangedEventArgs();
|
||||
|
||||
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
public PlaybackService(ISettingsService settingsService, IMusicSearchService musicSearchService) : base(settingsService)
|
||||
public PlaybackService(ISettingsService settingsService, IAlbumArtSearchService albumArtSearchService) : base(settingsService)
|
||||
{
|
||||
_musicSearchService = musicSearchService;
|
||||
_albumArtSearchService = albumArtSearchService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<PlaybackService>>();
|
||||
|
||||
_mediaSourceProvidersInfo = _settingsService.MediaSourceProvidersInfo;
|
||||
@@ -121,27 +127,20 @@ namespace BetterLyrics.WinUI3.Services
|
||||
);
|
||||
}
|
||||
|
||||
private async void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
{
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
string id = mediaSession.ControlSession.SourceAppUserModelId;
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
_mediaPropsCts?.Cancel();
|
||||
var cts = new CancellationTokenSource();
|
||||
_mediaPropsCts = cts;
|
||||
var token = cts.Token;
|
||||
|
||||
try
|
||||
_ = _OnAnyMediaPropertyChangedRunner.RunAsync(async token =>
|
||||
{
|
||||
SongInfo? songInfo;
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
string id = mediaSession.ControlSession.SourceAppUserModelId;
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
songInfo = new SongInfo
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
@@ -150,53 +149,26 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
|
||||
byte[]? bytes;
|
||||
|
||||
bytes = await _musicSearchService.SearchAlbumArtAsync(
|
||||
songInfo.Title,
|
||||
songInfo.Artist,
|
||||
songInfo.Album
|
||||
);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (bytes == null)
|
||||
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
bytes = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{songInfo.Artist} - {songInfo.Title}", 400, 400);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
songInfo.AlbumArtSwBitmap?.Dispose();
|
||||
songInfo.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
songInfo.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(songInfo));
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception) { }
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
|
||||
@@ -235,12 +207,54 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(null));
|
||||
_cachedSongInfo = null;
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false));
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task UpdateAlbumArtRelated(CancellationToken token)
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
{
|
||||
_logger.LogWarning("Cached song info is null, cannot update album art.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? bytes = await _albumArtSearchService.SearchAsync(
|
||||
_cachedSongInfo.Title,
|
||||
_cachedSongInfo.Artist,
|
||||
_cachedSongInfo?.Album ?? string.Empty,
|
||||
_SMTCAlbumArtBytes
|
||||
);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{_cachedSongInfo!.Artist} - {_cachedSongInfo.Title}", 400, 400);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs);
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
@@ -253,5 +267,21 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.AlbumArtSearchProvidersInfo))
|
||||
{
|
||||
// Album art search providers info changed, re-fetch album art
|
||||
_logger.LogInformation("Album art search providers info changed, refreshing album art.");
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,10 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private const string LyricsFontSizeKey = "LyricsFontSize";
|
||||
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
|
||||
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
|
||||
private const string LyricsHighlightSopeKey = "LyricsHighlightSope";
|
||||
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
|
||||
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
|
||||
private const string AlbumArtSearchProvidersInfoKey = "AlbumArtSearchProvidersInfo";
|
||||
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
|
||||
|
||||
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
|
||||
@@ -73,11 +75,14 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
|
||||
|
||||
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
|
||||
|
||||
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
|
||||
|
||||
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
|
||||
|
||||
private const string LyricsScrollEasingTypeKey = "LyricsScrollEasingType";
|
||||
private const string LyricsScrollDurationKey = "LyricsScrollDuration";
|
||||
|
||||
public const string TimelineSyncThresholdKey = "TimelineSyncThreshold";
|
||||
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService()
|
||||
@@ -96,7 +101,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
|
||||
)
|
||||
);
|
||||
SetDefault(MediaSourceProvidersInfoKey, "[]");
|
||||
if (LyricsSearchProvidersInfo.Count != Enum.GetValues<LyricsSearchProvider>().Length)
|
||||
{
|
||||
LyricsSearchProvidersInfo = Enum.GetValues<LyricsSearchProvider>()
|
||||
@@ -109,6 +113,31 @@ namespace BetterLyrics.WinUI3.Services
|
||||
))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
SetDefault(
|
||||
AlbumArtSearchProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
Enum.GetValues<AlbumArtSearchProvider>()
|
||||
.Select(p => new AlbumArtSearchProviderInfo(p, true))
|
||||
.ToList(),
|
||||
SourceGenerationContext.Default.ListAlbumArtSearchProviderInfo
|
||||
)
|
||||
);
|
||||
if (AlbumArtSearchProvidersInfo.Count != Enum.GetValues<AlbumArtSearchProvider>().Length)
|
||||
{
|
||||
AlbumArtSearchProvidersInfo = Enum.GetValues<AlbumArtSearchProvider>()
|
||||
.Select(p => new AlbumArtSearchProviderInfo(
|
||||
p,
|
||||
AlbumArtSearchProvidersInfo
|
||||
.Where(x => x.Provider == p)
|
||||
.FirstOrDefault()
|
||||
?.IsEnabled ?? true
|
||||
))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
SetDefault(MediaSourceProvidersInfoKey, "[]");
|
||||
|
||||
// App appearance
|
||||
SetDefault(LanguageKey, (int)Language.FollowSystem);
|
||||
|
||||
@@ -129,7 +158,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(IsCoverOverlayEnabledKey, true);
|
||||
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
|
||||
SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 200);
|
||||
SetDefault(CoverOverlayBlurAmountKey, 100);
|
||||
SetDefault(CoverImageRadiusKey, 12); // 12 %
|
||||
// Lyrics
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
|
||||
@@ -142,7 +171,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(LyricsBgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
SetDefault(LyricsFgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
SetDefault(LyricsStrokeFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
|
||||
|
||||
SetDefault(LyricsCustomBgFontColorKey, Colors.White.ToInt());
|
||||
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
|
||||
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
|
||||
@@ -151,7 +180,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
|
||||
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
|
||||
SetDefault(IsLyricsGlowEffectEnabledKey, true);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentChar);
|
||||
SetDefault(LyricsHighlightSopeKey, (int)LineRenderingType.LineStartToCurrentChar);
|
||||
SetDefault(IsFanLyricsEnabledKey, false);
|
||||
|
||||
SetDefault(LibreTranslateServerKey, "");
|
||||
@@ -159,10 +189,24 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(SelectedTargetLanguageIndexKey, 6);
|
||||
|
||||
SetDefault(LyricsFontStrokeWidthKey, 3);
|
||||
|
||||
SetDefault(IgnoreFullscreenWindowKey, false);
|
||||
|
||||
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
|
||||
|
||||
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
|
||||
SetDefault(LyricsScrollDurationKey, 500); // 500ms
|
||||
SetDefault(TimelineSyncThresholdKey, 0); // 0ms
|
||||
}
|
||||
|
||||
public EasingType LyricsScrollEasingType
|
||||
{
|
||||
get => (EasingType)GetValue<int>(LyricsScrollEasingTypeKey);
|
||||
set => SetValue(LyricsScrollEasingTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public int LyricsScrollDuration
|
||||
{
|
||||
get => GetValue<int>(LyricsScrollDurationKey);
|
||||
set => SetValue(LyricsScrollDurationKey, value);
|
||||
}
|
||||
|
||||
public LyricsDisplayType PreferredDisplayType
|
||||
@@ -380,6 +424,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
|
||||
}
|
||||
|
||||
public LineRenderingType LyricsHighlightScope
|
||||
{
|
||||
get => (LineRenderingType)GetValue<int>(LyricsHighlightSopeKey);
|
||||
set => SetValue(LyricsHighlightSopeKey, (int)value);
|
||||
}
|
||||
|
||||
public float LyricsLineSpacingFactor
|
||||
{
|
||||
get => GetValue<float>(LyricsLineSpacingFactorKey);
|
||||
@@ -403,6 +453,23 @@ namespace BetterLyrics.WinUI3.Services
|
||||
);
|
||||
}
|
||||
|
||||
public List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo
|
||||
{
|
||||
get =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(AlbumArtSearchProvidersInfoKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListAlbumArtSearchProviderInfo
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
AlbumArtSearchProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListAlbumArtSearchProviderInfo
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public List<MediaSourceProviderInfo> MediaSourceProvidersInfo
|
||||
{
|
||||
get =>
|
||||
@@ -450,6 +517,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(IgnoreFullscreenWindowKey, value);
|
||||
}
|
||||
|
||||
public int TimelineSyncThreshold
|
||||
{
|
||||
get => GetValue<int>(TimelineSyncThresholdKey);
|
||||
set => SetValue(TimelineSyncThresholdKey, value);
|
||||
}
|
||||
|
||||
private T? GetValue<T>(string key)
|
||||
{
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value))
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class TranslateService : BaseViewModel, ITranslateService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TranslateService(ISettingsService settingsService) :base(settingsService)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text and target language must be provided.");
|
||||
}
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(text);
|
||||
if (string.IsNullOrWhiteSpace(originalLangCode) || originalLangCode == targetLangCode)
|
||||
{
|
||||
return text; // No translation needed
|
||||
}
|
||||
else if (originalLangCode == "zh-Hant" && targetLangCode == "zh-Hans")
|
||||
{
|
||||
return ChineseConverter.ConvertToSimplifiedChinese(text);
|
||||
}
|
||||
else if (originalLangCode == "zh-Hans" && targetLangCode == "zh-Hant")
|
||||
{
|
||||
return ChineseConverter.ConvertToTraditionalChinese(text);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
|
||||
throw new InvalidOperationException("LibreTranslate server URL is not configured.");
|
||||
}
|
||||
|
||||
var url = $"{_settingsService.LibreTranslateServer}/translate";
|
||||
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
|
||||
[
|
||||
new("q", text),
|
||||
new("source", originalLangCode),
|
||||
new("target", targetLangCode),
|
||||
]));
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
token?.ThrowIfCancellationRequested();
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
|
||||
return result?.TranslatedText ?? string.Empty;
|
||||
}
|
||||
|
||||
public int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr)
|
||||
{
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
if (lyricsDataArr.Count > 1)
|
||||
{
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
{
|
||||
if (lyricsDataArr[i].LanguageCode == targetLangCode)
|
||||
{
|
||||
return i; // Translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // No translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>Local music libraries</value>
|
||||
<value>Local media library</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>Add folders storing music or lyrics</value>
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>Glow effect scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure lyrics search providers</value>
|
||||
<value>Configure lyrics source</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Drag to sort, the lyrics search order will be in the following order</value>
|
||||
@@ -310,7 +310,7 @@
|
||||
<value>No music playing now</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>Developer options</value>
|
||||
<value>Advanced options</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>Play test music</value>
|
||||
@@ -459,13 +459,10 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>Extra Black</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>Whole lyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>Current line</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>Current char</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
@@ -526,7 +523,7 @@
|
||||
<value>Exit</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>Unlock the window (Restart needed)</value>
|
||||
<value>Unlock the window</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
@@ -573,11 +570,11 @@
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>Easing animation type</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>Media sources</value>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>Playback sources</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Media source</value>
|
||||
<value>Playback sources</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Enable or disable lyrics display for a specified media source</value>
|
||||
@@ -654,4 +651,88 @@
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>Will automatically reset to 0 when switching songs</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>The translation in the lyrics will be read first. If there is no match, the machine translation will be requested from the LibreTranslate server</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure album cover source</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Drag to sort, the album art search order will be in the following order</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>Album art source</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>Local music files</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>Music player</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>Media library</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation type</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation duration</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>Linear</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>Smooth step</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>Ease-in-out sine</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>Ease-in-out quad</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>Ease-in-out elastic</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>Ease-in-out back</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>Ease-in-out bounce</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>Ease-in-out circ</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>Ease-in-out expo</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>Ease-in-out quint</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>Ease-in-out quart</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>Ease-in-out cubic</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>Current line start to current char</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>Highlight scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>Lyrics timeline sync threshold</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>If the lyrics progress is jittery, try increasing this threshold; changing this value can cause lyrics synchronization to deviate</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQ feedback & chat group</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>Join now</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>地元の音楽図書館</value>
|
||||
<value>地元のメディア図書館</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>音楽や歌詞を保存するフォルダーを追加します</value>
|
||||
@@ -280,7 +280,7 @@
|
||||
<value>について</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞ライブラリ</value>
|
||||
<value>歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>アプリの外観</value>
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>グローエフェクトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>歌詞検索プロバイダーを構成します</value>
|
||||
<value>歌詞ソースを構成します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>ドラッグしてソートすると、歌詞の検索注文は次の順序で行われます</value>
|
||||
@@ -310,7 +310,7 @@
|
||||
<value>今は音楽が再生されていません</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>開発者オプション</value>
|
||||
<value>高度なオプション</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>テスト音楽を再生します</value>
|
||||
@@ -459,13 +459,10 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>余分な黒</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>歌詞全体</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>現在の行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>現在の文字</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
@@ -526,7 +523,7 @@
|
||||
<value>プログラムを終了します</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>ウィンドウのロックを解除する(再起動が必要)</value>
|
||||
<value>ウィンドウのロックを解除します</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>ロック</value>
|
||||
@@ -573,11 +570,11 @@
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>アニメーションタイプを緩和します</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>メディアソース</value>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>再生ソース</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>メディアソース</value>
|
||||
<value>再生ソース</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>指定されたメディアソースの歌詞ディスプレイを有効または無効にする</value>
|
||||
@@ -654,4 +651,88 @@
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>曲を切り替えると、0 に自動的にリセットされます</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>歌詞の翻訳は最初に読まれます。一致していない場合、機械の翻訳はLibretranslate Serverから要求されます</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>アルバムカバーソースを構成します</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>ドラッグしてソートすると、アルバムアートサーチオーダーは次の順序で行われます</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>アルバムアートソース</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>ローカル音楽ファイル</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>音楽プレーヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>メディアライブラリ</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーションタイプ</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーションの期間</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>リニア</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>スムーズなステップ</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>サインがゆっくりと出入りします</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>セカンダリスローインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>弾力性は内外に遅くなります</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>リバウンドはスローアウトで遅くなります</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>ゆっくりと出入りします</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>丸い、ゆっくりと出入り</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>インデックスは内外に遅くなります</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>5つの遅いインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>4つの遅いインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>3つの遅いインとアウト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>現在のラインが現在の文字から始まります</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>ハイライトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌詞タイムライン同期しきい値</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>歌詞の進行が不安定な場合は、このしきい値を増やしてみてください。この値を変更すると、歌詞の同期が逸脱する可能性があります</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQフィードバック&チャットグループ</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>今すぐ参加してください</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>로컬 음악 도서관</value>
|
||||
<value>로컬 미디어 라이브러리</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>음악이나 가사를 저장하는 폴더 추가</value>
|
||||
@@ -280,7 +280,7 @@
|
||||
<value>에 대한</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>가사 도서관</value>
|
||||
<value>가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>앱 모양</value>
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>글로우 효과 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>가사 검색 제공 업체를 구성하십시오</value>
|
||||
<value>가사 소스를 구성하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>정렬하기 위해 드래그하면 가사 검색 순서는 다음 순서로됩니다.</value>
|
||||
@@ -310,7 +310,7 @@
|
||||
<value>지금 음악이 재생되지 않습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>개발자 옵션</value>
|
||||
<value>고급 옵션</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>테스트 음악을 재생하십시오</value>
|
||||
@@ -459,13 +459,10 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>여분의 검은 색</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>전체 가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>현재 라인</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>현재 숯</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
@@ -526,7 +523,7 @@
|
||||
<value>프로그램을 종료하십시오</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>창 잠금 해제 (다시 시작)</value>
|
||||
<value>창을 잠금 해제하십시오</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>잠금</value>
|
||||
@@ -573,11 +570,11 @@
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>애니메이션 유형 완화</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>미디어 소스</value>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>재생 소스</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>미디어 소스</value>
|
||||
<value>재생 소스</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>지정된 미디어 소스의 가사 디스플레이 활성화 또는 비활성화</value>
|
||||
@@ -654,4 +651,88 @@
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>노래를 전환 할 때 자동으로 0 으로 재설정됩니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>가사의 번역은 먼저 읽습니다. 일치하지 않으면 LibreTranslate 서버에서 기계 번역이 요청됩니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>앨범 표지 소스를 구성합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>분류하기 위해 드래그하면 앨범 아트 검색 순서는 다음 순서로됩니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>앨범 아트 소스</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>로컬 음악 파일</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>음악 플레이어</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>미디어 라이브러리</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>가사 스크롤링 애니메이션 유형</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>가사 스크롤링 애니메이션 지속 시간</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>선의</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>부드러운 단계</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>천천히 입력하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>이차 느린 속도가 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>탄력성이 속도가 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>리바운드는 느리게 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>천천히 안팎으로 튀어 나옵니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>둥글고 느리게 안팎으로</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>인덱스 속도가 느려집니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>5 번 느리게 안팎으로</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>4 개의 느린 안팎</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>세 번 느리게 안팎으로</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>현재 라인은 현재 숯으로 시작합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>하이라이트 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>가사 타임 라인 동기화 임계 값</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>가사 진행 상황이 불안하다면이 임계 값을 높이십시오. 이 값을 변경하면 가사가 동기화 될 수 있습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQ 피드백 및 채팅 그룹</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>지금 가입하십시오</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>本地音乐媒体库</value>
|
||||
<value>本地媒体库</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>添加存放音乐或歌词的文件夹</value>
|
||||
@@ -280,7 +280,7 @@
|
||||
<value>关于</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌词库</value>
|
||||
<value>歌词源</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>应用外观</value>
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>辉光效果作用范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌词搜索服务</value>
|
||||
<value>配置歌词源</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>拖动排序,歌词搜索顺序将按以下顺序</value>
|
||||
@@ -310,7 +310,7 @@
|
||||
<value>当前没有正在播放的音乐</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>开发者选项</value>
|
||||
<value>高级选项</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>播放测试音乐</value>
|
||||
@@ -459,13 +459,10 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>超黑</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>全部歌词</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>当前行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>当前字符</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
@@ -526,7 +523,7 @@
|
||||
<value>退出程序</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>解锁窗口(需要重新启动)</value>
|
||||
<value>解锁窗口</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>锁定</value>
|
||||
@@ -573,11 +570,11 @@
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>缓动动画类型</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>媒体来源</value>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>播放源</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>媒体来源</value>
|
||||
<value>播放源</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>为指定媒体源启用或禁用歌词显示</value>
|
||||
@@ -654,4 +651,88 @@
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>切换歌曲时将自动重置为 0</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>将优先读取歌词内翻译,若无匹配则向 LibreTranslate 服务器请求机器翻译</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置专辑封面源</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>拖动排序,专辑封面搜索顺序将按以下顺序</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>专辑封面源</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>本地音乐文件</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>音乐播放器</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>媒体库</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌词滚动动画类型</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌词滚动动画持续时间</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>线性</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>平滑步进</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>正弦缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>二次缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>弹性缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>回弹缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>弹跳缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>圆形缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>指数缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>五次缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>四次缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>三次缓入缓出</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>当前歌词开始到当前字符</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>高亮显示范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌词时间轴同步阈值</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>当歌词进度抖动时,请尝试增加该阈值;更改此值会导致歌词同步有偏差</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQ 反馈交流群</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>立即加入</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>本地音樂媒體庫</value>
|
||||
<value>本地媒體庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>新增存放音樂或歌詞的資料夾</value>
|
||||
@@ -280,7 +280,7 @@
|
||||
<value>關於</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞庫</value>
|
||||
<value>歌詞源</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>應用外觀</value>
|
||||
@@ -292,7 +292,7 @@
|
||||
<value>輝光效果作用範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌詞搜尋服務</value>
|
||||
<value>配置歌詞源</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>拖動排序,歌詞搜索順序將按以下順序</value>
|
||||
@@ -310,7 +310,7 @@
|
||||
<value>目前沒有正在播放的音樂</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>開發者選項</value>
|
||||
<value>高級選項</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>播放測試音樂</value>
|
||||
@@ -459,13 +459,10 @@
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>超黑</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>全部歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>目前行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsRendingScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>目前字元</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
@@ -526,7 +523,7 @@
|
||||
<value>退出程序</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>解鎖窗口(需要重新啟動)</value>
|
||||
<value>解鎖窗口</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>鎖定</value>
|
||||
@@ -573,11 +570,11 @@
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>缓动动画类型</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>媒體來源</value>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>播放來源</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>媒體來源</value>
|
||||
<value>播放來源</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
|
||||
<value>為指定媒體源啟用或禁用歌詞顯示</value>
|
||||
@@ -654,4 +651,88 @@
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>將在切換歌曲時自動重設為 0</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>將優先讀取歌詞內翻譯,若無匹配則向 LibreTranslate 伺服器請求機器翻譯</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置專輯封面源</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>拖曳排序,專輯封面搜尋順序將按以下順序</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>專輯封面來源</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchLocalProvider" xml:space="preserve">
|
||||
<value>本地音樂文件</value>
|
||||
</data>
|
||||
<data name="AlbumArtSearchSMTCProvider" xml:space="preserve">
|
||||
<value>音樂播放器</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>媒體庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌詞滾動動畫類型</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌詞滾動動畫持續時間</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeLinear.Content" xml:space="preserve">
|
||||
<value>線性</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
|
||||
<value>平滑步進</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutSine.Content" xml:space="preserve">
|
||||
<value>正弦緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuad.Content" xml:space="preserve">
|
||||
<value>二次緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutElastic.Content" xml:space="preserve">
|
||||
<value>彈性緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBack.Content" xml:space="preserve">
|
||||
<value>回彈緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutBounce.Content" xml:space="preserve">
|
||||
<value>彈跳緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCirc.Content" xml:space="preserve">
|
||||
<value>圓形緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutExpo.Content" xml:space="preserve">
|
||||
<value>指數緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuint.Content" xml:space="preserve">
|
||||
<value>五次緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutQuart.Content" xml:space="preserve">
|
||||
<value>四次緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingTypeEaseInOutCubic.Content" xml:space="preserve">
|
||||
<value>三次緩入緩出</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
|
||||
<value>當前歌詞開始到當前字符</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>高亮顯示範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌詞時間軸同步閾值</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>當歌詞進度抖動時,請嘗試增加該閾值;更改此值會導致歌詞同步偏差</value>
|
||||
</data>
|
||||
<data name="SettingsPageQQGroup.Header" xml:space="preserve">
|
||||
<value>QQ 回饋交流群</value>
|
||||
</data>
|
||||
<data name="SettingsPageDiscord.Header" xml:space="preserve">
|
||||
<value>Discord</value>
|
||||
</data>
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>立即加入</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -11,8 +11,7 @@ using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseWindowViewModel : BaseViewModel
|
||||
public partial class BaseWindowViewModel(ISettingsService settingsService) : BaseViewModel(settingsService)
|
||||
{
|
||||
public BaseWindowViewModel(ISettingsService settingsService) : base(settingsService) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<int>>, IRecipient<PropertyChangedMessage<bool>>
|
||||
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
private readonly IPlaybackService _playbackService;
|
||||
|
||||
@@ -23,15 +23,12 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
|
||||
{
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
IsFirstRun = _settingsService.IsFirstRun;
|
||||
IsTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
PreferredDisplayType = _settingsService.PreferredDisplayType;
|
||||
|
||||
_playbackService = playbackService;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
|
||||
|
||||
IsFirstRun = _settingsService.IsFirstRun;
|
||||
}
|
||||
|
||||
private void PlaybackService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
|
||||
@@ -51,12 +48,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial bool IsWelcomeTeachingTipOpen { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Visibility BottomCommandGridVisibility { get; set; } = Visibility.Visible;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsDisplayType PreferredDisplayType { get; set; }
|
||||
|
||||
@@ -69,7 +60,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsTranslationEnabled { get; set; } = false;
|
||||
public partial bool IsTranslationEnabled { get; set; }
|
||||
|
||||
partial void OnIsTranslationEnabledChanged(bool value)
|
||||
{
|
||||
@@ -98,19 +89,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
|
||||
{
|
||||
LyricsFontSize = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettingsWindow()
|
||||
private static void OpenSettingsWindow()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
public LyricsRendererViewModel(ISettingsService settingsService, IPlaybackService playbackService, ILyricsSearchService musicSearchService, ILibWatcherService libWatcherService, ITranslateService libreTranslateService) : base(settingsService)
|
||||
{
|
||||
_lyrcsSearchService = musicSearchService;
|
||||
_playbackService = playbackService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_translateService = libreTranslateService;
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
|
||||
|
||||
_albumArtCornerRadius = _settingsService.CoverImageRadius;
|
||||
_isDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
_albumArtBgOpacity = _settingsService.CoverOverlayOpacity;
|
||||
_albumArtBgBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
|
||||
_lyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
_lyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
|
||||
|
||||
_lyricsTextFormat.FontWeight = _settingsService.LyricsFontWeight.ToFontWeight();
|
||||
|
||||
_lyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
_lyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
||||
_lyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
||||
_lyricsFontSize = _settingsService.LyricsFontSize;
|
||||
_lyricsBlurAmount = _settingsService.LyricsBlurAmount;
|
||||
_isLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
_lyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
_lyricsHighlightScope = _settingsService.LyricsHighlightScope;
|
||||
|
||||
_customBgFontColor = _settingsService.LyricsCustomBgFontColor;
|
||||
_customFgFontColor = _settingsService.LyricsCustomFgFontColor;
|
||||
|
||||
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
|
||||
|
||||
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
|
||||
|
||||
_timelineSyncThreshold = _settingsService.TimelineSyncThreshold;
|
||||
|
||||
_canvasYScrollTransition.SetDuration(_settingsService.LyricsScrollDuration / 1000f);
|
||||
_canvasYScrollTransition.SetEasingType(_settingsService.LyricsScrollEasingType);
|
||||
|
||||
_libWatcherService.MusicLibraryFilesChanged +=
|
||||
LibWatcherService_MusicLibraryFilesChanged;
|
||||
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
|
||||
_playbackService.PositionChanged += PlaybackService_PositionChanged;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,30 +44,32 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
using var combined = new CanvasCommandList(control);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
DrawAlbumArtBackground(control, combinedDs);
|
||||
|
||||
if (_isDockMode)
|
||||
{
|
||||
DrawImmersiveBackground(control, combinedDs);
|
||||
DrawImmersiveBackground(control, combinedDs, 0f);
|
||||
}
|
||||
combinedDs.DrawImage(blurredLyrics);
|
||||
|
||||
if (_isDesktopMode)
|
||||
else if (_isDesktopMode)
|
||||
{
|
||||
ds.DrawImage(blurredLyrics);
|
||||
DrawImmersiveBackground(control, combinedDs, 12f);
|
||||
}
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
DrawAlbumArtBackground(control, combinedDs);
|
||||
}
|
||||
|
||||
combinedDs.DrawImage(blurredLyrics);
|
||||
|
||||
ds.DrawImage(combined);
|
||||
|
||||
DrawAlbumArt(control, ds);
|
||||
DrawTitleAndArtist(control, ds);
|
||||
|
||||
if (_isDebugOverlayEnabled)
|
||||
{
|
||||
var currentPlayingLine = _multiLangLyrics
|
||||
.SafeGet(_langIndex)
|
||||
?.SafeGet(_playingLineIndex);
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine != null)
|
||||
{
|
||||
@@ -78,29 +80,29 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
out float charProgress
|
||||
);
|
||||
|
||||
//ds.DrawText(
|
||||
// $"[DEBUG]\n" +
|
||||
// $"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
|
||||
// $"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
|
||||
// $"Cur time {_totalTime + _positionOffset}\n" +
|
||||
// $"Lang size {_multiLangLyrics.Count}\n" +
|
||||
// $"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
|
||||
// new Vector2(10, 10),
|
||||
// ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
|
||||
//);
|
||||
ds.DrawText(
|
||||
$"[DEBUG]\n" +
|
||||
$"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
|
||||
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
|
||||
$"Cur time {_totalTime + _positionOffset}\n" +
|
||||
$"Lang size {_lyricsDataArr.Count}\n" +
|
||||
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
|
||||
new Vector2(10, 10),
|
||||
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
|
||||
);
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
if (line != null)
|
||||
{
|
||||
ds.DrawText(
|
||||
$"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
|
||||
new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
|
||||
ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
|
||||
);
|
||||
}
|
||||
}
|
||||
//for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
//{
|
||||
// LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
// if (line != null)
|
||||
// {
|
||||
// ds.DrawText(
|
||||
// $"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
|
||||
// new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
|
||||
// ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
|
||||
// );
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,18 +112,19 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
float imageWidth = (float)canvasBitmap.Size.Width;
|
||||
float imageHeight = (float)canvasBitmap.Size.Height;
|
||||
|
||||
float scaleFactor = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2)) / MathF.Min(imageWidth, imageHeight);
|
||||
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2)) * 1.4f;
|
||||
float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);
|
||||
|
||||
float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
|
||||
float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
|
||||
|
||||
// Source: https://zhuanlan.zhihu.com/p/37178216
|
||||
float bright = _lyricsBgBrightnessTransition.Value / 1f * 2f; // 明度参数,范围在0.0f到2.0f之间
|
||||
// Original source: https://zhuanlan.zhihu.com/p/37178216
|
||||
float gain = _lyricsBgBrightnessTransition.Value;
|
||||
|
||||
float whiteX = Math.Min(2 - bright, 1);
|
||||
float whiteY = 1f;
|
||||
float blackX = Math.Max(1 - bright, 0);
|
||||
float blackY = 0f;
|
||||
float whiteX = 1 - 0.5f * gain;
|
||||
float whiteY = 0.5f + 0.5f * gain;
|
||||
float blackX = 0.5f - 0.5f * gain;
|
||||
float blackY = 0 + 0.5f * gain;
|
||||
|
||||
ds.DrawImage(new OpacityEffect
|
||||
{
|
||||
@@ -193,11 +196,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = CoverOverlayOpacity / 100f,
|
||||
Opacity = _albumArtBgOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = CoverOverlayBlurAmount,
|
||||
BlurAmount = _albumArtBgBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
},
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
@@ -269,9 +274,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
var currentPlayingLine = _multiLangLyrics
|
||||
.SafeGet(_langIndex)
|
||||
?.SafeGet(_playingLineIndex);
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
@@ -280,7 +285,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
@@ -307,7 +312,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
float centerX = position.X;
|
||||
float centerY = position.Y + layoutHeight / 2;
|
||||
|
||||
switch (LyricsAlignmentType)
|
||||
switch (_lyricsAlignmentType)
|
||||
{
|
||||
case TextAlignmentType.Left:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
||||
@@ -473,16 +478,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
Source = new BlendEffect
|
||||
{
|
||||
Background = IsLyricsGlowEffectEnabled
|
||||
Background = _isLyricsGlowEffectEnabled
|
||||
? new GaussianBlurEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = fgLyrics,
|
||||
AlphaMask = LyricsGlowEffectScope switch
|
||||
AlphaMask = _lyricsGlowEffectScope switch
|
||||
{
|
||||
LineRenderingType.UntilCurrentChar => mask,
|
||||
LineRenderingType.CurrentCharOnly => highlightMask,
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => fgLyrics,
|
||||
_ => mask,
|
||||
},
|
||||
},
|
||||
@@ -493,7 +499,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
Foreground = new AlphaMaskEffect
|
||||
{
|
||||
Source = fgLyrics,
|
||||
AlphaMask = mask,
|
||||
AlphaMask = _lyricsHighlightScope switch
|
||||
{
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => fgLyrics,
|
||||
_ => mask,
|
||||
},
|
||||
},
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
@@ -506,41 +518,21 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawImmersiveBackground(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
bool withGradient = true
|
||||
)
|
||||
private void DrawImmersiveBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, float radius)
|
||||
{
|
||||
ds.FillRectangle(
|
||||
CanvasCommandList list = new(control.Device);
|
||||
using var listDs = list.CreateDrawingSession();
|
||||
listDs.FillRoundedRectangle(
|
||||
new Rect(0, 0, _canvasWidth, _canvasHeight),
|
||||
new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 0f,
|
||||
Color = withGradient
|
||||
? Color.FromArgb(
|
||||
211,
|
||||
_immersiveBgTransition.Value.R,
|
||||
_immersiveBgTransition.Value.G,
|
||||
_immersiveBgTransition.Value.B
|
||||
)
|
||||
: _immersiveBgTransition.Value,
|
||||
},
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 1,
|
||||
Color = _immersiveBgTransition.Value,
|
||||
},
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(0, 0),
|
||||
EndPoint = new Vector2(0, _canvasHeight),
|
||||
}
|
||||
radius,
|
||||
radius,
|
||||
_immersiveBgTransition.Value
|
||||
);
|
||||
ds.DrawImage(new OpacityEffect
|
||||
{
|
||||
Source = list,
|
||||
Opacity = _immersiveBgOpacityTransition.Value
|
||||
});
|
||||
}
|
||||
|
||||
private CanvasLinearGradientBrush GetHorizontalFillBrush(
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
|
||||
IRecipient<PropertyChangedMessage<LineRenderingType>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<EasingType>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
|
||||
{
|
||||
@@ -32,7 +33,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
|
||||
RefreshLyricsAsync();
|
||||
_ = _refreshLyricsRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await RefreshLyricsAsync(tokne);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,20 +49,21 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
// Lyrics search providers info changed, re-fetch lyrics
|
||||
_logger.LogInformation("Lyrics search providers info changed, refreshing lyrics.");
|
||||
RefreshLyricsAsync();
|
||||
_ = _refreshLyricsRunner.RunAsync(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receive methods for handling messages from other view models
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.IsDynamicCoverOverlayEnabled))
|
||||
{
|
||||
IsDynamicCoverOverlayEnabled = message.NewValue;
|
||||
_isDynamicCoverOverlayEnabled = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled))
|
||||
{
|
||||
@@ -66,7 +71,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsGlowEffectEnabled))
|
||||
{
|
||||
IsLyricsGlowEffectEnabled = message.NewValue;
|
||||
_isLyricsGlowEffectEnabled = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsFanLyricsEnabled))
|
||||
{
|
||||
@@ -79,10 +84,21 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
|
||||
{
|
||||
_isDockMode = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
_isDesktopMode = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
|
||||
{
|
||||
_isLyricsWindowLocked = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsMouseWithinWindow))
|
||||
{
|
||||
_isMouseWithinWindow = message.NewValue;
|
||||
_immersiveBgOpacityTransition.StartTransition(_isDesktopMode ? (_isMouseWithinWindow ? 1f : 0f) : 1f);
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsPageViewModel)
|
||||
@@ -91,7 +107,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_isTranslationEnabled = message.NewValue;
|
||||
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _isTranslationEnabled);
|
||||
UpdateTranslationsAsync();
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +120,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_immersiveBgTransition.StartTransition(message.NewValue);
|
||||
_environmentalColor = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is SettingsPageViewModel)
|
||||
@@ -112,17 +128,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomBgFontColor))
|
||||
{
|
||||
_customBgFontColor = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomFgFontColor))
|
||||
{
|
||||
_customFgFontColor = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomStrokeFontColor))
|
||||
{
|
||||
_customStrokeFontColor = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +149,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsLineSpacingFactor))
|
||||
{
|
||||
LyricsLineSpacingFactor = message.NewValue;
|
||||
_lyricsLineSpacingFactor = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,36 +165,45 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
|
||||
{
|
||||
CoverOverlayOpacity = message.NewValue;
|
||||
_albumArtBgOpacity = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayBlurAmount))
|
||||
{
|
||||
CoverOverlayBlurAmount = message.NewValue;
|
||||
_albumArtBgBlurAmount = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsVerticalEdgeOpacity))
|
||||
{
|
||||
LyricsVerticalEdgeOpacity = message.NewValue;
|
||||
_lyricsVerticalEdgeOpacity = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBlurAmount))
|
||||
{
|
||||
LyricsBlurAmount = message.NewValue;
|
||||
_lyricsBlurAmount = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
|
||||
{
|
||||
LyricsFontSize = message.NewValue;
|
||||
_lyricsFontSize = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.SelectedTargetLanguageIndex))
|
||||
{
|
||||
_targetLanguageIndex = message.NewValue;
|
||||
_logger.LogInformation("Target language index changed: {Index}", _targetLanguageIndex);
|
||||
UpdateTranslationsAsync();
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontStrokeWidth))
|
||||
{
|
||||
_lyricsFontStrokeWidth = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsScrollDuration))
|
||||
{
|
||||
_canvasYScrollTransition.SetDuration(message.NewValue / 1000f);
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.TimelineSyncThreshold))
|
||||
{
|
||||
_timelineSyncThreshold = message.NewValue;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsPageViewModel)
|
||||
{
|
||||
@@ -194,7 +220,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsGlowEffectScope))
|
||||
{
|
||||
LyricsGlowEffectScope = message.NewValue;
|
||||
_lyricsGlowEffectScope = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsHighlightScope))
|
||||
{
|
||||
_lyricsHighlightScope = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +235,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsAlignmentType))
|
||||
{
|
||||
LyricsAlignmentType = message.NewValue;
|
||||
_lyricsAlignmentType = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.SongInfoAlignmentType))
|
||||
{
|
||||
@@ -227,17 +257,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontColorType))
|
||||
{
|
||||
_lyricsBgFontColorType = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFgFontColorType))
|
||||
{
|
||||
_lyricsFgFontColorType = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStrokeFontColorType))
|
||||
{
|
||||
_lyricsStrokeFontColorType = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +278,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontWeight))
|
||||
{
|
||||
LyricsFontWeight = message.NewValue;
|
||||
_lyricsTextFormat.FontWeight = message.NewValue.ToFontWeight();
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,25 +291,20 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBackgroundTheme))
|
||||
{
|
||||
_lyricsBgTheme = message.NewValue;
|
||||
UpdateFontColor();
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
public void Receive(PropertyChangedMessage<EasingType> message)
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_lyricsTextFormat.FontWeight = value.ToFontWeight();
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsScrollEasingType))
|
||||
{
|
||||
_canvasYScrollTransition.SetEasingType(message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,7 +14,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
private readonly ValueTransition<float> _canvasYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f
|
||||
durationSeconds: 0.5f,
|
||||
easingType: EasingType.EaseInOutCubic
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _immersiveBgTransition = new(
|
||||
@@ -22,6 +24,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _immersiveBgOpacityTransition = new(
|
||||
initialValue: 1f,
|
||||
durationSeconds: 0.2f
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _lyricsXTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f
|
||||
|
||||
@@ -7,6 +7,7 @@ using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
|
||||
@@ -43,12 +44,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_displayType = _displayTypeReceived;
|
||||
_playingLineIndex = playingLineIndex;
|
||||
|
||||
_immersiveBgOpacityTransition.Update(_elapsedTime);
|
||||
_immersiveBgTransition.Update(_elapsedTime);
|
||||
_albumArtBgTransition.Update(_elapsedTime);
|
||||
_lyricsBgBrightnessTransition.Update(_elapsedTime);
|
||||
_songInfoOpacityTransition.Update(_elapsedTime);
|
||||
|
||||
if (IsDynamicCoverOverlayEnabled)
|
||||
if (_isDynamicCoverOverlayEnabled)
|
||||
{
|
||||
_rotateAngle += _coverRotateSpeed;
|
||||
_rotateAngle %= MathF.PI * 2;
|
||||
@@ -129,14 +131,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
_lyricsTextFormat.FontSize = LyricsFontSize;
|
||||
_lyricsTextFormat.FontSize = _lyricsFontSize;
|
||||
|
||||
float y = 0;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
|
||||
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _multiLangLyrics[_langIndex].SafeGet(i);
|
||||
var line = _lyricsDataArr[_langIndex].LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
@@ -163,7 +165,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
y +=
|
||||
(float)line.CanvasTextLayout.LayoutBounds.Height
|
||||
/ line.CanvasTextLayout.LineCount
|
||||
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
|
||||
* (line.CanvasTextLayout.LineCount + _lyricsLineSpacingFactor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +179,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
if ((!_isPlayingLineChanged && forceScroll) || _isPlayingLineChanged)
|
||||
{
|
||||
LyricsLine? currentPlayingLine = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(_playingLineIndex);
|
||||
LyricsLine? currentPlayingLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
@@ -185,7 +187,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
if (playingTextLayout == null) return;
|
||||
|
||||
float? targetYScrollOffset = (float?)(-currentPlayingLine!.Position.Y + _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2);
|
||||
float? targetYScrollOffset = (float?)(-currentPlayingLine!.Position.Y + _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2);
|
||||
|
||||
if (!targetYScrollOffset.HasValue) return;
|
||||
|
||||
@@ -200,7 +202,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
// Update visible line indices
|
||||
for (int i = startLineIndex; i <= endLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null || line.CanvasTextLayout == null)
|
||||
{
|
||||
@@ -248,7 +250,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_endVisibleLineIndex = endVisibleLineIndex;
|
||||
}
|
||||
|
||||
private void UpdateFontColor()
|
||||
private void UpdateColorConfig()
|
||||
{
|
||||
if (_isDesktopMode || _isDockMode)
|
||||
{
|
||||
@@ -359,15 +361,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void UpdateLinesProps()
|
||||
{
|
||||
var currentPlayingLine = _multiLangLyrics
|
||||
.SafeGet(_langIndex)
|
||||
?.SafeGet(_playingLineIndex);
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null) continue;
|
||||
|
||||
@@ -384,9 +386,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
: 0
|
||||
);
|
||||
|
||||
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceFactor);
|
||||
line.BlurAmountTransition.StartTransition(_lyricsBlurAmount * distanceFactor);
|
||||
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
|
||||
line.OpacityTransition.StartTransition(_defaultOpacity - distanceFactor * _defaultOpacity * (1 - LyricsVerticalEdgeOpacity / 100f));
|
||||
line.OpacityTransition.StartTransition(_defaultOpacity - distanceFactor * _defaultOpacity * (1 - _lyricsVerticalEdgeOpacity / 100f));
|
||||
line.HighlightOpacityTransition.StartTransition(i == _playingLineIndex ? 1f : 0f);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
@@ -63,14 +64,30 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private readonly float _coverRotateSpeed = 0.003f;
|
||||
private float _rotateAngle = 0f;
|
||||
|
||||
private TextAlignmentType _lyricsAlignmentType;
|
||||
|
||||
private readonly float _lyricsGlowEffectAmount = 8f;
|
||||
private int _lyricsBlurAmount;
|
||||
private int _lyricsVerticalEdgeOpacity;
|
||||
|
||||
private ElementTheme _lyricsBgTheme;
|
||||
private LineRenderingType _lyricsGlowEffectScope;
|
||||
private LineRenderingType _lyricsHighlightScope;
|
||||
|
||||
private int _lyricsFontStrokeWidth;
|
||||
private int _lyricsFontSize;
|
||||
private float _lyricsLineSpacingFactor;
|
||||
|
||||
private LyricsFontColorType _lyricsBgFontColorType;
|
||||
private LyricsFontColorType _lyricsFgFontColorType;
|
||||
private LyricsFontColorType _lyricsStrokeFontColorType;
|
||||
|
||||
private float _maxLyricsWidth = 0f;
|
||||
|
||||
private readonly IMusicSearchService _musicSearchService;
|
||||
private readonly ILyricsSearchService _lyrcsSearchService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly IPlaybackService _playbackService;
|
||||
private readonly ILibreTranslateService _libreTranslateService;
|
||||
private readonly ITranslateService _translateService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly float _leftMargin = 36f;
|
||||
@@ -96,14 +113,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private Color? _customFgFontColor;
|
||||
private Color? _customStrokeFontColor;
|
||||
|
||||
private LyricsFontColorType _lyricsBgFontColorType;
|
||||
private LyricsFontColorType _lyricsFgFontColorType;
|
||||
private LyricsFontColorType _lyricsStrokeFontColorType;
|
||||
|
||||
private ElementTheme _lyricsBgTheme;
|
||||
|
||||
private int _lyricsFontStrokeWidth;
|
||||
|
||||
private int _playingLineIndex = -1;
|
||||
|
||||
private int _startVisibleLineIndex = -1;
|
||||
@@ -116,15 +125,23 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private bool _isPlaying = true;
|
||||
|
||||
private bool _isLyricsWindowLocked = false;
|
||||
private bool _isMouseWithinWindow = false;
|
||||
|
||||
private bool _isDynamicCoverOverlayEnabled;
|
||||
private bool _isLyricsGlowEffectEnabled;
|
||||
|
||||
private bool _isLayoutChanged = true;
|
||||
|
||||
private int _langIndex = 0;
|
||||
|
||||
private List<List<LyricsLine>> _multiLangLyrics = [];
|
||||
private List<string> _translations = [];
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
private List<string> _translationList = [];
|
||||
private bool _isTranslationEnabled = false;
|
||||
private int _targetLanguageIndex = 6;
|
||||
|
||||
private int _timelineSyncThreshold;
|
||||
|
||||
private CanvasTextFormat _lyricsTextFormat = new()
|
||||
{
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
@@ -149,92 +166,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
|
||||
};
|
||||
|
||||
private Task? _refreshLyricsTask;
|
||||
private CancellationTokenSource? _refreshLyricsCts;
|
||||
|
||||
private Task? _showTranslationsTask;
|
||||
private CancellationTokenSource? _showTranslationsCts;
|
||||
|
||||
public LyricsRendererViewModel(ISettingsService settingsService, IPlaybackService playbackService, IMusicSearchService musicSearchService, ILibWatcherService libWatcherService, ILibreTranslateService libreTranslateService) : base(settingsService)
|
||||
{
|
||||
_musicSearchService = musicSearchService;
|
||||
_playbackService = playbackService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_libreTranslateService = libreTranslateService;
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
|
||||
|
||||
_albumArtCornerRadius = _settingsService.CoverImageRadius;
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
|
||||
_lyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
_lyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
|
||||
|
||||
LyricsFontWeight = _settingsService.LyricsFontWeight;
|
||||
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
||||
LyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
|
||||
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
|
||||
_customBgFontColor = _settingsService.LyricsCustomBgFontColor;
|
||||
_customFgFontColor = _settingsService.LyricsCustomFgFontColor;
|
||||
|
||||
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
|
||||
|
||||
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
|
||||
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
|
||||
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
|
||||
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
|
||||
|
||||
_libWatcherService.MusicLibraryFilesChanged +=
|
||||
LibWatcherService_MusicLibraryFilesChanged;
|
||||
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.PositionChanged += PlaybackService_PositionChanged;
|
||||
|
||||
UpdateFontColor();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
public int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
public int CoverOverlayOpacity { get; set; }
|
||||
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
|
||||
private LatestOnlyTaskRunner _showTranslationsRunner = new();
|
||||
|
||||
private LyricsDisplayType _displayTypeReceived = LyricsDisplayType.PlaceholderOnly;
|
||||
private LyricsDisplayType _displayType = LyricsDisplayType.PlaceholderOnly;
|
||||
|
||||
public bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
|
||||
public bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
|
||||
public TextAlignmentType LyricsAlignmentType { get; set; }
|
||||
|
||||
public int LyricsBlurAmount { get; set; }
|
||||
private int _albumArtBgBlurAmount;
|
||||
private int _albumArtBgOpacity;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
public LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
public int LyricsVerticalEdgeOpacity { get; set; }
|
||||
public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; }
|
||||
@@ -245,9 +187,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
|
||||
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?[i];
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[i];
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
@@ -323,20 +265,23 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (
|
||||
SongInfo == null
|
||||
|| _multiLangLyrics.SafeGet(_langIndex) == null
|
||||
|| _multiLangLyrics[_langIndex].Count == 0
|
||||
|| _lyricsDataArr.ElementAtOrDefault(_langIndex) == null
|
||||
|| _lyricsDataArr[_langIndex].LyricsLines.Count == 0
|
||||
)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, _multiLangLyrics[_langIndex].Count - 1);
|
||||
return new Tuple<int, int>(0, _lyricsDataArr[_langIndex].LyricsLines.Count - 1);
|
||||
}
|
||||
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
|
||||
{
|
||||
_logger.LogInformation("Music library files changed: {ChangeType} {FilePath}, refreshing lyrics...", e.ChangeType, e.FilePath);
|
||||
RefreshLyricsAsync();
|
||||
_ = _refreshLyricsRunner.RunAsync(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
|
||||
@@ -346,32 +291,16 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
|
||||
{
|
||||
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) > 300)
|
||||
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
|
||||
{
|
||||
_totalTime = e.Position;
|
||||
}
|
||||
}
|
||||
|
||||
private async void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
||||
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
||||
{
|
||||
SongInfo = e.SongInfo;
|
||||
|
||||
if (SongInfo?.AlbumArtSwBitmap != _albumArtSwBitmap)
|
||||
{
|
||||
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
|
||||
_lastAlbumArtCanvasBitmap = null;
|
||||
|
||||
_albumArtSwBitmap = SongInfo?.AlbumArtSwBitmap;
|
||||
_albumArtCanvasBitmap = null;
|
||||
|
||||
_albumArtAccentColor = SongInfo?.AlbumArtAccentColor;
|
||||
|
||||
_albumArtBgTransition.Reset(0f);
|
||||
_albumArtBgTransition.StartTransition(1f);
|
||||
|
||||
UpdateFontColor();
|
||||
}
|
||||
|
||||
if (SongInfo?.Title != _songTitle || SongInfo?.Artist != _songArtist)
|
||||
{
|
||||
_lastSongTitle = _songTitle;
|
||||
@@ -384,182 +313,117 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_songInfoOpacityTransition.StartTransition(1f);
|
||||
|
||||
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
|
||||
await RefreshLyricsAsync();
|
||||
|
||||
_ = _refreshLyricsRunner.RunAsync(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
_totalTime = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshLyricsAsync()
|
||||
private void PlaybackService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
|
||||
{
|
||||
// 取消上一次
|
||||
_refreshLyricsCts?.Cancel();
|
||||
if (_refreshLyricsTask != null)
|
||||
if (e.AlbumArtSwBitmap != _albumArtSwBitmap)
|
||||
{
|
||||
await _refreshLyricsTask;
|
||||
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
|
||||
_lastAlbumArtCanvasBitmap = null;
|
||||
|
||||
_albumArtSwBitmap = e.AlbumArtSwBitmap;
|
||||
_albumArtCanvasBitmap = null;
|
||||
|
||||
_albumArtAccentColor = e.AlbumArtAccentColor;
|
||||
|
||||
_albumArtBgTransition.Reset(0f);
|
||||
_albumArtBgTransition.StartTransition(1f);
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
_refreshLyricsCts = cts;
|
||||
var token = cts.Token;
|
||||
|
||||
_refreshLyricsTask = RefreshLyricsCoreAsync(token);
|
||||
await _refreshLyricsTask;
|
||||
}
|
||||
|
||||
private async Task UpdateTranslationsAsync()
|
||||
private void UpdateTranslations()
|
||||
{
|
||||
IsTranslating = true;
|
||||
if (_isTranslationEnabled)
|
||||
{
|
||||
await ShowWithTranslationsAsync();
|
||||
_ = _refreshLyricsRunner.RunAsync(async token =>
|
||||
{
|
||||
await SetDisplayedAlongWithTranslationsAsync(token);
|
||||
IsTranslating = false;
|
||||
_isLayoutChanged = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowOriginalsOnly();
|
||||
}
|
||||
IsTranslating = false;
|
||||
}
|
||||
|
||||
private async Task ShowWithTranslationsAsync()
|
||||
{
|
||||
_showTranslationsCts?.Cancel();
|
||||
if (_showTranslationsTask != null)
|
||||
{
|
||||
await _showTranslationsTask;
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
_showTranslationsCts = cts;
|
||||
var token = cts.Token;
|
||||
|
||||
_showTranslationsTask = ShowTranslationsCoreAsync(token);
|
||||
await _showTranslationsTask;
|
||||
}
|
||||
|
||||
private async Task ShowTranslationsCoreAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Showing translations for lyrics...");
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
ShowOriginalsOnly();
|
||||
return;
|
||||
}
|
||||
var text = string.Join("\n", _multiLangLyrics.FirstOrDefault()?.Select(x => x.OriginalText) ?? []);
|
||||
var translated = await _libreTranslateService.TranslateAsync(text, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_translations = translated.Split('\n').ToList();
|
||||
bool totallySame = true;
|
||||
foreach (var langLyrics in _multiLangLyrics)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in langLyrics)
|
||||
{
|
||||
if (line.OriginalText != _translations[i])
|
||||
{
|
||||
totallySame = false;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var langLyrics in _multiLangLyrics)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in langLyrics)
|
||||
{
|
||||
line.DisplayedText = totallySame ? line.OriginalText : $"{line.OriginalText}\n{_translations[i]}";
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
IsTranslating = false;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
IsTranslating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowOriginalsOnly()
|
||||
private async Task SetDisplayedAlongWithTranslationsAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Showing original lyrics only, translations disabled.");
|
||||
foreach (var langLyrics in _multiLangLyrics)
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
string originalText = _lyricsDataArr[0].WrappedOriginalText;
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
|
||||
|
||||
if (originalLangCode == targetLangCode)
|
||||
{
|
||||
foreach (var line in langLyrics)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText;
|
||||
}
|
||||
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
}
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
private async Task RefreshLyricsCoreAsync(CancellationToken token)
|
||||
{
|
||||
try
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Refreshing lyrics...");
|
||||
|
||||
SetLyricsLoadingPlaceholder();
|
||||
|
||||
string? lyricsRaw = null;
|
||||
|
||||
if (SongInfo != null)
|
||||
// Try get translation from itself first
|
||||
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
|
||||
if (found >= 0)
|
||||
{
|
||||
lyricsRaw = await _musicSearchService.SearchLyricsAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
SongInfo.DurationMs ?? 0,
|
||||
token
|
||||
);
|
||||
_logger.LogInformation("Lyrics search result: {LyricsRaw}", lyricsRaw ?? "null");
|
||||
token.ThrowIfCancellationRequested();
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
|
||||
}
|
||||
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_multiLangLyrics = new LyricsParser().Parse(
|
||||
lyricsRaw,
|
||||
(int?)SongInfo?.DurationMs ?? (int)TimeSpan.FromMinutes(99).TotalMilliseconds
|
||||
);
|
||||
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _multiLangLyrics.Count);
|
||||
await UpdateTranslationsAsync();
|
||||
token.ThrowIfCancellationRequested();
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
private void SetLyricsLoadingPlaceholder()
|
||||
private async Task RefreshLyricsAsync(CancellationToken token)
|
||||
{
|
||||
_multiLangLyrics = [];
|
||||
_multiLangLyrics.Add(
|
||||
[
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
DisplayedText = "● ● ●",
|
||||
CharTimings = [],
|
||||
},
|
||||
]
|
||||
);
|
||||
_logger.LogInformation("Refreshing lyrics...");
|
||||
|
||||
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
|
||||
_isLayoutChanged = true;
|
||||
|
||||
string? lyricsRaw = null;
|
||||
|
||||
if (SongInfo != null)
|
||||
{
|
||||
lyricsRaw = await _lyrcsSearchService.SearchAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
SongInfo.DurationMs ?? 0,
|
||||
token
|
||||
);
|
||||
_logger.LogInformation("Lyrics search result: {LyricsRaw}", lyricsRaw ?? "null");
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
|
||||
}
|
||||
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
|
||||
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
|
||||
|
||||
// This ensures that original lyrics are always shown while waiting for translations
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
_isLayoutChanged = true;
|
||||
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
@@ -14,8 +13,11 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
@@ -25,7 +27,8 @@ namespace BetterLyrics.WinUI3
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
private ForegroundWindowWatcherHelper? _watcherHelper = null;
|
||||
private ForegroundWindowWatcher? _windowWatcher = null;
|
||||
private bool _ignoreFullscreenWindow = false;
|
||||
|
||||
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
@@ -48,12 +51,6 @@ namespace BetterLyrics.WinUI3
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsWindowLocked { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Notification Notification { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowInfoBar { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ElementTheme ThemeType { get; set; } = ElementTheme.Default;
|
||||
|
||||
@@ -61,9 +58,8 @@ namespace BetterLyrics.WinUI3
|
||||
public partial double TitleBarFontSize { get; set; } = 11;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double TitleBarHeight { get; set; } = 36;
|
||||
|
||||
private bool _ignoreFullscreenWindow = false;
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsMouseWithinWindow { get; set; } = false;
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
@@ -114,13 +110,13 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
}
|
||||
|
||||
public void StartWatchWindowColorChange(WindowColorSampleMode mode)
|
||||
public void StartWatchWindowColorChange(WindowPixelSampleMode mode)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
_watcherHelper = new ForegroundWindowWatcherHelper(
|
||||
_windowWatcher = new ForegroundWindowWatcher(
|
||||
hwnd,
|
||||
onWindowChanged =>
|
||||
{
|
||||
@@ -131,13 +127,19 @@ namespace BetterLyrics.WinUI3
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
}
|
||||
);
|
||||
_watcherHelper.Start();
|
||||
_windowWatcher.Start();
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
}
|
||||
|
||||
public void UpdateAccentColor(nint hwnd, WindowColorSampleMode mode)
|
||||
private void StopWatchWindowColorChange()
|
||||
{
|
||||
ActivatedWindowAccentColor = WindowColorHelper.GetDominantColor(hwnd, mode).ToColor();
|
||||
_windowWatcher?.Stop();
|
||||
_windowWatcher = null;
|
||||
}
|
||||
|
||||
public void UpdateAccentColor(nint hwnd, WindowPixelSampleMode mode)
|
||||
{
|
||||
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, mode).ToColor();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -146,16 +148,10 @@ namespace BetterLyrics.WinUI3
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
DesktopModeHelper.Lock(window);
|
||||
DesktopModeHelper.SetClickThrough(window, true);
|
||||
IsLyricsWindowLocked = true;
|
||||
}
|
||||
|
||||
private void StopWatchWindowColorChange()
|
||||
{
|
||||
_watcherHelper?.Stop();
|
||||
_watcherHelper = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleDesktopMode()
|
||||
{
|
||||
@@ -167,8 +163,8 @@ namespace BetterLyrics.WinUI3
|
||||
IsDesktopMode = !IsDesktopMode;
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
StartWatchWindowColorChange(WindowColorSampleMode.WindowEdge);
|
||||
DesktopModeHelper.Enable(window);
|
||||
StartWatchWindowColorChange(WindowPixelSampleMode.WindowEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,8 +183,8 @@ namespace BetterLyrics.WinUI3
|
||||
IsDockMode = !IsDockMode;
|
||||
if (IsDockMode)
|
||||
{
|
||||
StartWatchWindowColorChange(WindowColorSampleMode.BelowWindow);
|
||||
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 4);
|
||||
StartWatchWindowColorChange(WindowPixelSampleMode.BelowWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ using Windows.System;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Popups;
|
||||
using WinRT.Interop;
|
||||
using AppInfo = BetterLyrics.WinUI3.Helper.AppInfo;
|
||||
using MetadataHelper = BetterLyrics.WinUI3.Helper.MetadataHelper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -33,11 +33,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly IPlaybackService _playbackService;
|
||||
private readonly ILibreTranslateService _libreTranslateService;
|
||||
private readonly ITranslateService _libreTranslateService;
|
||||
|
||||
private readonly string _autoStartupTaskId = "AutoStartup";
|
||||
|
||||
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ILibreTranslateService libreTranslateService) : base(settingsService)
|
||||
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ITranslateService libreTranslateService) : base(settingsService)
|
||||
{
|
||||
_libWatcherService = libWatcherService;
|
||||
_playbackService = playbackService;
|
||||
@@ -48,6 +48,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
AlbumArtSearchProvidersInfo = [.. _settingsService.AlbumArtSearchProvidersInfo];
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
@@ -68,6 +69,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
LyricsHighlightScope = _settingsService.LyricsHighlightScope;
|
||||
IsFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
|
||||
LyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
@@ -79,18 +81,19 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
LyricsCustomStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
|
||||
|
||||
LyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
|
||||
LyricsBackgroundTheme = _settingsService.LyricsBackgroundTheme;
|
||||
|
||||
MediaSourceProvidersInfo = [.. _settingsService.MediaSourceProvidersInfo];
|
||||
|
||||
IgnoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
|
||||
|
||||
LyricsScrollEasingType = _settingsService.LyricsScrollEasingType;
|
||||
LyricsScrollDuration = _settingsService.LyricsScrollDuration;
|
||||
TimelineSyncThreshold = _settingsService.TimelineSyncThreshold;
|
||||
|
||||
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
BuildDate = (await Helper.AppInfo.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
|
||||
BuildDate = (await Helper.MetadataHelper.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,6 +146,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
|
||||
|
||||
@@ -202,6 +209,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial float LyricsLineSpacingFactor { get; set; }
|
||||
@@ -213,7 +224,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; }
|
||||
|
||||
public string Version { get; set; } = Helper.AppInfo.AppVersion;
|
||||
public string Version { get; set; } = MetadataHelper.AppVersion;
|
||||
|
||||
public string BuildDate { get; set; } = string.Empty;
|
||||
|
||||
@@ -235,15 +246,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IgnoreFullscreenWindow { get; set; }
|
||||
|
||||
partial void OnLyricsBackgroundThemeChanged(ElementTheme value)
|
||||
{
|
||||
_settingsService.LyricsBackgroundTheme = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial EasingType LyricsScrollEasingType { get; set; }
|
||||
|
||||
partial void OnLyricsFontStrokeWidthChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsFontStrokeWidth = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsScrollDuration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int TimelineSyncThreshold { get; set; }
|
||||
|
||||
public void OnLyricsSearchProvidersReordered()
|
||||
{
|
||||
@@ -255,9 +268,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
public void OpenMusicFolder(LocalLyricsFolder folder)
|
||||
public void OnAlbumArtSearchProvidersReordered()
|
||||
{
|
||||
OpenFolderInFileExplorer(folder.Path);
|
||||
_settingsService.AlbumArtSearchProvidersInfo = [.. AlbumArtSearchProvidersInfo];
|
||||
Broadcast(
|
||||
AlbumArtSearchProvidersInfo,
|
||||
AlbumArtSearchProvidersInfo,
|
||||
nameof(AlbumArtSearchProvidersInfo)
|
||||
);
|
||||
}
|
||||
|
||||
public void RemoveFolderAsync(LocalLyricsFolder folder)
|
||||
@@ -284,6 +302,16 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
public void ToggleAlbumArtSearchProvider(AlbumArtSearchProviderInfo providerInfo)
|
||||
{
|
||||
_settingsService.AlbumArtSearchProvidersInfo = [.. AlbumArtSearchProvidersInfo];
|
||||
Broadcast(
|
||||
AlbumArtSearchProvidersInfo,
|
||||
AlbumArtSearchProvidersInfo,
|
||||
nameof(AlbumArtSearchProvidersInfo)
|
||||
);
|
||||
}
|
||||
|
||||
public void ToggleMediaSourceProvider(MediaSourceProviderInfo providerInfo)
|
||||
{
|
||||
Broadcast(
|
||||
@@ -324,35 +352,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private async Task LaunchProjectGitHubPageAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
|
||||
await Launcher.LaunchUriAsync(new Uri(MetadataHelper.GithubUrl));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenCacheFolder()
|
||||
private static async Task OpenCacheFolderAsync()
|
||||
{
|
||||
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
|
||||
}
|
||||
|
||||
private void OpenFolderInFileExplorer(string path)
|
||||
{
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = path,
|
||||
UseShellExecute = true,
|
||||
}
|
||||
);
|
||||
await Launcher.LaunchFolderPathAsync(PathHelper.CacheFolder);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void PlayTestingMusicTask()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RestartApp()
|
||||
private static void RestartApp()
|
||||
{
|
||||
WindowHelper.RestartApp();
|
||||
}
|
||||
@@ -385,7 +395,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
string result = await _libreTranslateService.TranslateAsync("Hello, world!", null);
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[SelectedTargetLanguageIndex].Code;
|
||||
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
|
||||
@@ -435,51 +446,58 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
return result;
|
||||
}
|
||||
|
||||
partial void OnLyricsScrollEasingTypeChanged(EasingType value)
|
||||
{
|
||||
_settingsService.LyricsScrollEasingType = value;
|
||||
}
|
||||
partial void OnLyricsScrollDurationChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsScrollDuration = value;
|
||||
}
|
||||
partial void OnLyricsBackgroundThemeChanged(ElementTheme value)
|
||||
{
|
||||
_settingsService.LyricsBackgroundTheme = value;
|
||||
}
|
||||
partial void OnLyricsFontStrokeWidthChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsFontStrokeWidth = value;
|
||||
}
|
||||
partial void OnIgnoreFullscreenWindowChanged(bool value)
|
||||
{
|
||||
_settingsService.IgnoreFullscreenWindow = value;
|
||||
}
|
||||
|
||||
partial void OnSelectedTargetLanguageIndexChanged(int value)
|
||||
{
|
||||
_settingsService.SelectedTargetLanguageIndex = value;
|
||||
}
|
||||
|
||||
partial void OnLibreTranslateServerChanged(string value)
|
||||
{
|
||||
_settingsService.LibreTranslateServer = value;
|
||||
}
|
||||
|
||||
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
|
||||
{
|
||||
_settingsService.AutoStartWindowType = value;
|
||||
}
|
||||
|
||||
partial void OnAutoLockOnDesktopModeChanged(bool value)
|
||||
{
|
||||
_settingsService.AutoLockOnDesktopMode = value;
|
||||
}
|
||||
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
_settingsService.CoverImageRadius = value;
|
||||
}
|
||||
|
||||
partial void OnCoverOverlayBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayBlurAmount = value;
|
||||
}
|
||||
|
||||
partial void OnCoverOverlayOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayOpacity = value;
|
||||
}
|
||||
|
||||
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDynamicCoverOverlayEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnLanguageChanged(Enums.Language value)
|
||||
{
|
||||
switch (value)
|
||||
@@ -507,85 +525,78 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
_settingsService.Language = Language;
|
||||
}
|
||||
|
||||
partial void OnIsFanLyricsEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsFanLyricsEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnIsLyricsGlowEffectEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLyricsGlowEffectEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsAlignmentTypeChanged(TextAlignmentType value)
|
||||
{
|
||||
_settingsService.LyricsAlignmentType = value;
|
||||
}
|
||||
|
||||
partial void OnSongInfoAlignmentTypeChanged(TextAlignmentType value)
|
||||
{
|
||||
_settingsService.SongInfoAlignmentType = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBlurAmount = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsCustomBgFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomBgFontColor = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsCustomFgFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomFgFontColor = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsCustomStrokeFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomStrokeFontColor = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsBgFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsBgFontColorType = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsFgFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsFgFontColorType = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsStrokeFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsStrokeFontColorType = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsFontSize = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_settingsService.LyricsFontWeight = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsGlowEffectScope = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsHighlightScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsHighlightScope = value;
|
||||
}
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_settingsService.LyricsLineSpacingFactor = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsVerticalEdgeOpacity = value;
|
||||
}
|
||||
partial void OnTimelineSyncThresholdChanged(int value)
|
||||
{
|
||||
_settingsService.TimelineSyncThreshold = value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public class SettingsWindowViewModel : BaseWindowViewModel
|
||||
public partial class SettingsWindowViewModel(ISettingsService settingsService) : BaseWindowViewModel(settingsService)
|
||||
{
|
||||
public SettingsWindowViewModel(ISettingsService settingsService) : base(settingsService) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,14 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SystemTrayViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
|
||||
public partial class SystemTrayViewModel(ISettingsService settingsService) : BaseViewModel(settingsService), IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
public SystemTrayViewModel(ISettingsService settingsService) : base(settingsService) { }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsWindowLocked { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string ToolTipText { get; set; } = AppInfo.AppName;
|
||||
public partial string ToolTipText { get; set; } = MetadataHelper.AppName;
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
@@ -34,15 +32,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ExitApp()
|
||||
private static void ExitApp()
|
||||
{
|
||||
WindowHelper.ExitAllWindows();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettings()
|
||||
private static void OpenSettings()
|
||||
{
|
||||
// 打开设置窗口
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
@@ -52,7 +49,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
DesktopModeHelper.Unlock(window);
|
||||
DesktopModeHelper.SetClickThrough(window, false);
|
||||
IsLyricsWindowLocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
<StackPanel>
|
||||
<Slider
|
||||
x:Uid="MainPagePositionOffsetSlider"
|
||||
Maximum="2000"
|
||||
Minimum="-2000"
|
||||
Maximum="5000"
|
||||
Minimum="-5000"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
@@ -114,7 +114,7 @@
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</RelativePanel>
|
||||
<TextBlock Opacity="0.5" x:Uid="LyricsPagePositionOffsetHint"/>
|
||||
<TextBlock x:Uid="LyricsPagePositionOffsetHint" Opacity="0.5" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
|
||||
@@ -12,19 +12,20 @@
|
||||
xmlns:scontrols="using:ShadowViewer.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid x:Name="RootGrid" RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
PointerEntered="RootGrid_PointerEntered"
|
||||
PointerExited="RootGrid_PointerExited"
|
||||
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
|
||||
|
||||
<local:LyricsPage />
|
||||
|
||||
<!-- Top command -->
|
||||
<Grid
|
||||
x:Name="TopCommandGrid"
|
||||
Margin="6"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
Opacity="0"
|
||||
PointerEntered="TopCommandGrid_PointerEntered"
|
||||
PointerExited="TopCommandGrid_PointerExited">
|
||||
@@ -113,7 +114,7 @@
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Maximise -->
|
||||
<Button
|
||||
@@ -123,7 +124,7 @@
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Restore -->
|
||||
<Button
|
||||
@@ -134,7 +135,7 @@
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Close -->
|
||||
<Button
|
||||
@@ -144,7 +145,7 @@
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -267,5 +267,15 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel = TipContainerCenter;
|
||||
}
|
||||
|
||||
private void RootGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.IsMouseWithinWindow = true;
|
||||
}
|
||||
|
||||
private void RootGrid_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.IsMouseWithinWindow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,18 @@
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageMediaLib"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Glyph=}"
|
||||
Tag="MediaLib" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageAlbumLib"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="AlbumArtLib" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPagePlaybackLib"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="PlaybackLib" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageLyricsLib"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
@@ -178,8 +188,8 @@
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=OneWay}" />
|
||||
<Slider
|
||||
Maximum="200"
|
||||
Minimum="50"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="10"
|
||||
TickFrequency="10"
|
||||
@@ -229,10 +239,48 @@
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Media source lib -->
|
||||
<controls:Case Value="MediaLib">
|
||||
<controls:Case Value="AlbumArtLib">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageMediaSourceProvidersConfig" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<controls:SettingsCard
|
||||
x:Name="AlbumArtSearchProvidersSettingsExpander"
|
||||
x:Uid="SettingsPageAlbumArtSearchProvidersConfig"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
|
||||
<ListView
|
||||
x:Name="AlbumArtSearchProvidersListView"
|
||||
Margin="0,-4,0,0"
|
||||
AllowDrop="True"
|
||||
CanDragItems="True"
|
||||
CanReorderItems="True"
|
||||
DragItemsCompleted="AlbumArtSearchProvidersListView_DragItemsCompleted"
|
||||
ItemsSource="{x:Bind ViewModel.AlbumArtSearchProvidersInfo, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</ListView.OpacityTransition>
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:AlbumArtSearchProviderInfo">
|
||||
<controls:SettingsCard Padding="60,0,48,0" Header="{Binding Provider, Converter={StaticResource AlbumArtSearchProviderToDisplayNameConverter}, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" Toggled="AlbumArtSearchProviderToggleSwitch_Toggled" />
|
||||
</controls:SettingsCard>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Playback source -->
|
||||
<controls:Case Value="PlaybackLib">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageMediaSourceProvidersConfig" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<ListView
|
||||
x:Name="MediaSourceProvidersListView"
|
||||
Margin="0,-4,0,0"
|
||||
@@ -256,8 +304,8 @@
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Lyrics lib -->
|
||||
<controls:Case Value="LyricsLib">
|
||||
<!-- Media lib -->
|
||||
<controls:Case Value="MediaLib">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageMusicLib"
|
||||
@@ -324,12 +372,18 @@
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.ItemsFooter>
|
||||
</controls:SettingsExpander>
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Lyrics source -->
|
||||
<controls:Case Value="LyricsLib">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<controls:SettingsCard
|
||||
x:Name="LyricsSearchProvidersSettingsExpander"
|
||||
x:Uid="SettingsPageLyricsSearchProvidersConfig"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
Glyph=}" />
|
||||
|
||||
<ListView
|
||||
x:Name="LyricsSearchProvidersListView"
|
||||
@@ -599,6 +653,14 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsHighlightScope" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsHighlightScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsGlowEffect"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
@@ -608,8 +670,9 @@
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsGlowEffectScope" IsEnabled="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsGlowEffectScopeCurrentLine" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsGlowEffectScopeCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
@@ -619,12 +682,90 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFanLyricsEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageScrollEasing"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsScrollEasingType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeLinear" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeSmoothStep" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutSine" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuad" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutCubic" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuart" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuint" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutExpo" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutCirc" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBack" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutElastic" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBounce" />
|
||||
</ComboBox>
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageScrollDuration">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.LyricsScrollDuration, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=" ms" />
|
||||
<Slider
|
||||
Maximum="1000"
|
||||
Minimum="100"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.LyricsScrollDuration, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Lyrics translation -->
|
||||
<controls:Case Value="Translation">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.SelectedTargetLanguageIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="العربية" Tag="ar" />
|
||||
<ComboBoxItem Content="Azərbaycan dili" Tag="az" />
|
||||
<ComboBoxItem Content="简体中文" Tag="zh-Hans" />
|
||||
<ComboBoxItem Content="繁體中文" Tag="zh-Hant" />
|
||||
<ComboBoxItem Content="Čeština" Tag="cs" />
|
||||
<ComboBoxItem Content="Dansk" Tag="da" />
|
||||
<ComboBoxItem Content="Nederlands" Tag="nl" />
|
||||
<ComboBoxItem Content="English" Tag="en" />
|
||||
<ComboBoxItem Content="Esperanto" Tag="eo" />
|
||||
<ComboBoxItem Content="Suomi" Tag="fi" />
|
||||
<ComboBoxItem Content="Français" Tag="fr" />
|
||||
<ComboBoxItem Content="Deutsch" Tag="de" />
|
||||
<ComboBoxItem Content="Ελληνικά" Tag="el" />
|
||||
<ComboBoxItem Content="עברית" Tag="he" />
|
||||
<ComboBoxItem Content="हिन्दी" Tag="hi" />
|
||||
<ComboBoxItem Content="Magyar" Tag="hu" />
|
||||
<ComboBoxItem Content="Bahasa Indonesia" Tag="id" />
|
||||
<ComboBoxItem Content="Gaeilge" Tag="ga" />
|
||||
<ComboBoxItem Content="Italiano" Tag="it" />
|
||||
<ComboBoxItem Content="日本語" Tag="ja" />
|
||||
<ComboBoxItem Content="한국어" Tag="ko" />
|
||||
<ComboBoxItem Content="فارسی" Tag="fa" />
|
||||
<ComboBoxItem Content="Polski" Tag="pl" />
|
||||
<ComboBoxItem Content="Português" Tag="pt" />
|
||||
<ComboBoxItem Content="Русский" Tag="ru" />
|
||||
<ComboBoxItem Content="Slovenčina" Tag="sk" />
|
||||
<ComboBoxItem Content="Español" Tag="es" />
|
||||
<ComboBoxItem Content="Svenska" Tag="sv" />
|
||||
<ComboBoxItem Content="Türkçe" Tag="tr" />
|
||||
<ComboBoxItem Content="Українська" Tag="uk" />
|
||||
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageTranslationConfig"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
@@ -644,40 +785,6 @@
|
||||
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.SelectedTargetLanguageIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="العربية" Tag="ar" />
|
||||
<ComboBoxItem Content="Azərbaycan dili" Tag="az" />
|
||||
<ComboBoxItem Content="中文" Tag="zh" />
|
||||
<ComboBoxItem Content="Čeština" Tag="cs" />
|
||||
<ComboBoxItem Content="Dansk" Tag="da" />
|
||||
<ComboBoxItem Content="Nederlands" Tag="nl" />
|
||||
<ComboBoxItem Content="English" Tag="en" />
|
||||
<ComboBoxItem Content="Esperanto" Tag="eo" />
|
||||
<ComboBoxItem Content="Suomi" Tag="fi" />
|
||||
<ComboBoxItem Content="Français" Tag="fr" />
|
||||
<ComboBoxItem Content="Deutsch" Tag="de" />
|
||||
<ComboBoxItem Content="Ελληνικά" Tag="el" />
|
||||
<ComboBoxItem Content="עברית" Tag="he" />
|
||||
<ComboBoxItem Content="हिन्दी" Tag="hi" />
|
||||
<ComboBoxItem Content="Magyar" Tag="hu" />
|
||||
<ComboBoxItem Content="Bahasa Indonesia" Tag="id" />
|
||||
<ComboBoxItem Content="Gaeilge" Tag="ga" />
|
||||
<ComboBoxItem Content="Italiano" Tag="it" />
|
||||
<ComboBoxItem Content="日本語" Tag="ja" />
|
||||
<ComboBoxItem Content="한국어" Tag="ko" />
|
||||
<ComboBoxItem Content="فارسی" Tag="fa" />
|
||||
<ComboBoxItem Content="Polski" Tag="pl" />
|
||||
<ComboBoxItem Content="Português" Tag="pt" />
|
||||
<ComboBoxItem Content="Русский" Tag="ru" />
|
||||
<ComboBoxItem Content="Slovenčina" Tag="sk" />
|
||||
<ComboBoxItem Content="Español" Tag="es" />
|
||||
<ComboBoxItem Content="Svenska" Tag="sv" />
|
||||
<ComboBoxItem Content="Türkçe" Tag="tr" />
|
||||
<ComboBoxItem Content="Українська" Tag="uk" />
|
||||
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageTranslationInfo">
|
||||
<controls:SettingsCard.Description>
|
||||
<HyperlinkButton Margin="0,6,0,0" NavigateUri="https://github.com/LibreTranslate/LibreTranslate">
|
||||
@@ -696,6 +803,7 @@
|
||||
<!-- About -->
|
||||
<controls:Case Value="About">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Logo.png}">
|
||||
<controls:SettingsCard.Description>
|
||||
<RichTextBlock>
|
||||
@@ -717,24 +825,52 @@
|
||||
Glyph=}"
|
||||
IsClickEnabled="True" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageQQGroup" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/QQ.png}">
|
||||
<Button x:Uid="SettingsPageJoinNowButton" Click="QQGroupButton_Click" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDiscord" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Discord.png}">
|
||||
<Button x:Uid="SettingsPageJoinNowButton" Click="DiscodGroupButton_Click" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Dev -->
|
||||
<controls:Case Value="Dev">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
|
||||
<HyperlinkButton
|
||||
x:Uid="SettingsPagePlayingMockMusicButton"
|
||||
Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}"
|
||||
NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
|
||||
<HyperlinkButton x:Uid="SettingsPagePlayingMockMusicButton" NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageCache">
|
||||
<Button x:Uid="SettingsPageOpenLogFolderButton" Command="{x:Bind ViewModel.OpenCacheFolderCommand}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDebugOverlay">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDebugOverlayEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.TimelineSyncThreshold, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=" ms" />
|
||||
<Slider
|
||||
Maximum="1000"
|
||||
Minimum="0"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.TimelineSyncThreshold, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
@@ -105,5 +106,31 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
ViewModel.ToggleAutoStartupAsync(AutoStartupToggleSwitch.IsOn);
|
||||
}
|
||||
|
||||
private void AlbumArtSearchProvidersListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||
{
|
||||
ViewModel.OnAlbumArtSearchProvidersReordered();
|
||||
}
|
||||
|
||||
private void AlbumArtSearchProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleSwitch toggleSwitch)
|
||||
{
|
||||
if (toggleSwitch.DataContext is AlbumArtSearchProviderInfo providerInfo)
|
||||
{
|
||||
ViewModel.ToggleAlbumArtSearchProvider(providerInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void QQGroupButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Launcher.LaunchUriAsync(new Uri(MetadataHelper.QQGroupUrl));
|
||||
}
|
||||
|
||||
private void DiscodGroupButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Launcher.LaunchUriAsync(new Uri(MetadataHelper.DiscordUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
Privacy Policy – BetterLyrics
|
||||
|
||||
Effective Date: June 3, 2025
|
||||
|
||||
Thank you for using BetterLyrics.
|
||||
|
||||
BetterLyrics is a local lyrics viewer application built with WinUI 3. We respect your privacy and are committed to protecting it. This Privacy Policy explains how we handle your data.
|
||||
BetterLyrics is a lyrics viewer application built with WinUI 3. We respect your privacy and are committed to protecting it. This Privacy Policy explains how we handle your data.
|
||||
|
||||
1. No Personal Data Collection
|
||||
BetterLyrics does not collect, store, or transmit any personal data. All lyrics, preferences, and settings are stored locally on your device. We do not access or send your information to any server.
|
||||
1. Personal Data Collection
|
||||
BetterLyrics does not collect, store, or transmit any personal data by default. All lyrics, preferences, and settings are stored locally on your device. We do not access or send your personal information to any server without your explicit action.
|
||||
|
||||
2. Internet Usage
|
||||
BetterLyrics does not require an internet connection for its core functionality. The app does not make any network requests or communicate with external services unless you manually choose to view external resources (such as a linked demo video).
|
||||
BetterLyrics may connect to the internet to provide optional features, such as:
|
||||
|
||||
- Fetching lyrics from online sources
|
||||
- Viewing demo content via external links
|
||||
|
||||
These features are strictly opt-in. The app does not perform background communication or data transmission unless you explicitly initiate it.
|
||||
|
||||
3. Third-Party Libraries
|
||||
BetterLyrics uses third-party open-source libraries (e.g., Win2D, CommunityToolkit, DevWinUI). These libraries are used locally and do not perform any background tracking or data transmission.
|
||||
BetterLyrics uses third-party open-source libraries (e.g., Win2D, CommunityToolkit, DevWinUI). These libraries operate locally and do not perform background tracking or data collection.
|
||||
|
||||
4. Changes to This Policy
|
||||
If our privacy practices change in the future (for example, if online lyrics fetching is added), we will update this policy accordingly and notify users through app updates or store listing.
|
||||
If our privacy practices change (for example, when more online features are introduced), this policy will be updated accordingly. We will notify users through app updates or store listings.
|
||||
|
||||
5. Contact
|
||||
If you have any questions about this privacy policy, please contact us at: founchoo@outlook.com
|
||||
If you have any questions about this privacy policy, please contact us at: founchoo@outlook.com
|
||||
16
README.CN.md
@@ -6,11 +6,16 @@
|
||||
|
||||
<h2 align="center">
|
||||
BetterLyrics
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h3 align="center">
|
||||
使用 WinUI 3 构建的流畅动态歌词显示工具
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
---
|
||||
|
||||
- QQ[「BetterLyrics」反馈交流群(简体中文)](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388)
|
||||
- Discord [「BetterLyrics」反馈交流群(繁体中文/英文)](https://discord.gg/5yAQPnyCKv)
|
||||
|
||||
---
|
||||
|
||||
@@ -21,6 +26,7 @@ BetterLyrics
|
||||
- 流畅的用户界面随歌曲切换
|
||||
- 每个字符均支持渐变卡拉 OK(带光晕)效果
|
||||
- 沉浸式桌面歌词(停靠模式)
|
||||
- 本地翻译(支持 30 种语言)
|
||||
|
||||
> 该项目目前仍在开发中,最新的开发分支中可能存在错误和意外行为。
|
||||
|
||||
@@ -120,8 +126,4 @@ BetterLyrics
|
||||
|
||||
## 欢迎提出任何问题和 PR
|
||||
|
||||
如果您发现错误,请提交至 issues;如果您有任何想法,请随时在此处分享。
|
||||
|
||||
或者,您也可以加入群聊,分享您的宝贵反馈:
|
||||
- QQ[「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388)
|
||||
- Discord [「BetterLyrics」反馈交流群](https://discord.gg/rbnF556r)
|
||||
如果您发现错误,请提交至 issues;如果您有任何想法,请随时在此处分享。
|
||||
16
README.md
@@ -6,11 +6,16 @@
|
||||
|
||||
<h2 align="center">
|
||||
BetterLyrics
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h3 align="center">
|
||||
Your smooth dynamic lyrics display tool built with WinUI 3
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
---
|
||||
|
||||
- [「BetterLyrics」反馈交流群(简体中文)](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) on QQ
|
||||
- [「BetterLyrics」Feedback Chat Group (Traditional Chinese / English)](https://discord.gg/5yAQPnyCKv) on Discord
|
||||
|
||||
---
|
||||
|
||||
@@ -21,6 +26,7 @@ Your smooth dynamic lyrics display tool built with WinUI 3
|
||||
- Smooth user interface change from song to song
|
||||
- Gradient Karaoke (with glow) effect on every single character
|
||||
- Immersive desktop lyrics (dock mode)
|
||||
- Local translation (supporting 30 languages)
|
||||
|
||||
> This project is still under development now, bugs and unexpected behaviors may be existed in the latest dev branch.
|
||||
|
||||
@@ -120,8 +126,4 @@ You can `git clone` this project and build it yourself.
|
||||
|
||||
## Any issues and PRs are welcomed
|
||||
|
||||
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
|
||||
|
||||
Or alternatively join group chat to share your valuable feedback:
|
||||
- [「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) on QQ
|
||||
- [「BetterLyrics」Feedback Chat Group](https://discord.gg/rbnF556r) on Discord
|
||||
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
|
||||
7
docs/Gemfile
Normal file
@@ -0,0 +1,7 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem "jekyll", "~> 4.4.1" # installed by `gem jekyll`
|
||||
# gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2
|
||||
|
||||
gem "just-the-docs", "0.10.1" # pinned to the current release
|
||||
# gem "just-the-docs" # always download the latest release
|
||||
97
docs/Gemfile.lock
Normal file
@@ -0,0 +1,97 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
base64 (0.2.0)
|
||||
bigdecimal (3.1.9)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
csv (3.3.2)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
forwardable-extended (2.6.0)
|
||||
google-protobuf (4.29.3-arm64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.29.3-x86_64-linux)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.4.1)
|
||||
addressable (~> 2.4)
|
||||
base64 (~> 0.2)
|
||||
colorator (~> 1.0)
|
||||
csv (~> 3.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 1.0)
|
||||
jekyll-sass-converter (>= 2.0, < 4.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
json (~> 2.6)
|
||||
kramdown (~> 2.3, >= 2.3.1)
|
||||
kramdown-parser-gfm (~> 1.0)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3, >= 0.3.6)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 3.0, < 5.0)
|
||||
safe_yaml (~> 1.0)
|
||||
terminal-table (>= 1.8, < 4.0)
|
||||
webrick (~> 1.7)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-sass-converter (3.0.0)
|
||||
sass-embedded (~> 1.54)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
json (2.9.1)
|
||||
just-the-docs (0.10.1)
|
||||
jekyll (>= 3.8.5)
|
||||
jekyll-include-cache
|
||||
jekyll-seo-tag (>= 2.0)
|
||||
rake (>= 12.3.1)
|
||||
kramdown (2.5.1)
|
||||
rexml (>= 3.3.9)
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.4.0)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (6.0.1)
|
||||
rake (13.2.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.4.0)
|
||||
rouge (4.5.1)
|
||||
safe_yaml (1.0.5)
|
||||
sass-embedded (1.83.4-arm64-darwin)
|
||||
google-protobuf (~> 4.29)
|
||||
sass-embedded (1.83.4-x86_64-linux-gnu)
|
||||
google-protobuf (~> 4.29)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
unicode-display_width (2.6.0)
|
||||
webrick (1.9.1)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin
|
||||
x86_64-linux-gnu
|
||||
|
||||
DEPENDENCIES
|
||||
jekyll (~> 4.4.1)
|
||||
just-the-docs (= 0.10.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
8
docs/_config.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
title: BetterLyrics Docs
|
||||
description: BetterLyrics Docs
|
||||
theme: just-the-docs
|
||||
|
||||
url: https://jayfunc.github.io/BetterLyrics
|
||||
|
||||
aux_links:
|
||||
Template Repository: https://github.com/just-the-docs/just-the-docs-template
|
||||
44
docs/faq.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: FAQ
|
||||
layout: FAQ
|
||||
---
|
||||
|
||||
### I couldn't see any button that I can interact with
|
||||
|
||||
This app is built with immersive experience, just hover your mouse on the top/bottom area of the app and then you'll see everything.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### How can I lock the window when switching to desktop mode
|
||||
|
||||

|
||||
|
||||
Again, hover you mouse on the top, click on the lock icon and you're good to go!
|
||||
|
||||
### How can I unlock the window in desktop mode
|
||||
|
||||

|
||||
|
||||
It's in the system tray, right-click on the icon and you'll see "Unlock the window".
|
||||
|
||||
### There's a delay in lyrics timeline
|
||||
|
||||
Hover you mouse on the very bottom of the app,
|
||||
|
||||

|
||||
|
||||
And then click on the first icon button (Lyrics timeline offset), here you can adjust the offset freely.
|
||||
|
||||
### I'm using Apple Music, the lyrics is moving forward and afterward constantly
|
||||
|
||||

|
||||
|
||||
Hover your mouse at the very bottom of the app and then click on the very last icon button to go to settings page.
|
||||
|
||||

|
||||
|
||||
Go to "Advanced options" section, increase the threshold value (marked with the bigger red rectangle) until the lyrics is working properly.
|
||||
|
||||
----
|
||||
BIN
docs/image-1.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
docs/image-2.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
docs/image-3.png
Normal file
|
After Width: | Height: | Size: 631 KiB |
BIN
docs/image-4.png
Normal file
|
After Width: | Height: | Size: 599 KiB |
BIN
docs/image-5.png
Normal file
|
After Width: | Height: | Size: 859 KiB |
BIN
docs/image-6.png
Normal file
|
After Width: | Height: | Size: 854 KiB |
BIN
docs/image-7.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
docs/image.png
Normal file
|
After Width: | Height: | Size: 445 KiB |
15
docs/index.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Home
|
||||
layout: home
|
||||
---
|
||||
|
||||
----
|
||||
|
||||
[^1]: [It can take up to 10 minutes for changes to your site to publish after you push the changes to GitHub](https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/creating-a-github-pages-site-with-jekyll#creating-your-site).
|
||||
|
||||
[Just the Docs]: https://just-the-docs.github.io/just-the-docs/
|
||||
[GitHub Pages]: https://docs.github.com/en/pages
|
||||
[README]: https://github.com/just-the-docs/just-the-docs-template/blob/main/README.md
|
||||
[Jekyll]: https://jekyllrb.com
|
||||
[GitHub Pages / Actions workflow]: https://github.blog/changelog/2022-07-27-github-pages-custom-github-actions-workflows-beta/
|
||||
[use this template]: https://github.com/just-the-docs/just-the-docs-template/generate
|
||||