diff --git a/BetterLyrics.Core/BetterLyrics.Core.csproj b/BetterLyrics.Core/BetterLyrics.Core.csproj index 9d780f1..b760144 100644 --- a/BetterLyrics.Core/BetterLyrics.Core.csproj +++ b/BetterLyrics.Core/BetterLyrics.Core.csproj @@ -6,10 +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