feat!: Allow one smartplaylist to generate multiple user playlists.
This commit is contained in:
parent
eb45ed5772
commit
6662c2e1e5
4 changed files with 140 additions and 38 deletions
|
@ -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("");
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue