765 lines
35 KiB
HTML
Raw Normal View History

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"
data-require="emby-input,emby-button,emby-select,emby-checkbox">
2020-07-04 23:33:19 +09:00
<div data-role="content">
<div class="content-primary">
<form id="FingerprintConfigForm">
<fieldset class="verticalSection-extrabottompadding">
<legend>Analysis</legend>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="CacheFingerprints" type="checkbox" is="emby-checkbox" />
<span>Cache fingerprints to disk</span>
</label>
<div class="fieldDescription">
If checked, will store the audio fingerprints for all subsequently scanned files to
2022-06-10 23:08:27 -05:00
disk. Caching fingerprints avoids having to re-run ffmpeg on each file, at the expense
of disk usage.
</div>
</div>
<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 degree of parallelism to use when analyzing episodes.
</div>
</div>
<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="edl">
2022-07-08 01:02:50 -05:00
<summary>EDL file generation</summary>
2022-07-08 01:02:50 -05:00
<div class="selectContainer">
2022-07-16 21:53:08 -05:00
<label class="selectLabel" for="EdlAction">EDL Action</label>
2022-07-08 01:02:50 -05:00
<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">
Intro (show a skip button, *experimental*)
</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.
2022-07-08 01:02:50 -05:00
</div>
</div>
2022-07-08 01:02:50 -05:00
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="RegenerateEdl" 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
your episodes with the currently discovered introduction timestamps and EDL action.
2022-07-08 01:02:50 -05:00
</div>
</div>
</details>
2022-07-29 03:34:55 -05:00
<details id="intro_reqs">
2022-06-26 22:54:47 -05:00
<summary>Modify introduction requirements</summary>
<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>
<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>
</div>
2022-06-26 22:54:47 -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>
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>
</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">
If checked, intros will be automatically skipped. Will only work if web
sockets are configured correctly.<br />
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="ShowPromptAdjustment">
Show skip prompt at
</label>
<input id="ShowPromptAdjustment" type="number" is="emby-input" min="0" />
<div class="fieldDescription">
Seconds before the introduction starts to display the skip prompt at.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="HidePromptAdjustment">
Hide skip prompt after
</label>
<input id="HidePromptAdjustment" type="number" is="emby-input" min="2" />
<div class="fieldDescription">
Seconds after the introduction starts to hide the skip prompt at.
</div>
</div>
</fieldset>
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>
<br />
<button id="btnEraseTimestamps" is="emby-button" class="raised block emby-button">
<span>Erase introduction timestamps</span>
</button>
<p>
Erasing introduction timestamps is only necessary after upgrading the plugin if specifically
requested to do so in the plugin's changelog. After the timestamps are erased, run the
Analyze episodes scheduled task to re-analyze all media on the server.
</p>
2020-07-04 23:33:19 +09:00
</div>
</form>
</div>
2022-05-30 02:23:36 -05:00
2022-07-04 02:03:10 -05:00
<details id="statistics">
2022-07-05 16:16:48 -05:00
<summary>Analysis Statistics (experimental)</summary>
2022-07-04 02:03:10 -05:00
<pre id="statisticsText" style="font-size: larger"></p>
</details>
2022-06-29 20:52:16 -05:00
<details id="visualizer">
2022-07-17 02:13:02 -05:00
<summary>Advanced</summary>
2022-07-17 02:13:02 -05:00
<h3 style="margin:0">Select episodes to manage</h3>
<select id="troubleshooterShow"></select>
<select id="troubleshooterSeason"></select>
<br />
<select id="troubleshooterEpisode1"></select>
<select id="troubleshooterEpisode2"></select>
<br />
<br />
<div id="timestampEditor" style="display:none">
<h3 style="margin:0">Introduction timestamp editor</h3>
<p style="margin:0">All times are in seconds.</p>
<p id="editLeftEpisodeTitle" style="margin-bottom:0"></p>
<input style="width:4em" type="number" min="0" id="editLeftEpisodeStart"> to
<input style="width:4em;margin-bottom:10px" type="number" min="0" id="editLeftEpisodeEnd">
<p id="editRightEpisodeTitle" style="margin-top:0;margin-bottom:0"></p>
<input style="width:4em" type="number" min="0" id="editRightEpisodeStart"> to
<input style="width:4em;margin-bottom:10px" type="number" min="0" id="editRightEpisodeEnd">
<br />
<button id="btnUpdateTimestamps" type="button">
Update timestamps
</button>
<br />
<br />
<button id="btnEraseSeasonTimestamps" type="button">
Erase all timestamps for this season
</button>
</div>
<br />
<br />
<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>
<td style="min-width: 100px; font-weight: bold">Key</td>
<td style="font-weight: bold">Function</td>
</thead>
<tbody>
<tr>
<td>Up arrow</td>
<td>
Shift the left episode up by 0.128 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.128 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 />
2022-05-30 02:23:36 -05:00
<span>Shift amount:</span>
2022-05-30 02:23:36 -05:00
<input type="number" min="-3000" max="3000" value="0" id="offset">
<br />
<span id="suggestedShifts">Suggested shifts:</span>
<br />
2022-05-30 02:23:36 -05:00
<br />
<canvas id="troubleshooter"></canvas>
<span id="timestampContainer">
<span id="timestamps"></span> <br />
<span id="intros"></span>
</span>
</details>
2020-07-04 23:33:19 +09:00
</div>
<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 = [];
// fingerprint point comparison & miminum similarity threshold (at most 6 bits out of 32 can be different)
var fprDiffs = [];
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-07-04 02:03:10 -05:00
var statistics = document.querySelector("details#statistics");
2022-07-03 01:20:33 -05:00
var btnEraseTimestamps = document.querySelector("button#btnEraseTimestamps");
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
var configurationFields = [
"MaxParallelism",
"SelectedLibraries",
"AnalysisPercent",
"AnalysisLengthLimit",
"MinimumIntroDuration",
"EdlAction",
"ShowPromptAdjustment",
"HidePromptAdjustment"
]
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");
var txtSuggested = document.querySelector("span#suggestedShifts");
2022-07-03 01:20:33 -05:00
var btnSeasonEraseTimestamps = document.querySelector("button#btnEraseSeasonTimestamps");
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
var windowHashInterval = 0;
2022-05-30 02:23:36 -05:00
2022-06-29 20:52:16 -05:00
// when the fingerprint visualizer opens, populate show names
async function visualizerToggled() {
if (!visualizer.open) {
return;
}
Dashboard.showLoadingMsg();
2022-05-30 02:23:36 -05:00
shows = await getJson("Intros/Shows");
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-07-04 02:03:10 -05:00
async function statisticsToggled() {
if (!statistics.open) {
return;
}
// Blank any old statistics
const text = document.querySelector("pre#statisticsText");
2022-07-05 16:16:48 -05:00
text.textContent = "All CPU times are displayed as seconds.\n\n";
2022-07-04 02:03:10 -05:00
Dashboard.showLoadingMsg();
// Load the statistics from the server
let stats = await getJson("Intros/Statistics");
// Select which fields to print and label them with more friendly descriptions
let fields = "TotalTaskTime,TotalCPUTime,FingerprintCPUTime,FirstPassCPUTime,SecondPassCPUTime," +
"IndexSearches,QuickScans,FullScans,TotalQueuedEpisodes";
2022-07-04 02:03:10 -05:00
let friendlyNames = {
TotalTaskTime: "Total task time",
2022-07-05 16:16:48 -05:00
TotalCPUTime: "Total CPU time",
FingerprintCPUTime: "Fingerprint CPU time",
FirstPassCPUTime: "First pass CPU time",
SecondPassCPUTime: "Second pass CPU time",
IndexSearches: "Index searches",
2022-07-04 02:03:10 -05:00
QuickScans: "Quick scans",
FullScans: "Full scans",
TotalQueuedEpisodes: "Episodes queued",
};
// Print all friendly names and data points
for (var f of fields.split(",")) {
2022-07-05 16:16:48 -05:00
let name = friendlyNames[f].padEnd(25);
let value = stats[f];
// If this statistic is a measure of CPU time, divide by 1,000 to turn milliseconds into seconds.
2022-07-08 00:57:12 -05:00
if (name.includes("time")) {
2022-07-05 16:16:48 -05:00
value = Math.round(value / 1000);
}
text.textContent += name + value + "\n";
2022-07-04 02:03:10 -05:00
}
2022-07-05 16:16:48 -05:00
// Calculate the percentage of time (to two decimal places) spent waiting for fingerprints
const percentWait = Math.round((stats.FingerprintCPUTime * 10_000) / stats.TotalCPUTime) / 100;
// Breakdown CPU time by analysis component
text.textContent += "\nCPU time breakdown:\n";
text.textContent += "Fingerprint generation " + percentWait + "%\n";
2022-07-04 02:03:10 -05:00
Dashboard.hideLoadingMsg();
}
2022-05-30 02:23:36 -05:00
// show changed, populate seasons
async function showChanged() {
clearSelect(selectSeason);
// 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);
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++;
}
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();
lhs = await getJson("Intros/Episode/" + selectEpisode1.value + "/Chromaprint");
rhs = await getJson("Intros/Episode/" + selectEpisode2.value + "/Chromaprint");
2022-05-30 02:23:36 -05:00
Dashboard.hideLoadingMsg();
txtOffset.value = "0";
2022-05-30 02:23:36 -05:00
refreshBounds();
renderTroubleshooter();
findExactMatches();
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
2022-07-18 01:15:33 -05:00
let leftEpisodeIntro = await getJson("Episode/" + leftEpisode.value + "/IntroTimestamps/v1");
if (!leftEpisodeIntro.hasOwnProperty("IntroStart")) {
leftEpisodeIntro = { IntroStart: 0, IntroEnd: 0 };
}
2022-07-18 01:15:33 -05:00
let rightEpisodeIntro = await getJson("Episode/" + rightEpisode.value + "/IntroTimestamps/v1");
if (!rightEpisodeIntro.hasOwnProperty("IntroStart")) {
rightEpisodeIntro = { IntroStart: 0, IntroEnd: 0 };
}
// Update the editor for the first and second episodes
document.querySelector("#timestampEditor").style.display = "unset";
document.querySelector("#editLeftEpisodeTitle").textContent = leftEpisode.text;
document.querySelector("#editLeftEpisodeStart").value = Math.round(leftEpisodeIntro.IntroStart);
document.querySelector("#editLeftEpisodeEnd").value = Math.round(leftEpisodeIntro.IntroEnd);
document.querySelector("#editRightEpisodeTitle").textContent = rightEpisode.text;
document.querySelector("#editRightEpisodeStart").value = Math.round(rightEpisodeIntro.IntroStart);
document.querySelector("#editRightEpisodeEnd").value = Math.round(rightEpisodeIntro.IntroEnd);
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) {
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) {
return await fetchWithAuth(url, "GET").then(r => { return r.json(); });
}
// 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 = {
method: method,
2022-05-30 02:23:36 -05:00
headers: {
"Authorization": "MediaBrowser Token=" + ApiClient.accessToken()
},
body: body,
2022-05-30 02:23:36 -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
// key pressed
function keyDown(e) {
let episodeDelta = 0;
let offsetDelta = 0;
switch (e.key) {
case "ArrowDown":
// if the control key is pressed, shift LHS by 10s. Otherwise, shift by 1.
offsetDelta = e.ctrlKey ? 10 / 0.128 : 1;
break;
case "ArrowUp":
offsetDelta = e.ctrlKey ? -10 / 0.128 : -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);
}
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();
}
// 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);
}
// converts seconds to a readable timestamp (i.e. 127 becomes "02:07").
function secondsToString(seconds) {
return new Date(seconds * 1000).toISOString().substr(14, 5);
}
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();
ApiClient.getPluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b").then(function (config) {
document.querySelector('#AutoSkip').checked = config.AutoSkip;
2022-06-24 00:02:08 -05:00
document.querySelector('#RegenerateEdl').checked = config.RegenerateEdlFiles;
document.querySelector('#CacheFingerprints').checked = config.CacheFingerprints;
for (const field of configurationFields) {
document.querySelector("#" + field).value = config[field];
}
2020-12-02 17:47:22 -07:00
Dashboard.hideLoadingMsg();
});
2021-01-10 00:25:51 +09:00
});
document.querySelector('#FingerprintConfigForm')
.addEventListener('submit', function (e) {
2022-05-30 02:23:36 -05:00
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b").then(function (config) {
config.AutoSkip = document.querySelector('#AutoSkip').checked;
2022-06-24 00:02:08 -05:00
config.RegenerateEdlFiles = document.querySelector('#RegenerateEdl').checked;
config.CacheFingerprints = document.querySelector('#CacheFingerprints').checked;
for (const field of configurationFields) {
config[field] = document.querySelector("#" + field).value;
}
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
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-07-04 02:03:10 -05:00
statistics.addEventListener("toggle", statisticsToggled);
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);
btnEraseTimestamps.addEventListener("click", (e) => {
Dashboard.confirm(
"Are you sure you want to erase all previously discovered introduction timestamps?",
"Confirm timestamp erasure",
(result) => {
if (!result) {
return;
}
// reset all intro timestamps on the server so a new fingerprint comparison algorithm can be tested
2022-07-18 00:38:38 -05:00
fetchWithAuth("Intros/EraseTimestamps", "POST", null);
});
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;
2022-07-03 01:20:33 -05:00
2022-07-17 02:13:02 -05:00
const url = "Intros/Show/" + encodeURIComponent(show) + "/" + encodeURIComponent(season);
2022-07-18 00:38:38 -05:00
fetchWithAuth(url, "DELETE", null);
2022-07-17 02:13:02 -05:00
Dashboard.alert("Erased timestamps for " + season + " of " + show);
}
);
2022-07-03 01:20:33 -05:00
});
btnUpdateTimestamps.addEventListener("click", () => {
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
const newLhsIntro = {
IntroStart: document.querySelector("#editLeftEpisodeStart").value,
IntroEnd: document.querySelector("#editLeftEpisodeEnd").value,
};
const rhsId = selectEpisode2.options[selectEpisode2.selectedIndex].value;
const newRhsIntro = {
IntroStart: document.querySelector("#editRightEpisodeStart").value,
IntroEnd: document.querySelector("#editRightEpisodeEnd").value,
};
fetchWithAuth("Intros/Episode/" + lhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newLhsIntro));
fetchWithAuth("Intros/Episode/" + rhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newRhsIntro));
Dashboard.alert("New introduction timestamps saved");
});
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) {
lTime = y * 0.128;
rTime = (y + shift) * 0.128;
2022-05-30 03:24:19 -05:00
diffPos = y + shift;
2022-05-30 02:23:36 -05:00
} else {
lTime = (y - shift) * 0.128;
rTime = y * 0.128;
2022-05-30 03:24:19 -05:00
diffPos = y - shift;
2022-05-30 02:23:36 -05:00
}
const diff = fprDiffs[Math.floor(diffPos)];
2022-05-30 02:23:36 -05:00
if (!diff) {
timeContainer.style.display = "none";
return;
} else {
timeContainer.style.display = "unset";
}
2022-06-01 23:53:12 -05:00
const times = document.querySelector("span#timestamps");
// LHS timestamp, RHS timestamp, percent similarity
times.textContent =
secondsToString(lTime) + ", " +
secondsToString(rTime) + ", " +
Math.round(diff) + "%";
2022-05-30 02:23:36 -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
});
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>