Jump to content

Lyrics Plugin - fetch synced / plain text (for testing)


Recommended Posts

mickle026
Posted (edited)

This plugin  leverages the metadata reader call on metadata refresh.  So it really pretends to be a local file reader but its not.
It accesses the only free (no paid tiers) api database there is at the moment for lyrics, both LRC and TXT.

It is triggered by either "Replace metadata" or "search for missing metadata", you dont need replace metadata if you already have it, missing metadata is much faster.  It does therefore trigger ffprobe.

Unzip the archive and copy the dll to the plugins directory, restart the server.  You will find the config at the plugin and enable it, in your library.

image.png.8cf7a45fb77bc52b24b23e72e2d3bdde.png

 

The config screen allows you to choose what to download,

It operates seemlessly with a metadata refresh on the song track or Album, or if a newly added item being scanned in.

lrclib-plugin-screenshot.png.309e0d12203bcf7171702513d4d630d0.png

Luke recommends I should do this as a subtitle plugin and via scheduled tasks, instead of this way ( I think because it will add time to a library scan with both ffprobe and then fetching the data) , my testing showed about 1 sec per track to fetch the data - what do you think?

Anyway have a play and let me know what you think.

Accurate?
Fast enough?
Improvements?

Things like that

 

LRCLIBProvider.zip

Edited by mickle026
  • Like 1
  • 1 month later...
Posted

Works great for me!

It's a touch slow in a large library because ffmpeg has to touch every file. It definitely serves its purpose, but if there were one suggestion, it would be to figure out a way, when "Refresh missing metadata" is kicked off, to determine missing LRC/TXT files in a more efficient way than executing ffmpeg or some other binary against it. That way, you are only executing that relatively time-consuming process when necessary. Otherwise, great work! You've certainly filled a gap that I see plenty of demand for. I've spent quite a bit of time and not found anything that integrates into Emby or Lidarr and maintains the files alongside the media library files.

Much appreciated. Keep it up!!

Posted
Quote

Luke recommends I should do this as a subtitle plugin and via scheduled tasks, instead of this way ( I think because it will add time to a library scan with both ffprobe and then fetching the data)

no, a lot of other reasons. users will get a whole lyrics settings section in library option that is similar to subtitle options. they'll be able to manually search for lyrics using the same kind of function as searching for subtitles.

mickle026
Posted
On 08/03/2025 at 06:44, Luke said:

no, a lot of other reasons. users will get a whole lyrics settings section in library option that is similar to subtitle options. they'll be able to manually search for lyrics using the same kind of function as searching for subtitles.

I have the intention of adding the subtitle provider, just not yet I am super busy elsewhere.

  • Like 1
  • Thanks 1
mickle026
Posted
On 08/03/2025 at 06:15, a3gill said:

Works great for me!

It's a touch slow in a large library because ffmpeg has to touch every file. It definitely serves its purpose, but if there were one suggestion, it would be to figure out a way, when "Refresh missing metadata" is kicked off, to determine missing LRC/TXT files in a more efficient way than executing ffmpeg or some other binary against it. That way, you are only executing that relatively time-consuming process when necessary. Otherwise, great work! You've certainly filled a gap that I see plenty of demand for. I've spent quite a bit of time and not found anything that integrates into Emby or Lidarr and maintains the files alongside the media library files.

Much appreciated. Keep it up!!

I don't call or use ffmpeg, that's done by Emby core, when trying to determine everything else about the metadata, i.e. is any missing or in need of updating.

  • 6 months later...
Posted (edited)

This is a totally new build, it runs during library scans, metadata refresh or as a Task, it has also got enabled for downloading like subtitles, but that's not working in the UI.

This version is in testing, it still needs a few more tweaks. So its only attached for the DEVS to look into the subtitle (Lyric) download

I have implemented the subtitle interface, it doesn't show in the UI,

