diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs new file mode 100644 index 0000000..c8a99d2 --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs @@ -0,0 +1,17 @@ +namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration; + +/// +/// The NumberingScheme. +/// +public enum NumberingScheme +{ + /// + /// Default (no numbering). + /// + Default, + + /// + /// YYYYMMDD (e.g. 20250804 for August 4th, 2025). + /// + YYYYMMDD, +} diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs index 21e23b0..ba1ac2c 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs @@ -186,6 +186,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration /// public int TAJFPlaylistsSyncTaskInterval { get; set; } + /// + /// Gets or sets the preferred numbering scheme for episodes (index number) in Jellyfin. + /// + public NumberingScheme EpisodeNumberingScheme { get; set; } = NumberingScheme.Default; + /// /// Gets the playback progress owners Jellyfin usernames to synchronize data from TubeArchivist. /// diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/configPage.html b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/configPage.html index 86f016d..ae769f6 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/configPage.html +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/configPage.html @@ -41,6 +41,18 @@
This is the maximum length of the descriptions showed in series and episodes
+
+ + +
+ +
+
This is the numbering scheme for episodes in Jellyfin (IndexNumber). + Default is the same as no numbering which allows Jellyfin to use the fallback numbering scheme. +

Synchronization

@@ -160,6 +172,7 @@ document.querySelector('#JFTAPlaylistsSync').checked = config.JFTAPlaylistsSync; document.querySelector('#TAJFPlaylistsSync').checked = config.TAJFPlaylistsSync; document.querySelector('#JFUsernameFrom').value = config.JFUsernameFrom; + document.querySelector('#EpisodeNumberingScheme').value = config.EpisodeNumberingScheme; document.querySelector('#TAJFProgressTaskInterval').value = config.TAJFProgressTaskInterval; document.querySelector('#JFTAPlaylistsSyncTaskInterval').value = config.JFTAPlaylistsSyncTaskInterval; document.querySelector('#TAJFPlaylistsSyncTaskInterval').value = config.TAJFPlaylistsSyncTaskInterval; @@ -181,6 +194,7 @@ config.JFTAPlaylistsSync = document.querySelector('#JFTAPlaylistsSync').checked; config.TAJFPlaylistsSync = document.querySelector('#TAJFPlaylistsSync').checked; config.JFUsernameFrom = document.querySelector('#JFUsernameFrom').value; + config.EpisodeNumberingScheme = document.querySelector('#EpisodeNumberingScheme').value; config.TAJFProgressTaskInterval = document.querySelector('#TAJFProgressTaskInterval').value; config.JFTAPlaylistsSyncTaskInterval = document.querySelector('#JFTAPlaylistsSyncTaskInterval').value; config.TAJFPlaylistsSyncTaskInterval = document.querySelector('#TAJFPlaylistsSyncTaskInterval').value; diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs index 7e5a89c..7902298 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs @@ -162,20 +162,20 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata return; } - if (Instance!.Configuration.JFTAProgressSync && eventArgs.Users.Any(u => Instance!.Configuration.JFUsernameFrom.Equals(u.Username, StringComparison.Ordinal))) + var topParent = eventArgs.Item.GetTopParent(); + if ( + Instance!.Configuration.JFTAProgressSync && + eventArgs.Users.Any(u => Instance!.Configuration.JFUsernameFrom.Equals(u.Username, StringComparison.Ordinal)) && + eventArgs.PlaybackPositionTicks.HasValue && + string.Equals(topParent?.Name, Instance?.Configuration.CollectionTitle, StringComparison.OrdinalIgnoreCase) + ) { - BaseItem? season = LibraryManager.GetItemById(eventArgs.Item.ParentId); - BaseItem? channel = LibraryManager.GetItemById(season!.ParentId); - BaseItem? collection = LibraryManager.GetItemById(channel!.ParentId); - if (collection?.Name.ToLower(CultureInfo.CurrentCulture) == Instance?.Configuration.CollectionTitle.ToLower(CultureInfo.CurrentCulture) && eventArgs.PlaybackPositionTicks != null) + long progress = (long)eventArgs.PlaybackPositionTicks / TimeSpan.TicksPerSecond; + var videoId = Utils.GetVideoNameFromPath(eventArgs.Item.Path); + var statusCode = await TubeArchivistApi.GetInstance().SetProgress(videoId, progress).ConfigureAwait(true); + if (statusCode != System.Net.HttpStatusCode.OK) { - long progress = (long)eventArgs.PlaybackPositionTicks / TimeSpan.TicksPerSecond; - var videoId = Utils.GetVideoNameFromPath(eventArgs.Item.Path); - 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"); - } + Logger.LogCritical("{Message}", $"POST /video/{videoId}/progress returned {statusCode} for video {eventArgs.Item.Name} with progress {progress} seconds"); } } } @@ -188,16 +188,22 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata return; } + var topParent = eventArgs.Item.GetTopParent(); var user = _userManager.GetUserById(eventArgs.UserId); - if (user == null) + if ( + Configuration.JFTAProgressSync && + user != null && + Configuration.GetJFUsernamesToArray().Contains(user!.Username) && + string.Equals(topParent?.Name, Instance?.Configuration.CollectionTitle, StringComparison.OrdinalIgnoreCase) + ) { Logger.LogError("OnWatchedStatusChange callback called without user id for item {ItemName}", eventArgs.Item.Name); return; } - var userItemData = _userDataManager.GetUserData(user, eventArgs.Item); if (Configuration.JFTAProgressSync && user != null && Configuration.GetJFUsernamesToArray().Contains(user!.Username)) { + var userItemData = _userDataManager.GetUserData(user, eventArgs.Item); var isPlayed = eventArgs.Item.IsPlayed(user, userItemData); Logger.LogDebug("User {UserId} changed watched status to {Status} for the item {ItemName}", eventArgs.UserId, isPlayed, eventArgs.Item.Name); string itemYTId; diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs index d19acb1..d62e530 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Providers/EpisodeMetadataProvider.cs @@ -67,6 +67,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers }); result.HasMetadata = true; result.Item = video.ToEpisode(); + result.Item.Path = info.Path; result.Provider = Name; result.People = peopleInfo; } diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/JFToTubeArchivistProgressSyncTask.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/JFToTubeArchivistProgressSyncTask.cs index 661729e..0126395 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/JFToTubeArchivistProgressSyncTask.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/JFToTubeArchivistProgressSyncTask.cs @@ -153,7 +153,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks foreach (Episode video in videos) { var videoYTId = Utils.GetVideoNameFromPath(video.Path); - _logger.LogDebug("{VideoYtId}", videoYTId); + _logger.LogDebug("Current video extracted YouTube id: {VideoYtId}", videoYTId); HttpStatusCode statusCode; var userItemData = _userDataManager.GetUserData(user, channel); diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Video/Video.cs b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Video/Video.cs index dec2ec0..22b3f44 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Video/Video.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/Video/Video.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using Jellyfin.Plugin.TubeArchivistMetadata.Configuration; using Jellyfin.Plugin.TubeArchivistMetadata.Utilities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -126,6 +127,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist Overview = Utils.FormatDescription(Description), SeasonName = Published.Year.ToString(CultureInfo.CurrentCulture), ParentIndexNumber = Published.Year, + IndexNumber = Plugin.Instance?.Configuration?.EpisodeNumberingScheme switch + { + NumberingScheme.YYYYMMDD => (Published.Year * 10000) + (Published.Month * 100) + Published.Day, + _ => null + }, SeriesName = Channel.Name, ProductionYear = Published.Year, PremiereDate = Published, diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs index 8263ff7..d61e626 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Utils/Utils.cs @@ -1,6 +1,8 @@ +using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities { @@ -45,6 +47,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities /// A string with \n replaced by br tags. public static string FormatDescription(string description) { + if (description == null) + { + return ""; + } + var maxLength = 500; if (Plugin.Instance != null) { @@ -54,9 +61,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities if (description.Length > maxLength) { description = description.Substring(0, maxLength); - description = description.Replace("\n", "
", System.StringComparison.CurrentCulture); } + description = description.Replace("\n", "
", System.StringComparison.CurrentCulture); + return description; } diff --git a/README.md b/README.md index 7d90674..b42004c 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,16 @@ Keep in mind that Jellyfin lowest accepted period is of 1 minute (60 seconds) an ![Tasks intervals settings](https://github.com/user-attachments/assets/19db6b83-6715-477d-8ce7-b78526e87ba9) +## Episode numbering + +

There are different ways to number the episodes as they are configured in Jellyfin.
+This changes the number after E in S--E-- (for example S2024E100 for episode number 100 of season 2024).

+ +![Episode numbering scheme options](https://github.com/user-attachments/assets/6d36bc2c-ca9d-4a5c-8021-e15d399316fc) + +The options correlate with: +- Default - leave the numbering to what Jellyfin does by default (this is what the plugin has always done) +- YYYYMMDD - numbers the episode by the year, month, day (e.g. 20250804 for a video published on the 4th of August 2025) ## Build