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; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; using Jellyfin.Plugin.SmartPlaylist.Lisp; using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler; using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Object; using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean; namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks { public class GeneratePlaylist : IScheduledTask { public static readonly BaseItemKind[] AvailableFilterItems = { BaseItemKind.Audio }; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IPlaylistManager _playlistManager; private readonly IStore _store; public GeneratePlaylist( ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IPlaylistManager playlistManager, IServerApplicationPaths serverApplicationPaths ) { _logger = logger; _libraryManager = libraryManager; _userManager = userManager; _playlistManager = playlistManager; _store = new Store(new FileSystem(serverApplicationPaths)); } 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); public IEnumerable GetDefaultTriggers() { return new[] { new TaskTriggerInfo { IntervalTicks = TimeSpan.FromHours(24).Ticks, Type = TaskTriggerInfo.TriggerInterval, } }; } private void GetUsers() { foreach (var user in _userManager.Users) { _logger.LogInformation("User {0}", user); var query = new InternalItemsQuery(user) { IncludeItemTypes = AvailableFilterItems, Recursive = true, }; foreach (BaseItem item in _libraryManager.GetItemsResult(query).Items) { _logger.LogInformation("Item {0}", item); } } } private SmartPlaylistId CreateNewPlaylist(SmartPlaylistDto dto, User user) { var req = new PlaylistCreationRequest { Name = dto.Name, UserId = user.Id }; return Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id); } private IEnumerable FilterPlaylistItems(IEnumerable items, User user, SmartPlaylistDto smartPlaylist) { List results = new List(); Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); Executor executor = new Executor(); foreach (var i in items) { executor.environment["item"] = new Lisp_Object(i); var r = executor.eval(expression); _logger.LogInformation("Item {0} evaluated to {1}", i, r.ToString()); if (r is Lisp_Boolean r_bool) { if (r_bool.value) { results.Add(i.Id); } } else { _logger.LogInformation("Program did not return a boolean, returned {0}", r.ToString()); } } return results; } private IEnumerable GetAllUserMedia(User user) { var req = new InternalItemsQuery(user) { IncludeItemTypes = new[] {BaseItemKind.Audio}, Recursive = true, }; return _libraryManager.GetItemsResult(req).Items; } public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { _logger.LogInformation("Started regenerate Smart Playlists"); foreach (SmartPlaylistDto dto in await _store.GetAllSmartPlaylistsAsync()) { var user = _userManager.GetUserById(dto.User); List playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); if ((dto.Id == null) || !playlists.Any()) { _logger.LogInformation("Generating new smart playlist (dto.Id = {0}, playlists.Any() = {1})", dto.Id, playlists.Any()); _store.DeleteSmartPlaylist(dto.Id); dto.Id = CreateNewPlaylist(dto, user); await _store.SaveSmartPlaylistAsync(dto); playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); } var insertItems = FilterPlaylistItems(GetAllUserMedia(user), user, dto); Playlist playlist = playlists.First(); await ClearPlaylist(dto, playlist, user); await _playlistManager.AddItemToPlaylistAsync(playlist.Id, insertItems.ToArray(), user.Id); } } private async Task ClearPlaylist(SmartPlaylistDto smartPlaylist, Playlist playlist, User user) { var req = new InternalItemsQuery(user) { IncludeItemTypes = AvailableFilterItems, Recursive = true }; var existingItems = playlist.GetChildren(user, false, req).Select(x => x.Id.ToString()).ToList(); await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems); } } }