From c316d0554957fc4900e2ec94c02c82ecf979fae9 Mon Sep 17 00:00:00 2001 From: Nathan DeTar Date: Sun, 10 Apr 2022 02:20:58 -0700 Subject: [PATCH] Added sponsorblock skipping (#208) * Added sponsorblock skipping. * Basic framework for sending SB timestamps * Sponsorblock send timestamp UI improvements * Added Sponsorblock Icons * Minor UI tweaks * Revert UI changes, implement in new UI * Added notification when sponsor segment is skipped * Add formatting for notifications & SB messages * Added SB messages to JS player * Added SB skip notifcation to videos page. * Added SB messages to video page * Change SB messages. * Check channel_overwrites * Check Per Channel Settings. * Cleanup --- tubearchivist/home/templates/home/video.html | 31 ++++ tubearchivist/static/css/style.css | 19 +++ tubearchivist/static/script.js | 150 ++++++++++++++++++- 3 files changed, 199 insertions(+), 1 deletion(-) diff --git a/tubearchivist/home/templates/home/video.html b/tubearchivist/home/templates/home/video.html index 324d293..d1ff3a7 100644 --- a/tubearchivist/home/templates/home/video.html +++ b/tubearchivist/home/templates/home/video.html @@ -3,6 +3,36 @@ {% load static %} {% load humanize %}
+
+
+ {% if video.channel.channel_overwrites.integrate_sponsorblock %} + {% if video.channel.channel_overwrites.integrate_sponsorblock == True %} + {% if not video.sponsorblock %} +

This video doesn't have any sponsor segments added. To add a segment go to this video on YouTube and add a segment using the SponsorBlock extension.

+ {% endif %} + {% if video.sponsorblock %} + {% for segment in video.sponsorblock %} + {% if segment.locked != 1 %} +

This video has unlocked sponsor segments. Go to this video on YouTube and vote on the segments using the SponsorBlock extension.

+ {{ break }} + {% endif %} + {% endfor %} + {% endif %} + {% endif %} + {% elif config.downloads.integrate_sponsorblock %} + {% if not video.sponsorblock %} +

This video doesn't have any sponsor segments added. To add a segment go to this video on YouTube and add a segment using the SponsorBlock extension.

+ {% endif %} + {% if video.sponsorblock %} + {% for segment in video.sponsorblock %} + {% if segment.locked != 1 %} +

This video has unlocked sponsor segments. Go to this video on YouTube and vote on the segments using the SponsorBlock extension.

+ {{ break }} + {% endif %} + {% endfor %} + {% endif %} + {% endif %} +
{% if cast %} @@ -114,6 +144,7 @@
diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css index 0ddf32c..d92187a 100644 --- a/tubearchivist/static/css/style.css +++ b/tubearchivist/static/css/style.css @@ -62,6 +62,13 @@ h3 { color: var(--accent-font-light); } +h4 { + font-size: 0.7em; + margin-bottom: 7px; + font-family: Sen-Regular, sans-serif; + color: var(--accent-font-light); +} + p, i, li { font-family: Sen-Regular, sans-serif; margin-bottom: 10px; @@ -355,6 +362,18 @@ button:hover { height: 100vh; } +.notifications { + text-align: center; + width: 80%; + margin: auto; +} + +.sponsorblock { + text-align: center; + width: 80%; + margin: auto; +} + .video-player video, .video-main video { max-height: 80vh; diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index bc9696d..ee95b6f 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -327,9 +327,33 @@ function cancelDelete() { } // player +var sponsorBlock = []; function createPlayer(button) { var videoId = button.getAttribute('data-id'); var videoData = getVideoData(videoId); + + var sponsorBlockElements = ''; + if (videoData.config.downloads.integrate_sponsorblock && (typeof(videoData.data.channel.channel_overwrites) == "undefined" || typeof(videoData.data.channel.channel_overwrites.integrate_sponsorblock) == "undefined" || videoData.data.channel.channel_overwrites.integrate_sponsorblock == true)) { + sponsorBlock = videoData.data.sponsorblock; + if (!sponsorBlock) { + sponsorBlockElements = ` +
+

This video doesn't have any sponsor segments added. To add a segment go to this video on Youtube and add a segment using the SponsorBlock extension.

+
+ `; + } else { + for(let i in sponsorBlock) { + if(sponsorBlock[i].locked != 1) { + sponsorBlockElements = ` +
+

This video has unlocked sponsor segments. Go to this video on YouTube and vote on the segments using the SponsorBlock extension.

+
+ `; + break; + } + } + } + } var videoProgress = getVideoProgress(videoId).position; var videoName = videoData.data.title; @@ -353,7 +377,6 @@ function createPlayer(button) { var channelName = videoData.data.channel.channel_name; removePlayer(); - // document.getElementById(videoId).outerHTML = ''; // Remove watch indicator from video info // If cast integration is enabled create cast button var castButton = ''; @@ -383,6 +406,8 @@ function createPlayer(button) { const markup = `
${videoTag} +
+ ${sponsorBlockElements}
close-icon ${watchStatusIndicator} @@ -400,6 +425,53 @@ function createPlayer(button) { divPlayer.innerHTML = markup; } +// function sendSponsorBlockVote(uuid, vote) { +// var videoId = getVideoPlayerVideoId(); +// postSponsorSegmentVote(videoId, uuid, vote); +// } + +// var sponsorBlockTimestamps = []; +// function sendSponsorBlockSegment() { +// var videoId = getVideoPlayerVideoId(); +// var currentTime = getVideoPlayerCurrentTime(); +// var sponsorBlockElement = document.getElementById("sponsorblock"); +// if (sponsorBlockTimestamps[1]) { +// if (sponsorBlockTimestamps[1] > sponsorBlockTimestamps[0]) { +// postSponsorSegment(videoId, sponsorBlockTimestamps[0], sponsorBlockTimestamps[1]); +// sponsorBlockElement.innerHTML = ` +//

Timestamps sent! (Not really)

+// `; +// setTimeout(function(){ +// sponsorBlockElement.innerHTML = ` +// +// `; +// }, 3000); +// } else { +// sponsorBlockElement.innerHTML = ` +// Invalid Timestamps! +// `; +// setTimeout(function(){ +// sponsorBlockElement.innerHTML = ` +// +// `; +// }, 3000); +// } +// sponsorBlockTimestamps = []; +// } else if (sponsorBlockTimestamps[0]) { +// sponsorBlockTimestamps.push(currentTime); +// sponsorBlockElement.innerHTML = ` +//

${sponsorBlockTimestamps[0].toFixed(1)} s |

+//

${sponsorBlockTimestamps[1].toFixed(1)} s |

+// +// `; +// } else { +// sponsorBlockTimestamps.push(currentTime); +// sponsorBlockElement.innerHTML = ` +// +// `; +// } +// } + // Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)` function insertVideoTag(videoData, videoProgress) { var videoTag = createVideoTag(videoData, videoProgress); @@ -488,6 +560,32 @@ function onVideoProgress() { var videoId = getVideoPlayerVideoId(); var currentTime = getVideoPlayerCurrentTime(); var duration = getVideoPlayerDuration(); + var videoElement = getVideoPlayer(); + // var sponsorBlockElement = document.getElementById("sponsorblock"); + var notificationsElement = document.getElementById("notifications"); + if (sponsorBlock) { + for(let i in sponsorBlock) { + if(sponsorBlock[i].segment[0] <= currentTime + 0.3 && sponsorBlock[i].segment[0] >= currentTime) { + videoElement.currentTime = sponsorBlock[i].segment[1]; + notificationsElement.innerHTML += `

Skipped sponsor segment from ${formatTime(sponsorBlock[i].segment[0])} to ${formatTime(sponsorBlock[i].segment[1])}.

`; + } + // if(currentTime >= sponsorBlock[i].segment[1] && currentTime <= sponsorBlock[i].segment[1] + 0.2) { + // if(sponsorBlock[i].locked != 1) { + // sponsorBlockElement.innerHTML += ` + //
+ // + // + //
`; + // } + // } + if(currentTime > sponsorBlock[i].segment[1] + 10) { + var notificationsElementUUID = document.getElementById("notification-" + sponsorBlock[i].UUID); + if(notificationsElementUUID) { + notificationsElementUUID.outerHTML = ''; + } + } + } + } if ((currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second postVideoProgress(videoId, currentTime); if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched @@ -542,6 +640,32 @@ function formatNumbers(number) { return numberFormatted; } +// Formats times in seconds for frontend +function formatTime(time) { + var hoursUnformatted = time / 3600; + var minutesUnformatted = (time % 3600) / 60; + var secondsUnformatted = time % 60; + + var hoursFormatted = Math.trunc(hoursUnformatted); + if(minutesUnformatted < 10 && hoursFormatted > 0) { + var minutesFormatted = "0" + Math.trunc(minutesUnformatted); + } else { + var minutesFormatted = Math.trunc(minutesUnformatted); + } + if(secondsUnformatted < 10) { + var secondsFormatted = "0" + Math.trunc(secondsUnformatted); + } else { + var secondsFormatted = Math.trunc(secondsUnformatted); + } + + var timeUnformatted = ''; + if(hoursFormatted > 0) { + timeUnformatted = hoursFormatted + ":" + } + var timeFormatted = timeUnformatted.concat(minutesFormatted, ":", secondsFormatted); + return timeFormatted; +} + // Gets video data when passed video ID function getVideoData(videoId) { var apiEndpoint = "/api/video/" + videoId + "/"; @@ -599,6 +723,30 @@ function postVideoProgress(videoId, videoProgress) { } } +// Send sponsor segment when given video id and and timestamps +function postSponsorSegment(videoId, startTime, endTime) { + var apiEndpoint = "/api/video/" + videoId + "/sponsor/"; + var data = { + "segment": { + "startTime": startTime, + "endTime": endTime + } + }; + apiRequest(apiEndpoint, "POST", data); +} + +// Send sponsor segment when given video id and and timestamps +function postSponsorSegmentVote(videoId, uuid, vote) { + var apiEndpoint = "/api/video/" + videoId + "/sponsor/"; + var data = { + "vote": { + "uuid": uuid, + "yourVote": vote + } + }; + apiRequest(apiEndpoint, "POST", data); +} + // Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE") function apiRequest(apiEndpoint, method, data) { const xhttp = new XMLHttpRequest();