diff options
Diffstat (limited to 'MediaBrowser.Providers/Plugins/StudioImages')
4 files changed, 323 insertions, 0 deletions
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs new file mode 100644 index 000000000..fad989ab4 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs @@ -0,0 +1,24 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.StudioImages +{ + public class PluginConfiguration : BasePluginConfiguration + { + private string _repository = Plugin.DefaultServer; + + public string RepositoryUrl + { + get + { + return _repository; + } + + set + { + _repository = value.TrimEnd('/'); + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html new file mode 100644 index 000000000..5ede1a2bb --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> +<head> + <title>AudioDB</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"> + <div class="inputContainer"> + <input is="emby-input" type="text" id="repository" required label="Repository" /> + <div class="fieldDescription">This can be any Jellyfin-compatible artwork repository.</div> + </div> + <br /> + <div> + <button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button> + </div> + </form> + </div> + </div> + <script type="text/javascript"> + var PluginConfig = { + pluginId: "872a7849-1171-458d-a6fb-3de3d442ad30" + }; + + document.querySelector('.configPage') + .addEventListener('pageshow', function () { + Dashboard.showLoadingMsg(); + ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { + var repository = document.querySelector('#repository'); + repository.value = config.RepositoryUrl; + repository.dispatchEvent(new Event('change', { + bubbles: true, + cancelable: false + })); + + Dashboard.hideLoadingMsg(); + }); + }); + + document.querySelector('.configForm') + .addEventListener('submit', function (e) { + Dashboard.showLoadingMsg(); + + ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { + config.RepositoryUrl = document.querySelector('#server').value; + + ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); + }); + + e.preventDefault(); + return false; + }); + </script> + </div> +</body> +</html> diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs new file mode 100644 index 000000000..69a0569e5 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -0,0 +1,43 @@ +#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.StudioImages +{ + 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("872a7849-1171-458d-a6fb-3de3d442ad30"); + + public override string Name => "Studio Images"; + + public override string Description => "Get artwork for studios from any Jellyfin-compatible repository."; + + // TODO change this for a Jellyfin-hosted repository. + public const string DefaultServer = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname"; + + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml"; + + public IEnumerable<PluginPageInfo> GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html" + }; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs new file mode 100644 index 000000000..1be3fbd04 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs @@ -0,0 +1,198 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.StudioImages; + +namespace MediaBrowser.Providers.Studios +{ + public class StudiosImageProvider : IRemoteImageProvider + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IFileSystem _fileSystem; + private readonly String repositoryUrl; + + public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem) + { + _config = config; + _httpClientFactory = httpClientFactory; + _fileSystem = fileSystem; + repositoryUrl = Plugin.Instance.Configuration.RepositoryUrl; + } + + public string Name => "Artwork Repository"; + + public int Order => 0; + + public bool Supports(BaseItem item) + { + return item is Studio; + } + + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) + { + return new List<ImageType> + { + ImageType.Primary, + ImageType.Thumb + }; + } + + public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) + { + return GetImages(item, true, true, cancellationToken); + } + + private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, bool posters, bool thumbs, CancellationToken cancellationToken) + { + var list = new List<RemoteImageInfo>(); + + if (posters) + { + var posterPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudioposters.txt"); + + posterPath = await EnsurePosterList(posterPath, cancellationToken).ConfigureAwait(false); + + list.Add(GetImage(item, posterPath, ImageType.Primary, "folder")); + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (thumbs) + { + var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt"); + + thumbsPath = await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false); + + list.Add(GetImage(item, thumbsPath, ImageType.Thumb, "thumb")); + } + + return list.Where(i => i != null); + } + + private RemoteImageInfo GetImage(BaseItem item, string filename, ImageType type, string remoteFilename) + { + var list = GetAvailableImages(filename); + + var match = FindMatch(item, list); + + if (!string.IsNullOrEmpty(match)) + { + var url = GetUrl(match, remoteFilename); + + return new RemoteImageInfo + { + ProviderName = Name, + Type = type, + Url = url + }; + } + + return null; + } + + private string GetUrl(string image, string filename) + { + return string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}.jpg", repositoryUrl, image, filename); + } + + private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken) + { + string url = string.Format(CultureInfo.InvariantCulture, "{0}/studiothumbs.txt", repositoryUrl); + + return EnsureList(url, file, _fileSystem, cancellationToken); + } + + private Task<string> EnsurePosterList(string file, CancellationToken cancellationToken) + { + string url = string.Format(CultureInfo.InvariantCulture, "{0}/studioposters.txt", repositoryUrl); + + return EnsureList(url, file, _fileSystem, cancellationToken); + } + + public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) + { + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + return httpClient.GetAsync(url, cancellationToken); + } + + /// <summary> + /// Ensures the list. + /// </summary> + /// <param name="url">The URL.</param> + /// <param name="file">The file.</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, IFileSystem fileSystem, CancellationToken cancellationToken) + { + var fileInfo = fileSystem.GetFileInfo(file); + + if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) + { + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + + 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; + } + + public string FindMatch(BaseItem item, IEnumerable<string> images) + { + var name = GetComparableName(item.Name); + + return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase)); + } + + private string GetComparableName(string name) + { + 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) + { + using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (var reader = new StreamReader(fileStream)) + { + var lines = new List<string>(); + + while (!reader.EndOfStream) + { + var text = reader.ReadLine(); + + if (!string.IsNullOrWhiteSpace(text)) + { + lines.Add(text); + } + } + + return lines; + } + } + } + } +} |
