Reorganize FFmpeg wrapper
This commit is contained in:
parent
ff9ba16300
commit
af89e5f2b4
@ -98,38 +98,6 @@ public static class FFmpegWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run an FFmpeg command with the provided arguments and validate that the output contains
|
|
||||||
/// the provided string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="arguments">Arguments to pass to FFmpeg.</param>
|
|
||||||
/// <param name="mustContain">String that the output must contain. Case insensitive.</param>
|
|
||||||
/// <param name="bundleName">Support bundle key to store FFmpeg's output under.</param>
|
|
||||||
/// <param name="errorMessage">Error message to log if this requirement is not met.</param>
|
|
||||||
/// <returns>true on success, false on error.</returns>
|
|
||||||
private static bool CheckFFmpegRequirement(
|
|
||||||
string arguments,
|
|
||||||
string mustContain,
|
|
||||||
string bundleName,
|
|
||||||
string errorMessage)
|
|
||||||
{
|
|
||||||
Logger?.LogDebug("Checking FFmpeg requirement {Arguments}", arguments);
|
|
||||||
|
|
||||||
var output = Encoding.UTF8.GetString(GetOutput(arguments, string.Empty, false, 2000));
|
|
||||||
Logger?.LogTrace("Output of ffmpeg {Arguments}: {Output}", arguments, output);
|
|
||||||
ChromaprintLogs[bundleName] = output;
|
|
||||||
|
|
||||||
if (!output.Contains(mustContain, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Logger?.LogError("{ErrorMessage}", errorMessage);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger?.LogDebug("FFmpeg requirement {Arguments} met", arguments);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fingerprint a queued episode.
|
/// Fingerprint a queued episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -158,58 +126,6 @@ public static class FFmpegWrapper
|
|||||||
return Fingerprint(episode, mode, start, end);
|
return Fingerprint(episode, mode, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fingerprint a queued episode.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="episode">Queued episode to fingerprint.</param>
|
|
||||||
/// <param name="mode">Portion of media file to fingerprint.</param>
|
|
||||||
/// <param name="start">Time (in seconds) relative to the start of the file to start fingerprinting from.</param>
|
|
||||||
/// <param name="end">Time (in seconds) relative to the start of the file to stop fingerprinting at.</param>
|
|
||||||
/// <returns>Numerical fingerprint points.</returns>
|
|
||||||
private static uint[] Fingerprint(QueuedEpisode episode, AnalysisMode mode, int start, int end)
|
|
||||||
{
|
|
||||||
// Try to load this episode from cache before running ffmpeg.
|
|
||||||
if (LoadCachedFingerprint(episode, mode, out uint[] cachedFingerprint))
|
|
||||||
{
|
|
||||||
Logger?.LogTrace("Fingerprint cache hit on {File}", episode.Path);
|
|
||||||
return cachedFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger?.LogDebug(
|
|
||||||
"Fingerprinting [{Start}, {End}] from \"{File}\" (id {Id})",
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
episode.Path,
|
|
||||||
episode.EpisodeId);
|
|
||||||
|
|
||||||
var args = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"-ss {0} -i \"{1}\" -to {2} -ac 2 -f chromaprint -fp_format raw -",
|
|
||||||
start,
|
|
||||||
episode.Path,
|
|
||||||
end - start);
|
|
||||||
|
|
||||||
// Returns all fingerprint points as raw 32 bit unsigned integers (little endian).
|
|
||||||
var rawPoints = GetOutput(args, string.Empty);
|
|
||||||
if (rawPoints.Length == 0 || rawPoints.Length % 4 != 0)
|
|
||||||
{
|
|
||||||
Logger?.LogWarning("Chromaprint returned {Count} points for \"{Path}\"", rawPoints.Length, episode.Path);
|
|
||||||
throw new FingerprintException("chromaprint output for \"" + episode.Path + "\" was malformed");
|
|
||||||
}
|
|
||||||
|
|
||||||
var results = new List<uint>();
|
|
||||||
for (var i = 0; i < rawPoints.Length; i += 4)
|
|
||||||
{
|
|
||||||
var rawPoint = rawPoints.Slice(i, 4);
|
|
||||||
results.Add(BitConverter.ToUInt32(rawPoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to cache this fingerprint.
|
|
||||||
CacheFingerprint(episode, mode, results);
|
|
||||||
|
|
||||||
return results.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transforms a Chromaprint into an inverted index of fingerprint points to the last index it appeared at.
|
/// Transforms a Chromaprint into an inverted index of fingerprint points to the last index it appeared at.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -295,6 +211,75 @@ public static class FFmpegWrapper
|
|||||||
return silenceRanges.ToArray();
|
return silenceRanges.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets Chromaprint debugging logs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Markdown formatted logs.</returns>
|
||||||
|
public static string GetChromaprintLogs()
|
||||||
|
{
|
||||||
|
// Print the FFmpeg detection status at the top.
|
||||||
|
// Format: "* FFmpeg: `error`"
|
||||||
|
// Append two newlines to separate the bulleted list from the logs
|
||||||
|
var logs = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"* FFmpeg: `{0}`\n\n",
|
||||||
|
ChromaprintLogs["error"]);
|
||||||
|
|
||||||
|
// Always include ffmpeg version information
|
||||||
|
logs += FormatFFmpegLog("version");
|
||||||
|
|
||||||
|
// Don't print feature detection logs if the plugin started up okay
|
||||||
|
if (ChromaprintLogs["error"] == "okay")
|
||||||
|
{
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print all remaining logs
|
||||||
|
foreach (var kvp in ChromaprintLogs)
|
||||||
|
{
|
||||||
|
if (kvp.Key == "error" || kvp.Key == "version")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logs += FormatFFmpegLog(kvp.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run an FFmpeg command with the provided arguments and validate that the output contains
|
||||||
|
/// the provided string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="arguments">Arguments to pass to FFmpeg.</param>
|
||||||
|
/// <param name="mustContain">String that the output must contain. Case insensitive.</param>
|
||||||
|
/// <param name="bundleName">Support bundle key to store FFmpeg's output under.</param>
|
||||||
|
/// <param name="errorMessage">Error message to log if this requirement is not met.</param>
|
||||||
|
/// <returns>true on success, false on error.</returns>
|
||||||
|
private static bool CheckFFmpegRequirement(
|
||||||
|
string arguments,
|
||||||
|
string mustContain,
|
||||||
|
string bundleName,
|
||||||
|
string errorMessage)
|
||||||
|
{
|
||||||
|
Logger?.LogDebug("Checking FFmpeg requirement {Arguments}", arguments);
|
||||||
|
|
||||||
|
var output = Encoding.UTF8.GetString(GetOutput(arguments, string.Empty, false, 2000));
|
||||||
|
Logger?.LogTrace("Output of ffmpeg {Arguments}: {Output}", arguments, output);
|
||||||
|
ChromaprintLogs[bundleName] = output;
|
||||||
|
|
||||||
|
if (!output.Contains(mustContain, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Logger?.LogError("{ErrorMessage}", errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger?.LogDebug("FFmpeg requirement {Arguments} met", arguments);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs ffmpeg and returns standard output (or error).
|
/// Runs ffmpeg and returns standard output (or error).
|
||||||
/// If caching is enabled, will use cacheFilename to cache the output of this command.
|
/// If caching is enabled, will use cacheFilename to cache the output of this command.
|
||||||
@ -392,6 +377,58 @@ public static class FFmpegWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fingerprint a queued episode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">Queued episode to fingerprint.</param>
|
||||||
|
/// <param name="mode">Portion of media file to fingerprint.</param>
|
||||||
|
/// <param name="start">Time (in seconds) relative to the start of the file to start fingerprinting from.</param>
|
||||||
|
/// <param name="end">Time (in seconds) relative to the start of the file to stop fingerprinting at.</param>
|
||||||
|
/// <returns>Numerical fingerprint points.</returns>
|
||||||
|
private static uint[] Fingerprint(QueuedEpisode episode, AnalysisMode mode, int start, int end)
|
||||||
|
{
|
||||||
|
// Try to load this episode from cache before running ffmpeg.
|
||||||
|
if (LoadCachedFingerprint(episode, mode, out uint[] cachedFingerprint))
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Fingerprint cache hit on {File}", episode.Path);
|
||||||
|
return cachedFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger?.LogDebug(
|
||||||
|
"Fingerprinting [{Start}, {End}] from \"{File}\" (id {Id})",
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
episode.Path,
|
||||||
|
episode.EpisodeId);
|
||||||
|
|
||||||
|
var args = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"-ss {0} -i \"{1}\" -to {2} -ac 2 -f chromaprint -fp_format raw -",
|
||||||
|
start,
|
||||||
|
episode.Path,
|
||||||
|
end - start);
|
||||||
|
|
||||||
|
// Returns all fingerprint points as raw 32 bit unsigned integers (little endian).
|
||||||
|
var rawPoints = GetOutput(args, string.Empty);
|
||||||
|
if (rawPoints.Length == 0 || rawPoints.Length % 4 != 0)
|
||||||
|
{
|
||||||
|
Logger?.LogWarning("Chromaprint returned {Count} points for \"{Path}\"", rawPoints.Length, episode.Path);
|
||||||
|
throw new FingerprintException("chromaprint output for \"" + episode.Path + "\" was malformed");
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = new List<uint>();
|
||||||
|
for (var i = 0; i < rawPoints.Length; i += 4)
|
||||||
|
{
|
||||||
|
var rawPoint = rawPoints.Slice(i, 4);
|
||||||
|
results.Add(BitConverter.ToUInt32(rawPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to cache this fingerprint.
|
||||||
|
CacheFingerprint(episode, mode, results);
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to load an episode's fingerprint from cache. If caching is not enabled, calling this function is a no-op.
|
/// Tries to load an episode's fingerprint from cache. If caching is not enabled, calling this function is a no-op.
|
||||||
/// This function was created before the unified caching mechanism was introduced (in v0.1.7).
|
/// This function was created before the unified caching mechanism was introduced (in v0.1.7).
|
||||||
@ -507,43 +544,6 @@ public static class FFmpegWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets Chromaprint debugging logs.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Markdown formatted logs.</returns>
|
|
||||||
public static string GetChromaprintLogs()
|
|
||||||
{
|
|
||||||
// Print the FFmpeg detection status at the top.
|
|
||||||
// Format: "* FFmpeg: `error`"
|
|
||||||
// Append two newlines to separate the bulleted list from the logs
|
|
||||||
var logs = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"* FFmpeg: `{0}`\n\n",
|
|
||||||
ChromaprintLogs["error"]);
|
|
||||||
|
|
||||||
// Always include ffmpeg version information
|
|
||||||
logs += FormatFFmpegLog("version");
|
|
||||||
|
|
||||||
// Don't print feature detection logs if the plugin started up okay
|
|
||||||
if (ChromaprintLogs["error"] == "okay")
|
|
||||||
{
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print all remaining logs
|
|
||||||
foreach (var kvp in ChromaprintLogs)
|
|
||||||
{
|
|
||||||
if (kvp.Key == "error" || kvp.Key == "version")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
logs += FormatFFmpegLog(kvp.Key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatFFmpegLog(string key)
|
private static string FormatFFmpegLog(string key)
|
||||||
{
|
{
|
||||||
/* Format:
|
/* Format:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user