diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs index c8a99d2..c4ef00b 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/NumberingScheme.cs @@ -14,4 +14,10 @@ public enum NumberingScheme /// YYYYMMDD (e.g. 20250804 for August 4th, 2025). /// YYYYMMDD, + + /// + /// The video position within its TubeArchivist playlist. + /// Falls back to when the video does not belong to a playlist. + /// + PlaylistIndex, } diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PlaylistSeasonMapEntry.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PlaylistSeasonMapEntry.cs new file mode 100644 index 0000000..170843e --- /dev/null +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PlaylistSeasonMapEntry.cs @@ -0,0 +1,47 @@ +namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration +{ + /// + /// A persisted association between a TubeArchivist playlist and the Jellyfin season number + /// used to represent it. + /// + /// + /// Jellyfin groups episodes into seasons using ParentIndexNumber, which is an integer, + /// while TubeArchivist identifies playlists by string id. This entry persists the mapping so + /// season numbers stay stable across restarts and metadata refreshes. + /// A plain dictionary is not used because Jellyfin's XML configuration serializer does not + /// reliably round-trip dictionary types. + /// + public class PlaylistSeasonMapEntry + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A parameterless constructor is required by the XML serializer used to persist the + /// plugin configuration. + /// + public PlaylistSeasonMapEntry() + { + PlaylistId = string.Empty; + PlaylistName = string.Empty; + } + + /// + /// Gets or sets the TubeArchivist playlist id. + /// + public string PlaylistId { get; set; } + + /// + /// Gets or sets the Jellyfin season number representing the playlist. + /// + public int SeasonNumber { get; set; } + + /// + /// Gets or sets the last known TubeArchivist playlist name. + /// + /// + /// Stored so seasons keep a usable label when TubeArchivist is unreachable. + /// + public string PlaylistName { get; set; } + } +} diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs index ba1ac2c..9d303b9 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Configuration/PluginConfiguration.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Data; using System.Linq; +using System.Text.Json.Serialization; using Jellyfin.Plugin.TubeArchivistMetadata.Utilities; using MediaBrowser.Model.Plugins; using Microsoft.Extensions.Logging; @@ -47,6 +49,9 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration TAJFProgressTaskInterval = 60; JFTAPlaylistsSyncTaskInterval = 60; TAJFPlaylistsSyncTaskInterval = 60; + SortSeasonsByPlaylist = false; + SeasonFetcherAutoEnabled = false; + PlaylistSeasonMap = new Collection(); } /// @@ -191,6 +196,49 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration /// public NumberingScheme EpisodeNumberingScheme { get; set; } = NumberingScheme.Default; + /// + /// Gets or sets a value indicating whether to group episodes into seasons by TubeArchivist + /// playlist instead of by upload year. + /// + /// + /// Changing this setting requires a "Refresh metadata" with "Replace all metadata" enabled: + /// Jellyfin only overwrites an existing season name when replacing metadata. + /// + public bool SortSeasonsByPlaylist { get; set; } + + /// + /// Gets the persisted TubeArchivist playlist to Jellyfin season number associations. + /// + /// + /// + /// Populated automatically; not user editable. Entries are append-only so season numbers + /// remain stable once assigned. + /// + /// + /// is required and must not be removed. Jellyfin persists + /// plugin configuration as XML, but the plugin configuration API endpoint deserializes the + /// posted body with System.Text.Json, which ignores non-public setters. Without this + /// attribute the collection is dropped whenever the configuration page is saved, and + /// BasePlugin.UpdateConfiguration then writes the emptied collection back to disk. + /// A public setter is not an option because it violates CA2227. + /// This is unrelated to the Newtonsoft usage of the TubeArchivist API models. + /// + /// + [JsonInclude] + public Collection PlaylistSeasonMap { get; private set; } + + /// + /// Gets or sets a value indicating whether the one time migration which enables this + /// plugin's Season metadata fetcher on existing libraries has already run. + /// + /// + /// Libraries created before the Season provider existed store an empty Season fetcher list, + /// which Jellyfin treats as "all disabled" rather than "use the defaults". The migration + /// repairs that once; keeping this flag means a fetcher removed by hand afterwards stays + /// removed instead of being re-added on every restart. + /// + public bool SeasonFetcherAutoEnabled { get; set; } + /// /// Gets the playback progress owners Jellyfin usernames to synchronize data from TubeArchivist. /// diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs index 4ddeece..1d6b850 100644 --- a/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs +++ b/Jellyfin.Plugin.TubeArchivistMetadata/Constants.cs @@ -19,5 +19,19 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata /// Providers name. /// public const string ProviderName = "TubeArchivist"; + + /// + /// Season number used to group videos which do not belong to any TubeArchivist playlist. + /// + /// + /// Season 0 cannot be used: Jellyfin's SeasonMetadataService.BeforeSaveInternal + /// force-renames season 0 to the library's "Specials" display name. + /// + public const int UnsortedSeasonNumber = 9000; + + /// + /// Display name of the season grouping videos which do not belong to any TubeArchivist playlist. + /// + public const string UnsortedSeasonName = "Unsorted"; } }