Compare commits

...

31 Commits

Author SHA1 Message Date
DarkFighterLuke 9d745b0bc5 Use string.Empty 2025-10-23 22:26:39 +02:00
DarkFighterLuke a8e0896284 Bump version 2025-10-23 22:24:00 +02:00
DarkFighterLuke 53d9a17c2a Merge pull request #51 from wolffshots/master
Episodes by YYYYMMDD and some other cleanup
2025-10-23 22:21:19 +02:00
DarkFighterLuke 0c8bc243c6 Update Utils.cs 2025-10-23 22:19:22 +02:00
wolffshots 03ea6b9e5c chore: move episode naming to general options 2025-10-23 15:38:18 +02:00
wolffshots 6c9a824546 chore: merge remote-tracking branch 'parent/master' 2025-10-23 15:22:50 +02:00
DarkFighterLuke 5dc99e2fcf Update tasks intervals section in README
Added note about resource consumption with lower intervals.
2025-10-22 17:28:39 +02:00
DarkFighterLuke 478537bf0a Revise synchronization and add playlists features
Updated synchronization sections and added playlists synchronization details.
2025-10-22 17:13:02 +02:00
wolffshots 184c53ba00 chore: remove missed template entrie 2025-10-22 11:47:13 +02:00
wolffshots 70f063d33b Merge pull request #4 from wolffshots/jellyfin-10.11-merge
Merge commits from the upstream project to get the plugin working properly on Jellyfin 10.11
2025-10-22 11:43:22 +02:00
wolffshots 60753cb828 chore: merge upstream master and fix conflicts 2025-10-22 11:35:12 +02:00
DarkFighterLuke cba12adaa4 Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2025-10-22 07:00:32 +00:00
DarkFighterLuke d94fd1bac3 Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2025-10-22 06:57:57 +00:00
DarkFighterLuke dfdfac6be7 Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2025-10-21 21:54:14 +00:00
wolffshots cf87c6e829 Merge branch 'master' into master 2025-08-05 12:16:38 +02:00
wolffshots d7f4f5cbcc chore: explicitly handle null description 2025-08-05 10:37:54 +02:00
wolffshots 6e8b9ca744 doc: update README.md to reflect numbering options 2025-08-04 21:18:57 +02:00
wolffshots b0ca3a49a5 chore: catch errors on FormatDescription 2025-08-04 18:46:30 +02:00
wolffshots 4358072001 feat: add configuration for episode numbering scheme 2025-08-04 18:21:30 +02:00
DarkFighterLuke 38ed5dd9e0 Merge branch 'master' into master 2025-07-13 15:42:09 +02:00
DarkFighterLuke ff55b9f517 Update Plugin.cs 2025-07-13 14:49:41 +02:00
DarkFighterLuke 93a2de5d46 Merge branch 'master' into master 2025-07-13 13:26:17 +02:00
DarkFighterLuke 5b62e4c001 Update JFToTubeArchivistProgressSyncTask.cs 2025-07-13 13:22:18 +02:00
wolffshots 3e95ca26aa chore: make sync task logs debugs 2025-05-29 11:23:44 +02:00
wolffshots a58139eca0 chore: remove duplicated efforts 2025-05-29 11:19:27 +02:00
wolffshots 5ab5224328 chore: ran dotnet format on Plugin.cs 2025-05-29 11:00:44 +02:00
wolffshots d36d677179 chore: remove unused import 2025-05-29 10:57:02 +02:00
wolffshots 784592b362 chore: remove debugging log statements 2025-05-29 10:52:07 +02:00
jadon 2f90d6a03e chore: remove extra season and move into if 2025-05-28 23:32:06 +02:00
jadon af375987ee chore: check library before updating watch status 2025-05-28 23:29:33 +02:00
jadon ee683d5737 feat: set e# and logging 2025-05-28 23:25:17 +02:00
12 changed files with 129 additions and 35 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>1.4.1.0</Version>
<AssemblyVersion>1.4.1.0</AssemblyVersion>
<FileVersion>1.4.1.0</FileVersion>
<Version>1.4.2.0</Version>
<AssemblyVersion>1.4.2.0</AssemblyVersion>
<FileVersion>1.4.2.0</FileVersion>
</PropertyGroup>
</Project>
@@ -0,0 +1,17 @@
namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration;
/// <summary>
/// The NumberingScheme.
/// </summary>
public enum NumberingScheme
{
/// <summary>
/// Default (no numbering).
/// </summary>
Default,
/// <summary>
/// YYYYMMDD (e.g. 20250804 for August 4th, 2025).
/// </summary>
YYYYMMDD,
}
@@ -186,6 +186,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration
/// </summary>
public int TAJFPlaylistsSyncTaskInterval { get; set; }
/// <summary>
/// Gets or sets the preferred numbering scheme for episodes (index number) in Jellyfin.
/// </summary>
public NumberingScheme EpisodeNumberingScheme { get; set; } = NumberingScheme.Default;
/// <summary>
/// Gets the playback progress owners Jellyfin usernames to synchronize data from TubeArchivist.
/// </summary>
@@ -41,6 +41,18 @@
<div class="fieldDescription">This is the maximum length of the descriptions showed in series
and episodes</div>
</div>
<div class="selectContainer">
<label class="selectLabel" for="EpisodeNumberingScheme">Episode numbering scheme</label>
<select is="emby-select" id="EpisodeNumberingScheme" name="EpisodeNumberingScheme" label="Description markup" class="emby-select-withcolor emby-select">
<option value="Default">Default (no specified numbering)</option>
<option value="YYYYMMDD">YYYYMMDD (e.g. 20250804 for August 4th, 2025)</option>
</select>
<div class="selectArrowContainer">
<span class="selectArrow material-icons keyboard_arrow_down" aria-hidden="true"></span>
</div>
<div class="fieldDescription">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.
</div>
<div>
<h2>Synchronization</h2>
</div>
@@ -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;
+20 -14
View File
@@ -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;
@@ -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;
}
@@ -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);
@@ -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,
@@ -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
/// <returns>A string with \n replaced by br tags.</returns>
public static string FormatDescription(string description)
{
if (description == null)
{
return string.Empty;
}
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", "<br>", System.StringComparison.CurrentCulture);
}
description = description.Replace("\n", "<br>", System.StringComparison.CurrentCulture);
return description;
}
+40 -11
View File
@@ -77,24 +77,53 @@ _NOTE: If you are using Docker containers, it is important to mount the TubeArch
5. Save and come back to Home, you will see the newly added library. Jellyfin will have executed the metadata fetching for you after the collection creation and then you will see the metadata and the images of channels and videos
## Playback synchronization
<p>Starting from v1.3.1 this plugin offers playback progress and watched status bidirectional synchronization, but you can choose to enable only a one way synchronization (Jellyfin->TubeArchivist or TubeArchivist->Jellyfin) too.</p>
## Synchronization
This plugin has different bidirectional sycnhronization features, that can be configured in the specific section in the plugin configuration page:
![Synchronization settings](https://github.com/user-attachments/assets/b0bb556b-fce3-4a3e-bc6c-b0a2b482cedc)
### Jellyfin->TubeArchivist synchronization
### Playback synchronization
<p>Starting from v1.3.1 this plugin offers playback progress and watched status bidirectional synchronization, but you can choose to enable only a one way synchronization (Jellyfin -> TubeArchivist or TubeArchivist -> Jellyfin) too.</p>
#### Jellyfin -> TubeArchivist playback synchronization
<p>This kind of synchronization is done listening for progress and watched status changes while playing the videos for the specified users.<br>Furthermore, there is a task that runs at Jellyfin startup to synchronize the whole library.</p>
<p>In the plugin configuration you will find these settings:</p>
![JF->TA playback synchronization settings](https://github.com/user-attachments/assets/dc6be82f-e685-4896-a502-317681c47fc7)
<p>In the text field you can specify one Jellyfin username to synchronize data of to TubeArchivist.</p>
### TubeArchivist->Jellyfin synchronization
#### TubeArchivist -> Jellyfin playback synchronization
<p>This kind of synchronization is done using a Jellyfin scheduled task that regularly synchronizes data from TubeArchivist API to Jellyfin.</p>
<p>In the plugin configuration you will find these settings:</p>
<p>In the text field you can specify one or more Jellyfin usernames to update data for.</p>
![TA->JF playback synchronization settings](https://github.com/user-attachments/assets/1b4c33af-834f-45b3-9057-71830e7c8b4f)
<p>In the first text field you can specify one or more Jellyfin usernames to update data for.<br>
In the second field you can specify the interval in seconds the task should run at, so that you can choose according to your system requirements. The lower is the interval the higher will be the resources consuption on your system.</p>
### Playlists synchronization
<p>Starting from v.1.4.1 this plugin offers playlists bidirectional synchronization, but you can choose to enable only a one way synchronization (Jellyfin -> TubeArchivist or TubeArchivist -> Jellyfin) too.</p>
#### Jellyfin -> TubeArchivist playlists synchronization
<p>There is a task that retrieves playlists and recreates them on TubeArchivist with the videos in the same order. Please note that playlists can also have videos not beloging from TubeArchivist, they will be simply ignored, so you won't find them on TubeArchivist playlist.</p>
<p>It is present also a setting to automatically delete playlists from TubeArchivist when they are no more available on Jellyfin.</p>
#### TubeArchivist -> Jellyfin playlists synchronization
<p>There is a task that retrieves playlists from TubeArchivist and recreates them on Jellyfin with videos in the same order.</p>
<p>It is present, also in this case, a setting to automatically delete playlists from Jellyfin when they are no more present on TubeArchivist, but beware that the will be deleted also if they contain videos not beloning to TubeArchivist.</p>
> [!CAUTION]
> Pay attention when you enable the automatic deletion options, be sure that is your wanted behavior, especially when playlists contain also other videos not belonging from TubeArchivist, playlists removed won't be available again, there's no undo!
## Tasks intervals
<p>Since many of the feature are implemented as background tasks periodically executing, in the `Tasks intervals` section you will find the settings to adjust this period in seconds.<br>
Keep in mind that Jellyfin lowest accepted period is of 1 minute (60 seconds) and the lower is the interval the higher will be the resources consuption on your system.</p>
<p>Here are the configurable intervals:</p>
![Tasks intervals settings](https://github.com/user-attachments/assets/19db6b83-6715-477d-8ce7-b78526e87ba9)
## Episode numbering
<p>There are different ways to number the episodes as they are configured in Jellyfin.<br>
This changes the number after E in S--E-- (for example S2024E100 for episode number 100 of season 2024).</p>
![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
+2 -2
View File
@@ -2,7 +2,7 @@
name: "TubeArchivistMetadata"
guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715"
imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png
version: "1.4.1.0"
version: "1.4.2.0"
targetAbi: "10.11.0.0"
framework: "net9.0"
overview: "Metadata for your TubeArchivist library on Jellyfin"
@@ -14,4 +14,4 @@ owner: "DarkFighterLuke"
artifacts:
- "Jellyfin.Plugin.TubeArchivistMetadata.dll"
changelog: >
Introduce bidirectional playlists synchronization
Introduce episode numbering alternative schema
+11 -3
View File
@@ -8,13 +8,21 @@
"overview": "Metadata for your TubeArchivist library on Jellyfin",
"imageUrl": "https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png",
"versions": [
{
"version": "1.4.1.0",
"changelog": "Introduce bidirectional playlists synchronization\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.4.1/tubearchivistmetadata_1.4.1.0.zip",
"checksum": "2dc0b9140472a4f783f093a7ee425493",
"timestamp": "2025-10-22T07:00:27Z"
},
{
"version": "1.4.0.0",
"changelog": "Align compatibility with Jellyfin 10.11.x\n",
"changelog": "Introduce bidirectional playlists synchronization\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.4.0/tubearchivistmetadata_1.4.0.0.zip",
"checksum": "7afb9abd4361ee991c1b3eae2a8d4cec",
"timestamp": "2025-10-21T21:34:51Z"
"checksum": "4b81ab60022b532a64bb5eeb7be67dcc",
"timestamp": "2025-10-22T06:57:53Z"
},
{
"version": "1.3.7.0",