diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index 1a69627fa..f8f962ee0 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -122,6 +122,8 @@
"TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
"TaskRefreshChannels": "Refresh Channels",
"TaskRefreshChannelsDescription": "Refreshes internet channel information.",
+ "TaskDownloadMissingLyrics": "Download missing lyrics",
+ "TaskDownloadMissingLyricsDescription": "Task to download missing lyrics",
"TaskDownloadMissingSubtitles": "Download missing subtitles",
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
"TaskOptimizeDatabase": "Optimize database",
diff --git a/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs b/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs
new file mode 100644
index 000000000..184e8495b
--- /dev/null
+++ b/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Lyrics;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Lyrics;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Providers.Lyric;
+
+///
+/// Task to download lyrics.
+///
+public class LyricDownloadTask : IScheduledTask
+{
+ private const int QueryPageLimit = 100;
+
+ private static readonly BaseItemKind[] _itemKinds = [BaseItemKind.Audio];
+ private static readonly MediaType[] _mediaTypes = [MediaType.Audio];
+ private static readonly SourceType[] _sourceTypes = [SourceType.Library];
+ private static readonly DtoOptions _dtoOptions = new(false);
+
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILyricManager _lyricManager;
+ private readonly ILogger _logger;
+ private readonly ILocalizationManager _localizationManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public LyricDownloadTask(
+ ILibraryManager libraryManager,
+ ILyricManager lyricManager,
+ ILogger logger,
+ ILocalizationManager localizationManager)
+ {
+ _libraryManager = libraryManager;
+ _lyricManager = lyricManager;
+ _logger = logger;
+ _localizationManager = localizationManager;
+ }
+
+ ///
+ public string Name => _localizationManager.GetLocalizedString("TaskDownloadMissingLyrics");
+
+ ///
+ public string Key => "DownloadLrcLibLyrics";
+
+ ///
+ public string Description => _localizationManager.GetLocalizedString("TaskDownloadMissingLyricsDescription");
+
+ ///
+ public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory");
+
+ ///
+ public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
+ {
+ var totalCount = _libraryManager.GetCount(new InternalItemsQuery
+ {
+ Recursive = true,
+ IsVirtualItem = false,
+ IncludeItemTypes = _itemKinds,
+ DtoOptions = _dtoOptions,
+ MediaTypes = _mediaTypes,
+ SourceTypes = _sourceTypes
+ });
+
+ var completed = 0;
+
+ foreach (var library in _libraryManager.RootFolder.Children.ToList())
+ {
+ var libraryOptions = _libraryManager.GetLibraryOptions(library);
+ var itemQuery = new InternalItemsQuery
+ {
+ Recursive = true,
+ IsVirtualItem = false,
+ IncludeItemTypes = _itemKinds,
+ DtoOptions = _dtoOptions,
+ MediaTypes = _mediaTypes,
+ SourceTypes = _sourceTypes,
+ Limit = QueryPageLimit,
+ Parent = library
+ };
+
+ int previousCount;
+ var startIndex = 0;
+ do
+ {
+ itemQuery.StartIndex = startIndex;
+ var audioItems = _libraryManager.GetItemList(itemQuery);
+
+ foreach (var audioItem in audioItems.OfType