Added support for avatars/list.txt to be served from a playlist. Added an option to use the physical file or the playlist, and a text box to name the playlist.

This commit is contained in:
IAmParadox27 committed 2025-08-20 01:03:50 +01:00
1 parent 767be18e47
commit 66ed5358ee
6 files changed
+137 -4

No files matched your search

@@ -11,5 +11,9 @@ namespace Jellyfin.Plugin.MediaBar.Configuration
public class PluginConfiguration : BasePluginConfiguration
{
public MediaBarState Enabled { get; set; } = MediaBarState.Enabled;
public bool UseAvatarsFile { get; set; } = true;
public string AvatarsPlaylist { get; set; } = string.Empty;
}
}
@@ -28,6 +28,24 @@
</select>
<span>Whether the media bar should appear or not.</span>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input type="checkbox" id="chkUseAvatarsFile" is="emby-checkbox" class="emby-checkbox" data-id="chkUseAvatarsFile" />
<span class="checkboxLabel">Use physical avatars file</span>
<span class="checkboxOutline">
<span class="material-icons checkboxIcon checkboxIcon-checked check" aria-hidden="true"></span>
<span class="material-icons checkboxIcon checkboxIcon-unchecked " aria-hidden="true"></span>
</span>
</label>
<div class="fieldDescription">If checked, the plugin will look for a list.txt file in the avatars directory at the root of jellyfin-web.</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="txtAvatarsPlaylist">Avatars Playlist</label>
<label class="inputLabel inputLabel-float inputLabelUnfocused" for="txtAvatarsPlaylist"></label>
<input id="txtAvatarsPlaylist" type="text" is="emby-input" class="emby-input" data-id="txtAvatarsPlaylist" />
<div class="fieldDescription">If populated, and "Use physical avatars file" is disabled, this is the name of the playlist which is used to populate the media bar.<br />
If empty, the media bar will randomly populate.</div>
</div>
</div>
<br />
<button id="saveConfig" is="emby-button" type="submit" class="raised button-submit block">
@@ -49,6 +67,8 @@
Dashboard.showLoadingMsg();
const config = {
Enabled: document.querySelector("[data-id=enabledSelect]").value,
UseAvatarsFile: document.querySelector("[data-id=chkUseAvatarsFile]").checked,
AvatarsPlaylist: document.querySelector("[data-id=txtAvatarsPlaylist]").value,
};
window.ApiClient.updatePluginConfiguration(MediaBarConfig.pluginId, config)
@@ -65,6 +85,8 @@
window.ApiClient.getPluginConfiguration(MediaBarConfig.pluginId)
.then(function (config) {
document.querySelector("[data-id=enabledSelect]").value = config.Enabled;
document.querySelector("[data-id=chkUseAvatarsFile]").checked = config.UseAvatarsFile;
document.querySelector("[data-id=txtAvatarsPlaylist]").value = config.AvatarsPlaylist;
})
.catch(function (error) {
console.error(error);
@@ -1,8 +1,12 @@
using System.Reflection;
using System.Text.RegularExpressions;
using Jellyfin.Extensions;
using Jellyfin.Plugin.MediaBar.Helpers;
using Jellyfin.Plugin.MediaBar.Model;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using Microsoft.AspNetCore.Mvc;
using MediaBrowser.Controller.Entities.TV;
namespace Jellyfin.Plugin.MediaBar.Controllers
{
@@ -33,6 +37,19 @@ namespace Jellyfin.Plugin.MediaBar.Controllers
return Content(fileContents, contentType);
}
[HttpPost("Avatar/List")]
public ActionResult GetAvatarsList([FromBody] PatchRequestPayload payload, [FromServices] IPlaylistManager playlistManager, [FromServices] IUserManager userManager)
{
string? content = TransformationPatches.AvatarsList(payload, playlistManager, userManager);
if (content == null)
{
return NotFound();
}
return Content(content, "text/plain");
}
[HttpPost("Patch/IndexHtml")]
public ActionResult PatchIndexHtml([FromBody] PatchRequestPayload payload)
{
@@ -1,11 +1,78 @@
using System.Reflection;
using System.Text.RegularExpressions;
using Jellyfin.Extensions;
using Jellyfin.Plugin.MediaBar.Model;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
namespace Jellyfin.Plugin.MediaBar.Helpers
{
public static class TransformationPatches
{
public static string? AvatarsList(PatchRequestPayload payload, IPlaylistManager playlistManager, IUserManager userManager)
{
if (MediaBarPlugin.Instance.Configuration.UseAvatarsFile)
{
return payload.Contents;
}
IEnumerable<Guid> allUserIds = userManager.UsersIds;
Playlist? playlist = null;
Guid? userIdToUse = null;
foreach (Guid userId in allUserIds)
{
playlist = playlistManager.GetPlaylists(userId)
.FirstOrDefault(x => x.Name == MediaBarPlugin.Instance.Configuration.AvatarsPlaylist);
if (playlist != null)
{
userIdToUse = userId;
break;
}
}
if (playlist == null || userIdToUse == null)
{
return payload.Contents;
}
IEnumerable<Tuple<LinkedChild, BaseItem>> itemsRaw = playlist.GetManageableItems()
.Where(i => i.Item2.IsVisible(userManager.GetUserById(userIdToUse.Value)));
StringWriter stringWriter = new StringWriter();
stringWriter.WriteLine(MediaBarPlugin.Instance.Configuration.AvatarsPlaylist);
List<Guid> idsWritten = new List<Guid>();
foreach (Tuple<LinkedChild, BaseItem> item in itemsRaw)
{
BaseItem itemToUse = item.Item2;
if (item.Item2 is Episode episode)
{
itemToUse = episode.Series;
}
if (!idsWritten.Contains(itemToUse.Id))
{
idsWritten.Add(itemToUse.Id);
}
}
idsWritten.Shuffle();
foreach (Guid id in idsWritten)
{
stringWriter.WriteLine(id);
}
return stringWriter.ToString();
}
public static string IndexHtml(PatchRequestPayload payload)
{
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.index.html")!;
@@ -11,9 +11,12 @@ namespace Jellyfin.Plugin.MediaBar
public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448");
public override string Name => "Media Bar";
public static MediaBarPlugin Instance { get; private set; } = null!;
public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer)
{
Instance = this;
}
public IEnumerable<PluginPageInfo> GetPages()
@@ -7,6 +7,8 @@ using Jellyfin.Plugin.MediaBar.Helpers;
using Jellyfin.Plugin.MediaBar.Model;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -27,12 +29,17 @@ namespace Jellyfin.Plugin.MediaBar.Services
private readonly IServerApplicationHost m_serverApplicationHost;
private readonly ILogger<MediaBarPlugin> m_logger;
private readonly NamedPipeService m_namedPipeService;
private readonly IUserManager m_userManager;
private readonly IPlaylistManager m_playlistManager;
public StartupService(IServerApplicationHost serverApplicationHost, ILogger<MediaBarPlugin> logger, NamedPipeService namedPipeService)
public StartupService(IServerApplicationHost serverApplicationHost, ILogger<MediaBarPlugin> logger, NamedPipeService namedPipeService,
IUserManager userManager, IPlaylistManager playlistManager)
{
m_serverApplicationHost = serverApplicationHost;
m_logger = logger;
m_namedPipeService = namedPipeService;
m_userManager = userManager;
m_playlistManager = playlistManager;
}
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
@@ -71,6 +78,19 @@ namespace Jellyfin.Plugin.MediaBar.Services
payloads.Add(payload);
}
{
JObject payload = new JObject();
payload.Add("id", "8d374d6b-3c5b-464a-a2a2-96e92fa81345");
payload.Add("fileNamePattern", "avatars/list.txt");
payload.Add("transformationEndpoint", "/MediaBar/Avatar/List");
payload.Add("transformationPipe", "Jellyfin.Plugin.MediaBar.AvatarsList");
RegisterPipeEndpoint("Jellyfin.Plugin.MediaBar.AvatarsList", innerPayload =>
{
return TransformationPatches.AvatarsList(innerPayload, m_playlistManager, m_userManager) ?? "";
});
payloads.Add(payload);
}
string fileTransformationPipeName = "Jellyfin.Plugin.FileTransformation.NamedPipe";
MethodInfo? getPipePathMethod = typeof(PipeStream).GetMethod("GetPipePath", BindingFlags.Static | BindingFlags.NonPublic);
@@ -87,7 +107,7 @@ namespace Jellyfin.Plugin.MediaBar.Services
await pipeClient.WriteAsync(BitConverter.GetBytes((long)payloadBytes.Length));
await pipeClient.WriteAsync(payloadBytes, 0, payloadBytes.Length);
pipeClient.ReadByte();
await pipeClient.DisposeAsync();