Add support bundle

Bundles include the following information:
- Jellyfin version
- Plugin version
- Chromaprint detection status and logs
This commit is contained in:
ConfusedPolarBear 2022-08-25 00:39:20 -05:00
parent 6f86fa935e
commit 69ddd7a20d
3 changed files with 166 additions and 9 deletions

View File

@ -13,13 +13,13 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// </summary>
public static class Chromaprint
{
private static bool _loggedVersionInformation;
/// <summary>
/// Gets or sets the logger.
/// </summary>
public static ILogger? Logger { get; set; }
private static Dictionary<string, string> ChromaprintLogs { get; set; } = new();
/// <summary>
/// Check that the installed version of ffmpeg supports chromaprint.
/// </summary>
@ -28,44 +28,47 @@ public static class Chromaprint
{
try
{
// Log the output of "ffmpeg -version" at the first call to this function
if (!_loggedVersionInformation)
{
_loggedVersionInformation = true;
var version = Encoding.UTF8.GetString(GetOutput("-version", 2000));
Logger?.LogDebug("ffmpeg version information: {Version}", version);
}
// Log the output of "ffmpeg -version".
ChromaprintLogs["version"] = Encoding.UTF8.GetString(GetOutput("-version", 2000));
Logger?.LogDebug("ffmpeg version information: {Version}", ChromaprintLogs["version"]);
// First, validate that the installed version of ffmpeg supports chromaprint at all.
var muxers = Encoding.UTF8.GetString(GetOutput("-muxers", 2000));
ChromaprintLogs["muxer list"] = muxers;
Logger?.LogTrace("ffmpeg muxers: {Muxers}", muxers);
if (!muxers.Contains("chromaprint", StringComparison.OrdinalIgnoreCase))
{
ChromaprintLogs["error"] = "muxer_not_supported";
Logger?.LogError("The installed version of ffmpeg does not support chromaprint");
return false;
}
// Second, validate that ffmpeg understands the "-fp_format raw" option.
var muxerHelp = Encoding.UTF8.GetString(GetOutput("-h muxer=chromaprint", 2000));
ChromaprintLogs["muxer options"] = muxerHelp;
Logger?.LogTrace("ffmpeg chromaprint help: {MuxerHelp}", muxerHelp);
if (!muxerHelp.Contains("-fp_format", StringComparison.OrdinalIgnoreCase))
{
ChromaprintLogs["error"] = "fp_format_not_supported";
Logger?.LogError("The installed version of ffmpeg does not support the -fp_format flag");
return false;
}
else if (!muxerHelp.Contains("binary raw fingerprint", StringComparison.OrdinalIgnoreCase))
{
ChromaprintLogs["error"] = "fp_format_raw_not_supported";
Logger?.LogError("The installed version of ffmpeg does not support raw binary fingerprints");
return false;
}
Logger?.LogDebug("Installed version of ffmpeg meets fingerprinting requirements");
ChromaprintLogs["error"] = "okay";
return true;
}
catch
{
ChromaprintLogs["error"] = "unknown_error";
return false;
}
}
@ -273,4 +276,52 @@ public static class Chromaprint
{
return Path.Join(Plugin.Instance!.FingerprintCachePath, episode.EpisodeId.ToString("N"));
}
/// <summary>
/// Gets Chromaprint debugging logs.
/// </summary>
/// <returns>Markdown formatted logs.</returns>
public static string GetChromaprintLogs()
{
var logs = new StringBuilder(1024);
// Print the Chromaprint detection status at the top.
// Format: "* Chromaprint: `error`"
logs.Append("* Chromaprint: `");
logs.Append(ChromaprintLogs["error"]);
logs.Append("`\n\n"); // Use two newlines to separate the bulleted list from the logs
// Print all remaining logs
foreach (var kvp in ChromaprintLogs)
{
var name = kvp.Key;
var contents = kvp.Value;
if (string.Equals(name, "error", StringComparison.OrdinalIgnoreCase))
{
continue;
}
/* Format:
* FFmpeg NAME:
* ```
* LOGS
* ```
*/
logs.Append("FFmpeg ");
logs.Append(name);
logs.Append(":\n```\n");
logs.Append(contents);
// ensure the closing triple backtick is on a separate line
if (!contents.EndsWith('\n'))
{
logs.Append('\n');
}
logs.Append("```\n\n");
}
return logs.ToString();
}
}

