diff options
15 files changed, 277 insertions, 82 deletions
diff --git a/Directory.Packages.props b/Directory.Packages.props index 294cb45b1..09524549e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,7 @@ <PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" /> <PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" /> - <PackageVersion Include="coverlet.collector" Version="8.0.0" /> + <PackageVersion Include="coverlet.collector" Version="8.0.1" /> <PackageVersion Include="Diacritics" Version="4.1.4" /> <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" /> <PackageVersion Include="DotNet.Glob" Version="3.1.3" /> diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 1e885aad6..3ee1c757f 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books { public class BookResolver : ItemResolver<Book> { - private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" }; + private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf", ".m4b", ".m4a", ".aac", ".flac", ".mp3", ".opus" }; protected override Book Resolve(ItemResolveArgs args) { diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 534c64e93..dbbe2cbd0 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -16,14 +16,14 @@ "Folders": "Mappen", "Genres": "Genres", "HeaderAlbumArtists": "Albumartiesten", - "HeaderContinueWatching": "Verderkijken", + "HeaderContinueWatching": "Verder kijken", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteEpisodes": "Favoriete afleveringen", "HeaderFavoriteShows": "Favoriete series", "HeaderFavoriteSongs": "Favoriete nummers", "HeaderLiveTV": "Live-tv", - "HeaderNextUp": "Als volgende", + "HeaderNextUp": "Volgende", "HeaderRecordingGroups": "Opnamegroepen", "HomeVideos": "Homevideo's", "Inherit": "Erven", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 23acd3c53..2393e21b1 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -76,7 +76,7 @@ "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}", "Sync": "Synk", "System": "System", - "TvShows": "TV-serier", + "TvShows": "Tv-serier", "User": "Användare", "UserCreatedWithName": "Användaren {0} har skapats", "UserDeletedWithName": "Användaren {0} har tagits bort", diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 98dada84a..a42a33b1d 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -1,17 +1,17 @@ { "Albums": "專輯", - "AppDeviceValues": "程式:{0},設備:{1}", + "AppDeviceValues": "程式:{0},裝置:{1}", "Application": "應用程式", "Artists": "藝人", - "AuthenticationSucceededWithUserName": "成功授權 {0}", + "AuthenticationSucceededWithUserName": "{0} 成功通過驗證", "Books": "書籍", - "CameraImageUploadedFrom": "{0} 成功上傳一張新照片", + "CameraImageUploadedFrom": "{0} 已經成功上傳咗一張新相", "Channels": "頻道", "ChapterNameValue": "第 {0} 章", "Collections": "系列", - "DeviceOfflineWithName": "{0} 已中斷連接", - "DeviceOnlineWithName": "{0} 已連接", - "FailedLoginAttemptWithUserName": "{0} 登入失敗", + "DeviceOfflineWithName": "{0} 斷開咗連接", + "DeviceOnlineWithName": "{0} 連接咗", + "FailedLoginAttemptWithUserName": "來自 {0} 嘅登入嘗試失敗咗", "Favorites": "我的最愛", "Folders": "資料夾", "Genres": "風格", @@ -27,15 +27,15 @@ "HeaderRecordingGroups": "錄製組", "HomeVideos": "家庭影片", "Inherit": "繼承", - "ItemAddedWithName": "{0} 已被加入至媒體庫", - "ItemRemovedWithName": "{0} 已從媒體庫移除", + "ItemAddedWithName": "{0} 經已加咗入媒體庫", + "ItemRemovedWithName": "{0} 經已由媒體庫移除咗", "LabelIpAddressValue": "IP 地址:{0}", - "LabelRunningTimeValue": "運作時間:{0}", + "LabelRunningTimeValue": "運行時間:{0}", "Latest": "最新", - "MessageApplicationUpdated": "Jellyfin 已被更新", - "MessageApplicationUpdatedTo": "Jellyfin 已被更新至 {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已被更新", - "MessageServerConfigurationUpdated": "已更新伺服器設定", + "MessageApplicationUpdated": "Jellyfin 經已更新咗", + "MessageApplicationUpdatedTo": "Jellyfin 已經更新到 {0} 版本", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定「{0}」已經更新咗", + "MessageServerConfigurationUpdated": "伺服器設定經已更新咗", "MixedContent": "混合內容", "Movies": "電影", "Music": "音樂", @@ -43,99 +43,99 @@ "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知的季度", - "NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。", - "NotificationOptionApplicationUpdateAvailable": "有可用的更新", - "NotificationOptionApplicationUpdateInstalled": "完成更新應用程式", - "NotificationOptionAudioPlayback": "播放音訊", - "NotificationOptionAudioPlaybackStopped": "停止播放音訊", - "NotificationOptionCameraImageUploaded": "相片上傳", + "NewVersionIsAvailable": "有新版本嘅 Jellyfin 可以下載。", + "NotificationOptionApplicationUpdateAvailable": "有得更新應用程式", + "NotificationOptionApplicationUpdateInstalled": "應用程式更新好咗", + "NotificationOptionAudioPlayback": "開始播放音訊", + "NotificationOptionAudioPlaybackStopped": "停咗播放音訊", + "NotificationOptionCameraImageUploaded": "相機相片上傳咗", "NotificationOptionInstallationFailed": "安裝失敗", - "NotificationOptionNewLibraryContent": "新增媒體", + "NotificationOptionNewLibraryContent": "加咗新內容", "NotificationOptionPluginError": "插件錯誤", "NotificationOptionPluginInstalled": "安裝插件", "NotificationOptionPluginUninstalled": "解除安裝插件", - "NotificationOptionPluginUpdateInstalled": "完成更新插件", - "NotificationOptionServerRestartRequired": "伺服器需要重啟", - "NotificationOptionTaskFailed": "排程工作執行失敗", - "NotificationOptionUserLockedOut": "封鎖用戶", - "NotificationOptionVideoPlayback": "播放影片", - "NotificationOptionVideoPlaybackStopped": "停止播放影片", + "NotificationOptionPluginUpdateInstalled": "插件更新好咗", + "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", + "NotificationOptionTaskFailed": "排程工作失敗", + "NotificationOptionUserLockedOut": "用戶被鎖定咗", + "NotificationOptionVideoPlayback": "開始播放影片", + "NotificationOptionVideoPlaybackStopped": "停咗播放影片", "Photos": "相片", "Playlists": "播放清單", "Plugin": "插件", - "PluginInstalledWithName": "已安裝 {0}", - "PluginUninstalledWithName": "已移除 {0}", - "PluginUpdatedWithName": "已更新 {0}", + "PluginInstalledWithName": "裝好咗 {0}", + "PluginUninstalledWithName": "剷走咗 {0}", + "PluginUpdatedWithName": "更新好咗 {0}", "ProviderValue": "提供者:{0}", "ScheduledTaskFailedWithName": "{0} 執行失敗", "ScheduledTaskStartedWithName": "開始執行 {0}", - "ServerNameNeedsToBeRestarted": "{0} 需要重啟", + "ServerNameNeedsToBeRestarted": "{0} 需要重新啟動", "Shows": "節目", "Songs": "歌曲", - "StartupEmbyServerIsLoading": "正在載入 Jellyfin,請稍後再試。", - "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", + "StartupEmbyServerIsLoading": "Jellyfin 伺服器載入緊,請稍後再試。", + "SubtitleDownloadFailureFromForItem": "經 {0} 下載 {1} 嘅字幕失敗咗", "Sync": "同步", "System": "系統", "TvShows": "電視節目", "User": "用戶", - "UserCreatedWithName": "建立新用戶 {0}", - "UserDeletedWithName": "用戶 {0} 已被移除", - "UserDownloadingItemWithValues": "{0} 正在下載 {1}", - "UserLockedOutWithName": "用戶 {0} 已被封鎖", - "UserOfflineFromDevice": "{0} 終止了 {1} 的連接", - "UserOnlineFromDevice": "{0} 從 {1} 連線", - "UserPasswordChangedWithName": "{0} 的密碼已被更改", - "UserPolicyUpdatedWithName": "{0} 嘅用戶權限已經更新咗", - "UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}", - "UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}", - "ValueHasBeenAddedToLibrary": "{0} 已被加入至你的媒體庫", - "ValueSpecialEpisodeName": "特典 - {0}", + "UserCreatedWithName": "經已建立咗新用戶 {0}", + "UserDeletedWithName": "用戶 {0} 已經被刪除", + "UserDownloadingItemWithValues": "{0} 下載緊 {1}", + "UserLockedOutWithName": "用戶 {0} 經已被鎖定", + "UserOfflineFromDevice": "{0} 經已由 {1} 斷開咗連線", + "UserOnlineFromDevice": "{0} 正喺 {1} 連線", + "UserPasswordChangedWithName": "用戶 {0} 嘅密碼已經更改咗", + "UserPolicyUpdatedWithName": "用戶 {0} 嘅權限已經更新咗", + "UserStartedPlayingItemWithValues": "{0} 正喺 {2} 播緊 {1}", + "UserStoppedPlayingItemWithValues": "{0} 已經喺 {2} 停止播放 {1}", + "ValueHasBeenAddedToLibrary": "{0} 已經成功加入咗你嘅媒體庫", + "ValueSpecialEpisodeName": "特別篇 - {0}", "VersionNumber": "版本 {0}", - "TaskDownloadMissingSubtitles": "下載欠缺字幕", + "TaskDownloadMissingSubtitles": "下載漏咗嘅字幕", "TaskUpdatePlugins": "更新插件", "TasksApplicationCategory": "應用程式", - "TaskRefreshLibraryDescription": "掃描媒體庫以加入新增的檔案及重新載入元數據。", + "TaskRefreshLibraryDescription": "掃描媒體庫嚟搵新檔案,同時重新載入元數據。", "TasksMaintenanceCategory": "維護", - "TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在網上搜尋欠缺的字幕。", - "TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。", + "TaskDownloadMissingSubtitlesDescription": "根據元數據設定,喺網上幫你搵返啲欠缺嘅字幕。", + "TaskRefreshChannelsDescription": "重新整理網上頻道嘅資訊。", "TaskRefreshChannels": "重新載入頻道", - "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。", - "TaskCleanTranscode": "清理轉碼檔資料夾", - "TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。", - "TaskRefreshPeopleDescription": "更新你的媒體中有關的演員和導演的元數據。", - "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。", - "TaskCleanLogs": "清理紀錄檔資料夾", + "TaskCleanTranscodeDescription": "自動刪除超過一日嘅轉碼檔案。", + "TaskCleanTranscode": "清理轉碼資料夾", + "TaskUpdatePluginsDescription": "自動幫嗰啲設咗要自動更新嘅插件進行下載同安裝。", + "TaskRefreshPeopleDescription": "更新媒體庫入面演員同導演嘅元數據。", + "TaskCleanLogsDescription": "自動刪除超過 {0} 日嘅紀錄檔。", + "TaskCleanLogs": "清理日誌資料夾", "TaskRefreshLibrary": "掃描媒體庫", - "TaskRefreshChapterImagesDescription": "幫有章節嘅影片整縮圖。", - "TaskRefreshChapterImages": "提取章節圖像", - "TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。", - "TaskCleanCache": "清理緩存資料夾", + "TaskRefreshChapterImagesDescription": "幫有章節嘅影片整返啲章節縮圖。", + "TaskRefreshChapterImages": "擷取章節圖片", + "TaskCleanCacheDescription": "刪除系統已經唔再需要嘅快取檔案。", + "TaskCleanCache": "清理快取(Cache)資料夾", "TasksChannelsCategory": "網絡頻道", "TasksLibraryCategory": "媒體庫", "TaskRefreshPeople": "重新載入人物", - "TaskCleanActivityLog": "清理活動記錄", + "TaskCleanActivityLog": "清理活動紀錄", "Undefined": "未定義", "Forced": "強制", "Default": "預設", "TaskOptimizeDatabaseDescription": "壓縮數據庫並釋放剩餘空間。喺掃描媒體庫或者做咗一啲會修改數據庫嘅操作之後行呢個任務,或者可以提升效能。", "TaskOptimizeDatabase": "最佳化數據庫", - "TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。", + "TaskCleanActivityLogDescription": "刪除超過設定日期嘅活動記錄。", "TaskKeyframeExtractorDescription": "提取關鍵影格(Keyframe)嚟建立更準確嘅 HLS 播放列表。呢個任務可能要行好耐。", "TaskKeyframeExtractor": "關鍵影格提取器", "External": "外部", "HearingImpaired": "聽力障礙", - "TaskRefreshTrickplayImages": "建立 Trickplay 圖像", - "TaskRefreshTrickplayImagesDescription": "幫已啟用功能嘅媒體庫影片整快轉預覽圖。", + "TaskRefreshTrickplayImages": "產生搜畫預覽圖", + "TaskRefreshTrickplayImagesDescription": "爲已啟用功能嘅媒體庫影片製作快轉預覽圖。", "TaskExtractMediaSegments": "掃描媒體分段資訊", - "TaskExtractMediaSegmentsDescription": "從允許MediaSegment 功能的插件中獲取媒體片段。", - "TaskDownloadMissingLyrics": "下載欠缺歌詞", - "TaskDownloadMissingLyricsDescription": "下載歌詞", - "TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單", + "TaskExtractMediaSegmentsDescription": "從支援 MediaSegment 功能嘅插件入面提取媒體片段。", + "TaskDownloadMissingLyrics": "下載缺失嘅歌詞", + "TaskDownloadMissingLyricsDescription": "幫啲歌下載歌詞", + "TaskCleanCollectionsAndPlaylists": "清理媒體系列(Collections)同埋播放清單", "TaskAudioNormalization": "音訊同等化", - "TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。", - "TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。", - "TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。", - "TaskMoveTrickplayImages": "轉移 Trickplay 影像位置", - "CleanupUserDataTask": "用戶資料清理工作", + "TaskAudioNormalizationDescription": "掃描檔案入面嘅音訊標准化(Audio Normalization)數據。", + "TaskCleanCollectionsAndPlaylistsDescription": "自動清理資料庫同播放清單入面已經唔存在嘅項目。", + "TaskMoveTrickplayImagesDescription": "根據媒體庫設定,將現有嘅 Trickplay(快轉預覽)檔案搬去對應位置。", + "TaskMoveTrickplayImages": "搬移快轉預覽圖嘅位置", + "CleanupUserDataTask": "清理用戶資料嘅任務", "CleanupUserDataTaskDescription": "從用戶數據入面清除嗰啲已經被刪除咗超過 90 日嘅媒體相關資料。" } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c7b11f47d..9ebaef171 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -85,6 +85,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1); private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0); private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1); + private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0); private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled); @@ -1566,14 +1567,15 @@ namespace MediaBrowser.Controller.MediaEncoding int bitrate = state.OutputVideoBitrate.Value; - // Bit rate under 1000k is not allowed in h264_qsv + // Bit rate under 1000k is not allowed in h264_qsv. if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { bitrate = Math.Max(bitrate, 1000); } - // Currently use the same buffer size for all encoders - int bufsize = bitrate * 2; + // Currently use the same buffer size for all non-QSV encoders. + // Use long arithmetic to prevent int32 overflow for very high bitrate values. + int bufsize = (int)Math.Min((long)bitrate * 2, int.MaxValue); if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase)) { @@ -1603,7 +1605,13 @@ namespace MediaBrowser.Controller.MediaEncoding // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene changes - return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {bitrate + 1} -rc_init_occupancy {bitrate * 2} -bufsize {bitrate * 4}"); + // Use long arithmetic and clamp to int.MaxValue to prevent int32 overflow + // (e.g. bitrate * 4 wraps to a negative value for bitrates above ~537 million) + int qsvMaxrate = (int)Math.Min((long)bitrate + 1, int.MaxValue); + int qsvInitOcc = (int)Math.Min((long)bitrate * 2, int.MaxValue); + int qsvBufsize = (int)Math.Min((long)bitrate * 4, int.MaxValue); + + return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {qsvMaxrate} -rc_init_occupancy {qsvInitOcc} -bufsize {qsvBufsize}"); } if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase) @@ -7226,8 +7234,10 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion); } + int readrate = 0; if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp) { + readrate = 1; inputModifier += " -re"; } else if (encodingOptions.EnableSegmentDeletion @@ -7238,7 +7248,15 @@ namespace MediaBrowser.Controller.MediaEncoding { // Set an input read rate limit 10x for using SegmentDeletion with stream-copy // to prevent ffmpeg from exiting prematurely (due to fast drive) - inputModifier += " -readrate 10"; + readrate = 10; + inputModifier += $" -readrate {readrate}"; + } + + // Set a larger catchup value to revert to the old behavior, + // otherwise, remuxing might stall due to this new option + if (readrate > 0 && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateCatchupOption) + { + inputModifier += $" -readrate_catchup {readrate * 100}"; } var flags = new List<string>(); diff --git a/MediaBrowser.Providers/Books/Isbn/ISBNExternalId.cs b/MediaBrowser.Providers/Books/Isbn/ISBNExternalId.cs new file mode 100644 index 000000000..a86275d5a --- /dev/null +++ b/MediaBrowser.Providers/Books/Isbn/ISBNExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Books.Isbn +{ + /// <inheritdoc /> + public class IsbnExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "ISBN"; + + /// <inheritdoc /> + public string Key => "ISBN"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Book; + } +} diff --git a/MediaBrowser.Providers/Books/Isbn/ISBNExternalUrlProvider.cs b/MediaBrowser.Providers/Books/Isbn/ISBNExternalUrlProvider.cs new file mode 100644 index 000000000..9d7b1ff20 --- /dev/null +++ b/MediaBrowser.Providers/Books/Isbn/ISBNExternalUrlProvider.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Books.Isbn; + +/// <inheritdoc/> +public class IsbnExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "ISBN"; + + /// <inheritdoc /> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId("ISBN", out var externalId)) + { + if (item is Book) + { + yield return $"https://search.worldcat.org/search?q=bn:{externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs new file mode 100644 index 000000000..8cbd1f89a --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.ComicVine +{ + /// <inheritdoc /> + public class ComicVineExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "Comic Vine"; + + /// <inheritdoc /> + public string Key => "ComicVine"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Book; + } +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs new file mode 100644 index 000000000..912239917 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.ComicVine; + +/// <inheritdoc/> +public class ComicVineExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "Comic Vine"; + + /// <inheritdoc /> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId("ComicVine", out var externalId)) + { + switch (item) + { + case Person: + case Book: + yield return $"https://comicvine.gamespot.com/{externalId}"; + break; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs b/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs new file mode 100644 index 000000000..26b8e1138 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.ComicVine +{ + /// <inheritdoc /> + public class ComicVinePersonExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "Comic Vine"; + + /// <inheritdoc /> + public string Key => "ComicVine"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Person; + } +} diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs new file mode 100644 index 000000000..02d3b3697 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.GoogleBooks +{ + /// <inheritdoc /> + public class GoogleBooksExternalId : IExternalId + { + /// <inheritdoc /> + public string ProviderName => "Google Books"; + + /// <inheritdoc /> + public string Key => "GoogleBooks"; + + /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Book; + } +} diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs new file mode 100644 index 000000000..95047ee83 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.GoogleBooks; + +/// <inheritdoc/> +public class GoogleBooksExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc /> + public string Name => "Google Books"; + + /// <inheritdoc /> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId("GoogleBooks", out var externalId)) + { + if (item is Book) + { + yield return $"https://books.google.com/books?id={externalId}"; + } + } + } +} diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 0217bded1..0757155aa 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -547,7 +547,7 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString("aspectratio", hasAspectRatio.AspectRatio); } - if (item.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbCollection)) + if (item.TryGetProviderId(MetadataProvider.TmdbCollection, out var tmdbCollection)) { writer.WriteElementString("collectionnumber", tmdbCollection); writtenProviderIds.Add(MetadataProvider.TmdbCollection.ToString()); diff --git a/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs b/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs index be7ff5297..d877a0d12 100644 --- a/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs +++ b/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs @@ -156,6 +156,13 @@ namespace Jellyfin.LiveTv.IO if (mediaSource.ReadAtNativeFramerate) { inputModifier += " -re"; + + // Set a larger catchup value to revert to the old behavior, + // otherwise, remuxing might stall due to this new option + if (_mediaEncoder.EncoderVersion >= new Version(8, 0)) + { + inputModifier += " -readrate_catchup 100"; + } } if (mediaSource.RequiresLooping) |
