chore: merge upstream master and fix conflicts
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using J2N.Collections.Generic.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Task to sync TubeArchivist playlists to Jellyfin.
|
||||
/// </summary>
|
||||
public class JFToTubeArchivistPlaylistsSyncTask : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<Plugin> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IPlaylistManager _playlistManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JFToTubeArchivistPlaylistsSyncTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="userManager">User manager.</param>
|
||||
/// <param name="playlistManager">Playlists manager.</param>
|
||||
public JFToTubeArchivistPlaylistsSyncTask(ILogger<Plugin> logger, ILibraryManager libraryManager, IUserManager userManager, IPlaylistManager playlistManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_userManager = userManager;
|
||||
_playlistManager = playlistManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "JFToTubeArchivistPlaylistsSyncTask";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description => "This tasks syncs Jellyfin playlists to TubeArchivist";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Category => "TubeArchivistMetadata";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Key => "JFToTubeArchivistPlaylistsSyncTask";
|
||||
|
||||
private Dictionary<Guid, List<BaseItem>> GetAllVideos(IEnumerable<MediaBrowser.Controller.Playlists.Playlist> playlists, User user)
|
||||
{
|
||||
var items = new Dictionary<Guid, List<BaseItem>>();
|
||||
foreach (var playlist in playlists)
|
||||
{
|
||||
items[playlist.Id] = new List<BaseItem>(playlist.GetChildren(user, true, new InternalItemsQuery()));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private int CountTotalVideos(Dictionary<Guid, List<BaseItem>> playlistsItems)
|
||||
{
|
||||
var totalVideosCount = 0;
|
||||
foreach (var playlistId in playlistsItems.Keys)
|
||||
{
|
||||
totalVideosCount += playlistsItems[playlistId].Count;
|
||||
}
|
||||
|
||||
return totalVideosCount;
|
||||
}
|
||||
|
||||
private string GetPlaylistUpdatedName(string playlistName, string newId)
|
||||
{
|
||||
var index = playlistName.LastIndexOf(" (", StringComparison.CurrentCulture);
|
||||
if (index < 0)
|
||||
{
|
||||
return $"{playlistName} ({newId})";
|
||||
}
|
||||
else
|
||||
{
|
||||
var authorAndName = playlistName.Substring(0, index);
|
||||
return $"{authorAndName} ({newId})";
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveElementToPosition(Collection<PlaylistEntry> playlistEntries, int oldPosition, int newPosition)
|
||||
{
|
||||
var oldItem = playlistEntries[oldPosition];
|
||||
playlistEntries.RemoveAt(oldPosition);
|
||||
playlistEntries.Insert(newPosition, oldItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
progress.Report(0);
|
||||
if (Plugin.Instance!.Configuration.JFTAPlaylistsSync)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
_logger.LogInformation("Starting Jellyfin->TubeArchivist playlists synchronization.");
|
||||
var taApi = TubeArchivistApi.GetInstance();
|
||||
var taPlaylists = await taApi.GetPlaylists().ConfigureAwait(true);
|
||||
|
||||
var jfUsername = Plugin.Instance!.Configuration.JFUsernameFrom;
|
||||
|
||||
var user = _userManager.GetUserByName(jfUsername);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogInformation("{Message}", $"Jellyfin user with username {jfUsername} not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var userPlaylists = _playlistManager.GetPlaylists(user.Id).ToList();
|
||||
var jfItemsToAnalyze = GetAllVideos(userPlaylists, user);
|
||||
var totalVideosCount = CountTotalVideos(jfItemsToAnalyze);
|
||||
var processedVideosCount = 0;
|
||||
|
||||
_logger.LogInformation("Found a total of {PlaylistsCount} playlists to analyze with a total of {VideosCount} videos", userPlaylists.Count, totalVideosCount);
|
||||
if (taPlaylists != null && Plugin.Instance!.Configuration.JFTAPlaylistsDelete)
|
||||
{
|
||||
var taPlaylistsToDelete = taPlaylists.Where(tp => !userPlaylists.Select(up => Utils.GetTAPlaylistIdFromName(up.Name)).Contains(tp.Id));
|
||||
foreach (var taPlaylistToDelete in taPlaylistsToDelete)
|
||||
{
|
||||
_logger.LogInformation("Deleting TubeArchivist playlist {PlaylistName} ({PlaylistId})", taPlaylistToDelete.Name, taPlaylistToDelete.Id);
|
||||
await taApi.DeletePlaylist(taPlaylistToDelete.Id).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var jfPlaylist in userPlaylists)
|
||||
{
|
||||
_logger.LogInformation("Analyzing playlist {PlaylistName}...", jfPlaylist.Name);
|
||||
var taPlaylistId = Utils.GetTAPlaylistIdFromName(jfPlaylist.Name);
|
||||
if (taPlaylistId == null)
|
||||
{
|
||||
_logger.LogDebug("The playlist {PlaylistName} was not a TA playlist: could not find TA playlist id at the end", jfPlaylist.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to find a TA playlist with matching id
|
||||
// N.B.: only videos with TubeArchivist provider can be synced to TA, if there is a non matching video log a warn
|
||||
var taPlaylist = taPlaylists?.Where(tp => tp.Id == taPlaylistId).FirstOrDefault();
|
||||
|
||||
if (taPlaylist != null)
|
||||
{
|
||||
// If it exists and is custom add new videos and update videos order
|
||||
if (taPlaylist.Type == PlaylistType.Custom)
|
||||
{
|
||||
var jfItems = jfItemsToAnalyze[jfPlaylist.Id];
|
||||
_logger.LogInformation("Found {PlaylistVideosCount} videos in playlist {PlaylistName}", jfItems.Count, jfPlaylist.Name);
|
||||
|
||||
var itemsToProcess = new List<PlaylistItemAction>();
|
||||
|
||||
// Add videos to move/add to the TA playlist
|
||||
for (var i = 0; i < jfItems.Count; i++)
|
||||
{
|
||||
if (!jfItems[i].ProviderIds.ContainsKey(Constants.ProviderName))
|
||||
{
|
||||
_logger.LogError("Could not sync {JFItem} video from playlist {JFPlaylist} because it doesn't belong to TubeArchivist", jfItems[i].Name, jfPlaylist.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var position = taPlaylist.Entries.FindIndex(e => e.YoutubeId == jfItems[i].ProviderIds[Constants.ProviderName]);
|
||||
PlaylistItemAction action;
|
||||
if (position < 0)
|
||||
{
|
||||
_logger.LogDebug("Video {VideoName} from playlist {PlaylistName} not found in TA playlist", jfItems[i].Name, jfPlaylist.Name);
|
||||
var positionsToMove = i - position;
|
||||
action = new PlaylistItemAction(i, jfItems[i].ProviderIds[Constants.ProviderName], CustomPlaylistAction.Create, positionsToMove, jfItems[i].Name);
|
||||
taPlaylist.Entries.Insert(i, new PlaylistEntry(jfItems[i].ProviderIds[Constants.ProviderName], jfItems[i].Name, jfItems[i].Studios[0], i, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Found video {VideoName} at position {JFPosition} in playlist {PlaylistName} at position {TAPosition} in TA playlist", jfItems[i].Name, i, jfPlaylist.Name, position);
|
||||
if (i == 0)
|
||||
{
|
||||
action = new PlaylistItemAction(i, jfItems[i].ProviderIds[Constants.ProviderName], CustomPlaylistAction.Top, jfItems[i].Name);
|
||||
}
|
||||
else if (i == jfItems.Count)
|
||||
{
|
||||
action = new PlaylistItemAction(i, jfItems[i].ProviderIds[Constants.ProviderName], CustomPlaylistAction.Bottom, jfItems[i].Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
var positionsToMove = i - position;
|
||||
action = new PlaylistItemAction(i, jfItems[i].ProviderIds[Constants.ProviderName], positionsToMove > 0 ? CustomPlaylistAction.Up : CustomPlaylistAction.Down, Math.Abs(positionsToMove), jfItems[i].Name);
|
||||
}
|
||||
|
||||
var temp = taPlaylist.Entries[position];
|
||||
taPlaylist.Entries.RemoveAt(position);
|
||||
taPlaylist.Entries.Insert(i, temp);
|
||||
}
|
||||
|
||||
itemsToProcess.Add(action);
|
||||
}
|
||||
|
||||
// Add videos to delete from the TA playlist
|
||||
var itemsToDelete = taPlaylist.Entries
|
||||
.Where(e => !itemsToProcess
|
||||
.Select(i => i.YoutubeId)
|
||||
.Contains(e.YoutubeId))
|
||||
.ToList();
|
||||
foreach (var item in itemsToDelete)
|
||||
{
|
||||
_logger.LogDebug("Video {VideoName} not found in JF playlist, marking for deletion", item.Title);
|
||||
var ret = taPlaylist.Entries.Remove(item);
|
||||
}
|
||||
|
||||
itemsToProcess.AddRange(itemsToDelete.Select(e => new PlaylistItemAction(e.YoutubeId, e.Title)));
|
||||
|
||||
foreach (var item in itemsToProcess)
|
||||
{
|
||||
_logger.LogDebug("Analyzing video {VideoName} from playlist {PlaylistName} at position {Position}", item.Name, jfPlaylist.Name, item.Index);
|
||||
switch (item.Action)
|
||||
{
|
||||
case CustomPlaylistAction.Create:
|
||||
var success = await AddVideoToTAPlaylist(taApi, jfPlaylist, taPlaylistId, item).ConfigureAwait(true);
|
||||
if (!success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CustomPlaylistAction.Top:
|
||||
case CustomPlaylistAction.Bottom:
|
||||
_logger.LogDebug("Moving the video {VideoName} to the {Side} of the playlist {PlaylistName}", item.Name, item.Action, jfPlaylist.Name);
|
||||
await MoveOrDeleteVideo(taApi, jfPlaylist, taPlaylistId, item).ConfigureAwait(true);
|
||||
break;
|
||||
|
||||
case CustomPlaylistAction.Remove:
|
||||
_logger.LogDebug("Removing the video {VideoName} in playlist {PlaylistName}", item.Name, jfPlaylist.Name);
|
||||
await MoveOrDeleteVideo(taApi, jfPlaylist, taPlaylistId, item).ConfigureAwait(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.LogDebug("Moving the video {VideoName} {Direction} in playlist {PlaylistName}", item.Name, item.Action, jfPlaylist.Name);
|
||||
await MoveOrDeleteVideo(taApi, jfPlaylist, taPlaylistId, item).ConfigureAwait(true);
|
||||
break;
|
||||
}
|
||||
|
||||
processedVideosCount++;
|
||||
progress.Report(processedVideosCount * 100 / totalVideosCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Could not sync modifications on a YouTube downloaded TA playlist: {TAPlaylist}", $"{taPlaylist.Name} - {taPlaylist.Channel} ({taPlaylist.Id})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it doesn't, create a new custom playlist and add videos
|
||||
_logger.LogInformation("Playlist {PlaylistName} was not found on TubeArchivist. Creating a new playlist...", jfPlaylist.Name);
|
||||
|
||||
var playlistName = Utils.GetTAPlaylistNameFromName(jfPlaylist.Name);
|
||||
if (string.IsNullOrEmpty(playlistName))
|
||||
{
|
||||
playlistName = jfPlaylist.Name;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Creating a new TubeArchivist playlist with name: {PlaylistName}", playlistName);
|
||||
|
||||
var creationRequest = new CustomPlaylistCreation(playlistName);
|
||||
var createdTAPlaylist = await taApi.CreateCustomPlaylist(creationRequest).ConfigureAwait(true);
|
||||
if (createdTAPlaylist == null)
|
||||
{
|
||||
_logger.LogError("Failed to create the playlist {JFPlaylist}", playlistName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the JF playlist name with the new TA playlist id
|
||||
var newPlaylistName = GetPlaylistUpdatedName(jfPlaylist.Name, createdTAPlaylist.Id);
|
||||
var updateRequest = new PlaylistUpdateRequest
|
||||
{
|
||||
Id = jfPlaylist.Id,
|
||||
Name = newPlaylistName,
|
||||
UserId = user.Id
|
||||
};
|
||||
await _playlistManager.UpdatePlaylist(updateRequest).ConfigureAwait(true);
|
||||
_logger.LogDebug("Updated the playlist name from {NewPlaylistName} to {NewPlaylistName}", playlistName, newPlaylistName);
|
||||
|
||||
foreach (var jfItem in jfPlaylist.GetItemList(new InternalItemsQuery()))
|
||||
{
|
||||
_logger.LogDebug("Adding the video {VideoName} to the playlist {PlaylistName}", jfItem.Name, jfPlaylist.Name);
|
||||
var action = new CustomPlaylistEntryAction(CustomPlaylistAction.Create, jfItem.ProviderIds[Constants.ProviderName]);
|
||||
var response = await taApi.CustomPlaylistEntryAction(taPlaylistId, action).ConfigureAwait(true);
|
||||
if (response != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
_logger.LogError("Failed to add the video {JFItem} to playlist {JFPlaylist}", $"{jfItem.Name} ({jfItem.ProviderIds[Constants.ProviderName]})", jfPlaylist.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
processedVideosCount++;
|
||||
progress.Report(processedVideosCount * 100 / totalVideosCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Time elapsed: {Time}", DateTime.Now - start);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Jellyfin->TubeArchivist playlists synchronization is currently disabled.");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task MoveOrDeleteVideo(TubeArchivistApi taApi, MediaBrowser.Controller.Playlists.Playlist jfPlaylist, string taPlaylistId, PlaylistItemAction item)
|
||||
{
|
||||
var response = HttpStatusCode.OK;
|
||||
for (var i = 0; i < item.PositionsToMove; i++)
|
||||
{
|
||||
var action = new CustomPlaylistEntryAction(item.Action, item.YoutubeId);
|
||||
response = await taApi.CustomPlaylistEntryAction(taPlaylistId, action).ConfigureAwait(true);
|
||||
if (response != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
_logger.LogError("Failed to execute the operation {Operation} for the video {JFItem} in playlist {PlaylistName}", item.Action, $"{item.Name} ({item.YoutubeId})", jfPlaylist.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> AddVideoToTAPlaylist(TubeArchivistApi taApi, MediaBrowser.Controller.Playlists.Playlist jfPlaylist, string taPlaylistId, PlaylistItemAction item)
|
||||
{
|
||||
// TA playlist doesn't contain the JF video
|
||||
_logger.LogDebug("Adding the video {VideoName} to the playlist {PlaylistName}", item.Name, jfPlaylist.Name);
|
||||
var action = new CustomPlaylistEntryAction(CustomPlaylistAction.Create, item.YoutubeId);
|
||||
var response = await taApi.CustomPlaylistEntryAction(taPlaylistId, action).ConfigureAwait(true);
|
||||
if (response != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
_logger.LogError("Failed to add the video {JFItem} to playlist {JFPlaylist}", $"{item.Name} ({item.YoutubeId})", jfPlaylist.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since now we have the video added at the bottom of the TA playlist (previouse entries count + 1),
|
||||
// we need to move the video to the correct position
|
||||
var positionsToMove = item.PositionsToMove;
|
||||
action = new CustomPlaylistEntryAction(CustomPlaylistAction.Up, item.YoutubeId);
|
||||
if (item.PositionsToMove < 0)
|
||||
{
|
||||
positionsToMove = (int)Math.Abs((decimal)positionsToMove!);
|
||||
action = new CustomPlaylistEntryAction(CustomPlaylistAction.Up, item.YoutubeId);
|
||||
}
|
||||
|
||||
_logger.LogDebug("Moving the video {VideoName} of the playlist {PlaylistName} {Positions} up", item.Name, jfPlaylist.Name, positionsToMove);
|
||||
for (var i = 0; i < positionsToMove; i++)
|
||||
{
|
||||
response = await taApi.CustomPlaylistEntryAction(taPlaylistId, action).ConfigureAwait(true);
|
||||
if (response != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
_logger.LogError("Failed to move {Direction} the video {JFItem} in playlist {JFPlaylist}", item.Action, $"{item.Name} ({item.YoutubeId})", jfPlaylist.Name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return
|
||||
[
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfoType.IntervalTrigger,
|
||||
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private sealed class PlaylistItemAction
|
||||
{
|
||||
public PlaylistItemAction(
|
||||
int index,
|
||||
string youtubeId,
|
||||
CustomPlaylistAction action,
|
||||
int positionsToMove,
|
||||
string name)
|
||||
{
|
||||
Index = index;
|
||||
YoutubeId = youtubeId;
|
||||
Action = action;
|
||||
PositionsToMove = positionsToMove;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public PlaylistItemAction(
|
||||
int index,
|
||||
string youtubeId,
|
||||
CustomPlaylistAction action,
|
||||
string name)
|
||||
{
|
||||
Index = index;
|
||||
YoutubeId = youtubeId;
|
||||
Action = action;
|
||||
Name = name;
|
||||
PositionsToMove = 1;
|
||||
}
|
||||
|
||||
public PlaylistItemAction(
|
||||
string youtubeId,
|
||||
string name)
|
||||
{
|
||||
YoutubeId = youtubeId;
|
||||
Action = CustomPlaylistAction.Remove;
|
||||
Name = name;
|
||||
PositionsToMove = 1;
|
||||
}
|
||||
|
||||
public int? Index { get; set; }
|
||||
|
||||
public string YoutubeId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public CustomPlaylistAction Action { get; set; }
|
||||
|
||||
public int? PositionsToMove { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
/// <summary>
|
||||
/// Task to sync Jellyfin playback progresses to TubeArchivist.
|
||||
/// </summary>
|
||||
public class JFToTubearchivistProgressSyncTask : IScheduledTask
|
||||
public class JFToTubeArchivistProgressSyncTask : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<Plugin> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@@ -28,13 +28,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JFToTubearchivistProgressSyncTask"/> class.
|
||||
/// Initializes a new instance of the <see cref="JFToTubeArchivistProgressSyncTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="userManager">User manager.</param>
|
||||
/// <param name="userDataManager">User data manager.</param>
|
||||
public JFToTubearchivistProgressSyncTask(ILogger<Plugin> logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager)
|
||||
public JFToTubeArchivistProgressSyncTask(ILogger<Plugin> logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
@@ -58,7 +58,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
progress.Report(0);
|
||||
if (Plugin.Instance!.Configuration.JFTASync)
|
||||
if (Plugin.Instance!.Configuration.JFTAProgressSync)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
_logger.LogInformation("Starting Jellyfin->TubeArchivist playback progresses synchronization.");
|
||||
@@ -72,11 +72,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
return;
|
||||
}
|
||||
|
||||
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Name = Plugin.Instance?.Configuration.CollectionTitle,
|
||||
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
|
||||
}).FirstOrDefault();
|
||||
});
|
||||
|
||||
var collectionItem = items.Count > 0 ? items[0] : null;
|
||||
|
||||
if (collectionItem == null)
|
||||
{
|
||||
@@ -153,10 +155,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
var videoYTId = Utils.GetVideoNameFromPath(video.Path);
|
||||
_logger.LogDebug("Current video extracted YouTube id: {VideoYtId}", videoYTId);
|
||||
HttpStatusCode statusCode;
|
||||
var userItemData = _userDataManager.GetUserData(user, channel);
|
||||
|
||||
if (!isChannelCheckedForWatched && channel.IsPlayed(user))
|
||||
if (!isChannelCheckedForWatched && channel.IsPlayed(user, userItemData))
|
||||
{
|
||||
var isChannelPlayed = channel.IsPlayed(user);
|
||||
var isChannelPlayed = channel.IsPlayed(user, userItemData);
|
||||
statusCode = await taApi.SetWatchedStatus(channelYTId, isChannelPlayed).ConfigureAwait(true);
|
||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
@@ -172,7 +175,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
|
||||
if (!isChannelWatched)
|
||||
{
|
||||
var isVideoPlayed = video.IsPlayed(user);
|
||||
var isVideoPlayed = video.IsPlayed(user, userItemData);
|
||||
statusCode = await taApi.SetWatchedStatus(videoYTId, isVideoPlayed).ConfigureAwait(true);
|
||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
@@ -182,11 +185,14 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
_logger.LogDebug("{Message}", isVideoPlayed);
|
||||
if (!isVideoPlayed)
|
||||
{
|
||||
var playbackProgress = _userDataManager.GetUserData(user, video).PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
||||
statusCode = await taApi.SetProgress(videoYTId, playbackProgress).ConfigureAwait(true);
|
||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||
var playbackProgress = _userDataManager.GetUserData(user, video)?.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
||||
if (playbackProgress != null)
|
||||
{
|
||||
_logger.LogCritical("{Message}", $"POST /video/{videoYTId}/progress returned {statusCode} for video {video.Name} with progress {progress} seconds");
|
||||
statusCode = await taApi.SetProgress(videoYTId, playbackProgress.Value).ConfigureAwait(true);
|
||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
_logger.LogCritical("{Message}", $"POST /video/{videoYTId}/progress returned {statusCode} for video {video.Name} with progress {progress} seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +221,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
[
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerStartup,
|
||||
Type = TaskTriggerInfoType.StartupTrigger,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Task to sync TubeArchivist playlists to Jellyfin.
|
||||
/// </summary>
|
||||
public class TAToJellyfinPlaylistsSyncTask : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<Plugin> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IPlaylistManager _playlistManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TAToJellyfinPlaylistsSyncTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="userManager">User manager.</param>
|
||||
/// <param name="playlistManager">Playlists manager.</param>
|
||||
public TAToJellyfinPlaylistsSyncTask(ILogger<Plugin> logger, ILibraryManager libraryManager, IUserManager userManager, IPlaylistManager playlistManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_userManager = userManager;
|
||||
_playlistManager = playlistManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "TAToJellyfinPlaylistsSyncTask";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description => "This tasks syncs TubeArchivist playlists to Jellyfin";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Category => "TubeArchivistMetadata";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Key => "TAToJellyfinPlaylistsSyncTask";
|
||||
|
||||
private int CountTotalVideos(ISet<TubeArchivist.Playlist> taPlaylists)
|
||||
{
|
||||
var totalEntries = 0;
|
||||
foreach (var taPlaylist in taPlaylists)
|
||||
{
|
||||
totalEntries += taPlaylist.Entries.Count;
|
||||
}
|
||||
|
||||
return totalEntries;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
progress.Report(0);
|
||||
if (Plugin.Instance!.Configuration.TAJFPlaylistsSync)
|
||||
{
|
||||
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).ToList();
|
||||
|
||||
if (Plugin.Instance!.Configuration.TAJFPlaylistsDelete)
|
||||
{
|
||||
var jfPlaylistsToDelete = userPlaylists.Where(up => !taPlaylists.Select(tp => tp.Id).Contains(Utils.GetTAPlaylistIdFromName(up.Name)));
|
||||
foreach (var jfPlaylistToDelete in jfPlaylistsToDelete)
|
||||
{
|
||||
_logger.LogInformation("Deleting Jellyfin playlist {PlaylistName}", jfPlaylistToDelete.Name);
|
||||
_libraryManager.DeleteItem(jfPlaylistToDelete, new DeleteOptions());
|
||||
}
|
||||
}
|
||||
|
||||
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<Guid>();
|
||||
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 entries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
HasAnyProviderId = new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
Constants.ProviderName, taEntry.YoutubeId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var jfEntry = entries.Count > 0 ? entries[0] : null;
|
||||
|
||||
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 playlists synchronization is currently disabled.");
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return
|
||||
[
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfoType.IntervalTrigger,
|
||||
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
progress.Report(0);
|
||||
if (Plugin.Instance!.Configuration.TAJFSync)
|
||||
if (Plugin.Instance!.Configuration.TAJFProgressSync)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
_logger.LogInformation("Starting TubeArchivist->Jellyfin playback progresses synchronization.");
|
||||
@@ -72,11 +72,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
continue;
|
||||
}
|
||||
|
||||
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Name = Plugin.Instance?.Configuration.CollectionTitle,
|
||||
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
|
||||
}).FirstOrDefault();
|
||||
});
|
||||
|
||||
var collectionItem = items.Count > 0 ? items[0] : null;
|
||||
|
||||
if (collectionItem == null)
|
||||
{
|
||||
@@ -127,11 +129,13 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
continue;
|
||||
}
|
||||
|
||||
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Name = Plugin.Instance?.Configuration.CollectionTitle,
|
||||
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
|
||||
}).FirstOrDefault();
|
||||
});
|
||||
|
||||
var collectionItem = items.Count > 0 ? items[0] : null;
|
||||
|
||||
if (collectionItem == null)
|
||||
{
|
||||
@@ -185,8 +189,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
}
|
||||
|
||||
_userDataManager.SaveUserData(user, video, userUpdateData, UserDataSaveReason.UpdateUserData);
|
||||
_logger.LogDebug("{Message}", $"Playback progress for video {video.Name} set to {userItemData.PlaybackPositionTicks / TimeSpan.TicksPerSecond} seconds for user {jfUsername}.");
|
||||
_logger.LogDebug("{Message}", $"Watched status for video {video.Name} set to {userItemData.Played} seconds for user {jfUsername}.");
|
||||
_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);
|
||||
@@ -214,8 +218,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
||||
[
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerInterval,
|
||||
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFTaskInterval).Ticks
|
||||
Type = TaskTriggerInfoType.IntervalTrigger,
|
||||
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user