View File

@ -211,6 +211,12 @@
</form>
</div>
<details id="support">
<summary>Support Bundle</summary>
<textarea id="supportBundle" rows="20" cols="75" readonly></textarea>
</details>
<details id="statistics">
<summary>Analysis Statistics (experimental)</summary>
@ -326,6 +332,7 @@
// settings elements
var visualizer = document.querySelector("details#visualizer");
var statistics = document.querySelector("details#statistics");
var support = document.querySelector("details#support");
var btnEraseTimestamps = document.querySelector("button#btnEraseTimestamps");
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
@ -377,6 +384,39 @@
Dashboard.hideLoadingMsg();
}
// fetch the support bundle whenever the detail section is opened.
async function supportToggled() {
if (!support.open) {
return;
}
// Fetch the support bundle
const bundle = await fetchWithAuth("IntroSkipper/SupportBundle", "GET", null);
const bundleText = await bundle.text();
// Display it to the user
document.querySelector("textarea#supportBundle").value = bundleText;
// Attempt to copy it to the clipboard automatically, falling back to selecting
// all text and prompting the user to press ctrl c.
try {
navigator.clipboard.writeText(bundleText)
Dashboard.alert("Support bundle copied to clipboard");
} catch {
supportBundleCopyFailed();
}
}
// if the automatic bundle copy fails (likely due to an insecure context), have the
// user copy it manually.
function supportBundleCopyFailed() {
const ta = document.querySelector("textarea#supportBundle");
ta.focus();
ta.setSelectionRange(0, ta.value.length);
Dashboard.alert("Press Ctrl+C to copy support bundle");
}
async function statisticsToggled() {
if (!statistics.open) {
return;
@ -660,6 +700,7 @@
visualizer.addEventListener("toggle", visualizerToggled);
statistics.addEventListener("toggle", statisticsToggled);
support.addEventListener("toggle", supportToggled);
txtOffset.addEventListener("change", renderTroubleshooter);
selectShow.addEventListener("change", showChanged);
selectSeason.addEventListener("change", seasonChanged);

View File

@ -0,0 +1,65 @@
using System.Net.Mime;
using System.Text;
using MediaBrowser.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace ConfusedPolarBear.Plugin.IntroSkipper.Controllers;
/// <summary>
/// Troubleshooting controller.
/// </summary>
[Authorize(Policy = "RequiresElevation")]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
[Route("IntroSkipper")]
public class TroubleshootingController : ControllerBase
{
private readonly IApplicationHost _applicationHost;
private readonly ILogger<TroubleshootingController> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="TroubleshootingController"/> class.
/// </summary>
/// <param name="applicationHost">Application host.</param>
/// <param name="logger">Logger.</param>
public TroubleshootingController(
IApplicationHost applicationHost,
ILogger<TroubleshootingController> logger)
{
_applicationHost = applicationHost;
_logger = logger;
}
/// <summary>
/// Gets a Markdown formatted support bundle.
/// </summary>
/// <response code="200">Support bundle created.</response>
/// <returns>Support bundle.</returns>
[HttpGet("SupportBundle")]
[Produces(MediaTypeNames.Text.Plain)]
public ActionResult<string> GetSupportBundle()
{
var bundle = new StringBuilder();
bundle.Append("* Jellyfin version: ");
bundle.Append(_applicationHost.ApplicationVersionString);
bundle.Append('\n');
bundle.Append("* Plugin version: ");
bundle.Append(Plugin.Instance!.Version.ToString(3));
bundle.Append('\n');
bundle.Append("* Queue contents: ");
bundle.Append(Plugin.Instance!.TotalQueued);
bundle.Append(" episodes, ");
bundle.Append(Plugin.Instance!.AnalysisQueue.Count);
bundle.Append(" seasons");
bundle.Append('\n');
bundle.Append(Chromaprint.GetChromaprintLogs());
return bundle.ToString();
}
}