Merge pull request #56 from RepoDevil/master

Add parameters for ffmpeg options
This commit is contained in:
TwistedUmbrellaX 2024-03-04 09:18:50 -05:00 committed by GitHub
commit 77f43ed96e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 203 additions and 105 deletions

4
.gitignore vendored
View File

@ -5,3 +5,7 @@ BenchmarkDotNet.Artifacts/
# Ignore pre compiled web interface
docker/dist
# Visual Studio
.vs/
UpgradeLog*.htm

View File

@ -7,8 +7,8 @@ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
/// <summary>
/// Chapter name analyzer.

View File

@ -275,10 +275,10 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
{
var modifiedPoint = (uint)(originalPoint + i);
if (rhsIndex.ContainsKey(modifiedPoint))
if (rhsIndex.TryGetValue(modifiedPoint, out var value))
{
var lhsFirst = (int)lhsIndex[originalPoint];
var rhsFirst = (int)rhsIndex[modifiedPoint];
var rhsFirst = (int)value;
indexShifts.Add(rhsFirst - lhsFirst);
}
}

View File

@ -1,3 +1,4 @@
using System.Diagnostics;
using MediaBrowser.Model.Plugins;
namespace ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
@ -181,4 +182,14 @@ public class PluginConfiguration : BasePluginConfiguration
/// Gets or sets the notification text sent after automatically skipping an introduction.
/// </summary>
public string AutoSkipNotificationText { get; set; } = "Intro skipped";
/// <summary>
/// Gets or sets the number of threads for an ffmpeg process.
/// </summary>
public int ProcessThreads { get; set; } = 0;
/// <summary>
/// Gets or sets the relative priority for an ffmpeg process.
/// </summary>
public ProcessPriorityClass ProcessPriority { get; set; } = ProcessPriorityClass.BelowNormal;
}

View File

