795f42048f
- 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
179 lines
6.3 KiB
C#
179 lines
6.3 KiB
C#
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.");
|
||
}
|
||
}
|
||
}
|
||
}
|