Move from fpcalc to ffmpeg
This commit is contained in:
parent
db1fc45993
commit
3459e3ce4b
@ -1,14 +1,20 @@
|
|||||||
|
/* These tests require that the host system has a version of FFmpeg installed
|
||||||
|
* which supports both chromaprint and the "-fp_format raw" flag.
|
||||||
|
*/
|
||||||
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ConfusedPolarBear.Plugin.IntroSkipper.Tests;
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Tests;
|
||||||
|
|
||||||
|
// TODO: rename
|
||||||
|
|
||||||
public class TestFPCalc
|
public class TestFPCalc
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestInstallationCheck()
|
public void TestInstallationCheck()
|
||||||
{
|
{
|
||||||
Assert.True(FPCalc.CheckFPCalcInstalled());
|
Assert.True(FPCalc.CheckFFmpegVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -43,7 +49,7 @@ public class TestFPCalc
|
|||||||
3416816715, 3404331257, 3395844345, 3395836155, 3408464089, 3374975369, 1282036360, 1290457736,
|
3416816715, 3404331257, 3395844345, 3395836155, 3408464089, 3374975369, 1282036360, 1290457736,
|
||||||
1290400440, 1290314408, 1281925800, 1277727404, 1277792932, 1278785460, 1561962388, 1426698196,
|
1290400440, 1290314408, 1281925800, 1277727404, 1277792932, 1278785460, 1561962388, 1426698196,
|
||||||
3607924711, 4131892839, 4140215815, 4292259591, 3218515717, 3209938229, 3171964197, 3171956013,
|
3607924711, 4131892839, 4140215815, 4292259591, 3218515717, 3209938229, 3171964197, 3171956013,
|
||||||
4229117295, 4229312879, 4242407935, 4238016959, 4239987133, 4239990013, 3703060732, 1547188252,
|
4229117295, 4229312879, 4242407935, 4240114111, 4239987133, 4239990013, 3703060732, 1547188252,
|
||||||
1278748677, 1278748935, 1144662786, 1148854786, 1090388802, 1090388962, 1086260130, 1085940098,
|
1278748677, 1278748935, 1144662786, 1148854786, 1090388802, 1090388962, 1086260130, 1085940098,
|
||||||
1102709122, 45811586, 44634002, 44596656, 44592544, 1122527648, 1109944736, 1109977504, 1111030243,
|
1102709122, 45811586, 44634002, 44596656, 44592544, 1122527648, 1109944736, 1109977504, 1111030243,
|
||||||
1111017762, 1109969186, 1126721826, 1101556002, 1084844322, 1084979506, 1084914450, 1084914449,
|
1111017762, 1109969186, 1126721826, 1101556002, 1084844322, 1084979506, 1084914450, 1084914449,
|
||||||
|
@ -15,7 +15,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the output of fpcalc should be cached to the filesystem.
|
/// Gets or sets a value indicating whether the episode's fingerprint should be cached to the filesystem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CacheFingerprints { get; set; } = true;
|
public bool CacheFingerprints { get; set; } = true;
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@
|
|||||||
|
|
||||||
<div class="fieldDescription">
|
<div class="fieldDescription">
|
||||||
If checked, will store the fingerprints for all subsequently scanned files to disk.
|
If checked, will store the fingerprints for all subsequently scanned files to disk.
|
||||||
Caching fingerprints avoids having to re-run fpcalc on each file, at the expense of disk
|
Caching fingerprints avoids having to re-run chromaprint on each file, at the expense of
|
||||||
usage.
|
disk usage.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -66,7 +66,8 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Erasing introduction timestamps is only necessary after upgrading the plugin if specifically
|
Erasing introduction timestamps is only necessary after upgrading the plugin if specifically
|
||||||
requested to do so in the plugin's changelog. After the timestamps are erased, run the Analyze episodes scheduled task to re-analyze all media on the server.
|
requested to do so in the plugin's changelog. After the timestamps are erased, run the
|
||||||
|
Analyze episodes scheduled task to re-analyze all media on the server.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RootNamespace>ConfusedPolarBear.Plugin.IntroSkipper</RootNamespace>
|
<RootNamespace>ConfusedPolarBear.Plugin.IntroSkipper</RootNamespace>
|
||||||
<AssemblyVersion>0.1.0.0</AssemblyVersion>
|
<AssemblyVersion>0.1.5.0</AssemblyVersion>
|
||||||
<FileVersion>0.1.0.0</FileVersion>
|
<FileVersion>0.1.5.0</FileVersion>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
@ -51,10 +51,10 @@ public class Entrypoint : IServerEntryPoint
|
|||||||
{
|
{
|
||||||
FPCalc.Logger = _logger;
|
FPCalc.Logger = _logger;
|
||||||
|
|
||||||
// Assert that fpcalc is installed
|
// Assert that ffmpeg with chromaprint is installed
|
||||||
if (!FPCalc.CheckFPCalcInstalled())
|
if (!FPCalc.CheckFFmpegVersion())
|
||||||
{
|
{
|
||||||
_logger.LogError("fpcalc is not installed on this system - episodes will not be analyzed");
|
_logger.LogError("ffmpeg with chromaprint is not installed on this system - episodes will not be analyzed");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,12 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
// TODO: rename
|
||||||
|
|
||||||
namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wrapper for the fpcalc utility.
|
/// Wrapper for libchromaprint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class FPCalc
|
public static class FPCalc
|
||||||
{
|
{
|
||||||
@ -20,16 +22,16 @@ public static class FPCalc
|
|||||||
public static ILogger? Logger { get; set; }
|
public static ILogger? Logger { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check that the fpcalc utility is installed.
|
/// Check that the installed version of ffmpeg supports chromaprint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>true if fpcalc is installed, false on any error.</returns>
|
/// <returns>true if a compatible version of ffmpeg is installed, false on any error.</returns>
|
||||||
public static bool CheckFPCalcInstalled()
|
public static bool CheckFFmpegVersion()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var version = GetOutput("-version", 2000).TrimEnd();
|
var version = Encoding.UTF8.GetString(GetOutput("-version", 2000));
|
||||||
Logger?.LogInformation("fpcalc -version: {Version}", version);
|
Logger?.LogDebug("ffmpeg version: {Version}", version);
|
||||||
return version.StartsWith("fpcalc version", StringComparison.OrdinalIgnoreCase);
|
return version.Contains("--enable-chromaprint", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -44,7 +46,7 @@ public static class FPCalc
|
|||||||
/// <returns>Numerical fingerprint points.</returns>
|
/// <returns>Numerical fingerprint points.</returns>
|
||||||
public static ReadOnlyCollection<uint> Fingerprint(QueuedEpisode episode)
|
public static ReadOnlyCollection<uint> Fingerprint(QueuedEpisode episode)
|
||||||
{
|
{
|
||||||
// Try to load this episode from cache before running fpcalc.
|
// Try to load this episode from cache before running ffmpeg.
|
||||||
if (LoadCachedFingerprint(episode, out ReadOnlyCollection<uint> cachedFingerprint))
|
if (LoadCachedFingerprint(episode, out ReadOnlyCollection<uint> cachedFingerprint))
|
||||||
{
|
{
|
||||||
Logger?.LogDebug("Fingerprint cache hit on {File}", episode.Path);
|
Logger?.LogDebug("Fingerprint cache hit on {File}", episode.Path);
|
||||||
@ -53,32 +55,24 @@ public static class FPCalc
|
|||||||
|
|
||||||
Logger?.LogDebug("Fingerprinting {Duration} seconds from {File}", episode.FingerprintDuration, episode.Path);
|
Logger?.LogDebug("Fingerprinting {Duration} seconds from {File}", episode.FingerprintDuration, episode.Path);
|
||||||
|
|
||||||
// FIXME: revisit escaping
|
var args = string.Format(
|
||||||
var path = "\"" + episode.Path + "\"";
|
CultureInfo.InvariantCulture,
|
||||||
var duration = episode.FingerprintDuration.ToString(CultureInfo.InvariantCulture);
|
"-i \"{0}\" -to {1} -ac 2 -f chromaprint -fp_format raw -",
|
||||||
var args = " -raw -length " + duration + " " + path;
|
episode.Path,
|
||||||
|
episode.FingerprintDuration);
|
||||||
|
|
||||||
/* Returns output similar to the following:
|
// Returns all fingerprint points as raw 32 bit unsigned integers (little endian).
|
||||||
* DURATION=123
|
var rawPoints = GetOutput(args);
|
||||||
* FINGERPRINT=123456789,987654321,123456789,987654321,123456789,987654321
|
if (rawPoints.Length == 0 || rawPoints.Length % 4 != 0)
|
||||||
*/
|
|
||||||
|
|
||||||
var raw = GetOutput(args);
|
|
||||||
var lines = raw.Split("\n");
|
|
||||||
|
|
||||||
if (lines.Length < 2)
|
|
||||||
{
|
{
|
||||||
Logger?.LogTrace("fpcalc output is {Raw}", raw);
|
throw new FingerprintException("chromaprint output was malformed");
|
||||||
throw new FingerprintException("fpcalc output was malformed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the "FINGERPRINT=" prefix and split into an array of numbers.
|
|
||||||
var fingerprint = lines[1].Substring(12).Split(",");
|
|
||||||
|
|
||||||
var results = new List<uint>();
|
var results = new List<uint>();
|
||||||
foreach (var rawNumber in fingerprint)
|
for (var i = 0; i < rawPoints.Length; i += 4)
|
||||||
{
|
{
|
||||||
results.Add(Convert.ToUInt32(rawNumber, CultureInfo.InvariantCulture));
|
var rawPoint = rawPoints.Slice(i, 4);
|
||||||
|
results.Add(BitConverter.ToUInt32(rawPoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to cache this fingerprint.
|
// Try to cache this fingerprint.
|
||||||
@ -88,23 +82,43 @@ public static class FPCalc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs fpcalc and returns standard output.
|
/// Runs ffmpeg and returns standard output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">Arguments to pass to fpcalc.</param>
|
/// <param name="args">Arguments to pass to ffmpeg.</param>
|
||||||
/// <param name="timeout">Timeout (in seconds) to wait for fpcalc to exit.</param>
|
/// <param name="timeout">Timeout (in seconds) to wait for ffmpeg to exit.</param>
|
||||||
private static string GetOutput(string args, int timeout = 60 * 1000)
|
private static ReadOnlySpan<byte> GetOutput(string args, int timeout = 60 * 1000)
|
||||||
{
|
{
|
||||||
var info = new ProcessStartInfo("fpcalc", args);
|
var ffmpegPath = Plugin.Instance?.FFmpegPath ?? "ffmpeg";
|
||||||
info.CreateNoWindow = true;
|
|
||||||
info.RedirectStandardOutput = true;
|
|
||||||
|
|
||||||
var fpcalc = new Process();
|
var info = new ProcessStartInfo(ffmpegPath, args)
|
||||||
fpcalc.StartInfo = info;
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true
|
||||||
|
};
|
||||||
|
|
||||||
fpcalc.Start();
|
var ffmpeg = new Process
|
||||||
fpcalc.WaitForExit(timeout);
|
{
|
||||||
|
StartInfo = info
|
||||||
|
};
|
||||||
|
|
||||||
return fpcalc.StandardOutput.ReadToEnd();
|
ffmpeg.Start();
|
||||||
|
ffmpeg.WaitForExit(timeout);
|
||||||
|
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
var buf = new byte[4096];
|
||||||
|
var bytesRead = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bytesRead = ffmpeg.StandardOutput.BaseStream.Read(buf, 0, buf.Length);
|
||||||
|
ms.Write(buf, 0, bytesRead);
|
||||||
|
}
|
||||||
|
while (bytesRead > 0);
|
||||||
|
|
||||||
|
return ms.ToArray().AsSpan();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
@ -23,7 +24,11 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||||
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
/// <param name="serverConfiguration">Server configuration manager.</param>
|
||||||
|
public Plugin(
|
||||||
|
IApplicationPaths applicationPaths,
|
||||||
|
IXmlSerializer xmlSerializer,
|
||||||
|
IServerConfigurationManager serverConfiguration)
|
||||||
: base(applicationPaths, xmlSerializer)
|
: base(applicationPaths, xmlSerializer)
|
||||||
{
|
{
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
@ -37,6 +42,9 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
|
|
||||||
_introPath = Path.Join(applicationPaths.PluginConfigurationsPath, "intros", "intros.xml");
|
_introPath = Path.Join(applicationPaths.PluginConfigurationsPath, "intros", "intros.xml");
|
||||||
|
|
||||||
|
// Get the path to FFmpeg.
|
||||||
|
FFmpegPath = serverConfiguration.GetEncodingOptions().EncoderAppPathDisplay;
|
||||||
|
|
||||||
Intros = new Dictionary<Guid, Intro>();
|
Intros = new Dictionary<Guid, Intro>();
|
||||||
AnalysisQueue = new Dictionary<Guid, List<QueuedEpisode>>();
|
AnalysisQueue = new Dictionary<Guid, List<QueuedEpisode>>();
|
||||||
Instance = this;
|
Instance = this;
|
||||||
@ -64,6 +72,11 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string FingerprintCachePath { get; private set; }
|
public string FingerprintCachePath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full path to FFmpeg.
|
||||||
|
/// </summary>
|
||||||
|
public string FFmpegPath { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string Name => "Intro Skipper";
|
public override string Name => "Intro Skipper";
|
||||||
|
|
||||||
|
@ -104,6 +104,12 @@ public class FingerprinterTask : IScheduledTask
|
|||||||
var queue = Plugin.Instance!.AnalysisQueue;
|
var queue = Plugin.Instance!.AnalysisQueue;
|
||||||
var totalProcessed = 0;
|
var totalProcessed = 0;
|
||||||
|
|
||||||
|
if (queue.Count == 0)
|
||||||
|
{
|
||||||
|
throw new FingerprintException(
|
||||||
|
"No episodes to analyze: either no show libraries are defined or ffmpeg is not properly installed");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: make configurable
|
// TODO: make configurable
|
||||||
var options = new ParallelOptions();
|
var options = new ParallelOptions();
|
||||||
options.MaxDegreeOfParallelism = 2;
|
options.MaxDegreeOfParallelism = 2;
|
||||||
|
13
README.md
13
README.md
@ -1,18 +1,16 @@
|
|||||||
# Intro Skipper (ALPHA)
|
# Intro Skipper (beta)
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img alt="Plugin Banner" src="https://raw.githubusercontent.com/ConfusedPolarBear/intro-skipper/master/images/logo.png" />
|
<img alt="Plugin Banner" src="https://raw.githubusercontent.com/ConfusedPolarBear/intro-skipper/master/images/logo.png" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Analyzes the audio of television episodes to detect and skip over intros. Currently in alpha.
|
Analyzes the audio of television episodes to detect and skip over intros. Jellyfin must use a version of `ffmpeg` that has been compiled with `--enable-chromaprint` (`jellyfin-ffmpeg` versions 5.0.1-5 and later will work).
|
||||||
|
|
||||||
Installing this plugin (along with a modified web interface and `fpcalc`) will result in a skip intro button displaying in the video player, like this:
|
If you use the custom web interface on your server, you will be able to click a skip button to skip intros, like this:
|
||||||
|
|
||||||
![Skip intro button](images/skip-button.png)
|
![Skip intro button](images/skip-button.png)
|
||||||
|
|
||||||
If you use Jellyfin clients that do not use the web interface provided by the server, the plugin can be configured to automatically skip intros.
|
However, if you use Jellyfin clients that do not use the web interface provided by the server, the plugin can be configured to automatically skip intros.
|
||||||
|
|
||||||
This plugin **will not work** without installing `fpcalc`. The easiest way to do this is to follow the steps below.
|
|
||||||
|
|
||||||
## Introduction requirements
|
## Introduction requirements
|
||||||
|
|
||||||
@ -21,7 +19,8 @@ Show introductions will only 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
|
||||||
* At least 20 seconds long
|
* At least 20 seconds long
|
||||||
|
|
||||||
## Step 1: Install the modified web interface + fpcalc
|
## Step 1: Install the modified web interface (optional)
|
||||||
|
This step is only necessary if you do not use the automatic skip feature.
|
||||||
1. Run the `ghcr.io/confusedpolarbear/jellyfin-intro-skipper` container just as you would any other Jellyfin container
|
1. Run the `ghcr.io/confusedpolarbear/jellyfin-intro-skipper` container just as you would any other Jellyfin container
|
||||||
1. If you reuse the configuration data from another container, **make sure to create a backup first**.
|
1. If you reuse the configuration data from another container, **make sure to create a backup first**.
|
||||||
2. Follow the plugin installation steps below
|
2. Follow the plugin installation steps below
|
||||||
|
10
docker/Dockerfile.ffmpeg5
Normal file
10
docker/Dockerfile.ffmpeg5
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM jellyfin/jellyfin:10.8.0-beta3
|
||||||
|
|
||||||
|
RUN curl -Lo jellyfin-ffmpeg5.deb \
|
||||||
|
https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v5.0.1-5/jellyfin-ffmpeg5_5.0.1-5-bullseye_amd64.deb
|
||||||
|
|
||||||
|
RUN apt update
|
||||||
|
RUN apt install -y libxcb-randr0
|
||||||
|
RUN dpkg -i jellyfin-ffmpeg5.deb
|
||||||
|
|
||||||
|
COPY dist/ /jellyfin/jellyfin-web/
|
@ -4,3 +4,8 @@ Build instructions for the `ghcr.io/confusedpolarbear/jellyfin-intro-skipper` co
|
|||||||
2. Run `npm run build:production`
|
2. Run `npm run build:production`
|
||||||
3. Copy the `dist` folder into this folder
|
3. Copy the `dist` folder into this folder
|
||||||
4. Run `docker build .`
|
4. Run `docker build .`
|
||||||
|
|
||||||
|
## `Dockerfile.ffmpeg5` testing instructions
|
||||||
|
|
||||||
|
1. Follow steps 1 - 3 above (only needed if you don't use the automatic skip feature)
|
||||||
|
2. Run `docker build . -t jellyfin-ffmpeg5 -f Dockerfile.ffmpeg5`
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* Jellyfin 10.8.0 beta 2 (beta 3 may also work, untested)
|
* Jellyfin 10.8.0 beta 3
|
||||||
* Compiled [jellyfin-web](https://github.com/ConfusedPolarBear/jellyfin-web/tree/intros) interface with intro skip button
|
* Compiled [jellyfin-web](https://github.com/ConfusedPolarBear/jellyfin-web/tree/intros) interface with intro skip button
|
||||||
* [chromaprint](https://github.com/acoustid/chromaprint) (only versions 1.4.3 and later have been verified to work)
|
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
1. Install the `fpcalc` program
|
1. Install the latest version of ffmpeg from https://github.com/jellyfin/jellyfin-ffmpeg/releases
|
||||||
1. On Debian based distributions, this is provided by the `libchromaprint-tools` package
|
|
||||||
2. Compiled binaries can also be downloaded from the [GitHub repository](https://github.com/acoustid/chromaprint/releases/tag/v1.5.1)
|
|
||||||
2. Download the latest modified web interface from the releases tab and either:
|
2. Download the latest modified web interface from the releases tab and either:
|
||||||
1. Serve the web interface directly from your Jellyfin server, or
|
1. Serve the web interface directly from your Jellyfin server, or
|
||||||
2. Serve the web interface using an external web server
|
2. Serve the web interface using an external web server
|
||||||
|
Loading…
x
Reference in New Issue
Block a user