2024-10-11 18:01:16 +02:00
<!doctype html>
2020-07-04 23:33:19 +09:00
< html lang = "en" >
2024-10-11 18:01:16 +02:00
< head >
< meta charset = "utf-8" / >
< / head >
< body >
< div id = "TemplateConfigPage" data-role = "page" class = "page type-interior pluginConfigurationPage" data-require = "emby-input,emby-button,emby-select,emby-checkbox,emby-linkbutton" >
< div data-role = "content" >
< style >
summary {
cursor: pointer;
padding: 10px;
width: inherit;
margin: auto;
border: none;
text-align: center;
outline: none;
font-size: 1em;
outline: 2px solid rgba(155, 155, 155, 0.5);
}
h3.checkboxListLabel {
font-size: 1em;
margin-bottom: 4px;
}
< / style >
< div class = "content-primary" >
< form id = "FingerprintConfigForm" >
< fieldset class = "verticalSection-extrabottompadding" >
< legend > Analysis< / legend >
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "AutoDetectIntros" type = "checkbox" is = "emby-checkbox" / >
< span > Automatically Scan Intros< / span >
< / label >
< div class = "fieldDescription" > If enabled, introductions will be automatically analyzed for new media< / div >
2024-03-30 19:02:18 +01:00
< / div >
2024-10-11 18:01:16 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "AutoDetectCredits" type = "checkbox" is = "emby-checkbox" / >
< span > Automatically Scan Credits< / span >
< / label >
2024-03-30 19:02:18 +01:00
2024-10-11 18:01:16 +02:00
< div class = "fieldDescription" >
If enabled, credits will be automatically analyzed for new media
< br / >
< br / >
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 > .
< / div >
2024-03-30 19:02:18 +01:00
< / div >
2024-10-18 14:15:09 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
2024-10-28 20:11:50 -04:00
< label class = "emby-checkbox-label" >
< input id = "UpdateMediaSegments" type = "checkbox" is = "emby-checkbox" / >
< span > Update Segments for New Media During Scan< / span >
< / label >
< div class = "fieldDescription" >
2024-11-05 14:19:02 -05:00
Enable this option to update media segments for any uncached media during a library scan. < br / >
2024-11-04 15:22:04 -05:00
This includes recently added, modified, or previously skipped (but not ignored) files.< br / >
2024-10-28 20:11:50 -04:00
< b > Warning:< / b > This should be disabled if you're using media segment providers other than Intro Skipper.
< / div >
< / div >
< div class = "checkboxContainer" >
2024-10-18 14:15:09 +02:00
< label class = "emby-checkbox-label" >
< input id = "AnalyzeMovies" type = "checkbox" is = "emby-checkbox" / >
< span > Analyze Movies< / span >
< / label >
< / div >
2024-10-11 18:01:16 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "AnalyzeSeasonZero" type = "checkbox" is = "emby-checkbox" / >
2024-10-28 20:11:50 -04:00
< span > Analyze Season 0 (Specials / Extras)< / span >
2024-10-11 18:01:16 +02:00
< / label >
2022-06-09 20:34:18 -05:00
2024-10-11 18:01:16 +02:00
< div class = "fieldDescription" >
Note: Shows containing both a specials and extra folder will identify extras as season 0 and ignore specials, regardless of this setting.
< / div >
2022-06-09 20:34:18 -05:00
< / div >
2022-05-05 18:10:34 -05:00
2024-10-11 18:01:16 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "SelectAllLibraries" type = "checkbox" is = "emby-checkbox" / >
< span > Enable analysis for all libraries containing television episodes< / span >
< / label >
2024-09-17 08:41:56 +02:00
2024-10-11 18:01:16 +02:00
< div class = "folderAccessListContainer" >
< div class = "folderAccess" >
< h3 class = "checkboxListLabel" > Limit analysis to the following libraries< / h3 >
< div class = "checkboxList paperList" style = "padding: 0.5em 1em" id = "libraryCheckboxes" > < / div >
2024-09-17 08:41:56 +02:00
< / div >
2024-10-11 18:01:16 +02:00
< label class = "inputLabel" for = "SelectedLibraries" > < / label >
< input id = "SelectedLibraries" type = "hidden" is = "emby-input" / >
2024-09-14 18:52:39 +02:00
< / div >
< / div >
2022-07-08 00:57:12 -05:00
2024-10-11 18:01:16 +02:00
< details id = "intro_reqs" >
< summary > Modify Segment Parameters< / summary >
2022-06-26 22:54:47 -05:00
2024-10-20 15:14:03 -04:00
< p >
2024-11-02 18:17:22 +01:00
< b style = "color: orange" > Changing segment parameters requires regenerating media segments before changes take effect.< / b >
< br / >
Per the jellyfin MediaSegments API, records must be updated individually and may be slow to regenerate.
2024-10-20 15:14:03 -04:00
< / p >
2024-10-11 18:01:16 +02:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "AnalysisPercent" > 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 >
2022-06-26 22:54:47 -05:00
< / div >
2024-10-11 18:01:16 +02:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "AnalysisLengthLimit" > 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 >
2022-06-26 22:54:47 -05:00
< / div >
2024-10-11 18:01:16 +02:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "MinimumIntroDuration" > Minimum introduction duration (in seconds) < / label >
< input id = "MinimumIntroDuration" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" > Similar sounding audio which is shorter than this duration will not be considered an introduction.< / div >
2022-07-03 01:59:16 -05:00
< / div >
2022-06-26 22:54:47 -05:00
2024-10-11 18:01:16 +02: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 >
2022-09-03 00:39:35 -05:00
< / div >
2024-10-11 18:01:16 +02: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 >
2024-04-13 14:07:12 -04:00
< / div >
2024-10-11 18:01:16 +02:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "MaximumCreditsDuration" > Maximum credits duration (in seconds) < / label >
< input id = "MaximumCreditsDuration" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" > Similar sounding audio which is longer than this duration will not be considered credits.< / div >
2024-04-13 14:07:12 -04:00
< / div >
2024-10-18 14:15:09 +02:00
< div class = "inputContainer" id = "movieCreditsDuration" >
< label class = "inputLabel inputLabelUnfocused" for = "MaximumMovieCreditsDuration" > Maximum movie credits duration (in seconds) < / label >
< input id = "MaximumMovieCreditsDuration" type = "number" is = "emby-input" min = "1" / >
< div class = "fieldDescription" > Segments longer than this duration will not be considered movie credits.< / div >
2024-10-20 15:14:03 -04:00
< br / >
2024-10-18 14:15:09 +02:00
< / div >
2024-03-04 07:08:11 -05:00
2024-10-28 20:11:50 -04:00
< p >
The amount of each item's audio or content that will be analyzed is determined using both the percentage of audio and maximum runtime to analyze. The minimum of (duration * percent, maximum runtime) is the amount that will be analyzed.
< / p >
2024-10-19 22:49:47 +02:00
2024-10-28 20:11:50 -04:00
< p >
If the audio percentage or maximum runtime settings are modified, the cached fingerprints and timestamps for each series, season, or movie you want to analyze with the modified settings < b > will have to be deleted< / b > .
< br / >
Increasing either of these settings will cause episode analysis to take much longer.
< / p >
2024-10-19 22:49:47 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "RegenerateMediaSegments" type = "checkbox" is = "emby-checkbox" / >
2024-11-05 14:19:02 -05:00
< span > Regenerate All Media Segments on Next Run< / span >
2024-10-19 22:49:47 +02:00
< / label >
2024-11-05 14:19:02 -05:00
< div class = "fieldDescription" > When enabled, this option will < b > overwrite all existing media segments< / b > with your current Intro Skipper timestamps during the next analysis. Upon completion, this option will be automatically disabled.< / div >
2024-10-19 22:49:47 +02:00
< / div >
< / details >
2024-10-11 18:01:16 +02:00
< details id = "silence" >
< summary > Silence Detection Options< / summary >
< br / >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SilenceDetectionMaximumNoise" > Noise tolerance < / label >
< input id = "SilenceDetectionMaximumNoise" type = "number" is = "emby-input" min = "-90" max = "0" / >
< div class = "fieldDescription" > Noise tolerance in negative decibels.< / div >
2022-09-02 01:27:49 -05:00
< / div >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SilenceDetectionMinimumDuration" > Minimum silence duration < / label >
< input id = "SilenceDetectionMinimumDuration" type = "number" is = "emby-input" min = "0" step = "0.01" / >
< div class = "fieldDescription" > Minimum silence duration in seconds before adjusting introduction end time.< / div >
< / div >
< / details >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< details id = "detection" >
< summary > Process Configuration< / summary >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< br / >
2024-10-28 20:11:50 -04: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" > Maximum number of simultaneous async episode analysis operations.< / div >
< / div >
2024-10-11 18:01:16 +02: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-11-05 14:19:02 -05:00
< b > WARNING: Disabling the cache will cause all fingerprints to be recreated during analysis. For debug use only!< / b >
2024-10-11 18:01:16 +02:00
< br / >
< / div >
2024-03-03 21:46:17 -05:00
< / div >
2024-10-11 18:01:16 +02:00
< 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 >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< option value = "BelowNormal" > Below Normal< / option >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< option value = "Normal" > Normal< / option >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< option value = "AboveNormal" > Above Normal< / option >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< option value = "High" > High< / option >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< option value = "RealTime" > Highest< / option >
< / select >
2024-03-03 21:46:17 -05:00
2024-10-11 18:01:16 +02:00
< div class = "fieldDescription" > Sets the relative priority of the analysis ffmpeg process to other parallel operations (ie. transcoding, chapter detection, etc).< / div >
2024-03-03 21:46:17 -05:00
< / div >
2024-10-11 18:01:16 +02:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "ProcessThreads" > ffmpeg Threads < / label >
< input id = "ProcessThreads" type = "number" is = "emby-input" min = "0" max = "16" / >
< 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 >
2024-03-03 21:46:17 -05:00
< / div >
2024-10-11 18:01:16 +02:00
< / details >
2024-10-21 05:42:27 -04:00
2024-11-02 18:17:22 +01:00
< p align = "center" style = "font-size: 0.75em" > EDL file generation has been removed. Please use endrl's < a href = "https://github.com/endrl/jellyfin-plugin-edl" > EDL plugin< / a > .< / p >
2024-10-11 18:01:16 +02:00
< / fieldset >
2022-05-05 18:26:02 -05:00
2024-10-11 18:01:16 +02:00
< fieldset class = "verticalSection-extrabottompadding" >
< legend > Playback< / legend >
2022-09-02 01:54:49 -05:00
2024-10-11 18:01:16 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "AutoSkip" type = "checkbox" is = "emby-checkbox" / >
2024-10-28 20:11:50 -04:00
< span > Automatically Skip Intros< / span >
2024-10-11 18:01:16 +02:00
< / label >
2024-05-13 14:25:52 +02:00
2024-10-11 18:01:16 +02:00
< div class = "fieldDescription" >
2024-11-04 15:22:04 -05:00
If checked, intros will be automatically skipped for < b > all< / b > clients.< br / >
Note: Cannot be disabled from client popup (gear icon) settings.< br / >
2024-10-11 18:01:16 +02:00
If you access Jellyfin through a reverse proxy, it must be configured to proxy websockets.< br / >
< / div >
2024-08-02 13:41:03 +00:00
< / div >
2024-05-13 14:25:52 +02:00
2024-10-11 18:01:16 +02:00
< div id = "divSkipFirstEpisode" class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "SkipFirstEpisode" type = "checkbox" is = "emby-checkbox" / >
2024-10-28 20:11:50 -04:00
< span > Play Intro for First Episode of a Season< / span >
2024-10-11 18:01:16 +02:00
< / label >
2022-09-02 01:54:49 -05:00
2024-10-11 18:01:16 +02:00
< div class = "fieldDescription" > If checked, auto skip will play the introduction of the first episode in a season.< br / > < / div >
< br / >
2024-08-02 13:41:03 +00:00
< / div >
2024-03-02 15:28:28 -05:00
2024-10-11 18:01:16 +02:00
< div id = "divSecondsOfIntroStartToPlay" class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SecondsOfIntroStartToPlay" > Intro skip delay (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 >
2024-03-02 15:28:28 -05:00
< br / >
< / div >
2024-10-02 19:13:17 -04:00
2024-10-28 20:11:50 -04:00
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "RemainingSecondsOfIntro" > Intro playback duration (in seconds) < / label >
< input id = "RemainingSecondsOfIntro" type = "number" is = "emby-input" min = "0" / >
< div class = "fieldDescription" > Seconds of introduction ending that should be played. Defaults to 2.< / div >
< / div >
2024-10-11 18:01:16 +02:00
< div class = "checkboxContainer checkboxContainer-withDescription" >
2024-10-09 06:42:51 -04:00
< label class = "emby-checkbox-label" >
2024-10-11 18:01:16 +02:00
< input id = "AutoSkipCredits" type = "checkbox" is = "emby-checkbox" / >
2024-10-28 20:11:50 -04:00
< span > Automatically Skip Credits< / span >
2024-10-09 06:42:51 -04:00
< / label >
< div class = "fieldDescription" >
2024-11-04 15:22:04 -05:00
If checked, credits will be automatically skipped for < b > all< / b > clients.< br / >
Note: Cannot be disabled from client popup (gear icon) settings.< br / >
2024-10-11 18:01:16 +02:00
If you access Jellyfin through a reverse proxy, it must be configured to proxy websockets.< br / >
2024-10-09 06:42:51 -04:00
< / div >
< / div >
2024-10-11 18:01:16 +02:00
< div id = "divSecondsOfCreditsStartToPlay" class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "SecondsOfCreditsStartToPlay" > Credit skip delay (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 >
2024-10-09 06:42:51 -04:00
< br / >
< / div >
2024-10-12 06:35:46 -04:00
< details id = "AutoSkipClientList" style = "padding-bottom: 1em" >
< summary > Auto Skip Client List< / summary >
< br / >
< div class = "checkboxList paperList" style = "padding: 0.5em 1em" id = "autoSkipCheckboxes" > < / div >
< label class = "inputLabel" for = "ClientList" > < / label >
< input id = "ClientList" type = "hidden" is = "emby-input" / >
2024-11-04 15:22:04 -05:00
< div class = "fieldDescription" > Clients enabled in this list will < b > always skip automatically< / b > , regardless of the button settings below.< / div >
2024-10-12 06:35:46 -04:00
< / details >
2024-10-11 18:01:16 +02:00
< div id = "SkipButtonContainer" class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
2024-10-27 05:23:12 -04:00
< input id = "SkipButtonEnabled" type = "checkbox" is = "emby-checkbox" / >
2024-10-28 20:11:50 -04:00
< span id = "SkipButtonVisibleLabel" > Show All Skip Buttons< / span >
2024-10-09 06:42:51 -04:00
< / label >
2024-10-11 18:01:16 +02:00
2024-10-09 06:42:51 -04:00
< div class = "fieldDescription" >
2024-11-04 15:22:04 -05:00
(< b > Restart required!< / b > ) If checked, a skip button will be added < b > to the server< / b > and displayed according to the settings below.< br / >
2024-11-05 05:25:41 -05:00
This button is < b > separate< / b > from the Media Segment Actions in Jellyfin 10.10 and compatible clients.< br / >
2024-10-09 06:42:51 -04:00
< / div >
< / div >
2022-05-05 18:26:02 -05:00
2024-11-02 18:17:22 +01:00
< div id = "warningMessage" style = "color: #721c24; background-color: #f7cf1f; border: 1px solid #f5c6cb; border-radius: 4px; padding: 10px; margin-bottom: 10px" > Failed to add skip button to web interface. See < a href = "https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible" target = "_blank" rel = "noopener noreferrer" > troubleshooting guide< / a > for the most common issues.< / div >
2022-09-02 01:27:49 -05:00
2024-10-28 20:11:50 -04:00
< details >
< summary > User Interface Customization< / summary >
2024-10-11 18:01:16 +02:00
2024-11-05 05:25:41 -05:00
< p >
< b style = "color: orange" > These settings do not apply to Media Segment Actions in Jellyfin 10.10 and compatible clients.< / b >
< / p >
2024-10-28 20:11:50 -04:00
< div id = "SkipButtonSettings" >
< div id = "PersistContainer" class = "checkboxContainer checkboxContainer-withDescription" >
< label class = "emby-checkbox-label" >
< input id = "PersistSkipButton" type = "checkbox" is = "emby-checkbox" / >
< span > Display Button for Segment Duration< / span >
< / label >
< div class = "fieldDescription" >
2024-11-04 15:22:04 -05:00
If checked, skip button will remain visible for the entire intro (offset and timeout are ignored).< br / >
2024-10-28 20:11:50 -04:00
Note: If unchecked, button will only appear in the player controls after the set timeout.
< / div >
2024-10-11 18:01:16 +02:00
< / div >
2022-11-06 21:20:52 -06:00
2024-10-28 20:11:50 -04:00
< div id = "divShowPromptAdjustment" class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "ShowPromptAdjustment" > Skip prompt offset (in seconds) < / label >
< input id = "ShowPromptAdjustment" type = "number" is = "emby-input" min = "0" / >
< div class = "fieldDescription" > Seconds to display skip prompt before introduction begins.< / div >
< br / >
< / div >
2022-11-06 21:20:52 -06:00
2024-10-28 20:11:50 -04:00
< div id = "divHidePromptAdjustment" class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "HidePromptAdjustment" > Skip prompt timeout (in seconds) < / label >
< input id = "HidePromptAdjustment" type = "number" is = "emby-input" min = "2" / >
< div class = "fieldDescription" > Seconds after introduction before skip prompt is hidden.< / div >
< br / >
< / div >
2022-11-06 21:20:52 -06:00
2024-11-05 06:19:15 -05:00
< div class = "inputContainer" >
< label class = "inputLabel" for = "SkipButtonIntroText" > Skip intro button text < / label >
< input id = "SkipButtonIntroText" type = "text" is = "emby-input" / >
< div class = "fieldDescription" > Text to display in the skip intro button.< / div >
< / div >
2024-05-13 14:25:52 +02:00
2024-11-05 06:19:15 -05: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-05-13 14:25:52 +02:00
< / div >
2022-05-05 18:26:02 -05:00
2024-10-11 18:01:16 +02:00
< div id = "divAutoSkipNotificationText" class = "inputContainer" >
< label class = "inputLabel" for = "AutoSkipNotificationText" > Auto skip intro notification message < / label >
< input id = "AutoSkipNotificationText" type = "text" is = "emby-input" / >
< div class = "fieldDescription" > Message shown after automatically skipping an introduction. Leave blank to disable notification.< / div >
< / div >
2022-07-17 02:13:02 -05:00
2024-10-11 18:01:16 +02:00
< div id = "divAutoSkipCreditsNotificationText" class = "inputContainer" >
< label class = "inputLabel" for = "AutoSkipCreditsNotificationText" > Auto skip credits 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 >
< / details >
< / fieldset >
2024-03-01 19:56:43 -05:00
2024-10-11 18:01:16 +02:00
< div >
< button is = "emby-button" type = "submit" class = "raised button-submit block emby-button" >
< span > Save< / span >
< / button >
< / div >
< br / >
2024-03-01 19:56:43 -05:00
2024-10-11 18:01:16 +02:00
< fieldset class = "verticalSection-extrabottompadding" >
< legend > Advanced< / legend >
2024-03-01 19:56:43 -05:00
2024-10-11 18:01:16 +02:00
< details id = "visualizer" >
2024-11-04 15:22:04 -05:00
< summary > Edit Timestamps & Fingerprints< / summary >
2024-09-20 14:18:04 +03:00
2024-10-11 18:01:16 +02:00
< br / >
2024-10-18 14:15:09 +02:00
< label class = "inputLabel" for = "troubleshooterShow" > Select TV series / movie to manage< / label >
2024-10-11 18:01:16 +02:00
< select is = "emby-select" id = "troubleshooterShow" class = "emby-select-withcolor emby-select" > < / select >
2024-10-18 14:15:09 +02:00
< div id = "seasonSelection" >
< label class = "inputLabel" for = "troubleshooterSeason" > Select season to manage< / label >
< select is = "emby-select" id = "troubleshooterSeason" class = "emby-select-withcolor emby-select" > < / select >
< / div >
2024-10-11 18:01:16 +02:00
< br / >
2024-09-20 14:18:04 +03:00
2024-11-02 18:17:22 +01:00
< div id = "analyzerActionsSection" style = "display: none" >
< h3 style = "margin: 0" > Analyzer actions< / h3 >
2024-10-11 18:01:16 +02:00
< p style = "margin: 0" >
2024-11-02 18:17:22 +01:00
Choose how segments should be analyzed for this season.< br / >
Default uses all available detection methods (Chromaprint, Chapter, and BlackFrame for credits).< br / >
Select specific methods to limit analysis, or None to skip detection entirely.
2024-10-11 18:01:16 +02:00
< / p >
< br / >
2024-03-01 19:56:43 -05:00
2024-11-02 18:17:22 +01:00
< div id = "analyzerActionsContainer" >
< label for = "actionIntro" style = "margin-right: 1.5em; display: inline-block" >
< span > Introduction analysis< / span >
< select is = "emby-select" id = "actionIntro" class = "emby-select-withcolor emby-select" >
< option value = "Default" > Default< / option >
< option value = "Chapter" > Chapter< / option >
< option value = "Chromaprint" > Chromaprint< / option >
< option value = "None" > None< / option >
< / select >
2024-10-11 18:01:16 +02:00
< / label >
2024-11-02 18:17:22 +01:00
< label for = "actionCredits" style = "margin-right: 1.5em; display: inline-block" >
< span > Credits analysis< / span >
< select is = "emby-select" id = "actionCredits" class = "emby-select-withcolor emby-select" >
< option value = "Default" > Default< / option >
< option value = "Chapter" > Chapter< / option >
< option value = "Chromaprint" > Chromaprint< / option >
< option value = "BlackFrame" > BlackFrame< / option >
< option value = "None" > None< / option >
< / select >
2024-10-11 18:01:16 +02:00
< / label >
< / div >
< br / >
2024-11-02 18:17:22 +01:00
< button is = "emby-button" id = "saveAnalyzerActions" class = "raised button-submit block emby-button" style = "display: none" > Apply changes.< / button >
2024-10-06 12:13:30 +02:00
< / div >
2024-10-11 18:01:16 +02:00
< br / >
2024-10-18 14:15:09 +02:00
< div id = "episodeSelection" >
< label class = "inputLabel" for = "troubleshooterEpisode1" > Select the first episode< / label >
< select is = "emby-select" id = "troubleshooterEpisode1" class = "emby-select-withcolor emby-select" > < / select >
< label class = "inputLabel" for = "troubleshooterEpisode1" > Select the second episode< / label >
< select is = "emby-select" id = "troubleshooterEpisode2" class = "emby-select-withcolor emby-select" > < / select >
< br / >
< / div >
2024-10-11 18:01:16 +02:00
< div id = "timestampEditor" style = "display: none" >
< h3 style = "margin: 0" > Introduction timestamp editor< / h3 >
< br / >
< h4 style = "margin: 0" id = "editLeftEpisodeTitle" > < / h4 >
< br / >
< div class = "inlineForm" >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "introStart" > Intro Start< / label >
< input type = "text" id = "editLeftIntroEpisodeStartDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editLeftIntroEpisodeStartEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "introEnd" > Intro End< / label >
< input type = "text" id = "editLeftIntroEpisodeEndDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editLeftIntroEpisodeEndEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
< / div >
< div class = "inlineForm" >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "creditsStart" > Credits Start< / label >
< input type = "text" id = "editLeftCreditEpisodeStartDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editLeftCreditEpisodeStartEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "creditsEnd" > Credits End< / label >
< input type = "text" id = "editLeftCreditEpisodeEndDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editLeftCreditEpisodeEndEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
< / div >
< br / >
2024-10-18 14:15:09 +02:00
< div id = "rightEpisodeEditor" >
< h4 style = "margin: 0" id = "editRightEpisodeTitle" > < / h4 >
< br / >
< div class = "inlineForm" >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "introStart" > Intro Start< / label >
< input type = "text" id = "editRightIntroEpisodeStartDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editRightIntroEpisodeStartEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "introEnd" > Intro End< / label >
< input type = "text" id = "editRightIntroEpisodeEndDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editRightIntroEpisodeEndEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
2024-10-11 18:01:16 +02:00
< / div >
2024-10-18 14:15:09 +02:00
< div class = "inlineForm" >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "creditsStart" > Credits Start< / label >
< input type = "text" id = "editRightCreditEpisodeStartDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editRightCreditEpisodeStartEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
< div class = "inputContainer" >
< label class = "inputLabel inputLabelUnfocused" for = "creditsEnd" > Credits End< / label >
< input type = "text" id = "editRightCreditEpisodeEndDisplay" class = "emby-input custom-time-input" readonly / >
< input type = "number" id = "editRightCreditEpisodeEndEdit" class = "emby-input custom-time-input" style = "display: none" step = "any" min = "0" / >
< / div >
2024-10-11 18:01:16 +02:00
< / div >
2024-10-18 14:15:09 +02:00
< br / >
2024-10-11 18:01:16 +02:00
< / div >
2024-10-18 14:15:09 +02:00
< button is = "emby-button" id = "btnUpdateTimestamps" class = "raised button-submit block emby-button" type = "button" > Update timestamps< / button >
2024-10-11 18:01:16 +02:00
< br / >
2024-10-06 12:13:30 +02:00
< / div >
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
< div id = "timestampErrorDiv" style = "display: none" >
2024-10-22 19:13:33 -04:00
< br / >
2024-10-11 18:01:16 +02:00
< textarea id = "timestampError" rows = "2" cols = "75" readonly > < / textarea >
< br / >
< br / >
< / div >
2024-03-04 10:41:14 -05:00
2024-10-22 19:13:33 -04:00
< div id = "fingerprintVisualizer" style = "display: none" >
2024-10-18 14:15:09 +02: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 >
< tr >
< td style = "min-width: 100px; font-weight: bold" > Key< / td >
< td style = "font-weight: bold" > Function< / td >
< / tr >
< / thead >
< tbody >
< tr >
< td > Up arrow< / td >
< td > Shift the left episode up by 0.1238 seconds. Holding control will shift the episode by 10 seconds.< / td >
< / tr >
< tr >
< td > Down arrow< / td >
< td > Shift the left episode down by 0.1238 seconds. 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 / >
2024-03-01 19:56:43 -05:00
2024-10-18 14:15:09 +02:00
< span > Shift amount:< / span >
< input type = "number" min = "-3000" max = "3000" value = "0" id = "offset" / >
< br / >
< span id = "suggestedShifts" >
2024-10-22 19:13:33 -04:00
< span > Suggested shifts: < / span >
2024-10-18 14:15:09 +02:00
< / span >
2024-10-22 19:13:33 -04:00
< br / >
< br / >
2024-10-18 14:15:09 +02:00
< canvas id = "troubleshooter" style = "display: none" > < / canvas >
< span id = "timestampContainer" >
2024-10-22 19:13:33 -04:00
< span id = "timestamps" > < / span >
< br / >
2024-10-18 14:15:09 +02:00
< span id = "intros" > < / span >
< / span >
2024-10-22 19:13:33 -04:00
< br / >
2024-10-18 14:15:09 +02:00
< / div >
2024-10-11 18:01:16 +02:00
< br / >
2024-03-05 23:32:51 -05:00
2024-10-11 18:01:16 +02:00
< div id = "eraseSeasonContainer" style = "display: none" >
2024-10-18 14:15:09 +02:00
< button is = "emby-button" id = "btnEraseSeasonTimestamps" class = "button-submit emby-button" type = "button" > Erase all timestamps for this season< / button >
2024-06-07 17:09:48 +02:00
2024-10-11 18:01:16 +02:00
< input type = "checkbox" id = "eraseSeasonCacheCheckbox" style = "margin-left: 10px" / >
< label for = "eraseSeasonCacheCheckbox" style = "margin-left: 5px" > Erase cached fingerprint files< / label >
2024-10-18 14:15:09 +02:00
< br / >
2024-10-11 18:01:16 +02:00
< / div >
2024-03-05 23:32:51 -05:00
2024-10-18 14:15:09 +02:00
< div id = "eraseMovieContainer" style = "display: none" >
< button is = "emby-button" id = "btnEraseMovieTimestamps" class = "button-submit emby-button" type = "button" > Erase all timestamps for this movie< / button >
2024-03-05 23:32:51 -05:00
2024-10-18 14:15:09 +02:00
< input type = "checkbox" id = "eraseMovieCacheCheckbox" style = "margin-left: 10px" / >
< label for = "eraseMovieCacheCheckbox" style = "margin-left: 5px" > Erase cached fingerprint files< / label >
< br / >
< / div >
< button is = "emby-button" class = "button-submit emby-button" id = "btnEraseIntroTimestamps" > Erase all introduction timestamps (globally)< / button >
2024-10-11 18:01:16 +02:00
< br / >
2024-06-07 17:09:48 +02:00
2024-10-18 14:15:09 +02:00
< button is = "emby-button" class = "button-submit emby-button" id = "btnEraseCreditTimestamps" > Erase all end credits timestamps (globally)< / button >
< br / >
< br / >
2024-10-11 18:01:16 +02:00
< input type = "checkbox" id = "eraseModeCacheCheckbox" style = "margin-left: 10px" / >
< label for = "eraseModeCacheCheckbox" style = "margin-left: 5px" > Erase cached fingerprint files< / label >
2024-10-18 14:15:09 +02:00
< br / >
< br / >
2024-10-11 18:01:16 +02:00
< / details >
2024-08-10 10:25:06 +00:00
2024-10-27 12:35:39 -04:00
< details id = "support" >
< summary > Support Bundle Info< / summary >
< textarea id = "supportBundle" rows = "20" cols = "75" readonly > < / textarea >
< / details >
2024-10-11 18:01:16 +02:00
< details id = "storage" >
< br / >
< summary > Storage Usage< / summary >
< div class = "fieldDescription" > See how much space each library uses.< / div >
2024-10-29 10:19:19 -04:00
< textarea id = "storageText" rows = "20" cols = "75" readonly > < / textarea >
2024-10-11 18:01:16 +02:00
< / details >
< / fieldset >
< / form >
< / div >
2024-03-01 19:56:43 -05:00
< / div >
2022-07-16 23:35:15 -05:00
2024-10-11 18:01:16 +02:00
< script src = "configurationpage?name=visualizer.js" > < / script >
< script >
// first and second episodes to fingerprint & compare
var lhs = [];
var rhs = [];
// fingerprint point comparison & miminum similarity threshold (at most 6 bits out of 32 can be different)
var fprDiffs = [];
var fprDiffMinimum = (1 - 6 / 32) * 100;
// seasons grouped by show
var shows = {};
// settings elements
var visualizer = document.querySelector("details#visualizer");
var support = document.querySelector("details#support");
var storage = document.querySelector("details#storage");
var btnEraseIntroTimestamps = document.querySelector("button#btnEraseIntroTimestamps");
var btnEraseCreditTimestamps = document.querySelector("button#btnEraseCreditTimestamps");
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
var configurationFields = [
// analysis
"MaxParallelism",
"SelectedLibraries",
"ClientList",
"AnalysisPercent",
"AnalysisLengthLimit",
"MinimumIntroDuration",
"MaximumIntroDuration",
"MinimumCreditsDuration",
"MaximumCreditsDuration",
2024-10-18 14:15:09 +02:00
"MaximumMovieCreditsDuration",
2024-10-11 18:01:16 +02:00
"ProcessPriority",
"ProcessThreads",
// playback
"ShowPromptAdjustment",
"HidePromptAdjustment",
"RemainingSecondsOfIntro",
"SecondsOfIntroStartToPlay",
"SecondsOfCreditsStartToPlay",
// internals
"SilenceDetectionMaximumNoise",
"SilenceDetectionMinimumDuration",
// UI customization
"SkipButtonIntroText",
"SkipButtonEndCreditsText",
"AutoSkipNotificationText",
"AutoSkipCreditsNotificationText",
];
2024-10-27 05:23:12 -04:00
var booleanConfigurationFields = ["AutoDetectIntros", "AutoDetectCredits", "AnalyzeMovies", "AnalyzeSeasonZero", "SelectAllLibraries", "UpdateMediaSegments", "RegenerateMediaSegments", "CacheFingerprints", "AutoSkip", "AutoSkipCredits", "SkipFirstEpisode", "PersistSkipButton", "SkipButtonEnabled"];
2024-10-11 18:01:16 +02:00
// visualizer elements
2024-11-02 18:17:22 +01:00
var analyzerActionsSection = document.querySelector("div#analyzerActionsSection");
var actionIntro = analyzerActionsSection.querySelector("select#actionIntro");
var actionCredits = analyzerActionsSection.querySelector("select#actionCredits");
var saveAnalyzerActionsButton = analyzerActionsSection.querySelector("button#saveAnalyzerActions");
2024-10-11 18:01:16 +02:00
var canvas = document.querySelector("canvas#troubleshooter");
var selectShow = document.querySelector("select#troubleshooterShow");
2024-10-18 14:15:09 +02:00
var seasonSelection = document.getElementById("seasonSelection");
2024-10-11 18:01:16 +02:00
var selectSeason = document.querySelector("select#troubleshooterSeason");
2024-10-18 14:15:09 +02:00
var episodeSelection = document.getElementById("episodeSelection");
2024-10-11 18:01:16 +02:00
var selectEpisode1 = document.querySelector("select#troubleshooterEpisode1");
var selectEpisode2 = document.querySelector("select#troubleshooterEpisode2");
var txtOffset = document.querySelector("input#offset");
var txtSuggested = document.querySelector("span#suggestedShifts");
var btnSeasonEraseTimestamps = document.querySelector("button#btnEraseSeasonTimestamps");
var eraseSeasonContainer = document.getElementById("eraseSeasonContainer");
2024-10-18 14:15:09 +02:00
var btnMovieEraseTimestamps = document.querySelector("button#btnEraseMovieTimestamps");
var eraseMovieContainer = document.getElementById("eraseMovieContainer");
2024-10-11 18:01:16 +02:00
var timestampError = document.querySelector("textarea#timestampError");
var timestampEditor = document.querySelector("#timestampEditor");
2024-10-18 14:15:09 +02:00
var rightEpisodeEditor = document.getElementById("rightEpisodeEditor");
2024-10-11 18:01:16 +02:00
var btnUpdateTimestamps = document.querySelector("button#btnUpdateTimestamps");
var timeContainer = document.querySelector("span#timestampContainer");
2024-10-18 14:15:09 +02:00
var fingerprintVisualizer = document.getElementById("fingerprintVisualizer");
2024-10-11 18:01:16 +02:00
var windowHashInterval = 0;
2024-10-18 14:15:09 +02:00
var analyzeMovies = document.getElementById("AnalyzeMovies");
2024-10-11 18:01:16 +02:00
var autoSkip = document.querySelector("input#AutoSkip");
2024-10-27 05:23:12 -04:00
var skipButtonVisible = document.getElementById("SkipButtonEnabled");
2024-10-11 18:01:16 +02:00
var skipButtonVisibleLabel = document.getElementById("SkipButtonVisibleLabel");
var skipButtonSettings = document.getElementById("SkipButtonSettings");
var selectAllLibraries = document.querySelector("input#SelectAllLibraries");
var librariesContainer = document.querySelector("div.folderAccessListContainer");
var skipFirstEpisode = document.querySelector("div#divSkipFirstEpisode");
var secondsOfIntroStartToPlay = document.querySelector("div#divSecondsOfIntroStartToPlay");
var autoSkipClientList = document.getElementById("AutoSkipClientList");
var secondsOfCreditsStartToPlay = document.querySelector("div#divSecondsOfCreditsStartToPlay");
2024-10-18 14:15:09 +02:00
var movieCreditsDuration = document.getElementById("movieCreditsDuration");
2024-10-11 18:01:16 +02:00
var autoSkipNotificationText = document.querySelector("div#divAutoSkipNotificationText");
var autoSkipCredits = document.querySelector("input#AutoSkipCredits");
var autoSkipCreditsNotificationText = document.querySelector("div#divAutoSkipCreditsNotificationText");
function skipButtonVisibleChanged() {
if (autoSkip.checked & & autoSkipCredits.checked) {
skipButtonSettings.style.display = "none";
} else if (skipButtonVisible.checked) {
skipButtonSettings.style.display = "unset";
} else {
skipButtonSettings.style.display = "none";
}
2024-10-03 12:58:12 -04:00
}
2024-10-11 18:01:16 +02:00
skipButtonVisible.addEventListener("change", skipButtonVisibleChanged);
function skipButtonVisibleText() {
if (autoSkip.checked & & autoSkipCredits.checked) {
autoSkipClientList.style.display = "none";
skipButtonVisibleLabel.textContent = "Button unavailable due to auto skip";
} else if (autoSkip.checked) {
autoSkipClientList.style.display = "unset";
autoSkipClientList.style.width = "100%";
2024-10-28 20:11:50 -04:00
skipButtonVisibleLabel.textContent = "Show Skip Credit Button";
2024-10-11 18:01:16 +02:00
} else if (autoSkipCredits.checked) {
autoSkipClientList.style.display = "unset";
autoSkipClientList.style.width = "100%";
2024-10-28 20:11:50 -04:00
skipButtonVisibleLabel.textContent = "Show Skip Intro Button";
2024-10-11 18:01:16 +02:00
} else {
autoSkipClientList.style.display = "unset";
autoSkipClientList.style.width = "100%";
2024-10-28 20:11:50 -04:00
skipButtonVisibleLabel.textContent = "Show All Skip Buttons";
2024-10-11 18:01:16 +02:00
}
skipButtonVisibleChanged();
2024-10-02 19:13:17 -04:00
}
2024-10-11 18:01:16 +02:00
function autoSkipChanged() {
if (autoSkip.checked) {
skipFirstEpisode.style.display = "unset";
autoSkipNotificationText.style.display = "unset";
secondsOfIntroStartToPlay.style.display = "unset";
} else {
skipFirstEpisode.style.display = "none";
autoSkipNotificationText.style.display = "none";
secondsOfIntroStartToPlay.style.display = "none";
}
skipButtonVisibleText();
2024-03-18 21:10:24 +01:00
}
2024-10-11 18:01:16 +02:00
autoSkip.addEventListener("change", autoSkipChanged);
function autoSkipCreditsChanged() {
if (autoSkipCredits.checked) {
autoSkipCreditsNotificationText.style.display = "unset";
secondsOfCreditsStartToPlay.style.display = "unset";
} else {
autoSkipCreditsNotificationText.style.display = "none";
secondsOfCreditsStartToPlay.style.display = "none";
}
skipButtonVisibleText();
2024-05-13 14:25:52 +02:00
}
2024-10-11 18:01:16 +02:00
autoSkipCredits.addEventListener("change", autoSkipCreditsChanged);
2024-05-13 14:25:52 +02:00
2024-10-11 18:01:16 +02:00
skipButtonVisibleText(); // run once on launch for legacy installs
2024-10-03 12:58:12 -04:00
2024-10-11 18:01:16 +02:00
function selectAllLibrariesChanged() {
if (selectAllLibraries.checked) {
librariesContainer.style.display = "none";
} else {
librariesContainer.style.display = "unset";
2024-09-14 18:52:39 +02:00
}
2024-03-18 21:10:24 +01:00
}
2024-03-01 14:45:27 -05:00
2024-10-11 18:01:16 +02:00
selectAllLibraries.addEventListener("change", selectAllLibrariesChanged);
2024-03-01 14:45:27 -05:00
2024-10-11 18:01:16 +02:00
function updateList(textField, container) {
textField.value = Array.from(container.querySelectorAll('input[type="checkbox"]:checked'))
.map((checkbox) => checkbox.nextElementSibling.textContent)
.join(", ");
2022-06-29 20:52:16 -05:00
}
2024-10-11 18:01:16 +02:00
function generateCheckboxList(items, containerId, textFieldId) {
const container = document.getElementById(containerId);
const checkedItems = new Set(document.getElementById(textFieldId).value.split(", ").filter(Boolean));
const fragment = document.createDocumentFragment();
items.forEach((item) => {
const label = document.createElement("label");
label.className = "emby-checkbox-label";
label.innerHTML = '< input type = "checkbox" is = "emby-checkbox" ' + ( checkedItems . has ( item ) ? " checked " : " " ) + " > " + '< span class = "checkboxLabel" > ' + item + "< / span > ";
fragment.appendChild(label);
});
container.innerHTML = "";
container.appendChild(fragment);
container.addEventListener(
"change",
(e) => {
if (e.target.type === "checkbox") updateList(document.getElementById(textFieldId), container);
},
{ passive: true },
);
}
2022-08-30 02:39:10 -05:00
2024-10-11 18:01:16 +02:00
async function generateAutoSkipClientList() {
const response = await getJson("Devices");
const devices = [...new Set(response.Items.map((item) => item.AppName))];
generateCheckboxList(devices, "autoSkipCheckboxes", "ClientList");
}
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
async function populateLibraries() {
const response = await getJson("Library/VirtualFolders");
2024-10-18 14:15:09 +02:00
const tvLibraries = response.filter((item) => item.CollectionType === undefined || item.CollectionType === "tvshows" || item.CollectionType === "movies");
2024-10-11 18:01:16 +02:00
const libraryNames = tvLibraries.map((lib) => lib.Name || "Unnamed Library");
generateCheckboxList(libraryNames, "libraryCheckboxes", "SelectedLibraries");
}
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
var persistSkip = document.querySelector("input#PersistSkipButton");
var showAdjustment = document.querySelector("div#divShowPromptAdjustment");
var hideAdjustment = document.querySelector("div#divHidePromptAdjustment");
// prevent setting unavailable options
async function persistSkipChanged() {
if (persistSkip.checked) {
showAdjustment.style.display = "none";
hideAdjustment.style.display = "none";
} else {
showAdjustment.style.display = "unset";
hideAdjustment.style.display = "unset";
2024-09-21 18:06:11 +02:00
}
2022-05-30 02:23:36 -05:00
}
2024-10-11 18:01:16 +02:00
persistSkip.addEventListener("change", persistSkipChanged);
2022-06-29 20:52:16 -05:00
2024-10-18 14:15:09 +02:00
async function analyzeMoviesChanged() {
if (analyzeMovies.checked) {
movieCreditsDuration.style.display = "unset";
} else {
movieCreditsDuration.style.display = "none";
}
}
analyzeMovies.addEventListener("change", analyzeMoviesChanged);
2024-10-11 18:01:16 +02:00
// when the fingerprint visualizer opens, populate show names
async function visualizerToggled() {
if (!visualizer.open) {
2024-11-02 18:17:22 +01:00
analyzerActionsSection.style.display = "none";
saveAnalyzerActionsButton.style.display = "none";
2024-10-11 18:01:16 +02:00
return;
}
2024-09-21 18:06:11 +02:00
2024-10-11 18:01:16 +02:00
// ensure the series select is empty
selectShow.innerHTML = "";
2024-09-21 18:06:11 +02:00
2024-10-11 18:01:16 +02:00
Dashboard.showLoadingMsg();
shows = await getJson("Intros/Shows");
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
// Create an object to store shows by library
let showsByLibrary = {};
// Categorize shows by LibraryName
for (const show in shows) {
const libraryName = shows[show].LibraryName || "Uncategorized";
if (!showsByLibrary[libraryName]) {
showsByLibrary[libraryName] = [];
}
showsByLibrary[libraryName].push({
value: show,
text: shows[show].SeriesName + " (" + shows[show].ProductionYear + ")",
});
}
// Add categorized shows to the select element
for (const library in showsByLibrary) {
const optgroup = document.createElement("optgroup");
optgroup.label = library;
2022-08-25 00:39:20 -05:00
2024-10-11 18:01:16 +02:00
showsByLibrary[library].forEach(function (show) {
const option = document.createElement("option");
option.value = show.value;
option.textContent = show.text;
optgroup.appendChild(option);
});
selectShow.appendChild(optgroup);
}
selectShow.value = "";
Dashboard.hideLoadingMsg();
2022-08-25 00:39:20 -05:00
}
2024-10-11 18:01:16 +02: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();
// 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);
// Attempt to copy it to the clipboard automatically, falling back
// to prompting the user to press Ctrl + C.
try {
navigator.clipboard.writeText(bundleText);
Dashboard.alert("Support bundle copied to clipboard");
} catch {
Dashboard.alert("Press Ctrl+C to copy support bundle");
}
2024-08-10 10:25:06 +00:00
}
2024-10-11 18:01:16 +02: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
2024-10-29 10:19:19 -04:00
const ta = document.querySelector("textarea#storageText");
2024-10-11 18:01:16 +02:00
ta.value = bundleText;
2022-05-30 02:23:36 -05:00
}
2024-10-11 18:01:16 +02:00
// show changed, populate seasons
async function showChanged() {
2024-10-18 14:15:09 +02:00
seasonSelection.style.display = "unset";
2024-10-11 18:01:16 +02:00
clearSelect(selectSeason);
eraseSeasonContainer.style.display = "none";
2024-10-18 14:15:09 +02:00
eraseMovieContainer.style.display = "none";
episodeSelection.style.display = "unset";
2024-10-11 18:01:16 +02:00
clearSelect(selectEpisode1);
clearSelect(selectEpisode2);
2024-10-18 14:15:09 +02:00
if (shows[selectShow.value].IsMovie) {
movieLoaded();
return;
}
2024-10-11 18:01:16 +02:00
// add all seasons from this show to the season select
for (const season in shows[selectShow.value].Seasons) {
addItem(selectSeason, "Season " + shows[selectShow.value].Seasons[season], season);
}
selectSeason.value = "";
2022-05-30 02:23:36 -05:00
}
2024-10-11 18:01:16 +02:00
// season changed, reload all episodes
async function seasonChanged() {
const seasonData = encodeURI(selectShow.value) + "/" + encodeURI(selectSeason.value);
Dashboard.showLoadingMsg();
2024-11-02 18:17:22 +01:00
// show the analyzer actions editor.
saveAnalyzerActionsButton.style.display = "block";
const analyzerActions = (await getJson("Intros/AnalyzerActions/" + encodeURI(selectSeason.value))) || { Introduction: "Default", Credits: "Default" };
actionIntro.value = analyzerActions.Introduction || "Default";
actionCredits.value = analyzerActions.Credits || "Default";
analyzerActionsSection.style.display = "unset";
2024-10-11 18:01:16 +02:00
// show the erase season button
eraseSeasonContainer.style.display = "unset";
clearSelect(selectEpisode1);
clearSelect(selectEpisode2);
let i = 1;
const episodes = await getJson("Intros/Show/" + seasonData);
for (const episode in episodes) {
const strI = i.toLocaleString("en", { minimumIntegerDigits: 2, maximumFractionDigits: 0 });
addItem(selectEpisode1, strI + ": " + episodes[episode].Name, episodes[episode].Id);
addItem(selectEpisode2, strI + ": " + episodes[episode].Name, episodes[episode].Id);
i++;
}
Dashboard.hideLoadingMsg();
setTimeout(() => {
selectEpisode1.selectedIndex = 0;
selectEpisode2.selectedIndex = 1;
episodeChanged();
}, 100);
2022-05-30 02:23:36 -05:00
}
2024-10-11 18:01:16 +02:00
// episode changed, get fingerprints & calculate diff
async function episodeChanged() {
if (!selectEpisode1.value || !selectEpisode2.value) {
return;
}
Dashboard.showLoadingMsg();
timestampError.value = "";
2024-10-18 14:15:09 +02:00
fingerprintVisualizer.style.display = "unset";
2024-10-11 18:01:16 +02:00
canvas.style.display = "none";
lhs = await getJson("Intros/Episode/" + selectEpisode1.value + "/Chromaprint");
if (lhs === undefined) {
timestampError.value += "Error: " + selectEpisode1.value + " fingerprints failed!\n";
} else if (lhs === null) {
2024-10-22 19:13:33 -04:00
timestampError.value += selectEpisode1.value + " fingerprints missing or incomplete.\n";
2024-10-11 18:01:16 +02:00
}
2024-10-18 14:15:09 +02:00
rightEpisodeEditor.style.display = "unset";
2024-10-11 18:01:16 +02:00
rhs = await getJson("Intros/Episode/" + selectEpisode2.value + "/Chromaprint");
if (rhs === undefined) {
timestampError.value += "Error: " + selectEpisode2.value + " fingerprints failed!";
} else if (rhs === null) {
2024-10-22 19:13:33 -04:00
timestampError.value += selectEpisode2.value + " fingerprints missing or incomplete.\n";
2024-10-11 18:01:16 +02:00
}
if (timestampError.value == "") {
timestampErrorDiv.style.display = "none";
} else {
timestampErrorDiv.style.display = "unset";
}
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
Dashboard.hideLoadingMsg();
2024-03-04 10:41:14 -05:00
2024-10-11 18:01:16 +02:00
txtOffset.value = "0";
refreshBounds();
renderTroubleshooter();
findExactMatches();
updateTimestampEditor();
2024-03-04 10:41:14 -05:00
}
2024-03-05 17:33:51 -05:00
2024-10-18 14:15:09 +02:00
async function movieLoaded() {
Dashboard.showLoadingMsg();
2024-11-02 18:17:22 +01:00
saveAnalyzerActionsButton.textContent = "Apply to movie";
2024-10-18 14:15:09 +02:00
seasonSelection.style.display = "none";
episodeSelection.style.display = "none";
eraseMovieContainer.style.display = "unset";
timestampError.value = "";
fingerprintVisualizer.style.display = "none";
lhs = await getJson("Intros/Episode/" + selectShow.value + "/Chromaprint");
if (lhs === undefined) {
timestampError.value += "Error: " + selectShow.value + " fingerprints failed!\n";
} else if (lhs === null) {
2024-10-22 19:13:33 -04:00
timestampError.value += selectShow.value + " fingerprints missing or incomplete.\n";
2024-10-18 14:15:09 +02:00
}
rightEpisodeEditor.style.display = "none";
if (timestampError.value == "") {
timestampErrorDiv.style.display = "none";
} else {
timestampErrorDiv.style.display = "unset";
}
Dashboard.hideLoadingMsg();
txtOffset.value = "0";
// Try to get the timestamps of each intro, falling back a default value of zero if no intro was found
const leftEpisodeJson = await getJson("Episode/" + selectShow.value + "/Timestamps");
// Update the editor for the first and second episodes
timestampEditor.style.display = "unset";
document.querySelector("#editLeftEpisodeTitle").textContent = selectShow.value;
document.querySelector("#editLeftIntroEpisodeStartEdit").value = leftEpisodeJson.Introduction.Start;
document.querySelector("#editLeftIntroEpisodeEndEdit").value = leftEpisodeJson.Introduction.End;
document.querySelector("#editLeftCreditEpisodeStartEdit").value = leftEpisodeJson.Credits.Start;
2024-11-02 18:17:22 +01:00
document.querySelector("#editLeftCreditEpisodeEndEdit").value = leftEpisodeJson.Credits.End;
2024-10-18 14:15:09 +02:00
// Update display inputs
const inputs = document.querySelectorAll('#timestampEditor input[type="number"]');
inputs.forEach((input) => {
const displayInput = document.getElementById(input.id.replace("Edit", "Display"));
displayInput.value = formatTime(parseFloat(input.value) || 0);
});
setupTimeInputs();
}
2024-10-11 18:01:16 +02:00
function setupTimeInputs() {
const timestampEditor = document.getElementById("timestampEditor");
timestampEditor.querySelectorAll(".inputContainer").forEach((container) => {
const displayInput = container.querySelector('[id$="Display"]');
const editInput = container.querySelector('[id$="Edit"]');
displayInput.addEventListener("pointerdown", (e) => {
e.preventDefault();
switchToEdit(displayInput, editInput);
});
editInput.addEventListener("blur", () => switchToDisplay(displayInput, editInput));
displayInput.value = formatTime(parseFloat(editInput.value) || 0);
});
2024-03-04 10:41:14 -05:00
}
2024-03-05 17:33:51 -05:00
2024-10-11 18:01:16 +02:00
function switchToEdit(displayInput, editInput) {
displayInput.style.display = "none";
editInput.style.display = "";
editInput.focus();
2024-03-04 10:41:14 -05:00
}
2024-10-11 18:01:16 +02:00
function switchToDisplay(displayInput, editInput) {
editInput.style.display = "none";
displayInput.style.display = "";
2024-10-06 12:13:30 +02:00
displayInput.value = formatTime(parseFloat(editInput.value) || 0);
2024-10-11 18:01:16 +02:00
}
2024-10-06 12:13:30 +02:00
2024-10-11 18:01:16 +02:00
function formatTime(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
let result = [];
if (hours > 0) result.push(hours + " hour" + (hours !== 1 ? "s" : ""));
if (minutes > 0) result.push(minutes + " minute" + (minutes !== 1 ? "s" : ""));
if (seconds > 0 || result.length === 0) result.push(seconds + " second" + (seconds !== 1 ? "s" : ""));
return result.join(" ");
2022-05-30 02:23:36 -05:00
}
2024-10-11 18:01:16 +02:00
// 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
const leftEpisodeJson = await getJson("Episode/" + leftEpisode.value + "/Timestamps");
const rightEpisodeJson = await getJson("Episode/" + rightEpisode.value + "/Timestamps");
// Update the editor for the first and second episodes
timestampEditor.style.display = "unset";
document.querySelector("#editLeftEpisodeTitle").textContent = leftEpisode.text;
2024-10-16 16:05:59 +02:00
document.querySelector("#editLeftIntroEpisodeStartEdit").value = leftEpisodeJson.Introduction.Start;
document.querySelector("#editLeftIntroEpisodeEndEdit").value = leftEpisodeJson.Introduction.End;
document.querySelector("#editLeftCreditEpisodeStartEdit").value = leftEpisodeJson.Credits.Start;
document.querySelector("#editLeftCreditEpisodeEndEdit").value = leftEpisodeJson.Credits.End;
2024-10-11 18:01:16 +02:00
document.querySelector("#editRightEpisodeTitle").textContent = rightEpisode.text;
2024-10-16 16:05:59 +02:00
document.querySelector("#editRightIntroEpisodeStartEdit").value = rightEpisodeJson.Introduction.Start;
document.querySelector("#editRightIntroEpisodeEndEdit").value = rightEpisodeJson.Introduction.End;
document.querySelector("#editRightCreditEpisodeStartEdit").value = rightEpisodeJson.Credits.Start;
document.querySelector("#editRightCreditEpisodeEndEdit").value = rightEpisodeJson.Credits.End;
2024-10-11 18:01:16 +02:00
// Update display inputs
const inputs = document.querySelectorAll('#timestampEditor input[type="number"]');
inputs.forEach((input) => {
const displayInput = document.getElementById(input.id.replace("Edit", "Display"));
displayInput.value = formatTime(parseFloat(input.value) || 0);
2024-05-13 14:25:52 +02:00
});
2024-10-11 18:01:16 +02:00
setupTimeInputs();
2022-07-17 01:54:05 -05:00
}
2024-10-11 18:01:16 +02:00
// adds an item to a dropdown
function addItem(select, text, value) {
let item = new Option(text, value);
select.add(item);
}
2020-07-04 23:33:19 +09:00
2024-10-11 18:01:16 +02:00
// clear a select of items
function clearSelect(select) {
timestampError.value = "";
timestampErrorDiv.style.display = "none";
timestampEditor.style.display = "none";
timeContainer.style.display = "none";
canvas.style.display = "none";
let i,
L = select.options.length - 1;
for (i = L; i >= 0; i--) {
select.remove(i);
}
}
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
// make an authenticated GET to the server and parse the response as JSON
async function getJson(url) {
return await fetchWithAuth(url, "GET")
.then((r) => {
if (r.ok) {
return r.json();
} else {
return null;
}
})
.catch((err) => {
console.debug(err);
});
}
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
// make an authenticated fetch to the server
async function fetchWithAuth(url, method, body) {
url = ApiClient.serverAddress() + "/" + url;
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
const reqInit = {
method: method,
headers: {
Authorization: "MediaBrowser Token=" + ApiClient.accessToken(),
},
body: body,
};
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
if (method === "POST") {
reqInit.headers["Content-Type"] = "application/json";
}
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
return await fetch(url, reqInit);
2022-05-31 16:12:11 -05:00
}
2024-10-11 18:01:16 +02:00
// key pressed
function keyDown(e) {
let episodeDelta = 0;
let offsetDelta = 0;
switch (e.key) {
case "ArrowDown":
if (timestampError.value != "") {
// if the control key is pressed, shift LHS by 10s. Otherwise, shift by 1.
offsetDelta = e.ctrlKey ? 10 / 0.1238 : 1;
}
break;
case "ArrowUp":
if (timestampError.value != "") {
offsetDelta = e.ctrlKey ? -10 / 0.1238 : -1;
}
break;
case "ArrowRight":
episodeDelta = 2;
break;
case "ArrowLeft":
episodeDelta = -2;
break;
default:
return;
}
if (offsetDelta != 0) {
txtOffset.value = Number(txtOffset.value) + Math.floor(offsetDelta);
}
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
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;
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
// 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();
2022-05-31 16:12:11 -05:00
}
2024-10-11 18:01:16 +02:00
renderTroubleshooter();
e.preventDefault();
2022-05-31 16:12:11 -05:00
}
2024-10-11 18:01:16 +02:00
// check that the user is still on the configuration page
function checkWindowHash() {
const h = location.hash;
if (h === "#!/configurationpage?name=Intro%20Skipper" || h.includes("#!/dialog")) {
return;
}
console.debug("navigated away from intro skipper configuration page");
document.removeEventListener("keydown", keyDown);
clearInterval(windowHashInterval);
}
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
// converts seconds to a readable timestamp (i.e. 127 becomes "02:07").
function secondsToString(seconds) {
return new Date(seconds * 1000).toISOString().slice(14, 19);
2022-06-02 01:06:15 -05:00
}
2024-10-11 18:01:16 +02: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?";
const eraseCacheChecked = document.getElementById("eraseModeCacheCheckbox").checked;
Dashboard.confirm(body, title, (result) => {
2022-11-29 02:31:24 -06:00
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");
});
2024-10-11 18:01:16 +02:00
}
2022-11-29 02:31:24 -06:00
2024-10-11 18:01:16 +02:00
document.querySelector("#TemplateConfigPage").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-09-29 20:35:46 +02:00
if (config["SkipButtonWarning"]) {
2024-10-27 04:39:59 -04:00
document.getElementById("warningMessage").style.display = "unset";
2024-09-29 20:35:46 +02:00
} else {
2024-10-11 18:01:16 +02:00
document.getElementById("warningMessage").style.display = "none";
2024-09-29 20:35:46 +02:00
}
2024-09-14 18:52:39 +02:00
populateLibraries();
2024-09-17 08:41:56 +02:00
selectAllLibrariesChanged();
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
2024-10-11 18:01:16 +02:00
document.querySelector("#FingerprintConfigForm").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;
}
2024-10-11 18:01:16 +02: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
2024-10-11 18:01:16 +02:00
visualizer.addEventListener("toggle", visualizerToggled);
support.addEventListener("toggle", supportToggled);
storage.addEventListener("toggle", storageToggled);
txtOffset.addEventListener("change", renderTroubleshooter);
selectShow.addEventListener("change", showChanged);
selectSeason.addEventListener("change", seasonChanged);
selectEpisode1.addEventListener("change", episodeChanged);
selectEpisode2.addEventListener("change", episodeChanged);
btnEraseIntroTimestamps.addEventListener("click", (e) => {
2024-10-16 16:05:59 +02:00
eraseTimestamps("Introduction");
2024-10-11 18:01:16 +02:00
e.preventDefault();
});
btnEraseCreditTimestamps.addEventListener("click", (e) => {
2024-10-16 16:05:59 +02:00
eraseTimestamps("Credits");
2024-10-11 18:01:16 +02:00
e.preventDefault();
});
btnSeasonEraseTimestamps.addEventListener("click", () => {
Dashboard.confirm("Are you sure you want to erase all timestamps for this season?", "Confirm timestamp erasure", (result) => {
2022-07-17 02:13:02 -05:00
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;
2024-10-11 18:01:16 +02:00
});
});
2024-10-18 14:15:09 +02:00
btnMovieEraseTimestamps.addEventListener("click", () => {
Dashboard.confirm("Are you sure you want to erase all timestamps for this movie?", "Confirm timestamp erasure", (result) => {
if (!result) {
return;
}
const show = selectShow.value;
const eraseCacheChecked = document.getElementById("eraseMovieCacheCheckbox").checked;
const url = "Intros/Show/" + encodeURIComponent(show);
fetchWithAuth(url + "?eraseCache=" + eraseCacheChecked, "DELETE", null);
Dashboard.alert("Erased timestamps for " + show);
document.getElementById("eraseMovieCacheCheckbox").checked = false;
});
});
2024-11-02 18:17:22 +01:00
saveAnalyzerActionsButton.addEventListener("click", () => {
2024-10-11 18:01:16 +02:00
Dashboard.showLoadingMsg();
2024-11-02 18:17:22 +01:00
var url = "Intros/AnalyzerActions/UpdateSeason";
const actions = {
id: selectSeason.value,
analyzerActions: {
Introduction: actionIntro.value,
Credits: actionCredits.value,
},
2024-10-11 18:01:16 +02:00
};
2024-11-02 18:17:22 +01:00
fetchWithAuth(url, "POST", JSON.stringify(actions));
2024-10-11 18:01:16 +02:00
2024-11-02 18:17:22 +01:00
Dashboard.alert("Analyzer actions updated for " + selectSeason.value + " of " + selectShow.value);
2024-10-11 18:01:16 +02:00
Dashboard.hideLoadingMsg();
});
btnUpdateTimestamps.addEventListener("click", () => {
const getEditValue = (id) => parseFloat(document.getElementById(id).value) || 0;
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
const newLhs = {
2024-10-16 16:05:59 +02:00
Introduction: {
2024-10-11 18:01:16 +02:00
Start: getEditValue("editLeftIntroEpisodeStartEdit"),
End: getEditValue("editLeftIntroEpisodeEndEdit"),
},
2024-10-16 16:05:59 +02:00
Credits: {
2024-10-11 18:01:16 +02:00
Start: getEditValue("editLeftCreditEpisodeStartEdit"),
End: getEditValue("editLeftCreditEpisodeEndEdit"),
},
};
const rhsId = selectEpisode2.options[selectEpisode2.selectedIndex].value;
const newRhs = {
2024-10-16 16:05:59 +02:00
Introduction: {
2024-10-11 18:01:16 +02:00
Start: getEditValue("editRightIntroEpisodeStartEdit"),
End: getEditValue("editRightIntroEpisodeEndEdit"),
},
2024-10-16 16:05:59 +02:00
Credits: {
2024-10-11 18:01:16 +02:00
Start: getEditValue("editRightCreditEpisodeStartEdit"),
End: getEditValue("editRightCreditEpisodeEndEdit"),
},
};
fetchWithAuth("Episode/" + lhsId + "/Timestamps", "POST", JSON.stringify(newLhs));
fetchWithAuth("Episode/" + rhsId + "/Timestamps", "POST", JSON.stringify(newRhs));
Dashboard.alert("New introduction timestamps saved");
});
document.addEventListener("keydown", keyDown);
windowHashInterval = setInterval(checkWindowHash, 2500);
canvas.addEventListener("mousemove", (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const y = e.clientY - rect.top;
const shift = Number(txtOffset.value);
let lTime, rTime, diffPos;
if (shift < 0 ) {
lTime = y * 0.1238;
rTime = (y + shift) * 0.1238;
diffPos = y + shift;
} else {
lTime = (y - shift) * 0.1238;
rTime = y * 0.1238;
diffPos = y - shift;
2022-07-17 02:13:02 -05:00
}
2024-10-11 18:01:16 +02:00
const diff = fprDiffs[Math.floor(diffPos)];
if (!diff) {
timeContainer.style.display = "none";
return;
} else {
timeContainer.style.display = "unset";
2024-07-27 21:11:01 +00:00
}
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
const times = document.querySelector("span#timestamps");
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
// LHS timestamp, RHS timestamp, percent similarity
times.textContent = secondsToString(lTime) + ", " + secondsToString(rTime) + ", " + Math.round(diff) + "%";
timeContainer.style.position = "relative";
timeContainer.style.left = "25px";
timeContainer.style.top = (-1 * rect.height + y).toString() + "px";
});
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");
2022-05-31 16:12:11 -05:00
2024-10-11 18:01:16 +02:00
// Set the value of the time input
return formattedTime;
}
2022-05-30 02:23:36 -05:00
2024-10-11 18:01:16 +02:00
function getTimeInSeconds(time) {
let [hours, minutes, seconds] = time.split(":").map(Number);
return hours * 3600 + minutes * 60 + seconds;
}
< / script >
< / div >
< / body >
2020-07-04 23:33:19 +09:00
< / html >