Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c43b69b4cb | ||
|
|
94b22552e5 | ||
|
|
6deb16f6cb | ||
|
|
e467ab9c73 | ||
|
|
ea038c9c56 | ||
|
|
560250ad30 | ||
|
|
536acc69a5 | ||
|
|
0bbb379912 | ||
|
|
1f4d29e6f2 | ||
|
|
5d1d7476c9 | ||
|
|
e016baefe1 | ||
|
|
70b6194788 | ||
|
|
9f103b0ea3 | ||
|
|
2924140f95 | ||
|
|
3d3f168926 | ||
|
|
63b2285a36 | ||
|
|
780689fa05 | ||
|
|
01384717c4 | ||
|
|
e18d78170a | ||
|
|
023bf77afc | ||
|
|
2877ac2101 | ||
|
|
16d82109bb | ||
|
|
c703f04119 | ||
|
|
998853f9d2 | ||
|
|
f560735da0 | ||
|
|
ab9da73b49 | ||
|
|
dc364edf75 | ||
|
|
7fbc8fbfe7 | ||
|
|
4a00bb2ddf | ||
|
|
7472aa048f | ||
|
|
49b0f7a692 | ||
|
|
4d0602ebef | ||
|
|
626395d93a | ||
|
|
0ab5602569 | ||
|
|
37a7528762 | ||
|
|
3ca391a509 | ||
|
|
f5e542d2f3 | ||
|
|
107bdf8bee | ||
|
|
3e9e56f5cc |
@@ -12,13 +12,13 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.65.0" />
|
||||
Version="1.0.76.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>BetterLyrics</DisplayName>
|
||||
<PublisherDisplayName>founchoo</PublisherDisplayName>
|
||||
<PublisherDisplayName>Zhe Fang</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.1.6" />
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class AppleMusic
|
||||
{
|
||||
public const string MediaUserTokenKey = "AppleMusicMediaUserToken";
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@ namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class LXMusic
|
||||
{
|
||||
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration";
|
||||
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration,picUrl";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,37 +232,56 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.TranslationSettings.SelectedTargetLanguageIndex, Mode=TwoWay}">
|
||||
<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="Български" Tag="bg" />
|
||||
<ComboBoxItem Content="বাংলা" Tag="bn" />
|
||||
<ComboBoxItem Content="Català" Tag="ca" />
|
||||
<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="English" Tag="en" />
|
||||
<ComboBoxItem Content="Esperanto" Tag="eo" />
|
||||
<ComboBoxItem Content="Español" Tag="es" />
|
||||
<ComboBoxItem Content="Eesti" Tag="et" />
|
||||
<ComboBoxItem Content="Euskara" Tag="eu" />
|
||||
<ComboBoxItem Content="فارسی" Tag="fa" />
|
||||
<ComboBoxItem Content="Suomi" Tag="fi" />
|
||||
<ComboBoxItem Content="Français" Tag="fr" />
|
||||
<ComboBoxItem Content="Gaeilge" Tag="ga" />
|
||||
<ComboBoxItem Content="Galego" Tag="gl" />
|
||||
<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="Кыргызча" Tag="ky" />
|
||||
<ComboBoxItem Content="Lietuvių" Tag="lt" />
|
||||
<ComboBoxItem Content="Latviešu" Tag="lv" />
|
||||
<ComboBoxItem Content="Bahasa Melayu" Tag="ms" />
|
||||
<ComboBoxItem Content="Norsk bokmål" Tag="nb" />
|
||||
<ComboBoxItem Content="Nederlands" Tag="nl" />
|
||||
<ComboBoxItem Content="Português (Brasil)" Tag="pt-BR" />
|
||||
<ComboBoxItem Content="Polski" Tag="pl" />
|
||||
<ComboBoxItem Content="Português" Tag="pt" />
|
||||
<ComboBoxItem Content="Română" Tag="ro" />
|
||||
<ComboBoxItem Content="Русский" Tag="ru" />
|
||||
<ComboBoxItem Content="Slovenčina" Tag="sk" />
|
||||
<ComboBoxItem Content="Español" Tag="es" />
|
||||
<ComboBoxItem Content="Slovenščina" Tag="sl" />
|
||||
<ComboBoxItem Content="Shqip" Tag="sq" />
|
||||
<ComboBoxItem Content="Српски" Tag="sr" />
|
||||
<ComboBoxItem Content="Svenska" Tag="sv" />
|
||||
<ComboBoxItem Content="ไทย" Tag="th" />
|
||||
<ComboBoxItem Content="Filipino" Tag="tl" />
|
||||
<ComboBoxItem Content="Türkçe" Tag="tr" />
|
||||
<ComboBoxItem Content="Українська" Tag="uk" />
|
||||
<ComboBoxItem Content="اردو" Tag="ur" />
|
||||
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
|
||||
<ComboBoxItem Content="中文" Tag="zh" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageTranslationConfig" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
|
||||
@@ -337,6 +356,26 @@
|
||||
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
<!-- Apple Music token -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="media-user-token (for Apple Muisc)" />
|
||||
<controls:SettingsCard
|
||||
Background="{ThemeResource SystemFillColorCautionBackgroundBrush}"
|
||||
Foreground="{ThemeResource SystemFillColorCautionBrush}"
|
||||
Header="Use at your own risk">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBox
|
||||
MaxWidth="250"
|
||||
PlaceholderText="media-user-token"
|
||||
Text="{x:Bind ViewModel.AppleMusicMediaUserToken, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.SaveAppleMusicMediaUserTokenCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
LyricsSearchProvider.Netease => "网易云音乐",
|
||||
LyricsSearchProvider.Kugou => "酷狗音乐",
|
||||
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
|
||||
LyricsSearchProvider.AppleMusic => "Apple Music",
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
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
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
TranslationSearchProvider.Netease => "网易云音乐",
|
||||
TranslationSearchProvider.Kugou => "酷狗音乐",
|
||||
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
|
||||
TranslationSearchProvider.AppleMusic => "Apple Music",
|
||||
TranslationSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
TranslationSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
TranslationSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
AppleMusic,
|
||||
}
|
||||
|
||||
public static class LyricsSearchProviderExtensions
|
||||
@@ -28,6 +29,7 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LyricsSearchProvider.Netease => PathHelper.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
}
|
||||
@@ -41,6 +43,7 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LyricsSearchProvider.Kugou => LyricsFormat.Krc,
|
||||
LyricsSearchProvider.Netease => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.AmllTtmlDb => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.AppleMusic => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.LocalEslrcFile => LyricsFormat.Eslrc,
|
||||
LyricsSearchProvider.LocalTtmlFile => LyricsFormat.Ttml,
|
||||
@@ -71,6 +74,7 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
|
||||
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
|
||||
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
|
||||
LyricsSearchProvider.AppleMusic => TranslationSearchProvider.AppleMusic,
|
||||
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
|
||||
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
|
||||
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
Netease,
|
||||
LrcLib,
|
||||
AmllTtmlDb,
|
||||
AppleMusic,
|
||||
LocalMusicFile,
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
|
||||
126
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppleMusic.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class AppleMusic
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private string _accessToken = "";
|
||||
private string _storefront = "";
|
||||
private string _language = "";
|
||||
|
||||
public AppleMusic()
|
||||
{
|
||||
_client = new HttpClient();
|
||||
_client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36");
|
||||
_client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
_client.DefaultRequestHeaders.Add("Origin", "https://music.apple.com");
|
||||
_client.DefaultRequestHeaders.Add("Referer", "https://music.apple.com/");
|
||||
}
|
||||
|
||||
public async Task<bool> InitAsync()
|
||||
{
|
||||
await GetAccessTokenAsync();
|
||||
await SetMediaUserTokenAsync();
|
||||
return
|
||||
!string.IsNullOrEmpty(_accessToken) &&
|
||||
!string.IsNullOrEmpty(PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey));
|
||||
}
|
||||
|
||||
private async Task GetAccessTokenAsync()
|
||||
{
|
||||
var resp = await _client.GetStringAsync("https://music.apple.com/us/browse");
|
||||
var jsMatch = Regex.Match(resp, "(?<=index)(.*?)(?=\\.js\")");
|
||||
if (!jsMatch.Success) throw new Exception("Failed to find index.js");
|
||||
var jsUrl = $"https://music.apple.com/assets/index{jsMatch.Value}.js";
|
||||
var jsResp = await _client.GetStringAsync(jsUrl);
|
||||
var tokenMatch = Regex.Match(jsResp, "(?=eyJh)(.*?)(?=\")");
|
||||
if (!tokenMatch.Success) throw new Exception("Failed to find access token");
|
||||
_accessToken = tokenMatch.Value;
|
||||
_client.DefaultRequestHeaders.Remove("Authorization");
|
||||
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_accessToken}");
|
||||
}
|
||||
|
||||
private async Task SetMediaUserTokenAsync()
|
||||
{
|
||||
_client.DefaultRequestHeaders.Remove("media-user-token");
|
||||
_client.DefaultRequestHeaders.Add("media-user-token",
|
||||
PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey));
|
||||
var resp = await _client.GetStringAsync("https://amp-api.music.apple.com/v1/me/storefront");
|
||||
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
_storefront = json.GetProperty("data")[0].GetProperty("id").ToString();
|
||||
_language = json.GetProperty("data")[0].GetProperty("attributes").GetProperty("defaultLanguageTag").ToString();
|
||||
_client.DefaultRequestHeaders.Remove("Accept-Language");
|
||||
_client.DefaultRequestHeaders.Add("Accept-Language", $"{_language},en;q=0.9");
|
||||
}
|
||||
|
||||
public async Task<string?> GetLyricsAsync(string title, string artist)
|
||||
{
|
||||
string id = await SearchSongInfoAsync(artist, title);
|
||||
|
||||
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/songs/{id}";
|
||||
var url = apiUrl + $"?include[songs]=lyrics,syllable-lyrics&l={_language}";
|
||||
var resp = await _client.GetStringAsync(url);
|
||||
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
var data = json.GetProperty("data");
|
||||
if (data.GetArrayLength() == 0) return string.Empty;
|
||||
var song = data[0];
|
||||
|
||||
if (!song.TryGetProperty("relationships", out var relationships))
|
||||
return string.Empty;
|
||||
|
||||
if (relationships.TryGetProperty("syllable-lyrics", out var syllableLyrics) &&
|
||||
syllableLyrics.GetProperty("data").GetArrayLength() > 0)
|
||||
{
|
||||
var syllableLyric = syllableLyrics.GetProperty("data")[0];
|
||||
if (syllableLyric.TryGetProperty("attributes", out var attributes) &&
|
||||
attributes.TryGetProperty("ttml", out var ttml))
|
||||
{
|
||||
string? raw = ttml.GetString();
|
||||
if (raw != null && raw.Contains("begin=") && raw.Contains("end="))
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (relationships.TryGetProperty("lyrics", out var lyrics) &&
|
||||
// lyrics.GetProperty("data").GetArrayLength() > 0)
|
||||
//{
|
||||
// var lyric = lyrics.GetProperty("data")[0];
|
||||
// if (lyric.TryGetProperty("attributes", out var attributes) &&
|
||||
// attributes.TryGetProperty("ttml", out var ttml))
|
||||
// {
|
||||
// return ttml.GetString();
|
||||
// }
|
||||
//}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string> SearchSongInfoAsync(string artist, string title)
|
||||
{
|
||||
var query = $"{artist} {title}";
|
||||
var apiUrl = $"https://amp-api.music.apple.com/v1/catalog/{_storefront}/search";
|
||||
var url = apiUrl + $"?term={WebUtility.UrlEncode(query)}&types=songs&limit=1&l={_language}";
|
||||
var resp = await _client.GetStringAsync(url);
|
||||
var json = JsonSerializer.Deserialize(resp, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
var results = json.GetProperty("results");
|
||||
if (results.TryGetProperty("songs", out var songs) && songs.GetProperty("data").GetArrayLength() > 0)
|
||||
{
|
||||
var song = songs.GetProperty("data")[0];
|
||||
return song.GetProperty("id").ToString();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
@@ -219,5 +220,68 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return pixelData;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> DownloadImageAsByteArrayAsync(string url)
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
return await httpClient.GetByteArrayAsync(url);
|
||||
}
|
||||
|
||||
public static byte[]? DataUrlToByteArray(string dataUrl)
|
||||
{
|
||||
const string base64Marker = ";base64,";
|
||||
int base64Index = dataUrl.IndexOf(base64Marker, StringComparison.OrdinalIgnoreCase);
|
||||
if (base64Index >= 0)
|
||||
{
|
||||
string base64Data = dataUrl.Substring(base64Index + base64Marker.Length);
|
||||
return Convert.FromBase64String(base64Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非 base64,直接取逗号后内容并解码
|
||||
int commaIndex = dataUrl.IndexOf(',');
|
||||
if (commaIndex >= 0)
|
||||
{
|
||||
string rawData = dataUrl.Substring(commaIndex + 1);
|
||||
return System.Text.Encoding.UTF8.GetBytes(Uri.UnescapeDataString(rawData));
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]?> GetImageBytesFromUrlAsync(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (url.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// data URL,直接解析
|
||||
return DataUrlToByteArray(url);
|
||||
}
|
||||
else if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
|
||||
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
|
||||
{
|
||||
// 普通网络图片,下载
|
||||
return await DownloadImageAsByteArrayAsync(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他类型暂不支持
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using NTextCat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using TinyPinyin;
|
||||
using Windows.Globalization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
@@ -18,38 +11,77 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private static readonly RankedLanguageIdentifierFactory _factory = new();
|
||||
private static readonly RankedLanguageIdentifier _identifier;
|
||||
|
||||
public static List<Models.LanguageInfo> SupportedTargetLanguages =>
|
||||
public static List<Models.LanguageInfo> SupportedTargetLanguages { get; set; } =
|
||||
[
|
||||
new Models.LanguageInfo("ar", "العربية"),
|
||||
new Models.LanguageInfo("az", "Azərbaycan dili"),
|
||||
new Models.LanguageInfo("zh", "中文"),
|
||||
|
||||
new Models.LanguageInfo("bg", "Български"),
|
||||
new Models.LanguageInfo("bn", "বাংলা"),
|
||||
|
||||
new Models.LanguageInfo("ca", "Català"),
|
||||
new Models.LanguageInfo("cs", "Čeština"),
|
||||
|
||||
new Models.LanguageInfo("da", "Dansk"),
|
||||
new Models.LanguageInfo("nl", "Nederlands"),
|
||||
new Models.LanguageInfo("de", "Deutsch"),
|
||||
|
||||
new Models.LanguageInfo("el", "Ελληνικά"),
|
||||
new Models.LanguageInfo("en", "English"),
|
||||
new Models.LanguageInfo("eo", "Esperanto"),
|
||||
new Models.LanguageInfo("es", "Español"),
|
||||
new Models.LanguageInfo("et", "Eesti"),
|
||||
new Models.LanguageInfo("eu", "Euskara"),
|
||||
|
||||
new Models.LanguageInfo("fa", "فارسی"),
|
||||
new Models.LanguageInfo("fi", "Suomi"),
|
||||
new Models.LanguageInfo("fr", "Français"),
|
||||
new Models.LanguageInfo("de", "Deutsch"),
|
||||
new Models.LanguageInfo("el", "Ελληνικά"),
|
||||
|
||||
new Models.LanguageInfo("ga", "Gaeilge"),
|
||||
new Models.LanguageInfo("gl", "Galego"),
|
||||
|
||||
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("ky", "Кыргызча"),
|
||||
|
||||
new Models.LanguageInfo("lt", "Lietuvių"),
|
||||
new Models.LanguageInfo("lv", "Latviešu"),
|
||||
|
||||
new Models.LanguageInfo("ms", "Bahasa Melayu"),
|
||||
|
||||
new Models.LanguageInfo("nb", "Norsk bokmål"),
|
||||
new Models.LanguageInfo("nl", "Nederlands"),
|
||||
|
||||
new Models.LanguageInfo("pt-BR", "Português (Brasil)"),
|
||||
new Models.LanguageInfo("pl", "Polski"),
|
||||
new Models.LanguageInfo("pt", "Português"),
|
||||
|
||||
new Models.LanguageInfo("ro", "Română"),
|
||||
new Models.LanguageInfo("ru", "Русский"),
|
||||
|
||||
new Models.LanguageInfo("sk", "Slovenčina"),
|
||||
new Models.LanguageInfo("es", "Español"),
|
||||
new Models.LanguageInfo("sl", "Slovenščina"),
|
||||
new Models.LanguageInfo("sq", "Shqip"),
|
||||
new Models.LanguageInfo("sr", "Српски"),
|
||||
new Models.LanguageInfo("sv", "Svenska"),
|
||||
|
||||
new Models.LanguageInfo("th", "ไทย"),
|
||||
new Models.LanguageInfo("tl", "Filipino"),
|
||||
new Models.LanguageInfo("tr", "Türkçe"),
|
||||
|
||||
new Models.LanguageInfo("uk", "Українська"),
|
||||
new Models.LanguageInfo("ur", "اردو"),
|
||||
|
||||
new Models.LanguageInfo("vi", "Tiếng Việt"),
|
||||
|
||||
new Models.LanguageInfo("zh", "中文"),
|
||||
];
|
||||
|
||||
static LanguageHelper()
|
||||
@@ -83,11 +115,17 @@ namespace BetterLyrics.WinUI3.Services
|
||||
};
|
||||
}
|
||||
|
||||
public static int GetDefaultTargetLanguageIndex()
|
||||
public static string GetDefaultTargetLanguageCode()
|
||||
{
|
||||
int found = SupportedTargetLanguages.FindIndex(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
|
||||
if (found == -1) found = 7; // 默认使用英语
|
||||
return found;
|
||||
var found = SupportedTargetLanguages.Find(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
|
||||
if (found == null)
|
||||
{
|
||||
return "en";
|
||||
}
|
||||
else
|
||||
{
|
||||
return found.Code;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetOrderChar(string text)
|
||||
@@ -97,9 +135,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
if (char.IsLetter(c) && c < 128)
|
||||
return char.ToUpper(c).ToString();
|
||||
|
||||
if (PinyinHelper.IsChinese(c))
|
||||
if (Pinyin.Pinyin.Instance.IsHanzi(c.ToString()))
|
||||
{
|
||||
return PinyinHelper.GetPinyinInitials($"{c}");
|
||||
return Pinyin.Pinyin.Instance.HanziToPinyin(c.ToString(), Pinyin.ManTone.Style.NORMAL).ToStr().ToUpper().FirstOrDefault().ToString();
|
||||
}
|
||||
|
||||
return "#";
|
||||
@@ -107,7 +145,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public static string ToRomaji(string text)
|
||||
{
|
||||
return string.Join(" ", RomajiConverter.Core.Helpers.RomajiHelper.SentenceToRomaji(text).Select(x=>x.Romaji));
|
||||
return string.Join(" ", RomajiConverter.Core.Helpers.RomajiHelper.SentenceToRomaji(text).Select(x => x.Romaji));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -246,10 +245,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 只获取一级span,且排除ttm:role="x-bg"的span
|
||||
// 只获取一级span,且排除 ttm:role="x-bg" 的 span 和 ttm:role="x-roman"
|
||||
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")
|
||||
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-bg" &&
|
||||
s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value != "x-roman")
|
||||
.ToList();
|
||||
|
||||
// 原文和翻译分离
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
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 AppleMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "apple-music");
|
||||
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");
|
||||
|
||||
@@ -53,6 +54,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
|
||||
public static string KugouTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "kugou");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
|
||||
@@ -69,9 +71,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AppleMusicCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(KugouTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LibInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Url => $"https://www.nuget.org/packages/{Name}/";
|
||||
}
|
||||
}
|
||||
@@ -49,12 +49,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
partial void OnLyricsWindowBoundsChanged(Rect value)
|
||||
{
|
||||
double factor = 0.1;
|
||||
DemoLyricsWindowBounds = new Rect(
|
||||
value.X * factor,
|
||||
value.Y * factor,
|
||||
value.Width * factor,
|
||||
value.Height * factor
|
||||
);
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<Views.LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
var mointor = MonitorHelper.GetMonitorInfoExFromWindow(lyricsWindow);
|
||||
@@ -65,6 +59,12 @@ namespace BetterLyrics.WinUI3.Models
|
||||
mointor.rcMonitor.Width,
|
||||
mointor.rcMonitor.Height
|
||||
);
|
||||
DemoLyricsWindowBounds = new Rect(
|
||||
(value.X - mointor.rcMonitor.Left) * factor,
|
||||
(value.Y - mointor.rcMonitor.Top) * factor,
|
||||
value.Width * factor,
|
||||
value.Height * factor
|
||||
);
|
||||
DemoLyricsWindowMonitorBounds = new Rect(
|
||||
mointor.rcMonitor.Left * factor,
|
||||
mointor.rcMonitor.Top * factor,
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LibreTranslateServer { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTranslationEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowTranslationOnly { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SelectedTargetLanguageIndex { get; set; } = LanguageHelper.GetDefaultTargetLanguageIndex();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string SelectedTargetLanguageCode { get; set; } = LanguageHelper.GetDefaultTargetLanguageCode();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsJyutpingEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ChineseRomanization ChineseRomanization { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsChineseRomanizationEnabled { get; set; } = false;
|
||||
|
||||
@@ -21,11 +21,14 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public partial double? DurationMs { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? SourceAppUserModelId { get; set; } = null;
|
||||
public partial string? PlayerId { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Title { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? SongId { get; set; } = null;
|
||||
|
||||
public SongInfo() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, string? songId, CancellationToken token);
|
||||
|
||||
Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
private readonly HttpClient _amllTtmlDbHttpClient;
|
||||
private readonly HttpClient _lrcLibHttpClient;
|
||||
private readonly AppleMusic _appleMusic;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILogger _logger;
|
||||
@@ -43,6 +44,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHubUrl})"
|
||||
);
|
||||
_amllTtmlDbHttpClient = new();
|
||||
_appleMusic = new AppleMusic();
|
||||
}
|
||||
|
||||
private static bool IsAmllTtmlDbIndexInvalid()
|
||||
@@ -89,7 +91,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
public async Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, string? songId, CancellationToken token)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
@@ -121,7 +123,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var targetProvider = found.LyricsSearchProvider;
|
||||
if (targetProvider != null)
|
||||
{
|
||||
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, album, durationMs, token);
|
||||
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, album, durationMs, songId, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
continue;
|
||||
}
|
||||
|
||||
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, album, durationMs, token);
|
||||
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, album, durationMs, null, token);
|
||||
|
||||
if (lyricsSearchResult.IsFound)
|
||||
{
|
||||
@@ -149,13 +151,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var results = new List<LyricsSearchResult>();
|
||||
foreach (var provider in Enum.GetValues<LyricsSearchProvider>())
|
||||
{
|
||||
var searchResult = await SearchSingleAsync(provider, title, artist, album, durationMs, token);
|
||||
var searchResult = await SearchSingleAsync(provider, title, artist, album, durationMs, null, token);
|
||||
results.Add(searchResult);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchSingleAsync(LyricsSearchProvider provider, string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
private async Task<LyricsSearchResult> SearchSingleAsync(LyricsSearchProvider provider, string title, string artist, string album, double durationMs, string? songId, CancellationToken token)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
@@ -198,17 +200,20 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
lyricsSearchResult = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, songId, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
lyricsSearchResult = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
case LyricsSearchProvider.AppleMusic:
|
||||
lyricsSearchResult = await SearchAppleMusicAsync(title, artist, album, (int)durationMs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -431,7 +436,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private static async Task<LyricsSearchResult> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, Searchers searchers)
|
||||
private static async Task<LyricsSearchResult> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, string? songId, Searchers searchers)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
@@ -452,15 +457,23 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
break;
|
||||
}
|
||||
|
||||
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs = durationMs,
|
||||
Album = album,
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
);
|
||||
ISearchResult? result;
|
||||
if (searchers == Searchers.Netease && songId != null)
|
||||
{
|
||||
result = new NeteaseSearchResult(title, [artist], album, null, durationMs, songId);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs = durationMs,
|
||||
Album = album,
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (result is QQMusicSearchResult qqResult)
|
||||
{
|
||||
@@ -508,7 +521,21 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
string? original = null;
|
||||
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
|
||||
{
|
||||
original = Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(candidate.Id, candidate.AccessKey);
|
||||
original = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.Id, candidate.AccessKey);
|
||||
if (candidate.TransId != null)
|
||||
{
|
||||
string? translated = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.TransId, candidate.AccessKey);
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.KugouTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
@@ -518,5 +545,24 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchAppleMusicAsync(string title, string artist, string album, int durationMs)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = LyricsSearchProvider.AppleMusic,
|
||||
};
|
||||
|
||||
if (await _appleMusic.InitAsync())
|
||||
{
|
||||
var raw = await _appleMusic.GetLyricsAsync(title, artist);
|
||||
_logger.LogInformation("Apple Music lyrics search result for {Title} - {Artist}: {Raw}", title, artist, raw ?? "null");
|
||||
lyricsSearchResult.Raw = raw;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
MediaSourceProviderInfo? GetCurrentMediaSourceProviderInfo();
|
||||
|
||||
void UpdateLyrics();
|
||||
void UpdateTranslations();
|
||||
|
||||
bool IsPlaying { get; }
|
||||
SongInfo? SongInfo { get; }
|
||||
TimeSpan Position { get; }
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
|
||||
byte[]? bytes = await Task.Run(async () => await _albumArtSearchService.SearchAsync(
|
||||
SongInfo?.SourceAppUserModelId ?? "",
|
||||
SongInfo?.PlayerId ?? "",
|
||||
_cachedSongInfo.Title,
|
||||
_cachedSongInfo.Artist,
|
||||
_cachedSongInfo?.Album ?? string.Empty,
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
private async Task SetDisplayedAlongWithTranslationsAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[_settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
|
||||
string targetLangCode = _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode;
|
||||
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
|
||||
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
|
||||
if (originalText == null) return;
|
||||
@@ -156,11 +156,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
SongInfo.Title, SongInfo.Artist, SongInfo.Album, SongInfo.DurationMs);
|
||||
|
||||
var lyricsSearchResult = await Task.Run(async () => await _lyrcsSearchService.SearchSmartlyAsync(
|
||||
SongInfo.SourceAppUserModelId ?? "",
|
||||
SongInfo.PlayerId ?? "",
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
SongInfo.DurationMs ?? 0,
|
||||
SongInfo.SongId,
|
||||
token
|
||||
), token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
@@ -208,6 +209,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Kugou:
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.KugouTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Netease:
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
|
||||
@@ -248,12 +250,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLyrics()
|
||||
public void UpdateLyrics()
|
||||
{
|
||||
_refreshLyricsRunner.RunAsync(RefreshLyricsAsync);
|
||||
}
|
||||
|
||||
private void UpdateTranslations()
|
||||
public void UpdateTranslations()
|
||||
{
|
||||
_refreshTranslationRunner.RunAsync(RefreshTranslationAsync);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LibWatcherService;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
@@ -20,19 +19,12 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using EvtSource;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
using WindowsMediaController;
|
||||
@@ -40,7 +32,6 @@ using WindowsMediaController;
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsWindowMode>>,
|
||||
@@ -57,6 +48,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private double _lxMusicPositionSeconds = 0;
|
||||
private double _lxMusicDurationSeconds = 0;
|
||||
private byte[]? _lxMusicAlbumArtBytes = null;
|
||||
|
||||
private bool _cachedIsPlaying = false;
|
||||
private TimeSpan _cachedPosition = TimeSpan.Zero;
|
||||
@@ -292,21 +284,21 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
string id = mediaSession.Id;
|
||||
string sessionId = mediaSession.Id;
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
//RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(id))
|
||||
if (!IsMediaSourceEnabled(sessionId))
|
||||
{
|
||||
_cachedSongInfo = null;
|
||||
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
if (id == Constants.PlayerID.LXMusic)
|
||||
if (sessionId == Constants.PlayerID.LXMusic)
|
||||
{
|
||||
StopSSE();
|
||||
}
|
||||
@@ -321,38 +313,39 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
currentMediaSourceProviderInfo?.PositionOffset = 0;
|
||||
}
|
||||
|
||||
if (id == Constants.PlayerID.AppleMusic || id == Constants.PlayerID.AppleMusicAlternative)
|
||||
{
|
||||
string fixedArtist = mediaProperties.Artist.Split(" — ").FirstOrDefault() ?? mediaProperties.Artist;
|
||||
string fixedAlbum = mediaProperties.Artist.Split(" — ").LastOrDefault() ?? mediaProperties.AlbumTitle;
|
||||
string fixedArtist = mediaProperties.Artist;
|
||||
string fixedAlbum = mediaProperties.AlbumTitle;
|
||||
string? songId = null;
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = fixedArtist,
|
||||
Album = fixedAlbum,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
}
|
||||
else
|
||||
if (sessionId == Constants.PlayerID.AppleMusic || sessionId == Constants.PlayerID.AppleMusicAlternative)
|
||||
{
|
||||
_cachedSongInfo = new SongInfo
|
||||
fixedArtist = mediaProperties.Artist.Split(" — ").FirstOrDefault() ?? mediaProperties.Artist;
|
||||
fixedAlbum = mediaProperties.Artist.Split(" — ").LastOrDefault() ?? mediaProperties.AlbumTitle;
|
||||
}
|
||||
else if (sessionId == Constants.PlayerID.NetEaseCloudMusic)
|
||||
{
|
||||
songId = mediaProperties.Genres.FirstOrDefault()?.Replace("NCM-", "");
|
||||
if (songId != null && songId.Length != 10)
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
songId = null;
|
||||
}
|
||||
}
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = fixedArtist,
|
||||
Album = fixedAlbum,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
PlayerId = sessionId,
|
||||
SongId = songId
|
||||
};
|
||||
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
|
||||
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
if (id == Constants.PlayerID.LXMusic)
|
||||
if (sessionId == Constants.PlayerID.LXMusic)
|
||||
{
|
||||
StartSSE();
|
||||
}
|
||||
@@ -361,7 +354,11 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
if (sessionId == Constants.PlayerID.LXMusic && _lxMusicAlbumArtBytes != null)
|
||||
{
|
||||
_SMTCAlbumArtBytes = _lxMusicAlbumArtBytes;
|
||||
}
|
||||
else if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
}
|
||||
@@ -465,6 +462,11 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void StartSSE()
|
||||
{
|
||||
if (_sse != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sse = new EventSourceReader(new Uri($"{_settingsService.AppSettings.GeneralSettings.LXMusicServer}{Constants.LXMusic.QuerySuffix}")).Start();
|
||||
@@ -504,9 +506,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void Sse_MessageReceived(object sender, EventSourceMessageEventArgs e)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (_cachedSongInfo?.SourceAppUserModelId == Constants.PlayerID.LXMusic)
|
||||
if (_cachedSongInfo?.PlayerId == Constants.PlayerID.LXMusic)
|
||||
{
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.ValueKind == JsonValueKind.Number)
|
||||
@@ -525,6 +527,20 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.FromSeconds(_lxMusicPositionSeconds), TimeSpan.FromSeconds(_lxMusicDurationSeconds)));
|
||||
}
|
||||
}
|
||||
else if (data.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
if (e.Event == "picUrl")
|
||||
{
|
||||
string? picUrl = data.GetString();
|
||||
if (picUrl != null)
|
||||
{
|
||||
_logger.LogInformation("LX Music Album Art URL: {url}", picUrl);
|
||||
_lxMusicAlbumArtBytes = await ImageHelper.GetImageBytesFromUrlAsync(picUrl);
|
||||
_SMTCAlbumArtBytes = _lxMusicAlbumArtBytes;
|
||||
UpdateAlbumArt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -631,18 +647,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageIndex))
|
||||
{
|
||||
_logger.LogInformation("Target language index changed: {Index}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex);
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
@@ -652,6 +656,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageCode))
|
||||
{
|
||||
_logger.LogInformation("Target language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsWindowMode> message)
|
||||
|
||||
@@ -448,9 +448,6 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SetingsPageFeedback.Text" xml:space="preserve">
|
||||
<value>Feedback</value>
|
||||
</data>
|
||||
<data name="SetingsPageRef.Text" xml:space="preserve">
|
||||
<value>Library used</value>
|
||||
</data>
|
||||
<data name="SetingsPageThanks.Text" xml:space="preserve">
|
||||
<value>If you like this project, please consider supporting it by donating. Your support will help keep the project alive and encourage further development.</value>
|
||||
</data>
|
||||
|
||||
@@ -448,9 +448,6 @@
|
||||
<data name="SetingsPageFeedback.Text" xml:space="preserve">
|
||||
<value>フィードバック</value>
|
||||
</data>
|
||||
<data name="SetingsPageRef.Text" xml:space="preserve">
|
||||
<value>使用されているライブラリ</value>
|
||||
</data>
|
||||
<data name="SetingsPageThanks.Text" xml:space="preserve">
|
||||
<value>このプロジェクトが気に入っている場合は、寄付してサポートすることを検討してください。あなたのサポートは、プロジェクトを生かし続け、さらなる開発を促進するのに役立ちます。</value>
|
||||
</data>
|
||||
|
||||
@@ -448,9 +448,6 @@
|
||||
<data name="SetingsPageFeedback.Text" xml:space="preserve">
|
||||
<value>피드백</value>
|
||||
</data>
|
||||
<data name="SetingsPageRef.Text" xml:space="preserve">
|
||||
<value>사용 된 라이브러리</value>
|
||||
</data>
|
||||
<data name="SetingsPageThanks.Text" xml:space="preserve">
|
||||
<value>이 프로젝트가 마음에 들면 기부하여 지원을 고려하십시오. 귀하의 지원은 프로젝트를 계속 유지하고 추가 개발을 장려하는 데 도움이 될 것입니다.</value>
|
||||
</data>
|
||||
|
||||
@@ -448,9 +448,6 @@
|
||||
<data name="SetingsPageFeedback.Text" xml:space="preserve">
|
||||
<value>反馈</value>
|
||||
</data>
|
||||
<data name="SetingsPageRef.Text" xml:space="preserve">
|
||||
<value>使用的库</value>
|
||||
</data>
|
||||
<data name="SetingsPageThanks.Text" xml:space="preserve">
|
||||
<value>如果您喜欢这个项目,请考虑通过捐赠来支持它。您的支持将有助于保持项目的生命并鼓励进一步的发展。</value>
|
||||
</data>
|
||||
|
||||
@@ -448,9 +448,6 @@
|
||||
<data name="SetingsPageFeedback.Text" xml:space="preserve">
|
||||
<value>回饋</value>
|
||||
</data>
|
||||
<data name="SetingsPageRef.Text" xml:space="preserve">
|
||||
<value>使用的庫</value>
|
||||
</data>
|
||||
<data name="SetingsPageThanks.Text" xml:space="preserve">
|
||||
<value>如果您喜歡這個項目,請考慮通過捐贈來支持它。您的支持將有助於保持項目的生命並鼓勵進一步的發展。</value>
|
||||
</data>
|
||||
|
||||
@@ -110,6 +110,10 @@ namespace BetterLyrics.WinUI3
|
||||
[ObservableProperty] public partial Visibility DesktopFlyoutItemVisibility { get; set; } = Visibility.Visible;
|
||||
[ObservableProperty] public partial Visibility PIPFlyoutItemVisibility { get; set; } = Visibility.Visible;
|
||||
[ObservableProperty] public partial Visibility DockFlyoutItemVisibility { get; set; } = Visibility.Visible;
|
||||
[ObservableProperty] public partial Visibility MinimiseButtonVisibility { get; set; } = Visibility.Visible;
|
||||
[ObservableProperty] public partial Visibility MaximiseButtonVisibility { get; set; } = Visibility.Visible;
|
||||
[ObservableProperty] public partial Visibility RestoreButtonVisibility { get; set; } = Visibility.Visible;
|
||||
[ObservableProperty] public partial Visibility CloseButtonVisibility { get; set; } = Visibility.Visible;
|
||||
|
||||
[ObservableProperty] public partial bool IsFullScreenFlyoutItemChecked { get; set; } = false;
|
||||
[ObservableProperty] public partial bool IsDesktopFlyoutItemChecked { get; set; } = false;
|
||||
@@ -305,6 +309,8 @@ namespace BetterLyrics.WinUI3
|
||||
private void SetFullscreenTitleBarControlsStatus()
|
||||
{
|
||||
LockButtonVisibility = DesktopFlyoutItemVisibility = PIPFlyoutItemVisibility = DockFlyoutItemVisibility = Visibility.Collapsed;
|
||||
MinimiseButtonVisibility = MaximiseButtonVisibility = RestoreButtonVisibility = Visibility.Collapsed;
|
||||
CloseButtonVisibility = Visibility.Visible;
|
||||
IsFullScreenFlyoutItemChecked = true;
|
||||
IsImmersiveMode = true;
|
||||
}
|
||||
@@ -312,6 +318,8 @@ namespace BetterLyrics.WinUI3
|
||||
private void SetPIPModeTitleBarControlsStatus()
|
||||
{
|
||||
DesktopFlyoutItemVisibility = FullScreenFlyoutItemVisibility = DockFlyoutItemVisibility = LockButtonVisibility = Visibility.Collapsed;
|
||||
MinimiseButtonVisibility = MaximiseButtonVisibility = RestoreButtonVisibility = Visibility.Collapsed;
|
||||
CloseButtonVisibility = Visibility.Visible;
|
||||
IsImmersiveMode = true;
|
||||
IsPIPFlyoutItemChecked = true;
|
||||
}
|
||||
@@ -324,6 +332,8 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
overlappedPresenter.IsMinimizable = overlappedPresenter.IsMaximizable = false;
|
||||
DesktopFlyoutItemVisibility = LockButtonVisibility = FullScreenFlyoutItemVisibility = PIPFlyoutItemVisibility = Visibility.Collapsed;
|
||||
MinimiseButtonVisibility = MaximiseButtonVisibility = RestoreButtonVisibility = Visibility.Collapsed;
|
||||
CloseButtonVisibility = Visibility.Visible;
|
||||
IsImmersiveMode = true;
|
||||
IsDockFlyoutItemChecked = true;
|
||||
}
|
||||
@@ -337,6 +347,8 @@ namespace BetterLyrics.WinUI3
|
||||
overlappedPresenter.IsMinimizable = overlappedPresenter.IsMaximizable = false;
|
||||
DockFlyoutItemVisibility = FullScreenFlyoutItemVisibility = PIPFlyoutItemVisibility = Visibility.Collapsed;
|
||||
LockButtonVisibility = Visibility.Visible;
|
||||
MinimiseButtonVisibility = MaximiseButtonVisibility = RestoreButtonVisibility = Visibility.Collapsed;
|
||||
CloseButtonVisibility = Visibility.Visible;
|
||||
IsDesktopFlyoutItemChecked = true;
|
||||
}
|
||||
|
||||
@@ -349,6 +361,8 @@ namespace BetterLyrics.WinUI3
|
||||
overlappedPresenter.IsMinimizable = overlappedPresenter.IsMaximizable = true;
|
||||
DesktopFlyoutItemVisibility = DockFlyoutItemVisibility = PIPFlyoutItemVisibility = FullScreenFlyoutItemVisibility = Visibility.Visible;
|
||||
LockButtonVisibility = Visibility.Collapsed;
|
||||
MinimiseButtonVisibility = MaximiseButtonVisibility = CloseButtonVisibility = Visibility.Visible;
|
||||
RestoreButtonVisibility = Visibility.Collapsed;
|
||||
IsFullScreenFlyoutItemChecked = IsDesktopFlyoutItemChecked = IsDockFlyoutItemChecked = IsPIPFlyoutItemChecked = false;
|
||||
IsImmersiveMode = _settingsService.AppSettings.GeneralSettings.IsImmersiveMode;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial TranslationSearchProvider? TranslationSearchProvider { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedTargetLanguageIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string AppleMusicMediaUserToken { get; set; }
|
||||
|
||||
public PlaybackSettingsControlViewModel(
|
||||
ISettingsService settingsService,
|
||||
@@ -77,6 +81,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
AppSettings.MediaSourceProvidersInfo.CollectionChanged += MediaSourceProvidersInfo_CollectionChanged;
|
||||
|
||||
AppleMusicMediaUserToken = PasswordVaultHelper.Get(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey) ?? "";
|
||||
|
||||
SelectedTargetLanguageIndex = LanguageHelper.SupportedTargetLanguages.ToList().FindIndex(x => x.Code == AppSettings.TranslationSettings.SelectedTargetLanguageCode);
|
||||
|
||||
IsLastFMAuthenticated = _lastFMService.IsAuthenticated;
|
||||
LastFMUser = _lastFMService.User;
|
||||
|
||||
@@ -103,7 +111,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void MediaSessionsService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
|
||||
{
|
||||
var current = AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == e.SongInfo?.SourceAppUserModelId)?.FirstOrDefault();
|
||||
var current = AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == e.SongInfo?.PlayerId)?.FirstOrDefault();
|
||||
if (_mediaSessionsService.Position.TotalSeconds <= 1 && current?.ResetPositionOffsetOnSongChanged == true)
|
||||
{
|
||||
current.PositionOffset = 0;
|
||||
@@ -123,8 +131,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
|
||||
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, new System.Threading.CancellationToken());
|
||||
string result = await _libreTranslateService.TranslateTextAsync(
|
||||
"Hello, world!", AppSettings.TranslationSettings.SelectedTargetLanguageCode, new System.Threading.CancellationToken());
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
|
||||
@@ -184,6 +192,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveAppleMusicMediaUserToken()
|
||||
{
|
||||
PasswordVaultHelper.Save(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey, AppleMusicMediaUserToken);
|
||||
_mediaSessionsService.UpdateLyrics();
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsSearchProvider?> message)
|
||||
{
|
||||
if (message.Sender is MediaSessionsService)
|
||||
@@ -205,5 +220,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedTargetLanguageIndexChanged(int value)
|
||||
{
|
||||
AppSettings.TranslationSettings.SelectedTargetLanguageCode = LanguageHelper.SupportedTargetLanguages[value].Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial class SettingsPageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IMediaSessionsService _mediaSessionsService;
|
||||
|
||||
public string Version { get; set; } = MetadataHelper.AppVersion;
|
||||
|
||||
@@ -43,63 +44,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; } = "App";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LibInfo> Libs { get; set; }
|
||||
|
||||
public SettingsPageViewModel(ISettingsService settingsService)
|
||||
public SettingsPageViewModel(ISettingsService settingsService, IMediaSessionsService mediaSessionsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
Libs =
|
||||
[
|
||||
new LibInfo { Name = "3v.EvtSource" },
|
||||
new LibInfo { Name = "ColorThief.ImageSharp" },
|
||||
new LibInfo { Name = "CommunityToolkit.Labs.WinUI.MarqueeText" },
|
||||
new LibInfo { Name = "CommunityToolkit.Labs.WinUI.OpacityMaskView" },
|
||||
new LibInfo { Name = "CommunityToolkit.Labs.WinUI.Shimmer" },
|
||||
new LibInfo { Name = "CommunityToolkit.Mvvm" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Behaviors" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Controls.MetadataControl" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Controls.Primitives" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Controls.Segmented" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Controls.SettingsControls" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Controls.Sizers" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Converters" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Extensions" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Helpers" },
|
||||
new LibInfo { Name = "CommunityToolkit.WinUI.Media" },
|
||||
new LibInfo { Name = "csharp-pinyin" },
|
||||
new LibInfo { Name = "Dubya.WindowsMediaController" },
|
||||
new LibInfo { Name = "H.NotifyIcon.WinUI"},
|
||||
new LibInfo { Name = "Hqub.Last.fm"},
|
||||
new LibInfo { Name = "Lyricify.Lyrics.Helper-NativeAot"},
|
||||
new LibInfo { Name = "Microsoft.Extensions.DependencyInjection"},
|
||||
new LibInfo { Name = "Microsoft.Extensions.Logging"},
|
||||
new LibInfo { Name = "Microsoft.Graphics.Win2D"},
|
||||
new LibInfo { Name = "Microsoft.Windows.SDK.BuildTools"},
|
||||
new LibInfo { Name = "Microsoft.WindowsAppSDK"},
|
||||
new LibInfo { Name = "Nito.AsyncEx"},
|
||||
new LibInfo { Name = "Nito.AsyncEx.Tasks"},
|
||||
new LibInfo { Name = "NTextCat"},
|
||||
new LibInfo { Name = "RomajiConverter.Core"},
|
||||
new LibInfo { Name = "Serilog.Extensions.Logging"},
|
||||
new LibInfo { Name = "Serilog.Sinks.File"},
|
||||
new LibInfo { Name = "ShadowViewer.Controls.Notification"},
|
||||
new LibInfo { Name = "SixLabors.ImageSharp" },
|
||||
new LibInfo { Name = "System.Drawing.Common"},
|
||||
new LibInfo { Name = "System.Text.Encoding.CodePages"},
|
||||
new LibInfo { Name = "TagLibSharp" },
|
||||
new LibInfo { Name = "TinyPinyin.Net"},
|
||||
new LibInfo { Name = "Ude.NetStandard"},
|
||||
new LibInfo { Name = "Vanara.PInvoke.CoreAudio"},
|
||||
new LibInfo { Name = "Vanara.PInvoke.DwmApi"},
|
||||
new LibInfo { Name = "Vanara.PInvoke.Gdi32"},
|
||||
new LibInfo { Name = "Vanara.PInvoke.Shell32"},
|
||||
new LibInfo { Name = "Vanara.PInvoke.User32"},
|
||||
new LibInfo { Name = "WinUIEx" },
|
||||
new LibInfo { Name = "z440.atl.core" },
|
||||
];
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -123,11 +123,6 @@
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayExit"
|
||||
Click="ExitAppMenuFlyoutItem_Click"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
@@ -145,6 +140,7 @@
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize}"
|
||||
Glyph="" />
|
||||
</ToggleButton>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -156,6 +152,52 @@
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
|
||||
<!-- Window Minimise -->
|
||||
<Button
|
||||
x:Name="MinimiseButton"
|
||||
Click="MinimiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.MinimiseButtonVisibility, Mode=OneWay}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Maximise -->
|
||||
<Button
|
||||
x:Name="MaximiseButton"
|
||||
Click="MaximiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.MaximiseButtonVisibility, Mode=OneWay}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Restore -->
|
||||
<Button
|
||||
x:Name="RestoreButton"
|
||||
Click="RestoreButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.RestoreButtonVisibility, Mode=OneWay}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Close -->
|
||||
<Button
|
||||
x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.CloseButtonVisibility, Mode=OneWay}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
AppWindow.Changed += AppWindow_Changed;
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
UpdateTitleBarArea();
|
||||
|
||||
Title = App.ResourceLoader!.GetString("LyricsPageTitle");
|
||||
@@ -134,6 +135,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
_settingsService.AppSettings.StandardModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
|
||||
_settingsService.AppSettings.StandardModeSettings.IsMaximized = overlappedPresenter.State == OverlappedPresenterState.Maximized;
|
||||
ViewModel.MaximiseButtonVisibility = _settingsService.AppSettings.StandardModeSettings.IsMaximized ? Visibility.Collapsed : Visibility.Visible;
|
||||
ViewModel.RestoreButtonVisibility = _settingsService.AppSettings.StandardModeSettings.IsMaximized ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
break;
|
||||
case LyricsWindowMode.DockMode:
|
||||
@@ -222,14 +225,38 @@ namespace BetterLyrics.WinUI3.Views
|
||||
WindowHelper.OpenWindow<MusicGalleryWindow>();
|
||||
}
|
||||
|
||||
private void ExitAppMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.ExitApp();
|
||||
}
|
||||
|
||||
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel = TipContainerCenter;
|
||||
}
|
||||
|
||||
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Minimize();
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Maximize();
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.ExitOrClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,32 +220,6 @@
|
||||
</controls:SettingsExpander.ItemsHeader>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<TextBlock x:Uid="SetingsPageRef" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<ListView
|
||||
Padding="0,6"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefault}"
|
||||
CornerRadius="4"
|
||||
ItemsSource="{x:Bind ViewModel.Libs, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<RelativePanel Padding="42,0">
|
||||
<HyperlinkButton
|
||||
Margin="-12,0,0,0"
|
||||
Content="{Binding Name}"
|
||||
NavigateUri="{Binding Url}"
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True" />
|
||||
<TextBlock
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Text="{Binding Version}" />
|
||||
</RelativePanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
19
FAQ/FAQ.md
@@ -1,25 +1,28 @@
|
||||
### Where I can find the logs?
|
||||
`C:\Users\%USERNAME%\AppData\Local\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\logs`
|
||||
### ? Where I can find the logs?
|
||||
`%LocalAppData%\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\logs`
|
||||
|
||||
### How to install ".msixbundle" package?
|
||||
### ? Where I can find the lyrics cache?
|
||||
`%LocalAppData%\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\lyrics`
|
||||
|
||||
### ? How to install ".msixbundle" package? (for test package only)
|
||||
[See this doc](https://github.com/jayfunc/BetterLyrics/blob/dev/How2Install/How2Install.md)
|
||||
|
||||
### Lyrics are moving back and forth constantly, how to fix it?
|
||||
### ? Lyrics are moving back and forth constantly, how to fix it?
|
||||

|
||||
|
||||
Go to Settings > Playback sources > Disable "Lyrics timeline sync" or increase "Lyrics timeline sync threshold"
|
||||
|
||||
### Wrong lyrics are shown, how to fix it?
|
||||
### ? Wrong lyrics are shown, how to fix it?
|
||||

|
||||
|
||||
Open search panel to manually search for the correct lyrics.
|
||||
|
||||
### Playback control panel is not showing in dock mode, how to fix it?
|
||||
### ? Playback control panel is not showing in dock mode, how to fix it?
|
||||

|
||||
|
||||
Hover over the bottom of the lyrics window and click on the white line to show the playback control panel.
|
||||
|
||||
### How to lock/unlock the lyrics window in desktop mode?
|
||||
### ? How to lock/unlock the lyrics window in desktop mode?
|
||||

|
||||
|
||||

|
||||
@@ -30,5 +33,5 @@ Alternatively, you can also use the shortcut `Ctrl+Alt+U` (default) to toggle lo
|
||||
|
||||
You can change the shortcut in Settings > App appearance and behavior > Unlock and lock shortcut keys
|
||||
|
||||
### How to enable/disable immersive mode?
|
||||
### ? How to enable/disable immersive mode?
|
||||

|
||||
@@ -1,4 +1,4 @@
|
||||
# How to install ".msixbundle" package
|
||||
# How to install ".msixbundle" test package
|
||||
|
||||
## Pre-steps
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
Copyright (c) 2025 Zhe Fang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
106
README.md
@@ -23,7 +23,7 @@ BetterLyrics
|
||||
</div>
|
||||
|
||||
<h4 align="center">
|
||||
Your dynamic lyrics display tool built with WinUI 3 and Win2D — works with local playback and other players
|
||||
Your dynamic lyrics display tool, built with WinUI 3 and Win2D, works with local playback and other players
|
||||
</h3>
|
||||
|
||||
## 🎉 This project was featured by SSPAI!
|
||||
@@ -32,64 +32,73 @@ Check out the article: [BetterLyrics – An immersive and smooth lyrics display
|
||||
|
||||
## 🔈 Feedback and chat group
|
||||
|
||||
- [<img src="BetterLyrics.WinUI3\BetterLyrics.WinUI3\Assets\QQ.png" height="20"> QQ](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)
|
||||
- [<img src="BetterLyrics.WinUI3\BetterLyrics.WinUI3\Assets\Discord.png" height="12"> Discord](https://discord.gg/5yAQPnyCKv)
|
||||
- [<img src="BetterLyrics.WinUI3\BetterLyrics.WinUI3\Assets\Telegram.png" height="16"> Telegram](https://t.me/+svhSLZ7awPsxNGY1)
|
||||
[QQ 群](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 Server](https://discord.gg/5yAQPnyCKv) | [Telegram Group](https://t.me/+svhSLZ7awPsxNGY1)
|
||||
|
||||
## 🌟 Highlighted features
|
||||
|
||||
- 🌠 **Pleasing User Interface**
|
||||
- Fluent animations and effects
|
||||
- ↔️ **Strong Lyrics Translation**
|
||||
- Offline machine translation (supporting 30 languages)
|
||||
- Auto reading local lyrics files for embedded translation
|
||||
- Offline machine translation (supporting 30+ languages)
|
||||
- Auto-reading local lyrics files for embedded translation
|
||||
- 🧩 **Various Lyrics Source**
|
||||
- Local storage
|
||||
- 💾 Local storage
|
||||
- Music files (with embedded lyrics)
|
||||
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) files (with both core format and enhanced format)
|
||||
- [.eslrc](https://github.com/ESLyric/release) files
|
||||
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) files
|
||||
- Online lyrics providers
|
||||
- ☁️ Online lyrics providers
|
||||
- QQ 音乐
|
||||
- 网易云音乐
|
||||
- 酷狗音乐
|
||||
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
- <details><summary>⚠️ Apple Music (additional config needed)</summary>
|
||||
|
||||
- Open the Apple Music web app and the Developer Tools window. Refresh the page. Return to the Developer Tools window, select Fetch/XHR, select a request, find the Media-User-Token header in the request header, and copy its value.
|
||||
- Open BetterLyrics and go to the Playback Source settings. Enter the copied value in the Media-User-Token (for Apple Music) setting and click the accept icon on the right-hand side.
|
||||
|
||||
- 🎶 **Multiple Music Players Supported**
|
||||
|
||||
- <details><summary>⚠️ 网易云音乐</summary>
|
||||
- <details><summary>网易云音乐</summary>
|
||||
|
||||
- Please install the [BetterNCM plugin](https://microblock.cc/betterncm) first. If a downgrade guide pops up after the installation, please follow the guide to complete the downgrade of NetEase Cloud Music (downgrade to 2.10.13);
|
||||
- After that, please install the InfLink plugin in PluginMarket. After the installation is complete, please restart NetEase Cloud Music. At this point, all preparatory operations have been completed, enjoy it!
|
||||
- ⚠️ Please note that there is issues with timeline due to plugin issue
|
||||
- Please make sure that you have latest version (3.1.20+) of 网易云音乐 installed on your PC.
|
||||
- Install the [BetterNCM plugin](https://microblock.cc/betterncm) first
|
||||
- After that, install the [InfLink-rs](https://github.com/apoint123/inflink-rs) plugin in PluginMarket. After the installation is complete, please restart 网易云音乐. At this point, all preparatory operations have been completed. Enjoy it!
|
||||
|
||||
</details>
|
||||
|
||||
- <details><summary>⚠️ 酷狗音乐</summary>
|
||||
|
||||
- Please make sure that the Kugou Music setting "Support system playback controls, such as lock screen interface" is turned on
|
||||
- No timeline information broadcasted, which means when you change timeline position in Kugou Music, BetterLyrics has no way to detect this change.
|
||||
- ⚠️ Please note that there is issues with timeline due to Kugou itself
|
||||
- Please make sure that the 酷狗音乐 setting "Support system playback controls, such as lock screen interface" is turned on
|
||||
- No timeline information broadcast, which means when you change the timeline position in Kugou Music, BetterLyrics has no way to detect this change
|
||||
- ⚠️ Please note that there are issues with the timeline due to Kugou itself
|
||||
|
||||
</details>
|
||||
|
||||
- <details><summary>⚠️ foobar2000</summary>
|
||||
|
||||
- Make sure you have https://github.com/dumbie/foo_mediacontrol installed with it
|
||||
- ⚠️ Please note that there is issues with timeline due to plugin issue
|
||||
- ⚠️ Please note that there are issues with the timeline due to a plugin issue
|
||||
|
||||
</details>
|
||||
|
||||
- Apple Music
|
||||
- Spotify
|
||||
- QQ 音乐
|
||||
- PotPlayer
|
||||
- Media Player (System)
|
||||
|
||||
- <details><summary>QQ 音乐</summary>
|
||||
|
||||
- Please keep it at the latest version
|
||||
- Then open Settings in QQ 音乐, enable "Show System Media Transport Controls (SMTC)".
|
||||
|
||||
</details>
|
||||
|
||||
- <details><summary>LX Music</summary>
|
||||
|
||||
- Please make sure you have enabled "Open API" in LX Music settings page
|
||||
- Then open BetterLyrics, go to settings, go to "Advanced options", input your LX Music server address (mostly like http://127.0.0.1:23330) and there you go!
|
||||
- Then open BetterLyrics, go to settings, go to "Playback sources", input your LX Music server address (mostly like http://127.0.0.1:23330) and there you go!
|
||||
|
||||
</details>
|
||||
|
||||
@@ -113,7 +122,7 @@ Check out the article: [BetterLyrics – An immersive and smooth lyrics display
|
||||
|
||||
- 🪟 **Multiple Display Modes**
|
||||
- **Standard Mode**
|
||||
- Enjoy an immersive listening journey with rich lyrics animations and beautifully dynamic backgrounds
|
||||
- Enjoy an immersive listening journey with rich lyrics, animations and beautifully dynamic backgrounds
|
||||
- **Dock Mode**
|
||||
- A smart animated lyrics bar docked to your screen edge
|
||||
- **Desktop Mode**
|
||||
@@ -121,33 +130,29 @@ Check out the article: [BetterLyrics – An immersive and smooth lyrics display
|
||||
- 🧠 **Smart Behaviors**
|
||||
- Auto hide when music paused
|
||||
|
||||
> This project is still under development, bugs and unexpected behaviors may be existed in the latest branch.
|
||||
> This project is still under development, bugs and unexpected behaviours may exist in the latest branch.
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Standard mode
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
### Standard mode (narrow)
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
### Standard mode (fullscreen)
|
||||
|
||||

|
||||

|
||||
|
||||
### Dock mode
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
### Desktop mode
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Demonstration
|
||||
|
||||
@@ -155,36 +160,48 @@ Watch our introduction video (uploaded on 18 Aug 2025) on Bilibili [here](https:
|
||||
|
||||
## Try it now
|
||||
|
||||
### Microsoft Store
|
||||
### Microsoft Store (Latest version)
|
||||
|
||||
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
|
||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
|
||||
</a>
|
||||
|
||||
**Easiest** way to get it. **Unlimited** free trail or purchase (there is **no difference** between free and paid version)
|
||||
**Unlimited** free trail or purchase (there is **no difference** between free and paid version)
|
||||
|
||||
☕ If you find it useful, please consider [donating](#donations) or purchasing 🧧 it in **Microsoft Store**, I'll appreciate it! 🥰
|
||||
|
||||
> When there's a stable version built, Microsoft Store will be the first channel to get updated.
|
||||
### Unable to download from the MS Store? (Alternative way to download from MS Store, latest version too)
|
||||
|
||||
### Google Drive
|
||||
1. Visit https://store.rg-adguard.net/
|
||||
2. Type https://apps.microsoft.com/detail/9p1wcd1p597r in the link input area
|
||||
3. Select Retail from the drop-down list
|
||||
4. Click the check mark
|
||||
5. Select the largest installation package in the resulting list to download and install. If you fail to install, try to install the dependency packages first.
|
||||
|
||||
Or get it from Google Drive (see [release](https://github.com/jayfunc/BetterLyrics/releases) page for the link)
|
||||
### Unable to launch the app?
|
||||
|
||||
> Please note you are downloading ".zip" file, for guide on how to install it, please kindly follow [this doc](How2Install/How2Install.md).
|
||||
If you are using a third-party modified Windows, you probably can not launch the app.
|
||||
|
||||
To solve this issue, please try to download from [Google Drive (v1.0.76.0)](https://drive.google.com/file/d/1dKxp8Zf1xwAtt0JER0wHZ-94f855GdH0/view?usp=drive_link) (may not be the latest version) and follow the instructions [here](https://github.com/jayfunc/BetterLyrics/blob/dev/How2Install/How2Install.md).
|
||||
|
||||
## Build
|
||||
|
||||
Before you build, make sure that you have already replaced `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFMTemplate` with `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFM.cs`
|
||||
|
||||
## 💖 Many thanks to
|
||||
|
||||
- [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper)
|
||||
- Provide lyrics fetch, decryption, and parse for QQ, Netease, Kugou sources
|
||||
- Provide lyrics fetch, decryption, and parsing for QQ, Netease, and Kugou sources
|
||||
- [lrclib](https://github.com/tranxuanthang/lrclib)
|
||||
- LRCLIB lyrics API provider
|
||||
- [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics)
|
||||
- Apple Music lyrics fetch using Python
|
||||
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
|
||||
- Used for extracting pictures in music files
|
||||
- Used for extracting pictures from music files
|
||||
- [WinUIEx](https://github.com/dotMorten/WinUIEx)
|
||||
- Provide easy ways to access Win32 API regarding windowing
|
||||
- Provide easy ways to access the Win32 API regarding windowing
|
||||
- [TagLib#](https://github.com/mono/taglib-sharp)
|
||||
- Used for reading original lyrics content
|
||||
- Used for reading the original lyrics content
|
||||
- [Vanara](https://github.com/dahall/Vanara)
|
||||
- Win32 API wrapper
|
||||
- [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate)
|
||||
@@ -208,15 +225,16 @@ Or get it from Google Drive (see [release](https://github.com/jayfunc/BetterLyri
|
||||
|
||||
Cannot find your language?
|
||||
Don't worry! Start translating and become one of the contributors! 😆
|
||||
Click the [link](https://crowdin.com/project/betterlyrics/invite?h=d767e4f2dbd832d8fcdb6f7e5a198b402502866) to translate this app into your language via Crowdin now!
|
||||
|
||||
Fork this project and navigate to `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Strings\` then open any `.resw` file to start contributing!
|
||||
|
||||
## Star history
|
||||
|
||||
[](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
|
||||
|
||||
## Any issues and PRs are welcomed
|
||||
## Any issues and PRs are welcome
|
||||
|
||||
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
|
||||
If you find a bug, please file it in issues, or if you have any ideas, feel free to share them here.
|
||||
|
||||
## Donations
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 764 KiB |
BIN
Screenshots/desktop.png
Normal file
|
After Width: | Height: | Size: 565 KiB |
|
Before Width: | Height: | Size: 757 KiB |
|
Before Width: | Height: | Size: 34 KiB |
BIN
Screenshots/dock.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 7.6 MiB |
|
Before Width: | Height: | Size: 775 KiB |
|
Before Width: | Height: | Size: 382 KiB |
BIN
Screenshots/std-fullscreen.png
Normal file
|
After Width: | Height: | Size: 642 KiB |
BIN
Screenshots/std-narrow.png
Normal file
|
After Width: | Height: | Size: 847 KiB |
BIN
Screenshots/std.png
Normal file
|
After Width: | Height: | Size: 823 KiB |