@ -59,77 +59,8 @@
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="CacheFingerprints" type="checkbox" is="emby-checkbox" />
<span>Cache episode fingerprints</span>
</label>
<div class="fieldDescription">
If checked, episode fingerprints will be cached to the filesystem
<br />
<strong>WARNING: Disabling cache may result in lengthy detection</strong>
<br />
</div>
</div>
<details id="edl">
<summary>EDL file generation</summary>
<br />
<div class="selectContainer">
<label class="selectLabel" for="EdlAction">EDL Action</label>
<select is="emby-select" id="EdlAction" class="emby-select-withcolor emby-select">
<option value="None">
None (do not create or modify EDL files)
</option>
<option value="CommercialBreak">
Commercial Break (recommended, skips past the intro once)
</option>
<option value="Cut">
Cut (player will remove the intro from the video)
</option>
<option value="Intro">
Intro (show a skip button, *experimental*)
</option>
<option value="Mute">
Mute (audio will be muted)
</option>
<option value="SceneMarker">
Scene Marker (create a chapter marker)
</option>
</select>
<div class="fieldDescription">
If set to a value other than None, specifies which action to write to
<a href="https://kodi.wiki/view/Edit_decision_list">MPlayer compatible EDL files</a>
alongside your episode files. <br />
If this value is changed after EDL files are generated, you must check the
"Regenerate EDL files" checkbox below.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="RegenerateEdlFiles" type="checkbox" is="emby-checkbox" />
<span>Regenerate EDL files during next scan</span>
</label>
<div class="fieldDescription">
If checked, the plugin will <strong>overwrite all EDL files</strong> associated with
your episodes with the currently discovered introduction timestamps and EDL action.
</div>
</div>
</details>
<details id="intro_reqs">
<summary>Modify introduction requirements</summary>
<summary>Modify Intro Parameters</summary>
<br />
<div class="inputContainer">
@ -193,8 +124,63 @@
</p>
</details>
<details id="edl">
<summary>EDL File Generation</summary>
<br />
<div class="selectContainer">
<label class="selectLabel" for="EdlAction">EDL Action</label>
<select is="emby-select" id="EdlAction" class="emby-select-withcolor emby-select">
<option value="None">
None (do not create or modify EDL files)
</option>
<option value="CommercialBreak">
Commercial Break (recommended, skips past the intro once)
</option>
<option value="Cut">
Cut (player will remove the intro from the video)
</option>
<option value="Intro">
Intro (show a skip button, *experimental*)
</option>
<option value="Mute">
Mute (audio will be muted)
</option>
<option value="SceneMarker">
Scene Marker (create a chapter marker)
</option>
</select>
<div class="fieldDescription">
If set to a value other than None, specifies which action to write to
<a href="https://kodi.wiki/view/Edit_decision_list">MPlayer compatible EDL files</a>
alongside your episode files. <br />
If this value is changed after EDL files are generated, you must check the
"Regenerate EDL files" checkbox below.
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="RegenerateEdlFiles" type="checkbox" is="emby-checkbox" />
<span>Regenerate EDL files during next scan</span>
</label>
<div class="fieldDescription">
If checked, the plugin will <strong>overwrite all EDL files</strong> associated with
your episodes with the currently discovered introduction timestamps and EDL action.
</div>
</div>
</details>
<details id="silence">
<summary>Silence detection options</summary>
<summary>Silence Detection Options</summary>
<br />
<div class="inputContainer">
@ -219,6 +205,73 @@
</div>
</div>
</details>
<details id="detection">
<summary>Process Configuration</summary>
<br/>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="CacheFingerprints" type="checkbox" is="emby-checkbox" />
<span>Cache episode fingerprints</span>
</label>
<div class="fieldDescription">
If checked, episode fingerprints will be saved on the filesystem to improve analysis speed.
<br />
<strong>WARNING: May result in lengthy detection! Not recommended for large libraries!</strong>
<br />
</div>
</div>
<div class="selectContainer">
<label class="selectLabel" for="ProcessPriority">ffmpeg Priority</label>
<select is="emby-select" id="ProcessPriority" class="emby-select-withcolor emby-select">
<option value="Idle">
Idle
</option>
<option value="BelowNormal">
Below Normal
</option>
<option value="Normal">
Normal
</option>
<option value="AboveNormal">
Above Normal
</option>
<option value="High">
High
</option>
<option value="RealTime">
Highest
</option>
</select>
<div class="fieldDescription">
Sets the relative priority of the analysis ffmpeg process to other parallel operations
(ie. transcoding, chapter detection, etc).
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="ProcessThreads">
ffmpeg Threads
</label>
<input id="ProcessThreads" type="number" is="emby-input" min="0"
max="16" />
<div class="fieldDescription">
Number of simultaneous processes to use for ffmpeg operations.
<br />
This value is most often defined as 1 thread per CPU core,
but setting a value of 0 (default) will use the maximum threads available.
</div>
</div>
</details>
</fieldset>
<fieldset class="verticalSection-extrabottompadding">
@ -268,7 +321,7 @@
</label>
<div class="fieldDescription">
If checked, skip button will appear through entire intro (offset and timeout are ignored).<br />
If checked, skip button will appear throughout entire intro (offset and timeout are ignored).<br />
</div>
</div>
@ -388,13 +441,13 @@
</button>
<br />
<br />
<button id="btnEraseSeasonTimestamps" type="button">
Erase all timestamps for this season
</button>
<hr />
</div>
<button id="btnEraseSeasonTimestamps" type="button" style="display:none;">
Erase all timestamps for this season
</button>
<hr />
<button id="btnEraseIntroTimestamps">
Erase all introduction timestamps (globally)
</button>
@ -491,6 +544,8 @@
"MinimumIntroDuration",
"MaximumIntroDuration",
"EdlAction",
"ProcessPriority",
"ProcessThreads",
// playback
"ShowPromptAdjustment",
"HidePromptAdjustment",
@ -506,8 +561,8 @@
var booleanConfigurationFields = [
"AnalyzeSeasonZero",
"CacheFingerprints",
"RegenerateEdlFiles",
"CacheFingerprints",
"AutoSkip",
"SkipFirstEpisode",
"PersistSkipButton",
@ -618,6 +673,7 @@
// show changed, populate seasons
async function showChanged() {
clearSelect(selectSeason);
btnSeasonEraseTimestamps.style.display = "none";
// add all seasons from this show to the season select
for (var season of shows[selectShow.value]) {
@ -634,6 +690,7 @@
clearSelect(selectEpisode1);
clearSelect(selectEpisode2);
btnSeasonEraseTimestamps.style.display = "block";
let i = 1;
for (let episode of episodes) {
@ -714,7 +771,7 @@
// make an authenticated GET to the server and parse the response as JSON
async function getJson(url) {
return await fetchWithAuth(url, "GET").then(r => { return r.json(); });
return await fetchWithAuth(url, "GET").then(r => { return r.json(); }).catch(err => { console.debug(err) });
}
// make an authenticated fetch to the server

View File

@ -1,25 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>ConfusedPolarBear.Plugin.IntroSkipper</RootNamespace>
<AssemblyVersion>0.1.14.0</AssemblyVersion>
<FileVersion>0.1.14.0</FileVersion>
<AssemblyVersion>0.1.15.0</AssemblyVersion>
<FileVersion>0.1.15.0</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.Controller" Version="10.8.*" />
<PackageReference Include="Jellyfin.Model" Version="10.8.*" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.556" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<None Remove="Configuration\configPage.html" />
<EmbeddedResource Include="Configuration\configPage.html" />
@ -27,5 +24,4 @@
<EmbeddedResource Include="Configuration\inject.js" />
<EmbeddedResource Include="Configuration\version.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -400,8 +400,9 @@ public static class FFmpegWrapper
// for each file that is fingerprinted.
var prependArgument = string.Format(
CultureInfo.InvariantCulture,
"-hide_banner -loglevel {0} ",
logLevel);
"-hide_banner -loglevel {0} -threads {1} ",
logLevel,
Plugin.Instance?.Configuration.ProcessThreads ?? 0);
var info = new ProcessStartInfo(ffmpegPath, args.Insert(0, prependArgument))
{
@ -425,6 +426,17 @@ public static class FFmpegWrapper
ffmpeg.Start();
try
{
ffmpeg.PriorityClass = Plugin.Instance?.Configuration.ProcessPriority ?? ProcessPriorityClass.BelowNormal;
}
catch (Exception e)
{
Logger?.LogDebug(
"ffmpeg priority could not be modified. {Message}",
e.Message);
}
using (MemoryStream ms = new MemoryStream())
{
var buf = new byte[4096];

View File

@ -0,0 +1,10 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Legacy TODO", Scope = "type", Target = "~T:ConfusedPolarBear.Plugin.IntroSkipper.WarningManager")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Legacy TODO", Scope = "type", Target = "~T:ConfusedPolarBear.Plugin.IntroSkipper.IntroWithMetadata")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Legacy TODO", Scope = "type", Target = "~T:ConfusedPolarBear.Plugin.IntroSkipper.TimeRangeHelpers")]

View File

@ -331,7 +331,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
private void InjectSkipButton(string indexPath)
{
// Parts of this code are based off of JellyScrub's script injection code.
// https://github.com/nicknsy/jellyscrub/blob/4ce806f602988a662cfe3cdbaac35ee8046b7ec4/Nick.Plugin.Jellyscrub/JellyscrubPlugin.cs
// https://github.com/nicknsy/jellyscrub/blob/main/Nick.Plugin.Jellyscrub/JellyscrubPlugin.cs#L38
_logger.LogDebug("Reading index.html from {Path}", indexPath);
var contents = File.ReadAllText(indexPath);

View File

@ -43,13 +43,6 @@ public class QueueManager
/// <returns>Queued media items.</returns>
public ReadOnlyDictionary<Guid, List<QueuedEpisode>> GetMediaItems()
{
// Assert that ffmpeg with chromaprint is installed
if (!FFmpegWrapper.CheckFFmpegVersion())
{
throw new FingerprintException(
"ffmpeg with chromaprint is not installed on this system - episodes will not be analyzed. If Jellyfin is running natively, install jellyfin-ffmpeg5. If Jellyfin is running in a container, upgrade it to the latest version of 10.8.0.");
}
Plugin.Instance!.TotalQueued = 0;
LoadAnalysisSettings();

View File

@ -53,6 +53,13 @@ public class BaseItemAnalyzerTask
IProgress<double> progress,
CancellationToken cancellationToken)
{
// Assert that ffmpeg with chromaprint is installed
if (!FFmpegWrapper.CheckFFmpegVersion())
{
throw new FingerprintException(
"ffmpeg with chromaprint is not installed on this system - episodes will not be analyzed. If Jellyfin is running natively, install jellyfin-ffmpeg5. If Jellyfin is running in a container, upgrade it to the latest version of 10.8.0.");
}
var queueManager = new QueueManager(
_loggerFactory.CreateLogger<QueueManager>(),
_libraryManager);

View File

@ -17,14 +17,14 @@ This fork doesn't ship the custom web interface on your server. So the plugin ne
* Debian Linux based native installs: provided by the `jellyfin-ffmpeg5` package
* MacOS native installs: build ffmpeg with chromaprint support ([instructions](#installation-instructions-for-macos))
## Introduction requirements
## Introduction parameters
Show introductions will only be detected if they are:
Show introductions will be detected if they are:
* Located within the first 25% of an episode, or the first 10 minutes, whichever is smaller
* Located within the first 25% of an episode or the first 10 minutes, whichever is smaller
* Between 15 seconds and 2 minutes long
Ending credits will only be detected if they are shorter than 4 minutes.
Ending credits will be detected if they are shorter than 4 minutes.
All of these requirements can be customized as needed.

View File

@ -4,10 +4,18 @@
"name": "Intro Skipper",
"overview": "Automatically detect and skip intros in television episodes",
"description": "Analyzes the audio of television episodes and detects introduction sequences.",
"owner": "ConfusedPolarBear",
"owner": "jumoog, AbandonedCart (forked from ConfusedPolarBear)",
"category": "General",
"imageUrl": "https://raw.githubusercontent.com/jumoog/intro-skipper/master/images/logo.png",
"versions": [
{
"version": "0.1.15.0",
"changelog": "- See the full changelog at [GitHub](https://github.com/jumoog/intro-skipper/blob/master/CHANGELOG.md)\n",
"targetAbi": "10.8.4.0",
"sourceUrl": "https://github.com/jumoog/intro-skipper/releases/download/v0.1.15/intro-skipper-v0.1.15.zip",
"checksum": "cf05593afbb2be39b8de31dcb7fd8a50",
"timestamp": "2024-03-03T09:08:10Z"
},
{
"version": "0.1.14.0",
"changelog": "- See the full changelog at [GitHub](https://github.com/jumoog/intro-skipper/blob/master/CHANGELOG.md)\n",