2020-07-04 23:33:19 +09:00
<!DOCTYPE html>
< html lang = "en" >
2022-05-30 02:23:36 -05:00
2020-07-04 23:33:19 +09:00
< head >
< meta charset = "utf-8" >
< / head >
2022-05-30 02:23:36 -05:00
2020-07-04 23:33:19 +09:00
< body >
2022-05-30 02:23:36 -05:00
< div id = "TemplateConfigPage" data-role = "page" class = "page type-interior pluginConfigurationPage"
2024-05-13 14:25:52 +02:00
data-require="emby-input,emby-button,emby-select,emby-checkbox,emby-linkbutton">
2020-07-04 23:33:19 +09:00
< div data-role = "content" >
2024-03-01 19:56:43 -05:00
< style >
summary {
2024-05-13 14:25:52 +02:00
cursor: pointer;
padding: 10px;
width: inherit;
margin: auto;
border: none;
text-align: center;
outline: none;
font-size: 1.0em;
outline: 2px solid rgba(155, 155, 155, 0.5);
2024-03-01 19:56:43 -05:00
}
< / style >
2020-07-04 23:33:19 +09:00
< div class = "content-primary" >
2022-05-05 18:10:34 -05:00
< form id = "FingerprintConfigForm" >
2022-06-09 20:34:18 -05:00
< fieldset class = "verticalSection-extrabottompadding" >
< legend > Analysis< / legend >
2024-03-30 19:02:18 +01:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
2024-03-30 18:22:54 -04:00
< input id = "AutoDetectIntros" type = "checkbox" is = "emby-checkbox" / >
< span > Automatically Scan Intros< / span >
2024-03-30 19:02:18 +01:00
< / label >
< div class = "fieldDescription" >
2024-03-30 18:22:54 -04:00
If enabled, introductions will be automatically analyzed for new media
2024-03-30 19:02:18 +01:00
< / div >
< / div >
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
2024-03-30 18:22:54 -04:00
< input id = "AutoDetectCredits" type = "checkbox" is = "emby-checkbox" / >
< span > Automatically Scan Credits< / span >
2024-03-30 19:02:18 +01:00
< / label >
< div class = "fieldDescription" >
2024-03-30 18:22:54 -04:00
If enabled, credits will be automatically analyzed for new media
2024-04-14 01:13:43 -04:00
< br / >
< br / >
2024-05-14 03:49:30 -05:00
Note: Not selecting at least one automatic detection type will disable automatic scans. To configure the scheduled task, see < a is = "emby-linkbutton" class = "button-link" href = "#/dashboard/tasks" > scheduled tasks< / a > .
2024-03-30 19:02:18 +01:00
< / div >
< / div >
2022-06-09 20:34:18 -05:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
2022-09-27 21:42:51 -05:00
< input id = "AnalyzeSeasonZero" type = "checkbox" is = "emby-checkbox" / >
2024-03-04 11:51:55 -05:00
< span > Analyze season 0< / span >
2022-06-09 20:34:18 -05:00
< / label >
< div class = "fieldDescription" >
2024-03-04 11:51:55 -05:00
If checked, season 0 (specials / extras) will be included in analysis.
< br / >
2024-04-14 01:13:43 -04:00
< br / >
2024-03-04 11:51:55 -05:00
Note: Shows containing both a specials and extra folder will identify extras as season 0
and ignore specials, regardless of this setting.
2022-06-09 20:34:18 -05:00
< / div >
2022-06-07 18:33:59 -05:00
< / div >
2022-05-05 18:10:34 -05:00
2022-06-09 20:34:18 -05:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "MaxParallelism" >
Maximum degree of parallelism
< / label >
< input id = "MaxParallelism" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" >
2024-03-04 18:23:42 -05:00
Maximum number of simultaneous async episode analysis operations.
2022-06-09 20:34:18 -05:00
< / div >
< / div >
2022-06-27 00:21:30 -05:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SelectedLibraries" >
Limit analysis to the following libraries
< / label >
< input id = "SelectedLibraries" type = "text" is = "emby-input" / >
< div class = "fieldDescription" >
Enter the names of libraries to analyze, separated by commas. If this field is left
blank, all libraries on the server containing television episodes will be analyzed.
< / div >
< / div >
2022-07-08 00:57:12 -05:00
2022-07-29 03:34:55 -05:00
< details id = "intro_reqs" >
2024-04-13 14:58:19 -04:00
< summary > Modify Segment Parameters< / summary >
2022-06-26 22:54:47 -05:00
2024-03-01 19:56:43 -05:00
< br / >
2022-06-26 22:54:47 -05:00
< div class = "inputContainer" >
2022-07-16 21:53:08 -05:00
< label class = "inputLabel inputLabelUnfocused" for = "AnalysisPercent" >
2022-06-26 22:54:47 -05:00
Percent of audio to analyze
< / label >
< input id = "AnalysisPercent" type = "number" is = "emby-input" min = "1" max = "90" / >
< div class = "fieldDescription" >
Analysis will be limited to this percentage of each episode's audio. For example, a
value of 25 (the default) will limit analysis to the first quarter of each episode.
< / div >
< / div >
< div class = "inputContainer" >
2022-07-16 21:53:08 -05:00
< label class = "inputLabel inputLabelUnfocused" for = "AnalysisLengthLimit" >
2022-06-26 22:54:47 -05:00
Maximum runtime of audio to analyze (in minutes)
< / label >
< input id = "AnalysisLengthLimit" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" >
Analysis will be limited to this amount of each episode's audio. For example, a
value of 10 (the default) will limit analysis to the first 10 minutes of each
episode.
< / div >
< / div >
2022-07-03 01:59:16 -05:00
< div class = "inputContainer" >
2022-07-16 22:12:42 -05:00
< label class = "inputLabel inputLabelUnfocused" for = "MinimumIntroDuration" >
2022-07-03 01:59:16 -05:00
Minimum introduction duration (in seconds)
< / label >
2022-07-16 22:12:42 -05:00
< input id = "MinimumIntroDuration" type = "number" is = "emby-input" min = "1" / >
2022-07-03 01:59:16 -05:00
< div class = "fieldDescription" >
Similar sounding audio which is shorter than this duration will not be considered an
introduction.
< / div >
< / div >
2022-06-26 22:54:47 -05:00
2022-09-03 00:39:35 -05:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "MaximumIntroDuration" >
Maximum introduction duration (in seconds)
< / label >
< input id = "MaximumIntroDuration" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" >
Similar sounding audio which is longer than this duration will not be considered an
introduction.
< / div >
< / div >
2024-04-13 14:07:12 -04:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "MinimumCreditsDuration" >
Minimum credits duration (in seconds)
< / label >
< input id = "MinimumCreditsDuration" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" >
Similar sounding audio which is shorter than this duration will not be considered credits.
< / div >
< / div >
< div class = "inputContainer" >
2024-04-16 18:19:05 +02:00
< label class = "inputLabel inputLabelUnfocused" for = "MaximumCreditsDuration" >
2024-04-13 14:07:12 -04:00
Maximum credits duration (in seconds)
< / label >
2024-04-16 18:19:05 +02:00
< input id = "MaximumCreditsDuration" type = "number" is = "emby-input" min = "1" / >
2024-04-13 14:07:12 -04:00
< div class = "fieldDescription" >
Similar sounding audio which is longer than this duration will not be considered credits.
< / div >
< / div >
2022-07-03 01:59:16 -05:00
< p >
The amount of each episode's audio that will be analyzed is determined using both
the percentage of audio and maximum runtime of audio to analyze. The minimum of
(episode duration * percent, maximum runtime) is the amount of audio that will
2022-07-08 00:57:12 -05:00
be analyzed.
2022-06-26 22:54:47 -05:00
< / p >
2022-07-08 00:57:12 -05:00
< p >
If the audio percentage or maximum runtime settings are modified, the cached
fingerprints and introduction timestamps for each season you want to analyze with the
modified settings < strong > will have to be deleted.< / strong >
2022-07-03 01:59:16 -05:00
Increasing either of these settings will cause episode analysis to take much longer.
2022-07-08 00:57:12 -05:00
< / p >
2022-06-26 22:54:47 -05:00
< / details >
2022-09-02 01:27:49 -05:00
2024-03-04 07:08:11 -05:00
< 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" >
2024-04-20 21:12:04 +02:00
Intro/Credit (show a skip button, *experimental*)
2024-03-04 07:08:11 -05:00
< / 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
2024-04-20 21:12:04 +02:00
your episodes with the currently discovered introduction/credit timestamps and EDL action.
2024-03-04 07:08:11 -05:00
< / div >
< / div >
< / details >
2022-09-02 01:27:49 -05:00
< details id = "silence" >
2024-03-03 21:46:17 -05:00
< summary > Silence Detection Options< / summary >
2022-09-02 01:27:49 -05:00
2024-03-01 19:56:43 -05:00
< br / >
2022-09-02 01:27:49 -05:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SilenceDetectionMaximumNoise" >
Noise tolerance
< / label >
< input id = "SilenceDetectionMaximumNoise" type = "number" is = "emby-input" min = "-90"
2024-03-30 18:22:54 -04:00
max="0" />
2022-09-02 01:27:49 -05:00
< div class = "fieldDescription" >
Noise tolerance in negative decibels.
< / div >
< / div >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SilenceDetectionMinimumDuration" >
Minimum silence duration
< / label >
< input id = "SilenceDetectionMinimumDuration" type = "number" is = "emby-input" min = "0"
2024-03-30 18:22:54 -04:00
step="0.01" />
2022-09-02 01:27:49 -05:00
< div class = "fieldDescription" >
Minimum silence duration in seconds before adjusting introduction end time.
< / div >
< / div >
< / details >
2024-03-03 21:46:17 -05:00
< details id = "detection" >
2024-03-04 07:08:11 -05:00
< summary > Process Configuration< / summary >
2024-03-03 21:46:17 -05:00
2024-03-30 18:22:54 -04:00
< br / >
2024-03-04 17:50:12 -05:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "UseChromaprint" type = "checkbox" is = "emby-checkbox" / >
< span > Chromaprint analysis< / span >
< / label >
< div class = "fieldDescription" >
If checked, analysis will use Chromaprint to compare episode audio and identify intros.
< br / >
< strong > WARNING: Disabling this option may result in incomplete or innaccurate analysis!< / strong >
< br / >
< / div >
< / div >
2024-03-03 21:46:17 -05:00
< 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 / >
2024-05-26 22:16:25 +02:00
< strong > WARNING: Disabling the cache will cause all libraries to be re-scanned, which can take a very long time!< / strong >
2024-03-03 21:46:17 -05:00
< 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"
2024-03-30 18:22:54 -04:00
max="16" />
2024-03-03 21:46:17 -05:00
< 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 >
2022-06-09 20:34:18 -05:00
< / fieldset >
< fieldset class = "verticalSection-extrabottompadding" >
< legend > Playback< / legend >
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "AutoSkip" type = "checkbox" is = "emby-checkbox" / >
< span > Automatically skip intros< / span >
< / label >
< div class = "fieldDescription" >
2024-09-08 00:23:43 +03:00
If checked, intros will be automatically skipped for Apps without Skip Button.
2024-08-31 16:48:31 +00:00
If you access Jellyfin through a reverse proxy, it must be configured to proxy websockets.< br / >
2022-06-09 20:34:18 -05:00
< / div >
2022-05-05 18:10:34 -05:00
< / div >
2022-05-05 18:26:02 -05:00
2024-03-02 15:28:28 -05:00
< div id = "divSkipFirstEpisode" class = "checkboxContainer checkboxContainer-withDescription" >
2022-09-02 01:54:49 -05:00
< label class = "emby-checkbox-label" >
< input id = "SkipFirstEpisode" type = "checkbox" is = "emby-checkbox" / >
2024-05-13 14:25:52 +02:00
< span > Play intro for first episode of a season< / span >
2022-09-02 01:54:49 -05:00
< / label >
< div class = "fieldDescription" >
2024-05-13 14:25:52 +02:00
If checked, auto skip will play the introduction of the first episode in a season.< br / >
< / div >
< br / >
< / div >
2024-08-02 13:41:03 +00:00
< div id = "divSecondsOfIntroStartToPlay" class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SecondsOfIntroStartToPlay" >
Intro playback duration (in seconds)
< / label >
< input id = "SecondsOfIntroStartToPlay" type = "number" is = "emby-input" min = "0" / >
< div class = "fieldDescription" >
Seconds of introduction start that should be played. Defaults to 0.
< / div >
< br / >
< / div >
2024-05-13 14:25:52 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "AutoSkipCredits" type = "checkbox" is = "emby-checkbox" / >
< span > Automatically skip credits< / span >
< / label >
< div class = "fieldDescription" >
2024-08-31 16:48:31 +00:00
If checked, credits will be automatically skipped for Apps without Skip Button.
If you access Jellyfin through a reverse proxy, it must be configured to proxy websockets.< br / >
2022-09-02 01:54:49 -05:00
< / div >
< / div >
2024-08-02 13:41:03 +00:00
< div id = "divSecondsOfCreditsStartToPlay" class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SecondsOfCreditsStartToPlay" >
Credit playback duration (in seconds)
< / label >
< input id = "SecondsOfCreditsStartToPlay" type = "number" is = "emby-input" min = "0" / >
< div class = "fieldDescription" >
Seconds of credits start that should be played. Defaults to 0.
< / div >
< br / >
< / div >
2024-09-09 20:52:54 +02:00
< details id = "AutoSkipClientList" style = "padding-bottom: 1em;" >
< summary > Auto Skip Client List< / summary >
< br / >
< div class = "checkboxList paperList" style = "padding:.5em 1em" > < / div >
< label class = "inputLabel" for = "ClientList" > < / label >
< input id = "ClientList" type = "hidden" is = "emby-input" / >
< / details >
2024-03-02 15:28:28 -05:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "SkipButtonVisible" type = "checkbox" is = "emby-checkbox" / >
< span > Show skip intro button< / span >
< / label >
< div class = "fieldDescription" >
If checked, a skip button will be displayed at the start of an episode's introduction.
2024-03-05 14:14:20 -05:00
< strong > Only applies to the web interface and compatible applications.< / strong >
2024-03-02 15:28:28 -05:00
< br / >
< / div >
< / div >
2024-03-01 09:19:12 -05:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "PersistSkipButton" type = "checkbox" is = "emby-checkbox" / >
2024-03-01 14:45:27 -05:00
< span > Display button for intro duration< / span >
2024-03-01 09:19:12 -05:00
< / label >
< div class = "fieldDescription" >
2024-03-05 14:14:20 -05:00
If checked, skip button will remain visible throught the intro (offset and timeout are ignored).
< br / >
Note: If unchecked, button will only appear in the player controls after the set timeout.
2024-03-01 09:19:12 -05:00
< / div >
< / div >
2024-03-02 15:28:28 -05:00
< div id = "divShowPromptAdjustment" class = "inputContainer" >
2022-06-09 20:34:18 -05:00
< label class = "inputLabel inputLabelUnfocused" for = "ShowPromptAdjustment" >
2024-03-01 07:20:04 -05:00
Skip prompt offset (in seoncds)
2022-06-09 20:34:18 -05:00
< / label >
< input id = "ShowPromptAdjustment" type = "number" is = "emby-input" min = "0" / >
< div class = "fieldDescription" >
2024-03-01 07:20:04 -05:00
Seconds to display skip prompt before introduction begins.
2022-06-09 20:34:18 -05:00
< / div >
2022-05-05 18:26:02 -05:00
< / div >
2024-04-02 21:09:41 -04:00
< br / >
2022-05-05 18:26:02 -05:00
2024-03-02 15:28:28 -05:00
< div id = "divHidePromptAdjustment" class = "inputContainer" >
2022-06-09 20:34:18 -05:00
< label class = "inputLabel inputLabelUnfocused" for = "HidePromptAdjustment" >
2024-03-01 07:20:04 -05:00
Skip prompt timeout (in seconds)
2022-06-09 20:34:18 -05:00
< / label >
< input id = "HidePromptAdjustment" type = "number" is = "emby-input" min = "2" / >
< div class = "fieldDescription" >
2024-03-01 07:20:04 -05:00
Seconds after introduction before skip prompt is hidden.
2022-06-09 20:34:18 -05:00
< / div >
2022-05-05 18:26:02 -05:00
< / div >
2024-04-02 21:09:41 -04:00
< br / >
2022-09-02 01:27:49 -05:00
< div class = "inputContainer" >
2024-08-02 13:41:03 +00:00
< label class = "inputLabel inputLabelUnfocused" for = "RemainingSecondsOfIntro" >
2024-03-01 09:19:12 -05:00
Intro playback duration (in seconds)
2022-09-02 01:27:49 -05:00
< / label >
2024-08-02 13:41:03 +00:00
< input id = "RemainingSecondsOfIntro" type = "number" is = "emby-input" min = "0" / >
2022-09-02 01:27:49 -05:00
< div class = "fieldDescription" >
2024-03-01 09:19:12 -05:00
Seconds of introduction ending that should be played. Defaults to 2.
2022-09-02 01:27:49 -05:00
< / div >
< / div >
2022-11-06 21:20:52 -06:00
< details >
< summary > User Interface Customization< / summary >
2024-03-01 19:56:43 -05:00
< br / >
2022-11-06 21:20:52 -06:00
< div class = "inputContainer" >
2023-03-04 00:15:26 -06:00
< label class = "inputLabel" for = "SkipButtonIntroText" >
2022-11-06 21:20:52 -06:00
Skip intro button text
< / label >
2023-03-04 00:15:26 -06:00
< input id = "SkipButtonIntroText" type = "text" is = "emby-input" / >
2022-11-06 21:20:52 -06:00
< div class = "fieldDescription" >
Text to display in the skip intro button.
< / div >
< / div >
2023-03-04 00:15:26 -06:00
< div class = "inputContainer" >
< label class = "inputLabel" for = "SkipButtonEndCreditsText" >
Skip end credits button text
< / label >
< input id = "SkipButtonEndCreditsText" type = "text" is = "emby-input" / >
< div class = "fieldDescription" >
Text to display in the skip end credits button.
< / div >
< / div >
2024-03-02 15:28:28 -05:00
< div id = "divAutoSkipNotificationText" class = "inputContainer" >
2022-11-06 21:20:52 -06:00
< label class = "inputLabel" for = "AutoSkipNotificationText" >
Automatic skip notification message
< / label >
< input id = "AutoSkipNotificationText" type = "text" is = "emby-input" / >
< div class = "fieldDescription" >
2024-03-01 07:20:04 -05:00
Message shown after automatically skipping an introduction. Leave blank to disable notification.
2022-11-06 21:20:52 -06:00
< / div >
< / div >
2024-05-13 14:25:52 +02:00
< div id = "divAutoSkipCreditsNotificationText" class = "inputContainer" >
< label class = "inputLabel" for = "AutoSkipCreditsNotificationText" >
Automatic skip notification message
< / label >
< input id = "AutoSkipCreditsNotificationText" type = "text" is = "emby-input" / >
< div class = "fieldDescription" >
Message shown after automatically skipping credits. Leave blank to disable notification.
< / div >
< / div >
2022-11-06 21:20:52 -06:00
< / details >
2022-06-09 20:34:18 -05:00
< / fieldset >
2022-05-05 18:26:02 -05:00
2020-07-04 23:33:19 +09:00
< div >
< button is = "emby-button" type = "submit" class = "raised button-submit block emby-button" >
< span > Save< / span >
< / button >
< / div >
< / form >
2022-07-17 02:13:02 -05:00
< br / >
2024-03-01 19:56:43 -05:00
< fieldset class = "verticalSection-extrabottompadding" >
< legend > Advanced< / legend >
< details id = "support" >
2024-03-02 20:30:30 -05:00
< summary > Support Bundle Info< / summary >
2024-03-01 19:56:43 -05:00
< textarea id = "supportBundle" rows = "20" cols = "75" readonly > < / textarea >
< / details >
< details id = "visualizer" >
2024-03-02 15:28:28 -05:00
< summary > Manage Fingerprints< / summary >
2024-03-01 19:56:43 -05:00
< br / >
2024-07-27 21:11:01 +00:00
< label class = "inputLabel" for = "troubleshooterShow" > Select TV Series to manage< / label >
< select is = "emby-select" id = "troubleshooterShow" class = "emby-select-withcolor emby-select" > < / select >
< label class = "inputLabel" for = "troubleshooterSeason" > Select Season to manage< / label >
< select is = "emby-select" id = "troubleshooterSeason" class = "emby-select-withcolor emby-select" > < / select >
2024-03-01 19:56:43 -05:00
< br / >
2024-07-27 21:11:01 +00:00
< label class = "inputLabel" for = "troubleshooterEpisode1" > Select the first episode< / label >
< select is = "emby-select" id = "troubleshooterEpisode1" class = "emby-select-withcolor emby-select" > < / select >
2024-07-31 05:42:02 -07:00
< label class = "inputLabel" for = "troubleshooterEpisode1" > Select the second episode< / label >
2024-07-27 21:11:01 +00:00
< select is = "emby-select" id = "troubleshooterEpisode2" class = "emby-select-withcolor emby-select" > < / select >
2024-03-01 19:56:43 -05:00
< br / >
< div id = "timestampEditor" style = "display:none" >
< h3 style = "margin:0" > Introduction timestamp editor< / h3 >
2024-07-27 21:11:01 +00:00
< p style = "margin:0" > All times are displayed in the format (HH:MM:SS)< / p >
2024-03-01 19:56:43 -05:00
< br / >
2024-08-10 10:25:06 +00:00
< table class = "detailTable" >
2024-07-27 21:11:01 +00:00
< tr >
< th scope = "col" class = "detailTableHeaderCell" > Episode< / th >
< th scope = "col" class = "detailTableHeaderCell" > Section< / th >
< th scope = "col" class = "detailTableHeaderCell" > Start Time< / th >
< th scope = "col" class = "detailTableHeaderCell" > End Time< / th >
< / tr >
< tr >
< td rowspan = "2" id = "editLeftEpisodeTitle" > < / td >
< td > Intro< / td >
< td > < input type = "time" step = "1" id = "editLeftIntroEpisodeStart" > < / td >
< td > < input type = "time" step = "1" id = "editLeftIntroEpisodeEnd" > < / td >
< / tr >
< tr >
< td > Credits< / td >
< td > < input type = "time" step = "1" id = "editLeftCreditEpisodeStart" > < / td >
< td > < input type = "time" step = "1" id = "editLeftCreditEpisodeEnd" > < / td >
< / tr >
< tr >
< td rowspan = "2" id = "editRightEpisodeTitle" > < / td >
< td > Intro< / td >
< td > < input type = "time" step = "1" id = "editRightIntroEpisodeStart" > < / td >
< td > < input type = "time" step = "1" id = "editRightIntroEpisodeEnd" > < / td >
< / tr >
< tr >
< td > Credits< / td >
< td > < input type = "time" step = "1" id = "editRightCreditEpisodeStart" > < / td >
< td > < input type = "time" step = "1" id = "editRightCreditEpisodeEnd" > < / td >
< / tr >
< / table >
2024-03-05 23:32:51 -05:00
< br / >
2024-07-27 21:11:01 +00:00
< button is = "emby-select" id = "btnUpdateTimestamps" class = "raised button-submit block emby-button" type = "button" >
2024-03-01 19:56:43 -05:00
Update timestamps
< / button >
< br / >
< / div >
2022-05-30 02:23:36 -05:00
2024-03-12 14:40:07 -04:00
< div id = "timestampErrorDiv" style = "display:none" >
2024-05-13 14:25:52 +02:00
< textarea id = "timestampError" rows = "2" cols = "75" readonly > < / textarea >
< br / >
< br / >
2024-03-12 14:40:07 -04:00
< / div >
2024-03-04 10:41:14 -05:00
2024-03-01 19:56:43 -05:00
< h3 > Fingerprint Visualizer< / h3 >
< p >
Interactively compare the audio fingerprints of two episodes. < br / >
The blue and red bar to the right of the fingerprint diff turns blue
when the corresponding fingerprint points are at least 80% similar.
< / p >
< table >
< thead >
2024-07-27 21:11:01 +00:00
< tr >
< td style = "min-width: 100px; font-weight: bold" > Key< / td >
< td style = "font-weight: bold" > Function< / td >
< / tr >
2024-03-01 19:56:43 -05:00
< / thead >
< tbody >
< tr >
< td > Up arrow< / td >
< td >
2024-05-10 14:05:59 +02:00
Shift the left episode up by 0.1238 seconds.
2024-03-01 19:56:43 -05:00
Holding control will shift the episode by 10 seconds.
< / td >
< / tr >
< tr >
< td > Down arrow< / td >
< td >
2024-05-10 14:05:59 +02:00
Shift the left episode down by 0.1238 seconds.
2024-03-01 19:56:43 -05:00
Holding control will shift the episode by 10 seconds.
< / td >
< / tr >
< tr >
< td > Right arrow< / td >
< td > Advance to the next pair of episodes.< / td >
< / tr >
< tr >
< td > Left arrow< / td >
< td > Go back to the previous pair of episodes.< / td >
< / tr >
< / tbody >
< / table >
< br / >
< span > Shift amount:< / span >
< input type = "number" min = "-3000" max = "3000" value = "0" id = "offset" >
< br / >
< span id = "suggestedShifts" > Suggested shifts:< / span >
< br / >
< br / >
2024-03-05 23:32:51 -05:00
< canvas id = "troubleshooter" style = "display:none;" > < / canvas >
2024-03-01 19:56:43 -05:00
< span id = "timestampContainer" >
< span id = "timestamps" > < / span > < br / >
< span id = "intros" > < / span >
< / span >
2024-03-05 23:32:51 -05:00
< br / >
< br / >
2024-08-10 10:25:06 +00:00
< div id = "eraseSeasonContainer" style = "display: none;" >
2024-06-07 17:09:48 +02:00
< button id = "btnEraseSeasonTimestamps" type = "button" >
Erase all timestamps for this season
< / button >
< input type = "checkbox" id = "eraseSeasonCacheCheckbox" style = "margin-left: 10px;" >
< label for = "eraseSeasonCacheCheckbox" style = "margin-left: 5px;" > Erase cached fingerprint files< / label >
< / div >
2024-08-10 10:25:06 +00:00
< br / >
2024-03-05 23:32:51 -05:00
< button id = "btnEraseIntroTimestamps" >
Erase all introduction timestamps (globally)
< / button >
< br / >
< button id = "btnEraseCreditTimestamps" >
Erase all end credits timestamps (globally)
< / button >
2024-06-07 17:09:48 +02:00
< br / >
< input type = "checkbox" id = "eraseModeCacheCheckbox" style = "margin-left: 10px;" >
< label for = "eraseModeCacheCheckbox" style = "margin-left: 5px;" > Erase cached fingerprint files< / label >
2024-03-01 19:56:43 -05:00
< / details >
2024-08-10 10:25:06 +00:00
< details id = "storage" >
< br / >
< summary > Storage Usage< / summary >
< div class = "fieldDescription" >
See how much space each library uses.
< / div >
< textarea id = "storage" rows = "20" cols = "75" readonly > < / textarea >
< / details >
2024-03-01 19:56:43 -05:00
< / fieldset >
< / div >
2020-07-04 23:33:19 +09:00
< / div >
2022-07-16 23:35:15 -05:00
< script src = "configurationpage?name=visualizer.js" > < / script >
2022-05-30 02:23:36 -05:00
< script >
// first and second episodes to fingerprint & compare
var lhs = [];
var rhs = [];
2022-05-31 16:12:11 -05:00
2022-06-01 21:52:40 -05:00
// fingerprint point comparison & miminum similarity threshold (at most 6 bits out of 32 can be different)
2022-05-31 16:12:11 -05:00
var fprDiffs = [];
2022-06-01 21:52:40 -05:00
var fprDiffMinimum = (1 - 6 / 32) * 100;
2022-05-30 02:23:36 -05:00
// seasons grouped by show
var shows = {};
2022-07-03 01:20:33 -05:00
// settings elements
2022-06-29 20:52:16 -05:00
var visualizer = document.querySelector("details#visualizer");
2022-08-25 00:39:20 -05:00
var support = document.querySelector("details#support");
2024-08-10 10:25:06 +00:00
var storage = document.querySelector("details#storage");
2022-11-29 02:31:24 -06:00
var btnEraseIntroTimestamps = document.querySelector("button#btnEraseIntroTimestamps");
var btnEraseCreditTimestamps = document.querySelector("button#btnEraseCreditTimestamps");
2022-07-03 01:20:33 -05:00
2022-07-16 22:12:42 -05:00
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
var configurationFields = [
2022-09-02 01:27:49 -05:00
// analysis
2022-07-16 22:12:42 -05:00
"MaxParallelism",
"SelectedLibraries",
2024-09-09 20:52:54 +02:00
"ClientList",
2022-07-16 22:12:42 -05:00
"AnalysisPercent",
"AnalysisLengthLimit",
"MinimumIntroDuration",
2022-09-03 00:39:35 -05:00
"MaximumIntroDuration",
2024-04-13 14:07:12 -04:00
"MinimumCreditsDuration",
"MaximumCreditsDuration",
2022-07-16 22:12:42 -05:00
"EdlAction",
2024-03-03 21:46:17 -05:00
"ProcessPriority",
"ProcessThreads",
2022-09-02 01:27:49 -05:00
// playback
2022-07-16 22:12:42 -05:00
"ShowPromptAdjustment",
2022-09-02 01:27:49 -05:00
"HidePromptAdjustment",
2024-08-02 13:41:03 +00:00
"RemainingSecondsOfIntro",
"SecondsOfIntroStartToPlay",
"SecondsOfCreditsStartToPlay",
2022-09-02 01:27:49 -05:00
// internals
"SilenceDetectionMaximumNoise",
"SilenceDetectionMinimumDuration",
2023-03-04 00:15:26 -06:00
// UI customization
"SkipButtonIntroText",
"SkipButtonEndCreditsText",
2024-05-13 14:25:52 +02:00
"AutoSkipNotificationText",
"AutoSkipCreditsNotificationText"
2022-07-16 22:12:42 -05:00
]
2022-09-02 01:54:49 -05:00
var booleanConfigurationFields = [
2024-03-30 18:22:54 -04:00
"AutoDetectIntros",
"AutoDetectCredits",
2022-09-27 21:42:51 -05:00
"AnalyzeSeasonZero",
2022-09-25 00:11:07 -05:00
"RegenerateEdlFiles",
2024-03-04 17:50:12 -05:00
"UseChromaprint",
2024-03-03 21:46:17 -05:00
"CacheFingerprints",
2022-09-02 01:54:49 -05:00
"AutoSkip",
2024-05-13 14:25:52 +02:00
"AutoSkipCredits",
2022-11-06 21:20:52 -06:00
"SkipFirstEpisode",
2024-03-01 17:45:55 +01:00
"PersistSkipButton",
2024-03-01 12:46:23 -05:00
"SkipButtonVisible"
2022-09-02 01:54:49 -05:00
]
2022-07-03 01:20:33 -05:00
// visualizer elements
2022-06-01 23:53:12 -05:00
var canvas = document.querySelector("canvas#troubleshooter");
var selectShow = document.querySelector("select#troubleshooterShow");
var selectSeason = document.querySelector("select#troubleshooterSeason");
var selectEpisode1 = document.querySelector("select#troubleshooterEpisode1");
var selectEpisode2 = document.querySelector("select#troubleshooterEpisode2");
var txtOffset = document.querySelector("input#offset");
2022-07-03 01:47:55 -05:00
var txtSuggested = document.querySelector("span#suggestedShifts");
2022-07-03 01:20:33 -05:00
var btnSeasonEraseTimestamps = document.querySelector("button#btnEraseSeasonTimestamps");
2024-06-07 17:09:48 +02:00
var eraseSeasonContainer = document.getElementById("eraseSeasonContainer");
2024-03-04 10:41:14 -05:00
var timestampError = document.querySelector("textarea#timestampError");
2024-03-05 23:32:51 -05:00
var timestampEditor = document.querySelector("#timestampEditor");
2022-07-17 01:54:05 -05:00
var btnUpdateTimestamps = document.querySelector("button#btnUpdateTimestamps");
2022-06-01 23:53:12 -05:00
var timeContainer = document.querySelector("span#timestampContainer");
2022-07-03 01:20:33 -05:00
2022-06-02 01:06:15 -05:00
var windowHashInterval = 0;
2022-05-30 02:23:36 -05:00
2024-03-02 15:28:28 -05:00
var autoSkip = document.querySelector("input#AutoSkip");
var skipFirstEpisode = document.querySelector("div#divSkipFirstEpisode");
2024-08-02 13:41:03 +00:00
var secondsOfIntroStartToPlay = document.querySelector("div#divSecondsOfIntroStartToPlay");
2024-09-09 20:52:54 +02:00
var autoSkipClientList = document.getElementById("AutoSkipClientList");
2024-08-02 13:41:03 +00:00
var secondsOfCreditsStartToPlay = document.querySelector("div#divSecondsOfCreditsStartToPlay");
2024-03-02 15:28:28 -05:00
var autoSkipNotificationText = document.querySelector("div#divAutoSkipNotificationText");
2024-05-13 14:25:52 +02:00
var autoSkipCredits = document.querySelector("input#AutoSkipCredits");
var autoSkipCreditsNotificationText = document.querySelector("div#divAutoSkipCreditsNotificationText");
2024-03-02 15:28:28 -05:00
2024-09-09 20:52:54 +02:00
function autoSkipChanged() {
2024-03-18 21:10:24 +01:00
if (autoSkip.checked) {
skipFirstEpisode.style.display = 'unset';
autoSkipNotificationText.style.display = 'unset';
2024-08-02 13:41:03 +00:00
secondsOfIntroStartToPlay.style.display = 'unset';
2024-03-18 21:10:24 +01:00
} else {
skipFirstEpisode.style.display = 'none';
autoSkipNotificationText.style.display = 'none';
2024-08-02 13:41:03 +00:00
secondsOfIntroStartToPlay.style.display = 'none';
2024-03-18 21:10:24 +01:00
}
2024-09-09 20:52:54 +02:00
clientListVisible();
2024-03-02 15:28:28 -05:00
}
autoSkip.addEventListener("change", autoSkipChanged);
2024-09-09 20:52:54 +02:00
function autoSkipCreditsChanged() {
2024-05-13 14:25:52 +02:00
if (autoSkipCredits.checked) {
autoSkipCreditsNotificationText.style.display = 'unset';
2024-08-02 13:41:03 +00:00
secondsOfCreditsStartToPlay.style.display = 'unset';
2024-05-13 14:25:52 +02:00
} else {
autoSkipCreditsNotificationText.style.display = 'none';
2024-08-02 13:41:03 +00:00
secondsOfCreditsStartToPlay.style.display = 'none';
2024-05-13 14:25:52 +02:00
}
2024-09-09 20:52:54 +02:00
clientListVisible();
2024-05-13 14:25:52 +02:00
}
autoSkipCredits.addEventListener("change", autoSkipCreditsChanged);
2024-09-09 20:52:54 +02:00
function clientListVisible() {
if (autoSkip.checked || autoSkipCredits.checked) {
autoSkipClientList.style.display = 'unset';
autoSkipClientList.style.width = '100%';
} else {
autoSkipClientList.style.display = 'none';
}
}
async function getDeviceList() {
const response = await getJson("Devices");
const devices = [...new Set(response.Items.map(item => item.AppName))];
return devices;
}
function updateClientList() {
document.getElementById('ClientList').value = Array.from(
autoSkipClientList.querySelectorAll('input[type="checkbox"]:checked')
).map(checkbox => checkbox.nextElementSibling.textContent).join(', ');
}
async function generateAutoSkipClientList() {
var devices = await getDeviceList();
var deviceList = document.getElementById('ClientList').value;
var checkedDevices = deviceList ? deviceList.split(', ') : [];
var checkboxListHtml = devices.map(function(device) {
var id = 'chk' + device.replace(/\s+/g, '');
var isChecked = checkedDevices.includes(device) ? 'checked' : '';
return '< label class = "emby-checkbox-label" > ' +
'< input type = "checkbox" is = "emby-checkbox" id = "' + id + '" ' + isChecked + ' > ' +
'< span class = "checkboxLabel" > ' + device + '< / span > ' +
'< / label > ';
}).join('');
var checkboxList = autoSkipClientList.querySelector('.checkboxList.paperList');
checkboxList.innerHTML = checkboxListHtml;
var checkboxes = checkboxList.querySelectorAll('input[type="checkbox"]');
for (var i = 0; i < checkboxes.length ; i + + ) {
checkboxes[i].addEventListener('change', function() {
updateClientList();
});
}
}
2024-03-01 14:45:27 -05:00
var persistSkip = document.querySelector("input#PersistSkipButton");
2024-03-02 15:28:28 -05:00
var showAdjustment = document.querySelector("div#divShowPromptAdjustment");
var hideAdjustment = document.querySelector("div#divHidePromptAdjustment");
2024-03-01 14:45:27 -05:00
// prevent setting unavailable options
async function persistSkipChanged() {
2024-03-18 21:10:24 +01:00
if (persistSkip.checked) {
showAdjustment.style.display = 'none';
hideAdjustment.style.display = 'none';
} else {
showAdjustment.style.display = 'unset';
hideAdjustment.style.display = 'unset';
}
2024-03-01 14:45:27 -05:00
}
persistSkip.addEventListener("change", persistSkipChanged);
2022-06-29 20:52:16 -05:00
// when the fingerprint visualizer opens, populate show names
async function visualizerToggled() {
if (!visualizer.open) {
return;
}
2022-08-30 02:39:10 -05:00
// ensure the series select is empty
while (selectShow.options.length > 0) {
selectShow.remove(0);
}
2022-06-29 20:52:16 -05:00
Dashboard.showLoadingMsg();
2022-05-30 02:23:36 -05:00
shows = await getJson("Intros/Shows");
2022-05-31 16:12:11 -05:00
var sorted = [];
for (var series in shows) { sorted.push(series); }
sorted.sort();
for (var show of sorted) {
2022-05-30 02:23:36 -05:00
addItem(selectShow, show, show);
}
selectShow.value = "";
2022-06-29 20:52:16 -05:00
Dashboard.hideLoadingMsg();
2022-05-30 02:23:36 -05:00
}
2022-08-25 00:39:20 -05:00
// 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();
2022-10-26 19:30:21 -05:00
// Display it to the user and select all
const ta = document.querySelector("textarea#supportBundle");
ta.value = bundleText;
ta.focus();
ta.setSelectionRange(0, ta.value.length);
2022-08-25 00:39:20 -05:00
2022-10-26 19:30:21 -05:00
// Attempt to copy it to the clipboard automatically, falling back
// to prompting the user to press Ctrl + C.
2022-08-25 00:39:20 -05:00
try {
2022-10-26 19:30:21 -05:00
navigator.clipboard.writeText(bundleText);
2022-08-25 00:39:20 -05:00
Dashboard.alert("Support bundle copied to clipboard");
} catch {
2022-10-26 19:30:21 -05:00
Dashboard.alert("Press Ctrl+C to copy support bundle");
2022-08-25 00:39:20 -05:00
}
}
2024-08-10 10:25:06 +00:00
// fetch the storage whenever the detail section is opened.
async function storageToggled() {
if (!storage.open) {
return;
}
// Fetch the support bundle
const bundle = await fetchWithAuth("IntroSkipper/Storage", "GET", null);
const bundleText = await bundle.text();
// Display it to the user
const ta = document.querySelector("textarea#storage");
ta.value = bundleText;
}
2022-05-30 02:23:36 -05:00
// show changed, populate seasons
async function showChanged() {
clearSelect(selectSeason);
2024-06-07 17:09:48 +02:00
eraseSeasonContainer.style.display = "none";
2024-03-04 10:41:14 -05:00
clearSelect(selectEpisode1);
clearSelect(selectEpisode2);
2022-05-30 02:23:36 -05:00
// add all seasons from this show to the season select
for (var season of shows[selectShow.value]) {
addItem(selectSeason, season, season);
}
selectSeason.value = "";
}
// season changed, reload all episodes
async function seasonChanged() {
const url = "Intros/Show/" + encodeURI(selectShow.value) + "/" + selectSeason.value;
const episodes = await getJson(url);
clearSelect(selectEpisode1);
clearSelect(selectEpisode2);
2024-06-07 17:09:48 +02:00
eraseSeasonContainer.style.display = "unset";
2022-05-30 02:23:36 -05:00
let i = 1;
for (let episode of episodes) {
const strI = i.toLocaleString("en", { minimumIntegerDigits: 2, maximumFractionDigits: 0 });
addItem(selectEpisode1, strI + ": " + episode.Name, episode.Id);
addItem(selectEpisode2, strI + ": " + episode.Name, episode.Id);
i++;
}
2022-05-31 16:12:11 -05:00
setTimeout(() => {
selectEpisode1.selectedIndex = 0;
selectEpisode2.selectedIndex = 1;
episodeChanged();
}, 100);
2022-05-30 02:23:36 -05:00
}
// episode changed, get fingerprints & calculate diff
async function episodeChanged() {
if (!selectEpisode1.value || !selectEpisode2.value) {
return;
}
Dashboard.showLoadingMsg();
2024-03-04 10:41:14 -05:00
timestampError.value = "";
2024-03-05 23:32:51 -05:00
canvas.style.display = "none";
2024-03-04 10:41:14 -05:00
2022-07-17 01:54:05 -05:00
lhs = await getJson("Intros/Episode/" + selectEpisode1.value + "/Chromaprint");
2024-03-17 19:04:57 -04:00
if (lhs === undefined) {
2024-03-04 11:51:55 -05:00
timestampError.value += "Error: " + selectEpisode1.value + " fingerprints failed!\n";
2024-03-17 19:04:57 -04:00
} else if (lhs === null) {
timestampError.value += "Error: " + selectEpisode1.value + " fingerprints missing!\n";
2024-03-04 10:41:14 -05:00
}
2024-03-05 17:33:51 -05:00
2024-03-17 19:04:57 -04:00
rhs = await getJson("Intros/Episode/" + selectEpisode2.value + "/Chromaprint");
2024-03-04 10:41:14 -05:00
if (rhs === undefined) {
2024-03-04 11:51:55 -05:00
timestampError.value += "Error: " + selectEpisode2.value + " fingerprints failed!";
2024-03-17 19:04:57 -04:00
} else if (rhs === null) {
timestampError.value += "Error: " + selectEpisode2.value + " fingerprints missing!\n";
2024-03-04 10:41:14 -05:00
}
2024-03-05 17:33:51 -05:00
2024-03-04 10:41:14 -05:00
if (timestampError.value == "") {
2024-03-12 14:40:07 -04:00
timestampErrorDiv.style.display = "none";
2024-03-04 10:41:14 -05:00
} else {
2024-03-12 14:40:07 -04:00
timestampErrorDiv.style.display = "unset";
2024-03-04 10:41:14 -05:00
}
2022-05-30 02:23:36 -05:00
Dashboard.hideLoadingMsg();
2022-07-17 01:59:42 -05:00
txtOffset.value = "0";
2022-05-30 02:23:36 -05:00
refreshBounds();
renderTroubleshooter();
2022-07-03 01:47:55 -05:00
findExactMatches();
2022-07-17 01:54:05 -05:00
updateTimestampEditor();
}
// updates the timestamp editor
async function updateTimestampEditor() {
// Get the title and ID of the left and right episodes
const leftEpisode = selectEpisode1.options[selectEpisode1.selectedIndex];
const rightEpisode = selectEpisode2.options[selectEpisode2.selectedIndex];
// Try to get the timestamps of each intro, falling back a default value of zero if no intro was found
2024-07-27 21:11:01 +00:00
const leftEpisodeJson = await getJson("Episode/" + leftEpisode.value + "/Timestamps");
const rightEpisodeJson = await getJson("Episode/" + rightEpisode.value + "/Timestamps");
2022-07-17 01:54:05 -05:00
// Update the editor for the first and second episodes
2024-03-05 23:32:51 -05:00
timestampEditor.style.display = "unset";
2022-07-17 01:54:05 -05:00
document.querySelector("#editLeftEpisodeTitle").textContent = leftEpisode.text;
2024-09-12 08:37:47 +00:00
document.querySelector("#editLeftIntroEpisodeStart").value = setTime(Math.round(leftEpisodeJson.Introduction.Start));
document.querySelector("#editLeftIntroEpisodeEnd").value = setTime(Math.round(leftEpisodeJson.Introduction.End));
document.querySelector("#editLeftCreditEpisodeStart").value = setTime(Math.round(leftEpisodeJson.Credits.Start));
document.querySelector("#editLeftCreditEpisodeEnd").value = setTime(Math.round(leftEpisodeJson.Credits.End));
2022-07-17 01:54:05 -05:00
document.querySelector("#editRightEpisodeTitle").textContent = rightEpisode.text;
2024-09-12 08:37:47 +00:00
document.querySelector("#editRightIntroEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Introduction.Start));
document.querySelector("#editRightIntroEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Introduction.End));
document.querySelector("#editRightCreditEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Credits.Start));
document.querySelector("#editRightCreditEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Credits.End));
2022-05-30 02:23:36 -05:00
}
// adds an item to a dropdown
function addItem(select, text, value) {
let item = new Option(text, value);
select.add(item);
}
// clear a select of items
function clearSelect(select) {
2024-03-04 10:41:14 -05:00
timestampError.value = "";
2024-03-12 14:40:07 -04:00
timestampErrorDiv.style.display = "none";
2024-03-05 23:32:51 -05:00
timestampEditor.style.display = "none";
timeContainer.style.display = "none";
canvas.style.display = "none";
2022-05-30 02:23:36 -05:00
let i, L = select.options.length - 1;
for (i = L; i >= 0; i--) {
select.remove(i);
}
}
// make an authenticated GET to the server and parse the response as JSON
2022-07-18 00:38:38 -05:00
async function getJson(url) {
2024-03-10 21:31:50 -04:00
return await fetchWithAuth(url, "GET")
2024-05-13 14:25:52 +02:00
.then(r => {
if (r.ok) {
return r.json();
} else {
return null;
}
})
.catch(err => {
console.debug(err);
});
2022-07-17 01:54:05 -05:00
}
// make an authenticated fetch to the server
async function fetchWithAuth(url, method, body) {
2022-05-30 02:23:36 -05:00
url = ApiClient.serverAddress() + "/" + url;
const reqInit = {
2022-06-07 12:18:03 -05:00
method: method,
2022-05-30 02:23:36 -05:00
headers: {
"Authorization": "MediaBrowser Token=" + ApiClient.accessToken()
2022-07-17 01:54:05 -05:00
},
body: body,
2022-05-30 02:23:36 -05:00
};
2022-07-17 01:54:05 -05:00
if (method === "POST") {
reqInit.headers["Content-Type"] = "application/json";
}
return await fetch(url, reqInit);
2022-05-30 02:23:36 -05:00
}
2020-07-04 23:33:19 +09:00
2022-05-31 16:12:11 -05:00
// key pressed
function keyDown(e) {
let episodeDelta = 0;
let offsetDelta = 0;
switch (e.key) {
case "ArrowDown":
2024-03-05 17:58:32 -05:00
if (timestampError.value != "") {
// if the control key is pressed, shift LHS by 10s. Otherwise, shift by 1.
2024-05-10 14:05:59 +02:00
offsetDelta = e.ctrlKey ? 10 / 0.1238 : 1;
2024-03-05 17:58:32 -05:00
}
2022-05-31 16:12:11 -05:00
break;
case "ArrowUp":
2024-03-05 17:58:32 -05:00
if (timestampError.value != "") {
2024-05-10 14:05:59 +02:00
offsetDelta = e.ctrlKey ? -10 / 0.1238 : -1;
2024-03-05 17:58:32 -05:00
}
2022-05-31 16:12:11 -05:00
break;
case "ArrowRight":
episodeDelta = 2;
break;
case "ArrowLeft":
episodeDelta = -2;
break;
default:
return;
}
if (offsetDelta != 0) {
txtOffset.value = Number(txtOffset.value) + Math.floor(offsetDelta);
}
if (episodeDelta != 0) {
// calculate the number of episodes remaining in the LHS and RHS episode pickers
const lhsRemaining = selectEpisode1.selectedIndex;
const rhsRemaining = selectEpisode2.length - selectEpisode2.selectedIndex - 1;
// if we're moving forward and the right episode picker is close to the end, don't move.
if (episodeDelta > 0 & & rhsRemaining < = 1) {
return;
} else if (episodeDelta < 0 & & lhsRemaining < = 1 ) {
return;
}
selectEpisode1.selectedIndex += episodeDelta;
selectEpisode2.selectedIndex += episodeDelta;
episodeChanged();
}
renderTroubleshooter();
e.preventDefault();
}
2022-06-02 01:06:15 -05:00
// check that the user is still on the configuration page
function checkWindowHash() {
2022-06-07 12:18:03 -05:00
const h = location.hash;
if (h === "#!/configurationpage?name=Intro%20Skipper" || h.includes("#!/dialog")) {
2022-06-02 01:06:15 -05:00
return;
}
console.debug("navigated away from intro skipper configuration page");
document.removeEventListener("keydown", keyDown);
clearInterval(windowHashInterval);
}
2022-05-31 16:12:11 -05:00
// converts seconds to a readable timestamp (i.e. 127 becomes "02:07").
function secondsToString(seconds) {
2024-07-27 21:11:01 +00:00
return new Date(seconds * 1000).toISOString().slice(14, 19);
2022-05-31 16:12:11 -05:00
}
2022-11-29 02:31:24 -06:00
// erase all intro/credits timestamps
function eraseTimestamps(mode) {
const lower = mode.toLocaleLowerCase();
const title = "Confirm timestamp erasure";
const body = "Are you sure you want to erase all previously discovered " +
mode.toLocaleLowerCase() +
" timestamps?";
2024-06-07 17:09:48 +02:00
const eraseCacheChecked = document.getElementById("eraseModeCacheCheckbox").checked;
2022-11-29 02:31:24 -06:00
Dashboard.confirm(
body,
title,
(result) => {
if (!result) {
return;
}
2024-06-07 17:09:48 +02:00
fetchWithAuth("Intros/EraseTimestamps?mode=" + mode + "& eraseCache=" + eraseCacheChecked, "POST", null);
2022-11-29 02:31:24 -06:00
Dashboard.alert(mode + " timestamps erased");
});
}
2020-12-02 17:47:22 -07:00
document.querySelector('#TemplateConfigPage')
2022-05-30 02:23:36 -05:00
.addEventListener('pageshow', function () {
2020-12-02 17:47:22 -07:00
Dashboard.showLoadingMsg();
2022-05-31 16:12:11 -05:00
ApiClient.getPluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b").then(function (config) {
2022-07-16 22:12:42 -05:00
for (const field of configurationFields) {
document.querySelector("#" + field).value = config[field];
}
2022-05-05 18:26:02 -05:00
2022-09-02 01:54:49 -05:00
for (const field of booleanConfigurationFields) {
document.querySelector("#" + field).checked = config[field];
}
2024-03-02 15:28:28 -05:00
autoSkipChanged();
2024-05-13 14:25:52 +02:00
autoSkipCreditsChanged();
2024-03-01 14:45:27 -05:00
persistSkipChanged();
2024-09-09 20:52:54 +02:00
generateAutoSkipClientList();
2024-03-01 14:45:27 -05:00
2020-12-02 17:47:22 -07:00
Dashboard.hideLoadingMsg();
2022-05-05 18:10:34 -05:00
});
2021-01-10 00:25:51 +09:00
});
2022-05-05 18:10:34 -05:00
document.querySelector('#FingerprintConfigForm')
2022-06-02 01:06:15 -05:00
.addEventListener('submit', function (e) {
2022-05-30 02:23:36 -05:00
Dashboard.showLoadingMsg();
2022-05-31 16:12:11 -05:00
ApiClient.getPluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b").then(function (config) {
2022-07-16 22:12:42 -05:00
for (const field of configurationFields) {
config[field] = document.querySelector("#" + field).value;
}
2022-05-05 18:26:02 -05:00
2022-09-02 01:54:49 -05:00
for (const field of booleanConfigurationFields) {
config[field] = document.querySelector("#" + field).checked;
}
2022-06-20 01:39:56 -05:00
ApiClient.updatePluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b", config)
.then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result);
});
2020-07-04 23:33:19 +09:00
});
2022-05-30 02:23:36 -05:00
2022-06-02 01:06:15 -05:00
e.preventDefault();
2022-05-30 02:23:36 -05:00
return false;
2020-07-04 23:33:19 +09:00
});
2021-01-10 00:25:51 +09:00
2022-06-29 20:52:16 -05:00
visualizer.addEventListener("toggle", visualizerToggled);
2022-08-25 00:39:20 -05:00
support.addEventListener("toggle", supportToggled);
2024-08-10 10:25:06 +00:00
storage.addEventListener("toggle", storageToggled);
2022-05-30 02:23:36 -05:00
txtOffset.addEventListener("change", renderTroubleshooter);
selectShow.addEventListener("change", showChanged);
selectSeason.addEventListener("change", seasonChanged);
selectEpisode1.addEventListener("change", episodeChanged);
selectEpisode2.addEventListener("change", episodeChanged);
2022-11-29 02:31:24 -06:00
btnEraseIntroTimestamps.addEventListener("click", (e) => {
eraseTimestamps("Introduction");
e.preventDefault();
});
btnEraseCreditTimestamps.addEventListener("click", (e) => {
eraseTimestamps("Credits");
2022-06-07 12:18:03 -05:00
e.preventDefault();
});
2022-07-03 01:20:33 -05:00
btnSeasonEraseTimestamps.addEventListener("click", () => {
2022-07-17 02:13:02 -05:00
Dashboard.confirm(
"Are you sure you want to erase all timestamps for this season?",
"Confirm timestamp erasure",
(result) => {
if (!result) {
return;
}
2022-07-03 01:20:33 -05:00
2022-07-17 02:13:02 -05:00
const show = selectShow.value;
const season = selectSeason.value;
2024-06-07 17:09:48 +02:00
const eraseCacheChecked = document.getElementById("eraseSeasonCacheCheckbox").checked;
2022-07-03 01:20:33 -05:00
2022-07-17 02:13:02 -05:00
const url = "Intros/Show/" + encodeURIComponent(show) + "/" + encodeURIComponent(season);
2024-06-07 17:09:48 +02:00
fetchWithAuth(url + "?eraseCache=" + eraseCacheChecked, "DELETE", null);
2022-07-17 02:13:02 -05:00
Dashboard.alert("Erased timestamps for " + season + " of " + show);
2024-06-07 17:09:48 +02:00
document.getElementById("eraseSeasonCacheCheckbox").checked = false;
2022-07-17 02:13:02 -05:00
}
);
2022-07-03 01:20:33 -05:00
});
2022-07-17 01:54:05 -05:00
btnUpdateTimestamps.addEventListener("click", () => {
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
2024-07-27 21:11:01 +00:00
const newLhs = {
Introduction: {
IntroStart: getTimeInSeconds(document.getElementById('editLeftIntroEpisodeStart').value),
IntroEnd: getTimeInSeconds(document.getElementById('editLeftIntroEpisodeEnd').value)
},
Credits: {
IntroStart: getTimeInSeconds(document.getElementById('editLeftCreditEpisodeStart').value),
IntroEnd: getTimeInSeconds(document.getElementById('editLeftCreditEpisodeEnd').value)
}
2022-07-17 01:54:05 -05:00
};
const rhsId = selectEpisode2.options[selectEpisode2.selectedIndex].value;
2024-07-27 21:11:01 +00:00
const newRhs = {
Introduction: {
IntroStart: getTimeInSeconds(document.getElementById('editRightIntroEpisodeStart').value),
IntroEnd: getTimeInSeconds(document.getElementById('editRightIntroEpisodeEnd').value)
},
Credits: {
IntroStart: getTimeInSeconds(document.getElementById('editRightCreditEpisodeStart').value),
IntroEnd: getTimeInSeconds(document.getElementById('editRightCreditEpisodeEnd').value)
}
2022-07-17 01:54:05 -05:00
};
2024-07-27 21:11:01 +00:00
fetchWithAuth("Episode/" + lhsId + "/Timestamps", "POST", JSON.stringify(newLhs));
fetchWithAuth("Episode/" + rhsId + "/Timestamps", "POST", JSON.stringify(newRhs));
2022-07-17 01:54:05 -05:00
Dashboard.alert("New introduction timestamps saved");
});
2022-06-02 01:06:15 -05:00
document.addEventListener("keydown", keyDown);
windowHashInterval = setInterval(checkWindowHash, 2500);
2022-05-30 02:23:36 -05:00
canvas.addEventListener("mousemove", (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const y = e.clientY - rect.top;
const shift = Number(txtOffset.value);
2022-05-30 03:24:19 -05:00
let lTime, rTime, diffPos;
2022-05-30 02:23:36 -05:00
if (shift < 0 ) {
2024-05-10 14:05:59 +02:00
lTime = y * 0.1238;
rTime = (y + shift) * 0.1238;
2022-05-30 03:24:19 -05:00
diffPos = y + shift;
2022-05-30 02:23:36 -05:00
} else {
2024-05-10 14:05:59 +02:00
lTime = (y - shift) * 0.1238;
rTime = y * 0.1238;
2022-05-30 03:24:19 -05:00
diffPos = y - shift;
2022-05-30 02:23:36 -05:00
}
2022-05-31 16:12:11 -05:00
const diff = fprDiffs[Math.floor(diffPos)];
2022-05-30 02:23:36 -05:00
2022-05-31 16:12:11 -05:00
if (!diff) {
2022-06-01 21:52:40 -05:00
timeContainer.style.display = "none";
2022-05-31 16:12:11 -05:00
return;
} else {
2022-06-01 21:52:40 -05:00
timeContainer.style.display = "unset";
2022-05-31 16:12:11 -05:00
}
2022-06-01 23:53:12 -05:00
const times = document.querySelector("span#timestamps");
2022-05-31 16:12:11 -05:00
// LHS timestamp, RHS timestamp, percent similarity
times.textContent =
secondsToString(lTime) + ", " +
secondsToString(rTime) + ", " +
Math.round(diff) + "%";
2022-05-30 02:23:36 -05:00
2022-06-01 21:52:40 -05:00
timeContainer.style.position = "relative";
timeContainer.style.left = "25px";
timeContainer.style.top = (-1 * rect.height + y).toString() + "px";
2020-07-04 23:33:19 +09:00
});
2024-07-27 21:11:01 +00:00
function setTime(seconds) {
// Calculate hours, minutes, and remaining seconds
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let remainingSeconds = seconds % 60;
// Format as HH:MM:SS
let formattedTime =
String(hours).padStart(2, '0') + ':' +
String(minutes).padStart(2, '0') + ':' +
String(remainingSeconds).padStart(2, '0');
// Set the value of the time input
return formattedTime;
}
function getTimeInSeconds(time) {
let [hours, minutes, seconds] = time.split(':').map(Number);
return (hours * 3600) + (minutes * 60) + seconds;
}
2022-05-30 02:23:36 -05:00
< / script >
2020-07-04 23:33:19 +09:00
< / div >
< / body >
2022-05-30 02:23:36 -05:00
2020-07-04 23:33:19 +09:00
< / html >