Automate FFmpeg wrapper configuration

This commit is contained in:
2026-09-17 14:23:20 +02:00
parent 904aada05d
commit 25ccafc2a4
3 changed files with 60 additions and 36 deletions
Binary file not shown.
+58 -31
View File
@@ -1,11 +1,11 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins;
using Microsoft.Extensions.Logging;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace JellyfinPlugin namespace JellyfinPlugin
{ {
@@ -13,16 +13,18 @@ namespace JellyfinPlugin
{ {
private readonly ILogger<WrapperGenerator> _logger; private readonly ILogger<WrapperGenerator> _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IServerConfigurationManager _configManager;
public WrapperGenerator(ILogger<WrapperGenerator> logger, IApplicationPaths appPaths) public WrapperGenerator(ILogger<WrapperGenerator> logger, IApplicationPaths appPaths, IServerConfigurationManager configManager)
{ {
_logger = logger; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
_configManager = configManager;
} }
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
GenerateWrapper(); GenerateAndSetWrapper();
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -31,45 +33,67 @@ namespace JellyfinPlugin
return Task.CompletedTask; return Task.CompletedTask;
} }
private void GenerateWrapper() private void GenerateAndSetWrapper()
{ {
try 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"); string wrapperDir = Path.Combine(_appPaths.PluginsPath, "BlackBarRemover");
Directory.CreateDirectory(wrapperDir); Directory.CreateDirectory(wrapperDir);
string wrapperPath = Path.Combine(wrapperDir, "ffmpeg-wrapper.sh"); string wrapperPath = Path.Combine(wrapperDir, "ffmpeg-wrapper.sh");
string script = @"#!/bin/bash // Jeśli obecna ścieżka to już nasz wrapper, nie robimy pętli w nieskończoność.
# Zmienna przechowująca ścieżkę do prawdziwego FFmpeg Jellyfina (zmień jeśli trzeba) // Znajdujemy poprzednio zapisaną prawdziwą ścieżkę z pliku.
REAL_FFMPEG=""/usr/lib/jellyfin-ffmpeg/ffmpeg"" 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
# Zmienna przechowująca ścieżkę do prawdziwego FFmpeg Jellyfina
REAL_FFMPEG=""{realFfmpegPath}""
# Znajdujemy ścieżkę pliku wejściowego (argument po -i) # Znajdujemy ścieżkę pliku wejściowego (argument po -i)
INPUT_FILE="""" INPUT_FILE=""""
for i in ""${!@}""; do for i in ""${{!@}}""; do
if [[ ""${@:$i:1}"" == ""-i"" ]]; then if [[ ""${{@:$i:1}}"" == ""-i"" ]]; then
INPUT_FILE=""${@:$i+1:1}"" INPUT_FILE=""${{@:$i+1:1}}""
break break
fi fi
done done
# Pobieramy crop z API naszej wtyczki # Pobieramy crop z API naszej wtyczki
if [ -n ""$INPUT_FILE"" ]; then if [ -n ""$INPUT_FILE"" ]; then
# Jellyfin lokalnie słucha na 8096, odpytujemy API o crop
CROP_DATA=$(curl -s --max-time 1 -G --data-urlencode ""Path=$INPUT_FILE"" http://127.0.0.1:8096/BlackBarRemover/CropByPath) CROP_DATA=$(curl -s --max-time 1 -G --data-urlencode ""Path=$INPUT_FILE"" http://127.0.0.1:8096/BlackBarRemover/CropByPath)
if [ -n ""$CROP_DATA"" ] && [ ""$CROP_DATA"" != """" ]; then if [ -n ""$CROP_DATA"" ] && [ ""$CROP_DATA"" != """" ]; then
# Znaleziono crop! Wstrzykujemy filtr crop=W:H:X:Y do argumentów
# Dodajemy -vf crop przed argumentami wyjścia
NEW_ARGS=()
for arg in ""$@""; do
# Prosta iniekcja, w realnym środowisku szukamy gdzie wkleić -vf, zazwyczaj przed plikiem wyjściowym.
# Jellyfin sprzętowy tworzy filtry z -filter_complex, w softwareowym z -vf.
NEW_ARGS+=(""$arg"")
done
# Uruchamiamy ffmpeg z crop
# UWAGA: to jest uproszczenie. Prawdziwy wrapper IntroSkipper musi łączyć filtry.
# W tym MVP dodajemy prosty filter:
exec ""$REAL_FFMPEG"" ""$@"" -vf ""crop=$CROP_DATA"" exec ""$REAL_FFMPEG"" ""$@"" -vf ""crop=$CROP_DATA""
exit 0 exit 0
fi fi
@@ -80,22 +104,25 @@ exec ""$REAL_FFMPEG"" ""$@""
"; ";
File.WriteAllText(wrapperPath, script); File.WriteAllText(wrapperPath, script);
// Nadajemy uprawnienia do wykonywania w systemach Linux (używając shella)
if (Environment.OSVersion.Platform == PlatformID.Unix) if (Environment.OSVersion.Platform == PlatformID.Unix)
{ {
System.Diagnostics.Process.Start("chmod", $"+x \"{wrapperPath}\""); System.Diagnostics.Process.Start("chmod", $"+x \"{wrapperPath}\"");
} }
_logger.LogInformation("Wygenerowano skrypt FFmpeg Wrapper w lokalizacji: {Path}", 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) catch (Exception ex)
{ {
_logger.LogError(ex, "Błąd podczas generowania skryptu FFmpeg wrapper."); _logger.LogError(ex, "Błąd podczas generowania i ustawiania skryptu FFmpeg wrapper.");
} }
} }
public void Dispose()
{
}
} }
} }
+2 -5
View File
@@ -36,12 +36,9 @@ A Jellyfin plugin that automatically detects and removes black bars (letterboxin
- `Both`: Attempts both methods depending on the client. - `Both`: Attempts both methods depending on the client.
4. **Important for existing libraries**: The plugin automatically scans *new* files when they are added to Jellyfin. For your existing library, go to **Dashboard -> Scheduled Tasks** and run the **"Skanuj brakujące czarne pasy (Black Bar Remover)"** task manually. It will scan all existing movies and episodes in the background. 4. **Important for existing libraries**: The plugin automatically scans *new* files when they are added to Jellyfin. For your existing library, go to **Dashboard -> Scheduled Tasks** and run the **"Skanuj brakujące czarne pasy (Black Bar Remover)"** task manually. It will scan all existing movies and episodes in the background.
### Activating the Server-Side Wrapper (Optional) ### Activating the Server-Side Wrapper (Automagic)
If you selected "Server" or "Both" mode: If you select "Server" or "Both" mode, the plugin automatically generates a wrapper script and dynamically injects it into Jellyfin's `EncoderAppPath` configuration during startup. It takes over the transcoding process effortlessly. If it ever points to the wrong real FFmpeg, you can adjust it in the wrapper script.
1. The plugin automatically generates a wrapper script on startup at `<PluginsPath>/BlackBarRemover/ffmpeg-wrapper.sh`.
2. In the Jellyfin Dashboard, go to **Playback** -> **FFmpeg path**.
3. Change the path to point to the generated `ffmpeg-wrapper.sh` file.
### Activating the Client-Side CSS (Automatic) ### Activating the Client-Side CSS (Automatic)