diff --git a/BetterLyrics.Core/BetterLyrics.Core.csproj b/BetterLyrics.Core/BetterLyrics.Core.csproj
index 968ac4e..b760144 100644
--- a/BetterLyrics.Core/BetterLyrics.Core.csproj
+++ b/BetterLyrics.Core/BetterLyrics.Core.csproj
@@ -6,8 +6,4 @@
enable
-
-
-
-
diff --git a/BetterLyrics.Core/Class1.cs b/BetterLyrics.Core/Class1.cs
deleted file mode 100644
index eeb95c2..0000000
--- a/BetterLyrics.Core/Class1.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace BetterLyrics.Core
-{
- public class Class1
- {
-
- }
-}
diff --git a/BetterLyrics.Core/Interfaces/ILyricsProvider.cs b/BetterLyrics.Core/Interfaces/ILyricsProvider.cs
new file mode 100644
index 0000000..ce97800
--- /dev/null
+++ b/BetterLyrics.Core/Interfaces/ILyricsProvider.cs
@@ -0,0 +1,16 @@
+using BetterLyrics.Core.Models;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BetterLyrics.Core.Interfaces
+{
+ public interface ILyricsProvider
+ {
+ string Id { get; }
+ string Name { get; }
+ string Author { get; }
+
+ Task GetLyricsAsync(string title, string artist, string album, double duration);
+ }
+}
diff --git a/BetterLyrics.Core/Models/LyricsSearchResult.cs b/BetterLyrics.Core/Models/LyricsSearchResult.cs
new file mode 100644
index 0000000..3ffd7ec
--- /dev/null
+++ b/BetterLyrics.Core/Models/LyricsSearchResult.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BetterLyrics.Core.Models
+{
+ public record LyricsSearchResult(
+ string? Title,
+ string? Artist,
+ string? Album,
+ double? Duration,
+ string Raw,
+ string? Translation = null,
+ string? Transliteration = null,
+ string? Reference = null);
+}
diff --git a/BetterLyrics.Plugins.Demo/BetterLyrics.Plugins.Demo.csproj b/BetterLyrics.Plugins.Demo/BetterLyrics.Plugins.Demo.csproj
new file mode 100644
index 0000000..7a7889d
--- /dev/null
+++ b/BetterLyrics.Plugins.Demo/BetterLyrics.Plugins.Demo.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/BetterLyrics.Plugins.Demo/DemoLyricsProvider.cs b/BetterLyrics.Plugins.Demo/DemoLyricsProvider.cs
new file mode 100644
index 0000000..ce1cb78
--- /dev/null
+++ b/BetterLyrics.Plugins.Demo/DemoLyricsProvider.cs
@@ -0,0 +1,59 @@
+using BetterLyrics.Core.Interfaces;
+using BetterLyrics.Core.Models;
+
+namespace BetterLyrics.Plugins.Demo
+{
+ public class DemoLyricsProvider : ILyricsProvider
+ {
+ public string Id => "f7acc86b-6e3d-42c3-a9a9-8c05c5339412";
+ public string Name => "Demo Plugin";
+ public string Author => "jayfunc";
+
+ public async Task GetLyricsAsync(string title, string artist, string album, double duration)
+ {
+ await Task.Delay(300);
+
+ string searchedTitle = "Demo Song";
+ string searchedArtist = "Demo Artist";
+ string searchedAlbum = "Demo Album";
+ double searchedDuration = 25.0;
+
+ string searchedRaw =
+ $"[00:00.00]Welcome to use Demo Plugin\n" +
+ $"[00:05.00]Playing: {title} now\n" +
+ $"[00:10.00]Artist: {artist}\n" +
+ $"[00:15.00]Album: {album}\n" +
+ $"[00:20.00]Duration: {duration}\n" +
+ $"[00:25.00]This is a test lyrics source...";
+
+ string searchedTranslation =
+ $"[00:00.00]欢迎使用演示插件\n" +
+ $"[00:05.00]当前正在播放:{title}\n" +
+ $"[00:10.00]歌手:{artist}\n" +
+ $"[00:15.00]专辑:{album}\n" +
+ $"[00:20.00]时长:{duration}\n" +
+ $"[00:25.00]这是一个测试歌词源...";
+
+ string searchedTransliteration =
+ $"[00:00.00]ˈwɛlkəm tuː juːz ˈdɛmoʊ ˈplʌgɪn\n" +
+ $"[00:05.00]ˈpleɪɪŋ: {title} naʊ\n" +
+ $"[00:10.00]ˈɑːrtɪst: {artist}\n" +
+ $"[00:15.00]ˈælbəm: {album}\n" +
+ $"[00:20.00]dʊˈreɪʃən: {duration}\n" +
+ $"[00:25.00]ðɪs ɪz ə tɛst ˈlɪrɪks sɔːrs...";
+
+ string searchedReference = "https://path.to.lyrics/if.the.lyrics.was.originally.fetched.from.web";
+
+ return new LyricsSearchResult(
+ searchedTitle,
+ searchedArtist,
+ searchedAlbum,
+ searchedDuration,
+ searchedRaw,
+ searchedTranslation,
+ searchedTransliteration,
+ searchedReference);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs
index eabb8c7..a3f57e8 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs
@@ -10,6 +10,7 @@ using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Services.LyricsCacheService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.PlayHistoryService;
+using BetterLyrics.WinUI3.Services.PluginService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.SMTCService;
using BetterLyrics.WinUI3.Services.SongSearchMapService;
@@ -248,6 +249,7 @@ namespace BetterLyrics.WinUI3
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
// ViewModels
.AddSingleton()
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
index 9fe2abd..4b28fd7 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
@@ -96,6 +96,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -121,6 +125,7 @@
+
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs
index d6ce300..5929d5e 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsSearchProvider.cs
@@ -14,5 +14,6 @@ namespace BetterLyrics.WinUI3.Enums
LocalEslrcFile,
LocalTtmlFile,
AppleMusic,
+ Plugin = 999,
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TranslationSearchProvider.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TranslationSearchProvider.cs
index 68bcac4..d6d9d9a 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TranslationSearchProvider.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TranslationSearchProvider.cs
@@ -13,5 +13,6 @@
LocalEslrcFile,
LocalTtmlFile,
LibreTranslate,
+ Plugin = 999,
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TransliterationSearchProvider.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TransliterationSearchProvider.cs
index 8296857..20c90c4 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TransliterationSearchProvider.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/TransliterationSearchProvider.cs
@@ -13,6 +13,7 @@
LocalEslrcFile,
LocalTtmlFile,
BetterLyrics,
- CutletDocker
+ CutletDocker,
+ Plugin = 999,
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Entities/LyricsCacheItem.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Entities/LyricsCacheItem.cs
index ff80b22..64ca9b1 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Entities/LyricsCacheItem.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Entities/LyricsCacheItem.cs
@@ -47,10 +47,15 @@ namespace BetterLyrics.WinUI3.Models
[NotMapped][JsonIgnore] public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
+ [MaxLength(128)]
+ public string? PluginId { get; set; }
+
public object Clone()
{
return new LyricsCacheItem()
{
+ PluginId = this.PluginId,
+
Provider = this.Provider,
TranslationProvider = this.TranslationProvider,
TransliterationProvider = this.TransliterationProvider,
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs
index 73a9303..2874e86 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs
@@ -1,5 +1,6 @@
// 2025/6/23 by Zhe Fang
+using BetterLyrics.Core.Interfaces;
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
@@ -10,6 +11,7 @@ using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Providers;
using BetterLyrics.WinUI3.Services.FileSystemService;
using BetterLyrics.WinUI3.Services.LyricsCacheService;
+using BetterLyrics.WinUI3.Services.PluginService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.SongSearchMapService;
using Lyricify.Lyrics.Helpers;
@@ -36,6 +38,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private readonly IFileSystemService _fileSystemService;
private readonly ILyricsCacheService _lyricsCacheService;
private readonly ISongSearchMapService _songSearchMapService;
+ private readonly IPluginService _pluginService;
private readonly ILogger _logger;
public LyricsSearchService(
@@ -43,6 +46,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
IFileSystemService fileSystemService,
ILyricsCacheService lyricsCacheService,
ISongSearchMapService songSearchMapService,
+ IPluginService pluginService,
ILogger logger
)
{
@@ -50,6 +54,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
_fileSystemService = fileSystemService;
_lyricsCacheService = lyricsCacheService;
_songSearchMapService = songSearchMapService;
+ _pluginService = pluginService;
_logger = logger;
_lrcLibHttpClient = new();
@@ -207,11 +212,26 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
_logger.LogInformation("SearchAllAsync {SongInfo}", songInfo);
var results = new List();
+
foreach (var provider in Enum.GetValues())
{
+ if (provider == LyricsSearchProvider.Plugin) continue;
+
var searchResult = await SearchSingleAsync(songInfo, provider, checkCache, token);
results.Add(searchResult);
}
+
+ if (_pluginService.Providers.Any())
+ {
+ foreach (var plugin in _pluginService.Providers)
+ {
+ if (token.IsCancellationRequested) break;
+
+ var pluginResult = await SearchPluginAsync(songInfo, plugin, token);
+ results.Add(pluginResult);
+ }
+ }
+
return results;
}
@@ -673,5 +693,41 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
return lyricsSearchResult;
}
+
+ private async Task SearchPluginAsync(SongInfo songInfo, ILyricsProvider plugin, CancellationToken token)
+ {
+ var cacheItem = new LyricsCacheItem
+ {
+ Provider = LyricsSearchProvider.Plugin,
+ PluginId = plugin.Id,
+ };
+
+ try
+ {
+ var result = await plugin.GetLyricsAsync(songInfo.Title, songInfo.Artist, songInfo.Album, songInfo.Duration);
+
+ if (result != null && !string.IsNullOrEmpty(result.Raw))
+ {
+ cacheItem.Title = result.Title;
+ cacheItem.Artist = result.Artist;
+ cacheItem.Album = result.Album;
+ cacheItem.Duration = result.Duration;
+
+ cacheItem.Raw = result.Raw;
+ cacheItem.Translation = result.Translation;
+ cacheItem.Transliteration = result.Transliteration;
+
+ cacheItem.Reference = result.Reference ?? "about:blank";
+ cacheItem.MatchPercentage = MetadataComparer.CalculateScore(songInfo, cacheItem);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Plugin {PluginName} failed to search", plugin.Name);
+ }
+
+ return cacheItem;
+ }
+
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/IPluginService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/IPluginService.cs
new file mode 100644
index 0000000..f8f36e5
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/IPluginService.cs
@@ -0,0 +1,14 @@
+using BetterLyrics.Core.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BetterLyrics.WinUI3.Services.PluginService
+{
+ public interface IPluginService
+ {
+ IReadOnlyList Providers { get; }
+
+ void LoadPlugins();
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/PluginLoadContext.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/PluginLoadContext.cs
new file mode 100644
index 0000000..9b4133c
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/PluginLoadContext.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Text;
+
+namespace BetterLyrics.WinUI3.Services.PluginService
+{
+ public class PluginLoadContext : AssemblyLoadContext
+ {
+ private AssemblyDependencyResolver _resolver;
+
+ public PluginLoadContext(string pluginPath)
+ {
+ _resolver = new AssemblyDependencyResolver(pluginPath);
+ }
+
+ protected override Assembly? Load(AssemblyName assemblyName)
+ {
+ string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
+ if (assemblyPath != null)
+ {
+ return LoadFromAssemblyPath(assemblyPath);
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/PluginService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/PluginService.cs
new file mode 100644
index 0000000..c8b2c5f
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PluginService/PluginService.cs
@@ -0,0 +1,21 @@
+using BetterLyrics.Core.Interfaces;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BetterLyrics.WinUI3.Services.PluginService
+{
+ public class PluginService : IPluginService
+ {
+ private List _providers = new();
+
+ public IReadOnlyList Providers => _providers;
+
+ public void LoadPlugins()
+ {
+ // 在涉及加载程序集的地方:
+ // var context = new PluginLoadContext(pluginPath);
+ // 它是本文件夹下的 internal 或者是 public 类,直接用即可。
+ }
+ }
+}
diff --git a/BetterLyrics.sln b/BetterLyrics.sln
index 2acdd24..b63e83d 100644
--- a/BetterLyrics.sln
+++ b/BetterLyrics.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
-VisualStudioVersion = 18.1.11312.151 d18.0
+VisualStudioVersion = 18.1.11312.151
MinimumVisualStudioVersion = 10.0.40219.1
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BetterLyrics.WinUI3 (Package)", "BetterLyrics.WinUI3\BetterLyrics.WinUI3 (Package)\BetterLyrics.WinUI3 (Package).wapproj", "{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}"
EndProject
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorThief.WinUI3", "ColorT
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Core", "BetterLyrics.Core\BetterLyrics.Core.csproj", "{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.Plugins.Demo", "BetterLyrics.Plugins.Demo\BetterLyrics.Plugins.Demo.csproj", "{87D235CA-4311-4766-8186-AD9B193DFABC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -89,6 +91,18 @@ Global
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x64.Build.0 = Release|Any CPU
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x86.ActiveCfg = Release|Any CPU
{0F47FE6F-D0AA-49E5-8F33-78DFDEB1F810}.Release|x86.Build.0 = Release|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x64.Build.0 = Debug|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Debug|x86.Build.0 = Debug|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Release|ARM64.Build.0 = Release|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x64.ActiveCfg = Release|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x64.Build.0 = Release|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x86.ActiveCfg = Release|Any CPU
+ {87D235CA-4311-4766-8186-AD9B193DFABC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE