From ff82690d3c127ac37905de878263596b3186854b Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Mon, 24 Oct 2022 06:11:00 -0700 Subject: [PATCH 1/3] add keyboard shortcuts to video player (#342) * add keyboard shortcuts to video player * fix modal on the inline player --- tubearchivist/home/templates/home/video.html | 8 +- tubearchivist/static/css/style.css | 17 +++ tubearchivist/static/script.js | 145 ++++++++++++++++++- 3 files changed, 160 insertions(+), 10 deletions(-) diff --git a/tubearchivist/home/templates/home/video.html b/tubearchivist/home/templates/home/video.html index b7af89c..705f6c6 100644 --- a/tubearchivist/home/templates/home/video.html +++ b/tubearchivist/home/templates/home/video.html @@ -2,7 +2,9 @@ {% block content %} {% load static %} {% load humanize %} -
+
+
+
{% if video.sponsorblock.is_enabled %} @@ -67,7 +69,7 @@

thumbs-down: {{ video.stats.dislike_count|intcomma }}

{% endif %} {% if video.stats.average_rating %} -

Rating: +

Rating: {% for star in video.stats.average_rating %} {{ star }} {% endfor %} @@ -90,7 +92,7 @@

Playlist [{{ playlist_item.playlist_meta.current_idx|add:"1" }}]: {{ playlist_item.playlist_meta.playlist_name }}

-
+
{% if playlist_item.playlist_previous %} diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css index 7a3872e..4d73cf1 100644 --- a/tubearchivist/static/css/style.css +++ b/tubearchivist/static/css/style.css @@ -388,6 +388,7 @@ button:hover { display: grid; align-content: space-evenly; height: 100vh; + position: relative; /* needed for modal */ } .notifications { @@ -744,6 +745,22 @@ video:-webkit-full-screen { /* video page */ .video-main { margin: 1rem 0; + position: relative; /* needed for modal */ +} + +.video-modal { + position: absolute; + z-index: 1; + top: 20%; + width: 100%; + text-align: center; +} + +.video-modal-text { + background: rgba(0,0,0,.5); + color: #eeeeee; + font-size: 1.3em; + display: none; } .video-main video { diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index a1205d6..d680b06 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -1,4 +1,3 @@ - function sortChange(sortValue) { var payload = JSON.stringify({'sort_order': sortValue}); sendPost(payload); @@ -411,7 +410,7 @@ function createPlayer(button) { } else { var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); } - + var playerStats = `
views icon${videoViews}`; if (videoData.data.stats.like_count) { @@ -426,6 +425,7 @@ function createPlayer(button) { const markup = `
+
${videoTag}
${sponsorBlockElements} @@ -444,13 +444,14 @@ function createPlayer(button) { `; const divPlayer = document.getElementById("player"); divPlayer.innerHTML = markup; + recordTextTrackChanges(); } // 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); - var videoMain = document.getElementsByClassName("video-main"); - videoMain[0].innerHTML = videoTag; + var videoMain = document.querySelector(".video-main"); + videoMain.innerHTML += videoTag; } // Generates a video tag with subtitles when passed videoData and videoProgress. @@ -793,8 +794,8 @@ function setProgressBar(videoId, currentTime, duration) { } // progressBar = document.getElementById("progress-" + videoId); - - + + } // multi search form @@ -1046,7 +1047,7 @@ function createFulltext(fullText) {

${subtitle_start} - ${subtitle_end}

-

${subtitleLine}

+

${subtitleLine}

${channelName}

${videoTitle}

@@ -1173,3 +1174,133 @@ function animate(elementId, animationClass) { toAnimate.classList.remove(animationClass); } } + +// keep track of changes to the subtitles list made with the native UI +// needed so that when toggling subtitles with the shortcut we go to the last selected one, not the first one +addEventListener('DOMContentLoaded', recordTextTrackChanges); + +let lastSeenTextTrack = 0; +function recordTextTrackChanges() { + let player = getVideoPlayer(); + if (player == null) { + return; + } + player.textTracks.addEventListener('change', () => { + let active = [...player.textTracks].findIndex(x => x.mode === 'showing'); + if (active !== -1) { + lastSeenTextTrack = active; + } + }); +} + +// keyboard shortcuts for the video player +document.addEventListener('keydown', doShortcut); + +let modalHideTimeout = -1; +function showModal(html, duration) { + let player = getVideoPlayer(); + let modal = document.querySelector('.video-modal-text'); + modal.innerHTML = html; + modal.style.display = 'initial'; + clearTimeout(modalHideTimeout); + modalHideTimeout = setTimeout(() => { modal.style.display = 'none'; }, duration); +} + +let videoSpeeds = [.25, .5, .75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3]; +function doShortcut(e) { + if (!(e.target instanceof HTMLElement)) { + return; + } + let target = e.target; + let targetName = target.nodeName.toLowerCase(); + if (targetName === 'textarea' || targetName === 'input' || targetName === 'select' || target.isContentEditable) { + return; + } + if (e.altKey || e.ctrlKey || e.metaKey) { + return; + } + let player = getVideoPlayer(); + if (player == null) { + // not on the video page + return; + } + switch (e.key) { + case 'c': { + // toggle captions + let tracks = [...player.textTracks]; + if (tracks.length === 0) { + break; + } + let active = tracks.find(x => x.mode === 'showing'); + if (active != null) { + active.mode = 'disabled'; + } else { + tracks[lastSeenTextTrack].mode = 'showing'; + } + break; + } + case 'm': { + player.muted = !player.muted; + break; + } + case 'ArrowLeft': { + if (targetName === 'video') { + // hitting arrows while the video is focused will use the built-in skip + break; + } + showModal('- 5 seconds', 500); + player.currentTime -= 5; + break; + } + case 'ArrowRight': { + if (targetName === 'video') { + // hitting space while the video is focused will use the built-in skip + break; + } + showModal('+ 5 seconds', 500); + player.currentTime += 5; + break; + } + case '<': + case '>': { + // change speed + let currentSpeedIdx = videoSpeeds.findIndex(s => s >= player.playbackRate); + if (currentSpeedIdx === -1) { + // handle the case where the user manually set the speed above our max speed + currentSpeedIdx = videoSpeeds.length - 1; + } + let newSpeedIdx = e.key === '<' ? Math.max(0, currentSpeedIdx - 1) : Math.min(videoSpeeds.length - 1, currentSpeedIdx + 1); + let newSpeed = videoSpeeds[newSpeedIdx]; + player.playbackRate = newSpeed; + showModal(newSpeed + 'x', 500); + break; + } + case ' ': { + if (targetName === 'video') { + // hitting space while the video is focused will toggle it anyway + break; + } + e.preventDefault(); + if (player.paused) { + player.play(); + } else { + player.pause(); + } + break; + } + case '?': { + showModal(` + + + + + + + + + `, 3000); + break; + } + } +} + From 725f17bcd2e4887f3e0a599aeca9ddcba913c2a7 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 25 Oct 2022 11:13:14 +0700 Subject: [PATCH 2/3] fix playlist missing channel metadata --- tubearchivist/home/src/index/playlist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tubearchivist/home/src/index/playlist.py b/tubearchivist/home/src/index/playlist.py index 5bfad39..8358aa4 100644 --- a/tubearchivist/home/src/index/playlist.py +++ b/tubearchivist/home/src/index/playlist.py @@ -18,7 +18,10 @@ class YoutubePlaylist(YouTubeItem): es_path = False index_name = "ta_playlist" - yt_obs = {"extract_flat": True} + yt_obs = { + "extract_flat": True, + "allow_playlist_files": True, + } yt_base = "https://www.youtube.com/playlist?list=" def __init__(self, youtube_id): From f8dccfcd15b169946a3ff5c74cb588ce9e111e73 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 25 Oct 2022 11:22:32 +0700 Subject: [PATCH 3/3] use linuxarm64 ffmpeg builds for arm64 --- Dockerfile | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 351c052..fbde035 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,8 @@ FROM python:3.10.8-slim-bullseye AS builder ARG TARGETPLATFORM -RUN apt-get update -RUN apt-get install -y --no-install-recommends build-essential gcc libldap2-dev libsasl2-dev libssl-dev +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential gcc libldap2-dev libsasl2-dev libssl-dev # install requirements COPY ./tubearchivist/requirements.txt /requirements.txt @@ -32,19 +32,22 @@ RUN apt-get clean && apt-get -y update && apt-get -y install --no-install-recomm curl \ xz-utils && rm -rf /var/lib/apt/lists/* -# get newest patched ffmpeg and ffprobe builds for amd64 fall back to repo ffmpeg for arm64 -RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \ +# install patched ffmpeg build, default to linux64 +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ + curl -s https://api.github.com/repos/yt-dlp/FFmpeg-Builds/releases/latest \ + | grep browser_download_url \ + | grep ".*master.*linuxarm64.*tar.xz" \ + | cut -d '"' -f 4 \ + | xargs curl -L --output ffmpeg.tar.xz ; \ + else \ curl -s https://api.github.com/repos/yt-dlp/FFmpeg-Builds/releases/latest \ | grep browser_download_url \ | grep ".*master.*linux64.*tar.xz" \ | cut -d '"' -f 4 \ - | xargs curl -L --output ffmpeg.tar.xz && \ - tar -xf ffmpeg.tar.xz --strip-components=2 --no-anchored -C /usr/bin/ "ffmpeg" && \ - tar -xf ffmpeg.tar.xz --strip-components=2 --no-anchored -C /usr/bin/ "ffprobe" && \ - rm ffmpeg.tar.xz \ - ; elif [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \ - apt-get -y update && apt-get -y install --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/* \ - ; fi + | xargs curl -L --output ffmpeg.tar.xz ; \ + fi && \ + tar -xf ffmpeg.tar.xz --strip-components=2 --no-anchored -C /usr/bin/ "ffmpeg" && \ + tar -xf ffmpeg.tar.xz --strip-components=2 --no-anchored -C /usr/bin/ "ffprobe" # install debug tools for testing environment RUN if [ "$INSTALL_DEBUG" ] ; then \
Show help?
Toggle mutem
Toggle subtitles (if available)c
Increase speed>
Decrease speed<
Back 5 seconds
Forward 5 seconds