aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-codeql-analysis.yml6
-rw-r--r--Directory.Packages.props6
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/km.json132
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json6
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs10
-rw-r--r--Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs32
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs1
-rw-r--r--MediaBrowser.Controller/Providers/SeasonInfo.cs3
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs6
-rw-r--r--MediaBrowser.Model/Plugins/PluginPageInfo.cs4
-rw-r--r--MediaBrowser.Providers/Lyric/LyricScheduledTask.cs171
-rw-r--r--src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs2
16 files changed, 358 insertions, 32 deletions
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 2ca68e5f2..ca3be2665 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2
+ uses: github/codeql-action/init@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2
+ uses: github/codeql-action/autobuild@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2
+ uses: github/codeql-action/analyze@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 8ff5bc366..12fcc26a4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,7 @@
</PropertyGroup>
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies">
- <PackageVersion Include="AsyncKeyedLock" Version="7.0.0" />
+ <PackageVersion Include="AsyncKeyedLock" Version="7.0.1" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
@@ -47,7 +47,7 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
- <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
+ <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.4" />
@@ -80,7 +80,7 @@
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
- <PackageVersion Include="z440.atl.core" Version="5.26.0" />
+ <PackageVersion Include="z440.atl.core" Version="6.1.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 5291999dc..8ed72c208 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Data
/// <summary>
/// Gets the journal size limit. <see href="https://www.sqlite.org/pragma.html#pragma_journal_size_limit" />.
- /// The default (-1) is overriden to prevent unconstrained WAL size, as reported by users.
+ /// The default (-1) is overridden to prevent unconstrained WAL size, as reported by users.
/// </summary>
/// <value>The journal size limit.</value>
protected virtual int? JournalSizeLimit => 134_217_728; // 128MiB
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index 1a69627fa..d1410ef5e 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": "Downloads lyrics for songs",
"TaskDownloadMissingSubtitles": "Download missing subtitles",
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
"TaskOptimizeDatabase": "Optimize database",
diff --git a/Emby.Server.Implementations/Localization/Core/km.json b/Emby.Server.Implementations/Localization/Core/km.json
index 02f9d4443..5d10975f3 100644
--- a/Emby.Server.Implementations/Localization/Core/km.json
+++ b/Emby.Server.Implementations/Localization/Core/km.json
@@ -1,3 +1,133 @@
{
- "Albums": "Albums"
+ "Albums": "អាលប៊ុម",
+ "MessageApplicationUpdatedTo": "ម៉ាស៊ីនមេនៃJellyfinត្រូវបានអាប់ដេតទៅកាន់ {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "ការកំណត់ម៉ាស៊ីនមេ ផ្នែក {0} ត្រូវបានអាប់ដេត",
+ "MessageServerConfigurationUpdated": "ការកំណត់ម៉ាស៊ីនមេត្រូវបានអាប់ដេត",
+ "AppDeviceValues": "កម្មវិធី: {0}, ឧបករណ៍: {1}",
+ "MixedContent": "មាតិកាចម្រុះ",
+ "UserLockedOutWithName": "អ្នកប្រើប្រាស់ {0} ត្រូវ​បាន​ផ្អាក",
+ "Application": "កម្មវិធី",
+ "Artists": "សិល្បករ",
+ "AuthenticationSucceededWithUserName": "{0} បានផ្ទៀងផ្ទាត់ដោយជោគជ័យ",
+ "Books": "សៀវភៅ",
+ "NameSeasonNumber": "រដូវកាលទី {0}",
+ "NotificationOptionPluginInstalled": "Plugin បានដំឡើងរួច",
+ "CameraImageUploadedFrom": "រូបភាពកាមេរ៉ាថ្មីត្រូវបានបង្ហោះពី {0}",
+ "Channels": "ប៉ុស្ត៍",
+ "ChapterNameValue": "ជំពូក {0}",
+ "Collections": "បណ្តុំ",
+ "External": "ខាងក្រៅ",
+ "Default": "លំនាំដើម",
+ "NotificationOptionInstallationFailed": "ការដំឡើងមិនបានសម្រេច",
+ "DeviceOfflineWithName": "{0} បានផ្តាច់",
+ "Folders": "ថតឯកសារ",
+ "DeviceOnlineWithName": "{0} បានភ្ចាប់",
+ "HearingImpaired": "ខ្សោយការស្តាប់",
+ "HomeVideos": "វីឌីអូថតខ្លួនឯង",
+ "Favorites": "ចំណូលចិត្ត",
+ "HeaderFavoriteEpisodes": "ភាគដែលចូលចិត្ត",
+ "Forced": "បង្ខំ",
+ "Genres": "ប្រភេទ",
+ "HeaderFavoriteArtists": "សិល្បករដែលចូលចិត្ត",
+ "NotificationOptionApplicationUpdateAvailable": "កម្មវិធី យើងអាចអាប់ដេតបាន",
+ "NotificationOptionApplicationUpdateInstalled": "កម្មវិធី ដែលបានដំឡើងរួច",
+ "NotificationOptionAudioPlaybackStopped": "ការ​ចាក់សម្លេងបានផ្អាក",
+ "HeaderContinueWatching": "បន្តមើល",
+ "HeaderFavoriteAlbums": "អាល់ប៊ុមដែលចូលចិត្ត",
+ "HeaderFavoriteShows": "រឿងភាគដែលចូលចិត្ត",
+ "NewVersionIsAvailable": "មានជំនាន់ថ្មី ម៉ាស៊ីនមេJellyfin អាចទាញយកបាន.",
+ "HeaderAlbumArtists": "សិល្បករអាល់ប៊ុម",
+ "NotificationOptionCameraImageUploaded": "រូបភាពពីកាំមេរ៉ាបានអាប់ឡូតរួច",
+ "HeaderFavoriteSongs": "ចម្រៀងដែលចូលចិត្ត",
+ "HeaderNextUp": "បន្ទាប់",
+ "HeaderLiveTV": "ទូរទស្សន៍ផ្សាយផ្ទាល់",
+ "Movies": "រឿង",
+ "HeaderRecordingGroups": "ក្រុមនៃការថត",
+ "Music": "តន្ត្រី",
+ "Inherit": "មរតក",
+ "MusicVideos": "វីដេអូតន្ត្រី",
+ "NameInstallFailed": "{0} ការដំឡើងបានបរាជ័យ",
+ "NotificationOptionNewLibraryContent": "មាតិកាថ្មីៗត្រូវបានបន្ថែម",
+ "ItemAddedWithName": "{0} ត្រូវបានបន្ថែមទៅបណ្ណាល័យ",
+ "NameSeasonUnknown": "រដូវកាលមិនច្បាស់លាស់",
+ "ItemRemovedWithName": "{0} ត្រូវបានដកចេញពីបណ្ណាល័យ",
+ "LabelIpAddressValue": "លេខ IP: {0}",
+ "LabelRunningTimeValue": "ពេលវេលាកំពុងដំណើរការ: {0}",
+ "Latest": "ចុងក្រោយ",
+ "NotificationOptionAudioPlayback": "ការ​ចាក់​សំឡេង​បាន​ចាប់ផ្ដើម",
+ "NotificationOptionPluginError": "Plugin មិនដំណើរការ",
+ "NotificationOptionPluginUninstalled": "Plugin បានលុបចេញរួច",
+ "MessageApplicationUpdated": "ម៉ាស៊ីនមេនៃJellyfinត្រូវបានអាប់ដេត",
+ "NotificationOptionPluginUpdateInstalled": "Plugin អាប់ដេតបានដំឡើងរួច",
+ "NotificationOptionUserLockedOut": "អ្នកប្រើប្រាស់ត្រូវបានជាប់គាំង",
+ "NotificationOptionServerRestartRequired": "តម្រូវឱ្យចាប់ផ្ដើមម៉ាស៊ីនមេឡើងវិញ",
+ "Photos": "រូបថត",
+ "Playlists": "បញ្ជីចាក់",
+ "Plugin": "Plugin",
+ "PluginInstalledWithName": "{0} ត្រូវបានដំឡើង",
+ "NotificationOptionTaskFailed": "កិច្ចការដែលបានគ្រោងទុកបានបរាជ័យ",
+ "PluginUpdatedWithName": "{0} ត្រូវបានអាប់ដេត",
+ "NotificationOptionVideoPlayback": "ការចាក់វីដេអូបានចាប់ផ្តើម",
+ "Songs": "ចម្រៀង",
+ "ScheduledTaskStartedWithName": "{0} បានចាប់ផ្តើម",
+ "NotificationOptionVideoPlaybackStopped": "ការ​ចាក់​វីដេអូ​បាន​បញ្ឈប់",
+ "PluginUninstalledWithName": "{0} ត្រូវបានលុបចេញ",
+ "Shows": "រឿងភាគ",
+ "ProviderValue": "អ្នកផ្តល់សេវា: {0}",
+ "SubtitleDownloadFailureFromForItem": "សាប់ថាយថលបានបរាជ័យក្នុងការទាញយកពី {0} នៃ {1}",
+ "Sync": "ធ្វើអោយដំណាលគ្នា",
+ "System": "ប្រព័ន្ធ",
+ "TvShows": "កម្មវិធីទូរទស្សន៍",
+ "ScheduledTaskFailedWithName": "{0} បានបរាជ័យ",
+ "Undefined": "មិនបានកំណត់",
+ "User": "អ្នកប្រើប្រាស់",
+ "UserCreatedWithName": "អ្នកប្រើប្រាស់ {0} ត្រូវបានបង្កើតឡើង",
+ "ServerNameNeedsToBeRestarted": "{0} ចាំបាច់ត្រូវចាប់ផ្តើមឡើងវិញ",
+ "StartupEmbyServerIsLoading": "ម៉ាស៊ីនមេJellyfin កំពុងដំណើរការ. សូមព្យាយាមម្តងទៀតក្នុងពេលឆាប់ៗនេះ.",
+ "UserDeletedWithName": "អ្នកប្រើប្រាស់ {0} ត្រូវបានលុបចេញ",
+ "UserOnlineFromDevice": "{0} បានឃើញអនឡានពី {1}",
+ "UserDownloadingItemWithValues": "{0} កំពុងទាញយក {1}",
+ "UserOfflineFromDevice": "{0} បានផ្តាច់ចេញពី {1}",
+ "UserStartedPlayingItemWithValues": "{0} កំពុងចាក់ {1} នៅលើ {2}",
+ "TaskRefreshChapterImagesDescription": "បង្កើតរូបភាពតូចៗសម្រាប់វីដេអូដែលមានតាមជំពូក.",
+ "VersionNumber": "កំណែ {0}",
+ "TasksMaintenanceCategory": "តំហែរទាំ",
+ "TasksLibraryCategory": "បណ្ណាល័យ",
+ "TasksApplicationCategory": "កម្មវិធី",
+ "TaskCleanActivityLog": "សម្អាតកំណត់ហេតុសកម្មភាព",
+ "UserPasswordChangedWithName": "ពាក្យសម្ងាត់ត្រូវបានផ្លាស់ប្តូរសម្រាប់អ្នកប្រើប្រាស់ {0}",
+ "TaskCleanCache": "សម្អាតបញ្ជីឃ្លាំងសម្ងាត់",
+ "TaskRefreshChapterImages": "ដកស្រង់រូបភាពតាមជំពូក",
+ "UserPolicyUpdatedWithName": "គោលការណ៍អ្នកប្រើប្រាស់ត្រូវបានធ្វើបច្ចុប្បន្នភាពសម្រាប់ {0}",
+ "UserStoppedPlayingItemWithValues": "{0} បានបញ្ចប់ការចាក់ {1} នៅលើ {2}",
+ "ValueHasBeenAddedToLibrary": "{0} ត្រូវបានបញ្ចូលទៅក្នុងបណ្ណាល័យរឿងរបស់អ្នក",
+ "ValueSpecialEpisodeName": "ពិសេស - {0}",
+ "TasksChannelsCategory": "ប៉ុស្តតាមអ៊ីនធឺណិត",
+ "TaskAudioNormalization": "ធ្វើឱ្យមានតន្ត្រីមានសម្លេងស្មើគ្នា",
+ "TaskCleanActivityLogDescription": "លុបកំណត់ហេតុសកម្មភាពចាស់ជាងអាយុដែលបានកំណត់រចនាសម្ព័ន្ធ.",
+ "TaskCleanCacheDescription": "លុបឯកសារឃ្លាំងសម្ងាត់លែងត្រូវការដោយប្រព័ន្ធ.",
+ "TaskRefreshLibraryDescription": "ស្កេនបណ្ណាល័យរឿងរបស់អ្នក សម្រាប់ឯកសារថ្មីៗ និងmetadata ឡើងវិញ.",
+ "TaskCleanLogsDescription": "លុបឯកសារកំណត់ហេតុដែលមានអាយុកាលលើសពី {0} ថ្ងៃ.",
+ "TaskRefreshPeopleDescription": "ធ្វើបច្ចុប្បន្នភាព metadata សម្រាប់តួសម្តែង និងអ្នកដឹកនាំនៅក្នុងបណ្ណាល័យរឿងរបស់អ្នក.",
+ "TaskOptimizeDatabaseDescription": "បង្រួម Database និង Truncate free space. ដំណើរការកិច្ចការនេះ បន្ទាប់ពីការស្កេនបណ្ណាល័យ ឬធ្វើការផ្លាស់ប្តូរផ្សេងទៀត ដែលបញ្ជាក់ថា ការកែប្រែ Database អាចធ្វើឱ្យដំណើរការប្រសើរឡើង.",
+ "TaskRefreshTrickplayImages": "បង្កើតបណ្តុំរូបភាពតាម Trickplay",
+ "TaskRefreshTrickplayImagesDescription": "បង្កើត​ trickplay previews សម្រាប់​វីដេអូ​ក្នុង​បណ្ណាល័យ​ដែល​បានបង្ហាញ.",
+ "TaskKeyframeExtractorDescription": "ស្រង់យកFrame គន្លឹះៗពីវីដេអូ ដើម្បីបង្កើតបញ្ជីចាក់ HLS ច្បាស់លាស់ជាងមុន. កិច្ចការនេះអាចនឹងដំណើរការយូរ.",
+ "FailedLoginAttemptWithUserName": "បរាជ័យក្នុងការព្យាយាមចូលពី {0}",
+ "TaskCleanTranscode": "សម្អាតថតឯកសារ ​Transcode",
+ "TaskRefreshChannelsDescription": "Refreshes ព័ត៌មានបណ្តាញអ៊ីនធឺណិត.",
+ "TaskDownloadMissingSubtitles": "ទាញយកសាប់ថាយថលដែលបាត់",
+ "TaskRefreshChannels": "Refresh ឆានែល",
+ "TaskKeyframeExtractor": "ការញែក Keyframe",
+ "TaskAudioNormalizationDescription": "ស្កែនឯកសារសម្រាប់ធ្វើឱ្យមានតន្ត្រីមានសម្លេងស្មើគ្នា.",
+ "TaskRefreshLibrary": "ស្កេនបណ្ណាល័យរឿង",
+ "TaskCleanLogs": "សម្អាត Log Directory",
+ "TaskRefreshPeople": "Refresh អ្នកប្រើប្រាស់",
+ "TaskUpdatePlugins": "ធ្វើបច្ចុប្បន្នភាព Plugins",
+ "TaskUpdatePluginsDescription": "ទាញយក និងដំឡើងបច្ចុប្បន្នភាពសម្រាប់Plugins ដែលត្រូវបាន Config ដើម្បីធ្វើបច្ចុប្បន្នភាពដោយស្វ័យប្រវត្តិ.",
+ "TaskCleanTranscodeDescription": "លុបឯកសារ Transcode ដែលលើសពីមួយថ្ងៃ.",
+ "TaskDownloadMissingSubtitlesDescription": "ស្វែងរកតាមអ៊ីនធឺណិត សម្រាប់សាប់ថាយថល ដែលបាត់ដោយផ្អែកលើ metadata.",
+ "TaskOptimizeDatabase": "ធ្វើឱ្យ Database ប្រសើរឡើង",
+ "TaskCleanCollectionsAndPlaylistsDescription": "លុបរបស់របរចេញពីបណ្តុំ និងបញ្ជីចាក់ដែលលែងមាន.",
+ "TaskCleanCollectionsAndPlaylists": "សម្អាតបណ្តុំ និងបញ្ជីចាក់"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 4f076b680..0993c1a55 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Volgende",
"HeaderRecordingGroups": "Opnamegroepen",
- "HomeVideos": "Thuis video's",
+ "HomeVideos": "Homevideo's",
"Inherit": "Erven",
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index cd0120fc7..2f52aafa3 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -125,5 +125,9 @@
"TaskKeyframeExtractor": "Extractor de cadre cheie",
"HearingImpaired": "Ascultare Impară",
"TaskRefreshTrickplayImages": "Generează imagini Trickplay",
- "TaskRefreshTrickplayImagesDescription": "Generează previzualizările trickplay pentru videourile din librăriile selectate."
+ "TaskRefreshTrickplayImagesDescription": "Generează previzualizările trickplay pentru videourile din librăriile selectate.",
+ "TaskAudioNormalizationDescription": "Scanează fișiere pentru date necesare normalizării sunetului.",
+ "TaskAudioNormalization": "Normalizare sunet",
+ "TaskCleanCollectionsAndPlaylists": "Curăță colecțiile și listele de redare",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Elimină elementele care nu mai există din colecții și liste de redare."
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 62cb59335..afc93c3a8 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -857,6 +857,16 @@ public class LibraryController : BaseJellyfinApiController
.DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.ToArray();
+ result.LyricFetchers = plugins
+ .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LyricFetcher))
+ .Select(i => new LibraryOptionInfoDto
+ {
+ Name = i.Name,
+ DefaultEnabled = true
+ })
+ .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+
var typeOptions = new List<LibraryTypeOptionsDto>();
foreach (var type in types)
diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
index 78efacd94..53b5e3b7c 100644
--- a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
+++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
@@ -24,6 +24,11 @@ public class LibraryOptionsResultDto
public IReadOnlyList<LibraryOptionInfoDto> SubtitleFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
+ /// Gets or sets the list of lyric fetchers.
+ /// </summary>
+ public IReadOnlyList<LibraryOptionInfoDto> LyricFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
+
+ /// <summary>
/// Gets or sets the type options.
/// </summary>
public IReadOnlyList<LibraryTypeOptionsDto> TypeOptions { get; set; } = Array.Empty<LibraryTypeOptionsDto>();
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index d7ccfd8ae..a07187d2f 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -112,37 +112,31 @@ namespace MediaBrowser.Controller.Entities.Movies
return true;
}
- public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
+ private IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user)
{
- var children = base.GetChildren(user, includeLinkedChildren, query);
-
- if (string.Equals(DisplayOrder, "SortName", StringComparison.OrdinalIgnoreCase))
+ if (!Enum.TryParse<ItemSortBy>(DisplayOrder, out var sortBy))
{
- // Sort by name
- return LibraryManager.Sort(children, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
+ sortBy = ItemSortBy.PremiereDate;
}
- if (string.Equals(DisplayOrder, "PremiereDate", StringComparison.OrdinalIgnoreCase))
+ if (sortBy == ItemSortBy.Default)
{
- // Sort by release date
- return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending).ToList();
+ return items;
}
- // Default sorting
- return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending).ToList();
+ return LibraryManager.Sort(items, user, new[] { sortBy }, SortOrder.Ascending);
+ }
+
+ public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
+ {
+ var children = base.GetChildren(user, includeLinkedChildren, query);
+ return Sort(children, user).ToList();
}
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
var children = base.GetRecursiveChildren(user, query);
-
- if (string.Equals(DisplayOrder, "PremiereDate", StringComparison.OrdinalIgnoreCase))
- {
- // Sort by release date
- return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending).ToList();
- }
-
- return children;
+ return Sort(children, user).ToList();
}
public BoxSetInfo GetLookupInfo()
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 083f12746..181b9be2b 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -238,6 +238,7 @@ namespace MediaBrowser.Controller.Entities.TV
if (series is not null)
{
id.SeriesProviderIds = series.ProviderIds;
+ id.SeriesDisplayOrder = series.DisplayOrder;
}
return id;
diff --git a/MediaBrowser.Controller/Providers/SeasonInfo.cs b/MediaBrowser.Controller/Providers/SeasonInfo.cs
index 1edceb0e4..3af5ec2a3 100644
--- a/MediaBrowser.Controller/Providers/SeasonInfo.cs
+++ b/MediaBrowser.Controller/Providers/SeasonInfo.cs
@@ -1,4 +1,5 @@
#pragma warning disable CA2227, CS1591
+#nullable disable
using System;
using System.Collections.Generic;
@@ -13,5 +14,7 @@ namespace MediaBrowser.Controller.Providers
}
public Dictionary<string, string> SeriesProviderIds { get; set; }
+
+ public string SeriesDisplayOrder { get; set; }
}
}
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index c956bee47..b0f5c2a11 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -13,6 +13,8 @@ namespace MediaBrowser.Model.Configuration
DisabledSubtitleFetchers = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
DisabledLocalMetadataReaders = Array.Empty<string>();
+ DisabledLyricFetchers = Array.Empty<string>();
+ LyricFetcherOrder = Array.Empty<string>();
SkipSubtitlesIfAudioTrackMatches = true;
RequirePerfectSubtitleMatch = true;
@@ -97,6 +99,10 @@ namespace MediaBrowser.Model.Configuration
[DefaultValue(false)]
public bool SaveLyricsWithMedia { get; set; }
+ public string[] DisabledLyricFetchers { get; set; }
+
+ public string[] LyricFetcherOrder { get; set; }
+
public bool AutomaticallyAddToCollection { get; set; }
public EmbeddedSubtitleOptions AllowEmbeddedSubtitles { get; set; }
diff --git a/MediaBrowser.Model/Plugins/PluginPageInfo.cs b/MediaBrowser.Model/Plugins/PluginPageInfo.cs
index f4d83c28b..2ab93ea05 100644
--- a/MediaBrowser.Model/Plugins/PluginPageInfo.cs
+++ b/MediaBrowser.Model/Plugins/PluginPageInfo.cs
@@ -6,12 +6,12 @@ namespace MediaBrowser.Model.Plugins
public class PluginPageInfo
{
/// <summary>
- /// Gets or sets the name.
+ /// Gets or sets the name of the plugin.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
- /// Gets or sets the display name.
+ /// Gets or sets the display name of the plugin.
/// </summary>
public string? DisplayName { get; set; }
diff --git a/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs b/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs
new file mode 100644
index 000000000..7d02a7794
--- /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;
+
+/// <summary>
+/// Task to download lyrics.
+/// </summary>
+public class LyricScheduledTask : 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<LyricScheduledTask> _logger;
+ private readonly ILocalizationManager _localizationManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LyricScheduledTask"/> class.
+ /// </summary>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{DownloaderScheduledTask}"/> interface.</param>
+ /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ public LyricScheduledTask(
+ ILibraryManager libraryManager,
+ ILyricManager lyricManager,
+ ILogger<LyricScheduledTask> logger,
+ ILocalizationManager localizationManager)
+ {
+ _libraryManager = libraryManager;
+ _lyricManager = lyricManager;
+ _logger = logger;
+ _localizationManager = localizationManager;
+ }
+
+ /// <inheritdoc />
+ public string Name => _localizationManager.GetLocalizedString("TaskDownloadMissingLyrics");
+
+ /// <inheritdoc />
+ public string Key => "DownloadLyrics";
+
+ /// <inheritdoc />
+ public string Description => _localizationManager.GetLocalizedString("TaskDownloadMissingLyricsDescription");
+
+ /// <inheritdoc />
+ public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory");
+
+ /// <inheritdoc />
+ public async Task ExecuteAsync(IProgress<double> 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<Audio>())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ try
+ {
+ if (audioItem.GetMediaStreams().All(s => s.Type != MediaStreamType.Lyric))
+ {
+ _logger.LogDebug("Searching for lyrics for {Path}", audioItem.Path);
+ var lyricResults = await _lyricManager.SearchLyricsAsync(
+ new LyricSearchRequest
+ {
+ MediaPath = audioItem.Path,
+ SongName = audioItem.Name,
+ AlbumName = audioItem.Album,
+ ArtistNames = audioItem.GetAllArtists().ToList(),
+ Duration = audioItem.RunTimeTicks,
+ IsAutomated = true,
+ DisabledLyricFetchers = libraryOptions.DisabledLyricFetchers,
+ LyricFetcherOrder = libraryOptions.LyricFetcherOrder
+ },
+ cancellationToken)
+ .ConfigureAwait(false);
+
+ if (lyricResults.Count != 0)
+ {
+ _logger.LogDebug("Saving lyrics for {Path}", audioItem.Path);
+ await _lyricManager.DownloadLyricsAsync(
+ audioItem,
+ libraryOptions,
+ lyricResults[0].Id,
+ cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error downloading lyrics for {Path}", audioItem.Path);
+ }
+
+ completed++;
+ progress.Report(100d * completed / totalCount);
+ }
+
+ startIndex += QueryPageLimit;
+ previousCount = audioItems.Count;
+
+ } while (previousCount > 0);
+ }
+
+ progress.Report(100);
+ }
+
+ /// <inheritdoc />
+ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+ {
+ return
+ [
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromHours(24).Ticks
+ }
+ ];
+ }
+}
diff --git a/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs b/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
index cecc363f0..7dc30f727 100644
--- a/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
@@ -167,7 +167,7 @@ namespace Jellyfin.LiveTv.Listings
Overview = program.Description,
ProductionYear = program.CopyrightDate?.Year,
SeasonNumber = program.Episode.Series,
- IsSeries = program.Episode.Series is not null,
+ IsSeries = program.Episode.Episode is not null,
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
IsPremiere = program.Premiere is not null,
IsKids = programCategories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),