aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs29
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs199
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs34
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs2
-rw-r--r--MediaBrowser.Providers/Manager/RefreshResult.cs2
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj13
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs222
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs3
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs11
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs3
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs3
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs104
-rw-r--r--MediaBrowser.Providers/Movies/ImdbExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Music/AlbumInfoExtensions.cs12
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs4
-rw-r--r--MediaBrowser.Providers/Music/ImvdbId.cs2
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs1
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs19
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs1
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs37
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/Plugin.cs1
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs44
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs30
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html77
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs44
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs20
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs60
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs26
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs24
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs41
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs133
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs7
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs11
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs28
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/Zap2ItExternalId.cs2
76 files changed, 845 insertions, 525 deletions
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 7d259a9d3..d2a3344be 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -29,8 +31,6 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
public class ImageSaver
{
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
/// <summary>
/// The _config.
/// </summary>
@@ -263,8 +263,10 @@ namespace MediaBrowser.Providers.Manager
_fileSystem.SetAttributes(path, false, false);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ fileStreamOptions.PreallocationSize = source.Length;
+ await using (var fs = new FileStream(path, fileStreamOptions))
{
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
@@ -377,7 +379,7 @@ namespace MediaBrowser.Providers.Manager
var seasonMarker = season.IndexNumber.Value == 0
? "-specials"
- : season.IndexNumber.Value.ToString("00", UsCulture);
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
var imageFilename = "season" + seasonMarker + "-landscape" + extension;
@@ -400,7 +402,7 @@ namespace MediaBrowser.Providers.Manager
var seasonMarker = season.IndexNumber.Value == 0
? "-specials"
- : season.IndexNumber.Value.ToString("00", UsCulture);
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
var imageFilename = "season" + seasonMarker + "-banner" + extension;
@@ -437,9 +439,6 @@ namespace MediaBrowser.Providers.Manager
case ImageType.Backdrop:
filename = GetBackdropSaveFilename(item.GetImages(type), "backdrop", "backdrop", imageIndex);
break;
- case ImageType.Screenshot:
- filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex);
- break;
default:
filename = type.ToString().ToLowerInvariant();
break;
@@ -495,12 +494,12 @@ namespace MediaBrowser.Providers.Manager
var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList();
var current = 1;
- while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase))
+ while (filenames.Contains(numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase))
{
current++;
}
- return numberedIndexPrefix + current.ToString(UsCulture);
+ return numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
@@ -539,7 +538,7 @@ namespace MediaBrowser.Providers.Manager
var seasonMarker = season.IndexNumber.Value == 0
? "-specials"
- : season.IndexNumber.Value.ToString("00", UsCulture);
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
var imageFilename = "season" + seasonMarker + "-fanart" + extension;
@@ -556,7 +555,7 @@ namespace MediaBrowser.Providers.Manager
if (item.IsInMixedFolder)
{
- return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
+ return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(CultureInfo.InvariantCulture), extension) };
}
var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex);
@@ -568,7 +567,7 @@ namespace MediaBrowser.Providers.Manager
if (EnableExtraThumbsDuplication)
{
- list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension));
+ list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(CultureInfo.InvariantCulture) + extension));
}
return list.ToArray();
@@ -582,7 +581,7 @@ namespace MediaBrowser.Providers.Manager
var seasonMarker = season.IndexNumber.Value == 0
? "-specials"
- : season.IndexNumber.Value.ToString("00", UsCulture);
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
var imageFilename = "season" + seasonMarker + "-poster" + extension;
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index cc4c75d7d..1022a3fae 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -1,8 +1,7 @@
-#pragma warning disable CA1002, CS1591
+#nullable disable
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
@@ -24,6 +23,9 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Manager
{
+ /// <summary>
+ /// Utilities for managing images attached to items.
+ /// </summary>
public class ItemImageProvider
{
private readonly ILogger _logger;
@@ -46,6 +48,12 @@ namespace MediaBrowser.Providers.Manager
ImageType.Thumb
};
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ItemImageProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="providerManager">The provider manager for interacting with provider image references.</param>
+ /// <param name="fileSystem">The filesystem.</param>
public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
{
_logger = logger;
@@ -53,6 +61,13 @@ namespace MediaBrowser.Providers.Manager
_fileSystem = fileSystem;
}
+ /// <summary>
+ /// Verifies existing images have valid paths and adds any new local images provided.
+ /// </summary>
+ /// <param name="item">The <see cref="BaseItem"/> to validate images for.</param>
+ /// <param name="providers">The providers to use, must include <see cref="ILocalImageProvider"/>(s) for local scanning.</param>
+ /// <param name="directoryService">The directory service for <see cref="ILocalImageProvider"/>s to use.</param>
+ /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService)
{
var hasChanges = false;
@@ -72,6 +87,15 @@ namespace MediaBrowser.Providers.Manager
return hasChanges;
}
+ /// <summary>
+ /// Refreshes from the providers according to the given options.
+ /// </summary>
+ /// <param name="item">The <see cref="BaseItem"/> to gather images for.</param>
+ /// <param name="libraryOptions">The library options.</param>
+ /// <param name="providers">The providers to query for images.</param>
+ /// <param name="refreshOptions">The refresh options.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The refresh result.</returns>
public async Task<RefreshResult> RefreshImages(
BaseItem item,
LibraryOptions libraryOptions,
@@ -79,14 +103,10 @@ namespace MediaBrowser.Providers.Manager
ImageRefreshOptions refreshOptions,
CancellationToken cancellationToken)
{
+ var oldBackdropImages = Array.Empty<ItemImageInfo>();
if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
{
- ClearImages(item, ImageType.Backdrop);
- }
-
- if (refreshOptions.IsReplacingImage(ImageType.Screenshot))
- {
- ClearImages(item, ImageType.Screenshot);
+ oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray();
}
var result = new RefreshResult { UpdateType = ItemUpdateType.None };
@@ -94,16 +114,15 @@ namespace MediaBrowser.Providers.Manager
var typeName = item.GetType().Name;
var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
- // In order to avoid duplicates, only download these if there are none already
- var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop);
- var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot);
+ // track library limits, adding buffer to allow lazy replacing of current images
+ var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length;
var downloadedImages = new List<ImageType>();
foreach (var provider in providers)
{
if (provider is IRemoteImageProvider remoteProvider)
{
- await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
+ await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
continue;
}
@@ -113,11 +132,17 @@ namespace MediaBrowser.Providers.Manager
}
}
+ // only delete existing multi-images if new ones were added
+ if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
+ {
+ PruneImages(item, oldBackdropImages);
+ }
+
return result;
}
/// <summary>
- /// Refreshes from provider.
+ /// Refreshes from a dynamic provider.
/// </summary>
private async Task RefreshFromProvider(
BaseItem item,
@@ -152,19 +177,20 @@ namespace MediaBrowser.Providers.Manager
if (response.Protocol == MediaProtocol.Http)
{
_logger.LogDebug("Setting image url into item {0}", item.Id);
+ var index = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0;
item.SetImage(
new ItemImageInfo
{
Path = response.Path,
Type = imageType
},
- 0);
+ index);
}
else
{
var mimeType = MimeTypes.GetMimeType(response.Path);
- var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ var stream = AsyncFile.OpenRead(response.Path);
await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
}
@@ -205,9 +231,8 @@ namespace MediaBrowser.Providers.Manager
/// <param name="images">The images.</param>
/// <param name="savedOptions">The saved options.</param>
/// <param name="backdropLimit">The backdrop limit.</param>
- /// <param name="screenshotLimit">The screenshot limit.</param>
/// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns>
- private bool ContainsImages(BaseItem item, List<ImageType> images, TypeOptions savedOptions, int backdropLimit, int screenshotLimit)
+ private bool ContainsImages(BaseItem item, List<ImageType> images, TypeOptions savedOptions, int backdropLimit)
{
// Using .Any causes the creation of a DisplayClass aka. variable capture
for (var i = 0; i < _singularImages.Length; i++)
@@ -224,23 +249,17 @@ namespace MediaBrowser.Providers.Manager
return false;
}
- if (images.Contains(ImageType.Screenshot) && item.GetImages(ImageType.Screenshot).Count() < screenshotLimit)
- {
- return false;
- }
-
return true;
}
/// <summary>
- /// Refreshes from provider.
+ /// Refreshes from a remote provider.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="provider">The provider.</param>
/// <param name="refreshOptions">The refresh options.</param>
/// <param name="savedOptions">The saved options.</param>
/// <param name="backdropLimit">The backdrop limit.</param>
- /// <param name="screenshotLimit">The screenshot limit.</param>
/// <param name="downloadedImages">The downloaded images.</param>
/// <param name="result">The result.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -251,7 +270,6 @@ namespace MediaBrowser.Providers.Manager
ImageRefreshOptions refreshOptions,
TypeOptions savedOptions,
int backdropLimit,
- int screenshotLimit,
ICollection<ImageType> downloadedImages,
RefreshResult result,
CancellationToken cancellationToken)
@@ -265,7 +283,7 @@ namespace MediaBrowser.Providers.Manager
if (!refreshOptions.ReplaceAllImages &&
refreshOptions.ReplaceImages.Length == 0 &&
- ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit, screenshotLimit))
+ ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit))
{
return;
}
@@ -276,7 +294,7 @@ namespace MediaBrowser.Providers.Manager
item,
new RemoteImageQuery(provider.Name)
{
- IncludeAllLanguages = false,
+ IncludeAllLanguages = true,
IncludeDisabledProviders = false,
},
cancellationToken).ConfigureAwait(false);
@@ -304,13 +322,7 @@ namespace MediaBrowser.Providers.Manager
}
minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
- await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
-
- if (item is IHasScreenshots hasScreenshots)
- {
- minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
- await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
- }
+ await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -328,40 +340,36 @@ namespace MediaBrowser.Providers.Manager
return options.IsEnabled(type);
}
- private void ClearImages(BaseItem item, ImageType type)
+ private void PruneImages(BaseItem item, ItemImageInfo[] images)
{
- var deleted = false;
- var deletedImages = new List<ItemImageInfo>();
-
- foreach (var image in item.GetImages(type))
+ for (var i = 0; i < images.Length; i++)
{
- if (!image.IsLocalFile)
- {
- deletedImages.Add(image);
- continue;
- }
+ var image = images[i];
- try
- {
- _fileSystem.DeleteFile(image.Path);
- deleted = true;
- }
- catch (FileNotFoundException)
+ if (image.IsLocalFile)
{
+ try
+ {
+ _fileSystem.DeleteFile(image.Path);
+ }
+ catch (FileNotFoundException)
+ {
+ }
}
}
- item.RemoveImages(deletedImages);
-
- if (deleted)
- {
- item.ValidateImages(new DirectoryService(_fileSystem));
- }
+ item.RemoveImages(images);
}
+ /// <summary>
+ /// Merges a list of images into the provided item, validating existing images and replacing them or adding new images as necessary.
+ /// </summary>
+ /// <param name="item">The <see cref="BaseItem"/> to modify.</param>
+ /// <param name="images">The new images to place in <c>item</c>.</param>
+ /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images)
{
- var changed = false;
+ var changed = item.ValidateImages(new DirectoryService(_fileSystem));
for (var i = 0; i < _singularImages.Length; i++)
{
@@ -397,18 +405,6 @@ namespace MediaBrowser.Providers.Manager
currentImage.DateModified = newDateModified;
}
}
- else
- {
- var existing = item.GetImageInfo(type, 0);
- if (existing != null)
- {
- if (existing.IsLocalFile && !File.Exists(existing.Path))
- {
- item.RemoveImage(existing);
- changed = true;
- }
- }
- }
}
if (UpdateMultiImages(item, images, ImageType.Backdrop))
@@ -416,15 +412,6 @@ namespace MediaBrowser.Providers.Manager
changed = true;
}
- var hasScreenshots = item as IHasScreenshots;
- if (hasScreenshots != null)
- {
- if (UpdateMultiImages(item, images, ImageType.Screenshot))
- {
- changed = true;
- }
- }
-
return changed;
}
@@ -470,7 +457,7 @@ namespace MediaBrowser.Providers.Manager
CancellationToken cancellationToken)
{
var eligibleImages = images
- .Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth))
+ .Where(i => i.Type == type && i.Width >= minWidth)
.ToList();
if (EnableImageStub(item) && eligibleImages.Count > 0)
@@ -487,7 +474,20 @@ namespace MediaBrowser.Providers.Manager
try
{
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
+
+ // Sometimes providers send back bad urls. Just move to the next image
+ if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidden)
+ {
+ _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
+ continue;
+ }
+
+ if (!response.IsSuccessStatusCode)
+ {
+ _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response.StatusCode);
+ break;
+ }
+
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await _providerManager.SaveImage(
@@ -501,15 +501,8 @@ namespace MediaBrowser.Providers.Manager
result.UpdateType |= ItemUpdateType.ImageUpdate;
return true;
}
- catch (HttpRequestException ex)
+ catch (HttpRequestException)
{
- // Sometimes providers send back bad url's. Just move to the next image
- if (ex.StatusCode.HasValue
- && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
- {
- continue;
- }
-
break;
}
}
@@ -529,7 +522,7 @@ namespace MediaBrowser.Providers.Manager
return true;
}
- if (item is IItemByName && item is not MusicArtist)
+ if (item is IItemByName and not MusicArtist)
{
var hasDualAccess = item as IHasDualAccess;
if (hasDualAccess == null || hasDualAccess.IsAccessedByName)
@@ -562,7 +555,7 @@ namespace MediaBrowser.Providers.Manager
newIndex);
}
- private async Task DownloadBackdrops(BaseItem item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
+ private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
{
foreach (var image in images.Where(i => i.Type == imageType))
{
@@ -589,8 +582,21 @@ namespace MediaBrowser.Providers.Manager
{
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
- // If there's already an image of the same size, skip it
- if (response.Content.Headers.ContentLength.HasValue)
+ // Sometimes providers send back bad urls. Just move to the next image
+ if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidden)
+ {
+ _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
+ continue;
+ }
+
+ if (!response.IsSuccessStatusCode)
+ {
+ _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response.StatusCode);
+ break;
+ }
+
+ // If there's already an image of the same file size, skip it unless doing a full refresh
+ if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType))
{
try
{
@@ -616,15 +622,8 @@ namespace MediaBrowser.Providers.Manager
cancellationToken).ConfigureAwait(false);
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
}
- catch (HttpRequestException ex)
+ catch (HttpRequestException)
{
- // Sometimes providers send back bad urls. Just move onto the next image
- if (ex.StatusCode.HasValue
- && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
- {
- continue;
- }
-
break;
}
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index ab8d3a2a6..ffb3baeb1 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 7e60eced0..ca557f6d6 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -24,6 +26,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
@@ -209,8 +212,7 @@ namespace MediaBrowser.Providers.Manager
throw new ArgumentNullException(nameof(source));
}
- var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, true);
-
+ var fileStream = AsyncFile.OpenRead(source);
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
}
@@ -235,14 +237,7 @@ namespace MediaBrowser.Providers.Manager
var preferredLanguage = item.GetPreferredMetadataLanguage();
- var languages = new List<string>();
- if (!query.IncludeAllLanguages && !string.IsNullOrWhiteSpace(preferredLanguage))
- {
- languages.Add(preferredLanguage);
- }
-
- // TODO include [query.IncludeAllLanguages] as an argument to the providers
- var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType));
+ var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellationToken, query.ImageType));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -254,17 +249,21 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
/// <param name="item">The item.</param>
/// <param name="provider">The provider.</param>
- /// <param name="preferredLanguages">The preferred languages.</param>
+ /// <param name="preferredLanguage">The preferred language.</param>
+ /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="type">The type.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
private async Task<IEnumerable<RemoteImageInfo>> GetImages(
BaseItem item,
IRemoteImageProvider provider,
- IReadOnlyCollection<string> preferredLanguages,
+ string preferredLanguage,
+ bool includeAllLanguages,
CancellationToken cancellationToken,
ImageType? type = null)
{
+ bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
+
try
{
var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
@@ -274,14 +273,17 @@ namespace MediaBrowser.Providers.Manager
result = result.Where(i => i.Type == type.Value);
}
- if (preferredLanguages.Count > 0)
+ if (!includeAllLanguages && hasPreferredLanguage)
{
- result = result.Where(i => string.IsNullOrEmpty(i.Language) ||
- preferredLanguages.Contains(i.Language, StringComparer.OrdinalIgnoreCase) ||
+ // Filter out languages that do not match the preferred languages.
+ //
+ // TODO: should exception case of "en" (English) eventually be removed?
+ result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
+ string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
}
- return result;
+ return result.OrderByLanguageDescending(preferredLanguage);
}
catch (OperationCanceledException)
{
diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 6d088e6e7..b90136d50 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Manager/RefreshResult.cs b/MediaBrowser.Providers/Manager/RefreshResult.cs
index 72fc61e42..663ffc524 100644
--- a/MediaBrowser.Providers/Manager/RefreshResult.cs
+++ b/MediaBrowser.Providers/Manager/RefreshResult.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Manager
{
public ItemUpdateType UpdateType { get; set; }
- public string ErrorMessage { get; set; }
+ public string? ErrorMessage { get; set; }
public int Failures { get; set; }
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 3d866cdc2..9d0a6944b 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -16,9 +16,9 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
- <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
@@ -26,13 +26,10 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <AnalysisMode Condition=" '$(Configuration)' == 'Debug'">AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- <Nullable>disable</Nullable>
</PropertyGroup>
<!-- Code Analyzers-->
@@ -49,5 +46,7 @@
<EmbeddedResource Include="Plugins\Omdb\Configuration\config.html" />
<None Remove="Plugins\MusicBrainz\Configuration\config.html" />
<EmbeddedResource Include="Plugins\MusicBrainz\Configuration\config.html" />
+ <None Remove="Plugins\Tmdb\Configuration\config.html" />
+ <EmbeddedResource Include="Plugins\Tmdb\Configuration\config.html" />
</ItemGroup>
</Project>
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 12125cbb9..8c81b08db 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CA1002, CS1591
using System;
diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
new file mode 100644
index 000000000..c41d36d76
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -0,0 +1,222 @@
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ /// <summary>
+ /// Uses <see cref="IMediaEncoder"/> to extract embedded images.
+ /// </summary>
+ public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder
+ {
+ private static readonly string[] _primaryImageFileNames =
+ {
+ "poster",
+ "folder",
+ "cover",
+ "default"
+ };
+
+ private static readonly string[] _backdropImageFileNames =
+ {
+ "backdrop",
+ "fanart",
+ "background",
+ "art"
+ };
+
+ private static readonly string[] _logoImageFileNames =
+ {
+ "logo",
+ };
+
+ private readonly IMediaEncoder _mediaEncoder;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EmbeddedImageProvider"/> class.
+ /// </summary>
+ /// <param name="mediaEncoder">The media encoder for extracting attached/embedded images.</param>
+ public EmbeddedImageProvider(IMediaEncoder mediaEncoder)
+ {
+ _mediaEncoder = mediaEncoder;
+ }
+
+ /// <inheritdoc />
+ public string Name => "Embedded Image Extractor";
+
+ /// <inheritdoc />
+ // Default to after internet image providers but before Screen Grabber
+ public int Order => 99;
+
+ /// <inheritdoc />
+ public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
+ {
+ if (item is Video)
+ {
+ if (item is Episode)
+ {
+ return new[]
+ {
+ ImageType.Primary,
+ };
+ }
+
+ return new[]
+ {
+ ImageType.Primary,
+ ImageType.Backdrop,
+ ImageType.Logo,
+ };
+ }
+
+ return Array.Empty<ImageType>();
+ }
+
+ /// <inheritdoc />
+ public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
+ {
+ var video = (Video)item;
+
+ // No support for these
+ if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd)
+ {
+ return Task.FromResult(new DynamicImageResponse { HasImage = false });
+ }
+
+ return GetEmbeddedImage(video, type, cancellationToken);
+ }
+
+ private async Task<DynamicImageResponse> GetEmbeddedImage(Video item, ImageType type, CancellationToken cancellationToken)
+ {
+ MediaSourceInfo mediaSource = new MediaSourceInfo
+ {
+ VideoType = item.VideoType,
+ IsoType = item.IsoType,
+ Protocol = item.PathProtocol ?? MediaProtocol.File,
+ };
+
+ string[] imageFileNames = type switch
+ {
+ ImageType.Primary => _primaryImageFileNames,
+ ImageType.Backdrop => _backdropImageFileNames,
+ ImageType.Logo => _logoImageFileNames,
+ _ => _primaryImageFileNames
+ };
+
+ // Try attachments first
+ var attachmentStream = item.GetMediaSources(false)
+ .SelectMany(source => source.MediaAttachments)
+ .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName)
+ && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ if (attachmentStream != null)
+ {
+ return await ExtractAttachment(item, cancellationToken, attachmentStream, mediaSource);
+ }
+
+ // Fall back to EmbeddedImage streams
+ var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage);
+
+ if (imageStreams.Count == 0)
+ {
+ // Can't extract if we don't have any EmbeddedImage streams
+ return new DynamicImageResponse { HasImage = false };
+ }
+
+ // Extract first stream containing an element of imageFileNames
+ var imageStream = imageStreams
+ .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment)
+ && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ // Primary type only: default to first image if none found by label
+ if (imageStream == null)
+ {
+ if (type == ImageType.Primary)
+ {
+ imageStream = imageStreams[0];
+ }
+ else
+ {
+ // No streams matched, abort
+ return new DynamicImageResponse { HasImage = false };
+ }
+ }
+
+ string extractedImagePath =
+ await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken)
+ .ConfigureAwait(false);
+
+ return new DynamicImageResponse
+ {
+ Format = ImageFormat.Jpg,
+ HasImage = true,
+ Path = extractedImagePath,
+ Protocol = MediaProtocol.File
+ };
+ }
+
+ private async Task<DynamicImageResponse> ExtractAttachment(Video item, CancellationToken cancellationToken, MediaAttachment attachmentStream, MediaSourceInfo mediaSource)
+ {
+ var extension = string.IsNullOrEmpty(attachmentStream.MimeType)
+ ? Path.GetExtension(attachmentStream.FileName)
+ : MimeTypes.ToExtension(attachmentStream.MimeType);
+
+ if (string.IsNullOrEmpty(extension))
+ {
+ extension = ".jpg";
+ }
+
+ string extractedAttachmentPath =
+ await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken)
+ .ConfigureAwait(false);
+
+ ImageFormat format = extension switch
+ {
+ ".bmp" => ImageFormat.Bmp,
+ ".gif" => ImageFormat.Gif,
+ ".jpg" => ImageFormat.Jpg,
+ ".png" => ImageFormat.Png,
+ ".webp" => ImageFormat.Webp,
+ _ => ImageFormat.Jpg
+ };
+
+ return new DynamicImageResponse
+ {
+ Format = format,
+ HasImage = true,
+ Path = extractedAttachmentPath,
+ Protocol = MediaProtocol.File
+ };
+ }
+
+ /// <inheritdoc />
+ public bool Supports(BaseItem item)
+ {
+ if (item.IsShortcut)
+ {
+ return false;
+ }
+
+ if (!item.IsFileProtocol)
+ {
+ return false;
+ }
+
+ return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index cf271e7db..9eb79c39d 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index 4fff57273..d4b5d8655 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 1f17d8cd4..4ab15f60e 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CA1068, CS1591
using System;
@@ -557,6 +559,7 @@ namespace MediaBrowser.Providers.MediaInfo
subtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
+ true,
cancellationToken).ConfigureAwait(false);
// Rescan
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
index aa0743bd0..b2b93940a 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
@@ -1,8 +1,9 @@
+#nullable disable
+
#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -37,6 +38,7 @@ namespace MediaBrowser.Providers.MediaInfo
IEnumerable<string> languages,
string[] disabledSubtitleFetchers,
string[] subtitleFetcherOrder,
+ bool isAutomated,
CancellationToken cancellationToken)
{
var downloadedLanguages = new List<string>();
@@ -52,6 +54,7 @@ namespace MediaBrowser.Providers.MediaInfo
lang,
disabledSubtitleFetchers,
subtitleFetcherOrder,
+ isAutomated,
cancellationToken).ConfigureAwait(false);
if (downloaded)
@@ -72,6 +75,7 @@ namespace MediaBrowser.Providers.MediaInfo
string lang,
string[] disabledSubtitleFetchers,
string[] subtitleFetcherOrder,
+ bool isAutomated,
CancellationToken cancellationToken)
{
if (video.VideoType != VideoType.VideoFile)
@@ -110,6 +114,7 @@ namespace MediaBrowser.Providers.MediaInfo
disabledSubtitleFetchers,
subtitleFetcherOrder,
mediaType,
+ isAutomated,
cancellationToken);
}
@@ -123,6 +128,7 @@ namespace MediaBrowser.Providers.MediaInfo
string[] disabledSubtitleFetchers,
string[] subtitleFetcherOrder,
VideoContentType mediaType,
+ bool isAutomated,
CancellationToken cancellationToken)
{
// There's already subtitles for this language
@@ -170,7 +176,8 @@ namespace MediaBrowser.Providers.MediaInfo
IsPerfectMatch = requirePerfectMatch,
DisabledSubtitleFetchers = disabledSubtitleFetchers,
- SubtitleFetcherOrder = subtitleFetcherOrder
+ SubtitleFetcherOrder = subtitleFetcherOrder,
+ IsAutomated = isAutomated
};
if (video is Episode episode)
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index b3d065929..dd4a5f061 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -1,8 +1,9 @@
+#nullable disable
+
#pragma warning disable CA1002, CS1591
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index 9804ec3bb..1eacbf1e1 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -197,6 +199,7 @@ namespace MediaBrowser.Providers.MediaInfo
subtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
+ true,
cancellationToken).ConfigureAwait(false);
// Rescan
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 453938be7..d226182c0 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,47 +9,50 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
+ /// <summary>
+ /// Uses <see cref="IMediaEncoder"/> to create still images from the main video.
+ /// </summary>
public class VideoImageProvider : IDynamicImageProvider, IHasOrder
{
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger<VideoImageProvider> _logger;
- private readonly IFileSystem _fileSystem;
- public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger, IFileSystem fileSystem)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="VideoImageProvider"/> class.
+ /// </summary>
+ /// <param name="mediaEncoder">The media encoder for capturing images.</param>
+ /// <param name="logger">The logger.</param>
+ public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger)
{
_mediaEncoder = mediaEncoder;
_logger = logger;
- _fileSystem = fileSystem;
}
+ /// <inheritdoc />
public string Name => "Screen Grabber";
+ /// <inheritdoc />
// Make sure this comes after internet image providers
public int Order => 100;
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
- return new List<ImageType> { ImageType.Primary };
+ return new[] { ImageType.Primary };
}
+ /// <inheritdoc />
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
{
var video = (Video)item;
- // No support for this
- if (video.IsPlaceHolder)
- {
- return Task.FromResult(new DynamicImageResponse { HasImage = false });
- }
-
- // No support for this
- if (video.VideoType == VideoType.Dvd)
+ // No support for these
+ if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd)
{
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
@@ -59,64 +60,39 @@ namespace MediaBrowser.Providers.MediaInfo
// Can't extract if we didn't find a video stream in the file
if (!video.DefaultVideoStreamIndex.HasValue)
{
- _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {0}.", video.Path ?? string.Empty);
+ _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty);
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
return GetVideoImage(video, cancellationToken);
}
- public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken)
+ private async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken)
{
- var protocol = item.PathProtocol ?? MediaProtocol.File;
-
- var inputPath = item.Path;
-
- var mediaStreams =
- item.GetMediaStreams();
-
- var imageStreams =
- mediaStreams
- .Where(i => i.Type == MediaStreamType.EmbeddedImage)
- .ToList();
+ MediaSourceInfo mediaSource = new MediaSourceInfo
+ {
+ VideoType = item.VideoType,
+ IsoType = item.IsoType,
+ Protocol = item.PathProtocol ?? MediaProtocol.File,
+ };
- var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ??
- imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ??
- imageStreams.FirstOrDefault();
+ // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
+ // Always use 10 seconds for dvd because our duration could be out of whack
+ var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
+ item.RunTimeTicks.Value > 0
+ ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10)
+ : TimeSpan.FromSeconds(10);
- string extractedImagePath;
+ var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video);
- if (imageStream != null)
- {
- MediaSourceInfo mediaSource = new MediaSourceInfo
- {
- VideoType = item.VideoType,
- IsoType = item.IsoType,
- Protocol = item.PathProtocol.Value,
- };
-
- extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false);
- }
- else
+ if (videoStream == null)
{
- // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
- // Always use 10 seconds for dvd because our duration could be out of whack
- var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
- item.RunTimeTicks.Value > 0
- ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1))
- : TimeSpan.FromSeconds(10);
-
- var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
- var mediaSource = new MediaSourceInfo
- {
- VideoType = item.VideoType,
- IsoType = item.IsoType,
- Protocol = item.PathProtocol.Value,
- };
-
- extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("Skipping image extraction: no video stream found for {Path}.", item.Path ?? string.Empty);
+ return new DynamicImageResponse { HasImage = false };
}
+ string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
+
return new DynamicImageResponse
{
Format = ImageFormat.Jpg,
@@ -126,6 +102,7 @@ namespace MediaBrowser.Providers.MediaInfo
};
}
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
if (item.IsShortcut)
@@ -138,12 +115,7 @@ namespace MediaBrowser.Providers.MediaInfo
return false;
}
- if (item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia)
- {
- return true;
- }
-
- return false;
+ return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia;
}
}
}
diff --git a/MediaBrowser.Providers/Movies/ImdbExternalId.cs b/MediaBrowser.Providers/Movies/ImdbExternalId.cs
index a8d74aa0b..d00f37db5 100644
--- a/MediaBrowser.Providers/Movies/ImdbExternalId.cs
+++ b/MediaBrowser.Providers/Movies/ImdbExternalId.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
- public string UrlFormatString => "https://www.imdb.com/title/{0}";
+ public string? UrlFormatString => "https://www.imdb.com/title/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs
index 8151ab471..1bb5e1ea8 100644
--- a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs
+++ b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Movies
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
/// <inheritdoc />
- public string UrlFormatString => "https://www.imdb.com/name/{0}";
+ public string? UrlFormatString => "https://www.imdb.com/name/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Person;
diff --git a/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs
index dddfd02e4..d3fce37c7 100644
--- a/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs
+++ b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Music
{
public static class AlbumInfoExtensions
{
- public static string GetAlbumArtist(this AlbumInfo info)
+ public static string? GetAlbumArtist(this AlbumInfo info)
{
var id = info.SongInfos.SelectMany(i => i.AlbumArtists)
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Music
return info.AlbumArtists.Count > 0 ? info.AlbumArtists[0] : default;
}
- public static string GetReleaseGroupId(this AlbumInfo info)
+ public static string? GetReleaseGroupId(this AlbumInfo info)
{
var id = info.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup);
@@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Music
return id;
}
- public static string GetReleaseId(this AlbumInfo info)
+ public static string? GetReleaseId(this AlbumInfo info)
{
var id = info.GetProviderId(MetadataProvider.MusicBrainzAlbum);
@@ -47,9 +47,9 @@ namespace MediaBrowser.Providers.Music
return id;
}
- public static string GetMusicBrainzArtistId(this AlbumInfo info)
+ public static string? GetMusicBrainzArtistId(this AlbumInfo info)
{
- info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string id);
+ info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string? id);
if (string.IsNullOrEmpty(id))
{
@@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Music
return id;
}
- public static string GetMusicBrainzArtistId(this ArtistInfo info)
+ public static string? GetMusicBrainzArtistId(this ArtistInfo info)
{
info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out var id);
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 8c9a1f59b..7c5b80e1e 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -81,7 +81,7 @@ namespace MediaBrowser.Providers.Music
if (!item.AlbumArtists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase))
{
item.AlbumArtists = artists;
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
return updateType;
@@ -100,7 +100,7 @@ namespace MediaBrowser.Providers.Music
if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase))
{
item.Artists = artists;
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
return updateType;
diff --git a/MediaBrowser.Providers/Music/ImvdbId.cs b/MediaBrowser.Providers/Music/ImvdbId.cs
index a1726b996..ed69f369c 100644
--- a/MediaBrowser.Providers/Music/ImvdbId.cs
+++ b/MediaBrowser.Providers/Music/ImvdbId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
- public string UrlFormatString => null;
+ public string? UrlFormatString => null;
/// <inheritdoc />
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
index 067d585cb..8baca30c6 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs
index 138cfef19..3a400575b 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
- public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
+ public string? UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is MusicAlbum;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
index 36d8eeb40..ad0247fb2 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
@@ -13,6 +15,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.AudioDb
@@ -57,7 +60,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id);
- await using FileStream jsonStream = File.OpenRead(path);
+ await using FileStream jsonStream = AsyncFile.OpenRead(path);
var obj = await JsonSerializer.DeserializeAsync<AudioDbAlbumProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.album != null && obj.album.Count > 0)
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
index 9f2f7fc11..43f30824b 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CA1002, CS1591, SA1300
using System;
@@ -66,7 +68,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetAlbumInfoPath(_config.ApplicationPaths, id);
- await using FileStream jsonStream = File.OpenRead(path);
+ await using FileStream jsonStream = AsyncFile.OpenRead(path);
var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.album != null && obj.album.Count > 0)
@@ -172,8 +174,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
+
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ fileStreamOptions.PreallocationSize = stream.Length;
+ await using var xmlFileStream = new FileStream(path, fileStreamOptions);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs
index 8aceb48c0..b9e57eb26 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
/// <inheritdoc />
- public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
+ public string? UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is MusicArtist;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
index aa61a56f6..9c2447660 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
@@ -13,6 +15,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.AudioDb
@@ -59,7 +62,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id);
- await using FileStream jsonStream = File.OpenRead(path);
+ await using FileStream jsonStream = AsyncFile.OpenRead(path);
var obj = await JsonSerializer.DeserializeAsync<AudioDbArtistProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.artists != null && obj.artists.Count > 0)
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
index 2857c6c13..538dc67c4 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CA1034, CS1591, CA1002, SA1028, SA1300
using System;
@@ -65,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetArtistInfoPath(_config.ApplicationPaths, id);
- await using FileStream jsonStream = File.OpenRead(path);
+ await using FileStream jsonStream = AsyncFile.OpenRead(path);
var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.artists != null && obj.artists.Count > 0)
@@ -154,8 +156,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ fileStreamOptions.PreallocationSize = stream.Length;
+ await using var xmlFileStream = new FileStream(path, fileStreamOptions);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs
index 014481da2..f8f6253ff 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
/// <inheritdoc />
- public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
+ public string? UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs
index 787539104..fd598c918 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
/// <inheritdoc />
- public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
+ public string? UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
index 664474dcd..d61ec6cb1 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using MediaBrowser.Model.Plugins;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
index ba0d7b569..6c2ad0573 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
index 0cec9e359..9c27bd7d3 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using MediaBrowser.Model.Plugins;
@@ -12,24 +12,13 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
public string Server
{
- get
- {
- return _server;
- }
-
- set
- {
- _server = value.TrimEnd('/');
- }
+ get => _server;
+ set => _server = value.TrimEnd('/');
}
public long RateLimit
{
- get
- {
- return _rateLimit;
- }
-
+ get => _rateLimit;
set
{
if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer)
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
index 1b37e2a60..c54cdda3d 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
/// <inheritdoc />
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
index ef095111a..8f7fadd06 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
/// <inheritdoc />
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
+ public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index c97affdbf..5559b9db6 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591, SA1401
using System;
@@ -12,7 +14,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
index d654e1372..941ffea72 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
/// <inheritdoc />
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is MusicArtist;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 7cff5f595..1feb7f4ea 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
index f889a34b5..05db2d98f 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
/// <inheritdoc />
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
index 53783d2c0..acb652fe0 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
/// <inheritdoc />
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
+ public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
index 627f8f098..14805b9b7 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
/// <inheritdoc />
- public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
+ public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Audio;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index 69b69be42..cfa10dd64 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs
index 196f14e7c..099547005 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using MediaBrowser.Model.Plugins;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs
index 268538815..8bfdc461e 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.ComponentModel;
using System.Text.Json;
@@ -18,7 +16,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
if (reader.TokenType == JsonTokenType.String)
{
var str = reader.GetString();
- if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase))
+ if (str == null || str.Equals("N/A", StringComparison.OrdinalIgnoreCase))
{
return null;
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs
index c19589d45..f35880a04 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index 24ef80a35..f67ac6ede 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return result;
}
- if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId))
+ if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId))
{
if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue)
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index df67aff31..fa82089c8 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 02e696de5..2409993a2 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591, SA1300
using System;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 1ae712e9e..816a882b4 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS159, SA1300
using System;
@@ -26,7 +28,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IApplicationHost _appHost;
private readonly JsonSerializerOptions _jsonOptions;
@@ -79,7 +80,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
- && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year)
+ && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year)
&& year >= 0)
{
item.ProductionYear = year;
@@ -93,14 +94,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
if (!string.IsNullOrEmpty(result.imdbVotes)
- && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount)
+ && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount)
&& voteCount >= 0)
{
// item.VoteCount = voteCount;
}
if (!string.IsNullOrEmpty(result.imdbRating)
- && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating)
+ && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating)
&& imdbRating >= 0)
{
item.CommunityRating = imdbRating;
@@ -191,7 +192,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
- && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year)
+ && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year)
&& year >= 0)
{
item.ProductionYear = year;
@@ -205,14 +206,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
if (!string.IsNullOrEmpty(result.imdbVotes)
- && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount)
+ && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount)
&& voteCount >= 0)
{
// item.VoteCount = voteCount;
}
if (!string.IsNullOrEmpty(result.imdbRating)
- && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating)
+ && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating)
&& imdbRating >= 0)
{
item.CommunityRating = imdbRating;
@@ -236,31 +237,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken)
{
var path = await EnsureItemInfo(imdbId, cancellationToken).ConfigureAwait(false);
- await using var stream = File.OpenRead(path);
+ await using var stream = AsyncFile.OpenRead(path);
return await JsonSerializer.DeserializeAsync<RootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken)
{
var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken).ConfigureAwait(false);
- await using var stream = File.OpenRead(path);
+ await using var stream = AsyncFile.OpenRead(path);
return await JsonSerializer.DeserializeAsync<SeasonRootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
- internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
- {
- if (seriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string id))
- {
- // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet.
- if (!string.IsNullOrWhiteSpace(id))
- {
- return true;
- }
- }
-
- return false;
- }
-
/// <summary>Gets OMDB URL.</summary>
/// <param name="query">Appends query string to URL.</param>
/// <returns>OMDB URL with optional query string.</returns>
@@ -309,7 +296,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
imdbParam));
var rootObject = await GetDeserializedOmdbResponse<RootObject>(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
- await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
return path;
@@ -349,7 +336,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
seasonId));
var rootObject = await GetDeserializedOmdbResponse<SeasonRootObject>(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
- await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
return path;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
index 047df4f33..a0fba48f0 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
index 1f7ec6433..3217ac2f1 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs
@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet;
/// <inheritdoc />
- public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
+ public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index 5ad61c567..29a557c31 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -11,9 +13,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
@@ -66,42 +66,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
return Enumerable.Empty<RemoteImageInfo>();
}
- var remoteImages = new List<RemoteImageInfo>();
-
- for (var i = 0; i < collection.Images.Posters.Count; i++)
- {
- var poster = collection.Images.Posters[i];
- remoteImages.Add(new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
- CommunityRating = poster.VoteAverage,
- VoteCount = poster.VoteCount,
- Width = poster.Width,
- Height = poster.Height,
- Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
- ProviderName = Name,
- Type = ImageType.Primary,
- RatingType = RatingType.Score
- });
- }
+ var posters = collection.Images.Posters;
+ var backdrops = collection.Images.Backdrops;
+ var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count);
- for (var i = 0; i < collection.Images.Backdrops.Count; i++)
- {
- var backdrop = collection.Images.Backdrops[i];
- remoteImages.Add(new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath),
- CommunityRating = backdrop.VoteAverage,
- VoteCount = backdrop.VoteCount,
- Width = backdrop.Width,
- Height = backdrop.Height,
- ProviderName = Name,
- Type = ImageType.Backdrop,
- RatingType = RatingType.Score
- });
- }
+ _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages);
+ _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages);
- return remoteImages.OrderByLanguageDescending(language);
+ return remoteImages;
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index 5dd1f0b73..62bc9c65f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
new file mode 100644
index 000000000..9a78a7536
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
@@ -0,0 +1,30 @@
+using MediaBrowser.Model.Plugins;
+
+namespace MediaBrowser.Providers.Plugins.Tmdb
+{
+ /// <summary>
+ /// Plugin configuration class for TMDb library.
+ /// </summary>
+ public class PluginConfiguration : BasePluginConfiguration
+ {
+ /// <summary>
+ /// Gets or sets a value indicating whether include adult content when searching with TMDb.
+ /// </summary>
+ public bool IncludeAdult { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether tags should be imported for series from TMDb.
+ /// </summary>
+ public bool ExcludeTagsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether tags should be imported for movies from TMDb.
+ /// </summary>
+ public bool ExcludeTagsMovies { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating the maximum number of cast members to fetch for an item.
+ /// </summary>
+ public int MaxCastMembers { get; set; } = 15;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
new file mode 100644
index 000000000..12b4c7ca4
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>TMDb</title>
+</head>
+<body>
+ <div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
+ <div data-role="content">
+ <div class="content-primary">
+ <form class="configForm">
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="includeAdult" />
+ <span>Include adult content in search results.</span>
+ </label>
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="excludeTagsSeries" />
+ <span>Exclude tags/keywords from metadata fetched for series.</span>
+ </label>
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="excludeTagsMovies" />
+ <span>Exclude tags/keywords from metadata fetched for movies.</span>
+ </label>
+ <div class="inputContainer">
+ <input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" />
+ <div class="fieldDescription">The maximum number of cast members to fetch for an item.</div>
+ </div>
+ <br />
+ <div>
+ <button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
+ </div>
+ </form>
+ </div>
+ </div>
+ <script type="text/javascript">
+ var PluginConfig = {
+ pluginId: "b8715ed1-6c47-4528-9ad3-f72deb539cd4"
+ };
+
+ document.querySelector('.configPage')
+ .addEventListener('pageshow', function () {
+ Dashboard.showLoadingMsg();
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ document.querySelector('#includeAdult').checked = config.IncludeAdult;
+ document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries;
+ document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies;
+
+ var maxCastMembers = document.querySelector('#maxCastMembers');
+ maxCastMembers.value = config.MaxCastMembers;
+ maxCastMembers.dispatchEvent(new Event('change', {
+ bubbles: true,
+ cancelable: false
+ }));
+
+ Dashboard.hideLoadingMsg();
+ });
+ });
+
+
+ document.querySelector('.configForm')
+ .addEventListener('submit', function (e) {
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ config.IncludeAdult = document.querySelector('#includeAdult').checked;
+ config.ExcludeTagsSeries = document.querySelector('#excludeTagsSeries').checked;
+ config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked;
+ config.MaxCastMembers = document.querySelector('#maxCastMembers').value;
+ ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ });
+
+ e.preventDefault();
+ return false;
+ });
+ </script>
+ </div>
+</body>
+</html>
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
index f1a1b65d8..31310a8d4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs
@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public ExternalIdMediaType? Type => ExternalIdMediaType.Movie;
/// <inheritdoc />
- public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
+ public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
index f34d689c1..f71f7bd10 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -11,9 +13,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers;
using TMDbLib.Objects.Find;
@@ -83,42 +83,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return Enumerable.Empty<RemoteImageInfo>();
}
- var remoteImages = new List<RemoteImageInfo>();
-
- for (var i = 0; i < movie.Images.Posters.Count; i++)
- {
- var poster = movie.Images.Posters[i];
- remoteImages.Add(new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
- CommunityRating = poster.VoteAverage,
- VoteCount = poster.VoteCount,
- Width = poster.Width,
- Height = poster.Height,
- Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
- ProviderName = Name,
- Type = ImageType.Primary,
- RatingType = RatingType.Score
- });
- }
+ var posters = movie.Images.Posters;
+ var backdrops = movie.Images.Backdrops;
+ var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count);
- for (var i = 0; i < movie.Images.Backdrops.Count; i++)
- {
- var backdrop = movie.Images.Backdrops[i];
- remoteImages.Add(new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetPosterUrl(backdrop.FilePath),
- CommunityRating = backdrop.VoteAverage,
- VoteCount = backdrop.VoteCount,
- Width = backdrop.Width,
- Height = backdrop.Height,
- ProviderName = Name,
- Type = ImageType.Backdrop,
- RatingType = RatingType.Score
- });
- }
+ _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages);
+ _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages);
- return remoteImages.OrderByLanguageDescending(language);
+ return remoteImages;
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 54f8d450a..fcaacc90d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -239,8 +241,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResult.Credits?.Cast != null)
{
- // TODO configurable
- foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
+ foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
{
var personInfo = new PersonInfo
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
index de74a7a4c..9804d60bd 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
/// <inheritdoc />
- public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
+ public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
index e4c908a62..7ce4cfe67 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
@@ -10,7 +10,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.People
@@ -61,23 +60,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return Enumerable.Empty<RemoteImageInfo>();
}
- var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count];
+ var profiles = personResult.Images.Profiles;
+ var remoteImages = new List<RemoteImageInfo>(profiles.Count);
- for (var i = 0; i < personResult.Images.Profiles.Count; i++)
- {
- var image = personResult.Images.Profiles[i];
- remoteImages[i] = new RemoteImageInfo
- {
- ProviderName = Name,
- Type = ImageType.Primary,
- Width = image.Width,
- Height = image.Height,
- Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
- Url = _tmdbClientManager.GetProfileUrl(image.FilePath)
- };
- }
+ _tmdbClientManager.ConvertProfilesToRemoteImageInfo(profiles, language, remoteImages);
- return remoteImages.OrderByLanguageDescending(language);
+ return remoteImages;
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index dac118388..8790e3759 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs
new file mode 100644
index 000000000..4adde8366
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs
@@ -0,0 +1,60 @@
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace MediaBrowser.Providers.Plugins.Tmdb
+{
+ /// <summary>
+ /// Plugin class for the TMDb library.
+ /// </summary>
+ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Plugin"/> class.
+ /// </summary>
+ /// <param name="applicationPaths">application paths.</param>
+ /// <param name="xmlSerializer">xml serializer.</param>
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ /// <summary>
+ /// Gets the instance of TMDb plugin.
+ /// </summary>
+ public static Plugin Instance { get; private set; }
+
+ /// <inheritdoc/>
+ public override Guid Id => new Guid("b8715ed1-6c47-4528-9ad3-f72deb539cd4");
+
+ /// <inheritdoc/>
+ public override string Name => "TMDb";
+
+ /// <inheritdoc/>
+ public override string Description => "Get metadata for movies and other video content from TheMovieDb.";
+
+ // TODO remove when plugin removed from server.
+
+ /// <inheritdoc/>
+ public override string ConfigurationFileName => "Jellyfin.Plugin.Tmdb.xml";
+
+ /// <summary>
+ /// Return the plugin configuration page.
+ /// </summary>
+ /// <returns>PluginPageInfo.</returns>
+ public IEnumerable<PluginPageInfo> GetPages()
+ {
+ yield return new PluginPageInfo
+ {
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index ba18c542f..5eec776b5 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -10,9 +12,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
@@ -74,25 +74,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return Enumerable.Empty<RemoteImageInfo>();
}
- var remoteImages = new RemoteImageInfo[stills.Count];
- for (var i = 0; i < stills.Count; i++)
- {
- var image = stills[i];
- remoteImages[i] = new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetStillUrl(image.FilePath),
- CommunityRating = image.VoteAverage,
- VoteCount = image.VoteCount,
- Width = image.Width,
- Height = image.Height,
- Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
- ProviderName = Name,
- Type = ImageType.Primary,
- RatingType = RatingType.Score
- };
- }
+ var remoteImages = new List<RemoteImageInfo>(stills.Count);
+
+ _tmdbClientManager.ConvertStillsToRemoteImageInfo(stills, language, remoteImages);
- return remoteImages.OrderByLanguageDescending(language);
+ return remoteImages;
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index 8ec8f6464..8ac9d0cab 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -152,7 +154,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (credits?.Cast != null)
{
- foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
+ foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
{
metadataResult.AddPerson(new PersonInfo
{
@@ -166,7 +168,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (credits?.GuestStars != null)
{
- foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
+ foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
{
metadataResult.AddPerson(new PersonInfo
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index 0d23c7872..4446fa966 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -11,9 +11,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
@@ -63,25 +61,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return Enumerable.Empty<RemoteImageInfo>();
}
- var remoteImages = new RemoteImageInfo[posters.Count];
- for (var i = 0; i < posters.Count; i++)
- {
- var image = posters[i];
- remoteImages[i] = new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetPosterUrl(image.FilePath),
- CommunityRating = image.VoteAverage,
- VoteCount = image.VoteCount,
- Width = image.Width,
- Height = image.Height,
- Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
- ProviderName = Name,
- Type = ImageType.Primary,
- RatingType = RatingType.Score
- };
- }
+ var remoteImages = new List<RemoteImageInfo>(posters.Count);
+
+ _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages);
- return remoteImages.OrderByLanguageDescending(language);
+ return remoteImages;
}
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index 66e30115d..7afaddc24 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
var result = new MetadataResult<Season>();
- info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string seriesTmdbId);
+ info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId);
var seasonNumber = info.IndexNumber;
@@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var credits = seasonResult.Credits;
if (credits?.Cast != null)
{
- var cast = credits.Cast.OrderBy(c => c.Order).Take(TmdbUtils.MaxCastMembers).ToList();
+ var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList();
for (var i = 0; i < cast.Count; i++)
{
result.AddPerson(new PersonInfo
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
index 6ecc055d7..8a2be80cd 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public ExternalIdMediaType? Type => ExternalIdMediaType.Series;
/// <inheritdoc />
- public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
+ public string? UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index 326c116b3..5ef3736c4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -11,9 +11,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
@@ -71,43 +69,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var posters = series.Images.Posters;
var backdrops = series.Images.Backdrops;
+ var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count);
- var remoteImages = new RemoteImageInfo[posters.Count + backdrops.Count];
+ _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages);
+ _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages);
- for (var i = 0; i < posters.Count; i++)
- {
- var poster = posters[i];
- remoteImages[i] = new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
- CommunityRating = poster.VoteAverage,
- VoteCount = poster.VoteCount,
- Width = poster.Width,
- Height = poster.Height,
- Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
- ProviderName = Name,
- Type = ImageType.Primary,
- RatingType = RatingType.Score
- };
- }
-
- for (var i = 0; i < backdrops.Count; i++)
- {
- var backdrop = series.Images.Backdrops[i];
- remoteImages[posters.Count + i] = new RemoteImageInfo
- {
- Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath),
- CommunityRating = backdrop.VoteAverage,
- VoteCount = backdrop.VoteCount,
- Width = backdrop.Width,
- Height = backdrop.Height,
- ProviderName = Name,
- Type = ImageType.Backdrop,
- RatingType = RatingType.Score
- };
- }
-
- return remoteImages.OrderByLanguageDescending(language);
+ return remoteImages;
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index da76345b5..77e22ffbf 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -329,7 +331,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
{
if (seriesResult.Credits?.Cast != null)
{
- foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
+ foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers))
{
var personInfo = new PersonInfo
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
index 4de4bf4db..cb644c8ca 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
@@ -1,8 +1,13 @@
-using System;
+#nullable disable
+
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Caching.Memory;
using TMDbLib.Client;
using TMDbLib.Objects.Collections;
@@ -55,11 +60,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
+ var extraMethods = MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Videos;
+ if (!(Plugin.Instance?.Configuration.ExcludeTagsMovies).GetValueOrDefault())
+ {
+ extraMethods |= MovieMethods.Keywords;
+ }
+
movie = await _tmDbClient.GetMovieAsync(
tmdbId,
TmdbUtils.NormalizeLanguage(language),
imageLanguages,
- MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Keywords | MovieMethods.Videos,
+ extraMethods,
cancellationToken).ConfigureAwait(false);
if (movie != null)
@@ -121,11 +132,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
+ var extraMethods = TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups;
+ if (!(Plugin.Instance?.Configuration.ExcludeTagsSeries).GetValueOrDefault())
+ {
+ extraMethods |= TvShowMethods.Keywords;
+ }
+
series = await _tmDbClient.GetTvShowAsync(
tmdbId,
language: TmdbUtils.NormalizeLanguage(language),
includeImageLanguage: imageLanguages,
- extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups,
+ extraMethods: extraMethods,
cancellationToken: cancellationToken).ConfigureAwait(false);
if (series != null)
@@ -358,7 +375,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), firstAirDateYear: year, cancellationToken: cancellationToken)
+ .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, firstAirDateYear: year, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
@@ -386,7 +403,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchPersonAsync(name, cancellationToken: cancellationToken)
+ .SearchPersonAsync(name, includeAdult: Plugin.Instance.Configuration.IncludeAdult, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
@@ -428,7 +445,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), year: year, cancellationToken: cancellationToken)
+ .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, year: year, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
@@ -469,33 +486,29 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
}
/// <summary>
- /// Gets the absolute URL of the poster.
+ /// Handles bad path checking and builds the absolute url.
/// </summary>
- /// <param name="posterPath">The relative URL of the poster.</param>
+ /// <param name="size">The image size to fetch.</param>
+ /// <param name="path">The relative URL of the image.</param>
/// <returns>The absolute URL.</returns>
- public string GetPosterUrl(string posterPath)
+ private string GetUrl(string size, string path)
{
- if (string.IsNullOrEmpty(posterPath))
+ if (string.IsNullOrEmpty(path))
{
return null;
}
- return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath).ToString();
+ return _tmDbClient.GetImageUrl(size, path).ToString();
}
/// <summary>
- /// Gets the absolute URL of the backdrop image.
+ /// Gets the absolute URL of the poster.
/// </summary>
- /// <param name="posterPath">The relative URL of the backdrop image.</param>
+ /// <param name="posterPath">The relative URL of the poster.</param>
/// <returns>The absolute URL.</returns>
- public string GetBackdropUrl(string posterPath)
+ public string GetPosterUrl(string posterPath)
{
- if (string.IsNullOrEmpty(posterPath))
- {
- return null;
- }
-
- return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.BackdropSizes[^1], posterPath).ToString();
+ return GetUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath);
}
/// <summary>
@@ -505,27 +518,79 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <returns>The absolute URL.</returns>
public string GetProfileUrl(string actorProfilePath)
{
- if (string.IsNullOrEmpty(actorProfilePath))
- {
- return null;
- }
+ return GetUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath);
+ }
+
+ /// <summary>
+ /// Converts poster <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
+ /// </summary>
+ /// <param name="images">The input images.</param>
+ /// <param name="requestLanguage">The requested language.</param>
+ /// <param name="results">The collection to add the remote images into.</param>
+ public void ConvertPostersToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
+ {
+ ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.PosterSizes[^1], ImageType.Primary, requestLanguage, results);
+ }
- return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath).ToString();
+ /// <summary>
+ /// Converts backdrop <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
+ /// </summary>
+ /// <param name="images">The input images.</param>
+ /// <param name="requestLanguage">The requested language.</param>
+ /// <param name="results">The collection to add the remote images into.</param>
+ public void ConvertBackdropsToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
+ {
+ ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.BackdropSizes[^1], ImageType.Backdrop, requestLanguage, results);
}
/// <summary>
- /// Gets the absolute URL of the still image.
+ /// Converts profile <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
/// </summary>
- /// <param name="filePath">The relative URL of the still image.</param>
- /// <returns>The absolute URL.</returns>
- public string GetStillUrl(string filePath)
+ /// <param name="images">The input images.</param>
+ /// <param name="requestLanguage">The requested language.</param>
+ /// <param name="results">The collection to add the remote images into.</param>
+ public void ConvertProfilesToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
+ {
+ ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.ProfileSizes[^1], ImageType.Primary, requestLanguage, results);
+ }
+
+ /// <summary>
+ /// Converts still <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
+ /// </summary>
+ /// <param name="images">The input images.</param>
+ /// <param name="requestLanguage">The requested language.</param>
+ /// <param name="results">The collection to add the remote images into.</param>
+ public void ConvertStillsToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results)
{
- if (string.IsNullOrEmpty(filePath))
+ ConvertToRemoteImageInfo(images, _tmDbClient.Config.Images.StillSizes[^1], ImageType.Primary, requestLanguage, results);
+ }
+
+ /// <summary>
+ /// Converts <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s.
+ /// </summary>
+ /// <param name="images">The input images.</param>
+ /// <param name="size">The size of the image to fetch.</param>
+ /// <param name="type">The type of the image.</param>
+ /// <param name="requestLanguage">The requested language.</param>
+ /// <param name="results">The collection to add the remote images into.</param>
+ private void ConvertToRemoteImageInfo(List<ImageData> images, string size, ImageType type, string requestLanguage, List<RemoteImageInfo> results)
+ {
+ for (var i = 0; i < images.Count; i++)
{
- return null;
+ var image = images[i];
+ results.Add(new RemoteImageInfo
+ {
+ Url = GetUrl(size, image.FilePath),
+ CommunityRating = image.VoteAverage,
+ VoteCount = image.VoteCount,
+ Width = image.Width,
+ Height = image.Height,
+ Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, requestLanguage),
+ ProviderName = TmdbUtils.ProviderName,
+ Type = type,
+ RatingType = RatingType.Score
+ });
}
-
- return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.StillSizes[^1], filePath).ToString();
}
private Task EnsureClientConfigAsync()
@@ -540,7 +605,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
GC.SuppressFinalize(this);
}
-/// <summary>
+ /// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index b713736a0..a3a78103e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
@@ -31,11 +29,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public const string ApiKey = "4219e299c89411838049ab0dab19ebd5";
/// <summary>
- /// Maximum number of cast members to pull.
- /// </summary>
- public const int MaxCastMembers = 15;
-
- /// <summary>
/// The crew types to keep.
/// </summary>
public static readonly string[] WantedCrewTypes =
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index 63e78d15e..7f1665532 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -146,7 +148,7 @@ namespace MediaBrowser.Providers.Studios
Directory.CreateDirectory(Path.GetDirectoryName(file));
await using var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false);
- await using var fileStream = new FileStream(file, FileMode.Create);
+ await using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
@@ -172,19 +174,16 @@ namespace MediaBrowser.Providers.Studios
public IEnumerable<string> GetAvailableImages(string file)
{
- using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
+ using var fileStream = File.OpenRead(file);
using var reader = new StreamReader(fileStream);
- var lines = new List<string>();
foreach (var line in reader.ReadAllLines())
{
if (!string.IsNullOrWhiteSpace(line))
{
- lines.Add(line);
+ yield return line;
}
}
-
- return lines;
}
}
}
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index 0c791a2fe..c2b420c33 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System;
@@ -21,7 +23,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
-using static MediaBrowser.Model.IO.IODefaults;
namespace MediaBrowser.Providers.Subtitles
{
@@ -187,8 +188,8 @@ namespace MediaBrowser.Providers.Subtitles
{
var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia;
- using var stream = response.Stream;
- using var memoryStream = new MemoryStream();
+ await using var stream = response.Stream;
+ await using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
@@ -236,7 +237,7 @@ namespace MediaBrowser.Providers.Subtitles
foreach (var savePath in savePaths)
{
- _logger.LogInformation("Saving subtitles to {0}", savePath);
+ _logger.LogInformation("Saving subtitles to {SavePath}", savePath);
_monitor.ReportFileSystemChangeBeginning(savePath);
@@ -244,8 +245,10 @@ namespace MediaBrowser.Providers.Subtitles
{
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true);
+ var fileOptions = AsyncFile.WriteOptions;
+ fileOptions.Mode = FileMode.CreateNew;
+ fileOptions.PreallocationSize = stream.Length;
+ using var fs = new FileStream(savePath, fileOptions);
await stream.CopyToAsync(fs).ConfigureAwait(false);
return;
@@ -254,13 +257,9 @@ namespace MediaBrowser.Providers.Subtitles
{
// Bug in analyzer -- https://github.com/dotnet/roslyn-analyzers/issues/5160
#pragma warning disable CA1508
- exs ??= new List<Exception>()
- {
- ex
- };
+ (exs ??= new List<Exception>()).Add(ex);
#pragma warning restore CA1508
-
- }
+ }
finally
{
_monitor.ReportFileSystemChangeComplete(savePath, false);
@@ -276,7 +275,7 @@ namespace MediaBrowser.Providers.Subtitles
}
/// <inheritdoc />
- public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, CancellationToken cancellationToken)
+ public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, bool isAutomated, CancellationToken cancellationToken)
{
if (video.VideoType != VideoType.VideoFile)
{
@@ -310,7 +309,8 @@ namespace MediaBrowser.Providers.Subtitles
ProductionYear = video.ProductionYear,
ProviderIds = video.ProviderIds,
RuntimeTicks = video.RunTimeTicks,
- IsPerfectMatch = isPerfectMatch ?? false
+ IsPerfectMatch = isPerfectMatch ?? false,
+ IsAutomated = isAutomated
};
if (video is Episode episode)
diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
index 0f22f8a9b..b173fc7a3 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV
if (!string.Equals(item.Name, seasonZeroDisplayName, StringComparison.OrdinalIgnoreCase))
{
item.Name = seasonZeroDisplayName;
- updatedType = updatedType | ItemUpdateType.MetadataEdit;
+ updatedType |= ItemUpdateType.MetadataEdit;
}
}
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index dcb693408..9d223b4b6 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs
index 3cb18e424..087e4036a 100644
--- a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs
+++ b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.TV
public ExternalIdMediaType? Type => null;
/// <inheritdoc />
- public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}";
+ public string? UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}";
/// <inheritdoc />
public bool Supports(IHasProviderIds item) => item is Series;