From 69ddd7a20de629518f4170e8bf28c7f3ba567a74 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Thu, 25 Aug 2022 00:39:20 -0500 Subject: [PATCH] Add support bundle Bundles include the following information: - Jellyfin version - Plugin version - Chromaprint detection status and logs --- .../Chromaprint.cs | 69 ++++++++++++++++--- .../Configuration/configPage.html | 41 +++++++++++ .../Controllers/TroubleshootingController.cs | 65 +++++++++++++++++ 3 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 ConfusedPolarBear.Plugin.IntroSkipper/Controllers/TroubleshootingController.cs diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Chromaprint.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Chromaprint.cs index c969f61..68fd809 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Chromaprint.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Chromaprint.cs @@ -13,13 +13,13 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper; /// public static class Chromaprint { - private static bool _loggedVersionInformation; - /// /// Gets or sets the logger. /// public static ILogger? Logger { get; set; } + private static Dictionary ChromaprintLogs { get; set; } = new(); + /// /// Check that the installed version of ffmpeg supports chromaprint. /// @@ -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")); } + + /// + /// Gets Chromaprint debugging logs. + /// + /// Markdown formatted logs. + 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(); + } } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html index ec3401a..a385500 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html @@ -211,6 +211,12 @@ +
+ Support Bundle + + +
+
Analysis Statistics (experimental) @@ -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); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/TroubleshootingController.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/TroubleshootingController.cs new file mode 100644 index 0000000..3554feb --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/TroubleshootingController.cs @@ -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; + +/// +/// Troubleshooting controller. +/// +[Authorize(Policy = "RequiresElevation")] +[ApiController] +[Produces(MediaTypeNames.Application.Json)] +[Route("IntroSkipper")] +public class TroubleshootingController : ControllerBase +{ + private readonly IApplicationHost _applicationHost; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Application host. + /// Logger. + public TroubleshootingController( + IApplicationHost applicationHost, + ILogger logger) + { + _applicationHost = applicationHost; + _logger = logger; + } + + /// + /// Gets a Markdown formatted support bundle. + /// + /// Support bundle created. + /// Support bundle. + [HttpGet("SupportBundle")] + [Produces(MediaTypeNames.Text.Plain)] + public ActionResult 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(); + } +}