mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-15 12:10:47 +01:00
Remove "download images in advance" option
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -25,7 +27,7 @@ namespace MediaBrowser.Providers.Books
|
||||
protected override void MergeData(
|
||||
MetadataResult<AudioBook> source,
|
||||
MetadataResult<AudioBook> target,
|
||||
MetadataFields[] lockedFields,
|
||||
MetadataField[] lockedFields,
|
||||
bool replaceData,
|
||||
bool mergeMetadataSettings)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Books
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -43,7 +45,7 @@ namespace MediaBrowser.Providers.BoxSets
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Channels
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -23,7 +24,7 @@ namespace MediaBrowser.Providers.Folders
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -26,7 +28,7 @@ namespace MediaBrowser.Providers.Folders
|
||||
public override int Order => 10;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Folder> source, MetadataResult<Folder> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Folder> source, MetadataResult<Folder> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Folders
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Genres
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Genre> source, MetadataResult<Genre> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Genre> source, MetadataResult<Genre> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.LiveTv
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -9,30 +11,33 @@ using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
using Season = MediaBrowser.Controller.Entities.TV.Season;
|
||||
|
||||
namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ImageSaver
|
||||
/// Class ImageSaver.
|
||||
/// </summary>
|
||||
public class ImageSaver
|
||||
{
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// The _config
|
||||
/// The _config.
|
||||
/// </summary>
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
/// <summary>
|
||||
/// The _directory watchers
|
||||
/// The _directory watchers.
|
||||
/// </summary>
|
||||
private readonly ILibraryMonitor _libraryMonitor;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
@@ -53,6 +58,16 @@ namespace MediaBrowser.Providers.Manager
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private bool EnableExtraThumbsDuplication
|
||||
{
|
||||
get
|
||||
{
|
||||
var config = _config.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
|
||||
|
||||
return config.EnableExtraThumbsDuplication;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the image.
|
||||
/// </summary>
|
||||
@@ -63,7 +78,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <param name="imageIndex">Index of the image.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentNullException">mimeType</exception>
|
||||
/// <exception cref="ArgumentNullException">mimeType.</exception>
|
||||
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
return SaveImage(item, source, mimeType, type, imageIndex, null, cancellationToken);
|
||||
@@ -78,11 +93,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && !(item is Audio);
|
||||
|
||||
if (item is User)
|
||||
{
|
||||
saveLocally = true;
|
||||
}
|
||||
|
||||
if (type != ImageType.Primary && item is Episode)
|
||||
{
|
||||
saveLocally = false;
|
||||
@@ -105,6 +115,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (saveLocallyWithMedia.HasValue && !saveLocallyWithMedia.Value)
|
||||
{
|
||||
saveLocally = saveLocallyWithMedia.Value;
|
||||
@@ -122,35 +133,40 @@ namespace MediaBrowser.Providers.Manager
|
||||
var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
|
||||
|
||||
// If there are more than one output paths, the stream will need to be seekable
|
||||
var memoryStream = new MemoryStream();
|
||||
using (source)
|
||||
if (paths.Length > 1 && !source.CanSeek)
|
||||
{
|
||||
await source.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
}
|
||||
var memoryStream = new MemoryStream();
|
||||
await using (source.ConfigureAwait(false))
|
||||
{
|
||||
await source.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
source = memoryStream;
|
||||
source = memoryStream;
|
||||
}
|
||||
|
||||
var currentImage = GetCurrentImage(item, type, index);
|
||||
var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile;
|
||||
var currentImagePath = currentImage == null ? null : currentImage.Path;
|
||||
var currentImagePath = currentImage?.Path;
|
||||
|
||||
var savedPaths = new List<string>();
|
||||
|
||||
using (source)
|
||||
await using (source.ConfigureAwait(false))
|
||||
{
|
||||
var currentPathIndex = 0;
|
||||
|
||||
foreach (var path in paths)
|
||||
for (int i = 0; i < paths.Length; i++)
|
||||
{
|
||||
source.Position = 0;
|
||||
if (i != 0)
|
||||
{
|
||||
source.Position = 0;
|
||||
}
|
||||
|
||||
string retryPath = null;
|
||||
if (paths.Length == retryPaths.Length)
|
||||
{
|
||||
retryPath = retryPaths[currentPathIndex];
|
||||
retryPath = retryPaths[i];
|
||||
}
|
||||
var savedPath = await SaveImageToLocation(source, path, retryPath, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var savedPath = await SaveImageToLocation(source, paths[i], retryPath, cancellationToken).ConfigureAwait(false);
|
||||
savedPaths.Add(savedPath);
|
||||
currentPathIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +188,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -181,6 +196,11 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveImage(Stream source, string path)
|
||||
{
|
||||
await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<string> SaveImageToLocation(Stream source, string path, string retryPath, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -217,7 +237,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
source.Position = 0;
|
||||
await SaveImageToLocation(source, retryPath, cancellationToken).ConfigureAwait(false);
|
||||
return retryPath;
|
||||
}
|
||||
@@ -244,9 +263,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
_fileSystem.SetAttributes(path, false, false);
|
||||
|
||||
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
|
||||
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
|
||||
{
|
||||
await source.CopyToAsync(fs, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_config.Configuration.SaveMetadataHidden)
|
||||
@@ -302,7 +321,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// imageIndex
|
||||
/// or
|
||||
/// imageIndex
|
||||
/// imageIndex.
|
||||
/// </exception>
|
||||
private ItemImageInfo GetCurrentImage(BaseItem item, ImageType type, int imageIndex)
|
||||
{
|
||||
@@ -318,7 +337,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <param name="path">The path.</param>
|
||||
/// <exception cref="ArgumentNullException">imageIndex
|
||||
/// or
|
||||
/// imageIndex</exception>
|
||||
/// imageIndex.
|
||||
/// </exception>
|
||||
private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path)
|
||||
{
|
||||
item.SetImagePath(type, imageIndex ?? 0, _fileSystem.GetFileInfo(path));
|
||||
@@ -336,7 +356,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// imageIndex
|
||||
/// or
|
||||
/// imageIndex
|
||||
/// imageIndex.
|
||||
/// </exception>
|
||||
private string GetStandardSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally)
|
||||
{
|
||||
@@ -345,7 +365,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Unable to determine image file extension from mime type {0}", mimeType));
|
||||
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to determine image file extension from mime type {0}", mimeType));
|
||||
}
|
||||
|
||||
if (type == ImageType.Thumb && saveLocally)
|
||||
@@ -439,7 +459,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
|
||||
}
|
||||
|
||||
else if (item.IsInMixedFolder)
|
||||
{
|
||||
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
|
||||
@@ -458,6 +477,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
filename = folderName;
|
||||
}
|
||||
|
||||
path = Path.Combine(item.GetInternalMetadataPath(), filename + extension);
|
||||
}
|
||||
|
||||
@@ -490,7 +510,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <param name="imageIndex">Index of the image.</param>
|
||||
/// <param name="mimeType">Type of the MIME.</param>
|
||||
/// <returns>IEnumerable{System.String}.</returns>
|
||||
/// <exception cref="ArgumentNullException">imageIndex</exception>
|
||||
/// <exception cref="ArgumentNullException">imageIndex.</exception>
|
||||
private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType)
|
||||
{
|
||||
var season = item as Season;
|
||||
@@ -549,6 +569,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
@@ -593,16 +614,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) };
|
||||
}
|
||||
|
||||
private bool EnableExtraThumbsDuplication
|
||||
{
|
||||
get
|
||||
{
|
||||
var config = _config.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
|
||||
|
||||
return config.EnableExtraThumbsDuplication;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the save path for item in mixed folder.
|
||||
/// </summary>
|
||||
@@ -617,6 +628,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
imageFilename = "poster";
|
||||
}
|
||||
|
||||
var folder = Path.GetDirectoryName(item.Path);
|
||||
|
||||
return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -26,6 +28,22 @@ namespace MediaBrowser.Providers.Manager
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Image types that are only one per item.
|
||||
/// </summary>
|
||||
private readonly ImageType[] _singularImages =
|
||||
{
|
||||
ImageType.Primary,
|
||||
ImageType.Art,
|
||||
ImageType.Banner,
|
||||
ImageType.Box,
|
||||
ImageType.BoxRear,
|
||||
ImageType.Disc,
|
||||
ImageType.Logo,
|
||||
ImageType.Menu,
|
||||
ImageType.Thumb
|
||||
};
|
||||
|
||||
public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -52,12 +70,18 @@ namespace MediaBrowser.Providers.Manager
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
public async Task<RefreshResult> RefreshImages(BaseItem item, LibraryOptions libraryOptions, List<IImageProvider> providers, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
|
||||
public async Task<RefreshResult> RefreshImages(
|
||||
BaseItem item,
|
||||
LibraryOptions libraryOptions,
|
||||
List<IImageProvider> providers,
|
||||
ImageRefreshOptions refreshOptions,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
|
||||
{
|
||||
ClearImages(item, ImageType.Backdrop);
|
||||
}
|
||||
|
||||
if (refreshOptions.IsReplacingImage(ImageType.Screenshot))
|
||||
{
|
||||
ClearImages(item, ImageType.Screenshot);
|
||||
@@ -75,19 +99,15 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
var remoteProvider = provider as IRemoteImageProvider;
|
||||
|
||||
if (remoteProvider != null)
|
||||
if (provider is IRemoteImageProvider remoteProvider)
|
||||
{
|
||||
await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, typeOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
var dynamicImageProvider = provider as IDynamicImageProvider;
|
||||
|
||||
if (dynamicImageProvider != null)
|
||||
if (provider is IDynamicImageProvider dynamicImageProvider)
|
||||
{
|
||||
await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, libraryOptions, downloadedImages, result, cancellationToken).ConfigureAwait(false);
|
||||
await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, downloadedImages, result, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +117,11 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <summary>
|
||||
/// Refreshes from provider.
|
||||
/// </summary>
|
||||
private async Task RefreshFromProvider(BaseItem item,
|
||||
private async Task RefreshFromProvider(
|
||||
BaseItem item,
|
||||
IDynamicImageProvider provider,
|
||||
ImageRefreshOptions refreshOptions,
|
||||
TypeOptions savedOptions,
|
||||
LibraryOptions libraryOptions,
|
||||
ICollection<ImageType> downloadedImages,
|
||||
RefreshResult result,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -112,7 +132,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
foreach (var imageType in images)
|
||||
{
|
||||
if (!IsEnabled(savedOptions, imageType, item)) continue;
|
||||
if (!IsEnabled(savedOptions, imageType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType)))
|
||||
{
|
||||
@@ -127,12 +150,13 @@ namespace MediaBrowser.Providers.Manager
|
||||
if (response.Protocol == MediaProtocol.Http)
|
||||
{
|
||||
_logger.LogDebug("Setting image url into item {0}", item.Id);
|
||||
item.SetImage(new ItemImageInfo
|
||||
{
|
||||
Path = response.Path,
|
||||
Type = imageType
|
||||
|
||||
}, 0);
|
||||
item.SetImage(
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = response.Path,
|
||||
Type = imageType
|
||||
},
|
||||
0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -151,7 +175,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
|
||||
downloadedImages.Add(imageType);
|
||||
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
|
||||
result.UpdateType |= ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,29 +191,13 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image types that are only one per item
|
||||
/// </summary>
|
||||
private readonly ImageType[] _singularImages =
|
||||
{
|
||||
ImageType.Primary,
|
||||
ImageType.Art,
|
||||
ImageType.Banner,
|
||||
ImageType.Box,
|
||||
ImageType.BoxRear,
|
||||
ImageType.Disc,
|
||||
ImageType.Logo,
|
||||
ImageType.Menu,
|
||||
ImageType.Thumb
|
||||
};
|
||||
|
||||
private bool HasImage(BaseItem item, ImageType type)
|
||||
{
|
||||
return item.HasImage(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if an item already contains the given images
|
||||
/// Determines if an item already contains the given images.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="images">The images.</param>
|
||||
@@ -221,6 +229,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// Refreshes from provider.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="libraryOptions">The library options.</param>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="refreshOptions">The refresh options.</param>
|
||||
/// <param name="savedOptions">The saved options.</param>
|
||||
@@ -230,7 +239,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task RefreshFromProvider(BaseItem item, LibraryOptions libraryOptions,
|
||||
private async Task RefreshFromProvider(
|
||||
BaseItem item,
|
||||
LibraryOptions libraryOptions,
|
||||
IRemoteImageProvider provider,
|
||||
ImageRefreshOptions refreshOptions,
|
||||
TypeOptions savedOptions,
|
||||
@@ -256,20 +267,24 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
_logger.LogDebug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
|
||||
|
||||
var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery
|
||||
{
|
||||
ProviderName = provider.Name,
|
||||
IncludeAllLanguages = false,
|
||||
IncludeDisabledProviders = false,
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
var images = await _providerManager.GetAvailableRemoteImages(
|
||||
item,
|
||||
new RemoteImageQuery(provider.Name)
|
||||
{
|
||||
IncludeAllLanguages = false,
|
||||
IncludeDisabledProviders = false,
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var list = images.ToList();
|
||||
int minWidth;
|
||||
|
||||
foreach (var imageType in _singularImages)
|
||||
{
|
||||
if (!IsEnabled(savedOptions, imageType, item)) continue;
|
||||
if (!IsEnabled(savedOptions, imageType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType)))
|
||||
{
|
||||
@@ -286,8 +301,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
|
||||
await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var hasScreenshots = item as IHasScreenshots;
|
||||
if (hasScreenshots != null)
|
||||
if (item is IHasScreenshots hasScreenshots)
|
||||
{
|
||||
minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
|
||||
await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
|
||||
@@ -304,7 +318,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEnabled(TypeOptions options, ImageType type, BaseItem item)
|
||||
private bool IsEnabled(TypeOptions options, ImageType type)
|
||||
{
|
||||
return options.IsEnabled(type);
|
||||
}
|
||||
@@ -329,7 +343,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +378,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo);
|
||||
|
||||
// If date changed then we need to reset saved image dimensions
|
||||
@@ -428,7 +440,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
return changed;
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadImage(BaseItem item, LibraryOptions libraryOptions,
|
||||
private async Task<bool> DownloadImage(
|
||||
BaseItem item,
|
||||
LibraryOptions libraryOptions,
|
||||
IRemoteImageProvider provider,
|
||||
RefreshResult result,
|
||||
IEnumerable<RemoteImageInfo> images,
|
||||
@@ -440,10 +454,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
.Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth))
|
||||
.ToList();
|
||||
|
||||
if (EnableImageStub(item, type, libraryOptions) && eligibleImages.Count > 0)
|
||||
if (EnableImageStub(item, libraryOptions) && eligibleImages.Count > 0)
|
||||
{
|
||||
SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
|
||||
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
|
||||
result.UpdateType |= ItemUpdateType.ImageUpdate;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -453,20 +467,29 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
try
|
||||
{
|
||||
var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
|
||||
await _providerManager.SaveImage(item, response.Content, response.ContentType, type, null, cancellationToken).ConfigureAwait(false);
|
||||
await _providerManager.SaveImage(
|
||||
item,
|
||||
stream,
|
||||
response.Content.Headers.ContentType.MediaType,
|
||||
type,
|
||||
null,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
|
||||
result.UpdateType |= ItemUpdateType.ImageUpdate;
|
||||
return true;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
// Sometimes providers send back bad url's. Just move to the next image
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
||||
if (ex.StatusCode.HasValue
|
||||
&& (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -474,7 +497,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool EnableImageStub(BaseItem item, ImageType type, LibraryOptions libraryOptions)
|
||||
private bool EnableImageStub(BaseItem item, LibraryOptions libraryOptions)
|
||||
{
|
||||
if (item is LiveTvProgram)
|
||||
{
|
||||
@@ -494,7 +517,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We always want to use prefetched images
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -507,14 +530,15 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls, int newIndex)
|
||||
{
|
||||
var path = string.Join("|", urls.Take(1).ToArray());
|
||||
var path = string.Join('|', urls.Take(1));
|
||||
|
||||
item.SetImage(new ItemImageInfo
|
||||
{
|
||||
Path = path,
|
||||
Type = imageType
|
||||
|
||||
}, newIndex);
|
||||
item.SetImage(
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = path,
|
||||
Type = imageType
|
||||
},
|
||||
newIndex);
|
||||
}
|
||||
|
||||
private async Task DownloadBackdrops(BaseItem item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
|
||||
@@ -533,23 +557,23 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
var url = image.Url;
|
||||
|
||||
if (EnableImageStub(item, imageType, libraryOptions))
|
||||
if (EnableImageStub(item, libraryOptions))
|
||||
{
|
||||
SaveImageStub(item, imageType, new[] { url });
|
||||
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
|
||||
result.UpdateType |= ItemUpdateType.ImageUpdate;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// If there's already an image of the same size, skip it
|
||||
if (response.ContentLength.HasValue)
|
||||
if (response.Content.Headers.ContentLength.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.ContentLength.Value))
|
||||
if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.Content.Headers.ContentLength.Value))
|
||||
{
|
||||
response.Content.Dispose();
|
||||
continue;
|
||||
@@ -561,16 +585,25 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
await _providerManager.SaveImage(item, response.Content, response.ContentType, imageType, null, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
await _providerManager.SaveImage(
|
||||
item,
|
||||
stream,
|
||||
response.Content.Headers.ContentType.MediaType,
|
||||
imageType,
|
||||
null,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
// Sometimes providers send back bad urls. Just move onto the next image
|
||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
||||
if (ex.StatusCode.HasValue
|
||||
&& (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -19,13 +21,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
where TItemType : BaseItem, IHasLookupInfo<TIdType>, new()
|
||||
where TIdType : ItemLookupInfo, new()
|
||||
{
|
||||
protected readonly IServerConfigurationManager ServerConfigurationManager;
|
||||
protected readonly ILogger Logger;
|
||||
protected readonly IProviderManager ProviderManager;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
protected readonly ILibraryManager LibraryManager;
|
||||
|
||||
protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager)
|
||||
protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger<MetadataService<TItemType, TIdType>> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager)
|
||||
{
|
||||
ServerConfigurationManager = serverConfigurationManager;
|
||||
Logger = logger;
|
||||
@@ -34,6 +30,26 @@ namespace MediaBrowser.Providers.Manager
|
||||
LibraryManager = libraryManager;
|
||||
}
|
||||
|
||||
protected IServerConfigurationManager ServerConfigurationManager { get; }
|
||||
|
||||
protected ILogger<MetadataService<TItemType, TIdType>> Logger { get; }
|
||||
|
||||
protected IProviderManager ProviderManager { get; }
|
||||
|
||||
protected IFileSystem FileSystem { get; }
|
||||
|
||||
protected ILibraryManager LibraryManager { get; }
|
||||
|
||||
protected virtual bool EnableUpdatingPremiereDateFromChildren => false;
|
||||
|
||||
protected virtual bool EnableUpdatingGenresFromChildren => false;
|
||||
|
||||
protected virtual bool EnableUpdatingStudiosFromChildren => false;
|
||||
|
||||
protected virtual bool EnableUpdatingOfficialRatingFromChildren => false;
|
||||
|
||||
public virtual int Order => 0;
|
||||
|
||||
private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService)
|
||||
{
|
||||
try
|
||||
@@ -50,7 +66,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
public async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var itemOfType = (TItemType)item;
|
||||
var config = ProviderManager.GetMetadataOptions(item);
|
||||
|
||||
var updateType = ItemUpdateType.None;
|
||||
var requiresRefresh = false;
|
||||
@@ -84,7 +99,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
// Always validate images and check for new locally stored ones.
|
||||
if (itemImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions.DirectoryService))
|
||||
{
|
||||
updateType = updateType | ItemUpdateType.ImageUpdate;
|
||||
updateType |= ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -100,7 +115,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
bool hasRefreshedMetadata = true;
|
||||
bool hasRefreshedImages = true;
|
||||
var isFirstRefresh = item.DateLastRefreshed == default(DateTime);
|
||||
var isFirstRefresh = item.DateLastRefreshed == default;
|
||||
|
||||
// Next run metadata providers
|
||||
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||
@@ -112,7 +127,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
|
||||
{
|
||||
updateType = updateType | ItemUpdateType.MetadataImport;
|
||||
updateType |= ItemUpdateType.MetadataImport;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,12 +140,12 @@ namespace MediaBrowser.Providers.Manager
|
||||
ApplySearchResult(id, refreshOptions.SearchResult);
|
||||
}
|
||||
|
||||
//await FindIdentities(id, cancellationToken).ConfigureAwait(false);
|
||||
// await FindIdentities(id, cancellationToken).ConfigureAwait(false);
|
||||
id.IsAutomated = refreshOptions.IsAutomated;
|
||||
|
||||
var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
updateType = updateType | result.UpdateType;
|
||||
updateType |= result.UpdateType;
|
||||
if (result.Failures > 0)
|
||||
{
|
||||
hasRefreshedMetadata = false;
|
||||
@@ -145,9 +160,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (providers.Count > 0)
|
||||
{
|
||||
var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
|
||||
var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
updateType = updateType | result.UpdateType;
|
||||
updateType |= result.UpdateType;
|
||||
if (result.Failures > 0)
|
||||
{
|
||||
hasRefreshedImages = false;
|
||||
@@ -156,7 +171,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
|
||||
var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
|
||||
updateType = updateType | beforeSaveResult;
|
||||
updateType |= beforeSaveResult;
|
||||
|
||||
// Save if changes were made, or it's never been saved before
|
||||
if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
|
||||
@@ -173,7 +188,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
// If any of these properties are set then make sure the updateType is not None, just to force everything to save
|
||||
if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
|
||||
{
|
||||
updateType = updateType | ItemUpdateType.MetadataDownload;
|
||||
updateType |= ItemUpdateType.MetadataDownload;
|
||||
}
|
||||
|
||||
if (hasRefreshedMetadata && hasRefreshedImages)
|
||||
@@ -182,11 +197,11 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
else
|
||||
{
|
||||
item.DateLastRefreshed = default(DateTime);
|
||||
item.DateLastRefreshed = default;
|
||||
}
|
||||
|
||||
// Save to database
|
||||
SaveItem(metadataResult, libraryOptions, updateType, cancellationToken);
|
||||
await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
@@ -201,25 +216,26 @@ namespace MediaBrowser.Providers.Manager
|
||||
lookupInfo.Year = result.ProductionYear;
|
||||
}
|
||||
|
||||
protected void SaveItem(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
protected async Task SaveItemAsync(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
|
||||
{
|
||||
if (result.Item.SupportsPeople && result.People != null)
|
||||
{
|
||||
var baseItem = result.Item;
|
||||
|
||||
LibraryManager.UpdatePeople(baseItem, result.People);
|
||||
SavePeopleMetadata(result.People, libraryOptions, cancellationToken);
|
||||
await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
result.Item.UpdateToRepository(reason, cancellationToken);
|
||||
|
||||
await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void SavePeopleMetadata(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
|
||||
private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var person in people)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (person.ProviderIds.Any() || !string.IsNullOrWhiteSpace(person.ImageUrl))
|
||||
if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl))
|
||||
{
|
||||
var updateType = ItemUpdateType.MetadataDownload;
|
||||
|
||||
@@ -236,21 +252,21 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
|
||||
{
|
||||
AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken);
|
||||
await AddPersonImageAsync(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
saveEntity = true;
|
||||
updateType = updateType | ItemUpdateType.ImageUpdate;
|
||||
updateType |= ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
|
||||
if (saveEntity)
|
||||
{
|
||||
personEntity.UpdateToRepository(updateType, cancellationToken);
|
||||
await personEntity.UpdateToRepositoryAsync(updateType, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
|
||||
private async Task AddPersonImageAsync(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -262,11 +278,13 @@ namespace MediaBrowser.Providers.Manager
|
||||
Logger.LogError(ex, "Error in AddPersonImage");
|
||||
}
|
||||
|
||||
personEntity.SetImage(new ItemImageInfo
|
||||
{
|
||||
Path = imageUrl,
|
||||
Type = ImageType.Primary
|
||||
}, 0);
|
||||
personEntity.SetImage(
|
||||
new ItemImageInfo
|
||||
{
|
||||
Path = imageUrl,
|
||||
Type = ImageType.Primary
|
||||
},
|
||||
0);
|
||||
}
|
||||
|
||||
protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
||||
@@ -276,7 +294,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Befores the save.
|
||||
/// Before the save.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
|
||||
@@ -321,6 +339,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
{
|
||||
@@ -333,13 +352,12 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
protected virtual IList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
|
||||
{
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
if (item is Folder folder)
|
||||
{
|
||||
return folder.GetRecursiveChildren();
|
||||
}
|
||||
|
||||
return new List<BaseItem>();
|
||||
return Array.Empty<BaseItem>();
|
||||
}
|
||||
|
||||
protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType)
|
||||
@@ -386,7 +404,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
if (!child.IsFolder)
|
||||
{
|
||||
ticks += (child.RunTimeTicks ?? 0);
|
||||
ticks += child.RunTimeTicks ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,6 +437,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
dateLastMediaAdded = childDateCreated;
|
||||
}
|
||||
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
@@ -433,14 +452,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
return updateType;
|
||||
}
|
||||
|
||||
protected virtual bool EnableUpdatingPremiereDateFromChildren => false;
|
||||
|
||||
protected virtual bool EnableUpdatingGenresFromChildren => false;
|
||||
|
||||
protected virtual bool EnableUpdatingStudiosFromChildren => false;
|
||||
|
||||
protected virtual bool EnableUpdatingOfficialRatingFromChildren => false;
|
||||
|
||||
private ItemUpdateType UpdatePremiereDate(TItemType item, IList<BaseItem> children)
|
||||
{
|
||||
var updateType = ItemUpdateType.None;
|
||||
@@ -483,7 +494,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
var updateType = ItemUpdateType.None;
|
||||
|
||||
if (!item.LockedFields.Contains(MetadataFields.Genres))
|
||||
if (!item.LockedFields.Contains(MetadataField.Genres))
|
||||
{
|
||||
var currentList = item.Genres;
|
||||
|
||||
@@ -504,7 +515,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
var updateType = ItemUpdateType.None;
|
||||
|
||||
if (!item.LockedFields.Contains(MetadataFields.Studios))
|
||||
if (!item.LockedFields.Contains(MetadataField.Studios))
|
||||
{
|
||||
var currentList = item.Studios;
|
||||
|
||||
@@ -525,7 +536,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
var updateType = ItemUpdateType.None;
|
||||
|
||||
if (!item.LockedFields.Contains(MetadataFields.OfficialRating))
|
||||
if (!item.LockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
if (item.UpdateRatingToItems(children))
|
||||
{
|
||||
@@ -649,7 +660,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
return type == typeof(TItemType);
|
||||
}
|
||||
|
||||
protected virtual async Task<RefreshResult> RefreshWithProviders(MetadataResult<TItemType> metadata,
|
||||
protected virtual async Task<RefreshResult> RefreshWithProviders(
|
||||
MetadataResult<TItemType> metadata,
|
||||
TIdType id,
|
||||
MetadataRefreshOptions options,
|
||||
List<IMetadataProvider> providers,
|
||||
@@ -715,7 +727,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
userDataList.AddRange(localItem.UserDataList);
|
||||
}
|
||||
|
||||
MergeData(localItem, temp, new MetadataFields[] { }, !options.ReplaceAllMetadata, true);
|
||||
MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
|
||||
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
|
||||
|
||||
// Only one local provider allowed per item
|
||||
@@ -723,6 +735,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
hasLocalMetadata = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -763,20 +776,20 @@ namespace MediaBrowser.Providers.Manager
|
||||
else
|
||||
{
|
||||
// TODO: If the new metadata from above has some blank data, this can cause old data to get filled into those empty fields
|
||||
MergeData(metadata, temp, new MetadataFields[] { }, false, false);
|
||||
MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false);
|
||||
MergeData(temp, metadata, item.LockedFields, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;
|
||||
// var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0;
|
||||
|
||||
foreach (var provider in customProviders.Where(i => !(i is IPreRefreshProvider)))
|
||||
{
|
||||
await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//ImportUserData(item, userDataList, cancellationToken);
|
||||
// ImportUserData(item, userDataList, cancellationToken);
|
||||
|
||||
return refreshResult;
|
||||
}
|
||||
@@ -797,7 +810,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
try
|
||||
{
|
||||
refreshResult.UpdateType = refreshResult.UpdateType | await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
|
||||
refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -840,7 +853,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
result.Provider = provider.Name;
|
||||
|
||||
MergeData(result, temp, new MetadataFields[] { }, false, false);
|
||||
MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
|
||||
MergeNewData(temp.Item, id);
|
||||
|
||||
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
|
||||
@@ -865,15 +878,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
return refreshResult;
|
||||
}
|
||||
|
||||
private string NormalizeLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(language))
|
||||
{
|
||||
return "en";
|
||||
}
|
||||
return language;
|
||||
}
|
||||
|
||||
private void MergeNewData(TItemType source, TIdType lookupInfo)
|
||||
{
|
||||
// Copy new provider id's that may have been obtained
|
||||
@@ -889,24 +893,23 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void MergeData(MetadataResult<TItemType> source,
|
||||
protected abstract void MergeData(
|
||||
MetadataResult<TItemType> source,
|
||||
MetadataResult<TItemType> target,
|
||||
MetadataFields[] lockedFields,
|
||||
MetadataField[] lockedFields,
|
||||
bool replaceData,
|
||||
bool mergeMetadataSettings);
|
||||
|
||||
public virtual int Order => 0;
|
||||
|
||||
private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService)
|
||||
{
|
||||
try
|
||||
{
|
||||
var hasChanged = changeMonitor.HasChanged(item, directoryService);
|
||||
|
||||
//if (hasChanged)
|
||||
//{
|
||||
// logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name);
|
||||
//}
|
||||
if (hasChanged)
|
||||
{
|
||||
Logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name);
|
||||
}
|
||||
|
||||
return hasChanged;
|
||||
}
|
||||
@@ -917,11 +920,4 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RefreshResult
|
||||
{
|
||||
public ItemUpdateType UpdateType { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public int Failures { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -16,109 +18,118 @@ using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Priority_Queue;
|
||||
using Book = MediaBrowser.Controller.Entities.Book;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
using Season = MediaBrowser.Controller.Entities.TV.Season;
|
||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||
|
||||
namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ProviderManager
|
||||
/// Class ProviderManager.
|
||||
/// </summary>
|
||||
public class ProviderManager : IProviderManager, IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly object _refreshQueueLock = new object();
|
||||
private readonly ILogger<ProviderManager> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryMonitor _libraryMonitor;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
|
||||
private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
|
||||
new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
|
||||
|
||||
private IImageProvider[] ImageProviders { get; set; }
|
||||
|
||||
private IMetadataService[] _metadataServices = { };
|
||||
private IMetadataProvider[] _metadataProviders = { };
|
||||
private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>();
|
||||
private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>();
|
||||
private IEnumerable<IMetadataSaver> _savers;
|
||||
|
||||
private IExternalId[] _externalIds;
|
||||
|
||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
|
||||
public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
|
||||
public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
|
||||
private bool _isProcessingRefreshQueue;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProviderManager" /> class.
|
||||
/// Initializes a new instance of the <see cref="ProviderManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpClientFactory">The Http client factory.</param>
|
||||
/// <param name="subtitleManager">The subtitle manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="libraryMonitor">The library monitor.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
/// <param name="appPaths">The server application paths.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
public ProviderManager(
|
||||
IHttpClient httpClient,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ISubtitleManager subtitleManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
ILogger<ProviderManager> logger,
|
||||
IFileSystem fileSystem,
|
||||
IServerApplicationPaths appPaths,
|
||||
ILibraryManager libraryManager,
|
||||
IJsonSerializer json)
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configurationManager = configurationManager;
|
||||
_libraryMonitor = libraryMonitor;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
_libraryManager = libraryManager;
|
||||
_json = json;
|
||||
_subtitleManager = subtitleManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata providers.
|
||||
/// </summary>
|
||||
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
|
||||
IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
|
||||
IEnumerable<IExternalId> externalIds)
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
|
||||
|
||||
private IImageProvider[] ImageProviders { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddParts(
|
||||
IEnumerable<IImageProvider> imageProviders,
|
||||
IEnumerable<IMetadataService> metadataServices,
|
||||
IEnumerable<IMetadataProvider> metadataProviders,
|
||||
IEnumerable<IMetadataSaver> metadataSavers,
|
||||
IEnumerable<IExternalId> externalIds)
|
||||
{
|
||||
ImageProviders = imageProviders.ToArray();
|
||||
|
||||
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
|
||||
_metadataProviders = metadataProviders.ToArray();
|
||||
_externalIds = externalIds.OrderBy(i => i.Name).ToArray();
|
||||
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
|
||||
|
||||
_savers = metadataSavers.Where(i =>
|
||||
{
|
||||
var configurable = i as IConfigurableProvider;
|
||||
|
||||
return configurable == null || configurable.IsEnabled;
|
||||
}).ToArray();
|
||||
_savers = metadataSavers
|
||||
.Where(i => !(i is IConfigurableProvider configurable) || configurable.IsEnabled)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
IMetadataService service = null;
|
||||
var type = item.GetType();
|
||||
|
||||
foreach (var current in _metadataServices)
|
||||
{
|
||||
if (current.CanRefreshPrimary(type))
|
||||
{
|
||||
service = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type));
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
@@ -141,35 +152,58 @@ namespace MediaBrowser.Providers.Manager
|
||||
return Task.FromResult(ItemUpdateType.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var response = await _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url,
|
||||
BufferContent = false
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Workaround for tvheadend channel icons
|
||||
// TODO: Isolate this hack into the tvh plugin
|
||||
if (string.IsNullOrEmpty(response.ContentType))
|
||||
throw new HttpException("Invalid image received.")
|
||||
{
|
||||
if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
response.ContentType = "image/png";
|
||||
}
|
||||
}
|
||||
|
||||
await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
|
||||
StatusCode = response.StatusCode
|
||||
};
|
||||
}
|
||||
|
||||
var contentType = response.Content.Headers.ContentType.MediaType;
|
||||
|
||||
// Workaround for tvheadend channel icons
|
||||
// TODO: Isolate this hack into the tvh plugin
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
contentType = "image/png";
|
||||
}
|
||||
}
|
||||
|
||||
// thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...
|
||||
if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new HttpException("Invalid image received.")
|
||||
{
|
||||
StatusCode = HttpStatusCode.NotFound
|
||||
};
|
||||
}
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
await SaveImage(
|
||||
item,
|
||||
stream,
|
||||
contentType,
|
||||
type,
|
||||
imageIndex,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(source))
|
||||
@@ -182,6 +216,14 @@ namespace MediaBrowser.Providers.Manager
|
||||
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task SaveImage(Stream source, string mimeType, string path)
|
||||
{
|
||||
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
|
||||
.SaveImage(source, path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
|
||||
@@ -201,7 +243,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
languages.Add(preferredLanguage);
|
||||
}
|
||||
|
||||
var tasks = providers.Select(i => GetImages(item, cancellationToken, i, languages, query.ImageType));
|
||||
var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
@@ -212,12 +254,17 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// Gets the images.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="preferredLanguages">The preferred languages.</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, CancellationToken cancellationToken, IRemoteImageProvider provider, List<string> preferredLanguages, ImageType? type = null)
|
||||
private async Task<IEnumerable<RemoteImageInfo>> GetImages(
|
||||
BaseItem item,
|
||||
IRemoteImageProvider provider,
|
||||
IReadOnlyCollection<string> preferredLanguages,
|
||||
CancellationToken cancellationToken,
|
||||
ImageType? type = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -243,25 +290,23 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{0} failed in GetImageInfos for type {1}", provider.GetType().Name, item.GetType().Name);
|
||||
_logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
|
||||
return new List<RemoteImageInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported image providers.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>IEnumerable{IImageProvider}.</returns>
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
|
||||
{
|
||||
return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo
|
||||
{
|
||||
Name = i.Name,
|
||||
SupportedImages = i.GetSupportedImages(item).ToArray()
|
||||
});
|
||||
return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image providers for the provided item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="refreshOptions">The image refresh options.</param>
|
||||
/// <returns>The image providers for the item.</returns>
|
||||
public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
|
||||
{
|
||||
return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
|
||||
@@ -275,7 +320,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
|
||||
var typeFetcherOrder = typeOptions?.ImageFetcherOrder;
|
||||
|
||||
return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, options, refreshOptions, includeDisabled))
|
||||
return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled))
|
||||
.OrderBy(i =>
|
||||
{
|
||||
// See if there's a user-defined order
|
||||
@@ -296,6 +341,13 @@ namespace MediaBrowser.Providers.Manager
|
||||
.ThenBy(GetOrder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata providers for the provided item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="libraryOptions">The library options.</param>
|
||||
/// <typeparam name="T">The type of metadata provider.</typeparam>
|
||||
/// <returns>The metadata providers.</returns>
|
||||
public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
|
||||
where T : BaseItem
|
||||
{
|
||||
@@ -311,7 +363,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
var currentOptions = globalMetadataOptions;
|
||||
|
||||
return _metadataProviders.OfType<IMetadataProvider<T>>()
|
||||
.Where(i => CanRefresh(i, item, libraryOptions, currentOptions, includeDisabled, forceEnableInternetMetadata))
|
||||
.Where(i => CanRefresh(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata))
|
||||
.OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions))
|
||||
.ThenBy(GetDefaultOrder);
|
||||
}
|
||||
@@ -321,14 +373,20 @@ namespace MediaBrowser.Providers.Manager
|
||||
var options = GetMetadataOptions(item);
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||
|
||||
return GetImageProviders(item, libraryOptions, options,
|
||||
new ImageRefreshOptions(
|
||||
new DirectoryService(_fileSystem)),
|
||||
includeDisabled)
|
||||
.OfType<IRemoteImageProvider>();
|
||||
return GetImageProviders(
|
||||
item,
|
||||
libraryOptions,
|
||||
options,
|
||||
new ImageRefreshOptions(new DirectoryService(_fileSystem)),
|
||||
includeDisabled).OfType<IRemoteImageProvider>();
|
||||
}
|
||||
|
||||
private bool CanRefresh(IMetadataProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata)
|
||||
private bool CanRefresh(
|
||||
IMetadataProvider provider,
|
||||
BaseItem item,
|
||||
LibraryOptions libraryOptions,
|
||||
bool includeDisabled,
|
||||
bool forceEnableInternetMetadata)
|
||||
{
|
||||
if (!includeDisabled)
|
||||
{
|
||||
@@ -364,7 +422,12 @@ namespace MediaBrowser.Providers.Manager
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CanRefresh(IImageProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled)
|
||||
private bool CanRefresh(
|
||||
IImageProvider provider,
|
||||
BaseItem item,
|
||||
LibraryOptions libraryOptions,
|
||||
ImageRefreshOptions refreshOptions,
|
||||
bool includeDisabled)
|
||||
{
|
||||
if (!includeDisabled)
|
||||
{
|
||||
@@ -392,7 +455,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{0} failed in Supports for type {1}", provider.GetType().Name, item.GetType().Name);
|
||||
_logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -404,9 +467,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <returns>System.Int32.</returns>
|
||||
private int GetOrder(IImageProvider provider)
|
||||
{
|
||||
var hasOrder = provider as IHasOrder;
|
||||
|
||||
if (hasOrder == null)
|
||||
if (!(provider is IHasOrder hasOrder))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -433,7 +494,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
if (provider is IRemoteMetadataProvider)
|
||||
{
|
||||
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
|
||||
var typeFetcherOrder = typeOptions == null ? null : typeOptions.MetadataFetcherOrder;
|
||||
var typeFetcherOrder = typeOptions?.MetadataFetcherOrder;
|
||||
|
||||
var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
|
||||
|
||||
@@ -451,9 +512,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
private int GetDefaultOrder(IMetadataProvider provider)
|
||||
{
|
||||
var hasOrder = provider as IHasOrder;
|
||||
|
||||
if (hasOrder != null)
|
||||
if (provider is IHasOrder hasOrder)
|
||||
{
|
||||
return hasOrder.Order;
|
||||
}
|
||||
@@ -461,9 +520,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MetadataPluginSummary[] GetAllMetadataPlugins()
|
||||
{
|
||||
return new MetadataPluginSummary[]
|
||||
return new[]
|
||||
{
|
||||
GetPluginSummary<Movie>(),
|
||||
GetPluginSummary<BoxSet>(),
|
||||
@@ -485,7 +545,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
// Give it a dummy path just so that it looks like a file system item
|
||||
var dummy = new T()
|
||||
var dummy = new T
|
||||
{
|
||||
Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
|
||||
ParentId = Guid.NewGuid()
|
||||
@@ -500,16 +560,17 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
var libraryOptions = new LibraryOptions();
|
||||
|
||||
var imageProviders = GetImageProviders(dummy, libraryOptions, options,
|
||||
new ImageRefreshOptions(
|
||||
new DirectoryService(_fileSystem)),
|
||||
true)
|
||||
.ToList();
|
||||
var imageProviders = GetImageProviders(
|
||||
dummy,
|
||||
libraryOptions,
|
||||
options,
|
||||
new ImageRefreshOptions(new DirectoryService(_fileSystem)),
|
||||
true).ToList();
|
||||
|
||||
var pluginList = summary.Plugins.ToList();
|
||||
|
||||
AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
|
||||
AddImagePlugins(pluginList, dummy, imageProviders);
|
||||
AddImagePlugins(pluginList, imageProviders);
|
||||
|
||||
var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
|
||||
|
||||
@@ -540,14 +601,14 @@ namespace MediaBrowser.Providers.Manager
|
||||
var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList();
|
||||
|
||||
// Locals
|
||||
list.AddRange(providers.Where(i => (i is ILocalMetadataProvider)).Select(i => new MetadataPlugin
|
||||
list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
|
||||
{
|
||||
Name = i.Name,
|
||||
Type = MetadataPluginType.LocalMetadataProvider
|
||||
}));
|
||||
|
||||
// Fetchers
|
||||
list.AddRange(providers.Where(i => (i is IRemoteMetadataProvider)).Select(i => new MetadataPlugin
|
||||
list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
|
||||
{
|
||||
Name = i.Name,
|
||||
Type = MetadataPluginType.MetadataFetcher
|
||||
@@ -561,12 +622,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
}));
|
||||
}
|
||||
|
||||
private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
|
||||
where T : BaseItem
|
||||
private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
|
||||
{
|
||||
|
||||
// Locals
|
||||
list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
|
||||
list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
|
||||
{
|
||||
Name = i.Name,
|
||||
Type = MetadataPluginType.LocalImageProvider
|
||||
@@ -580,6 +639,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MetadataOptions GetMetadataOptions(BaseItem item)
|
||||
{
|
||||
var type = item.GetType().Name;
|
||||
@@ -589,17 +649,13 @@ namespace MediaBrowser.Providers.Manager
|
||||
new MetadataOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the metadata.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public void SaveMetadata(BaseItem item, ItemUpdateType updateType)
|
||||
{
|
||||
SaveMetadata(item, updateType, _savers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the metadata.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
|
||||
{
|
||||
SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)));
|
||||
@@ -611,7 +667,6 @@ namespace MediaBrowser.Providers.Manager
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="updateType">Type of the update.</param>
|
||||
/// <param name="savers">The savers.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
|
||||
{
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||
@@ -620,11 +675,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
_logger.LogDebug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
|
||||
|
||||
var fileSaver = saver as IMetadataFileSaver;
|
||||
|
||||
if (fileSaver != null)
|
||||
if (saver is IMetadataFileSaver fileSaver)
|
||||
{
|
||||
string path = null;
|
||||
string path;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -691,11 +744,9 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
if (updateType >= ItemUpdateType.MetadataEdit)
|
||||
{
|
||||
var fileSaver = saver as IMetadataFileSaver;
|
||||
|
||||
// Manual edit occurred
|
||||
// Even if save local is off, save locally anyway if the metadata file already exists
|
||||
if (fileSaver == null || !File.Exists(fileSaver.GetSavePath(item)))
|
||||
if (!(saver is IMetadataFileSaver fileSaver) || !File.Exists(fileSaver.GetSavePath(item)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -726,6 +777,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, CancellationToken cancellationToken)
|
||||
where TItemType : BaseItem, new()
|
||||
where TLookupType : ItemLookupInfo
|
||||
@@ -740,7 +792,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
|
||||
private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
|
||||
where TItemType : BaseItem, new()
|
||||
where TLookupType : ItemLookupInfo
|
||||
{
|
||||
@@ -779,6 +831,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
|
||||
{
|
||||
searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
|
||||
@@ -823,12 +876,14 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
//_logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList));
|
||||
// _logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList));
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(IRemoteSearchProvider<TLookupType> provider, TLookupType searchInfo,
|
||||
private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(
|
||||
IRemoteSearchProvider<TLookupType> provider,
|
||||
TLookupType searchInfo,
|
||||
CancellationToken cancellationToken)
|
||||
where TLookupType : ItemLookupInfo
|
||||
{
|
||||
@@ -844,7 +899,8 @@ namespace MediaBrowser.Providers.Manager
|
||||
return list;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
|
||||
/// <inheritdoc/>
|
||||
public Task<HttpResponseMessage> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
|
||||
{
|
||||
var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -856,7 +912,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
return provider.GetImageResponse(url, cancellationToken);
|
||||
}
|
||||
|
||||
public IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
|
||||
private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
|
||||
{
|
||||
return _externalIds.Where(i =>
|
||||
{
|
||||
@@ -872,6 +928,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
|
||||
{
|
||||
return GetExternalIds(item)
|
||||
@@ -891,29 +948,29 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
return new ExternalUrl
|
||||
{
|
||||
Name = i.Name,
|
||||
Name = i.ProviderName,
|
||||
Url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
i.UrlFormatString,
|
||||
value)
|
||||
};
|
||||
|
||||
}).Where(i => i != null).Concat(item.GetRelatedUrls());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
|
||||
{
|
||||
return GetExternalIds(item)
|
||||
.Select(i => new ExternalIdInfo
|
||||
{
|
||||
Name = i.Name,
|
||||
Name = i.ProviderName,
|
||||
Key = i.Key,
|
||||
Type = i.Type,
|
||||
UrlFormatString = i.UrlFormatString
|
||||
});
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<Guid, Guid> GetRefreshQueue()
|
||||
{
|
||||
lock (_refreshQueueLock)
|
||||
@@ -929,22 +986,25 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnRefreshStart(BaseItem item)
|
||||
{
|
||||
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
_logger.LogDebug("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
_activeRefreshes[item.Id] = 0;
|
||||
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnRefreshComplete(BaseItem item)
|
||||
{
|
||||
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
_logger.LogDebug("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
_activeRefreshes.Remove(item.Id, out _);
|
||||
|
||||
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double? GetRefreshProgress(Guid id)
|
||||
{
|
||||
if (_activeRefreshes.TryGetValue(id, out double value))
|
||||
@@ -955,6 +1015,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnRefreshProgress(BaseItem item, double progress)
|
||||
{
|
||||
var id = item.Id;
|
||||
@@ -974,12 +1035,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
|
||||
}
|
||||
|
||||
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
|
||||
new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
|
||||
|
||||
private readonly object _refreshQueueLock = new object();
|
||||
private bool _isProcessingRefreshQueue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority)
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -1023,7 +1079,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
if (item != null)
|
||||
{
|
||||
// Try to throttle this a little bit.
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var task = item is MusicArtist artist
|
||||
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
|
||||
@@ -1053,17 +1109,14 @@ namespace MediaBrowser.Providers.Manager
|
||||
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Collection folders don't validate their children so we'll have to simulate that here
|
||||
|
||||
if (item is CollectionFolder collectionFolder)
|
||||
switch (item)
|
||||
{
|
||||
await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item is Folder folder)
|
||||
{
|
||||
case CollectionFolder collectionFolder:
|
||||
await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case Folder folder:
|
||||
await folder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,7 +1126,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options, true).ConfigureAwait(false);
|
||||
await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1109,20 +1162,41 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshItem(item, options, cancellationToken);
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_disposeCancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_disposeCancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_disposeCancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -14,7 +16,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
public static void MergeBaseItemData<T>(
|
||||
MetadataResult<T> sourceResult,
|
||||
MetadataResult<T> targetResult,
|
||||
MetadataFields[] lockedFields,
|
||||
MetadataField[] lockedFields,
|
||||
bool replaceData,
|
||||
bool mergeMetadataSettings)
|
||||
where T : BaseItem
|
||||
@@ -24,14 +26,15 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
throw new ArgumentException("Item cannot be null.", nameof(sourceResult));
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Name))
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentException("Item cannot be null.", nameof(targetResult));
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataField.Name))
|
||||
{
|
||||
if (replaceData || string.IsNullOrEmpty(target.Name))
|
||||
{
|
||||
@@ -62,7 +65,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
target.EndDate = source.EndDate;
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Genres))
|
||||
if (!lockedFields.Contains(MetadataField.Genres))
|
||||
{
|
||||
if (replaceData || target.Genres.Length == 0)
|
||||
{
|
||||
@@ -75,7 +78,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
target.IndexNumber = source.IndexNumber;
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.OfficialRating))
|
||||
if (!lockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
if (replaceData || string.IsNullOrEmpty(target.OfficialRating))
|
||||
{
|
||||
@@ -93,7 +96,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
target.Tagline = source.Tagline;
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Overview))
|
||||
if (!lockedFields.Contains(MetadataField.Overview))
|
||||
{
|
||||
if (replaceData || string.IsNullOrEmpty(target.Overview))
|
||||
{
|
||||
@@ -106,12 +109,11 @@ namespace MediaBrowser.Providers.Manager
|
||||
target.ParentIndexNumber = source.ParentIndexNumber;
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Cast))
|
||||
if (!lockedFields.Contains(MetadataField.Cast))
|
||||
{
|
||||
if (replaceData || targetResult.People == null || targetResult.People.Count == 0)
|
||||
{
|
||||
targetResult.People = sourceResult.People;
|
||||
|
||||
}
|
||||
else if (targetResult.People != null && sourceResult.People != null)
|
||||
{
|
||||
@@ -129,7 +131,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
target.ProductionYear = source.ProductionYear;
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Runtime))
|
||||
if (!lockedFields.Contains(MetadataField.Runtime))
|
||||
{
|
||||
if (replaceData || !target.RunTimeTicks.HasValue)
|
||||
{
|
||||
@@ -140,7 +142,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Studios))
|
||||
if (!lockedFields.Contains(MetadataField.Studios))
|
||||
{
|
||||
if (replaceData || target.Studios.Length == 0)
|
||||
{
|
||||
@@ -148,7 +150,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.Tags))
|
||||
if (!lockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
if (replaceData || target.Tags.Length == 0)
|
||||
{
|
||||
@@ -156,7 +158,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
}
|
||||
}
|
||||
|
||||
if (!lockedFields.Contains(MetadataFields.ProductionLocations))
|
||||
if (!lockedFields.Contains(MetadataField.ProductionLocations))
|
||||
{
|
||||
if (replaceData || target.ProductionLocations.Length == 0)
|
||||
{
|
||||
|
||||
15
MediaBrowser.Providers/Manager/RefreshResult.cs
Normal file
15
MediaBrowser.Providers/Manager/RefreshResult.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
||||
namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
public class RefreshResult
|
||||
{
|
||||
public ItemUpdateType UpdateType { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public int Failures { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,20 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
|
||||
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.0.4" />
|
||||
<PackageReference Include="TvDbSharper" Version="3.0.1" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
|
||||
<PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
|
||||
<PackageReference Include="TvDbSharper" Version="3.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
@@ -44,11 +47,9 @@
|
||||
<ItemGroup>
|
||||
<None Remove="Plugins\AudioDb\Configuration\config.html" />
|
||||
<EmbeddedResource Include="Plugins\AudioDb\Configuration\config.html" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Plugins\Omdb\Configuration\config.html" />
|
||||
<EmbeddedResource Include="Plugins\Omdb\Configuration\config.html" />
|
||||
<None Remove="Plugins\MusicBrainz\Configuration\config.html" />
|
||||
<EmbeddedResource Include="Plugins\MusicBrainz\Configuration\config.html" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -17,7 +19,7 @@ using MediaBrowser.Model.IO;
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses ffmpeg to create video images
|
||||
/// Uses ffmpeg to create video images.
|
||||
/// </summary>
|
||||
public class AudioImageProvider : IDynamicImageProvider
|
||||
{
|
||||
@@ -32,6 +34,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images");
|
||||
|
||||
public string Name => "Image Extractor";
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType> { ImageType.Primary };
|
||||
@@ -79,7 +85,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,15 +97,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
private string GetAudioImagePath(Audio item)
|
||||
{
|
||||
string filename = null;
|
||||
string filename;
|
||||
|
||||
if (item.GetType() == typeof(Audio))
|
||||
{
|
||||
var albumArtist = item.AlbumArtists.FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(albumArtist))
|
||||
if (item.AlbumArtists.Count > 0
|
||||
&& !string.IsNullOrWhiteSpace(item.Album)
|
||||
&& !string.IsNullOrWhiteSpace(item.AlbumArtists[0]))
|
||||
{
|
||||
filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
filename = (item.Album + "-" + item.AlbumArtists[0]).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -115,21 +120,18 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg";
|
||||
}
|
||||
|
||||
var prefix = filename.Substring(0, 1);
|
||||
var prefix = filename.AsSpan().Slice(0, 1);
|
||||
|
||||
return Path.Combine(AudioImagesPath, prefix, filename);
|
||||
return Path.Join(AudioImagesPath, prefix, filename);
|
||||
}
|
||||
|
||||
public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images");
|
||||
|
||||
public string Name => "Image Extractor";
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
if (item.IsShortcut)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.IsFileProtocol)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -15,32 +15,31 @@ using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
class FFProbeAudioInfo
|
||||
public class FFProbeAudioInfo
|
||||
{
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public FFProbeAudioInfo(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IApplicationPaths appPaths, IJsonSerializer json, ILibraryManager libraryManager)
|
||||
public FFProbeAudioInfo(
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepo,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepo = itemRepo;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
_libraryManager = libraryManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
public async Task<ItemUpdateType> Probe<T>(T item, MetadataRefreshOptions options,
|
||||
public async Task<ItemUpdateType> Probe<T>(
|
||||
T item,
|
||||
MetadataRefreshOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
where T : Audio
|
||||
{
|
||||
@@ -55,20 +54,21 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
protocol = _mediaSourceManager.GetPathProtocol(path);
|
||||
}
|
||||
|
||||
var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||
{
|
||||
MediaType = DlnaProfileType.Audio,
|
||||
MediaSource = new MediaSourceInfo
|
||||
var result = await _mediaEncoder.GetMediaInfo(
|
||||
new MediaInfoRequest
|
||||
{
|
||||
Path = path,
|
||||
Protocol = protocol
|
||||
}
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
MediaType = DlnaProfileType.Audio,
|
||||
MediaSource = new MediaSourceInfo
|
||||
{
|
||||
Path = path,
|
||||
Protocol = protocol
|
||||
}
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Fetch(item, cancellationToken, result);
|
||||
Fetch(item, result, cancellationToken);
|
||||
}
|
||||
|
||||
return ItemUpdateType.MetadataImport;
|
||||
@@ -78,10 +78,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
/// Fetches the specified audio.
|
||||
/// </summary>
|
||||
/// <param name="audio">The audio.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="mediaInfo">The media information.</param>
|
||||
/// <returns>Task.</returns>
|
||||
protected void Fetch(Audio audio, CancellationToken cancellationToken, Model.MediaInfo.MediaInfo mediaInfo)
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
protected void Fetch(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var mediaStreams = mediaInfo.MediaStreams;
|
||||
|
||||
@@ -91,8 +90,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
audio.RunTimeTicks = mediaInfo.RunTimeTicks;
|
||||
audio.Size = mediaInfo.Size;
|
||||
|
||||
//var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
|
||||
//audio.Container = extension;
|
||||
// var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
|
||||
// audio.Container = extension;
|
||||
|
||||
FetchDataFromTags(audio, mediaInfo);
|
||||
|
||||
@@ -100,7 +99,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches data from the tags dictionary
|
||||
/// Fetches data from the tags dictionary.
|
||||
/// </summary>
|
||||
/// <param name="audio">The audio.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
@@ -112,7 +111,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
audio.Name = data.Name;
|
||||
}
|
||||
|
||||
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataFields.Cast))
|
||||
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
|
||||
{
|
||||
var people = new List<PersonInfo>();
|
||||
|
||||
@@ -143,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
|
||||
}
|
||||
|
||||
if (!audio.LockedFields.Contains(MetadataFields.Genres))
|
||||
if (!audio.LockedFields.Contains(MetadataField.Genres))
|
||||
{
|
||||
audio.Genres = Array.Empty<string>();
|
||||
|
||||
@@ -153,16 +152,16 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (!audio.LockedFields.Contains(MetadataFields.Studios))
|
||||
if (!audio.LockedFields.Contains(MetadataField.Studios))
|
||||
{
|
||||
audio.SetStudios(data.Studios);
|
||||
}
|
||||
|
||||
audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist));
|
||||
audio.SetProviderId(MetadataProviders.MusicBrainzArtist, data.GetProviderId(MetadataProviders.MusicBrainzArtist));
|
||||
audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, data.GetProviderId(MetadataProviders.MusicBrainzAlbum));
|
||||
audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, data.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup));
|
||||
audio.SetProviderId(MetadataProviders.MusicBrainzTrack, data.GetProviderId(MetadataProviders.MusicBrainzTrack));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzArtist, data.GetProviderId(MetadataProvider.MusicBrainzArtist));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, data.GetProviderId(MetadataProvider.MusicBrainzAlbum));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, data.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzTrack, data.GetProviderId(MetadataProvider.MusicBrainzTrack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Chapters;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -18,9 +18,7 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
@@ -37,24 +35,54 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
IPreRefreshProvider,
|
||||
IHasItemChangeMonitor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IIsoManager _isoManager;
|
||||
private readonly ILogger<FFProbeProvider> _logger;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IBlurayExaminer _blurayExaminer;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IEncodingManager _encodingManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
private readonly IChapterManager _chapterManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly SubtitleResolver _subtitleResolver;
|
||||
|
||||
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
||||
|
||||
public FFProbeProvider(
|
||||
ILogger<FFProbeProvider> logger,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepo,
|
||||
IBlurayExaminer blurayExaminer,
|
||||
ILocalizationManager localization,
|
||||
IEncodingManager encodingManager,
|
||||
IServerConfigurationManager config,
|
||||
ISubtitleManager subtitleManager,
|
||||
IChapterManager chapterManager,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepo = itemRepo;
|
||||
_blurayExaminer = blurayExaminer;
|
||||
_localization = localization;
|
||||
_encodingManager = encodingManager;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
_chapterManager = chapterManager;
|
||||
_libraryManager = libraryManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
|
||||
_subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
|
||||
}
|
||||
|
||||
public string Name => "ffprobe";
|
||||
|
||||
// Run last
|
||||
public int Order => 100;
|
||||
|
||||
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||
{
|
||||
var video = item as Video;
|
||||
@@ -119,45 +147,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
return FetchAudioInfo(item, options, cancellationToken);
|
||||
}
|
||||
|
||||
private SubtitleResolver _subtitleResolver;
|
||||
|
||||
public FFProbeProvider(
|
||||
ILogger<FFProbeProvider> logger,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IChannelManager channelManager,
|
||||
IIsoManager isoManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepo,
|
||||
IBlurayExaminer blurayExaminer,
|
||||
ILocalizationManager localization,
|
||||
IApplicationPaths appPaths,
|
||||
IJsonSerializer json,
|
||||
IEncodingManager encodingManager,
|
||||
IServerConfigurationManager config,
|
||||
ISubtitleManager subtitleManager,
|
||||
IChapterManager chapterManager,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_isoManager = isoManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepo = itemRepo;
|
||||
_blurayExaminer = blurayExaminer;
|
||||
_localization = localization;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
_encodingManager = encodingManager;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
_chapterManager = chapterManager;
|
||||
_libraryManager = libraryManager;
|
||||
_channelManager = channelManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
|
||||
_subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
|
||||
}
|
||||
|
||||
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
||||
public Task<ItemUpdateType> FetchVideoInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
where T : Video
|
||||
{
|
||||
@@ -209,9 +198,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
private string NormalizeStrmLine(string line)
|
||||
{
|
||||
return line.Replace("\t", string.Empty)
|
||||
.Replace("\r", string.Empty)
|
||||
.Replace("\n", string.Empty)
|
||||
return line.Replace("\t", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("\r", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("\n", string.Empty, StringComparison.Ordinal)
|
||||
.Trim();
|
||||
}
|
||||
|
||||
@@ -240,11 +229,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
FetchShortcutInfo(item);
|
||||
}
|
||||
|
||||
var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _appPaths, _json, _libraryManager);
|
||||
var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _libraryManager);
|
||||
|
||||
return prober.Probe(item, options, cancellationToken);
|
||||
}
|
||||
// Run last
|
||||
public int Order => 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -176,7 +175,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
mediaAttachments = mediaInfo.MediaAttachments;
|
||||
|
||||
video.TotalBitrate = mediaInfo.Bitrate;
|
||||
//video.FormatName = (mediaInfo.Container ?? string.Empty)
|
||||
// video.FormatName = (mediaInfo.Container ?? string.Empty)
|
||||
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one
|
||||
@@ -283,7 +282,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
var video = (Video)item;
|
||||
|
||||
//video.PlayableStreamFileNames = blurayInfo.Files.ToList();
|
||||
// video.PlayableStreamFileNames = blurayInfo.Files.ToList();
|
||||
|
||||
// Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output
|
||||
if (blurayInfo.Files.Length > 1)
|
||||
@@ -342,7 +341,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the longest playlist on a bdrom
|
||||
/// Gets information about the longest playlist on a bdrom.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>VideoStream.</returns>
|
||||
@@ -368,7 +367,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
var isFullRefresh = refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
|
||||
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.OfficialRating))
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(data.OfficialRating) || isFullRefresh)
|
||||
{
|
||||
@@ -376,7 +375,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Genres))
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Genres))
|
||||
{
|
||||
if (video.Genres.Length == 0 || isFullRefresh)
|
||||
{
|
||||
@@ -389,7 +388,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Studios))
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Studios))
|
||||
{
|
||||
if (video.Studios.Length == 0 || isFullRefresh)
|
||||
{
|
||||
@@ -404,6 +403,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
video.ProductionYear = data.ProductionYear;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.PremiereDate.HasValue)
|
||||
{
|
||||
if (!video.PremiereDate.HasValue || isFullRefresh)
|
||||
@@ -411,6 +411,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
video.PremiereDate = data.PremiereDate;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.IndexNumber.HasValue)
|
||||
{
|
||||
if (!video.IndexNumber.HasValue || isFullRefresh)
|
||||
@@ -418,6 +419,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
video.IndexNumber = data.IndexNumber;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.ParentIndexNumber.HasValue)
|
||||
{
|
||||
if (!video.ParentIndexNumber.HasValue || isFullRefresh)
|
||||
@@ -426,7 +428,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Name))
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Name))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(data.Name) && libraryOptions.EnableEmbeddedTitles)
|
||||
{
|
||||
@@ -444,7 +446,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
|
||||
}
|
||||
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Overview))
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Overview))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(video.Overview) || isFullRefresh)
|
||||
{
|
||||
@@ -457,7 +459,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
var isFullRefresh = options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
|
||||
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Cast))
|
||||
if (!video.IsLocked && !video.LockedFields.Contains(MetadataField.Cast))
|
||||
{
|
||||
if (isFullRefresh || _libraryManager.GetPeople(video).Count == 0)
|
||||
{
|
||||
@@ -537,17 +539,18 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
if (enableSubtitleDownloading && enabled)
|
||||
{
|
||||
var downloadedLanguages = await new SubtitleDownloader(_logger,
|
||||
_subtitleManager)
|
||||
.DownloadSubtitles(video,
|
||||
currentStreams.Concat(externalSubtitleStreams).ToList(),
|
||||
skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches,
|
||||
requirePerfectMatch,
|
||||
subtitleDownloadLanguages,
|
||||
libraryOptions.DisabledSubtitleFetchers,
|
||||
libraryOptions.SubtitleFetcherOrder,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var downloadedLanguages = await new SubtitleDownloader(
|
||||
_logger,
|
||||
_subtitleManager).DownloadSubtitles(
|
||||
video,
|
||||
currentStreams.Concat(externalSubtitleStreams).ToList(),
|
||||
skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches,
|
||||
requirePerfectMatch,
|
||||
subtitleDownloadLanguages,
|
||||
libraryOptions.DisabledSubtitleFetchers,
|
||||
libraryOptions.SubtitleFetcherOrder,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Rescan
|
||||
if (downloadedLanguages.Count > 0)
|
||||
@@ -565,7 +568,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
/// Creates dummy chapters.
|
||||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <return>An array of dummy chapters.</returns>
|
||||
/// <returns>An array of dummy chapters.</returns>
|
||||
private ChapterInfo[] CreateDummyChapters(Video video)
|
||||
{
|
||||
var runtime = video.RunTimeTicks ?? 0;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -25,7 +27,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_subtitleManager = subtitleManager;
|
||||
}
|
||||
|
||||
public async Task<List<string>> DownloadSubtitles(Video video,
|
||||
public async Task<List<string>> DownloadSubtitles(
|
||||
Video video,
|
||||
List<MediaStream> mediaStreams,
|
||||
bool skipIfEmbeddedSubtitlesPresent,
|
||||
bool skipIfAudioTrackMatches,
|
||||
@@ -39,8 +42,16 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
foreach (var lang in languages)
|
||||
{
|
||||
var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches, requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, cancellationToken).ConfigureAwait(false);
|
||||
var downloaded = await DownloadSubtitles(
|
||||
video,
|
||||
mediaStreams,
|
||||
skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches,
|
||||
requirePerfectMatch,
|
||||
lang,
|
||||
disabledSubtitleFetchers,
|
||||
subtitleFetcherOrder,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (downloaded)
|
||||
{
|
||||
@@ -51,7 +62,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
return downloadedLanguages;
|
||||
}
|
||||
|
||||
public Task<bool> DownloadSubtitles(Video video,
|
||||
public Task<bool> DownloadSubtitles(
|
||||
Video video,
|
||||
List<MediaStream> mediaStreams,
|
||||
bool skipIfEmbeddedSubtitlesPresent,
|
||||
bool skipIfAudioTrackMatches,
|
||||
@@ -87,11 +99,21 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
return DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches,
|
||||
requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, mediaType, cancellationToken);
|
||||
return DownloadSubtitles(
|
||||
video,
|
||||
mediaStreams,
|
||||
skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches,
|
||||
requirePerfectMatch,
|
||||
lang,
|
||||
disabledSubtitleFetchers,
|
||||
subtitleFetcherOrder,
|
||||
mediaType,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadSubtitles(Video video,
|
||||
private async Task<bool> DownloadSubtitles(
|
||||
Video video,
|
||||
List<MediaStream> mediaStreams,
|
||||
bool skipIfEmbeddedSubtitlesPresent,
|
||||
bool skipIfAudioTrackMatches,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -6,7 +8,6 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
@@ -60,15 +61,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
public List<string> GetExternalSubtitleFiles(Video video,
|
||||
IDirectoryService directoryService,
|
||||
bool clearCache)
|
||||
public List<string> GetExternalSubtitleFiles(
|
||||
Video video,
|
||||
IDirectoryService directoryService,
|
||||
bool clearCache)
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
@@ -87,7 +88,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
return list;
|
||||
}
|
||||
|
||||
private void AddExternalSubtitleStreams(List<MediaStream> streams, string folder,
|
||||
private void AddExternalSubtitleStreams(
|
||||
List<MediaStream> streams,
|
||||
string folder,
|
||||
string videoPath,
|
||||
int startIndex,
|
||||
IDirectoryService directoryService,
|
||||
@@ -98,7 +101,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
|
||||
}
|
||||
|
||||
public void AddExternalSubtitleStreams(List<MediaStream> streams,
|
||||
public void AddExternalSubtitleStreams(
|
||||
List<MediaStream> streams,
|
||||
string videoPath,
|
||||
int startIndex,
|
||||
string[] files)
|
||||
@@ -185,13 +189,13 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
private string NormalizeFilenameForSubtitleComparison(string filename)
|
||||
{
|
||||
// Try to account for sloppy file naming
|
||||
filename = filename.Replace("_", string.Empty);
|
||||
filename = filename.Replace(" ", string.Empty);
|
||||
filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
|
||||
filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
|
||||
|
||||
// can't normalize this due to languages such as pt-br
|
||||
//filename = filename.Replace("-", string.Empty);
|
||||
// filename = filename.Replace("-", string.Empty);
|
||||
|
||||
//filename = filename.Replace(".", string.Empty);
|
||||
// filename = filename.Replace(".", string.Empty);
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -10,11 +12,10 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
@@ -23,29 +24,37 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly ILogger<SubtitleScheduledTask> _logger;
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
public SubtitleScheduledTask(
|
||||
ILibraryManager libraryManager,
|
||||
IJsonSerializer json,
|
||||
IServerConfigurationManager config,
|
||||
ISubtitleManager subtitleManager,
|
||||
ILogger<SubtitleScheduledTask> logger,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
ILocalizationManager localization)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
_logger = logger;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_json = json;
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles");
|
||||
|
||||
public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription");
|
||||
|
||||
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
|
||||
|
||||
public string Key => "DownloadSubtitles";
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool IsEnabled => true;
|
||||
|
||||
public bool IsLogged => true;
|
||||
|
||||
private SubtitleOptions GetOptions()
|
||||
{
|
||||
return _config.GetConfiguration<SubtitleOptions>("subtitles");
|
||||
@@ -64,23 +73,23 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(library);
|
||||
|
||||
string[] subtitleDownloadLanguages;
|
||||
bool SkipIfEmbeddedSubtitlesPresent;
|
||||
bool SkipIfAudioTrackMatches;
|
||||
bool RequirePerfectMatch;
|
||||
bool skipIfEmbeddedSubtitlesPresent;
|
||||
bool skipIfAudioTrackMatches;
|
||||
bool requirePerfectMatch;
|
||||
|
||||
if (libraryOptions.SubtitleDownloadLanguages == null)
|
||||
{
|
||||
subtitleDownloadLanguages = options.DownloadLanguages;
|
||||
SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent;
|
||||
SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches;
|
||||
RequirePerfectMatch = options.RequirePerfectMatch;
|
||||
skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent;
|
||||
skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches;
|
||||
requirePerfectMatch = options.RequirePerfectMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
|
||||
SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
||||
RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
||||
skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
||||
requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
||||
}
|
||||
|
||||
foreach (var lang in subtitleDownloadLanguages)
|
||||
@@ -96,12 +105,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
Recursive = true
|
||||
};
|
||||
|
||||
if (SkipIfAudioTrackMatches)
|
||||
if (skipIfAudioTrackMatches)
|
||||
{
|
||||
query.HasNoAudioTrackWithLanguage = lang;
|
||||
}
|
||||
|
||||
if (SkipIfEmbeddedSubtitlesPresent)
|
||||
if (skipIfEmbeddedSubtitlesPresent)
|
||||
{
|
||||
// Exclude if it already has any subtitles of the same language
|
||||
query.HasNoSubtitleTrackWithLanguage = lang;
|
||||
@@ -158,36 +167,37 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(video);
|
||||
|
||||
string[] subtitleDownloadLanguages;
|
||||
bool SkipIfEmbeddedSubtitlesPresent;
|
||||
bool SkipIfAudioTrackMatches;
|
||||
bool RequirePerfectMatch;
|
||||
bool skipIfEmbeddedSubtitlesPresent;
|
||||
bool skipIfAudioTrackMatches;
|
||||
bool requirePerfectMatch;
|
||||
|
||||
if (libraryOptions.SubtitleDownloadLanguages == null)
|
||||
{
|
||||
subtitleDownloadLanguages = options.DownloadLanguages;
|
||||
SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent;
|
||||
SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches;
|
||||
RequirePerfectMatch = options.RequirePerfectMatch;
|
||||
skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent;
|
||||
skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches;
|
||||
requirePerfectMatch = options.RequirePerfectMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
|
||||
SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
||||
RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
||||
skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
|
||||
skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
|
||||
requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
|
||||
}
|
||||
|
||||
var downloadedLanguages = await new SubtitleDownloader(_logger,
|
||||
_subtitleManager)
|
||||
.DownloadSubtitles(video,
|
||||
mediaStreams,
|
||||
SkipIfEmbeddedSubtitlesPresent,
|
||||
SkipIfAudioTrackMatches,
|
||||
RequirePerfectMatch,
|
||||
subtitleDownloadLanguages,
|
||||
libraryOptions.DisabledSubtitleFetchers,
|
||||
libraryOptions.SubtitleFetcherOrder,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var downloadedLanguages = await new SubtitleDownloader(
|
||||
_logger,
|
||||
_subtitleManager).DownloadSubtitles(
|
||||
video,
|
||||
mediaStreams,
|
||||
skipIfEmbeddedSubtitlesPresent,
|
||||
skipIfAudioTrackMatches,
|
||||
requirePerfectMatch,
|
||||
subtitleDownloadLanguages,
|
||||
libraryOptions.DisabledSubtitleFetchers,
|
||||
libraryOptions.SubtitleFetcherOrder,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Rescan
|
||||
if (downloadedLanguages.Count > 0)
|
||||
@@ -201,25 +211,11 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
return new[]
|
||||
{
|
||||
// Every so often
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
|
||||
};
|
||||
}
|
||||
|
||||
public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles");
|
||||
|
||||
public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription");
|
||||
|
||||
public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
|
||||
|
||||
public string Key => "DownloadSubtitles";
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool IsEnabled => true;
|
||||
|
||||
public bool IsLogged => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -17,7 +19,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
public class VideoImageProvider : IDynamicImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<VideoImageProvider> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger, IFileSystem fileSystem)
|
||||
@@ -27,6 +29,11 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public string Name => "Screen Grabber";
|
||||
|
||||
// Make sure this comes after internet image providers
|
||||
public int Order => 100;
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType> { ImageType.Primary };
|
||||
@@ -93,6 +100,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
videoIndex++;
|
||||
}
|
||||
|
||||
if (mediaStream == imageStream)
|
||||
{
|
||||
break;
|
||||
@@ -124,14 +132,13 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
};
|
||||
}
|
||||
|
||||
public string Name => "Screen Grabber";
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
if (item.IsShortcut)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.IsFileProtocol)
|
||||
{
|
||||
return false;
|
||||
@@ -146,7 +153,5 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|
||||
return false;
|
||||
}
|
||||
// Make sure this comes after internet image providers
|
||||
public int Order => 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class ImdbExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "IMDb";
|
||||
public string ProviderName => "IMDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.Imdb.ToString();
|
||||
public string Key => MetadataProvider.Imdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.imdb.com/title/{0}";
|
||||
@@ -30,19 +36,4 @@ namespace MediaBrowser.Providers.Movies
|
||||
return item is Movie || item is MusicVideo || item is Series || item is Episode || item is Trailer;
|
||||
}
|
||||
}
|
||||
|
||||
public class ImdbPersonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "IMDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.Imdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.imdb.com/name/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Person;
|
||||
}
|
||||
}
|
||||
27
MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs
Normal file
27
MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class ImdbPersonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "IMDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Imdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.imdb.com/name/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Person;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -28,15 +30,17 @@ namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.ProductionYear.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.IsFullLocalMetadata(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -28,15 +30,17 @@ namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.ProductionYear.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.IsFullLocalMetadata(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
public static class Extensions
|
||||
public static class AlbumInfoExtensions
|
||||
{
|
||||
public static string GetAlbumArtist(this AlbumInfo info)
|
||||
{
|
||||
@@ -16,16 +18,16 @@ namespace MediaBrowser.Providers.Music
|
||||
return id;
|
||||
}
|
||||
|
||||
return info.AlbumArtists.FirstOrDefault();
|
||||
return info.AlbumArtists.Count > 0 ? info.AlbumArtists[0] : default;
|
||||
}
|
||||
|
||||
public static string GetReleaseGroupId(this AlbumInfo info)
|
||||
{
|
||||
var id = info.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
|
||||
var id = info.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup))
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup))
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
@@ -34,11 +36,11 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
public static string GetReleaseId(this AlbumInfo info)
|
||||
{
|
||||
var id = info.GetProviderId(MetadataProviders.MusicBrainzAlbum);
|
||||
var id = info.GetProviderId(MetadataProvider.MusicBrainzAlbum);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzAlbum))
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbum))
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
@@ -47,16 +49,16 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
public static string GetMusicBrainzArtistId(this AlbumInfo info)
|
||||
{
|
||||
info.ProviderIds.TryGetValue(MetadataProviders.MusicBrainzAlbumArtist.ToString(), out string id);
|
||||
info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string id);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
info.ArtistProviderIds.TryGetValue(MetadataProviders.MusicBrainzArtist.ToString(), out id);
|
||||
info.ArtistProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out id);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist))
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist))
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
@@ -65,11 +67,11 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
public static string GetMusicBrainzArtistId(this ArtistInfo info)
|
||||
{
|
||||
info.ProviderIds.TryGetValue(MetadataProviders.MusicBrainzArtist.ToString(), out var id);
|
||||
info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out var id);
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist))
|
||||
return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist))
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -45,7 +47,7 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
|
||||
{
|
||||
if (!item.LockedFields.Contains(MetadataFields.Name))
|
||||
if (!item.LockedFields.Contains(MetadataField.Name))
|
||||
{
|
||||
var name = children.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
|
||||
@@ -108,7 +110,7 @@ namespace MediaBrowser.Providers.Music
|
||||
protected override void MergeData(
|
||||
MetadataResult<MusicAlbum> source,
|
||||
MetadataResult<MusicAlbum> target,
|
||||
MetadataFields[] lockedFields,
|
||||
MetadataField[] lockedFields,
|
||||
bool replaceData,
|
||||
bool mergeMetadataSettings)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -39,7 +41,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<MusicArtist> source, MetadataResult<MusicArtist> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<MusicArtist> source, MetadataResult<MusicArtist> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
public class ImvdbId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "IMVDb";
|
||||
public string ProviderName => "IMVDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "IMVDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => null;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -25,7 +27,7 @@ namespace MediaBrowser.Providers.Music
|
||||
protected override void MergeData(
|
||||
MetadataResult<MusicVideo> source,
|
||||
MetadataResult<MusicVideo> target,
|
||||
MetadataFields[] lockedFields,
|
||||
MetadataField[] lockedFields,
|
||||
bool replaceData,
|
||||
bool mergeMetadataSettings)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.MusicGenres
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<MusicGenre> source, MetadataResult<MusicGenre> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<MusicGenre> source, MetadataResult<MusicGenre> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.People
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Person> source, MetadataResult<Person> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Person> source, MetadataResult<Person> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Photos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -22,7 +24,7 @@ namespace MediaBrowser.Providers.Photos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -8,7 +10,6 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PlaylistsNET.Content;
|
||||
|
||||
@@ -20,17 +21,18 @@ namespace MediaBrowser.Providers.Playlists
|
||||
IPreRefreshProvider,
|
||||
IHasItemChangeMonitor
|
||||
{
|
||||
private ILogger _logger;
|
||||
private IFileSystem _fileSystem;
|
||||
private readonly ILogger<PlaylistItemsProvider> _logger;
|
||||
|
||||
public PlaylistItemsProvider(IFileSystem fileSystem, ILogger<PlaylistItemsProvider> logger)
|
||||
public PlaylistItemsProvider(ILogger<PlaylistItemsProvider> logger)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string Name => "Playlist Reader";
|
||||
|
||||
// Run last
|
||||
public int Order => 100;
|
||||
|
||||
public Task<ItemUpdateType> FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = item.Path;
|
||||
@@ -61,18 +63,22 @@ namespace MediaBrowser.Providers.Playlists
|
||||
{
|
||||
return GetWplItems(stream);
|
||||
}
|
||||
|
||||
if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetZplItems(stream);
|
||||
}
|
||||
|
||||
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetM3uItems(stream);
|
||||
}
|
||||
|
||||
if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetM3u8Items(stream);
|
||||
}
|
||||
|
||||
if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetPlsItems(stream);
|
||||
@@ -95,7 +101,7 @@ namespace MediaBrowser.Providers.Playlists
|
||||
|
||||
private IEnumerable<LinkedChild> GetM3u8Items(Stream stream)
|
||||
{
|
||||
var content = new M3u8Content();
|
||||
var content = new M3uContent();
|
||||
var playlist = content.GetFromStream(stream);
|
||||
|
||||
return playlist.PlaylistEntries.Select(i => new LinkedChild
|
||||
@@ -157,7 +163,5 @@ namespace MediaBrowser.Providers.Playlists
|
||||
|
||||
return false;
|
||||
}
|
||||
// Run last
|
||||
public int Order => 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -37,7 +39,7 @@ namespace MediaBrowser.Providers.Playlists
|
||||
=> item.GetLinkedChildren();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -15,13 +18,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
public class AudioDbAlbumImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IJsonSerializer json)
|
||||
public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IJsonSerializer json)
|
||||
{
|
||||
_config = config;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_json = json;
|
||||
}
|
||||
|
||||
@@ -45,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var id = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
|
||||
var id = item.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
@@ -92,13 +95,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
return httpClient.GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -24,16 +26,16 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public static AudioDbAlbumProvider Current;
|
||||
|
||||
public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json)
|
||||
public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
|
||||
{
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_json = json;
|
||||
|
||||
Current = this;
|
||||
@@ -104,11 +106,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
item.Genres = new[] { result.strGenre };
|
||||
}
|
||||
|
||||
item.SetProviderId(MetadataProviders.AudioDbArtist, result.idArtist);
|
||||
item.SetProviderId(MetadataProviders.AudioDbAlbum, result.idAlbum);
|
||||
item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist);
|
||||
item.SetProviderId(MetadataProvider.AudioDbAlbum, result.idAlbum);
|
||||
|
||||
item.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, result.strMusicBrainzArtistID);
|
||||
item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, result.strMusicBrainzID);
|
||||
item.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, result.strMusicBrainzArtistID);
|
||||
item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, result.strMusicBrainzID);
|
||||
|
||||
string overview = null;
|
||||
|
||||
@@ -172,18 +174,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
using (var httpResponse = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (var response = httpResponse.Content)
|
||||
using (var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
||||
{
|
||||
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
|
||||
}
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string GetAlbumDataPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId)
|
||||
@@ -210,42 +204,79 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
public class Album
|
||||
{
|
||||
public string idAlbum { get; set; }
|
||||
|
||||
public string idArtist { get; set; }
|
||||
|
||||
public string strAlbum { get; set; }
|
||||
|
||||
public string strArtist { get; set; }
|
||||
|
||||
public string intYearReleased { get; set; }
|
||||
|
||||
public string strGenre { get; set; }
|
||||
|
||||
public string strSubGenre { get; set; }
|
||||
|
||||
public string strReleaseFormat { get; set; }
|
||||
|
||||
public string intSales { get; set; }
|
||||
|
||||
public string strAlbumThumb { get; set; }
|
||||
|
||||
public string strAlbumCDart { get; set; }
|
||||
|
||||
public string strDescriptionEN { get; set; }
|
||||
|
||||
public string strDescriptionDE { get; set; }
|
||||
|
||||
public string strDescriptionFR { get; set; }
|
||||
|
||||
public string strDescriptionCN { get; set; }
|
||||
|
||||
public string strDescriptionIT { get; set; }
|
||||
|
||||
public string strDescriptionJP { get; set; }
|
||||
|
||||
public string strDescriptionRU { get; set; }
|
||||
|
||||
public string strDescriptionES { get; set; }
|
||||
|
||||
public string strDescriptionPT { get; set; }
|
||||
|
||||
public string strDescriptionSE { get; set; }
|
||||
|
||||
public string strDescriptionNL { get; set; }
|
||||
|
||||
public string strDescriptionHU { get; set; }
|
||||
|
||||
public string strDescriptionNO { get; set; }
|
||||
|
||||
public string strDescriptionIL { get; set; }
|
||||
|
||||
public string strDescriptionPL { get; set; }
|
||||
|
||||
public object intLoved { get; set; }
|
||||
|
||||
public object intScore { get; set; }
|
||||
|
||||
public string strReview { get; set; }
|
||||
|
||||
public object strMood { get; set; }
|
||||
|
||||
public object strTheme { get; set; }
|
||||
|
||||
public object strSpeed { get; set; }
|
||||
|
||||
public object strLocation { get; set; }
|
||||
|
||||
public string strMusicBrainzID { get; set; }
|
||||
|
||||
public string strMusicBrainzArtistID { get; set; }
|
||||
|
||||
public object strItunesID { get; set; }
|
||||
|
||||
public object strAmazonID { get; set; }
|
||||
|
||||
public string strLocked { get; set; }
|
||||
}
|
||||
|
||||
@@ -255,7 +286,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -15,14 +18,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClient httpClient)
|
||||
public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_config = config;
|
||||
_json = json;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -47,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
|
||||
var id = item.GetProviderId(MetadataProvider.MusicBrainzArtist);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
@@ -133,13 +136,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
return list;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
return httpClient.GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -21,25 +23,25 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IHasOrder
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public static AudioDbArtistProvider Current;
|
||||
|
||||
private const string ApiKey = "195003";
|
||||
public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey;
|
||||
|
||||
public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json)
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
|
||||
{
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_json = json;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public static AudioDbArtistProvider Current { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDB";
|
||||
|
||||
@@ -85,15 +87,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
|
||||
private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage)
|
||||
{
|
||||
//item.HomePageUrl = result.strWebsite;
|
||||
// item.HomePageUrl = result.strWebsite;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.strGenre))
|
||||
{
|
||||
item.Genres = new[] { result.strGenre };
|
||||
}
|
||||
|
||||
item.SetProviderId(MetadataProviders.AudioDbArtist, result.idArtist);
|
||||
item.SetProviderId(MetadataProviders.MusicBrainzArtist, result.strMusicBrainzID);
|
||||
item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist);
|
||||
item.SetProviderId(MetadataProvider.MusicBrainzArtist, result.strMusicBrainzID);
|
||||
|
||||
string overview = null;
|
||||
|
||||
@@ -153,23 +155,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
|
||||
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
|
||||
|
||||
using (var httpResponse = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (var response = httpResponse.Content)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
|
||||
using (var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
||||
{
|
||||
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,45 +191,85 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
public class Artist
|
||||
{
|
||||
public string idArtist { get; set; }
|
||||
|
||||
public string strArtist { get; set; }
|
||||
|
||||
public string strArtistAlternate { get; set; }
|
||||
|
||||
public object idLabel { get; set; }
|
||||
|
||||
public string intFormedYear { get; set; }
|
||||
|
||||
public string intBornYear { get; set; }
|
||||
|
||||
public object intDiedYear { get; set; }
|
||||
|
||||
public object strDisbanded { get; set; }
|
||||
|
||||
public string strGenre { get; set; }
|
||||
|
||||
public string strSubGenre { get; set; }
|
||||
|
||||
public string strWebsite { get; set; }
|
||||
|
||||
public string strFacebook { get; set; }
|
||||
|
||||
public string strTwitter { get; set; }
|
||||
|
||||
public string strBiographyEN { get; set; }
|
||||
|
||||
public string strBiographyDE { get; set; }
|
||||
|
||||
public string strBiographyFR { get; set; }
|
||||
|
||||
public string strBiographyCN { get; set; }
|
||||
|
||||
public string strBiographyIT { get; set; }
|
||||
|
||||
public string strBiographyJP { get; set; }
|
||||
|
||||
public string strBiographyRU { get; set; }
|
||||
|
||||
public string strBiographyES { get; set; }
|
||||
|
||||
public string strBiographyPT { get; set; }
|
||||
|
||||
public string strBiographySE { get; set; }
|
||||
|
||||
public string strBiographyNL { get; set; }
|
||||
|
||||
public string strBiographyHU { get; set; }
|
||||
|
||||
public string strBiographyNO { get; set; }
|
||||
|
||||
public string strBiographyIL { get; set; }
|
||||
|
||||
public string strBiographyPL { get; set; }
|
||||
|
||||
public string strGender { get; set; }
|
||||
|
||||
public string intMembers { get; set; }
|
||||
|
||||
public string strCountry { get; set; }
|
||||
|
||||
public string strCountryCode { get; set; }
|
||||
|
||||
public string strArtistThumb { get; set; }
|
||||
|
||||
public string strArtistLogo { get; set; }
|
||||
|
||||
public string strArtistFanart { get; set; }
|
||||
|
||||
public string strArtistFanart2 { get; set; }
|
||||
|
||||
public string strArtistFanart3 { get; set; }
|
||||
|
||||
public string strArtistBanner { get; set; }
|
||||
|
||||
public string strMusicBrainzID { get; set; }
|
||||
|
||||
public object strLastFMChart { get; set; }
|
||||
|
||||
public string strLocked { get; set; }
|
||||
}
|
||||
|
||||
@@ -247,7 +279,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is MusicAlbum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is MusicArtist;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbOtherAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using MediaBrowser.Model.Plugins;
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
|
||||
@@ -28,29 +28,31 @@
|
||||
pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8"
|
||||
};
|
||||
|
||||
$('.configPage').on('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||
$('#enable').checked = config.Enable;
|
||||
$('#replaceAlbumName').checked = config.ReplaceAlbumName;
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
$('.configForm').on('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
var form = this;
|
||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||
config.Enable = $('#enable', form).checked;
|
||||
config.ReplaceAlbumName = $('#replaceAlbumName', form).checked;
|
||||
|
||||
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
document.querySelector('.configPage')
|
||||
.addEventListener('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||
document.querySelector('#enable').checked = config.Enable;
|
||||
document.querySelector('#replaceAlbumName').checked = config.ReplaceAlbumName;
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
document.querySelector('.configForm')
|
||||
.addEventListener('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||
config.Enable = document.querySelector('#enable').checked;
|
||||
config.ReplaceAlbumName = document.querySelector('#replaceAlbumName').checked;
|
||||
|
||||
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.AudioDbAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is MusicAlbum;
|
||||
}
|
||||
|
||||
public class AudioDbOtherAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb Album";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.AudioDbAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||
}
|
||||
|
||||
public class AudioDbArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.AudioDbArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is MusicArtist;
|
||||
}
|
||||
|
||||
public class AudioDbOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb Artist";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.AudioDbArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
@@ -9,6 +11,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
{
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public static Plugin Instance { get; private set; }
|
||||
|
||||
public override Guid Id => new Guid("a629c0da-fac5-4c7e-931a-7174223f14c8");
|
||||
@@ -17,11 +25,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
|
||||
public override string Description => "Get artist and album metadata or images from AudioDB.";
|
||||
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
// TODO remove when plugin removed from server.
|
||||
public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml";
|
||||
|
||||
public IEnumerable<PluginPageInfo> GetPages()
|
||||
{
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@@ -35,21 +37,19 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
return GetResultsFromResponse(stream);
|
||||
}
|
||||
using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
return GetResultsFromResponse(stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
// They seem to throw bad request failures on any term with a slash
|
||||
var nameToSearch = searchInfo.Name.Replace('/', ' ');
|
||||
|
||||
var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
|
||||
var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
|
||||
|
||||
using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
await using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
var results = GetResultsFromResponse(stream).ToList();
|
||||
|
||||
@@ -62,15 +62,11 @@ namespace MediaBrowser.Providers.Music
|
||||
if (HasDiacritics(searchInfo.Name))
|
||||
{
|
||||
// Try again using the search with accent characters url
|
||||
url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
|
||||
url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
|
||||
|
||||
using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
return GetResultsFromResponse(stream);
|
||||
}
|
||||
}
|
||||
using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
return GetResultsFromResponse(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,11 +104,13 @@ namespace MediaBrowser.Providers.Music
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
return ParseArtistList(subReader).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -150,6 +148,7 @@ namespace MediaBrowser.Providers.Music
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
var mbzId = reader.GetAttribute("id");
|
||||
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
@@ -160,8 +159,10 @@ namespace MediaBrowser.Providers.Music
|
||||
yield return artist;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -197,11 +198,13 @@ namespace MediaBrowser.Providers.Music
|
||||
result.Name = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "annotation":
|
||||
{
|
||||
result.Overview = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// there is sort-name if ever needed
|
||||
@@ -216,7 +219,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
}
|
||||
|
||||
result.SetProviderId(MetadataProviders.MusicBrainzArtist, artistId);
|
||||
result.SetProviderId(MetadataProvider.MusicBrainzArtist, artistId);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name))
|
||||
{
|
||||
@@ -249,7 +252,7 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
if (singleResult != null)
|
||||
{
|
||||
musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist);
|
||||
musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
|
||||
result.Item.Overview = singleResult.Overview;
|
||||
|
||||
if (Plugin.Instance.Configuration.ReplaceArtistName)
|
||||
@@ -262,7 +265,7 @@ namespace MediaBrowser.Providers.Music
|
||||
if (!string.IsNullOrWhiteSpace(musicBrainzId))
|
||||
{
|
||||
result.HasMetadata = true;
|
||||
result.Item.SetProviderId(MetadataProviders.MusicBrainzArtist, musicBrainzId);
|
||||
result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -290,7 +293,7 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
public string Name => "MusicBrainz";
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using MediaBrowser.Model.Plugins;
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.MusicBrainz
|
||||
{
|
||||
|
||||
@@ -36,33 +36,47 @@
|
||||
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
|
||||
};
|
||||
|
||||
$('.musicBrainzConfigPage').on('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||
$('#server').val(config.Server).change();
|
||||
$('#rateLimit').val(config.RateLimit).change();
|
||||
$('#enable').checked = config.Enable;
|
||||
$('#replaceArtistName').checked = config.ReplaceArtistName;
|
||||
document.querySelector('.musicBrainzConfigPage')
|
||||
.addEventListener('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||
var server = document.querySelector('#server');
|
||||
server.value = config.Server;
|
||||
server.dispatchEvent(new Event('change', {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
}));
|
||||
|
||||
var rateLimit = document.querySelector('#rateLimit');
|
||||
rateLimit.value = config.RateLimit;
|
||||
rateLimit.dispatchEvent(new Event('change', {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
}));
|
||||
|
||||
document.querySelector('#enable').checked = config.Enable;
|
||||
document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.musicBrainzConfigForm').on('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
var form = this;
|
||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||
config.Server = $('#server', form).val();
|
||||
config.RateLimit = $('#rateLimit', form).val();
|
||||
config.Enable = $('#enable', form).checked;
|
||||
config.ReplaceArtistName = $('#replaceArtistName', form).checked;
|
||||
|
||||
ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
|
||||
document.querySelector('.musicBrainzConfigForm')
|
||||
.addEventListener('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||
config.Server = document.querySelector('#server').value;
|
||||
config.RateLimit = document.querySelector('#rateLimit').value;
|
||||
config.Enable = document.querySelector('#enable').checked;
|
||||
config.ReplaceArtistName = document.querySelector('#replaceArtistName').checked;
|
||||
|
||||
ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
@@ -8,10 +11,13 @@ namespace MediaBrowser.Providers.Music
|
||||
public class MusicBrainzReleaseGroupExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Release Group";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString();
|
||||
public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
|
||||
@@ -23,10 +29,13 @@ namespace MediaBrowser.Providers.Music
|
||||
public class MusicBrainzAlbumArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album Artist";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString();
|
||||
public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
@@ -38,10 +47,13 @@ namespace MediaBrowser.Providers.Music
|
||||
public class MusicBrainzAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzAlbum.ToString();
|
||||
public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
|
||||
@@ -53,10 +65,13 @@ namespace MediaBrowser.Providers.Music
|
||||
public class MusicBrainzArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzArtist.ToString();
|
||||
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
@@ -68,11 +83,14 @@ namespace MediaBrowser.Providers.Music
|
||||
public class MusicBrainzOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Artist";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public string Key => MetadataProviders.MusicBrainzArtist.ToString();
|
||||
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
@@ -84,10 +102,13 @@ namespace MediaBrowser.Providers.Music
|
||||
public class MusicBrainzTrackId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Track";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.MusicBrainzTrack.ToString();
|
||||
public string Key => MetadataProvider.MusicBrainzTrack.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -23,6 +25,13 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// For each single MB lookup/search, this is the maximum number of
|
||||
/// attempts that shall be made whilst receiving a 503 Server
|
||||
/// Unavailable (indicating throttled) response.
|
||||
/// </summary>
|
||||
private const uint MusicBrainzQueryAttempts = 5u;
|
||||
|
||||
/// <summary>
|
||||
/// The Jellyfin user-agent is unrestricted but source IP must not exceed
|
||||
/// one request per second, therefore we rate limit to avoid throttling.
|
||||
@@ -31,29 +40,21 @@ namespace MediaBrowser.Providers.Music
|
||||
/// </summary>
|
||||
private readonly long _musicBrainzQueryIntervalMs;
|
||||
|
||||
/// <summary>
|
||||
/// For each single MB lookup/search, this is the maximum number of
|
||||
/// attempts that shall be made whilst receiving a 503 Server
|
||||
/// Unavailable (indicating throttled) response.
|
||||
/// </summary>
|
||||
private const uint MusicBrainzQueryAttempts = 5u;
|
||||
|
||||
internal static MusicBrainzAlbumProvider Current;
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<MusicBrainzAlbumProvider> _logger;
|
||||
|
||||
private readonly string _musicBrainzBaseUrl;
|
||||
|
||||
private SemaphoreSlim _apiRequestLock = new SemaphoreSlim(1, 1);
|
||||
private Stopwatch _stopWatchMusicBrainz = new Stopwatch();
|
||||
|
||||
public MusicBrainzAlbumProvider(
|
||||
IHttpClient httpClient,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IApplicationHost appHost,
|
||||
ILogger<MusicBrainzAlbumProvider> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
|
||||
@@ -66,6 +67,8 @@ namespace MediaBrowser.Providers.Music
|
||||
Current = this;
|
||||
}
|
||||
|
||||
internal static MusicBrainzAlbumProvider Current { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz";
|
||||
|
||||
@@ -109,7 +112,7 @@ namespace MediaBrowser.Providers.Music
|
||||
else
|
||||
{
|
||||
// I'm sure there is a better way but for now it resolves search for 12" Mixes
|
||||
var queryName = searchInfo.Name.Replace("\"", string.Empty);
|
||||
var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
|
||||
|
||||
url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
@@ -121,11 +124,9 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
return GetResultsFromResponse(stream);
|
||||
}
|
||||
using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
return GetResultsFromResponse(stream);
|
||||
}
|
||||
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
@@ -163,17 +164,17 @@ namespace MediaBrowser.Providers.Music
|
||||
Name = i.Artists[0].Item1
|
||||
};
|
||||
|
||||
result.AlbumArtist.SetProviderId(MetadataProviders.MusicBrainzArtist, i.Artists[0].Item2);
|
||||
result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(i.ReleaseId))
|
||||
{
|
||||
result.SetProviderId(MetadataProviders.MusicBrainzAlbum, i.ReleaseId);
|
||||
result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
|
||||
{
|
||||
result.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, i.ReleaseGroupId);
|
||||
result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -247,12 +248,12 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
if (!string.IsNullOrEmpty(releaseId))
|
||||
{
|
||||
result.Item.SetProviderId(MetadataProviders.MusicBrainzAlbum, releaseId);
|
||||
result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(releaseGroupId))
|
||||
{
|
||||
result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId);
|
||||
result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,27 +277,25 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}",
|
||||
var url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"/ws/2/release/?query=\"{0}\" AND arid:{1}",
|
||||
WebUtility.UrlEncode(albumName),
|
||||
artistId);
|
||||
|
||||
using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
using (var oReader = new StreamReader(stream, Encoding.UTF8))
|
||||
using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
using var oReader = new StreamReader(stream, Encoding.UTF8);
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
var settings = new XmlReaderSettings()
|
||||
{
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
using (var reader = XmlReader.Create(oReader, settings))
|
||||
{
|
||||
return ReleaseResult.Parse(reader).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
using var reader = XmlReader.Create(oReader, settings);
|
||||
return ReleaseResult.Parse(reader).FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
|
||||
@@ -307,23 +306,19 @@ namespace MediaBrowser.Providers.Music
|
||||
WebUtility.UrlEncode(albumName),
|
||||
WebUtility.UrlEncode(artistName));
|
||||
|
||||
using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
using (var oReader = new StreamReader(stream, Encoding.UTF8))
|
||||
using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
using var oReader = new StreamReader(stream, Encoding.UTF8);
|
||||
var settings = new XmlReaderSettings()
|
||||
{
|
||||
var settings = new XmlReaderSettings()
|
||||
{
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
using (var reader = XmlReader.Create(oReader, settings))
|
||||
{
|
||||
return ReleaseResult.Parse(reader).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
using var reader = XmlReader.Create(oReader, settings);
|
||||
return ReleaseResult.Parse(reader).FirstOrDefault();
|
||||
}
|
||||
|
||||
private class ReleaseResult
|
||||
@@ -361,6 +356,7 @@ namespace MediaBrowser.Providers.Music
|
||||
return ParseReleaseList(subReader).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -396,6 +392,7 @@ namespace MediaBrowser.Providers.Music
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
var releaseId = reader.GetAttribute("id");
|
||||
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
@@ -406,8 +403,10 @@ namespace MediaBrowser.Providers.Music
|
||||
yield return release;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -446,6 +445,7 @@ namespace MediaBrowser.Providers.Music
|
||||
result.Title = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "date":
|
||||
{
|
||||
var val = reader.ReadElementContentAsString();
|
||||
@@ -453,19 +453,23 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
result.Year = date.Year;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "annotation":
|
||||
{
|
||||
result.Overview = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "release-group":
|
||||
{
|
||||
result.ReleaseGroupId = reader.GetAttribute("id");
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
|
||||
case "artist-credit":
|
||||
{
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
@@ -480,6 +484,7 @@ namespace MediaBrowser.Providers.Music
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -497,7 +502,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueTuple<string, string> ParseArtistCredit(XmlReader reader)
|
||||
private static (string, string) ParseArtistCredit(XmlReader reader)
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
@@ -518,6 +523,7 @@ namespace MediaBrowser.Providers.Music
|
||||
return ParseArtistNameCredit(subReader);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -531,7 +537,7 @@ namespace MediaBrowser.Providers.Music
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueTuple<string, string>();
|
||||
return default;
|
||||
}
|
||||
|
||||
private static (string, string) ParseArtistNameCredit(XmlReader reader)
|
||||
@@ -556,6 +562,7 @@ namespace MediaBrowser.Providers.Music
|
||||
return ParseArtistArtistCredit(subReader, id);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -593,6 +600,7 @@ namespace MediaBrowser.Providers.Music
|
||||
name = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -613,30 +621,21 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
using (var oReader = new StreamReader(stream, Encoding.UTF8))
|
||||
using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
using var oReader = new StreamReader(stream, Encoding.UTF8);
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
var settings = new XmlReaderSettings()
|
||||
{
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
using (var reader = XmlReader.Create(oReader, settings))
|
||||
{
|
||||
var result = ReleaseResult.Parse(reader).FirstOrDefault();
|
||||
using var reader = XmlReader.Create(oReader, settings);
|
||||
var result = ReleaseResult.Parse(reader).FirstOrDefault();
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result.ReleaseId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return result?.ReleaseId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -649,57 +648,57 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
using (var oReader = new StreamReader(stream, Encoding.UTF8))
|
||||
using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
using var oReader = new StreamReader(stream, Encoding.UTF8);
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
var settings = new XmlReaderSettings()
|
||||
{
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
ValidationType = ValidationType.None,
|
||||
CheckCharacters = false,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
using (var reader = XmlReader.Create(oReader, settings))
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
using (var reader = XmlReader.Create(oReader, settings))
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
switch (reader.Name)
|
||||
{
|
||||
switch (reader.Name)
|
||||
case "release-group-list":
|
||||
{
|
||||
case "release-group-list":
|
||||
{
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
return GetFirstReleaseGroupId(subReader);
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
using (var subReader = reader.ReadSubtree())
|
||||
{
|
||||
return GetFirstReleaseGroupId(subReader);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,6 +718,7 @@ namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
return reader.GetAttribute("id");
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
reader.Skip();
|
||||
@@ -741,58 +741,64 @@ namespace MediaBrowser.Providers.Music
|
||||
/// A number of retries shall be made in order to try and satisfy the request before
|
||||
/// giving up and returning null.
|
||||
/// </summary>
|
||||
internal async Task<HttpResponseInfo> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
|
||||
internal async Task<HttpResponseMessage> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
var options = new HttpRequestOptions
|
||||
await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
Url = _musicBrainzBaseUrl.TrimEnd('/') + url,
|
||||
CancellationToken = cancellationToken,
|
||||
// MusicBrainz request a contact email address is supplied, as comment, in user agent field:
|
||||
// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent
|
||||
UserAgent = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} ( {1} )",
|
||||
_appHost.ApplicationUserAgent,
|
||||
_appHost.ApplicationUserAgentAddress),
|
||||
BufferContent = false
|
||||
};
|
||||
HttpResponseMessage response;
|
||||
var attempts = 0u;
|
||||
var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url;
|
||||
|
||||
HttpResponseInfo response;
|
||||
var attempts = 0u;
|
||||
|
||||
do
|
||||
{
|
||||
attempts++;
|
||||
|
||||
if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs)
|
||||
do
|
||||
{
|
||||
// MusicBrainz is extremely adamant about limiting to one request per second
|
||||
var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
|
||||
await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
|
||||
attempts++;
|
||||
|
||||
if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs)
|
||||
{
|
||||
// MusicBrainz is extremely adamant about limiting to one request per second.
|
||||
var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
|
||||
await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Write time since last request to debug log as evidence we're meeting rate limit
|
||||
// requirement, before resetting stopwatch back to zero.
|
||||
_logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
|
||||
_stopWatchMusicBrainz.Restart();
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
|
||||
|
||||
// MusicBrainz request a contact email address is supplied, as comment, in user agent field:
|
||||
// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent .
|
||||
request.Headers.UserAgent.ParseAdd(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} ( {1} )",
|
||||
_appHost.ApplicationUserAgent,
|
||||
_appHost.ApplicationUserAgentAddress));
|
||||
|
||||
response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(request).ConfigureAwait(false);
|
||||
|
||||
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling).
|
||||
}
|
||||
while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
|
||||
|
||||
// Log error if unable to query MB database due to throttling.
|
||||
if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable)
|
||||
{
|
||||
_logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl);
|
||||
}
|
||||
|
||||
// Write time since last request to debug log as evidence we're meeting rate limit
|
||||
// requirement, before resetting stopwatch back to zero.
|
||||
_logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
|
||||
_stopWatchMusicBrainz.Restart();
|
||||
|
||||
response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
|
||||
|
||||
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling)
|
||||
return response;
|
||||
}
|
||||
while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
|
||||
|
||||
// Log error if unable to query MB database due to throttling
|
||||
if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable)
|
||||
finally
|
||||
{
|
||||
_logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url);
|
||||
_apiRequestLock.Release();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
@@ -21,6 +23,9 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
|
||||
|
||||
public const long DefaultRateLimit = 2000u;
|
||||
|
||||
// TODO remove when plugin removed from server.
|
||||
public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
|
||||
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
{
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
public bool CastAndCrew { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>OMDb</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="castAndCrew" />
|
||||
<span>Collect information about the cast and other crew members from OMDb.</span>
|
||||
</label>
|
||||
<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: "a628c0da-fac5-4c7e-9d1a-7134223f14c8"
|
||||
};
|
||||
|
||||
document.querySelector('.configPage')
|
||||
.addEventListener('pageshow', function () {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||
document.querySelector('#castAndCrew').checked = config.CastAndCrew;
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
document.querySelector('.configForm')
|
||||
.addEventListener('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||
config.CastAndCrew = document.querySelector('#castAndCrew').checked;
|
||||
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -17,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
public class OmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly OmdbItemProvider _itemProvider;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
@@ -26,17 +28,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
public OmdbEpisodeProvider(
|
||||
IJsonSerializer jsonSerializer,
|
||||
IApplicationHost appHost,
|
||||
IHttpClient httpClient,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager,
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager configurationManager)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_appHost = appHost;
|
||||
_itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, libraryManager, fileSystem, configurationManager);
|
||||
_itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClientFactory, libraryManager, fileSystem, configurationManager);
|
||||
}
|
||||
|
||||
// After TheTvDb
|
||||
@@ -63,19 +65,19 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
return result;
|
||||
}
|
||||
|
||||
if (info.SeriesProviderIds.TryGetValue(MetadataProviders.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)
|
||||
{
|
||||
result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager)
|
||||
.FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProviders.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager)
|
||||
.FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProvider.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _itemProvider.GetImageResponse(url, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
@@ -17,21 +21,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
{
|
||||
public class OmdbImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
|
||||
public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public string Name => "The Open Movie Database";
|
||||
|
||||
// After other internet providers, because they're better
|
||||
// But before fallback providers like screengrab
|
||||
public int Order => 90;
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
@@ -42,11 +52,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
|
||||
var imdbId = item.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
var provider = new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager);
|
||||
var provider = new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdbId))
|
||||
{
|
||||
@@ -68,7 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
list.Add(new RemoteImageInfo
|
||||
{
|
||||
ProviderName = Name,
|
||||
Url = string.Format("https://img.omdbapi.com/?i={0}&apikey=2c9d9507", imdbId)
|
||||
Url = string.Format(CultureInfo.InvariantCulture, "https://img.omdbapi.com/?i={0}&apikey=2c9d9507", imdbId)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -77,23 +87,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
return list;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name => "The Open Movie Database";
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Movie || item is Trailer || item is Episode;
|
||||
}
|
||||
// After other internet providers, because they're better
|
||||
// But before fallback providers like screengrab
|
||||
public int Order => 90;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
@@ -24,7 +27,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
@@ -33,19 +36,21 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
public OmdbItemProvider(
|
||||
IJsonSerializer jsonSerializer,
|
||||
IApplicationHost appHost,
|
||||
IHttpClient httpClient,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager,
|
||||
IFileSystem fileSystem,
|
||||
IServerConfigurationManager configurationManager)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public string Name => "The Open Movie Database";
|
||||
|
||||
// After primary option
|
||||
public int Order => 2;
|
||||
|
||||
@@ -68,12 +73,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
{
|
||||
var episodeSearchInfo = searchInfo as EpisodeInfo;
|
||||
|
||||
var imdbId = searchInfo.GetProviderId(MetadataProviders.Imdb);
|
||||
var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
var urlQuery = "plot=full&r=json";
|
||||
if (type == "episode" && episodeSearchInfo != null)
|
||||
{
|
||||
episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out imdbId);
|
||||
episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out imdbId);
|
||||
}
|
||||
|
||||
var name = searchInfo.Name;
|
||||
@@ -103,6 +108,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
{
|
||||
urlQuery += "&t=" + WebUtility.UrlEncode(name);
|
||||
}
|
||||
|
||||
urlQuery += "&type=" + type;
|
||||
}
|
||||
else
|
||||
@@ -117,75 +123,72 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
{
|
||||
urlQuery += string.Format(CultureInfo.InvariantCulture, "&Episode={0}", searchInfo.IndexNumber);
|
||||
}
|
||||
|
||||
if (searchInfo.ParentIndexNumber.HasValue)
|
||||
{
|
||||
urlQuery += string.Format(CultureInfo.InvariantCulture, "&Season={0}", searchInfo.ParentIndexNumber);
|
||||
}
|
||||
}
|
||||
|
||||
var url = OmdbProvider.GetOmdbUrl(urlQuery, _appHost, cancellationToken);
|
||||
var url = OmdbProvider.GetOmdbUrl(urlQuery);
|
||||
|
||||
using (var response = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
|
||||
using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var resultList = new List<SearchResult>();
|
||||
|
||||
if (isSearch)
|
||||
{
|
||||
using (var stream = response.Content)
|
||||
var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync<SearchResultList>(stream).ConfigureAwait(false);
|
||||
if (searchResultList != null && searchResultList.Search != null)
|
||||
{
|
||||
var resultList = new List<SearchResult>();
|
||||
|
||||
if (isSearch)
|
||||
{
|
||||
var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync<SearchResultList>(stream).ConfigureAwait(false);
|
||||
if (searchResultList != null && searchResultList.Search != null)
|
||||
{
|
||||
resultList.AddRange(searchResultList.Search);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await _jsonSerializer.DeserializeFromStreamAsync<SearchResult>(stream).ConfigureAwait(false);
|
||||
if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resultList.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return resultList.Select(result =>
|
||||
{
|
||||
var item = new RemoteSearchResult
|
||||
{
|
||||
IndexNumber = searchInfo.IndexNumber,
|
||||
Name = result.Title,
|
||||
ParentIndexNumber = searchInfo.ParentIndexNumber,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
|
||||
if (episodeSearchInfo != null && episodeSearchInfo.IndexNumberEnd.HasValue)
|
||||
{
|
||||
item.IndexNumberEnd = episodeSearchInfo.IndexNumberEnd.Value;
|
||||
}
|
||||
|
||||
item.SetProviderId(MetadataProviders.Imdb, result.imdbID);
|
||||
|
||||
if (result.Year.Length > 0
|
||||
&& int.TryParse(result.Year.Substring(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear))
|
||||
{
|
||||
item.ProductionYear = parsedYear;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Released)
|
||||
&& DateTime.TryParse(result.Released, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var released))
|
||||
{
|
||||
item.PremiereDate = released;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result.Poster) && !string.Equals(result.Poster, "N/A", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ImageUrl = result.Poster;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
resultList.AddRange(searchResultList.Search);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await _jsonSerializer.DeserializeFromStreamAsync<SearchResult>(stream).ConfigureAwait(false);
|
||||
if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resultList.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return resultList.Select(result =>
|
||||
{
|
||||
var item = new RemoteSearchResult
|
||||
{
|
||||
IndexNumber = searchInfo.IndexNumber,
|
||||
Name = result.Title,
|
||||
ParentIndexNumber = searchInfo.ParentIndexNumber,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
|
||||
if (episodeSearchInfo != null && episodeSearchInfo.IndexNumberEnd.HasValue)
|
||||
{
|
||||
item.IndexNumberEnd = episodeSearchInfo.IndexNumberEnd.Value;
|
||||
}
|
||||
|
||||
item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
|
||||
|
||||
if (result.Year.Length > 0
|
||||
&& int.TryParse(result.Year.AsSpan().Slice(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear))
|
||||
{
|
||||
item.ProductionYear = parsedYear;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Released)
|
||||
&& DateTime.TryParse(result.Released, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var released))
|
||||
{
|
||||
item.PremiereDate = released;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result.Poster) && !string.Equals(result.Poster, "N/A", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ImageUrl = result.Poster;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken)
|
||||
@@ -198,8 +201,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
return GetSearchResults(searchInfo, "movie", cancellationToken);
|
||||
}
|
||||
|
||||
public string Name => "The Open Movie Database";
|
||||
|
||||
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Series>
|
||||
@@ -208,7 +209,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
QueriedById = true
|
||||
};
|
||||
|
||||
var imdbId = info.GetProviderId(MetadataProviders.Imdb);
|
||||
var imdbId = info.GetProviderId(MetadataProvider.Imdb);
|
||||
if (string.IsNullOrWhiteSpace(imdbId))
|
||||
{
|
||||
imdbId = await GetSeriesImdbId(info, cancellationToken).ConfigureAwait(false);
|
||||
@@ -217,10 +218,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
result.Item.SetProviderId(MetadataProviders.Imdb, imdbId);
|
||||
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
|
||||
result.HasMetadata = true;
|
||||
|
||||
await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -240,7 +241,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
QueriedById = true
|
||||
};
|
||||
|
||||
var imdbId = info.GetProviderId(MetadataProviders.Imdb);
|
||||
var imdbId = info.GetProviderId(MetadataProvider.Imdb);
|
||||
if (string.IsNullOrWhiteSpace(imdbId))
|
||||
{
|
||||
imdbId = await GetMovieImdbId(info, cancellationToken).ConfigureAwait(false);
|
||||
@@ -249,10 +250,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
result.Item.SetProviderId(MetadataProviders.Imdb, imdbId);
|
||||
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
|
||||
result.HasMetadata = true;
|
||||
|
||||
await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -262,49 +263,67 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
{
|
||||
var results = await GetSearchResultsInternal(info, "movie", false, cancellationToken).ConfigureAwait(false);
|
||||
var first = results.FirstOrDefault();
|
||||
return first == null ? null : first.GetProviderId(MetadataProviders.Imdb);
|
||||
return first?.GetProviderId(MetadataProvider.Imdb);
|
||||
}
|
||||
|
||||
private async Task<string> GetSeriesImdbId(SeriesInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var results = await GetSearchResultsInternal(info, "series", false, cancellationToken).ConfigureAwait(false);
|
||||
var first = results.FirstOrDefault();
|
||||
return first == null ? null : first.GetProviderId(MetadataProviders.Imdb);
|
||||
return first?.GetProviderId(MetadataProvider.Imdb);
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
class SearchResult
|
||||
private class SearchResult
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Year { get; set; }
|
||||
|
||||
public string Rated { get; set; }
|
||||
|
||||
public string Released { get; set; }
|
||||
|
||||
public string Season { get; set; }
|
||||
|
||||
public string Episode { get; set; }
|
||||
|
||||
public string Runtime { get; set; }
|
||||
|
||||
public string Genre { get; set; }
|
||||
|
||||
public string Director { get; set; }
|
||||
|
||||
public string Writer { get; set; }
|
||||
|
||||
public string Actors { get; set; }
|
||||
|
||||
public string Plot { get; set; }
|
||||
|
||||
public string Language { get; set; }
|
||||
|
||||
public string Country { get; set; }
|
||||
|
||||
public string Awards { get; set; }
|
||||
|
||||
public string Poster { get; set; }
|
||||
|
||||
public string Metascore { get; set; }
|
||||
|
||||
public string imdbRating { get; set; }
|
||||
|
||||
public string imdbVotes { get; set; }
|
||||
|
||||
public string imdbID { get; set; }
|
||||
|
||||
public string seriesID { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public string Response { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -23,14 +25,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
|
||||
public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_configurationManager = configurationManager;
|
||||
_appHost = appHost;
|
||||
@@ -60,7 +62,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
|
||||
&& int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year)
|
||||
&& int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year)
|
||||
&& year >= 0)
|
||||
{
|
||||
item.ProductionYear = year;
|
||||
@@ -77,7 +79,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
&& int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount)
|
||||
&& voteCount >= 0)
|
||||
{
|
||||
//item.VoteCount = voteCount;
|
||||
// item.VoteCount = voteCount;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.imdbRating)
|
||||
@@ -87,14 +89,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
item.CommunityRating = imdbRating;
|
||||
}
|
||||
|
||||
//if (!string.IsNullOrEmpty(result.Website))
|
||||
//{
|
||||
// item.HomePageUrl = result.Website;
|
||||
//}
|
||||
if (!string.IsNullOrEmpty(result.Website))
|
||||
{
|
||||
item.HomePageUrl = result.Website;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result.imdbID))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.Imdb, result.imdbID);
|
||||
item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
|
||||
}
|
||||
|
||||
ParseAdditionalMetadata(itemResult, result);
|
||||
@@ -121,7 +123,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(episodeImdbId))
|
||||
{
|
||||
foreach (var episode in (seasonResult.Episodes ?? new RootObject[] { }))
|
||||
foreach (var episode in seasonResult.Episodes)
|
||||
{
|
||||
if (string.Equals(episodeImdbId, episode.imdbID, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -134,7 +136,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
// finally, search by numbers
|
||||
if (result == null)
|
||||
{
|
||||
foreach (var episode in (seasonResult.Episodes ?? new RootObject[] { }))
|
||||
foreach (var episode in seasonResult.Episodes)
|
||||
{
|
||||
if (episode.Episode == episodeNumber)
|
||||
{
|
||||
@@ -161,7 +163,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
|
||||
&& int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year)
|
||||
&& int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year)
|
||||
&& year >= 0)
|
||||
{
|
||||
item.ProductionYear = year;
|
||||
@@ -178,7 +180,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
&& int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount)
|
||||
&& voteCount >= 0)
|
||||
{
|
||||
//item.VoteCount = voteCount;
|
||||
// item.VoteCount = voteCount;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.imdbRating)
|
||||
@@ -188,14 +190,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
item.CommunityRating = imdbRating;
|
||||
}
|
||||
|
||||
//if (!string.IsNullOrEmpty(result.Website))
|
||||
//{
|
||||
// item.HomePageUrl = result.Website;
|
||||
//}
|
||||
if (!string.IsNullOrEmpty(result.Website))
|
||||
{
|
||||
item.HomePageUrl = result.Website;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(result.imdbID))
|
||||
{
|
||||
item.SetProviderId(MetadataProviders.Imdb, result.imdbID);
|
||||
item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
|
||||
}
|
||||
|
||||
ParseAdditionalMetadata(itemResult, result);
|
||||
@@ -243,7 +245,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
|
||||
{
|
||||
if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out string id) && !string.IsNullOrEmpty(id))
|
||||
if (seriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string id) && !string.IsNullOrEmpty(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))
|
||||
@@ -255,15 +257,16 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetOmdbUrl(string query, IApplicationHost appHost, CancellationToken cancellationToken)
|
||||
public static string GetOmdbUrl(string query)
|
||||
{
|
||||
const string url = "https://www.omdbapi.com?apikey=2c9d9507";
|
||||
const string Url = "https://www.omdbapi.com?apikey=2c9d9507";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return url;
|
||||
return Url;
|
||||
}
|
||||
return url + "&" + query;
|
||||
|
||||
return Url + "&" + query;
|
||||
}
|
||||
|
||||
private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
|
||||
@@ -288,17 +291,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
}
|
||||
}
|
||||
|
||||
var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), _appHost, cancellationToken);
|
||||
var url = GetOmdbUrl(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"i={0}&plot=short&tomatoes=true&r=json",
|
||||
imdbParam));
|
||||
|
||||
using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
_jsonSerializer.SerializeToFile(rootObject, path);
|
||||
}
|
||||
}
|
||||
using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
_jsonSerializer.SerializeToFile(rootObject, path);
|
||||
|
||||
return path;
|
||||
}
|
||||
@@ -325,30 +328,25 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
}
|
||||
}
|
||||
|
||||
var url = GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), _appHost, cancellationToken);
|
||||
var url = GetOmdbUrl(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"i={0}&season={1}&detail=full",
|
||||
imdbParam,
|
||||
seasonId));
|
||||
|
||||
using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
_jsonSerializer.SerializeToFile(rootObject, path);
|
||||
}
|
||||
}
|
||||
using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
_jsonSerializer.SerializeToFile(rootObject, path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static Task<HttpResponseInfo> GetOmdbResponse(IHttpClient httpClient, string url, CancellationToken cancellationToken)
|
||||
public static Task<HttpResponseMessage> GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return httpClient.SendAsync(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true,
|
||||
EnableDefaultUserAgent = true
|
||||
}, HttpMethod.Get);
|
||||
return httpClient.GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
internal string GetDataFilePath(string imdbId)
|
||||
@@ -360,7 +358,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
|
||||
|
||||
var filename = string.Format("{0}.json", imdbId);
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "{0}.json", imdbId);
|
||||
|
||||
return Path.Combine(dataPath, filename);
|
||||
}
|
||||
@@ -374,7 +372,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
|
||||
|
||||
var filename = string.Format("{0}_season_{1}.json", imdbId, seasonId);
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "{0}_season_{1}.json", imdbId, seasonId);
|
||||
|
||||
return Path.Combine(dataPath, filename);
|
||||
}
|
||||
@@ -386,7 +384,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
var isConfiguredForEnglish = IsConfiguredForEnglish(item) || _configurationManager.Configuration.EnableNewOmdbSupport;
|
||||
|
||||
// Grab series genres because imdb data is better than tvdb. Leave movies alone
|
||||
// Grab series genres because IMDb data is better than TVDB. Leave movies alone
|
||||
// But only do it if english is the preferred language because this data will not be localized
|
||||
if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre))
|
||||
{
|
||||
@@ -407,45 +405,50 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
item.Overview = result.Plot;
|
||||
}
|
||||
|
||||
//if (!string.IsNullOrWhiteSpace(result.Director))
|
||||
//{
|
||||
// var person = new PersonInfo
|
||||
// {
|
||||
// Name = result.Director.Trim(),
|
||||
// Type = PersonType.Director
|
||||
// };
|
||||
if (!Plugin.Instance.Configuration.CastAndCrew)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// itemResult.AddPerson(person);
|
||||
//}
|
||||
if (!string.IsNullOrWhiteSpace(result.Director))
|
||||
{
|
||||
var person = new PersonInfo
|
||||
{
|
||||
Name = result.Director.Trim(),
|
||||
Type = PersonType.Director
|
||||
};
|
||||
|
||||
//if (!string.IsNullOrWhiteSpace(result.Writer))
|
||||
//{
|
||||
// var person = new PersonInfo
|
||||
// {
|
||||
// Name = result.Director.Trim(),
|
||||
// Type = PersonType.Writer
|
||||
// };
|
||||
itemResult.AddPerson(person);
|
||||
}
|
||||
|
||||
// itemResult.AddPerson(person);
|
||||
//}
|
||||
if (!string.IsNullOrWhiteSpace(result.Writer))
|
||||
{
|
||||
var person = new PersonInfo
|
||||
{
|
||||
Name = result.Director.Trim(),
|
||||
Type = PersonType.Writer
|
||||
};
|
||||
|
||||
//if (!string.IsNullOrWhiteSpace(result.Actors))
|
||||
//{
|
||||
// var actorList = result.Actors.Split(',');
|
||||
// foreach (var actor in actorList)
|
||||
// {
|
||||
// if (!string.IsNullOrWhiteSpace(actor))
|
||||
// {
|
||||
// var person = new PersonInfo
|
||||
// {
|
||||
// Name = actor.Trim(),
|
||||
// Type = PersonType.Actor
|
||||
// };
|
||||
itemResult.AddPerson(person);
|
||||
}
|
||||
|
||||
// itemResult.AddPerson(person);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
if (!string.IsNullOrWhiteSpace(result.Actors))
|
||||
{
|
||||
var actorList = result.Actors.Split(',');
|
||||
foreach (var actor in actorList)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(actor))
|
||||
{
|
||||
var person = new PersonInfo
|
||||
{
|
||||
Name = actor.Trim(),
|
||||
Type = PersonType.Actor
|
||||
};
|
||||
|
||||
itemResult.AddPerson(person);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConfiguredForEnglish(BaseItem item)
|
||||
@@ -459,40 +462,70 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
internal class SeasonRootObject
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string seriesID { get; set; }
|
||||
|
||||
public int Season { get; set; }
|
||||
|
||||
public int? totalSeasons { get; set; }
|
||||
|
||||
public RootObject[] Episodes { get; set; }
|
||||
|
||||
public string Response { get; set; }
|
||||
}
|
||||
|
||||
internal class RootObject
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Year { get; set; }
|
||||
|
||||
public string Rated { get; set; }
|
||||
|
||||
public string Released { get; set; }
|
||||
|
||||
public string Runtime { get; set; }
|
||||
|
||||
public string Genre { get; set; }
|
||||
|
||||
public string Director { get; set; }
|
||||
|
||||
public string Writer { get; set; }
|
||||
|
||||
public string Actors { get; set; }
|
||||
|
||||
public string Plot { get; set; }
|
||||
|
||||
public string Language { get; set; }
|
||||
|
||||
public string Country { get; set; }
|
||||
|
||||
public string Awards { get; set; }
|
||||
|
||||
public string Poster { get; set; }
|
||||
|
||||
public List<OmdbRating> Ratings { get; set; }
|
||||
|
||||
public string Metascore { get; set; }
|
||||
|
||||
public string imdbRating { get; set; }
|
||||
|
||||
public string imdbVotes { get; set; }
|
||||
|
||||
public string imdbID { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
|
||||
public string DVD { get; set; }
|
||||
|
||||
public string BoxOffice { get; set; }
|
||||
|
||||
public string Production { get; set; }
|
||||
|
||||
public string Website { get; set; }
|
||||
|
||||
public string Response { get; set; }
|
||||
|
||||
public int Episode { get; set; }
|
||||
|
||||
public float? GetRottenTomatoScore()
|
||||
@@ -509,12 +542,15 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class OmdbRating
|
||||
{
|
||||
public string Source { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
40
MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
Normal file
40
MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
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.Omdb
|
||||
{
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
{
|
||||
public static Plugin Instance { get; private set; }
|
||||
|
||||
public override Guid Id => new Guid("a628c0da-fac5-4c7e-9d1a-7134223f14c8");
|
||||
|
||||
public override string Name => "OMDb";
|
||||
|
||||
public override string Description => "Get metadata for movies and other video content from OMDb.";
|
||||
|
||||
// TODO remove when plugin removed from server.
|
||||
public override string ConfigurationFileName => "Jellyfin.Plugin.Omdb.xml";
|
||||
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public IEnumerable<PluginPageInfo> GetPages()
|
||||
{
|
||||
yield return new PluginPageInfo
|
||||
{
|
||||
Name = Name,
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
}
|
||||
}
|
||||
29
MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
Normal file
29
MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public class Plugin : BasePlugin<PluginConfiguration>
|
||||
{
|
||||
public static Plugin Instance { get; private set; }
|
||||
|
||||
public override Guid Id => new Guid("a677c0da-fac5-4cde-941a-7134223f14c8");
|
||||
|
||||
public override string Name => "TheTVDB";
|
||||
|
||||
public override string Description => "Get metadata for movies and other video content from TheTVDB.";
|
||||
|
||||
// TODO remove when plugin removed from server.
|
||||
public override string ConfigurationFileName => "Jellyfin.Plugin.TheTvdb.xml";
|
||||
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@@ -16,7 +20,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
private const string DefaultLanguage = "en";
|
||||
|
||||
private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1);
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly TvDbClient _tvDbClient;
|
||||
private DateTime _tokenCreatedAt;
|
||||
@@ -77,32 +80,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Traverse all episode pages and join them together
|
||||
var episodes = new List<EpisodeRecord>();
|
||||
var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
episodes.AddRange(episodePage.Data);
|
||||
if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
|
||||
{
|
||||
return episodes;
|
||||
}
|
||||
|
||||
int next = episodePage.Links.Next.Value;
|
||||
int last = episodePage.Links.Last.Value;
|
||||
|
||||
for (var page = next; page <= last; ++page)
|
||||
{
|
||||
episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
episodes.AddRange(episodePage.Data);
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(
|
||||
string imdbId,
|
||||
string language,
|
||||
@@ -120,6 +97,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
var cacheKey = GenerateKey("series", zap2ItId, language);
|
||||
return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<Actor[]>> GetActorsAsync(
|
||||
int tvdbId,
|
||||
string language,
|
||||
@@ -172,7 +150,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
|
||||
searchInfo.SeriesProviderIds.TryGetValue(
|
||||
nameof(MetadataProvider.Tvdb),
|
||||
out var seriesTvdbId);
|
||||
|
||||
var episodeQuery = new EpisodeQuery();
|
||||
@@ -190,7 +169,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value;
|
||||
break;
|
||||
default:
|
||||
//aired order
|
||||
// aired order
|
||||
episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
|
||||
episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
|
||||
break;
|
||||
@@ -199,10 +178,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
else if (searchInfo.PremiereDate.HasValue)
|
||||
{
|
||||
// tvdb expects yyyy-mm-dd format
|
||||
episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd");
|
||||
episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken);
|
||||
return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), episodeQuery, language, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<string> GetEpisodeTvdbId(
|
||||
@@ -214,7 +193,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
var episodePage =
|
||||
await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return episodePage.Data.FirstOrDefault()?.Id.ToString();
|
||||
return episodePage.Data.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(
|
||||
@@ -226,6 +205,45 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
|
||||
var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
|
||||
|
||||
if (imagesSummary.Data.Fanart > 0)
|
||||
{
|
||||
yield return KeyType.Fanart;
|
||||
}
|
||||
|
||||
if (imagesSummary.Data.Series > 0)
|
||||
{
|
||||
yield return KeyType.Series;
|
||||
}
|
||||
|
||||
if (imagesSummary.Data.Poster > 0)
|
||||
{
|
||||
yield return KeyType.Poster;
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeasonAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
|
||||
var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
|
||||
|
||||
if (imagesSummary.Data.Season > 0)
|
||||
{
|
||||
yield return KeyType.Season;
|
||||
}
|
||||
|
||||
if (imagesSummary.Data.Fanart > 0)
|
||||
{
|
||||
yield return KeyType.Fanart;
|
||||
}
|
||||
|
||||
// TODO seasonwide is not supported in TvDbSharper
|
||||
}
|
||||
|
||||
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out T cachedValue))
|
||||
@@ -233,23 +251,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_cache.TryGetValue(key, out cachedValue))
|
||||
{
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
_tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
|
||||
var result = await resultFactory.Invoke().ConfigureAwait(false);
|
||||
_cache.Set(key, result, TimeSpan.FromHours(1));
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cacheWriteLock.Release();
|
||||
}
|
||||
_tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
|
||||
var result = await resultFactory.Invoke().ConfigureAwait(false);
|
||||
_cache.Set(key, result, TimeSpan.FromHours(1));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GenerateKey(params object[] objects)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -16,13 +20,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public class TvdbEpisodeImageProvider : IRemoteImageProvider
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TvdbEpisodeImageProvider> _logger;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger<TvdbEpisodeImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
public TvdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, ILogger<TvdbEpisodeImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_tvdbClientManager = tvdbClientManager;
|
||||
}
|
||||
@@ -53,28 +57,35 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
// Process images
|
||||
try
|
||||
{
|
||||
var episodeInfo = new EpisodeInfo
|
||||
string episodeTvdbId = null;
|
||||
|
||||
if (episode.IndexNumber.HasValue && episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
IndexNumber = episode.IndexNumber.Value,
|
||||
ParentIndexNumber = episode.ParentIndexNumber.Value,
|
||||
SeriesProviderIds = series.ProviderIds,
|
||||
SeriesDisplayOrder = series.DisplayOrder
|
||||
};
|
||||
string episodeTvdbId = await _tvdbClientManager
|
||||
.GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
|
||||
var episodeInfo = new EpisodeInfo
|
||||
{
|
||||
IndexNumber = episode.IndexNumber.Value,
|
||||
ParentIndexNumber = episode.ParentIndexNumber.Value,
|
||||
SeriesProviderIds = series.ProviderIds,
|
||||
SeriesDisplayOrder = series.DisplayOrder
|
||||
};
|
||||
|
||||
episodeTvdbId = await _tvdbClientManager
|
||||
.GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(episodeTvdbId))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
|
||||
episodeInfo.ParentIndexNumber,
|
||||
episodeInfo.IndexNumber,
|
||||
series.GetProviderId(MetadataProviders.Tvdb));
|
||||
episode.ParentIndexNumber,
|
||||
episode.IndexNumber,
|
||||
series.GetProviderId(MetadataProvider.Tvdb));
|
||||
return imageResult;
|
||||
}
|
||||
|
||||
var episodeResult =
|
||||
await _tvdbClientManager
|
||||
.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId), language, cancellationToken)
|
||||
.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId, CultureInfo.InvariantCulture), language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var image = GetImageInfo(episodeResult.Data);
|
||||
@@ -85,7 +96,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
}
|
||||
catch (TvDbServerException e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProviders.Tvdb));
|
||||
_logger.LogError(e, "Failed to retrieve episode images for series {TvDbId}", series.GetProviderId(MetadataProvider.Tvdb));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +112,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
return new RemoteImageInfo
|
||||
{
|
||||
Width = Convert.ToInt32(episode.ThumbWidth),
|
||||
Height = Convert.ToInt32(episode.ThumbHeight),
|
||||
Width = Convert.ToInt32(episode.ThumbWidth, CultureInfo.InvariantCulture),
|
||||
Height = Convert.ToInt32(episode.ThumbHeight, CultureInfo.InvariantCulture),
|
||||
ProviderName = Name,
|
||||
Url = TvdbUtils.BannerUrl + episode.Filename,
|
||||
Type = ImageType.Primary
|
||||
@@ -111,13 +122,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -14,19 +17,18 @@ using TvDbSharper.Dto;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Class RemoteEpisodeProvider
|
||||
/// Class RemoteEpisodeProvider.
|
||||
/// </summary>
|
||||
public class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TvdbEpisodeProvider> _logger;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public TvdbEpisodeProvider(IHttpClient httpClient, ILogger<TvdbEpisodeProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
public TvdbEpisodeProvider(IHttpClientFactory httpClientFactory, ILogger<TvdbEpisodeProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_tvdbClientManager = tvdbClientManager;
|
||||
}
|
||||
@@ -95,7 +97,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
QueriedById = true
|
||||
};
|
||||
|
||||
string seriesTvdbId = searchInfo.GetProviderId(MetadataProviders.Tvdb);
|
||||
string seriesTvdbId = searchInfo.GetProviderId(MetadataProvider.Tvdb);
|
||||
string episodeTvdbId = null;
|
||||
try
|
||||
{
|
||||
@@ -104,7 +106,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
.ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(episodeTvdbId))
|
||||
{
|
||||
_logger.LogError("Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
|
||||
_logger.LogError(
|
||||
"Episode {SeasonNumber}x{EpisodeNumber} not found for series {SeriesTvdbId}",
|
||||
searchInfo.ParentIndexNumber, searchInfo.IndexNumber, seriesTvdbId);
|
||||
return result;
|
||||
}
|
||||
@@ -139,20 +142,27 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
Name = episode.EpisodeName,
|
||||
Overview = episode.Overview,
|
||||
CommunityRating = (float?)episode.SiteRating,
|
||||
|
||||
OfficialRating = episode.ContentRating,
|
||||
}
|
||||
};
|
||||
result.ResetPeople();
|
||||
|
||||
var item = result.Item;
|
||||
item.SetProviderId(MetadataProviders.Tvdb, episode.Id.ToString());
|
||||
item.SetProviderId(MetadataProviders.Imdb, episode.ImdbId);
|
||||
item.SetProviderId(MetadataProvider.Tvdb, episode.Id.ToString());
|
||||
item.SetProviderId(MetadataProvider.Imdb, episode.ImdbId);
|
||||
|
||||
if (string.Equals(id.SeriesDisplayOrder, "dvd", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
|
||||
item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
|
||||
}
|
||||
else if (string.Equals(id.SeriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (episode.AbsoluteNumber.GetValueOrDefault() != 0)
|
||||
{
|
||||
item.IndexNumber = episode.AbsoluteNumber;
|
||||
}
|
||||
}
|
||||
else if (episode.AiredEpisodeNumber.HasValue)
|
||||
{
|
||||
item.IndexNumber = episode.AiredEpisodeNumber;
|
||||
@@ -188,7 +198,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
for (var i = 0; i < episode.GuestStars.Length; ++i)
|
||||
{
|
||||
var currentActor = episode.GuestStars[i];
|
||||
var roleStartIndex = currentActor.IndexOf('(');
|
||||
var roleStartIndex = currentActor.IndexOf('(', StringComparison.Ordinal);
|
||||
|
||||
if (roleStartIndex == -1)
|
||||
{
|
||||
@@ -207,7 +217,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
for (var j = i + 1; j < episode.GuestStars.Length; ++j)
|
||||
{
|
||||
var currentRole = episode.GuestStars[j];
|
||||
var roleEndIndex = currentRole.IndexOf(')');
|
||||
var roleEndIndex = currentRole.IndexOf(')', StringComparison.Ordinal);
|
||||
|
||||
if (roleEndIndex == -1)
|
||||
{
|
||||
@@ -242,13 +252,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -18,15 +21,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TvdbPersonImageProvider> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger<TvdbPersonImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClientFactory httpClientFactory, ILogger<TvdbPersonImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_tvdbClientManager = tvdbClientManager;
|
||||
}
|
||||
@@ -57,7 +60,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
EnableImages = false
|
||||
}
|
||||
|
||||
}).Cast<Series>()
|
||||
.Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds))
|
||||
.ToList();
|
||||
@@ -73,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
private async Task<RemoteImageInfo> GetImageFromSeriesData(Series series, string personName, CancellationToken cancellationToken)
|
||||
{
|
||||
var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb));
|
||||
var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProvider.Tvdb));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -103,13 +105,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -18,13 +21,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TvdbSeasonImageProvider> _logger;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger<TvdbSeasonImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
public TvdbSeasonImageProvider(IHttpClientFactory httpClientFactory, ILogger<TvdbSeasonImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_tvdbClientManager = tvdbClientManager;
|
||||
}
|
||||
@@ -55,16 +58,16 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
|
||||
{
|
||||
return new RemoteImageInfo[] { };
|
||||
return Array.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb));
|
||||
var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProvider.Tvdb));
|
||||
var seasonNumber = season.IndexNumber.Value;
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
|
||||
var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart };
|
||||
foreach (var keyType in keyTypes)
|
||||
var keyTypes = _tvdbClientManager.GetImageKeyTypesForSeasonAsync(tvdbId, language, cancellationToken).ConfigureAwait(false);
|
||||
await foreach (var keyType in keyTypes)
|
||||
{
|
||||
var imageQuery = new ImagesQuery
|
||||
{
|
||||
@@ -89,7 +92,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
private IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
|
||||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
|
||||
// any languages with null ids are ignored
|
||||
var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data.Where(x => x.Id.HasValue);
|
||||
foreach (Image image in images)
|
||||
{
|
||||
var imageInfo = new RemoteImageInfo
|
||||
@@ -113,8 +117,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType);
|
||||
list.Add(imageInfo);
|
||||
}
|
||||
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
|
||||
return list.OrderByDescending(i =>
|
||||
{
|
||||
if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -143,13 +147,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
@@ -18,13 +21,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TvdbSeriesImageProvider> _logger;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger<TvdbSeriesImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
public TvdbSeriesImageProvider(IHttpClientFactory httpClientFactory, ILogger<TvdbSeriesImageProvider> logger, TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_tvdbClientManager = tvdbClientManager;
|
||||
}
|
||||
@@ -57,9 +60,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart };
|
||||
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProviders.Tvdb));
|
||||
foreach (KeyType keyType in keyTypes)
|
||||
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
|
||||
var allowedKeyTypes = _tvdbClientManager.GetImageKeyTypesForSeriesAsync(tvdbId, language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (KeyType keyType in allowedKeyTypes)
|
||||
{
|
||||
var imageQuery = new ImagesQuery
|
||||
{
|
||||
@@ -79,6 +83,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
tvdbId);
|
||||
}
|
||||
}
|
||||
|
||||
return remoteImages;
|
||||
}
|
||||
|
||||
@@ -110,8 +115,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType);
|
||||
list.Add(imageInfo);
|
||||
}
|
||||
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
|
||||
return list.OrderByDescending(i =>
|
||||
{
|
||||
if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -140,13 +145,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@@ -22,15 +26,16 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
|
||||
{
|
||||
internal static TvdbSeriesProvider Current { get; private set; }
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<TvdbSeriesProvider> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public TvdbSeriesProvider(IHttpClient httpClient, ILogger<TvdbSeriesProvider> logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager)
|
||||
public TvdbSeriesProvider(IHttpClientFactory httpClientFactory, ILogger<TvdbSeriesProvider> logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_localizationManager = localizationManager;
|
||||
@@ -94,22 +99,22 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
var series = result.Item;
|
||||
|
||||
if (seriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId))
|
||||
if (seriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out var tvdbId) && !string.IsNullOrEmpty(tvdbId))
|
||||
{
|
||||
series.SetProviderId(MetadataProviders.Tvdb, tvdbId);
|
||||
series.SetProviderId(MetadataProvider.Tvdb, tvdbId);
|
||||
}
|
||||
|
||||
if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId))
|
||||
if (seriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out var imdbId) && !string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
series.SetProviderId(MetadataProviders.Imdb, imdbId);
|
||||
tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProviders.Imdb.ToString(), metadataLanguage,
|
||||
series.SetProviderId(MetadataProvider.Imdb, imdbId);
|
||||
tvdbId = await GetSeriesByRemoteId(imdbId, MetadataProvider.Imdb.ToString(), metadataLanguage,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (seriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It))
|
||||
if (seriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out var zap2It) && !string.IsNullOrEmpty(zap2It))
|
||||
{
|
||||
series.SetProviderId(MetadataProviders.Zap2It, zap2It);
|
||||
tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProviders.Zap2It.ToString(), metadataLanguage,
|
||||
series.SetProviderId(MetadataProvider.Zap2It, zap2It);
|
||||
tvdbId = await GetSeriesByRemoteId(zap2It, MetadataProvider.Zap2It.ToString(), metadataLanguage,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -119,7 +124,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
await _tvdbClientManager
|
||||
.GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
MapSeriesToResult(result, seriesResult.Data, metadataLanguage);
|
||||
await MapSeriesToResult(result, seriesResult.Data, metadataLanguage).ConfigureAwait(false);
|
||||
}
|
||||
catch (TvDbServerException e)
|
||||
{
|
||||
@@ -145,12 +150,11 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
private async Task<string> GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
TvDbResponse<SeriesSearchResult[]> result = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(idType, MetadataProvider.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = await _tvdbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -176,9 +180,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
/// <returns>True, if the dictionary contains a valid TV provider ID, otherwise false.</returns>
|
||||
internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
|
||||
{
|
||||
return seriesProviderIds.ContainsKey(MetadataProviders.Tvdb.ToString()) ||
|
||||
seriesProviderIds.ContainsKey(MetadataProviders.Imdb.ToString()) ||
|
||||
seriesProviderIds.ContainsKey(MetadataProviders.Zap2It.ToString());
|
||||
return seriesProviderIds.ContainsKey(MetadataProvider.Tvdb.ToString()) ||
|
||||
seriesProviderIds.ContainsKey(MetadataProvider.Imdb.ToString()) ||
|
||||
seriesProviderIds.ContainsKey(MetadataProvider.Zap2It.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -245,24 +249,29 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
Name = tvdbTitles.FirstOrDefault(),
|
||||
ProductionYear = firstAired.Year,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner
|
||||
|
||||
SearchProviderName = Name
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(seriesSearchResult.Banner))
|
||||
{
|
||||
// Results from their Search endpoints already include the /banners/ part in the url, because reasons...
|
||||
remoteSearchResult.ImageUrl = TvdbUtils.TvdbImageBaseUrl + seriesSearchResult.Banner;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var seriesSesult =
|
||||
await _tvdbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSesult.Data.ImdbId);
|
||||
remoteSearchResult.SetProviderId(MetadataProviders.Zap2It, seriesSesult.Data.Zap2itId);
|
||||
remoteSearchResult.SetProviderId(MetadataProvider.Imdb, seriesSesult.Data.ImdbId);
|
||||
remoteSearchResult.SetProviderId(MetadataProvider.Zap2It, seriesSesult.Data.Zap2itId);
|
||||
}
|
||||
catch (TvDbServerException e)
|
||||
{
|
||||
_logger.LogError(e, "Unable to retrieve series with id {TvdbId}", seriesSearchResult.Id);
|
||||
}
|
||||
|
||||
remoteSearchResult.SetProviderId(MetadataProviders.Tvdb, seriesSearchResult.Id.ToString());
|
||||
remoteSearchResult.SetProviderId(MetadataProvider.Tvdb, seriesSearchResult.Id.ToString());
|
||||
list.Add(new Tuple<List<string>, RemoteSearchResult>(tvdbTitles, remoteSearchResult));
|
||||
}
|
||||
|
||||
@@ -273,15 +282,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The remove
|
||||
/// </summary>
|
||||
const string remove = "\"'!`?";
|
||||
/// <summary>
|
||||
/// The spacers
|
||||
/// </summary>
|
||||
const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are two types of dashes, short and long)
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the comparable.
|
||||
/// </summary>
|
||||
@@ -291,47 +291,25 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
name = name.Normalize(NormalizationForm.FormKD);
|
||||
var sb = new StringBuilder();
|
||||
foreach (var c in name)
|
||||
{
|
||||
if (c >= 0x2B0 && c <= 0x0333)
|
||||
{
|
||||
// skip char modifier and diacritics
|
||||
}
|
||||
else if (remove.IndexOf(c) > -1)
|
||||
{
|
||||
// skip chars we are removing
|
||||
}
|
||||
else if (spacers.IndexOf(c) > -1)
|
||||
{
|
||||
sb.Append(" ");
|
||||
}
|
||||
else if (c == '&')
|
||||
{
|
||||
sb.Append(" and ");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " ");
|
||||
|
||||
return Regex.Replace(sb.ToString().Trim(), @"\s+", " ");
|
||||
name = name.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " ");
|
||||
name = name.Replace("&", " and " );
|
||||
name = Regex.Replace(name, @"[\p{Lm}\p{Mn}]", string.Empty); // Remove diacritics, etc
|
||||
name = Regex.Replace(name, @"[\W\p{Pc}]+", " "); // Replace sequences of non-word characters and _ with " "
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
private void MapSeriesToResult(MetadataResult<Series> result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage)
|
||||
private async Task MapSeriesToResult(MetadataResult<Series> result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage)
|
||||
{
|
||||
Series series = result.Item;
|
||||
series.SetProviderId(MetadataProviders.Tvdb, tvdbSeries.Id.ToString());
|
||||
series.SetProviderId(MetadataProvider.Tvdb, tvdbSeries.Id.ToString());
|
||||
series.Name = tvdbSeries.SeriesName;
|
||||
series.Overview = (tvdbSeries.Overview ?? string.Empty).Trim();
|
||||
result.ResultLanguage = metadataLanguage;
|
||||
series.AirDays = TVUtils.GetAirDays(tvdbSeries.AirsDayOfWeek);
|
||||
series.AirTime = tvdbSeries.AirsTime;
|
||||
series.CommunityRating = (float?)tvdbSeries.SiteRating;
|
||||
series.SetProviderId(MetadataProviders.Imdb, tvdbSeries.ImdbId);
|
||||
series.SetProviderId(MetadataProviders.Zap2It, tvdbSeries.Zap2itId);
|
||||
series.SetProviderId(MetadataProvider.Imdb, tvdbSeries.ImdbId);
|
||||
series.SetProviderId(MetadataProvider.Zap2It, tvdbSeries.Zap2itId);
|
||||
if (Enum.TryParse(tvdbSeries.Status, true, out SeriesStatus seriesStatus))
|
||||
{
|
||||
series.Status = seriesStatus;
|
||||
@@ -363,20 +341,21 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
try
|
||||
{
|
||||
var episodeSummary = _tvdbClientManager
|
||||
.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data;
|
||||
var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max();
|
||||
var episodeQuery = new EpisodeQuery
|
||||
var episodeSummary = await _tvdbClientManager.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (episodeSummary.Data.AiredSeasons.Length != 0)
|
||||
{
|
||||
AiredSeason = maxSeasonNumber
|
||||
};
|
||||
var episodesPage =
|
||||
_tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data;
|
||||
result.Item.EndDate = episodesPage.Select(e =>
|
||||
var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Max(s => Convert.ToInt32(s, CultureInfo.InvariantCulture));
|
||||
var episodeQuery = new EpisodeQuery
|
||||
{
|
||||
DateTime.TryParse(e.FirstAired, out var firstAired);
|
||||
return firstAired;
|
||||
}).Max();
|
||||
AiredSeason = maxSeasonNumber
|
||||
};
|
||||
var episodesPage = await _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
result.Item.EndDate = episodesPage.Data
|
||||
.Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null)
|
||||
.Max();
|
||||
}
|
||||
}
|
||||
catch (TvDbServerException e)
|
||||
{
|
||||
@@ -394,10 +373,14 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
Type = PersonType.Actor,
|
||||
Name = (actor.Name ?? string.Empty).Trim(),
|
||||
Role = actor.Role,
|
||||
ImageUrl = TvdbUtils.BannerUrl + actor.Image,
|
||||
SortOrder = actor.SortOrder
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(actor.Image))
|
||||
{
|
||||
personInfo.ImageUrl = TvdbUtils.BannerUrl + actor.Image;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(personInfo.Name))
|
||||
{
|
||||
result.AddPerson(personInfo);
|
||||
@@ -409,7 +392,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
public async Task Identify(SeriesInfo info)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(info.GetProviderId(MetadataProviders.Tvdb)))
|
||||
if (!string.IsNullOrWhiteSpace(info.GetProviderId(MetadataProvider.Tvdb)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -421,21 +404,16 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
var id = entry.GetProviderId(MetadataProviders.Tvdb);
|
||||
info.SetProviderId(MetadataProviders.Tvdb, id);
|
||||
var id = entry.GetProviderId(MetadataProvider.Tvdb);
|
||||
info.SetProviderId(MetadataProvider.Tvdb, id);
|
||||
}
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url,
|
||||
BufferContent = false
|
||||
});
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
@@ -7,7 +9,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||
{
|
||||
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
|
||||
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
|
||||
public const string BannerUrl = TvdbBaseUrl + "banners/";
|
||||
public const string TvdbImageBaseUrl = "https://www.thetvdb.com";
|
||||
public const string BannerUrl = TvdbImageBaseUrl + "/banners/";
|
||||
|
||||
public static ImageType GetImageTypeFromKeyType(string keyType)
|
||||
{
|
||||
|
||||
@@ -2,16 +2,23 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Tmdb.BoxSets
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMDB box set.
|
||||
/// </summary>
|
||||
public class TmdbBoxSetExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.TmdbCollection.ToString();
|
||||
public string Key => MetadataProvider.TmdbCollection.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
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
|
||||
{
|
||||
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is BoxSet;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary,
|
||||
ImageType.Backdrop
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (tmdbId <= 0)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (collection?.Images == null)
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
||||
{
|
||||
public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo>
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
var language = searchInfo.MetadataLanguage;
|
||||
|
||||
if (tmdbId > 0)
|
||||
{
|
||||
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (collection == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
Name = collection.Name,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
|
||||
if (collection.Images != null)
|
||||
{
|
||||
result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath);
|
||||
}
|
||||
|
||||
result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
return new[] { result };
|
||||
}
|
||||
|
||||
var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var collections = new List<RemoteSearchResult>();
|
||||
for (var i = 0; i < collectionSearchResults.Count; i++)
|
||||
{
|
||||
var collection = new RemoteSearchResult
|
||||
{
|
||||
Name = collectionSearchResults[i].Name,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
collections.Add(collection);
|
||||
}
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
var language = id.MetadataLanguage;
|
||||
// We don't already have an Id, need to fetch it
|
||||
if (tmdbId <= 0)
|
||||
{
|
||||
var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (searchResults != null && searchResults.Count > 0)
|
||||
{
|
||||
tmdbId = searchResults[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
var result = new MetadataResult<BoxSet>();
|
||||
|
||||
if (tmdbId > 0)
|
||||
{
|
||||
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (collection != null)
|
||||
{
|
||||
var item = new BoxSet
|
||||
{
|
||||
Name = collection.Name,
|
||||
Overview = collection.Overview
|
||||
};
|
||||
|
||||
item.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,24 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMBD movie.
|
||||
/// </summary>
|
||||
public class TmdbMovieExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.Tmdb.ToString();
|
||||
public string Key => MetadataProvider.Tmdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Movie;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
|
||||
@@ -26,7 +32,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies
|
||||
return true;
|
||||
}
|
||||
|
||||
return item is Movie || item is MusicVideo || item is Trailer;
|
||||
return item is Movie;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
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;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
||||
{
|
||||
public class TmdbMovieImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbMovieImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Movie || item is Trailer;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary,
|
||||
ImageType.Backdrop
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var movieTmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
if (movieTmdbId <= 0)
|
||||
{
|
||||
var movieImdbId = item.GetProviderId(MetadataProvider.Imdb);
|
||||
if (string.IsNullOrEmpty(movieImdbId))
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var movieResult = await _tmdbClientManager.FindByExternalIdAsync(movieImdbId, FindExternalSource.Imdb, language, cancellationToken).ConfigureAwait(false);
|
||||
if (movieResult?.MovieResults != null && movieResult.MovieResults.Count > 0)
|
||||
{
|
||||
movieTmdbId = movieResult.MovieResults[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (movieTmdbId <= 0)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var movie = await _tmdbClientManager
|
||||
.GetMovieAsync(movieTmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (movie?.Images == null)
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
300
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
Normal file
300
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
Normal file
@@ -0,0 +1,300 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// Class MovieDbProvider.
|
||||
/// </summary>
|
||||
public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbMovieProvider(
|
||||
ILibraryManager libraryManager,
|
||||
TmdbClientManager tmdbClientManager,
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order => 1;
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (tmdbId == 0)
|
||||
{
|
||||
var movieResults = await _tmdbClientManager
|
||||
.SearchMovieAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var remoteSearchResults = new List<RemoteSearchResult>();
|
||||
for (var i = 0; i < movieResults.Count; i++)
|
||||
{
|
||||
var movieResult = movieResults[i];
|
||||
var remoteSearchResult = new RemoteSearchResult
|
||||
{
|
||||
Name = movieResult.Title ?? movieResult.OriginalTitle,
|
||||
ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
|
||||
Overview = movieResult.Overview,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
|
||||
var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
|
||||
remoteSearchResult.PremiereDate = releaseDate;
|
||||
remoteSearchResult.ProductionYear = releaseDate?.Year;
|
||||
|
||||
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
|
||||
remoteSearchResults.Add(remoteSearchResult);
|
||||
}
|
||||
|
||||
return remoteSearchResults;
|
||||
}
|
||||
|
||||
var movie = await _tmdbClientManager
|
||||
.GetMovieAsync(tmdbId, searchInfo.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var remoteResult = new RemoteSearchResult
|
||||
{
|
||||
Name = movie.Title ?? movie.OriginalTitle,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
|
||||
Overview = movie.Overview
|
||||
};
|
||||
|
||||
if (movie.ReleaseDate != null)
|
||||
{
|
||||
var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
|
||||
remoteResult.PremiereDate = releaseDate;
|
||||
remoteResult.ProductionYear = releaseDate.Year;
|
||||
}
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(movie.ImdbId))
|
||||
{
|
||||
remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
|
||||
}
|
||||
|
||||
return new[] { remoteResult };
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
|
||||
var imdbId = info.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
// ParseName is required here.
|
||||
// Caller provides the filename with extension stripped and NOT the parsed filename
|
||||
var parsedName = _libraryManager.ParseName(info.Name);
|
||||
var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (searchResults.Count > 0)
|
||||
{
|
||||
tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
return new MetadataResult<Movie>();
|
||||
}
|
||||
|
||||
var movieResult = await _tmdbClientManager
|
||||
.GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var movie = new Movie
|
||||
{
|
||||
Name = movieResult.Title ?? movieResult.OriginalTitle,
|
||||
Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
|
||||
Tagline = movieResult.Tagline,
|
||||
ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
|
||||
};
|
||||
var metadataResult = new MetadataResult<Movie>
|
||||
{
|
||||
HasMetadata = true,
|
||||
ResultLanguage = info.MetadataLanguage,
|
||||
Item = movie
|
||||
};
|
||||
|
||||
movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
|
||||
movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
|
||||
if (movieResult.BelongsToCollection != null)
|
||||
{
|
||||
movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture));
|
||||
movie.CollectionName = movieResult.BelongsToCollection.Name;
|
||||
}
|
||||
|
||||
movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);
|
||||
|
||||
if (movieResult.Releases?.Countries != null)
|
||||
{
|
||||
var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList();
|
||||
|
||||
var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, StringComparison.OrdinalIgnoreCase));
|
||||
var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (ourRelease != null)
|
||||
{
|
||||
var ratingPrefix = string.Equals(info.MetadataCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? string.Empty : info.MetadataCountryCode + "-";
|
||||
var newRating = ratingPrefix + ourRelease.Certification;
|
||||
|
||||
newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
movie.OfficialRating = newRating;
|
||||
}
|
||||
else if (usRelease != null)
|
||||
{
|
||||
movie.OfficialRating = usRelease.Certification;
|
||||
}
|
||||
}
|
||||
|
||||
movie.PremiereDate = movieResult.ReleaseDate;
|
||||
movie.ProductionYear = movieResult.ReleaseDate?.Year;
|
||||
|
||||
if (movieResult.ProductionCompanies != null)
|
||||
{
|
||||
movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name));
|
||||
}
|
||||
|
||||
var genres = movieResult.Genres;
|
||||
|
||||
foreach (var genre in genres.Select(g => g.Name))
|
||||
{
|
||||
movie.AddGenre(genre);
|
||||
}
|
||||
|
||||
if (movieResult.Keywords?.Keywords != null)
|
||||
{
|
||||
for (var i = 0; i < movieResult.Keywords.Keywords.Count; i++)
|
||||
{
|
||||
movie.AddTag(movieResult.Keywords.Keywords[i].Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (movieResult.Credits?.Cast != null)
|
||||
{
|
||||
// TODO configurable
|
||||
foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
|
||||
{
|
||||
var personInfo = new PersonInfo
|
||||
{
|
||||
Name = actor.Name.Trim(),
|
||||
Role = actor.Character,
|
||||
Type = PersonType.Actor,
|
||||
SortOrder = actor.Order
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
|
||||
{
|
||||
personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
|
||||
}
|
||||
|
||||
if (actor.Id > 0)
|
||||
{
|
||||
personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
metadataResult.AddPerson(personInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (movieResult.Credits?.Crew != null)
|
||||
{
|
||||
var keepTypes = new[]
|
||||
{
|
||||
PersonType.Director,
|
||||
PersonType.Writer,
|
||||
PersonType.Producer
|
||||
};
|
||||
|
||||
foreach (var person in movieResult.Credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) &&
|
||||
!keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var personInfo = new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(person.ProfilePath))
|
||||
{
|
||||
personInfo.ImageUrl = _tmdbClientManager.GetPosterUrl(person.ProfilePath);
|
||||
}
|
||||
|
||||
if (person.Id > 0)
|
||||
{
|
||||
personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
metadataResult.AddPerson(personInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (movieResult.Videos?.Results != null)
|
||||
{
|
||||
var trailers = new List<MediaUrl>();
|
||||
for (var i = 0; i < movieResult.Videos.Results.Count; i++)
|
||||
{
|
||||
var video = movieResult.Videos.Results[0];
|
||||
if (!TmdbUtils.IsTrailerType(video))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
trailers.Add(new MediaUrl
|
||||
{
|
||||
Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", video.Key),
|
||||
Name = video.Name
|
||||
});
|
||||
}
|
||||
|
||||
movie.RemoteTrailers = trailers;
|
||||
}
|
||||
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Tmdb.People
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMDB person.
|
||||
/// </summary>
|
||||
public class TmdbPersonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.Tmdb.ToString();
|
||||
public string Key => MetadataProvider.Tmdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
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
|
||||
{
|
||||
public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbPersonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Order => 0;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Person;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var person = (Person)item;
|
||||
var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (personTmdbId > 0)
|
||||
{
|
||||
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
|
||||
if (personResult?.Images?.Profiles == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var remoteImages = new List<RemoteImageInfo>();
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
for (var i = 0; i < personResult.Images.Profiles.Count; i++)
|
||||
{
|
||||
var image = personResult.Images.Profiles[i];
|
||||
remoteImages.Add(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)
|
||||
});
|
||||
}
|
||||
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
Normal file
144
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||
{
|
||||
public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo>
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbPersonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (personTmdbId <= 0)
|
||||
{
|
||||
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (personResult != null)
|
||||
{
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
Name = personResult.Name,
|
||||
SearchProviderName = Name,
|
||||
Overview = personResult.Biography
|
||||
};
|
||||
|
||||
if (personResult.Images?.Profiles != null && personResult.Images.Profiles.Count > 0)
|
||||
{
|
||||
result.ImageUrl = _tmdbClientManager.GetProfileUrl(personResult.Images.Profiles[0].FilePath);
|
||||
}
|
||||
|
||||
result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture));
|
||||
result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
|
||||
|
||||
return new[] { result };
|
||||
}
|
||||
}
|
||||
|
||||
// TODO why? Because of the old rate limit?
|
||||
if (searchInfo.IsAutomated)
|
||||
{
|
||||
// Don't hammer moviedb searching by name
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var remoteSearchResults = new List<RemoteSearchResult>();
|
||||
for (var i = 0; i < personSearchResult.Count; i++)
|
||||
{
|
||||
var person = personSearchResult[i];
|
||||
var remoteSearchResult = new RemoteSearchResult
|
||||
{
|
||||
SearchProviderName = Name,
|
||||
Name = person.Name,
|
||||
ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath)
|
||||
};
|
||||
|
||||
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||
remoteSearchResults.Add(remoteSearchResult);
|
||||
}
|
||||
|
||||
return remoteSearchResults;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo id, CancellationToken cancellationToken)
|
||||
{
|
||||
var personTmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
// We don't already have an Id, need to fetch it
|
||||
if (personTmdbId <= 0)
|
||||
{
|
||||
var personSearchResults = await _tmdbClientManager.SearchPersonAsync(id.Name, cancellationToken).ConfigureAwait(false);
|
||||
if (personSearchResults.Count > 0)
|
||||
{
|
||||
personTmdbId = personSearchResults[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
var result = new MetadataResult<Person>();
|
||||
|
||||
if (personTmdbId > 0)
|
||||
{
|
||||
var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.HasMetadata = true;
|
||||
|
||||
var item = new Person
|
||||
{
|
||||
// Take name from incoming info, don't rename the person
|
||||
// TODO: This should go in PersonMetadataService, not each person provider
|
||||
Name = id.Name,
|
||||
HomePageUrl = person.Homepage,
|
||||
Overview = person.Biography,
|
||||
PremiereDate = person.Birthday?.ToUniversalTime(),
|
||||
EndDate = person.Deathday?.ToUniversalTime()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(person.PlaceOfBirth))
|
||||
{
|
||||
item.ProductionLocations = new[] { person.PlaceOfBirth };
|
||||
}
|
||||
|
||||
item.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (!string.IsNullOrEmpty(person.ImdbId))
|
||||
{
|
||||
item.SetProviderId(MetadataProvider.Imdb, person.ImdbId);
|
||||
}
|
||||
|
||||
result.HasMetadata = true;
|
||||
result.Item = item;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
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
|
||||
{
|
||||
public class TmdbEpisodeImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
// After TheTvDb
|
||||
public int Order => 1;
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var episode = (Controller.Entities.TV.Episode)item;
|
||||
var series = episode.Series;
|
||||
|
||||
var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (seriesTmdbId <= 0)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var seasonNumber = episode.ParentIndexNumber;
|
||||
var episodeNumber = episode.IndexNumber;
|
||||
|
||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var stills = episodeResult?.Images?.Stills;
|
||||
if (stills == null)
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Controller.Entities.TV.Episode;
|
||||
}
|
||||
}
|
||||
}
|
||||
207
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
Normal file
207
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
{
|
||||
public class TmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
// After TheTvDb
|
||||
public int Order => 1;
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// The search query must either provide an episode number or date
|
||||
if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var metadataResult = await GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!metadataResult.HasMetadata)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var item = metadataResult.Item;
|
||||
|
||||
return new[]
|
||||
{
|
||||
new RemoteSearchResult
|
||||
{
|
||||
IndexNumber = item.IndexNumber,
|
||||
Name = item.Name,
|
||||
ParentIndexNumber = item.ParentIndexNumber,
|
||||
PremiereDate = item.PremiereDate,
|
||||
ProductionYear = item.ProductionYear,
|
||||
ProviderIds = item.ProviderIds,
|
||||
SearchProviderName = Name,
|
||||
IndexNumberEnd = item.IndexNumberEnd
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var metadataResult = new MetadataResult<Episode>();
|
||||
|
||||
// Allowing this will dramatically increase scan times
|
||||
if (info.IsMissingEpisode)
|
||||
{
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string tmdbId);
|
||||
|
||||
var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture);
|
||||
if (seriesTmdbId <= 0)
|
||||
{
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
var seasonNumber = info.ParentIndexNumber;
|
||||
var episodeNumber = info.IndexNumber;
|
||||
|
||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
||||
{
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
var episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (episodeResult == null)
|
||||
{
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
metadataResult.HasMetadata = true;
|
||||
metadataResult.QueriedById = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(episodeResult.Overview))
|
||||
{
|
||||
// if overview is non-empty, we can assume that localized data was returned
|
||||
metadataResult.ResultLanguage = info.MetadataLanguage;
|
||||
}
|
||||
|
||||
var item = new Episode
|
||||
{
|
||||
Name = info.Name,
|
||||
IndexNumber = info.IndexNumber,
|
||||
ParentIndexNumber = info.ParentIndexNumber,
|
||||
IndexNumberEnd = info.IndexNumberEnd
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId))
|
||||
{
|
||||
item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId);
|
||||
}
|
||||
|
||||
item.PremiereDate = episodeResult.AirDate;
|
||||
item.ProductionYear = episodeResult.AirDate?.Year;
|
||||
|
||||
item.Name = episodeResult.Name;
|
||||
item.Overview = episodeResult.Overview;
|
||||
|
||||
item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage);
|
||||
|
||||
if (episodeResult.Videos?.Results != null)
|
||||
{
|
||||
foreach (var video in episodeResult.Videos.Results)
|
||||
{
|
||||
if (TmdbUtils.IsTrailerType(video))
|
||||
{
|
||||
item.AddTrailerUrl("https://www.youtube.com/watch?v=" + video.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var credits = episodeResult.Credits;
|
||||
|
||||
if (credits?.Cast != null)
|
||||
{
|
||||
foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
|
||||
{
|
||||
metadataResult.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = actor.Name.Trim(),
|
||||
Role = actor.Character,
|
||||
Type = PersonType.Actor,
|
||||
SortOrder = actor.Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (credits?.GuestStars != null)
|
||||
{
|
||||
foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
|
||||
{
|
||||
metadataResult.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = guest.Name.Trim(),
|
||||
Role = guest.Character,
|
||||
Type = PersonType.GuestStar,
|
||||
SortOrder = guest.Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// and the rest from crew
|
||||
if (credits?.Crew != null)
|
||||
{
|
||||
foreach (var person in credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
|
||||
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
metadataResult.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
metadataResult.Item = item;
|
||||
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
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
|
||||
{
|
||||
public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public int Order => 1;
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var season = (Season)item;
|
||||
var series = season?.Series;
|
||||
|
||||
var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||
|
||||
if (seriesTmdbId <= 0 || season?.IndexNumber == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var seasonResult = await _tmdbClientManager
|
||||
.GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var posters = seasonResult?.Images?.Posters;
|
||||
if (posters == null)
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary
|
||||
};
|
||||
}
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Season;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
Normal file
122
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
{
|
||||
public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Season>();
|
||||
|
||||
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string seriesTmdbId);
|
||||
|
||||
var seasonNumber = info.IndexNumber;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(seriesTmdbId) || !seasonNumber.HasValue)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var seasonResult = await _tmdbClientManager
|
||||
.GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (seasonResult == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.HasMetadata = true;
|
||||
result.Item = new Season
|
||||
{
|
||||
Name = info.Name,
|
||||
IndexNumber = seasonNumber,
|
||||
Overview = seasonResult?.Overview
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(seasonResult.ExternalIds?.TvdbId))
|
||||
{
|
||||
result.Item.SetProviderId(MetadataProvider.Tvdb, seasonResult.ExternalIds.TvdbId);
|
||||
}
|
||||
|
||||
// TODO why was this disabled?
|
||||
var credits = seasonResult.Credits;
|
||||
if (credits?.Cast != null)
|
||||
{
|
||||
var cast = credits.Cast.OrderBy(c => c.Order).Take(TmdbUtils.MaxCastMembers).ToList();
|
||||
for (var i = 0; i < cast.Count; i++)
|
||||
{
|
||||
result.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = cast[i].Name.Trim(),
|
||||
Role = cast[i].Character,
|
||||
Type = PersonType.Actor,
|
||||
SortOrder = cast[i].Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (credits?.Crew != null)
|
||||
{
|
||||
foreach (var person in credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
|
||||
&& !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.AddPerson(new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.Item.PremiereDate = seasonResult.AirDate;
|
||||
result.Item.ProductionYear = seasonResult.AirDate?.Year;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<RemoteSearchResult>());
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Tmdb.TV
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMDB series.
|
||||
/// </summary>
|
||||
public class TmdbSeriesExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProviders.Tmdb.ToString();
|
||||
public string Key => MetadataProvider.Tmdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Series;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
|
||||
@@ -0,0 +1,117 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
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
|
||||
{
|
||||
public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbSeriesImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
// After tvdb and fanart
|
||||
public int Order => 2;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Series;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary,
|
||||
ImageType.Backdrop
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
var series = await _tmdbClientManager
|
||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (series?.Images == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var posters = series.Images.Posters;
|
||||
var backdrops = series.Images.Backdrops;
|
||||
|
||||
var remoteImages = new RemoteImageInfo[posters.Count + backdrops.Count];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
398
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
Normal file
398
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
Normal file
@@ -0,0 +1,398 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using TMDbLib.Objects.Find;
|
||||
using TMDbLib.Objects.Search;
|
||||
using TMDbLib.Objects.TvShows;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
{
|
||||
public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbSeriesProvider(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
Current = this;
|
||||
}
|
||||
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
// After TheTVDB
|
||||
public int Order => 1;
|
||||
|
||||
internal static TmdbSeriesProvider Current { get; private set; }
|
||||
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
var series = await _tmdbClientManager
|
||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (series != null)
|
||||
{
|
||||
var remoteResult = MapTvShowToRemoteSearchResult(series);
|
||||
|
||||
return new[] { remoteResult };
|
||||
}
|
||||
}
|
||||
|
||||
var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
var findResult = await _tmdbClientManager
|
||||
.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var tvResults = findResult?.TvResults;
|
||||
if (tvResults != null)
|
||||
{
|
||||
var imdbIdResults = new RemoteSearchResult[tvResults.Count];
|
||||
for (var i = 0; i < tvResults.Count; i++)
|
||||
{
|
||||
var remoteResult = MapSearchTvToRemoteSearchResult(tvResults[i]);
|
||||
remoteResult.SetProviderId(MetadataProvider.Imdb, imdbId);
|
||||
imdbIdResults[i] = remoteResult;
|
||||
}
|
||||
|
||||
return imdbIdResults;
|
||||
}
|
||||
}
|
||||
|
||||
var tvdbId = searchInfo.GetProviderId(MetadataProvider.Tvdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(tvdbId))
|
||||
{
|
||||
var findResult = await _tmdbClientManager
|
||||
.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var tvResults = findResult?.TvResults;
|
||||
if (tvResults != null)
|
||||
{
|
||||
var tvIdResults = new RemoteSearchResult[tvResults.Count];
|
||||
for (var i = 0; i < tvResults.Count; i++)
|
||||
{
|
||||
var remoteResult = MapSearchTvToRemoteSearchResult(tvResults[i]);
|
||||
remoteResult.SetProviderId(MetadataProvider.Tvdb, tvdbId);
|
||||
tvIdResults[i] = remoteResult;
|
||||
}
|
||||
|
||||
return tvIdResults;
|
||||
}
|
||||
}
|
||||
|
||||
var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var remoteResults = new RemoteSearchResult[tvSearchResults.Count];
|
||||
for (var i = 0; i < tvSearchResults.Count; i++)
|
||||
{
|
||||
remoteResults[i] = MapSearchTvToRemoteSearchResult(tvSearchResults[i]);
|
||||
}
|
||||
|
||||
return remoteResults;
|
||||
}
|
||||
|
||||
private RemoteSearchResult MapTvShowToRemoteSearchResult(TvShow series)
|
||||
{
|
||||
var remoteResult = new RemoteSearchResult
|
||||
{
|
||||
Name = series.Name ?? series.OriginalName,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = _tmdbClientManager.GetPosterUrl(series.PosterPath),
|
||||
Overview = series.Overview
|
||||
};
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(CultureInfo.InvariantCulture));
|
||||
if (series.ExternalIds != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(series.ExternalIds.ImdbId))
|
||||
{
|
||||
remoteResult.SetProviderId(MetadataProvider.Imdb, series.ExternalIds.ImdbId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(series.ExternalIds.TvdbId))
|
||||
{
|
||||
remoteResult.SetProviderId(MetadataProvider.Tvdb, series.ExternalIds.TvdbId);
|
||||
}
|
||||
}
|
||||
|
||||
remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
|
||||
|
||||
return remoteResult;
|
||||
}
|
||||
|
||||
private RemoteSearchResult MapSearchTvToRemoteSearchResult(SearchTv series)
|
||||
{
|
||||
var remoteResult = new RemoteSearchResult
|
||||
{
|
||||
Name = series.Name ?? series.OriginalName,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = _tmdbClientManager.GetPosterUrl(series.PosterPath),
|
||||
Overview = series.Overview
|
||||
};
|
||||
|
||||
remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(CultureInfo.InvariantCulture));
|
||||
remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
|
||||
|
||||
return remoteResult;
|
||||
}
|
||||
|
||||
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new MetadataResult<Series>
|
||||
{
|
||||
QueriedById = true
|
||||
};
|
||||
|
||||
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
var imdbId = info.GetProviderId(MetadataProvider.Imdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (searchResult != null)
|
||||
{
|
||||
tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
var tvdbId = info.GetProviderId(MetadataProvider.Tvdb);
|
||||
|
||||
if (!string.IsNullOrEmpty(tvdbId))
|
||||
{
|
||||
var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (searchResult != null)
|
||||
{
|
||||
tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
result.QueriedById = false;
|
||||
var searchResults = await _tmdbClientManager.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (searchResults.Count > 0)
|
||||
{
|
||||
tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var tvShow = await _tmdbClientManager
|
||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
result = new MetadataResult<Series>
|
||||
{
|
||||
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
|
||||
ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
|
||||
};
|
||||
|
||||
foreach (var person in GetPersons(tvShow))
|
||||
{
|
||||
result.AddPerson(person);
|
||||
}
|
||||
|
||||
result.HasMetadata = result.Item != null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode)
|
||||
{
|
||||
var series = new Series
|
||||
{
|
||||
Name = seriesResult.Name,
|
||||
OriginalTitle = seriesResult.OriginalName
|
||||
};
|
||||
|
||||
series.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
series.CommunityRating = Convert.ToSingle(seriesResult.VoteAverage);
|
||||
|
||||
series.Overview = seriesResult.Overview;
|
||||
|
||||
if (seriesResult.Networks != null)
|
||||
{
|
||||
series.Studios = seriesResult.Networks.Select(i => i.Name).ToArray();
|
||||
}
|
||||
|
||||
if (seriesResult.Genres != null)
|
||||
{
|
||||
series.Genres = seriesResult.Genres.Select(i => i.Name).ToArray();
|
||||
}
|
||||
|
||||
if (seriesResult.Keywords?.Results != null)
|
||||
{
|
||||
for (var i = 0; i < seriesResult.Keywords.Results.Count; i++)
|
||||
{
|
||||
series.AddTag(seriesResult.Keywords.Results[i].Name);
|
||||
}
|
||||
}
|
||||
|
||||
series.HomePageUrl = seriesResult.Homepage;
|
||||
|
||||
series.RunTimeTicks = seriesResult.EpisodeRunTime.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault();
|
||||
|
||||
if (string.Equals(seriesResult.Status, "Ended", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
series.Status = SeriesStatus.Ended;
|
||||
series.EndDate = seriesResult.LastAirDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
series.Status = SeriesStatus.Continuing;
|
||||
}
|
||||
|
||||
series.PremiereDate = seriesResult.FirstAirDate;
|
||||
|
||||
var ids = seriesResult.ExternalIds;
|
||||
if (ids != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ids.ImdbId))
|
||||
{
|
||||
series.SetProviderId(MetadataProvider.Imdb, ids.ImdbId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ids.TvrageId))
|
||||
{
|
||||
series.SetProviderId(MetadataProvider.TvRage, ids.TvrageId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ids.TvdbId))
|
||||
{
|
||||
series.SetProviderId(MetadataProvider.Tvdb, ids.TvdbId);
|
||||
}
|
||||
}
|
||||
|
||||
var contentRatings = seriesResult.ContentRatings.Results ?? new List<ContentRating>();
|
||||
|
||||
var ourRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase));
|
||||
var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
|
||||
var minimumRelease = contentRatings.FirstOrDefault();
|
||||
|
||||
if (ourRelease != null)
|
||||
{
|
||||
series.OfficialRating = ourRelease.Rating;
|
||||
}
|
||||
else if (usRelease != null)
|
||||
{
|
||||
series.OfficialRating = usRelease.Rating;
|
||||
}
|
||||
else if (minimumRelease != null)
|
||||
{
|
||||
series.OfficialRating = minimumRelease.Rating;
|
||||
}
|
||||
|
||||
if (seriesResult.Videos?.Results != null)
|
||||
{
|
||||
foreach (var video in seriesResult.Videos.Results)
|
||||
{
|
||||
if (TmdbUtils.IsTrailerType(video))
|
||||
{
|
||||
series.AddTrailerUrl("https://www.youtube.com/watch?v=" + video.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult)
|
||||
{
|
||||
if (seriesResult.Credits?.Cast != null)
|
||||
{
|
||||
foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(TmdbUtils.MaxCastMembers))
|
||||
{
|
||||
var personInfo = new PersonInfo
|
||||
{
|
||||
Name = actor.Name.Trim(),
|
||||
Role = actor.Character,
|
||||
Type = PersonType.Actor,
|
||||
SortOrder = actor.Order,
|
||||
ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath)
|
||||
};
|
||||
|
||||
if (actor.Id > 0)
|
||||
{
|
||||
personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
yield return personInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (seriesResult.Credits?.Crew != null)
|
||||
{
|
||||
var keepTypes = new[]
|
||||
{
|
||||
PersonType.Director,
|
||||
PersonType.Writer,
|
||||
PersonType.Producer
|
||||
};
|
||||
|
||||
foreach (var person in seriesResult.Credits.Crew)
|
||||
{
|
||||
// Normalize this
|
||||
var type = TmdbUtils.MapCrewToPersonType(person);
|
||||
|
||||
if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
|
||||
&& !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new PersonInfo
|
||||
{
|
||||
Name = person.Name.Trim(),
|
||||
Role = person.Job,
|
||||
Type = type
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
469
MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
Normal file
469
MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
Normal file
@@ -0,0 +1,469 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using TMDbLib.Client;
|
||||
using TMDbLib.Objects.Collections;
|
||||
using TMDbLib.Objects.Find;
|
||||
using TMDbLib.Objects.General;
|
||||
using TMDbLib.Objects.Movies;
|
||||
using TMDbLib.Objects.People;
|
||||
using TMDbLib.Objects.Search;
|
||||
using TMDbLib.Objects.TvShows;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager class for abstracting the TMDb API client library.
|
||||
/// </summary>
|
||||
public class TmdbClientManager
|
||||
{
|
||||
private const int CacheDurationInHours = 1;
|
||||
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly TMDbClient _tmDbClient;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TmdbClientManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memoryCache">An instance of <see cref="IMemoryCache"/>.</param>
|
||||
public TmdbClientManager(IMemoryCache memoryCache)
|
||||
{
|
||||
_memoryCache = memoryCache;
|
||||
_tmDbClient = new TMDbClient(TmdbUtils.ApiKey);
|
||||
// Not really interested in NotFoundException
|
||||
_tmDbClient.ThrowApiExceptions = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a movie from the TMDb API based on its TMDb id.
|
||||
/// </summary>
|
||||
/// <param name="tmdbId">The movie's TMDb id.</param>
|
||||
/// <param name="language">The movie's language.</param>
|
||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb movie or null if not found.</returns>
|
||||
public async Task<Movie> GetMovieAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out Movie movie))
|
||||
{
|
||||
return movie;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
movie = await _tmDbClient.GetMovieAsync(
|
||||
tmdbId,
|
||||
TmdbUtils.NormalizeLanguage(language),
|
||||
imageLanguages,
|
||||
MovieMethods.Credits | MovieMethods.Releases | MovieMethods.Images | MovieMethods.Keywords | MovieMethods.Videos,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (movie != null)
|
||||
{
|
||||
_memoryCache.Set(key, movie, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection from the TMDb API based on its TMDb id.
|
||||
/// </summary>
|
||||
/// <param name="tmdbId">The collection's TMDb id.</param>
|
||||
/// <param name="language">The collection's language.</param>
|
||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb collection or null if not found.</returns>
|
||||
public async Task<Collection> GetCollectionAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out Collection collection))
|
||||
{
|
||||
return collection;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
collection = await _tmDbClient.GetCollectionAsync(
|
||||
tmdbId,
|
||||
TmdbUtils.NormalizeLanguage(language),
|
||||
imageLanguages,
|
||||
CollectionMethods.Images,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (collection != null)
|
||||
{
|
||||
_memoryCache.Set(key, collection, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tv show from the TMDb API based on its TMDb id.
|
||||
/// </summary>
|
||||
/// <param name="tmdbId">The tv show's TMDb id.</param>
|
||||
/// <param name="language">The tv show's language.</param>
|
||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb tv show information or null if not found.</returns>
|
||||
public async Task<TvShow> GetSeriesAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out TvShow series))
|
||||
{
|
||||
return series;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
series = await _tmDbClient.GetTvShowAsync(
|
||||
tmdbId,
|
||||
language: TmdbUtils.NormalizeLanguage(language),
|
||||
includeImageLanguage: imageLanguages,
|
||||
extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (series != null)
|
||||
{
|
||||
_memoryCache.Set(key, series, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a tv season from the TMDb API based on the tv show's TMDb id.
|
||||
/// </summary>
|
||||
/// <param name="tvShowId">The tv season's TMDb id.</param>
|
||||
/// <param name="seasonNumber">The season number.</param>
|
||||
/// <param name="language">The tv season's language.</param>
|
||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb tv season information or null if not found.</returns>
|
||||
public async Task<TvSeason> GetSeasonAsync(int tvShowId, int seasonNumber, string language, string imageLanguages, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out TvSeason season))
|
||||
{
|
||||
return season;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
season = await _tmDbClient.GetTvSeasonAsync(
|
||||
tvShowId,
|
||||
seasonNumber,
|
||||
language: TmdbUtils.NormalizeLanguage(language),
|
||||
includeImageLanguage: imageLanguages,
|
||||
extraMethods: TvSeasonMethods.Credits | TvSeasonMethods.Images | TvSeasonMethods.ExternalIds | TvSeasonMethods.Videos,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
_memoryCache.Set(key, season, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return season;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a movie from the TMDb API based on the tv show's TMDb id.
|
||||
/// </summary>
|
||||
/// <param name="tvShowId">The tv show's TMDb id.</param>
|
||||
/// <param name="seasonNumber">The season number.</param>
|
||||
/// <param name="episodeNumber">The episode number.</param>
|
||||
/// <param name="language">The episode's language.</param>
|
||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb tv episode information or null if not found.</returns>
|
||||
public async Task<TvEpisode> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string language, string imageLanguages, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out TvEpisode episode))
|
||||
{
|
||||
return episode;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
episode = await _tmDbClient.GetTvEpisodeAsync(
|
||||
tvShowId,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
language: TmdbUtils.NormalizeLanguage(language),
|
||||
includeImageLanguage: imageLanguages,
|
||||
extraMethods: TvEpisodeMethods.Credits | TvEpisodeMethods.Images | TvEpisodeMethods.ExternalIds | TvEpisodeMethods.Videos,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
_memoryCache.Set(key, episode, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return episode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id.
|
||||
/// </summary>
|
||||
/// <param name="personTmdbId">The person's TMDb id.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb person information or null if not found.</returns>
|
||||
public async Task<Person> GetPersonAsync(int personTmdbId, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}";
|
||||
if (_memoryCache.TryGetValue(key, out Person person))
|
||||
{
|
||||
return person;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
person = await _tmDbClient.GetPersonAsync(
|
||||
personTmdbId,
|
||||
PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (person != null)
|
||||
{
|
||||
_memoryCache.Set(key, person, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item from the TMDb API based on its id from an external service eg. IMDb id, TvDb id.
|
||||
/// </summary>
|
||||
/// <param name="externalId">The item's external id.</param>
|
||||
/// <param name="source">The source of the id eg. IMDb.</param>
|
||||
/// <param name="language">The item's language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb item or null if not found.</returns>
|
||||
public async Task<FindContainer> FindByExternalIdAsync(
|
||||
string externalId,
|
||||
FindExternalSource source,
|
||||
string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out FindContainer result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
result = await _tmDbClient.FindAsync(
|
||||
source,
|
||||
externalId,
|
||||
TmdbUtils.NormalizeLanguage(language),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
_memoryCache.Set(key, result, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a tv show using the TMDb API based on its name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tv show.</param>
|
||||
/// <param name="language">The tv show's language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb tv show information.</returns>
|
||||
public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"searchseries-{name}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv> series))
|
||||
{
|
||||
return series.Results;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
var searchResults = await _tmDbClient
|
||||
.SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (searchResults.Results.Count > 0)
|
||||
{
|
||||
_memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return searchResults.Results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a person based on their name using the TMDb API.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the person.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb person information.</returns>
|
||||
public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"searchperson-{name}";
|
||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson> person))
|
||||
{
|
||||
return person.Results;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
var searchResults = await _tmDbClient
|
||||
.SearchPersonAsync(name, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (searchResults.Results.Count > 0)
|
||||
{
|
||||
_memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return searchResults.Results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a movie based on its name using the TMDb API.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the movie.</param>
|
||||
/// <param name="language">The movie's language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb movie information.</returns>
|
||||
public Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
return SearchMovieAsync(name, 0, language, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a movie based on its name using the TMDb API.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the movie.</param>
|
||||
/// <param name="year">The release year of the movie.</param>
|
||||
/// <param name="language">The movie's language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb movie information.</returns>
|
||||
public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie> movies))
|
||||
{
|
||||
return movies.Results;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
var searchResults = await _tmDbClient
|
||||
.SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), year: year, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (searchResults.Results.Count > 0)
|
||||
{
|
||||
_memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return searchResults.Results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a collection based on its name using the TMDb API.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the collection.</param>
|
||||
/// <param name="language">The collection's language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The TMDb collection information.</returns>
|
||||
public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"collectionsearch-{name}-{language}";
|
||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection> collections))
|
||||
{
|
||||
return collections.Results;
|
||||
}
|
||||
|
||||
await EnsureClientConfigAsync().ConfigureAwait(false);
|
||||
|
||||
var searchResults = await _tmDbClient
|
||||
.SearchCollectionAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (searchResults.Results.Count > 0)
|
||||
{
|
||||
_memoryCache.Set(key, searchResults, TimeSpan.FromHours(CacheDurationInHours));
|
||||
}
|
||||
|
||||
return searchResults.Results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute URL of the poster.
|
||||
/// </summary>
|
||||
/// <param name="posterPath">The relative URL of the poster.</param>
|
||||
/// <returns>The absolute URL.</returns>
|
||||
public string GetPosterUrl(string posterPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(posterPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.PosterSizes[^1], posterPath).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute URL of the backdrop image.
|
||||
/// </summary>
|
||||
/// <param name="posterPath">The relative URL of the backdrop image.</param>
|
||||
/// <returns>The absolute URL.</returns>
|
||||
public string GetBackdropUrl(string posterPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(posterPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.BackdropSizes[^1], posterPath).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute URL of the profile image.
|
||||
/// </summary>
|
||||
/// <param name="actorProfilePath">The relative URL of the profile image.</param>
|
||||
/// <returns>The absolute URL.</returns>
|
||||
public string GetProfileUrl(string actorProfilePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(actorProfilePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.ProfileSizes[^1], actorProfilePath).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute URL of the still image.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The relative URL of the still image.</param>
|
||||
/// <returns>The absolute URL.</returns>
|
||||
public string GetStillUrl(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _tmDbClient.GetImageUrl(_tmDbClient.Config.Images.StillSizes[^1], filePath).ToString();
|
||||
}
|
||||
|
||||
private Task EnsureClientConfigAsync()
|
||||
{
|
||||
return !_tmDbClient.HasConfig ? _tmDbClient.GetConfigAsync() : Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
163
MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
Normal file
163
MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using TMDbLib.Objects.General;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities for the TMDb provider.
|
||||
/// </summary>
|
||||
public static class TmdbUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// URL of the TMDB instance to use.
|
||||
/// </summary>
|
||||
public const string BaseTmdbUrl = "https://www.themoviedb.org/";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the provider.
|
||||
/// </summary>
|
||||
public const string ProviderName = "TheMovieDb";
|
||||
|
||||
/// <summary>
|
||||
/// API key to use when performing an API call.
|
||||
/// </summary>
|
||||
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 =
|
||||
{
|
||||
PersonType.Director,
|
||||
PersonType.Writer,
|
||||
PersonType.Producer
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps the TMDB provided roles for crew members to Jellyfin roles.
|
||||
/// </summary>
|
||||
/// <param name="crew">Crew member to map against the Jellyfin person types.</param>
|
||||
/// <returns>The Jellyfin person type.</returns>
|
||||
public static string MapCrewToPersonType(Crew crew)
|
||||
{
|
||||
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return PersonType.Director;
|
||||
}
|
||||
|
||||
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return PersonType.Producer;
|
||||
}
|
||||
|
||||
if (crew.Department.Equals("writing", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return PersonType.Writer;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a video is a trailer.
|
||||
/// </summary>
|
||||
/// <param name="video">The TMDb video.</param>
|
||||
/// <returns>A boolean indicating whether the video is a trailer.</returns>
|
||||
public static bool IsTrailerType(Video video)
|
||||
{
|
||||
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
|
||||
&& (!video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|
||||
|| !video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a language string for use with TMDb's include image language parameter.
|
||||
/// </summary>
|
||||
/// <param name="preferredLanguage">The preferred language as either a 2 letter code with or without country code.</param>
|
||||
/// <returns>The comma separated language string.</returns>
|
||||
public static string GetImageLanguagesParam(string preferredLanguage)
|
||||
{
|
||||
var languages = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(preferredLanguage))
|
||||
{
|
||||
preferredLanguage = NormalizeLanguage(preferredLanguage);
|
||||
|
||||
languages.Add(preferredLanguage);
|
||||
|
||||
if (preferredLanguage.Length == 5) // like en-US
|
||||
{
|
||||
// Currenty, TMDB supports 2-letter language codes only
|
||||
// They are planning to change this in the future, thus we're
|
||||
// supplying both codes if we're having a 5-letter code.
|
||||
languages.Add(preferredLanguage.Substring(0, 2));
|
||||
}
|
||||
}
|
||||
|
||||
languages.Add("null");
|
||||
|
||||
if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
languages.Add("en");
|
||||
}
|
||||
|
||||
return string.Join(',', languages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a language string for use with TMDb's language parameter.
|
||||
/// </summary>
|
||||
/// <param name="language">The language code.</param>
|
||||
/// <returns>The normalized language code.</returns>
|
||||
public static string NormalizeLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
return language;
|
||||
}
|
||||
|
||||
// They require this to be uppercase
|
||||
// Everything after the hyphen must be written in uppercase due to a way TMDB wrote their api.
|
||||
// See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab
|
||||
var parts = language.Split('-');
|
||||
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
language = parts[0] + "-" + parts[1].ToUpperInvariant();
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the image's language code preferring the 5 letter language code eg. en-US.
|
||||
/// </summary>
|
||||
/// <param name="imageLanguage">The image's actual language code.</param>
|
||||
/// <param name="requestLanguage">The requested language code.</param>
|
||||
/// <returns>The language code.</returns>
|
||||
public static string AdjustImageLanguage(string imageLanguage, string requestLanguage)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(imageLanguage)
|
||||
&& !string.IsNullOrEmpty(requestLanguage)
|
||||
&& requestLanguage.Length > 2
|
||||
&& imageLanguage.Length == 2
|
||||
&& requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return requestLanguage;
|
||||
}
|
||||
|
||||
return imageLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -21,7 +23,7 @@ namespace MediaBrowser.Providers.Studios
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Studio> source, MetadataResult<Studio> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Studio> source, MetadataResult<Studio> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -18,18 +21,20 @@ namespace MediaBrowser.Providers.Studios
|
||||
public class StudiosImageProvider : IRemoteImageProvider
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public StudiosImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
|
||||
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
|
||||
{
|
||||
_config = config;
|
||||
_httpClient = httpClient;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public string Name => "Emby Designs";
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Studio;
|
||||
@@ -99,33 +104,27 @@ namespace MediaBrowser.Providers.Studios
|
||||
|
||||
private string GetUrl(string image, string filename)
|
||||
{
|
||||
return string.Format("https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studios/{0}/{1}.jpg", image, filename);
|
||||
return string.Format(CultureInfo.InvariantCulture, "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studios/{0}/{1}.jpg", image, filename);
|
||||
}
|
||||
|
||||
private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
|
||||
{
|
||||
const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studiothumbs.txt";
|
||||
|
||||
return EnsureList(url, file, _httpClient, _fileSystem, cancellationToken);
|
||||
return EnsureList(url, file, _fileSystem, cancellationToken);
|
||||
}
|
||||
|
||||
private Task<string> EnsurePosterList(string file, CancellationToken cancellationToken)
|
||||
{
|
||||
const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studioposters.txt";
|
||||
|
||||
return EnsureList(url, file, _httpClient, _fileSystem, cancellationToken);
|
||||
return EnsureList(url, file, _fileSystem, cancellationToken);
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url,
|
||||
BufferContent = false
|
||||
});
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
return httpClient.GetAsync(url, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,30 +132,21 @@ namespace MediaBrowser.Providers.Studios
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="file">The file.</param>
|
||||
/// <param name="httpClient">The HTTP client.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task<string> EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, CancellationToken cancellationToken)
|
||||
public async Task<string> EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
|
||||
{
|
||||
var fileInfo = fileSystem.GetFileInfo(file);
|
||||
|
||||
if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
|
||||
using (var res = await httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (var content = res.Content)
|
||||
using (var fileStream = new FileStream(file, FileMode.Create))
|
||||
{
|
||||
await content.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||||
await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false);
|
||||
await using var fileStream = new FileStream(file, FileMode.Create);
|
||||
await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return file;
|
||||
@@ -171,12 +161,12 @@ namespace MediaBrowser.Providers.Studios
|
||||
|
||||
private string GetComparableName(string name)
|
||||
{
|
||||
return name.Replace(" ", string.Empty)
|
||||
.Replace(".", string.Empty)
|
||||
.Replace("&", string.Empty)
|
||||
.Replace("!", string.Empty)
|
||||
.Replace(",", string.Empty)
|
||||
.Replace("/", string.Empty);
|
||||
return name.Replace(" ", string.Empty, StringComparison.Ordinal)
|
||||
.Replace(".", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("&", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("!", string.Empty, StringComparison.Ordinal)
|
||||
.Replace(",", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("/", string.Empty, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAvailableImages(string file)
|
||||
@@ -201,6 +191,5 @@ namespace MediaBrowser.Providers.Studios
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -25,7 +27,7 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
{
|
||||
public class SubtitleManager : ISubtitleManager
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger<SubtitleManager> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryMonitor _monitor;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
@@ -104,6 +106,7 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
_logger.LogError(ex, "Error downloading subtitles from {Provider}", provider.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.Empty<RemoteSubtitleInfo>();
|
||||
}
|
||||
|
||||
@@ -145,7 +148,7 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var parts = subtitleId.Split(new[] { '_' }, 2);
|
||||
var provider = GetProvider(parts.First());
|
||||
var provider = GetProvider(parts[0]);
|
||||
|
||||
var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia;
|
||||
|
||||
@@ -300,7 +303,7 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
|
||||
private ISubtitleProvider GetProvider(string id)
|
||||
{
|
||||
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name)));
|
||||
return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -311,7 +314,6 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
Index = index,
|
||||
ItemId = item.Id,
|
||||
Type = MediaStreamType.Subtitle
|
||||
|
||||
}).First();
|
||||
|
||||
var path = stream.Path;
|
||||
@@ -365,9 +367,7 @@ namespace MediaBrowser.Providers.Subtitles
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = GetProviderId(i.Name)
|
||||
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
public class DummySeasonProvider
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public DummySeasonProvider(
|
||||
ILogger logger,
|
||||
ILocalizationManager localization,
|
||||
ILibraryManager libraryManager,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
_logger = logger;
|
||||
_localization = localization;
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public async Task<bool> Run(Series series, CancellationToken cancellationToken)
|
||||
{
|
||||
var seasonsRemoved = RemoveObsoleteSeasons(series);
|
||||
|
||||
var hasNewSeasons = await AddDummySeasonFolders(series, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (hasNewSeasons)
|
||||
{
|
||||
//var directoryService = new DirectoryService(_fileSystem);
|
||||
|
||||
//await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//await series.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(directoryService))
|
||||
// .ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return seasonsRemoved || hasNewSeasons;
|
||||
}
|
||||
|
||||
private async Task<bool> AddDummySeasonFolders(Series series, CancellationToken cancellationToken)
|
||||
{
|
||||
var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode)
|
||||
.Cast<Episode>()
|
||||
.Where(i => !i.IsInSeasonFolder)
|
||||
.ToList();
|
||||
|
||||
var hasChanges = false;
|
||||
|
||||
List<Season> seasons = null;
|
||||
|
||||
// Loop through the unique season numbers
|
||||
foreach (var seasonNumber in episodesInSeriesFolder.Select(i => i.ParentIndexNumber ?? -1)
|
||||
.Where(i => i >= 0)
|
||||
.Distinct()
|
||||
.ToList())
|
||||
{
|
||||
if (seasons == null)
|
||||
{
|
||||
seasons = series.Children.OfType<Season>().ToList();
|
||||
}
|
||||
var existingSeason = seasons
|
||||
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber);
|
||||
|
||||
if (existingSeason == null)
|
||||
{
|
||||
await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false);
|
||||
hasChanges = true;
|
||||
seasons = null;
|
||||
}
|
||||
else if (existingSeason.IsVirtualItem)
|
||||
{
|
||||
existingSeason.IsVirtualItem = false;
|
||||
existingSeason.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken);
|
||||
seasons = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown season - create a dummy season to put these under
|
||||
if (episodesInSeriesFolder.Any(i => !i.ParentIndexNumber.HasValue))
|
||||
{
|
||||
if (seasons == null)
|
||||
{
|
||||
seasons = series.Children.OfType<Season>().ToList();
|
||||
}
|
||||
|
||||
var existingSeason = seasons
|
||||
.FirstOrDefault(i => !i.IndexNumber.HasValue);
|
||||
|
||||
if (existingSeason == null)
|
||||
{
|
||||
await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
hasChanges = true;
|
||||
seasons = null;
|
||||
}
|
||||
else if (existingSeason.IsVirtualItem)
|
||||
{
|
||||
existingSeason.IsVirtualItem = false;
|
||||
existingSeason.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken);
|
||||
seasons = null;
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the season.
|
||||
/// </summary>
|
||||
public async Task<Season> AddSeason(Series series,
|
||||
int? seasonNumber,
|
||||
bool isVirtualItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
string seasonName;
|
||||
if (seasonNumber == null)
|
||||
{
|
||||
seasonName = _localization.GetLocalizedString("NameSeasonUnknown");
|
||||
}
|
||||
else if (seasonNumber == 0)
|
||||
{
|
||||
seasonName = _libraryManager.GetLibraryOptions(series).SeasonZeroDisplayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
seasonName = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("NameSeasonNumber"),
|
||||
seasonNumber.Value);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Creating Season {0} entry for {1}", seasonName, series.Name);
|
||||
|
||||
var season = new Season
|
||||
{
|
||||
Name = seasonName,
|
||||
IndexNumber = seasonNumber,
|
||||
Id = _libraryManager.GetNewItemId(
|
||||
series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName,
|
||||
typeof(Season)),
|
||||
IsVirtualItem = isVirtualItem,
|
||||
SeriesId = series.Id,
|
||||
SeriesName = series.Name
|
||||
};
|
||||
|
||||
season.SetParent(series);
|
||||
|
||||
series.AddChild(season, cancellationToken);
|
||||
|
||||
await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return season;
|
||||
}
|
||||
|
||||
private bool RemoveObsoleteSeasons(Series series)
|
||||
{
|
||||
var existingSeasons = series.Children.OfType<Season>().ToList();
|
||||
|
||||
var physicalSeasons = existingSeasons
|
||||
.Where(i => i.LocationType != LocationType.Virtual)
|
||||
.ToList();
|
||||
|
||||
var virtualSeasons = existingSeasons
|
||||
.Where(i => i.LocationType == LocationType.Virtual)
|
||||
.ToList();
|
||||
|
||||
var seasonsToRemove = virtualSeasons
|
||||
.Where(i =>
|
||||
{
|
||||
if (i.IndexNumber.HasValue)
|
||||
{
|
||||
var seasonNumber = i.IndexNumber.Value;
|
||||
|
||||
// If there's a physical season with the same number, delete it
|
||||
if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value == seasonNumber)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no episodes with this season number, delete it
|
||||
if (!i.GetEpisodes().Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var hasChanges = false;
|
||||
|
||||
foreach (var seasonToRemove in seasonsToRemove)
|
||||
{
|
||||
_logger.LogInformation("Removing virtual season {0} {1}", series.Name, seasonToRemove.IndexNumber);
|
||||
|
||||
_libraryManager.DeleteItem(seasonToRemove, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = true
|
||||
|
||||
}, false);
|
||||
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
@@ -66,7 +68,7 @@ namespace MediaBrowser.Providers.TV
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Providers.Plugins.TheTvdb;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
public class MissingEpisodeProvider
|
||||
{
|
||||
private const double UnairedEpisodeThresholdDays = 2;
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly TvdbClientManager _tvdbClientManager;
|
||||
|
||||
public MissingEpisodeProvider(
|
||||
ILogger logger,
|
||||
IServerConfigurationManager config,
|
||||
ILibraryManager libraryManager,
|
||||
ILocalizationManager localization,
|
||||
IFileSystem fileSystem,
|
||||
TvdbClientManager tvdbClientManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_libraryManager = libraryManager;
|
||||
_localization = localization;
|
||||
_fileSystem = fileSystem;
|
||||
_tvdbClientManager = tvdbClientManager;
|
||||
}
|
||||
|
||||
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
|
||||
{
|
||||
var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);
|
||||
if (string.IsNullOrEmpty(tvdbId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var episodes = await _tvdbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken);
|
||||
|
||||
var episodeLookup = episodes
|
||||
.Select(i =>
|
||||
{
|
||||
DateTime.TryParse(i.FirstAired, out var firstAired);
|
||||
var seasonNumber = i.AiredSeason.GetValueOrDefault(-1);
|
||||
var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1);
|
||||
return (seasonNumber, episodeNumber, firstAired);
|
||||
})
|
||||
.Where(i => i.seasonNumber != -1 && i.episodeNumber != -1)
|
||||
.OrderBy(i => i.seasonNumber)
|
||||
.ThenBy(i => i.episodeNumber)
|
||||
.ToList();
|
||||
|
||||
var allRecursiveChildren = series.GetRecursiveChildren();
|
||||
|
||||
var hasBadData = HasInvalidContent(allRecursiveChildren);
|
||||
|
||||
// Be conservative here to avoid creating missing episodes for ones they already have
|
||||
var addMissingEpisodes = !hasBadData && _libraryManager.GetLibraryOptions(series).ImportMissingEpisodes;
|
||||
|
||||
var anySeasonsRemoved = RemoveObsoleteOrMissingSeasons(allRecursiveChildren, episodeLookup);
|
||||
|
||||
if (anySeasonsRemoved)
|
||||
{
|
||||
// refresh this
|
||||
allRecursiveChildren = series.GetRecursiveChildren();
|
||||
}
|
||||
|
||||
var anyEpisodesRemoved = RemoveObsoleteOrMissingEpisodes(allRecursiveChildren, episodeLookup, addMissingEpisodes);
|
||||
|
||||
if (anyEpisodesRemoved)
|
||||
{
|
||||
// refresh this
|
||||
allRecursiveChildren = series.GetRecursiveChildren();
|
||||
}
|
||||
|
||||
var hasNewEpisodes = false;
|
||||
|
||||
if (addNewItems && series.IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(series), TvdbSeriesProvider.Current.Name))
|
||||
{
|
||||
hasNewEpisodes = await AddMissingEpisodes(series, allRecursiveChildren, addMissingEpisodes, episodeLookup, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a series has any seasons or episodes without season or episode numbers
|
||||
/// If this data is missing no virtual items will be added in order to prevent possible duplicates
|
||||
/// </summary>
|
||||
private bool HasInvalidContent(IList<BaseItem> allItems)
|
||||
{
|
||||
return allItems.OfType<Season>().Any(i => !i.IndexNumber.HasValue) ||
|
||||
allItems.OfType<Episode>().Any(i =>
|
||||
{
|
||||
if (!i.ParentIndexNumber.HasValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// You could have episodes under season 0 with no number
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> AddMissingEpisodes(
|
||||
Series series,
|
||||
IEnumerable<BaseItem> allItems,
|
||||
bool addMissingEpisodes,
|
||||
IReadOnlyCollection<(int seasonNumber, int episodenumber, DateTime firstAired)> episodeLookup,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var existingEpisodes = allItems.OfType<Episode>().ToList();
|
||||
|
||||
var seasonCounts = episodeLookup.GroupBy(e => e.seasonNumber).ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
var hasChanges = false;
|
||||
|
||||
foreach (var tuple in episodeLookup)
|
||||
{
|
||||
if (tuple.seasonNumber <= 0 || tuple.episodenumber <= 0)
|
||||
{
|
||||
// Ignore episode/season zeros
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingEpisode = GetExistingEpisode(existingEpisodes, seasonCounts, tuple);
|
||||
|
||||
if (existingEpisode != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var airDate = tuple.firstAired;
|
||||
|
||||
var now = DateTime.UtcNow.AddDays(-UnairedEpisodeThresholdDays);
|
||||
|
||||
if (airDate < now && addMissingEpisodes || airDate > now)
|
||||
{
|
||||
// tvdb has a lot of nearly blank episodes
|
||||
_logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.seasonNumber, tuple.episodenumber);
|
||||
await AddEpisode(series, tuple.seasonNumber, tuple.episodenumber, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the virtual entry after a corresponding physical version has been added
|
||||
/// </summary>
|
||||
private bool RemoveObsoleteOrMissingEpisodes(
|
||||
IEnumerable<BaseItem> allRecursiveChildren,
|
||||
IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup,
|
||||
bool allowMissingEpisodes)
|
||||
{
|
||||
var existingEpisodes = allRecursiveChildren.OfType<Episode>();
|
||||
|
||||
var physicalEpisodes = new List<Episode>();
|
||||
var virtualEpisodes = new List<Episode>();
|
||||
foreach (var episode in existingEpisodes)
|
||||
{
|
||||
if (episode.LocationType == LocationType.Virtual)
|
||||
{
|
||||
virtualEpisodes.Add(episode);
|
||||
}
|
||||
else
|
||||
{
|
||||
physicalEpisodes.Add(episode);
|
||||
}
|
||||
}
|
||||
|
||||
var episodesToRemove = virtualEpisodes
|
||||
.Where(i =>
|
||||
{
|
||||
if (!i.IndexNumber.HasValue || !i.ParentIndexNumber.HasValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var seasonNumber = i.ParentIndexNumber.Value;
|
||||
var episodeNumber = i.IndexNumber.Value;
|
||||
|
||||
// If there's a physical episode with the same season and episode number, delete it
|
||||
if (physicalEpisodes.Any(p =>
|
||||
p.ParentIndexNumber.HasValue && p.ParentIndexNumber.Value == seasonNumber &&
|
||||
p.ContainsEpisodeNumber(episodeNumber)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the episode no longer exists in the remote lookup, delete it
|
||||
if (!episodeLookup.Any(e => e.seasonNumber == seasonNumber && e.episodeNumber == episodeNumber))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's missing, but not unaired, remove it
|
||||
return !allowMissingEpisodes && i.IsMissingEpisode &&
|
||||
(!i.PremiereDate.HasValue ||
|
||||
i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) <
|
||||
DateTime.Now.Date);
|
||||
});
|
||||
|
||||
var hasChanges = false;
|
||||
|
||||
foreach (var episodeToRemove in episodesToRemove)
|
||||
{
|
||||
_libraryManager.DeleteItem(episodeToRemove, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = true
|
||||
}, false);
|
||||
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the obsolete or missing seasons.
|
||||
/// </summary>
|
||||
/// <param name="allRecursiveChildren"></param>
|
||||
/// <param name="episodeLookup">The episode lookup.</param>
|
||||
/// <returns><see cref="bool" />.</returns>
|
||||
private bool RemoveObsoleteOrMissingSeasons(
|
||||
IList<BaseItem> allRecursiveChildren,
|
||||
IEnumerable<(int seasonNumber, int episodeNumber, DateTime firstAired)> episodeLookup)
|
||||
{
|
||||
var existingSeasons = allRecursiveChildren.OfType<Season>().ToList();
|
||||
|
||||
var physicalSeasons = new List<Season>();
|
||||
var virtualSeasons = new List<Season>();
|
||||
foreach (var season in existingSeasons)
|
||||
{
|
||||
if (season.LocationType == LocationType.Virtual)
|
||||
{
|
||||
virtualSeasons.Add(season);
|
||||
}
|
||||
else
|
||||
{
|
||||
physicalSeasons.Add(season);
|
||||
}
|
||||
}
|
||||
|
||||
var allEpisodes = allRecursiveChildren.OfType<Episode>().ToList();
|
||||
|
||||
var seasonsToRemove = virtualSeasons
|
||||
.Where(i =>
|
||||
{
|
||||
if (i.IndexNumber.HasValue)
|
||||
{
|
||||
var seasonNumber = i.IndexNumber.Value;
|
||||
|
||||
// If there's a physical season with the same number, delete it
|
||||
if (physicalSeasons.Any(p => p.IndexNumber.HasValue && p.IndexNumber.Value == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the season no longer exists in the remote lookup, delete it, but only if an existing episode doesn't require it
|
||||
return episodeLookup.All(e => e.seasonNumber != seasonNumber) && allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder);
|
||||
}
|
||||
|
||||
// Season does not have a number
|
||||
// Remove if there are no episodes directly in series without a season number
|
||||
return allEpisodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
|
||||
});
|
||||
|
||||
var hasChanges = false;
|
||||
|
||||
foreach (var seasonToRemove in seasonsToRemove)
|
||||
{
|
||||
_libraryManager.DeleteItem(seasonToRemove, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = true
|
||||
}, false);
|
||||
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the episode.
|
||||
/// </summary>
|
||||
/// <param name="series">The series.</param>
|
||||
/// <param name="seasonNumber">The season number.</param>
|
||||
/// <param name="episodeNumber">The episode number.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task AddEpisode(Series series, int seasonNumber, int episodeNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
var season = series.Children.OfType<Season>()
|
||||
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber);
|
||||
|
||||
if (season == null)
|
||||
{
|
||||
var provider = new DummySeasonProvider(_logger, _localization, _libraryManager, _fileSystem);
|
||||
season = await provider.AddSeason(series, seasonNumber, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var name = "Episode " + episodeNumber.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var episode = new Episode
|
||||
{
|
||||
Name = name,
|
||||
IndexNumber = episodeNumber,
|
||||
ParentIndexNumber = seasonNumber,
|
||||
Id = _libraryManager.GetNewItemId(
|
||||
series.Id + seasonNumber.ToString(CultureInfo.InvariantCulture) + name,
|
||||
typeof(Episode)),
|
||||
IsVirtualItem = true,
|
||||
SeasonId = season?.Id ?? Guid.Empty,
|
||||
SeriesId = series.Id
|
||||
};
|
||||
|
||||
season.AddChild(episode, cancellationToken);
|
||||
|
||||
await episode.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the existing episode.
|
||||
/// </summary>
|
||||
/// <param name="existingEpisodes">The existing episodes.</param>
|
||||
/// <param name="seasonCounts"></param>
|
||||
/// <param name="episodeTuple"></param>
|
||||
/// <returns>Episode.</returns>
|
||||
private Episode GetExistingEpisode(IEnumerable<Episode> existingEpisodes, IReadOnlyDictionary<int, int> seasonCounts, (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple)
|
||||
{
|
||||
var seasonNumber = episodeTuple.seasonNumber;
|
||||
var episodeNumber = episodeTuple.episodeNumber;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var episode = GetExistingEpisode(existingEpisodes, seasonNumber, episodeNumber);
|
||||
if (episode != null)
|
||||
{
|
||||
return episode;
|
||||
}
|
||||
|
||||
seasonNumber--;
|
||||
|
||||
if (seasonCounts.ContainsKey(seasonNumber))
|
||||
{
|
||||
episodeNumber += seasonCounts[seasonNumber];
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Episode GetExistingEpisode(IEnumerable<Episode> existingEpisodes, int season, int episode)
|
||||
=> existingEpisodes.FirstOrDefault(i => i.ParentIndexNumber == season && i.ContainsEpisodeNumber(episode));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -25,6 +27,9 @@ namespace MediaBrowser.Providers.TV
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool EnableUpdatingPremiereDateFromChildren => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType)
|
||||
{
|
||||
@@ -65,9 +70,6 @@ namespace MediaBrowser.Providers.TV
|
||||
return updateType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool EnableUpdatingPremiereDateFromChildren => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList<BaseItem> GetChildrenForMetadataUpdates(Season item)
|
||||
=> item.GetEpisodes();
|
||||
@@ -86,7 +88,7 @@ namespace MediaBrowser.Providers.TV
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void MergeData(MetadataResult<Season> source, MetadataResult<Season> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
protected override void MergeData(MetadataResult<Season> source, MetadataResult<Season> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||
{
|
||||
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user