diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs
index 3e3e95d..51d006b 100644
--- a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs
+++ b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
@@ -41,6 +42,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
+ /// Instance of the interface.
public Plugin(
IApplicationPaths applicationPaths,
IXmlSerializer xmlSerializer,
@@ -49,7 +51,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
ILibraryManager libraryManager,
ITaskManager taskManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IPlaylistManager playlistManager)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
@@ -68,6 +71,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
userDataManager.UserDataSaved += OnWatchedStatusChange;
var taToJellyfinProgressSyncTask = new TAToJellyfinProgressSyncTask(logger, libraryManager, userManager, userDataManager);
+ var taToJellyfinPlaylistsSyncTask = new TAToJellyfinPlaylistsSyncTask(logger, libraryManager, userManager, playlistManager);
var jfToTubearchivistProgressSyncTask = new JFToTubearchivistProgressSyncTask(logger, libraryManager, userManager, userDataManager);
var isTAJFTaskPresent = taskManager.ScheduledTasks.Any(t => t.Name.Equals(taToJellyfinProgressSyncTask.Name, StringComparison.Ordinal));
if (Instance!.Configuration.TAJFSync && !isTAJFTaskPresent)
@@ -75,6 +79,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
logger.LogInformation("Queueing task {TaskName}.", taToJellyfinProgressSyncTask.Name);
taskManager.AddTasks([taToJellyfinProgressSyncTask]);
taskManager.Execute();
+
+ logger.LogInformation("Queueing task {TaskName}.", taToJellyfinPlaylistsSyncTask.Name);
+ taskManager.AddTasks([taToJellyfinProgressSyncTask]);
+ taskManager.Execute();
}
var isJFTATaskPresent = taskManager.ScheduledTasks.Any(t => t.Name.Equals(jfToTubearchivistProgressSyncTask.Name, StringComparison.Ordinal));
diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs
new file mode 100644
index 0000000..17a86ed
--- /dev/null
+++ b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinPlaylistsSyncTask.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist;
+using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Playlists;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
+{
+ ///
+ /// Task to sync TubeArchivist playback progresses to Jellyfin.
+ ///
+ public class TAToJellyfinPlaylistsSyncTask : IScheduledTask
+ {
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IUserManager _userManager;
+ private readonly IPlaylistManager _playlistManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Logger.
+ /// Library manager.
+ /// User manager.
+ /// Playlists manager.
+ public TAToJellyfinPlaylistsSyncTask(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IPlaylistManager playlistManager)
+ {
+ _logger = logger;
+ _libraryManager = libraryManager;
+ _userManager = userManager;
+ _playlistManager = playlistManager;
+ }
+
+ ///
+ public string Name => "TAToJellyfinPlaylistsSyncTask";
+
+ ///
+ public string Description => "This tasks syncs TubeArchivist playlists to Jellyfin";
+
+ ///
+ public string Category => "TubeArchivistMetadata";
+
+ ///
+ public string Key => "TAToJellyfinPlaylistsSyncTask";
+
+ private int CountTotalVideos(ISet taPlaylists)
+ {
+ var totalEntries = 0;
+ foreach (var taPlaylist in taPlaylists)
+ {
+ totalEntries += taPlaylist.Entries.Count;
+ }
+
+ return totalEntries;
+ }
+
+ ///
+ public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
+ {
+ progress.Report(0);
+ if (Plugin.Instance!.Configuration.TAJFSync)
+ {
+ var start = DateTime.Now;
+ _logger.LogInformation("Starting TubeArchivist->Jellyfin playlists synchronization.");
+ var taApi = TubeArchivistApi.GetInstance();
+ _logger.LogInformation("Getting TubeArchivist playlists");
+ var taPlaylists = await taApi.GetPlaylists().ConfigureAwait(true);
+ _logger.LogInformation("Received playlists:\n{Playlists}", taPlaylists);
+ if (taPlaylists != null)
+ {
+ var totalVideosCount = CountTotalVideos(taPlaylists);
+ 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 userPlaylists = _playlistManager.GetPlaylists(user.Id);
+
+ foreach (var taPlaylist in taPlaylists)
+ {
+ var playlistName = taPlaylist.Type == PlaylistType.Regular ? $"{taPlaylist.Name} - {taPlaylist.Channel} ({taPlaylist.Id})" : $"{taPlaylist.Name} ({taPlaylist.Id})";
+ var userPlaylist = userPlaylists.Where(up => up.Name == playlistName).FirstOrDefault();
+
+ var jfEntryIds = new List();
+ var currentPlaylistVideos = 0;
+ foreach (var taEntry in taPlaylist.Entries)
+ {
+ currentPlaylistVideos++;
+ var taEntryStr = $"{taEntry.Uploader} - {taEntry.Title} ({taEntry.YoutubeId}) in playlist {playlistName}";
+ if (!taEntry.IsDownloaded)
+ {
+ _logger.LogInformation("The entry {TAEntry} was skipped because has not been downloaded by TubeArchivist", taEntryStr);
+ continue;
+ }
+
+ var jfEntry = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ HasAnyProviderId = new Dictionary()
+ {
+ {
+ Constants.ProviderName, taEntry.YoutubeId
+ }
+ }
+ }).FirstOrDefault();
+
+ if (jfEntry != null)
+ {
+ jfEntryIds.Add(jfEntry.Id);
+ }
+ else
+ {
+ _logger.LogWarning("The relative video for {TAEntry} was not found on Jellyfin", taEntryStr);
+ }
+ }
+
+ if (userPlaylist != null)
+ {
+ var updateRequest = new PlaylistUpdateRequest
+ {
+ Id = userPlaylist.Id,
+ UserId = user.Id,
+ Name = playlistName,
+ Ids = jfEntryIds
+ };
+
+ var result = _playlistManager.UpdatePlaylist(updateRequest);
+ _logger.LogInformation("Updated playlist {PlaylistName} with id {Id}", playlistName, result.Id);
+ }
+ else
+ {
+ var creationRequest = new PlaylistCreationRequest
+ {
+ Name = playlistName,
+ ItemIdList = jfEntryIds,
+ MediaType = MediaType.Video,
+ UserId = user.Id
+ };
+
+ var result = await _playlistManager.CreatePlaylist(creationRequest).ConfigureAwait(true);
+ _logger.LogInformation("Created playlist {PlaylistName} with id {Id}", playlistName, result.Id);
+ }
+
+ processedVideosCount += currentPlaylistVideos;
+ progress.Report(processedVideosCount * 100 / totalVideosCount);
+ }
+ }
+ }
+
+ _logger.LogInformation("Time elapsed: {Time}", DateTime.Now - start);
+ }
+ else
+ {
+ _logger.LogInformation("TubeArchivist->Jellyfin playback synchronization is currently disabled.");
+ }
+
+ progress.Report(100);
+ }
+
+ ///
+ public IEnumerable GetDefaultTriggers()
+ {
+ return
+ [
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFTaskInterval).Ticks
+ },
+ ];
+ }
+ }
+}