Compare commits
11
Commits
v1.4.3
..
754d4abd45
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
754d4abd45 | ||
|
|
5f4afcc28e | ||
|
|
42c02b33eb | ||
|
|
13553ef1c6 | ||
|
|
ac2e4dac9c | ||
|
|
0bbb3c36cd | ||
|
|
db0d9b115c | ||
|
|
bc343dd0c8 | ||
|
|
9ed4869ece | ||
|
|
c2eebbe1a9 | ||
|
|
e198b0fb92 |
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.4.3.0</Version>
|
<Version>1.4.5.0</Version>
|
||||||
<AssemblyVersion>1.4.3.0</AssemblyVersion>
|
<AssemblyVersion>1.4.5.0</AssemblyVersion>
|
||||||
<FileVersion>1.4.3.0</FileVersion>
|
<FileVersion>1.4.5.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -172,10 +172,17 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
|
|||||||
{
|
{
|
||||||
long progress = (long)eventArgs.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
long progress = (long)eventArgs.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
||||||
var videoId = Utils.GetVideoNameFromPath(eventArgs.Item.Path);
|
var videoId = Utils.GetVideoNameFromPath(eventArgs.Item.Path);
|
||||||
var statusCode = await TubeArchivistApi.GetInstance().SetProgress(videoId, progress).ConfigureAwait(true);
|
try
|
||||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
|
||||||
{
|
{
|
||||||
Logger.LogCritical("{Message}", $"POST /video/{videoId}/progress returned {statusCode} for video {eventArgs.Item.Name} with progress {progress} seconds");
|
var statusCode = await TubeArchivistApi.GetInstance().SetProgress(videoId, progress).ConfigureAwait(true);
|
||||||
|
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
Logger.LogCritical("{Message}", $"POST /video/{videoId}/progress returned {statusCode} for video {eventArgs.Item.Name} with progress {progress} seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogCritical("An exception occurred while calling POST /video/{VideoId}/progress for for video {VideoName} with progress {Progress} seconds: {ExceptionMessage}", videoId, eventArgs.Item.Name, progress, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
public string Name => "TubeArchivist";
|
public string Name => "TubeArchivist";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(BaseItem item) => item is Episode;
|
public bool Supports(BaseItem item) => item is Episode && Utils.IsTubeArchivistItem(item);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
@@ -65,7 +65,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
_logger.LogDebug("{Message}", string.Format(CultureInfo.CurrentCulture, "Getting images for video: {0} ({1})", video?.Title, videoTAId));
|
_logger.LogDebug("{Message}", string.Format(CultureInfo.CurrentCulture, "Getting images for video: {0} ({1})", video?.Title, videoTAId));
|
||||||
_logger.LogDebug("{Message}", "Thumb URI: " + video?.VidThumbUrl);
|
_logger.LogDebug("{Message}", "Thumb URI: " + video?.VidThumbUrl);
|
||||||
|
|
||||||
if (video != null)
|
if (video != null && Utils.HasImageUrl(video.VidThumbUrl))
|
||||||
{
|
{
|
||||||
list.Add(new RemoteImageInfo
|
list.Add(new RemoteImageInfo
|
||||||
{
|
{
|
||||||
@@ -87,6 +87,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!Utils.HasImageUrl(url))
|
||||||
|
{
|
||||||
|
throw new HttpRequestException("TubeArchivist returned an empty image URL.");
|
||||||
|
}
|
||||||
|
|
||||||
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
|
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
|
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = new MetadataResult<Episode>();
|
var result = new MetadataResult<Episode>();
|
||||||
|
if (string.IsNullOrWhiteSpace(info.Path))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
var taApi = TubeArchivistApi.GetInstance();
|
var taApi = TubeArchivistApi.GetInstance();
|
||||||
var videoTAId = Utils.GetVideoNameFromPath(info.Path);
|
var videoTAId = Utils.GetVideoNameFromPath(info.Path);
|
||||||
var video = await taApi.GetVideo(videoTAId).ConfigureAwait(true);
|
var video = await taApi.GetVideo(videoTAId).ConfigureAwait(true);
|
||||||
@@ -79,6 +84,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var results = new List<RemoteSearchResult>();
|
var results = new List<RemoteSearchResult>();
|
||||||
|
if (string.IsNullOrWhiteSpace(searchInfo.Path))
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
var taApi = TubeArchivistApi.GetInstance();
|
var taApi = TubeArchivistApi.GetInstance();
|
||||||
var videoTAId = Utils.GetVideoNameFromPath(searchInfo.Path);
|
var videoTAId = Utils.GetVideoNameFromPath(searchInfo.Path);
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
public string Name => "TubeArchivist";
|
public string Name => "TubeArchivist";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(BaseItem item) => item is Series;
|
public bool Supports(BaseItem item) => item is Series && Utils.IsTubeArchivistItem(item);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new[] { ImageType.Primary };
|
return new[] { ImageType.Primary, ImageType.Art, ImageType.Banner, ImageType.Backdrop };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -62,10 +62,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
var channel = await taApi.GetChannel(channelTAId).ConfigureAwait(true);
|
var channel = await taApi.GetChannel(channelTAId).ConfigureAwait(true);
|
||||||
_logger.LogDebug("{Message}", string.Format(CultureInfo.CurrentCulture, "Getting images for channel: {0} ({1})", channel?.Name, channelTAId));
|
_logger.LogDebug("{Message}", string.Format(CultureInfo.CurrentCulture, "Getting images for channel: {0} ({1})", channel?.Name, channelTAId));
|
||||||
_logger.LogDebug("{Message}", "Thumb URI: " + channel?.ThumbUrl);
|
_logger.LogDebug("{Message}", "Thumb URI: " + channel?.ThumbUrl);
|
||||||
_logger.LogDebug("{Message}", "TVArt URI: " + channel?.TvartUrl);
|
|
||||||
_logger.LogDebug("{Message}", "Banner URI: " + channel?.BannerUrl);
|
|
||||||
|
|
||||||
if (channel != null)
|
if (channel != null && Utils.HasImageUrl(channel.ThumbUrl))
|
||||||
{
|
{
|
||||||
list.Add(new RemoteImageInfo
|
list.Add(new RemoteImageInfo
|
||||||
{
|
{
|
||||||
@@ -73,18 +71,17 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
Type = ImageType.Primary,
|
Type = ImageType.Primary,
|
||||||
Url = channel.ThumbUrl
|
Url = channel.ThumbUrl
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel != null && Utils.HasImageUrl(channel.TvartUrl))
|
||||||
|
{
|
||||||
list.Add(new RemoteImageInfo
|
list.Add(new RemoteImageInfo
|
||||||
{
|
{
|
||||||
ProviderName = Name,
|
ProviderName = Name,
|
||||||
Type = ImageType.Art,
|
Type = ImageType.Art,
|
||||||
Url = channel.TvartUrl
|
Url = channel.TvartUrl
|
||||||
});
|
});
|
||||||
list.Add(new RemoteImageInfo
|
|
||||||
{
|
|
||||||
ProviderName = Name,
|
|
||||||
Type = ImageType.Banner,
|
|
||||||
Url = channel.BannerUrl
|
|
||||||
});
|
|
||||||
list.Add(new RemoteImageInfo
|
list.Add(new RemoteImageInfo
|
||||||
{
|
{
|
||||||
ProviderName = Name,
|
ProviderName = Name,
|
||||||
@@ -93,6 +90,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel != null && Utils.HasImageUrl(channel.BannerUrl))
|
||||||
|
{
|
||||||
|
list.Add(new RemoteImageInfo
|
||||||
|
{
|
||||||
|
ProviderName = Name,
|
||||||
|
Type = ImageType.Banner,
|
||||||
|
Url = channel.BannerUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +112,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!Utils.HasImageUrl(url))
|
||||||
|
{
|
||||||
|
throw new HttpRequestException("TubeArchivist returned an empty image URL.");
|
||||||
|
}
|
||||||
|
|
||||||
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
|
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
|
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = new MetadataResult<Series>();
|
var result = new MetadataResult<Series>();
|
||||||
|
if (string.IsNullOrWhiteSpace(info.Path))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
var taApi = TubeArchivistApi.GetInstance();
|
var taApi = TubeArchivistApi.GetInstance();
|
||||||
var channelTAId = Utils.GetChannelNameFromPath(info.Path);
|
var channelTAId = Utils.GetChannelNameFromPath(info.Path);
|
||||||
var channel = await taApi.GetChannel(channelTAId).ConfigureAwait(true);
|
var channel = await taApi.GetChannel(channelTAId).ConfigureAwait(true);
|
||||||
@@ -77,6 +82,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
|
|||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var results = new List<RemoteSearchResult>();
|
var results = new List<RemoteSearchResult>();
|
||||||
|
if (string.IsNullOrWhiteSpace(searchInfo.Path))
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
var taApi = TubeArchivistApi.GetInstance();
|
var taApi = TubeArchivistApi.GetInstance();
|
||||||
var channelTAId = Utils.GetChannelNameFromPath(searchInfo.Path);
|
var channelTAId = Utils.GetChannelNameFromPath(searchInfo.Path);
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
Type = TaskTriggerInfoType.IntervalTrigger,
|
Type = TaskTriggerInfoType.IntervalTrigger,
|
||||||
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
|
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.JFTAPlaylistsSyncTaskInterval).Ticks
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,10 +201,17 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||||||
var playbackProgress = _userDataManager.GetUserData(user, video)?.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
var playbackProgress = _userDataManager.GetUserData(user, video)?.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
||||||
if (playbackProgress != null)
|
if (playbackProgress != null)
|
||||||
{
|
{
|
||||||
statusCode = await taApi.SetProgress(videoYTId, playbackProgress.Value).ConfigureAwait(true);
|
try
|
||||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
|
||||||
{
|
{
|
||||||
_logger.LogCritical("{Message}", $"POST /video/{videoYTId}/progress returned {statusCode} for video {video.Name} with progress {progress} seconds");
|
statusCode = await taApi.SetProgress(videoYTId, playbackProgress.Value).ConfigureAwait(true);
|
||||||
|
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
_logger.LogCritical("{Message}", $"POST /video/{videoYTId}/progress returned {statusCode} for video {video.Name} with progress {progress} seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogCritical("An exception occurred while calling POST /video/{VideoId}/progress for for video {VideoName} with progress {Progress} seconds: {ExceptionMessage}", videoYTId, videoYTId, playbackProgress.Value, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,15 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
processedVideosCount += currentPlaylistVideos;
|
processedVideosCount += currentPlaylistVideos;
|
||||||
progress.Report(processedVideosCount * 100 / totalVideosCount);
|
|
||||||
|
if (totalVideosCount == 0)
|
||||||
|
{
|
||||||
|
progress.Report(100);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress.Report(processedVideosCount * 100 / totalVideosCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +201,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
Type = TaskTriggerInfoType.IntervalTrigger,
|
Type = TaskTriggerInfoType.IntervalTrigger,
|
||||||
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
|
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFPlaylistsSyncTaskInterval).Ticks
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
{
|
{
|
||||||
Name = Name,
|
Name = Name,
|
||||||
SearchProviderName = Constants.ProviderName,
|
SearchProviderName = Constants.ProviderName,
|
||||||
ImageUrl = ThumbUrl,
|
ImageUrl = Utils.HasImageUrl(ThumbUrl) ? ThumbUrl : null,
|
||||||
ProviderIds = new Dictionary<string, string>() { { Constants.ProviderName, Id } }
|
ProviderIds = new Dictionary<string, string>() { { Constants.ProviderName, Id } }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -118,14 +118,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
Constants.ProviderName, Id
|
Constants.ProviderName, Id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ImageInfos = new[]
|
ImageInfos = Utils.HasImageUrl(ThumbUrl) ?
|
||||||
{
|
[
|
||||||
new ItemImageInfo
|
new ItemImageInfo
|
||||||
{
|
{
|
||||||
Path = ThumbUrl,
|
Path = ThumbUrl,
|
||||||
Type = ImageType.Primary
|
Type = ImageType.Primary
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
] :
|
||||||
|
[],
|
||||||
Tags = this.Tags != null ?
|
Tags = this.Tags != null ?
|
||||||
this.Tags.ToArray<string>() :
|
this.Tags.ToArray<string>() :
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class representing pagination information from the TubeArchivist API.
|
||||||
|
/// </summary>
|
||||||
|
public class PaginationInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PaginationInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pageSize">The size of the page.</param>
|
||||||
|
/// <param name="pageFrom">The starting page number.</param>
|
||||||
|
/// <param name="prevPages">The collection of previous page numbers.</param>
|
||||||
|
/// <param name="currentPage">The current page number.</param>
|
||||||
|
/// <param name="maxHits">A value indicating whether the max hits have been reached.</param>
|
||||||
|
/// <param name="parameters">The parameters used for pagination.</param>
|
||||||
|
/// <param name="lastPage">The last page number.</param>
|
||||||
|
/// <param name="nextPages">The collection of next page numbers.</param>
|
||||||
|
/// <param name="totalHits">The total number of hits.</param>
|
||||||
|
public PaginationInfo(
|
||||||
|
int pageSize,
|
||||||
|
int pageFrom,
|
||||||
|
Collection<int> prevPages,
|
||||||
|
int currentPage,
|
||||||
|
bool maxHits,
|
||||||
|
string parameters,
|
||||||
|
int lastPage,
|
||||||
|
Collection<int> nextPages,
|
||||||
|
int totalHits)
|
||||||
|
{
|
||||||
|
this.PageSize = pageSize;
|
||||||
|
this.PageFrom = pageFrom;
|
||||||
|
this.PrevPages = prevPages;
|
||||||
|
this.CurrentPage = currentPage;
|
||||||
|
this.MaxHits = maxHits;
|
||||||
|
this.Parameters = parameters;
|
||||||
|
this.LastPage = lastPage;
|
||||||
|
this.NextPages = nextPages;
|
||||||
|
this.TotalHits = totalHits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the page size.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "page_size")]
|
||||||
|
public int PageSize { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the page from.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "page_from")]
|
||||||
|
public int PageFrom { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the previous pages.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "prev_pages")]
|
||||||
|
public Collection<int> PrevPages { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current page.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "current_page")]
|
||||||
|
public int CurrentPage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the max hits have been reached.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "max_hits")]
|
||||||
|
public bool MaxHits { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parameters.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "params")]
|
||||||
|
public string Parameters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last page.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "last_page")]
|
||||||
|
public int LastPage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next pages.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "next_pages")]
|
||||||
|
public Collection<int> NextPages { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total hits.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "total_hits")]
|
||||||
|
public int TotalHits { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,5 +112,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
/// <value>One of the <see cref="PlaylistType"/> values indicating the playlist's type.</value>
|
/// <value>One of the <see cref="PlaylistType"/> values indicating the playlist's type.</value>
|
||||||
[JsonProperty(PropertyName = "playlist_type")]
|
[JsonProperty(PropertyName = "playlist_type")]
|
||||||
public PlaylistType Type { get; set; }
|
public PlaylistType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string of the playlist's key fields for logging.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string containing the playlist id, name, and entries count.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "PlaylistId: " + this.Id +
|
||||||
|
", Name: " + this.Name +
|
||||||
|
", EntriesCount: " + this.Entries.Count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
/// Gets or sets the contained data object.
|
/// Gets or sets the contained data object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T? Data { get; set; }
|
public T? Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the pagination info object.
|
||||||
|
/// </summary>
|
||||||
|
public PaginationInfo? Paginate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
{
|
{
|
||||||
url = response.Headers.Location;
|
url = response.Headers.Location;
|
||||||
_logger.LogInformation("{Message}", "Received redirect to: " + url);
|
_logger.LogInformation("{Message}", "Received redirect to: " + url);
|
||||||
response = await client.GetAsync(url).ConfigureAwait(true);
|
response = await client.GetAsync(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + url)).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("{Message}", url + ": " + response.StatusCode);
|
_logger.LogInformation("{Message}", url + ": " + response.StatusCode);
|
||||||
@@ -228,6 +228,50 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
{
|
{
|
||||||
string rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
|
string rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
|
||||||
playlists = JsonConvert.DeserializeObject<ResponseContainer<ISet<Playlist>?>>(rawData);
|
playlists = JsonConvert.DeserializeObject<ResponseContainer<ISet<Playlist>?>>(rawData);
|
||||||
|
if (playlists?.Paginate != null)
|
||||||
|
{
|
||||||
|
var lastPage = playlists.Paginate.LastPage;
|
||||||
|
_logger.LogInformation("Pagination info: Current page {CurrentPage} / Last page {LastPage}, Total hits: {TotalHits}", playlists.Paginate.CurrentPage, playlists.Paginate.LastPage, playlists.Paginate.TotalHits);
|
||||||
|
|
||||||
|
while (playlists.Paginate.CurrentPage < lastPage)
|
||||||
|
{
|
||||||
|
var nextPage = playlists.Paginate.CurrentPage + 1;
|
||||||
|
var pagedUrl = new Uri(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + playlistsEndpoint + "?page=" + nextPage));
|
||||||
|
response = await client.GetAsync(pagedUrl).ConfigureAwait(true);
|
||||||
|
while (response.StatusCode == HttpStatusCode.Moved)
|
||||||
|
{
|
||||||
|
url = response.Headers.Location;
|
||||||
|
_logger.LogInformation("{Message}", "Received redirect to: " + url);
|
||||||
|
response = await client.GetAsync(Utils.SanitizeUrl(Plugin.Instance?.Configuration.TubeArchivistUrl + url)).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("{Message}", pagedUrl + ": " + response.StatusCode);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
|
||||||
|
var nextPagePlaylists = JsonConvert.DeserializeObject<ResponseContainer<ISet<Playlist>?>>(rawData);
|
||||||
|
if (nextPagePlaylists?.Data != null)
|
||||||
|
{
|
||||||
|
foreach (var playlist in nextPagePlaylists.Data)
|
||||||
|
{
|
||||||
|
playlists.Data?.Add(playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPagePlaylists?.Paginate != null)
|
||||||
|
{
|
||||||
|
playlists.Paginate = nextPagePlaylists.Paginate;
|
||||||
|
_logger.LogInformation("Pagination info: Current page {CurrentPage} / Last page {LastPage}, Total hits: {TotalHits}", playlists.Paginate.CurrentPage, playlists.Paginate.LastPage, playlists.Paginate.TotalHits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogCritical("Failed to retrieve page {PageNumber} of playlists during pagination.", nextPage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlists?.Data;
|
return playlists?.Data;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
Name = Title,
|
Name = Title,
|
||||||
SearchProviderName = Constants.ProviderName,
|
SearchProviderName = Constants.ProviderName,
|
||||||
ProductionYear = Published.Year,
|
ProductionYear = Published.Year,
|
||||||
ImageUrl = VidThumbUrl,
|
ImageUrl = Utils.HasImageUrl(VidThumbUrl) ? VidThumbUrl : null,
|
||||||
PremiereDate = Published,
|
PremiereDate = Published,
|
||||||
ProviderIds = new Dictionary<string, string>() { { Constants.ProviderName, YoutubeId } }
|
ProviderIds = new Dictionary<string, string>() { { Constants.ProviderName, YoutubeId } }
|
||||||
};
|
};
|
||||||
@@ -142,14 +142,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
Constants.ProviderName, YoutubeId
|
Constants.ProviderName, YoutubeId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ImageInfos = new[]
|
ImageInfos = Utils.HasImageUrl(VidThumbUrl) ?
|
||||||
{
|
[
|
||||||
new ItemImageInfo
|
new ItemImageInfo
|
||||||
{
|
{
|
||||||
Path = VidThumbUrl,
|
Path = VidThumbUrl,
|
||||||
Type = ImageType.Primary
|
Type = ImageType.Primary
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
] :
|
||||||
|
[],
|
||||||
Tags = this.Tags.ToArray<string>()
|
Tags = this.Tags.ToArray<string>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Extensions.Logging;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
|
namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
|
||||||
{
|
{
|
||||||
@@ -22,20 +21,59 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
|
|||||||
/// <returns>The URL string without spaces, doubled slashes and with a trailing slash.</returns>
|
/// <returns>The URL string without spaces, doubled slashes and with a trailing slash.</returns>
|
||||||
public static string SanitizeUrl(string inputUrl)
|
public static string SanitizeUrl(string inputUrl)
|
||||||
{
|
{
|
||||||
// Extract the schema part
|
if (string.IsNullOrWhiteSpace(inputUrl))
|
||||||
Match schemaMatch = Regex.Match(inputUrl, @"^(?<schema>https?://)");
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove double slashes and spaces from the remaining part of the URL
|
// Extract the schema part (http:// or https://)
|
||||||
string cleanedPath = Regex.Replace(inputUrl.Substring(schemaMatch.Length), @"[/\s]+", "/");
|
Match schemaMatch = Regex.Match(inputUrl, @"^(?<schema>https?://)", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
// If no schema found, treat whole string as the rest
|
||||||
|
int schemaLength = schemaMatch.Success ? schemaMatch.Length : 0;
|
||||||
|
|
||||||
|
// Separate the main part from query (?) and fragment (#)
|
||||||
|
string rest = inputUrl.Substring(schemaLength);
|
||||||
|
string pathPart = rest;
|
||||||
|
string queryAndFragment = string.Empty;
|
||||||
|
|
||||||
|
int qIndex = rest.IndexOf('?', StringComparison.Ordinal);
|
||||||
|
int fIndex = rest.IndexOf('#', StringComparison.Ordinal);
|
||||||
|
|
||||||
|
int splitIndex = -1;
|
||||||
|
if (qIndex >= 0 && fIndex >= 0)
|
||||||
|
{
|
||||||
|
splitIndex = Math.Min(qIndex, fIndex);
|
||||||
|
}
|
||||||
|
else if (qIndex >= 0)
|
||||||
|
{
|
||||||
|
splitIndex = qIndex;
|
||||||
|
}
|
||||||
|
else if (fIndex >= 0)
|
||||||
|
{
|
||||||
|
splitIndex = fIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splitIndex >= 0)
|
||||||
|
{
|
||||||
|
pathPart = rest.Substring(0, splitIndex);
|
||||||
|
queryAndFragment = rest.Substring(splitIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove double slashes and spaces from the path part
|
||||||
|
string cleanedPath = Regex.Replace(pathPart, @"[/\s]+", "/");
|
||||||
|
|
||||||
// Remove slashes at the start
|
// Remove slashes at the start
|
||||||
cleanedPath = cleanedPath.TrimStart('/');
|
cleanedPath = cleanedPath.TrimStart('/');
|
||||||
|
|
||||||
// Add a trailing slash if not already present
|
// Add a trailing slash only when there are no query or fragment parts
|
||||||
cleanedPath = cleanedPath.TrimEnd('/') + "/";
|
if (string.IsNullOrEmpty(queryAndFragment))
|
||||||
|
{
|
||||||
|
cleanedPath = cleanedPath.TrimEnd('/') + "/";
|
||||||
|
}
|
||||||
|
|
||||||
// Combine the schema and cleaned path
|
// Combine the schema and cleaned path and re-append query/fragment
|
||||||
string cleanedUrl = schemaMatch.Groups["schema"].Value + cleanedPath;
|
string cleanedUrl = (schemaMatch.Success ? schemaMatch.Groups["schema"].Value : string.Empty) + cleanedPath + queryAndFragment;
|
||||||
|
|
||||||
return cleanedUrl;
|
return cleanedUrl;
|
||||||
}
|
}
|
||||||
@@ -88,6 +126,32 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
|
|||||||
return path.Split(DetectDirectorySeparator(path)).Last();
|
return path.Split(DetectDirectorySeparator(path)).Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the Jellyfin item belongs to the configured TubeArchivist collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Jellyfin item.</param>
|
||||||
|
/// <returns>True when the item belongs to the configured TubeArchivist collection.</returns>
|
||||||
|
public static bool IsTubeArchivistItem(BaseItem item)
|
||||||
|
{
|
||||||
|
if (Plugin.Instance == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var topParent = item.GetTopParent();
|
||||||
|
return string.Equals(topParent?.Name, Plugin.Instance.Configuration.CollectionTitle, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a TubeArchivist image URL can be fetched safely.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">Image URL returned by TubeArchivist.</param>
|
||||||
|
/// <returns>True when the image URL is non-empty.</returns>
|
||||||
|
public static bool HasImageUrl(string? url)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(url);
|
||||||
|
}
|
||||||
|
|
||||||
private static char DetectDirectorySeparator(string path)
|
private static char DetectDirectorySeparator(string path)
|
||||||
{
|
{
|
||||||
int backslashCount = path.Count(c => c == '\\');
|
int backslashCount = path.Count(c => c == '\\');
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
name: "TubeArchivistMetadata"
|
name: "TubeArchivistMetadata"
|
||||||
guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715"
|
guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715"
|
||||||
imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png
|
imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png
|
||||||
version: "1.4.3.0"
|
version: "1.4.5.0"
|
||||||
targetAbi: "10.11.0.0"
|
targetAbi: "10.11.0.0"
|
||||||
framework: "net9.0"
|
framework: "net9.0"
|
||||||
overview: "Metadata for your TubeArchivist library on Jellyfin"
|
overview: "Metadata for your TubeArchivist library on Jellyfin"
|
||||||
@@ -14,4 +14,4 @@ owner: "DarkFighterLuke"
|
|||||||
artifacts:
|
artifacts:
|
||||||
- "Jellyfin.Plugin.TubeArchivistMetadata.dll"
|
- "Jellyfin.Plugin.TubeArchivistMetadata.dll"
|
||||||
changelog: >
|
changelog: >
|
||||||
Fix JF->TA watched status sync
|
Limit providers to TubeArchivist paths, skip empty image URLs, and fix playlist sync intervals
|
||||||
|
|||||||
@@ -8,6 +8,30 @@
|
|||||||
"overview": "Metadata for your TubeArchivist library on Jellyfin",
|
"overview": "Metadata for your TubeArchivist library on Jellyfin",
|
||||||
"imageUrl": "https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png",
|
"imageUrl": "https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png",
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.4.5.0",
|
||||||
|
"changelog": "Limit providers to TubeArchivist paths, skip empty image URLs, and fix playlist sync intervals\n",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://git.felixfoertsch.de/felixfoertsch/tubearchivist-jf-plugin/releases/download/v1.4.5/tubearchivistmetadata_1.4.5.0.zip",
|
||||||
|
"checksum": "dad305a75a6a07f5d8185a6e8340cbbd",
|
||||||
|
"timestamp": "2026-07-01T06:20:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.4.4.0",
|
||||||
|
"changelog": "Handle TubeArchivist playlists pagination\n",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.4.4/tubearchivistmetadata_1.4.4.0.zip",
|
||||||
|
"checksum": "90f1d388f7363537df8287e996a96bbd",
|
||||||
|
"timestamp": "2025-12-02T13:40:47Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.4.3.0",
|
||||||
|
"changelog": "Introduce episode numbering alternative schema\n",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.4.3/tubearchivistmetadata_1.4.3.0.zip",
|
||||||
|
"checksum": "297d4f15af1132939f43c905b1ad393e",
|
||||||
|
"timestamp": "2025-11-13T19:31:49Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.4.2.0",
|
"version": "1.4.2.0",
|
||||||
"changelog": "Introduce episode numbering alternative schema\n",
|
"changelog": "Introduce episode numbering alternative schema\n",
|
||||||
|
|||||||
Reference in New Issue
Block a user