diff options
| -rw-r--r-- | .github/workflows/ci-codeql-analysis.yml | 6 | ||||
| -rw-r--r-- | Directory.Packages.props | 6 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 2 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/en-US.json | 2 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/km.json | 132 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/nl.json | 2 | ||||
| -rw-r--r-- | Emby.Server.Implementations/Localization/Core/ro.json | 6 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/LibraryController.cs | 10 | ||||
| -rw-r--r-- | Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs | 5 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 32 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/TV/Season.cs | 1 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Providers/SeasonInfo.cs | 3 | ||||
| -rw-r--r-- | MediaBrowser.Model/Configuration/LibraryOptions.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.Model/Plugins/PluginPageInfo.cs | 4 | ||||
| -rw-r--r-- | MediaBrowser.Providers/Lyric/LyricScheduledTask.cs | 171 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs | 2 |
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)), |
