2024-11-18 20:59:20 +01:00
|
|
|
using System.Globalization;
|
2024-06-27 01:47:44 +02:00
|
|
|
using MediaBrowser.Model.Tasks;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using MediaBrowser.Controller;
|
|
|
|
using MediaBrowser.Controller.Library;
|
|
|
|
using MediaBrowser.Controller.Playlists;
|
|
|
|
using MediaBrowser.Controller.Providers;
|
|
|
|
using MediaBrowser.Model.IO;
|
2024-06-29 18:29:40 +02:00
|
|
|
using Jellyfin.Data.Entities;
|
|
|
|
using Jellyfin.Data.Enums;
|
2024-10-30 15:20:33 +01:00
|
|
|
using MediaBrowser.Model.Entities;
|
2024-06-29 18:29:40 +02:00
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
|
using MediaBrowser.Model.Playlists;
|
2024-06-27 01:47:44 +02:00
|
|
|
|
2024-10-24 23:53:21 +02:00
|
|
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
|
|
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
2024-11-07 00:48:56 +01:00
|
|
|
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Object;
|
|
|
|
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean;
|
2024-10-24 23:53:21 +02:00
|
|
|
|
|
|
|
|
2024-06-27 01:47:44 +02:00
|
|
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
|
|
|
public class GeneratePlaylist : IScheduledTask {
|
2024-10-24 23:53:21 +02:00
|
|
|
|
|
|
|
public static readonly BaseItemKind[] AvailableFilterItems = {
|
2024-10-25 23:37:47 +02:00
|
|
|
BaseItemKind.Audio,
|
|
|
|
BaseItemKind.MusicAlbum,
|
|
|
|
BaseItemKind.Playlist,
|
2024-10-24 23:53:21 +02:00
|
|
|
};
|
|
|
|
|
2024-06-27 01:47:44 +02:00
|
|
|
private readonly ILogger _logger;
|
2024-06-29 18:29:40 +02:00
|
|
|
private readonly ILibraryManager _libraryManager;
|
|
|
|
private readonly IUserManager _userManager;
|
2024-10-25 02:18:13 +02:00
|
|
|
private readonly IProviderManager _providerManager;
|
|
|
|
private readonly IFileSystem _fileSystem;
|
2024-10-24 23:53:21 +02:00
|
|
|
private readonly IPlaylistManager _playlistManager;
|
|
|
|
|
|
|
|
private readonly IStore _store;
|
2024-06-29 18:29:40 +02:00
|
|
|
|
2024-06-27 01:47:44 +02:00
|
|
|
public GeneratePlaylist(
|
2024-06-29 18:29:40 +02:00
|
|
|
ILogger<Plugin> logger,
|
|
|
|
ILibraryManager libraryManager,
|
2024-10-24 23:53:21 +02:00
|
|
|
IUserManager userManager,
|
2024-10-25 02:18:13 +02:00
|
|
|
IProviderManager providerManager,
|
|
|
|
IFileSystem fileSystem,
|
2024-10-24 23:53:21 +02:00
|
|
|
IPlaylistManager playlistManager,
|
|
|
|
IServerApplicationPaths serverApplicationPaths
|
2024-06-27 01:47:44 +02:00
|
|
|
) {
|
|
|
|
_logger = logger;
|
2024-06-29 18:29:40 +02:00
|
|
|
_libraryManager = libraryManager;
|
|
|
|
_userManager = userManager;
|
2024-10-25 02:18:13 +02:00
|
|
|
_providerManager = providerManager;
|
|
|
|
_fileSystem = fileSystem;
|
2024-10-24 23:53:21 +02:00
|
|
|
_playlistManager = playlistManager;
|
|
|
|
|
2024-10-25 02:18:13 +02:00
|
|
|
_store = new Store(new SmartPlaylistFileSystem(serverApplicationPaths));
|
2024-06-27 01:47:44 +02:00
|
|
|
}
|
2024-06-29 18:29:40 +02:00
|
|
|
|
2024-06-27 01:47:44 +02:00
|
|
|
public string Category => "Library";
|
|
|
|
public string Name => "(re)generate Smart Playlists";
|
|
|
|
public string Description => "Generate or regenerate all Smart Playlists";
|
|
|
|
public string Key => nameof(GeneratePlaylist);
|
2024-06-29 18:29:40 +02:00
|
|
|
|
2024-06-27 01:47:44 +02:00
|
|
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() {
|
|
|
|
return new[] {
|
|
|
|
new TaskTriggerInfo {
|
2024-10-24 23:53:21 +02:00
|
|
|
IntervalTicks = TimeSpan.FromHours(24).Ticks,
|
2024-06-27 01:47:44 +02:00
|
|
|
Type = TaskTriggerInfo.TriggerInterval,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2024-06-29 18:29:40 +02:00
|
|
|
|
|
|
|
private void GetUsers() {
|
|
|
|
foreach (var user in _userManager.Users) {
|
|
|
|
_logger.LogInformation("User {0}", user);
|
|
|
|
var query = new InternalItemsQuery(user) {
|
2024-10-24 23:53:21 +02:00
|
|
|
IncludeItemTypes = AvailableFilterItems,
|
2024-06-29 18:29:40 +02:00
|
|
|
Recursive = true,
|
|
|
|
};
|
|
|
|
foreach (BaseItem item in _libraryManager.GetItemsResult(query).Items) {
|
|
|
|
_logger.LogInformation("Item {0}", item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-30 19:33:01 +01:00
|
|
|
private PlaylistId CreateNewPlaylist(string name, UserId userId) {
|
2024-10-30 15:20:33 +01:00
|
|
|
_logger.LogDebug("Creating playlist '{0}'", name);
|
2024-10-24 23:53:21 +02:00
|
|
|
var req = new PlaylistCreationRequest {
|
2024-10-25 23:37:47 +02:00
|
|
|
Name = name,
|
|
|
|
UserId = userId,
|
2024-10-30 15:20:33 +01:00
|
|
|
Users = [new PlaylistUserPermissions(userId)],
|
|
|
|
Public = false,
|
2024-10-24 23:53:21 +02:00
|
|
|
};
|
2024-10-25 02:18:13 +02:00
|
|
|
var playlistGuid = Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id);
|
|
|
|
return playlistGuid;
|
2024-10-24 23:53:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private IEnumerable<Guid> FilterPlaylistItems(IEnumerable<BaseItem> items, User user, SmartPlaylistDto smartPlaylist) {
|
|
|
|
List<Guid> results = new List<Guid>();
|
|
|
|
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
|
2024-10-27 00:54:40 +02:00
|
|
|
Executor executor = new Executor(new DefaultEnvironment());
|
2024-10-26 03:49:52 +02:00
|
|
|
executor.environment.Set("user", new Lisp_Object(user));
|
2024-11-08 03:09:17 +01:00
|
|
|
if (Plugin.Instance is not null) {
|
|
|
|
executor.eval(Plugin.Instance.Configuration.InitialProgram);
|
|
|
|
} else {
|
|
|
|
throw new ApplicationException("Plugin Instance is not yet initialized");
|
|
|
|
}
|
2024-10-24 23:53:21 +02:00
|
|
|
foreach (var i in items) {
|
2024-10-26 03:49:52 +02:00
|
|
|
executor.environment.Set("item", new Lisp_Object(i));
|
2024-10-24 23:53:21 +02:00
|
|
|
var r = executor.eval(expression);
|
2024-10-25 23:37:47 +02:00
|
|
|
_logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString());
|
2024-11-07 00:48:56 +01:00
|
|
|
if ((r is not Lisp_Boolean r_bool) || (r_bool.Value())) {
|
2024-10-26 23:57:02 +02:00
|
|
|
_logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name);
|
|
|
|
results.Add(i.Id);
|
2024-10-24 23:53:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
private IEnumerable<BaseItem> GetAllUserMedia(User user) {
|
|
|
|
var req = new InternalItemsQuery(user) {
|
2024-10-25 23:37:47 +02:00
|
|
|
IncludeItemTypes = AvailableFilterItems,
|
2024-10-24 23:53:21 +02:00
|
|
|
Recursive = true,
|
|
|
|
};
|
|
|
|
return _libraryManager.GetItemsResult(req).Items;
|
|
|
|
}
|
|
|
|
|
2024-06-27 01:47:44 +02:00
|
|
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
|
2024-10-24 23:53:21 +02:00
|
|
|
_logger.LogInformation("Started regenerate Smart Playlists");
|
|
|
|
foreach (SmartPlaylistDto dto in await _store.GetAllSmartPlaylistsAsync()) {
|
2024-10-25 23:37:47 +02:00
|
|
|
var changedDto = false;
|
|
|
|
if (dto.Playlists.Length == 0) {
|
|
|
|
dto.Playlists = _userManager.UsersIds.Select(x => new SmartPlaylistLinkDto {
|
|
|
|
UserId = x,
|
|
|
|
PlaylistId = CreateNewPlaylist(dto.Name, x),
|
|
|
|
}).ToArray();
|
|
|
|
changedDto = true;
|
|
|
|
}
|
|
|
|
foreach (SmartPlaylistLinkDto playlistLink in dto.Playlists) {
|
|
|
|
if (playlistLink.PlaylistId == Guid.Empty) {
|
|
|
|
// not initialized
|
|
|
|
playlistLink.PlaylistId = CreateNewPlaylist(dto.Name, playlistLink.UserId);
|
|
|
|
changedDto = true;
|
|
|
|
} else if (_playlistManager.GetPlaylists(playlistLink.UserId).Where(x => x.Id == playlistLink.PlaylistId).ToArray().Length == 0) {
|
|
|
|
// somehow the corresponding playlist doesnt
|
|
|
|
// exist anymore, did the user delete it?
|
|
|
|
playlistLink.PlaylistId = CreateNewPlaylist(dto.Name, playlistLink.UserId);
|
|
|
|
changedDto = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (changedDto) {
|
|
|
|
_store.DeleteSmartPlaylist(dto); // delete in case the file was not the canonical one.
|
2024-10-24 23:53:21 +02:00
|
|
|
await _store.SaveSmartPlaylistAsync(dto);
|
|
|
|
}
|
2024-11-07 22:32:11 +01:00
|
|
|
var i = 0;
|
2024-10-25 23:37:47 +02:00
|
|
|
foreach (SmartPlaylistLinkDto playlistLink in dto.Playlists) {
|
|
|
|
User? user = _userManager.GetUserById(playlistLink.UserId);
|
|
|
|
if (user == null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var insertItems = FilterPlaylistItems(GetAllUserMedia(user), user, dto).ToArray();
|
|
|
|
var playlist = _playlistManager.GetPlaylists(playlistLink.UserId).Where(x => x.Id == playlistLink.PlaylistId).First();
|
|
|
|
await ClearPlaylist(playlist);
|
|
|
|
await _playlistManager.AddItemToPlaylistAsync(playlist.Id, insertItems, playlistLink.UserId);
|
2024-11-07 22:32:11 +01:00
|
|
|
i += 1;
|
|
|
|
progress.Report(((double)i)/dto.Playlists.Count());
|
2024-10-25 23:37:47 +02:00
|
|
|
}
|
2024-10-24 23:53:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-25 23:37:47 +02:00
|
|
|
private async Task ClearPlaylist(Playlist playlist) {
|
2024-10-25 02:18:13 +02:00
|
|
|
// fuck if I know
|
|
|
|
if (_libraryManager.GetItemById(playlist.Id) is not Playlist playlist_new) {
|
|
|
|
throw new ArgumentException("");
|
|
|
|
}
|
|
|
|
var existingItems = playlist_new.GetManageableItems().ToList();
|
2024-11-18 20:59:20 +01:00
|
|
|
await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems.Select(x => x.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture)));
|
2024-06-27 01:47:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|