feat!: Allow one smartplaylist to generate multiple user playlists.

This commit is contained in:
redxef 2024-10-25 23:37:47 +02:00
parent eb45ed5772
commit 6662c2e1e5
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
4 changed files with 140 additions and 38 deletions

View file

@ -32,7 +32,9 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
public class GeneratePlaylist : IScheduledTask { public class GeneratePlaylist : IScheduledTask {
public static readonly BaseItemKind[] AvailableFilterItems = { public static readonly BaseItemKind[] AvailableFilterItems = {
BaseItemKind.Audio BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.Playlist,
}; };
private readonly ILogger _logger; private readonly ILogger _logger;
@ -90,10 +92,10 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
} }
} }
private SmartPlaylistId CreateNewPlaylist(SmartPlaylistDto dto) { private SmartPlaylistId CreateNewPlaylist(string name, UserId userId) {
var req = new PlaylistCreationRequest { var req = new PlaylistCreationRequest {
Name = dto.Name, Name = name,
UserId = dto.User UserId = userId,
}; };
var playlistGuid = Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id); var playlistGuid = Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id);
return playlistGuid; return playlistGuid;
@ -107,7 +109,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
foreach (var i in items) { foreach (var i in items) {
executor.environment["item"] = new Lisp_Object(i); executor.environment["item"] = new Lisp_Object(i);
var r = executor.eval(expression); var r = executor.eval(expression);
_logger.LogDebug("Item {0} evaluated to {1}", i, r.ToString()); _logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString());
if (r is Lisp_Boolean r_bool) { if (r is Lisp_Boolean r_bool) {
if (r_bool.value) { if (r_bool.value) {
_logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name); _logger.LogDebug("Added '{0}' to Smart Playlist {1}", i, smartPlaylist.Name);
@ -122,7 +124,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
private IEnumerable<BaseItem> GetAllUserMedia(User user) { private IEnumerable<BaseItem> GetAllUserMedia(User user) {
var req = new InternalItemsQuery(user) { var req = new InternalItemsQuery(user) {
IncludeItemTypes = new[] {BaseItemKind.Audio}, IncludeItemTypes = AvailableFilterItems,
Recursive = true, Recursive = true,
}; };
return _libraryManager.GetItemsResult(req).Items; return _libraryManager.GetItemsResult(req).Items;
@ -131,23 +133,44 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
_logger.LogInformation("Started regenerate Smart Playlists"); _logger.LogInformation("Started regenerate Smart Playlists");
foreach (SmartPlaylistDto dto in await _store.GetAllSmartPlaylistsAsync()) { foreach (SmartPlaylistDto dto in await _store.GetAllSmartPlaylistsAsync()) {
var user = _userManager.GetUserById(dto.User); var changedDto = false;
List<Playlist> playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); if (dto.Playlists.Length == 0) {
if ((dto.Id == null) || !playlists.Any()) { dto.Playlists = _userManager.UsersIds.Select(x => new SmartPlaylistLinkDto {
_logger.LogInformation("Generating new smart playlist (dto.Id = {0}, playlists.Any() = {1})", dto.Id, playlists.Any()); UserId = x,
_store.DeleteSmartPlaylist(dto); PlaylistId = CreateNewPlaylist(dto.Name, x),
dto.Id = CreateNewPlaylist(dto); }).ToArray();
await _store.SaveSmartPlaylistAsync(dto); changedDto = true;
playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList(); }
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.
await _store.SaveSmartPlaylistAsync(dto);
}
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);
} }
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) { private async Task ClearPlaylist(Playlist playlist) {
// fuck if I know // fuck if I know
if (_libraryManager.GetItemById(playlist.Id) is not Playlist playlist_new) { if (_libraryManager.GetItemById(playlist.Id) is not Playlist playlist_new) {
throw new ArgumentException(""); throw new ArgumentException("");

View file

@ -1,28 +1,103 @@
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Jellyfin.Plugin.SmartPlaylist { namespace Jellyfin.Plugin.SmartPlaylist {
[Serializable]
public class SmartPlaylistDto {
private string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))";
public SmartPlaylistId Id { get; set; }
public string? Name { get; set; }
public UserId User { get; set; }
public string? Program { get; set; }
public string? Filename { get; set; }
public int MaxItems { get; set; } = -1;
public void Fill(string filename) { class GuidDeserializer {
if (Id == Guid.Empty) { public static Guid deserialize(string v) {
if (v.Length == 32) {
return Guid.ParseExact(v, "N");
}
return Guid.Parse(v);
}
}
[Serializable]
public class SmartPlaylistLinkDto : ISerializable {
public PlaylistId PlaylistId { get; set; }
public UserId UserId { get; set; }
public SmartPlaylistLinkDto() {
PlaylistId = Guid.Empty;
UserId = Guid.Empty;
}
protected SmartPlaylistLinkDto(SerializationInfo info, StreamingContext context) {
if (info.GetValue("PlaylistId", typeof(PlaylistId)) is PlaylistId _PlaylistId) {
PlaylistId = _PlaylistId;
} else {
PlaylistId = Guid.Empty;
}
if (info.GetValue("UserId", typeof(UserId)) is UserId _UserId) {
UserId = _UserId;
} else {
UserId = Guid.Empty;
}
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue("PlaylistId", PlaylistId);
info.AddValue("UserId", UserId);
}
}
[Serializable]
public class SmartPlaylistDto : ISerializable {
private static string DEFAULT_PROGRAM = "(begin (invoke item 'IsFavoriteOrLiked' (user)))";
public SmartPlaylistId Id { get; set; }
public SmartPlaylistLinkDto[] Playlists { get; set; }
public string Name { get; set; }
public string Program { get; set; }
public string? Filename { get; set; }
public bool Enabled { get; set; }
public SmartPlaylistDto() {
Id = Guid.NewGuid();
Playlists = [];
Name = Id.ToString();
Program = DEFAULT_PROGRAM;
Filename = null;
Enabled = true;
}
protected SmartPlaylistDto(SerializationInfo info, StreamingContext context) {
if (info.GetValue("Id", typeof(SmartPlaylistId)) is SmartPlaylistId _Id) {
Id = _Id;
} else {
Id = Guid.NewGuid(); Id = Guid.NewGuid();
} }
if (Name == null) { if (info.GetValue("Playlists", typeof(SmartPlaylistLinkDto[])) is SmartPlaylistLinkDto[] _Playlists) {
Playlists = _Playlists;
} else {
Playlists = [];
}
if (info.GetValue("Name", typeof(string)) is string _Name) {
Name = _Name;
} else {
Name = Id.ToString(); Name = Id.ToString();
} }
if (Program == null) { if (info.GetValue("Program", typeof(string)) is string _Program) {
Program = _Program;
} else {
Program = DEFAULT_PROGRAM; Program = DEFAULT_PROGRAM;
} }
if (Filename == null) { if (info.GetValue("Filename", typeof(string)) is string _Filename) {
Filename = filename; Filename = _Filename;
} } else {
Filename = null;
}
if (info.GetValue("Enabled", typeof(bool)) is bool _Enabled) {
Enabled = _Enabled;
} else {
Enabled = true;
}
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue("Id", Id);
info.AddValue("Playlists", Playlists);
info.AddValue("Name", Name);
info.AddValue("Program", Program);
info.AddValue("Filename", Filename);
info.AddValue("Enabled", Enabled);
} }
} }
} }

View file

@ -2,7 +2,7 @@ using System.Text.Json;
namespace Jellyfin.Plugin.SmartPlaylist { namespace Jellyfin.Plugin.SmartPlaylist {
public interface IStore { public interface IStore {
Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId); Task<SmartPlaylistDto> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId);
Task<SmartPlaylistDto[]> GetAllSmartPlaylistsAsync(); Task<SmartPlaylistDto[]> GetAllSmartPlaylistsAsync();
Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist); Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist);
void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist); void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist);
@ -13,13 +13,16 @@ namespace Jellyfin.Plugin.SmartPlaylist {
public Store(ISmartPlaylistFileSystem fileSystem) { public Store(ISmartPlaylistFileSystem fileSystem) {
_fileSystem = fileSystem; _fileSystem = fileSystem;
} }
private async Task<SmartPlaylistDto?> LoadPlaylistAsync(string filename) { private async Task<SmartPlaylistDto> LoadPlaylistAsync(string filename) {
await using var r = File.OpenRead(filename); await using var r = File.OpenRead(filename);
var dto = (await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false)); var dto = (await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false));
dto.Fill(filename); if (dto == null) {
throw new ApplicationException("");
}
dto.Filename = filename;
return dto; return dto;
} }
public async Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) { public async Task<SmartPlaylistDto> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) {
string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId); string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId);
return await LoadPlaylistAsync(filename).ConfigureAwait(false); return await LoadPlaylistAsync(filename).ConfigureAwait(false);
} }

View file

@ -1,4 +1,5 @@
global using System; global using System;
global using UserId = System.Guid; global using UserId = System.Guid;
global using PlaylistId = System.Guid;
global using SmartPlaylistId = System.Guid; global using SmartPlaylistId = System.Guid;