Files
blackBarRemover/JellyfinPlugin/Api/BlackBarService.cs
T
karol 5f8674fae4
Build and Release Plugin / release (push) Failing after 20s
Fix Client Mode execution and network interception logic
2026-09-17 16:38:23 +02:00

171 lines
6.3 KiB
C#

using System;
using System.Linq;
using System.Net.Mime;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace JellyfinPlugin.Api
{
[ApiController]
[Route("BlackBarRemover")]
public class BlackBarController : ControllerBase
{
private readonly ILibraryManager _libraryManager;
private readonly ILogger<BlackBarController> _logger;
public BlackBarController(ILibraryManager libraryManager, ILogger<BlackBarController> logger)
{
_libraryManager = libraryManager;
_logger = logger;
}
[HttpGet("CropByPath")]
[Produces("text/plain")]
public ContentResult GetCropByPath([FromQuery] string path)
{
if (string.IsNullOrEmpty(path)) return Content(string.Empty, "text/plain");
var mode = Plugin.Instance!.Configuration.PlaybackMode;
if (mode == "Client") return Content(string.Empty, "text/plain");
var item = _libraryManager.RootFolder.GetRecursiveChildren().FirstOrDefault(x => x.Path == path);
if (item != null && item.ProviderIds != null && item.ProviderIds.TryGetValue("BlackBarCrop", out string? cropData))
{
return Content(cropData ?? string.Empty, "text/plain");
}
return Content(string.Empty, "text/plain");
}
[HttpGet("CropById")]
[Produces("text/plain")]
public ContentResult GetCropById([FromQuery] string itemId)
{
if (string.IsNullOrEmpty(itemId)) return Content(string.Empty, "text/plain");
var mode = Plugin.Instance!.Configuration.PlaybackMode;
if (mode == "Server") return Content(string.Empty, "text/plain");
if (Guid.TryParse(itemId, out Guid id))
{
var item = _libraryManager.GetItemById(id);
if (item != null && item.ProviderIds != null && item.ProviderIds.TryGetValue("BlackBarCrop", out string? cropData))
{
return Content(cropData ?? string.Empty, "text/plain");
}
}
return Content(string.Empty, "text/plain");
}
[HttpGet("ClientScript.js")]
[Produces("application/javascript")]
public ContentResult GetClientScript()
{
string js = @"
(function() {
console.log('Black Bar Remover: Script loaded');
let currentItemId = null;
let interceptedItemId = null;
function applyCrop(cropData) {
const video = document.querySelector('video');
if (!video) return;
const parts = cropData.split(':');
if (parts.length === 4) {
const w = parseInt(parts[0]);
const h = parseInt(parts[1]);
const x = parseInt(parts[2]);
const y = parseInt(parts[3]);
const topPercent = (y / 1080) * 100;
const bottomPercent = ((1080 - (y + h)) / 1080) * 100;
const leftPercent = (x / 1920) * 100;
const rightPercent = ((1920 - (x + w)) / 1920) * 100;
video.style.clipPath = `inset(${topPercent}% ${rightPercent}% ${bottomPercent}% ${leftPercent}%)`;
const scaleY = 1 / (1 - (topPercent + bottomPercent)/100);
const scaleX = 1 / (1 - (leftPercent + rightPercent)/100);
const scale = Math.max(scaleY, scaleX);
if (scale > 1.01) {
video.style.transform = `scale(${scale})`;
}
console.log('Black Bar Remover: Applied crop', cropData, 'scale', scale);
}
}
function fetchCrop(itemId) {
console.log('Black Bar Remover: Fetching crop data for item', itemId);
window._originalFetch('/BlackBarRemover/CropById?itemId=' + itemId)
.then(r => r.text())
.then(crop => {
console.log('Black Bar Remover: Received crop data:', crop);
if (crop) {
setTimeout(() => applyCrop(crop), 500);
setTimeout(() => applyCrop(crop), 2000);
}
})
.catch(e => console.error('Black Bar Remover: API fetch error:', e));
}
if (!window._originalFetch) {
window._originalFetch = window.fetch;
window.fetch = async function(...args) {
const url = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url);
if (url) {
const match = url.match(/\/Items\/([a-f0-9]{32}|[a-f0-9\-]{36})/i);
if (match) {
const id = match[1];
if (id && id !== interceptedItemId) {
interceptedItemId = id;
console.log('Black Bar Remover: Intercepted itemId from fetch:', id);
}
}
}
return window._originalFetch.apply(this, args);
};
}
if (!window._originalXHR) {
window._originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
const xhr = new window._originalXHR();
const originalOpen = xhr.open;
xhr.open = function(method, url, ...rest) {
if (url && typeof url === 'string') {
const match = url.match(/\/Items\/([a-f0-9]{32}|[a-f0-9\-]{36})/i);
if (match) {
const id = match[1];
if (id && id !== interceptedItemId) {
interceptedItemId = id;
console.log('Black Bar Remover: Intercepted itemId from XHR:', id);
}
}
}
return originalOpen.apply(this, [method, url, ...rest]);
};
return xhr;
};
}
setInterval(() => {
const video = document.querySelector('video');
if (video && interceptedItemId && interceptedItemId !== currentItemId) {
currentItemId = interceptedItemId;
console.log('Black Bar Remover: Video element found for intercepted item:', currentItemId);
fetchCrop(currentItemId);
} else if (!video) {
currentItemId = null;
}
}, 2000);
})();
";
return Content(js, "application/javascript");
}
}
}