Compare commits

...

13 Commits

Author SHA1 Message Date
DarkFighterLuke a162ebb25d Add compatibility with Jellyfin 10.10 2024-11-04 21:40:29 +01:00
DarkFighterLuke 36eec27d9f Update .gitignore 2024-11-04 21:39:19 +01:00
DarkFighterLuke ce8aea629b Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2024-09-14 13:42:01 +00:00
DarkFighterLuke a8f821625b Update version 2024-09-14 15:41:00 +02:00
DarkFighterLuke 6cb2d89b95 Merge branch 'master' of github.com:tubearchivist/tubearchivist-jf-plugin 2024-09-14 15:36:40 +02:00
DarkFighterLuke 080f3856b7 Update changelog 2024-09-14 15:36:30 +02:00
DarkFighterLuke b436ae13a1 Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2024-09-14 13:33:10 +00:00
DarkFighterLuke dce122840c Adjust logs levels 2024-09-14 15:31:19 +02:00
DarkFighterLuke 9129bbda71 Report tasks progress
modified:   Jellyfin.Plugin.TubeArchivistMetadata/Tasks/JFToTubeArchivistProgressSyncTask.cs
	modified:   Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinProgressSyncTask.cs
2024-09-14 15:25:13 +02:00
DarkFighterLuke 698237a77a Merge pull request #18 from IceToast/master
feat: Add backdrop image to series
2024-09-13 15:11:57 +02:00
DarkFighterLuke 0bb2cb1108 Update README.md
Add playback progress synchronization info
2024-09-12 16:10:00 +02:00
DarkFighterLuke 286d43c840 Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2024-09-12 13:36:15 +00:00
IceToast 6675f7f762 feat: Add backdrop image to series 2024-07-15 21:47:06 +02:00
11 changed files with 184 additions and 23 deletions
+1
View File
@@ -3,3 +3,4 @@ obj/
.vs/
.idea/
artifacts
nuget.config
+3 -3
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>1.3.0.0</Version>
<AssemblyVersion>1.3.0.0</AssemblyVersion>
<FileVersion>1.3.0.0</FileVersion>
<Version>1.3.3.0</Version>
<AssemblyVersion>1.3.3.0</AssemblyVersion>
<FileVersion>1.3.3.0</FileVersion>
</PropertyGroup>
</Project>
@@ -118,14 +118,14 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration
{
get
{
_logger.LogInformation("JFUsernamesTo configured: {Message}", string.Join(", ", _jfUsernamesTo));
_logger.LogDebug("JFUsernamesTo configured: {Message}", string.Join(", ", _jfUsernamesTo));
return string.Join(", ", _jfUsernamesTo);
}
set
{
value.Replace(" ", string.Empty, StringComparison.CurrentCulture).Split(',').ToList().ForEach(u => _jfUsernamesTo.Add(u));
_logger.LogInformation("Set JFUsernamesTo to: {Message}", value);
_logger.LogDebug("Set JFUsernamesTo to: {Message}", value);
}
}
@@ -11,8 +11,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.Controller" Version="10.9.0-20240509061132" />
<PackageReference Include="Jellyfin.Model" Version="10.9.0-20240509061132" />
<PackageReference Include="Jellyfin.Controller" Version="10.10.0" />
<PackageReference Include="Jellyfin.Model" Version="10.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
@@ -164,7 +164,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
var statusCode = await TubeArchivistApi.GetInstance().SetProgress(videoId, progress).ConfigureAwait(true);
if (statusCode != System.Net.HttpStatusCode.OK)
{
Logger.LogInformation("{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");
}
}
}
@@ -176,7 +176,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
if (user != null && Configuration.GetJFUsernamesToArray().Contains(user!.Username))
{
var isPlayed = eventArgs.Item.IsPlayed(user);
Logger.LogInformation("User {UserId} changed watched status to {Status} for the item {ItemName}", eventArgs.UserId, isPlayed, eventArgs.Item.Name);
Logger.LogDebug("User {UserId} changed watched status to {Status} for the item {ItemName}", eventArgs.UserId, isPlayed, eventArgs.Item.Name);
string itemYTId;
if (eventArgs.Item is Series)
{
@@ -194,7 +194,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
var statusCode = await TubeArchivistApi.GetInstance().SetWatchedStatus(itemYTId, isPlayed).ConfigureAwait(true);
if (statusCode != System.Net.HttpStatusCode.OK)
{
Logger.LogInformation("POST /watched returned {StatusCode} for item {ItemName} ({VideoYTId}) with watched status {IsPlayed}", statusCode, eventArgs.Item.Name, itemYTId, isPlayed);
Logger.LogCritical("POST /watched returned {StatusCode} for item {ItemName} ({VideoYTId}) with watched status {IsPlayed}", statusCode, eventArgs.Item.Name, itemYTId, isPlayed);
}
}
}
@@ -85,6 +85,12 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
Type = ImageType.Banner,
Url = channel.BannerUrl
});
list.Add(new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Backdrop,
Url = channel.TvartUrl
});
}
return list;
@@ -56,11 +56,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
/// <inheritdoc/>
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
progress.Report(0);
if (Plugin.Instance!.Configuration.JFTASync)
{
var start = DateTime.Now;
_logger.LogInformation("Starting Jellyfin->TubeArchivist playback progresses synchronization.");
var taApi = TubeArchivistApi.GetInstance();
var videosCount = 0;
foreach (var jfUsername in Plugin.Instance!.Configuration.GetJFUsernamesToArray())
{
var user = _userManager.GetUserByName(jfUsername);
@@ -89,7 +91,60 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
IncludeItemTypes = new[] { BaseItemKind.Series }
});
_logger.LogInformation("Analyzing collection {Id} with name {Name}", collectionItem.Id, collectionItem.Name);
_logger.LogInformation("Found {Message} channels", channels.Count);
_logger.LogDebug("Found {Message} channels", channels.Count);
foreach (Series channel in channels)
{
var channelYTId = Utils.GetChannelNameFromPath(channel.Path);
var years = channel.GetChildren(user, false, new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Season }
});
_logger.LogDebug("Found {Years} years in channel {ChannelName}", years.Count, channel.Name);
foreach (Season year in years)
{
var videos = year.GetChildren(user, false, new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Episode }
});
_logger.LogDebug("Found {Videos} videos in year {YearName} of the channel {ChannelName}", videos.Count, year.Name, channel.Name);
videosCount += videos.Count;
}
}
}
}
_logger.LogInformation("Found a total of {VideosCount} videos", videosCount);
var processedVideosCount = 0;
foreach (var jfUsername in Plugin.Instance!.Configuration.GetJFUsernamesToArray())
{
var user = _userManager.GetUserByName(jfUsername);
if (user == null)
{
_logger.LogInformation("{Message}", $"Jellyfin user with username {jfUsername} not found");
continue;
}
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
{
Name = Plugin.Instance?.Configuration.CollectionTitle,
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
}).FirstOrDefault();
if (collectionItem == null)
{
var message = $"Collection '{Plugin.Instance?.Configuration.CollectionTitle}' not found.";
_logger.LogCritical("{Message}", message);
}
else
{
var collection = (CollectionFolder)collectionItem;
var channels = collection.GetChildren(user, false, new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Series }
});
foreach (Series channel in channels)
{
@@ -100,7 +155,6 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
{
IncludeItemTypes = new[] { BaseItemKind.Season }
});
_logger.LogInformation("Found {Years} years in channel {ChannelName}", years.Count, channel.Name);
foreach (Season year in years)
{
@@ -108,16 +162,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
{
IncludeItemTypes = new[] { BaseItemKind.Episode }
});
_logger.LogInformation("Found {Videos} videos in year {YearName} of the channel {ChannelName}", videos.Count, year.Name, channel.Name);
videosCount += videos.Count;
foreach (Episode video in videos)
{
var videoYTId = Utils.GetVideoNameFromPath(video.Path);
var playbackProgress = _userDataManager.GetUserData(user.Id, video).PlaybackPositionTicks / TimeSpan.TicksPerSecond;
var playbackProgress = _userDataManager.GetUserData(user, video).PlaybackPositionTicks / TimeSpan.TicksPerSecond;
var statusCode = await taApi.SetProgress(videoYTId, playbackProgress).ConfigureAwait(true);
if (statusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogInformation("{Message}", $"POST /video/{videoYTId}/progress returned {statusCode} for video {video.Name} with progress {progress} seconds");
_logger.LogCritical("{Message}", $"POST /video/{videoYTId}/progress returned {statusCode} for video {video.Name} with progress {progress} seconds");
}
if (!isChannelCheckedForWatched && channel.IsPlayed(user))
@@ -126,7 +180,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
statusCode = await taApi.SetWatchedStatus(channelYTId, isChannelPlayed).ConfigureAwait(true);
if (statusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogInformation("{Message}", $"POST /watched returned {statusCode} for channel {channel.Name} ({channelYTId}) with wacthed status {isChannelPlayed}");
_logger.LogCritical("{Message}", $"POST /watched returned {statusCode} for channel {channel.Name} ({channelYTId}) with wacthed status {isChannelPlayed}");
}
else
{
@@ -142,9 +196,12 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
statusCode = await taApi.SetWatchedStatus(videoYTId, isVideoPlayed).ConfigureAwait(true);
if (statusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogInformation("{Message}", $"POST /watched returned {statusCode} for video {video.Name} ({videoYTId}) with wacthed status {isVideoPlayed}");
_logger.LogCritical("{Message}", $"POST /watched returned {statusCode} for video {video.Name} ({videoYTId}) with wacthed status {isVideoPlayed}");
}
}
processedVideosCount++;
progress.Report(processedVideosCount * 100 / videosCount);
}
}
}
@@ -157,6 +214,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
{
_logger.LogInformation("Jellyfin->TubeArchivist playback synchronization is currently disabled.");
}
progress.Report(100);
}
/// <inheritdoc/>
@@ -56,11 +56,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
/// <inheritdoc/>
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
progress.Report(0);
if (Plugin.Instance!.Configuration.TAJFSync)
{
var start = DateTime.Now;
_logger.LogInformation("Starting TubeArchivist->Jellyfin playback progresses synchronization.");
var taApi = TubeArchivistApi.GetInstance();
var videosCount = 0;
foreach (var jfUsername in Plugin.Instance!.Configuration.GetJFUsernamesToArray())
{
var user = _userManager.GetUserByName(jfUsername);
@@ -89,7 +91,60 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
IncludeItemTypes = new[] { BaseItemKind.Series }
});
_logger.LogInformation("Analyzing collection {Id} with name {Name}", collectionItem.Id, collectionItem.Name);
_logger.LogInformation("Found {Message} channels", channels.Count);
_logger.LogDebug("Found {Message} channels", channels.Count);
foreach (Series channel in channels)
{
var years = channel.GetChildren(user, false, new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Season }
});
_logger.LogDebug("Found {Years} years in channel {ChannelName}", years.Count, channel.Name);
foreach (Season year in years)
{
var videos = year.GetChildren(user, false, new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Episode }
});
_logger.LogDebug("Found {Videos} videos in year {YearName} of the channel {ChannelName}", videos.Count, year.Name, channel.Name);
videosCount += videos.Count;
}
}
}
}
_logger.LogInformation("Found a total of {VideosCount} videos", videosCount);
var processedVideosCount = 0;
foreach (var jfUsername in Plugin.Instance!.Configuration.GetJFUsernamesToArray())
{
var user = _userManager.GetUserByName(jfUsername);
if (user == null)
{
_logger.LogInformation("{Message}", $"Jellyfin user with username {jfUsername} not found");
continue;
}
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
{
Name = Plugin.Instance?.Configuration.CollectionTitle,
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
}).FirstOrDefault();
if (collectionItem == null)
{
var message = $"Collection '{Plugin.Instance?.Configuration.CollectionTitle}' not found.";
_logger.LogCritical("{Message}", message);
}
else
{
var collection = (CollectionFolder)collectionItem;
var channels = collection.GetChildren(user, false, new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Series }
});
foreach (Series channel in channels)
{
@@ -97,7 +152,6 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
{
IncludeItemTypes = new[] { BaseItemKind.Season }
});
_logger.LogInformation("Found {Years} years in channel {ChannelName}", years.Count, channel.Name);
foreach (Season year in years)
{
@@ -105,7 +159,6 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
{
IncludeItemTypes = new[] { BaseItemKind.Episode }
});
_logger.LogInformation("Found {Videos} videos in year {YearName} of the channel {ChannelName}", videos.Count, year.Name, channel.Name);
foreach (Episode video in videos)
{
@@ -134,6 +187,9 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
_userDataManager.SaveUserData(user, video, userUpdateData, UserDataSaveReason.UpdateUserData);
_logger.LogInformation("{Message}", $"Playback progress for video {video.Name} set to {userItemData.PlaybackPositionTicks / TimeSpan.TicksPerSecond} seconds for user {jfUsername}.");
_logger.LogInformation("{Message}", $"Watched status for video {video.Name} set to {userItemData.Played} seconds for user {jfUsername}.");
processedVideosCount++;
progress.Report(processedVideosCount * 100 / videosCount);
}
}
}
@@ -147,6 +203,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
{
_logger.LogInformation("TubeArchivist->Jellyfin playback synchronization is currently disabled.");
}
progress.Report(100);
}
/// <inheritdoc/>
+21
View File
@@ -49,6 +49,7 @@ Once installed, you have to configure the following parameters in the plugin con
<li>TubeArchivist instance address</li>
<li>TubeArchivist API key</li>
<li>Overviews length (channels and videos descriptions)</li>
<li>Playback synchronization settings discussed in the <a href="#playback-synchronization">Playback synchronization</a> paragraph</li>
</ul>
![Plugin configuration](https://github.com/tubearchivist/tubearchivist-jf-plugin/assets/31162436/d34464ea-ddfb-44b3-9d3e-5d5974956c58)
@@ -65,6 +66,26 @@ _NOTE: If you are using Docker containers, it is important to mount the TubeArch
4. Scrolling down, uncheck all metadata and image providers except `TubeArchivist`. (You won't find TubeArchivist in seasons providers, so just disable everything there)
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>
### Jellyfin->TubeArchivist 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
<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>
![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>
## Build
1. To build this plugin you will need [.Net 8.x](https://dotnet.microsoft.com/download/dotnet/8.0).
+3 -3
View File
@@ -2,8 +2,8 @@
name: "TubeArchivistMetadata"
guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715"
imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png
version: "1.3.0.0"
targetAbi: "10.9.1.0"
version: "1.3.3.0"
targetAbi: "10.10.0.0"
framework: "net8.0"
overview: "Metadata for your TubeArchivist library on Jellyfin"
description: >
@@ -14,4 +14,4 @@ owner: "DarkFighterLuke"
artifacts:
- "Jellyfin.Plugin.TubeArchivistMetadata.dll"
changelog: >
Add support for Jellyfin 10.9.1
Add compatibility with Jellyfin 10.10
+16
View File
@@ -8,6 +8,22 @@
"overview": "Metadata for your TubeArchivist library on Jellyfin",
"imageUrl": "https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png",
"versions": [
{
"version": "1.3.2.0",
"changelog": "Add tasks progress status Adjust logs levels\n",
"targetAbi": "10.9.1.0",
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.3.2/tubearchivistmetadata_1.3.2.0.zip",
"checksum": "6162235807535a5c2a7570276798e69e",
"timestamp": "2024-09-14T13:41:58Z"
},
{
"version": "1.3.1.0",
"changelog": "Add support for Jellyfin 10.9.1\n",
"targetAbi": "10.9.1.0",
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.3.1/tubearchivistmetadata_1.3.1.0.zip",
"checksum": "b0b526bbd1702970108e3b9a8c3709de",
"timestamp": "2024-09-12T13:36:12Z"
},
{
"version": "1.3.0.0",
"changelog": "Add support for Jellyfin 10.9.1\n",