It enums the audio as video content, there is no other options for audio.

 

    public class LrcSubtitleProvider : ISubtitleProvider
    {
        private readonly ILogger _logger;

        public LrcSubtitleProvider(ILogger logger)
        {
            _logger = logger;
        }

        public string Name => "LRCLIB Lyrics";

        public IEnumerable<VideoContentType> SupportedMediaTypes => new[]
        {
            VideoContentType.Audio
        };

Screenshot2025-09-15014611.png.70dfe7ba4a0540666c5b8a602a706b9d.png

 

Screenshot_15-9-2025_14759_localhost.jpeg.864b1d0976d490e2ac1262980ffa9619.jpegScreenshot_15-9-2025_14745_localhost.jpeg.2f1a4f02d323e14617fd39d53cd1bed4.jpeg

 

what do i need to do for it to show, or is that on the devs?

@Luke I can pm my code if your need me to, but here is the subtitle interface

 

namespace LRCLIBProvider
{
    public class LrcSubtitleProvider : ISubtitleProvider
    {
        private readonly ILogger _logger;

        public LrcSubtitleProvider(ILogger logger)
        {
            _logger = logger;
        }

        public string Name => "LRCLIB Lyrics";

        public IEnumerable<VideoContentType> SupportedMediaTypes => new[]
        {
            VideoContentType.Audio,

        };

        public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken)
        {
            var results = new List<RemoteSubtitleInfo>();

            try
            {
                string artist = request.ProviderIds.TryGetValue("Artist", out var a) ? a : "";
                string title = request.Name ?? "";

                if (string.IsNullOrWhiteSpace(artist) || string.IsNullOrWhiteSpace(title))
                    return results;

                var root = await LyricApiClient.FetchLyricsAsync(
                    artist,
                    title,
                    0,
                    Plugin.Instance?.Configuration.ServerUrl,
                    cancellationToken
                );

                if (root != null)
                {
                    if (!string.IsNullOrWhiteSpace(root.syncedLyrics))
                    {
                        results.Add(new RemoteSubtitleInfo
                        {
                            Id = $"synced:{artist}:{title}",
                            Name = "Synced Lyrics",
                            ProviderName = Name,
                            Language = "en",
                            Format = "lrc"
                        });
                    }

                    if (!string.IsNullOrWhiteSpace(root.plainLyrics))
                    {
                        results.Add(new RemoteSubtitleInfo
                        {
                            Id = $"plain:{artist}:{title}",
                            Name = "Plain Lyrics",
                            ProviderName = Name,
                            Language = "en",
                            Format = "txt"
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.ErrorException("Error in LrcSubtitleProvider.Search", ex);
            }

            return results;
        }

        public async Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken)
        {
            try
            {
                var parts = id.Split(':');
                var type = parts[0];
                var artist = parts.Length > 1 ? parts[1] : "";
                var title = parts.Length > 2 ? parts[2] : "";

                var root = await LyricApiClient.FetchLyricsAsync(
                    artist,
                    title,
                    0,
                    Plugin.Instance?.Configuration.ServerUrl,
                    cancellationToken
                );

                string text = type == "synced" ? root?.syncedLyrics : root?.plainLyrics;
                if (string.IsNullOrWhiteSpace(text))
                    text = "";

                if (Plugin.Instance?.Configuration.DownloadToMediaDirectory == true)
                {
                    try
                    {
                        var filePath = Path.Combine(
                            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                            $"{title}.{(type == "synced" ? "lrc" : "txt")}"
                        );
                        File.WriteAllText(filePath, text, Encoding.UTF8);
                        _logger.Info($"Saved lyrics to {filePath}");
                    }
                    catch (Exception ex)
                    {
                        _logger.ErrorException("Failed to save lyrics file", ex);
                    }
                }

                var bytes = Encoding.UTF8.GetBytes(text);
                return new SubtitleResponse
                {
                    Format = type == "synced" ? "lrc" : "txt",
                    Stream = new MemoryStream(bytes)
                };
            }
            catch (Exception ex)
            {
                _logger.ErrorException("Error in LrcSubtitleProvider.GetSubtitles", ex);
                return new SubtitleResponse
                {
                    Format = "txt",
                    Stream = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty))
                };
            }
        }
    }
}

 

LRCLIBProvider.dll

Edited by mickle026
  • Thanks 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...