Files
blackBarRemover/JellyfinPlugin/WrapperGenerator.cs
T
karol 795f42048f feat(server): elegantly force transcoding for black bar removal
- Intercept HLS playlist queries to append `allowVideoStreamCopy=false`
- Append `transcodeReasons=VideoResolutionNotSupported` for accurate dashboard reporting
- Modify ffmpeg-wrapper.sh to cleanly inject `-vf crop=...` during active transcodes
- Strip `file:` prefix from inputs in the wrapper to ensure correct API lookups
- Remove hacky URL manipulations (e.g., dummy codecs) that caused FFmpeg crashes
2026-09-17 17:41:59 +02:00

179 lines
6.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace JellyfinPlugin
{
public class WrapperGenerator : IHostedService
{
private readonly ILogger<WrapperGenerator> _logger;
private readonly IApplicationPaths _appPaths;
private readonly IServerConfigurationManager _configManager;
public WrapperGenerator(ILogger<WrapperGenerator> logger, IApplicationPaths appPaths, IServerConfigurationManager configManager)
{
_logger = logger;
_appPaths = appPaths;
_configManager = configManager;
}
public Task StartAsync(CancellationToken cancellationToken)
{
GenerateAndSetWrapper();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void GenerateAndSetWrapper()
{
try
{
var mode = Plugin.Instance!.Configuration.PlaybackMode;
if (mode == "Client")
{
// Jeżeli wybrano tylko klienta, nie nadpisujemy ffmpeg
return;
}
// Pobieramy obecną konfigurację kodowania
var encodingConfig = _configManager.GetConfiguration("encoding");
if (encodingConfig == null) return;
// Używamy dynamic, aby ominąć potrzebę rzutowania na konkretny, wewnętrzny typ Jellyfin (EncodingOptions)
dynamic dynConfig = encodingConfig;
string currentFfmpegPath = dynConfig.EncoderAppPath ?? "/usr/lib/jellyfin-ffmpeg/ffmpeg";
string wrapperDir = Path.Combine(_appPaths.PluginsPath, "BlackBarRemover");
Directory.CreateDirectory(wrapperDir);
string wrapperPath = Path.Combine(wrapperDir, "ffmpeg-wrapper.sh");
// Jeśli obecna ścieżka to już nasz wrapper, nie robimy pętli w nieskończoność.
// Znajdujemy poprzednio zapisaną prawdziwą ścieżkę z pliku.
string realFfmpegPath = currentFfmpegPath;
if (currentFfmpegPath == wrapperPath)
{
if (File.Exists(wrapperPath))
{
var lines = File.ReadAllLines(wrapperPath);
foreach (var line in lines)
{
if (line.StartsWith("REAL_FFMPEG="))
{
realFfmpegPath = line.Split('=')[1].Trim('"');
break;
}
}
}
if (realFfmpegPath == wrapperPath) realFfmpegPath = "/usr/lib/jellyfin-ffmpeg/ffmpeg";
}
string script = $@"#!/bin/bash
REAL_FFMPEG=""{realFfmpegPath}""
INPUT_FILE=""""
for (( i=1; i<=$#; i++ )); do
arg=""${{!i}}""
if [[ ""$arg"" == ""-i"" ]]; then
let next_idx=i+1
if [ $next_idx -le $# ]; then
INPUT_FILE=""${{!next_idx}}""
fi
break
fi
done
if [ -n ""$INPUT_FILE"" ]; then
# Jellyfin przekazuje ścieżkę z prefiksem file: (np. file:""/home/..."").
# Shell usuwa cudzysłowy automatycznie – zostaje nam tylko file: do usunięcia.
CLEAN_PATH=""${{INPUT_FILE#file:}}""
CROP_DATA=$(curl -s --max-time 1 -G --data-urlencode ""Path=$CLEAN_PATH"" http://127.0.0.1:8096/BlackBarRemover/CropByPath)
if [ -n ""$CROP_DATA"" ] && [ ""$CROP_DATA"" != """" ]; then
# Modyfikujemy argumenty w locie
declare -a NEW_ARGS
HAS_VF=0
IS_COPY=0
for (( i=1; i<=$#; i++ )); do
arg=""${{!i}}""
if [[ ""$arg"" == ""copy"" ]]; then
let prev_idx=i-1
prev_arg=""""
if [ $prev_idx -gt 0 ]; then
prev_arg=""${{!prev_idx}}""
fi
if [[ ""$prev_arg"" == *codec:v* ]] || [[ ""$prev_arg"" == *-c:v* ]] || [[ ""$prev_arg"" == *-vcodec* ]]; then
IS_COPY=1
fi
fi
if [[ ""$arg"" == ""-vf"" ]]; then
HAS_VF=1
NEW_ARGS+=(""-vf"")
next_arg=""""
let next_idx=i+1
if [ $next_idx -le $# ]; then
next_arg=""${{!next_idx}}""
fi
NEW_ARGS+=(""crop=$CROP_DATA,$next_arg"")
let i++
continue
fi
NEW_ARGS+=(""$arg"")
done
# Jeżeli Jellyfin nadal upiera się przy copy, nie możemy wstrzyknąć filtra crop (bo zcrashuje FFmpega)
if [ $IS_COPY -eq 1 ]; then
exec ""$REAL_FFMPEG"" ""$@""
exit 0
fi
if [ $HAS_VF -eq 0 ]; then
NEW_ARGS+=(""-vf"")
NEW_ARGS+=(""crop=$CROP_DATA"")
fi
exec ""$REAL_FFMPEG"" ""${{NEW_ARGS[@]}}""
exit 0
fi
fi
exec ""$REAL_FFMPEG"" ""$@""
";
File.WriteAllText(wrapperPath, script);
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
System.Diagnostics.Process.Start("chmod", $"+x \"{wrapperPath}\"");
}
_logger.LogInformation("Wygenerowano skrypt FFmpeg Wrapper w lokalizacji: {Path}", wrapperPath);
// Teraz automagicznie podmieniamy ścieżkę w Jellyfin!
if (dynConfig.EncoderAppPath != wrapperPath)
{
dynConfig.EncoderAppPath = wrapperPath;
_configManager.SaveConfiguration("encoding", encodingConfig);
_logger.LogInformation("Automatycznie zmieniono ścieżkę FFmpeg w konfiguracji Jellyfin na wrapper wtyczki.");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Błąd podczas generowania i ustawiania skryptu FFmpeg wrapper.");
}
}
}
}