Skip to content

Commit

Permalink
Merge pull request #73 from nicknsy/beta-1.0.0.8
Browse files Browse the repository at this point in the history
Beta 1.0.0.8
  • Loading branch information
nicknsy authored Feb 15, 2023
2 parents 57ac3ca + 636f7fb commit ee63a7b
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 49 deletions.
11 changes: 7 additions & 4 deletions Nick.Plugin.Jellyscrub/Api/TrickplayController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
Expand Down Expand Up @@ -33,6 +33,7 @@ public class TrickplayController : ControllerBase
private readonly ILibraryMonitor _libraryMonitor;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerConfigurationManager _configurationManager;
private readonly EncodingHelper _encodingHelper;

private readonly PluginConfiguration _config;

Expand All @@ -47,7 +48,8 @@ public TrickplayController(
IApplicationPaths appPaths,
ILibraryMonitor libraryMonitor,
IMediaEncoder mediaEncoder,
IServerConfigurationManager configurationManager)
IServerConfigurationManager configurationManager,
EncodingHelper encodingHelper)
{
_assembly = Assembly.GetExecutingAssembly();
_trickplayScriptPath = GetType().Namespace + ".trickplay.js";
Expand All @@ -60,6 +62,7 @@ public TrickplayController(
_libraryMonitor = libraryMonitor;
_mediaEncoder = mediaEncoder;
_configurationManager = configurationManager;
_encodingHelper = encodingHelper;

_config = JellyscrubPlugin.Instance!.Configuration;
}
Expand Down Expand Up @@ -112,7 +115,7 @@ public async Task<ActionResult> GetManifest([FromRoute, Required] Guid itemId)
}
else if (_config.OnDemandGeneration)
{
new VideoProcessor(_loggerFactory, _loggerFactory.CreateLogger<VideoProcessor>(), _mediaEncoder, _configurationManager, _fileSystem, _appPaths, _libraryMonitor)
new VideoProcessor(_loggerFactory, _loggerFactory.CreateLogger<VideoProcessor>(), _mediaEncoder, _configurationManager, _fileSystem, _appPaths, _libraryMonitor, _encodingHelper)
.Run(item, CancellationToken.None);
return StatusCode(503);
}
Expand Down Expand Up @@ -150,7 +153,7 @@ public async Task<ActionResult> GetBIF([FromRoute, Required] Guid itemId, [FromR
}
else if (_config.OnDemandGeneration && _config.WidthResolutions.Contains(width))
{
new VideoProcessor(_loggerFactory, _loggerFactory.CreateLogger<VideoProcessor>(), _mediaEncoder, _configurationManager, _fileSystem, _appPaths, _libraryMonitor)
new VideoProcessor(_loggerFactory, _loggerFactory.CreateLogger<VideoProcessor>(), _mediaEncoder, _configurationManager, _fileSystem, _appPaths, _libraryMonitor, _encodingHelper)
.Run(item, CancellationToken.None);
return StatusCode(503);
}
Expand Down
8 changes: 8 additions & 0 deletions Nick.Plugin.Jellyscrub/Configuration/HwAccelerationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Nick.Plugin.Jellyscrub.Configuration;

public enum HwAccelerationOptions
{
None,
NoEncode,
Full
}
8 changes: 8 additions & 0 deletions Nick.Plugin.Jellyscrub/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ public class PluginConfiguration : BasePluginConfiguration
/// </summary>
public PluginConfiguration() {}

/// <summary>
/// Whether or not to use HW acceleration options set in Jellyfin
/// for BIF generation. Set to NoEncode on older VAAPI or QSV devices that
/// can't HW encode MJPEG.
/// default = None
/// </summary>
public HwAccelerationOptions HwAcceleration { get; set; } = HwAccelerationOptions.None;

/// <summary>
/// Determines whether or not trickplays are generated on demand
/// if client requests are none are available.
Expand Down
31 changes: 21 additions & 10 deletions Nick.Plugin.Jellyscrub/Configuration/configPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
<form class="jellyscrubConfigurationForm">
<br />

<div class="inputContainer">
<select is="emby-select" id="hwAcceleration" name="HW Acceleration" label="HW Acceleration">
<option id="optAccelerationNone" value="None">None</option>
<option id="optAccelerationNoEncode" value="NoEncode">No Encode - decode, filters</option>
<option id="optAccelerationFull" value="Full">Full - decode, filters, encode</option>
</select>
<div class="fieldDescription">Uses the same options as set in "Playback" to HW accelerate BIF generation. Counts towards NVENC stream limit.</div>
<div class="fieldDescription">"Full" will default to SW encode on NVENC. On older VAAPI and QSV devices without mjpeg encode support, set to "No Encode".</div>
</div>

<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkOnDemand" />
Expand Down Expand Up @@ -56,15 +66,15 @@
</div>

<!--
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkStyleTrickplayContainer" />
<span>Style Trickplay Container</span>
</label>
<div class="fieldDescription checkboxFieldDescription">Injects a style element into the page that makes the trickplay preview look better.</div>
<div class="fieldDescription"><strong>Note:</strong> This will not overwrite custom CSS as set in General settings.</div>
</div>
-->
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkStyleTrickplayContainer" />
<span>Style Trickplay Container</span>
</label>
<div class="fieldDescription checkboxFieldDescription">Injects a style element into the page that makes the trickplay preview look better.</div>
<div class="fieldDescription"><strong>Note:</strong> This will not overwrite custom CSS as set in General settings.</div>
</div>
-->

<div class="inputContainer">
<input is="emby-input" type="number" id="intervalInput" pattern="[0-9]*" min="0" required label="Image Interval">
Expand Down Expand Up @@ -145,6 +155,7 @@

ApiClient.getPluginConfiguration(pluginId).then(function (config) {

page.querySelector('#hwAcceleration').value = config.HwAcceleration;
page.querySelector('#chkOnDemand').checked = config.OnDemandGeneration;
page.querySelector('#chkEnableDuringScan').checked = config.ExtractionDuringLibraryScan;
page.querySelector('#scanBehavior').value = config.ScanBehavior;
Expand All @@ -167,7 +178,7 @@
var form = this;

ApiClient.getPluginConfiguration(pluginId).then(function (config) {

config.HwAcceleration = form.querySelector('#hwAcceleration').value;
config.OnDemandGeneration = form.querySelector('#chkOnDemand').checked;
config.ExtractionDuringLibraryScan = form.querySelector('#chkEnableDuringScan').checked;
config.ScanBehavior = form.querySelector('#scanBehavior').value;
Expand Down
101 changes: 75 additions & 26 deletions Nick.Plugin.Jellyscrub/Drawing/OldMediaEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO;
using Nick.Plugin.Jellyscrub.Configuration;
using MediaBrowser.Model.Configuration;

namespace Nick.Plugin.Jellyscrub.Drawing;

Expand All @@ -22,25 +23,30 @@ public class OldMediaEncoder
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly EncodingHelper _encodingHelper;

private readonly SemaphoreSlim _thumbnailResourcePool = new(1, 1);
private readonly object _runningProcessesLock = new();
private readonly List<ProcessWrapper> _runningProcesses = new();

private readonly PluginConfiguration _config;
private string _ffmpegPath;
private int _threads;
private readonly PluginConfiguration _config;
private bool _doHwAcceleration;
private bool _doHwEncode;

public OldMediaEncoder(
ILogger<OldMediaEncoder> logger,
IMediaEncoder mediaEncoder,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem)
IFileSystem fileSystem,
EncodingHelper encodingHelper)
{
_logger = logger;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_encodingHelper = encodingHelper;

_config = JellyscrubPlugin.Instance!.Configuration;
var configThreads = _config.ProcessThreads;
Expand All @@ -55,6 +61,10 @@ public OldMediaEncoder(
}

_threads = configThreads == -1 ? EncodingHelper.GetNumberOfThreads(null, encodingConfig, null) : configThreads;

var hwAcceleration = _config.HwAcceleration;
_doHwAcceleration = (hwAcceleration != HwAccelerationOptions.None);
_doHwEncode = (hwAcceleration == HwAccelerationOptions.Full);
}

public async Task ExtractVideoImagesOnInterval(
Expand All @@ -69,35 +79,57 @@ public async Task ExtractVideoImagesOnInterval(
int maxWidth,
CancellationToken cancellationToken)
{
var inputArgument = _mediaEncoder.GetInputArgument(inputFile, mediaSource);
var options = _doHwAcceleration ? _configurationManager.GetEncodingOptions() : new EncodingOptions();

var vf = "-filter:v fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture);
var maxWidthParam = maxWidth.ToString(CultureInfo.InvariantCulture);
// A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin.
// Additionally, we must set a few fields without defaults to prevent null pointer exceptions.
if (!_doHwAcceleration)
{
options.EnableHardwareEncoding = false;
options.HardwareAccelerationType = string.Empty;
options.EnableTonemapping = false;
}

vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
var hwAccelType = options.HardwareAccelerationType;

// HDR Software Tonemapping
if ((string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
&& _mediaEncoder.SupportsFilter("zscale"))
var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth };
var jobState = new EncodingJobInfo(TranscodingJobType.Progressive)
{
vf += ",zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p";
}
IsVideoRequest = true, // must be true for InputVideoHwaccelArgs to return non-empty value
MediaSource = mediaSource,
VideoStream = videoStream,
BaseRequest = baseRequest, // GetVideoProcessingFilterParam errors if null
MediaPath = inputFile,
OutputVideoCodec = GetOutputCodec(hwAccelType)
};

Directory.CreateDirectory(targetDirectory);
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%08d.jpg");
// Get input and filter arguments
var inputArgs = _encodingHelper.GetInputArgument(jobState, options, null).Trim();
if (string.IsNullOrWhiteSpace(inputArgs)) throw new InvalidOperationException("EncodingHelper returned empty input arguments.");

var args = string.Format(CultureInfo.InvariantCulture, "-threads {3} -i {0} -threads {4} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads, _threads);
if (!_doHwAcceleration) inputArgs = "-threads " + _threads + " " + inputArgs; // HW accel args set a different input thread count, only set if disabled

if (!string.IsNullOrWhiteSpace(container))
{
var inputFormat = EncodingHelper.GetInputFormat(container);
if (!string.IsNullOrWhiteSpace(inputFormat))
{
args = "-f " + inputFormat + " " + args;
}
}
var filterParams = _encodingHelper.GetVideoProcessingFilterParam(jobState, options, jobState.OutputVideoCodec).Trim();
if (string.IsNullOrWhiteSpace(filterParams) || filterParams.IndexOf("\"") == -1) throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters.");

filterParams = filterParams.Insert(filterParams.IndexOf("\"") + 1, "fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture) + ","); // set framerate

// Output arguments
Directory.CreateDirectory(targetDirectory);
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%08d.jpg");

// Final command arguments
var args = string.Format(
CultureInfo.InvariantCulture,
"-loglevel error {0} {1} -threads {2} -c:v {3} -f {4} \"{5}\"",
inputArgs,
filterParams,
_threads,
jobState.OutputVideoCodec,
"image2",
outputPath);

// Start ffmpeg process
var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
Expand Down Expand Up @@ -151,6 +183,7 @@ public async Task ExtractVideoImagesOnInterval(

if (!ranToCompletion)
{
_logger.LogInformation("Killing ffmpeg process due to inactivity.");
StopProcess(processWrapper, 1000);
}
}
Expand All @@ -163,7 +196,7 @@ public async Task ExtractVideoImagesOnInterval(

if (exitCode == -1)
{
var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputArgument);
var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputFile);

_logger.LogError(msg);

Expand All @@ -172,6 +205,22 @@ public async Task ExtractVideoImagesOnInterval(
}
}

public string GetOutputCodec(string hwaccelType)
{
if (_doHwAcceleration && _doHwEncode)
{
switch (hwaccelType.ToLower())
{
case "vaapi":
return "mjpeg_vaapi";
case "qsv":
return "mjpeg_qsv";
}
}

return "mjpeg";
}

private void StartProcess(ProcessWrapper process)
{
process.Process.Start();
Expand Down Expand Up @@ -200,7 +249,7 @@ private void StopProcess(ProcessWrapper process, int waitTimeMs)
return;
}

_logger.LogInformation("Killing ffmpeg process");
_logger.LogInformation("Killing process \"{0}\"", process.Process.ProcessName);

process.Process.Kill();
}
Expand All @@ -211,7 +260,7 @@ private void StopProcess(ProcessWrapper process, int waitTimeMs)
}
catch (Exception ex)
{
_logger.LogError(ex, "Error killing process");
_logger.LogError(ex, "Error killing process \"{0}\"", process.Process.ProcessName);
}
}

Expand Down
13 changes: 10 additions & 3 deletions Nick.Plugin.Jellyscrub/Drawing/VideoProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ public VideoProcessor(
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILibraryMonitor libraryMonitor)
ILibraryMonitor libraryMonitor,
EncodingHelper encodingHelper)
{
_logger = logger;
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryMonitor = libraryMonitor;
_config = JellyscrubPlugin.Instance!.Configuration;
_oldEncoder = new OldMediaEncoder(loggerFactory.CreateLogger<OldMediaEncoder>(), mediaEncoder, configurationManager, fileSystem);
_oldEncoder = new OldMediaEncoder(loggerFactory.CreateLogger<OldMediaEncoder>(), mediaEncoder, configurationManager, fileSystem, encodingHelper);
}

/*
Expand Down Expand Up @@ -77,6 +78,10 @@ private async Task Run(BaseItem item, MediaSourceInfo mediaSource, int width, in
await CreateManifest(item, width).ConfigureAwait(false);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while creating BIF file");
}
finally
{
BifWriterSemaphore.Release();
Expand Down Expand Up @@ -218,6 +223,8 @@ await _oldEncoder.ExtractVideoImagesOnInterval(inputPath, mediaSource.Container,
.OrderBy(i => i.FullName)
.ToList();

if (images.Count == 0) throw new InvalidOperationException("Cannot make BIF file from 0 images.");

var bifTempPath = Path.Combine(tempDirectory, Guid.NewGuid().ToString("N"));

using (var fs = new FileStream(bifTempPath, FileMode.Create, FileAccess.Write, FileShare.Read))
Expand All @@ -234,7 +241,7 @@ await _oldEncoder.ExtractVideoImagesOnInterval(inputPath, mediaSource.Container,

// Create .ignore file so trickplay folder is not picked up as a season when TV folder structure is improper.
var ignorePath = Path.Combine(Directory.GetParent(path).FullName, ".ignore");
if (!File.Exists(ignorePath)) File.Create(ignorePath);
if (!File.Exists(ignorePath)) await File.Create(ignorePath).DisposeAsync();

_logger.LogInformation("Finished creation of trickplay file {0}", path);
}
Expand Down
4 changes: 2 additions & 2 deletions Nick.Plugin.Jellyscrub/Nick.Plugin.Jellyscrub.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyVersion>1.0.0.7</AssemblyVersion>
<FileVersion>1.0.0.7</FileVersion>
<AssemblyVersion>1.0.0.8</AssemblyVersion>
<FileVersion>1.0.0.8</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit ee63a7b

Please sign in to comment.