From ebf7725ca6e3d7af01a56cbffb2f8c65684370a0 Mon Sep 17 00:00:00 2001 From: DarkFighterLuke Date: Sat, 9 Mar 2024 16:57:11 +0100 Subject: [PATCH] Implement basic metadata and image providers for episodes --- .../Constants.cs | 5 + ...llyfin.Plugin.TubeArchivistMetadata.csproj | 1 + .../Plugin.cs | 11 ++ .../Providers/EpisodeImageProvider.cs | 71 ++++++++++ .../Providers/EpisodeMetadataProvider.cs | 87 ++++++++++++ .../TubeArchivist/Channel.cs | 71 ++++++++++ .../TubeArchivist/ResponseContainer.cs | 14 ++ .../TubeArchivist/TubeArchivistApi.cs | 124 ++++++++++++++++++ .../TubeArchivist/Video.cs | 122 +++++++++++++++++ 9 files changed, 506 insertions(+) create mode 100644 Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeImageProvider.cs create mode 100644 Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs create mode 100644 Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Channel.cs create mode 100644 Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/ResponseContainer.cs create mode 100644 Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs create mode 100644 Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Video.cs diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs index 8528022..4ddeece 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs @@ -14,5 +14,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata /// Plugin GUID. /// public const string PluginGuid = "dc97d0c6-28b0-4242-afb4-5833ae1b3715"; + + /// + /// Providers name. + /// + public const string ProviderName = "TubeArchivist"; } } diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Jellyfin.Plugin.TubeArchivistMetadata.csproj b/Jellyfin.Plugin.TubeArchivistMetadata/Jellyfin.Plugin.TubeArchivistMetadata.csproj index 46ee0d1..2df209c 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Jellyfin.Plugin.TubeArchivistMetadata.csproj +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Jellyfin.Plugin.TubeArchivistMetadata.csproj @@ -13,6 +13,7 @@ + diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs index 02b26a6..195d5a4 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Net.Http; using Jellyfin.Plugin.TubeArchivistMetadata.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; @@ -23,6 +24,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata : base(applicationPaths, xmlSerializer) { Instance = this; + HttpClientHandler handler = new HttpClientHandler(); + handler.AllowAutoRedirect = false; + handler.CheckCertificateRevocationList = true; + HttpClient = new HttpClient(handler); + HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Token", Instance?.Configuration.TubeArchivistApiKey); } /// @@ -36,6 +42,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata /// public static Plugin? Instance { get; private set; } + /// + /// Gets the HTTP client used globally in the plugin. + /// + public HttpClient HttpClient { get; } + /// public IEnumerable GetPages() => new[] { diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeImageProvider.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeImageProvider.cs new file mode 100644 index 0000000..34363ac --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeImageProvider.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers +{ + /// + /// Image provider which interacts with TubeArchivist library. + /// + public class EpisodeImageProvider : IRemoteImageProvider + { + /// + /// Gets the provider name. + /// + public string Name => "TubeArchivist"; + + /// + public bool Supports(BaseItem item) => item is Episode; + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + return new[] { ImageType.Primary }; + } + + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var list = new List(); + var taApi = TubeArchivistApi.GetInstance(); + var videoTAId = item.Path.Split("/").Last().Split(".").First(); + var video = await taApi.GetVideo(videoTAId).ConfigureAwait(true); + + if (video != null) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Type = ImageType.Primary, + Url = video.VidThumbUrl + }); + Console.WriteLine("ThumbURL: " + video.VidThumbUrl); + } + + return list; + } + + /// + public async Task GetImageResponse(string url, CancellationToken cancellationToken) + { + if (Plugin.Instance == null) + { + throw new DataException("Uninitialized plugin!"); + } + else + { + return await Plugin.Instance.HttpClient.GetAsync(new Uri(Plugin.Instance.Configuration.TubeArchivistUrl + url), cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs new file mode 100644 index 0000000..cfce945 --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Newtonsoft.Json; + +namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers +{ + /// + /// Metadata provider which interacts with TubeArchivist library. + /// + public class EpisodeMetadataProvider : IRemoteMetadataProvider + { + /// + /// Gets the provider name. + /// + public string Name => "TubeArchivist"; + + /// + public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult(); + var taApi = TubeArchivistApi.GetInstance(); + var videoTAId = info.Path.Split("/").Last().Split(".").First(); + var video = await taApi.GetVideo(videoTAId).ConfigureAwait(true); + Console.WriteLine("Metadata video received: "); + Console.WriteLine(JsonConvert.SerializeObject(video)); + Console.WriteLine("Media YT id: " + videoTAId); + + if (video != null) + { + var peopleInfo = new List(); + PeopleHelper.AddPerson(peopleInfo, new PersonInfo + { + Name = video.Channel.Name, + ImageUrl = video.Channel.ThumbUrl, + Type = PersonType.Actor, + }); + result.HasMetadata = true; + result.Item = video.ToEpisode(); + result.Provider = Name; + result.People = peopleInfo; + } + + Console.WriteLine(result.Item.ProductionYear); + return result; + } + + /// + public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) + { + var results = new List(); + + var taApi = TubeArchivistApi.GetInstance(); + var videoTAId = searchInfo.Path.Split("/").Last().Split(".").First(); + var video = await taApi.GetVideo(videoTAId).ConfigureAwait(true); + if (video != null) + { + results.Add(video.ToSearchResult()); + } + + return results; + } + + /// + public async Task GetImageResponse(string url, CancellationToken cancellationToken) + { + if (Plugin.Instance == null) + { + throw new DataException("Uninitialized plugin!"); + } + else + { + return await Plugin.Instance.HttpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Channel.cs b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Channel.cs new file mode 100644 index 0000000..908d01f --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Channel.cs @@ -0,0 +1,71 @@ +using Newtonsoft.Json; + +namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist +{ + /// + /// A class representing TubeArchivist API channel data. + /// + public class Channel + { + /// + /// Initializes a new instance of the class. + /// + /// URL of the channel banner image. + /// Channel description. + /// Channel YouTube id. + /// Channel name. + /// URL of the channel thumb image. + /// URL of the channel tvart image. + public Channel( + string bannerUrl, + string description, + string id, + string name, + string thumbUrl, + string tvartUrl) + { + this.BannerUrl = bannerUrl; + this.Description = description; + this.Id = id; + this.Name = name; + this.ThumbUrl = thumbUrl; + this.TvartUrl = tvartUrl; + } + + /// + /// Gets or sets the URL of the channel banner image. + /// + [JsonProperty(PropertyName = "channel_banner_url")] + public string BannerUrl { get; set; } + + /// + /// Gets or sets the channel description. + /// + [JsonProperty(PropertyName = "channel_description")] + public string Description { get; set; } + + /// + /// Gets or sets the channel YouTube id. + /// + [JsonProperty(PropertyName = "channel_id")] + public string Id { get; set; } + + /// + /// Gets or sets the channel name. + /// + [JsonProperty(PropertyName = "channel_name")] + public string Name { get; set; } + + /// + /// Gets or sets the URL of the channel thumb image. + /// + [JsonProperty(PropertyName = "channel_thumb_url")] + public string ThumbUrl { get; set; } + + /// + /// Gets or sets the URL of the channel tvart image. + /// + [JsonProperty(PropertyName = "channel_tvart_url")] + public string TvartUrl { get; set; } + } +} diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/ResponseContainer.cs b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/ResponseContainer.cs new file mode 100644 index 0000000..753c10a --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/ResponseContainer.cs @@ -0,0 +1,14 @@ +namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist +{ + /// + /// Class representing the base common structure of TubeArchivist API responses. + /// + /// The contained data type. + public class ResponseContainer + { + /// + /// Gets or sets the contained data object. + /// + public T? Data { get; set; } + } +} diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs new file mode 100644 index 0000000..8242000 --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs @@ -0,0 +1,124 @@ +using System; +using System.Data; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist +{ + /// + /// Class to interact with TubeArchivist API. + /// + public class TubeArchivistApi + { + private HttpClient client; + private static TubeArchivistApi _taApiInstance = null!; + + /// + /// Initializes a new instance of the class. + /// + /// HTTP client to make requests to TubeArchivist API. + private TubeArchivistApi(HttpClient httpClient) + { + client = httpClient; + } + + /// + /// Gets the instance of the class. + /// + /// The TubeArchivistApi instance. + public static TubeArchivistApi GetInstance() + { + if (_taApiInstance == null) + { + if (Plugin.Instance != null) + { + _taApiInstance = new TubeArchivistApi(Plugin.Instance.HttpClient); + } + else + { + throw new DataException("Uninitialized plugin!"); + } + } + + return _taApiInstance; + } + + /// + /// Retrieves the given channel information from TubeArchivist. + /// + /// YouTube channel id. + /// A task. + public async Task GetChannel(string channelId) + { + ResponseContainer? channel = null; + Console.WriteLine(Plugin.Instance?.Configuration.TubeArchivistUrl); + Console.WriteLine(Plugin.Instance?.Configuration.TubeArchivistApiKey); + + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", Plugin.Instance?.Configuration.TubeArchivistApiKey); + + var channelsEndpoint = "/api/channel/"; + var url = new Uri(Plugin.Instance?.Configuration.TubeArchivistUrl + channelsEndpoint + channelId); + var response = await client.GetAsync(url).ConfigureAwait(true); + Console.WriteLine(response.StatusCode); + while (response.StatusCode == HttpStatusCode.Moved) + { + Console.WriteLine("Received redirect to: " + response.Headers.Location); + response = await client.GetAsync(response.Headers.Location).ConfigureAwait(true); + } + + Console.WriteLine(url + ": " + response.StatusCode); + + if (response.IsSuccessStatusCode) + { + string rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true); + channel = JsonConvert.DeserializeObject>(rawData); + + Console.WriteLine("Channel null? " + channel == null); + Console.WriteLine("Channel: " + channel?.Data?.Name); + if (channel != null && channel.Data != null) + { + Console.WriteLine(JsonConvert.SerializeObject(channel)); + } + } + + return channel?.Data; + } + + /// + /// Retrieves the given video information from TubeArchivist. + /// + /// YouTube video id. + /// A task. + public async Task GetVideo(string videoId) + { + ResponseContainer