From 94ece08a1e04d9ba1f62000b04c7b7ca707c6ab6 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Fri, 11 Sep 2020 21:21:56 +0200 Subject: hotkeys.js: add 'c' for transcript --- youtube/static/js/common.js | 23 +++++++++++++++++++++++ youtube/static/js/hotkeys.js | 16 +++++++++++++--- youtube/templates/watch.html | 2 ++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 youtube/static/js/common.js diff --git a/youtube/static/js/common.js b/youtube/static/js/common.js new file mode 100644 index 0000000..f2e62fe --- /dev/null +++ b/youtube/static/js/common.js @@ -0,0 +1,23 @@ +Q = document.querySelector.bind(document); +function text(msg) { return document.createTextNode(msg); } +function clearNode(node) { while (node.firstChild) node.removeChild(node.firstChild); } +function toMS(s) { + var s = Math.floor(s); + var m = Math.floor(s/60); var s = s % 60; + return `0${m}:`.slice(-3) + `0${s}`.slice(-2); +} + + +var cur_tt_idx = 0; +function getActiveTranscriptTrackIdx() { + let tts = Q("video").textTracks; + if (!tts.length) return; + for (let i=0; i < tts.length; i++) { + if (tts[i].mode == "showing") { + cur_tt_idx = i; + return cur_tt_idx; + } + } + return cur_tt_idx; +} +function getActiveTranscriptTrack() { return Q("video").textTracks[getActiveTranscriptTrackIdx()]; } \ No newline at end of file diff --git a/youtube/static/js/hotkeys.js b/youtube/static/js/hotkeys.js index c696ac0..8b28f28 100644 --- a/youtube/static/js/hotkeys.js +++ b/youtube/static/js/hotkeys.js @@ -1,9 +1,7 @@ -Q = document.querySelector.bind(document); - function onKeyDown(e) { if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return false; - console.log(e); + // console.log(e); let v = Q("video"); let c = e.key.toLowerCase(); if (c == "k") { @@ -25,6 +23,18 @@ function onKeyDown(e) { e.preventDefault(); v.currentTime = v.currentTime + 10; } + else if (c == "f") { + e.preventDefault(); + if (document.fullscreen) document.exitFullscreen(); + else v.requestFullscreen(); + } + else if (c == "c") { + e.preventDefault(); + let tt = getActiveTranscriptTrack(); + if (tt == null) return; + if (tt.mode == "showing") tt.mode = "disabled"; + else tt.mode = "showing"; + } } window.addEventListener('DOMContentLoaded', function() { diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 74b9887..b3847d5 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -608,6 +608,8 @@ Reload without invidious (for usage of new identity button). {% endif %} {% endif %} + + {% if settings.use_video_hotkeys %} {% endif %} -- cgit v1.2.3 From 05a7907be4648a7ddaf6c036ad401cac6e10da9f Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Fri, 11 Sep 2020 21:27:11 +0200 Subject: add transcript-table.js --- youtube/static/js/transcript-table.js | 112 ++++++++++++++++++++++++++++++++++ youtube/templates/watch.html | 9 +++ 2 files changed, 121 insertions(+) create mode 100644 youtube/static/js/transcript-table.js diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js new file mode 100644 index 0000000..235b1e4 --- /dev/null +++ b/youtube/static/js/transcript-table.js @@ -0,0 +1,112 @@ +var details_tt, select_tt, table_tt; + +function renderCues() { + var tt = Q("video").textTracks[select_tt.selectedIndex]; + let cuesL = [...tt.cues]; + + clearNode(table_tt); + console.log("render cues..", tt.cues.length); + + var tt_type = cuesL[0].text.startsWith(" \n"); + for (let i=0; i < cuesL.length; i++) { + let txt, startTime = tt.cues[i].startTime; + if (tt_type) { + if (i % 2) continue; + txt = tt.cues[i].text.split('\n')[1].replace(/<[\d:.]*?>(.*?)<\/c>/g, "$1"); + } else { + txt = tt.cues[i].text; + } + + let tr, td, a; + tr = document.createElement("tr"); + + td = document.createElement("td") + a = document.createElement("a"); + a.appendChild(text(toMS(startTime))); + a.href = "javascript:;"; // TODO: replace this with ?t parameter + a.addEventListener("click", (e) => { + Q("video").currentTime = startTime; + }) + td.appendChild(a); + tr.appendChild(td); + + td = document.createElement("td") + td.appendChild(text(txt)); + tr.appendChild(td); + + table_tt.appendChild(tr);; + }; + + var lastActiveRow = null; + function colorCurRow(e) { + // console.log("cuechange:", e); + var idxC = cuesL.findIndex((c) => c == tt.activeCues[0]); + var idxT = tt_type ? Math.floor(idxC / 2) : idxC; + + if (lastActiveRow) lastActiveRow.style.backgroundColor = ""; + if (idxT < 0) return; + var row = table_tt.rows[idxT]; + row.style.backgroundColor = "#0cc12e42"; + lastActiveRow = row; + } + colorCurRow(); + tt.addEventListener("cuechange", colorCurRow); +} + +function loadCues() { + let tts = Q("video").textTracks; + let tt = tts[select_tt.selectedIndex]; + for (let ttI of tts) if (ttI !== tt) ttI.mode = "disabled"; + if (tt.mode == "disabled") tt.mode = "hidden"; + + var iC = setInterval(() => { + if (tt.cues && tt.cues.length) { + renderCues(); + clearInterval(iC); + } + }, 100); +} + +window.addEventListener('DOMContentLoaded', function() { + let tts = Q("video").textTracks; + if (!tts.length) return; + + details_tt = document.createElement("details"); + details_tt.addEventListener("toggle", () => { + if (details_tt.open) loadCues(); + }); + + var s = document.createElement("summary"); + s.appendChild(text("Transcript")); + details_tt.appendChild(s); + + var divR = document.createElement("div"); + select_tt = document.createElement("select"); + for (let tt of tts) { + let option = document.createElement("option"); + option.appendChild(text(tt.label)); + select_tt.appendChild(option); + } + select_tt.addEventListener("change", loadCues); + divR.appendChild(select_tt); + + table_tt = document.createElement("table"); + table_tt.id = "transcript-table"; + table_tt.appendChild(text("loading..")); + divR.appendChild(table_tt); + + tts.addEventListener("change", (e) => { + console.log(e); + var idx = getActiveTranscriptTrackIdx(); // sadly not provided by 'e' + if (tts[idx].mode == "showing") { + select_tt.selectedIndex = idx; + loadCues(); + } + else if (details_tt.open && tts[idx].mode == "disabled") { + tts[idx].mode = "hidden"; // so we still receive 'oncuechange' + } + }) + + details_tt.appendChild(divR); + Q(".side-videos").prepend(details_tt); +}); diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index b3847d5..13e0620 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -305,6 +305,14 @@ .format-codecs{ width: 120px; } + + table#transcript-table { + border-collapse: collapse; + width: 100%; + } + table#transcript-table td, th { + border: 1px solid #dddddd; + } {% endblock style %} {% block main %} @@ -613,4 +621,5 @@ Reload without invidious (for usage of new identity button). {% if settings.use_video_hotkeys %} {% endif %} + {% endblock main %} -- cgit v1.2.3 From 8222b6205b337730b964a27744699edfaed46b29 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Sat, 12 Sep 2020 10:17:56 +0200 Subject: hotkeys.js: ignore if e.ctrlKey --- youtube/static/js/hotkeys.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/youtube/static/js/hotkeys.js b/youtube/static/js/hotkeys.js index 8b28f28..b295db9 100644 --- a/youtube/static/js/hotkeys.js +++ b/youtube/static/js/hotkeys.js @@ -4,7 +4,8 @@ function onKeyDown(e) { // console.log(e); let v = Q("video"); let c = e.key.toLowerCase(); - if (c == "k") { + if (e.ctrlKey) return; + else if (c == "k") { v.paused ? v.play() : v.pause(); } else if (c == "arrowleft") { -- cgit v1.2.3 From 081058c07e10a4123efcce813918873887eb78c5 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Sat, 12 Sep 2020 10:20:43 +0200 Subject: default to last textTrack --- youtube/static/js/common.js | 11 ++++++++++- youtube/static/js/transcript-table.js | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/youtube/static/js/common.js b/youtube/static/js/common.js index f2e62fe..4c7ee51 100644 --- a/youtube/static/js/common.js +++ b/youtube/static/js/common.js @@ -20,4 +20,13 @@ function getActiveTranscriptTrackIdx() { } return cur_tt_idx; } -function getActiveTranscriptTrack() { return Q("video").textTracks[getActiveTranscriptTrackIdx()]; } \ No newline at end of file +function getActiveTranscriptTrack() { return Q("video").textTracks[getActiveTranscriptTrackIdx()]; } + +function getDefaultTranscriptTrackIdx() { + let tts = Q("video").textTracks; + return tts.length - 1; +} + +window.addEventListener('DOMContentLoaded', function() { + cur_tt_idx = getDefaultTranscriptTrackIdx(); +}); diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js index 235b1e4..6b57911 100644 --- a/youtube/static/js/transcript-table.js +++ b/youtube/static/js/transcript-table.js @@ -87,6 +87,7 @@ window.addEventListener('DOMContentLoaded', function() { option.appendChild(text(tt.label)); select_tt.appendChild(option); } + select_tt.selectedIndex = getDefaultTranscriptTrackIdx(); select_tt.addEventListener("change", loadCues); divR.appendChild(select_tt); @@ -96,7 +97,7 @@ window.addEventListener('DOMContentLoaded', function() { divR.appendChild(table_tt); tts.addEventListener("change", (e) => { - console.log(e); + // console.log(e); var idx = getActiveTranscriptTrackIdx(); // sadly not provided by 'e' if (tts[idx].mode == "showing") { select_tt.selectedIndex = idx; -- cgit v1.2.3 From 537a8e8ab585c003366d77c08380739243d39a42 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Sat, 12 Sep 2020 10:23:31 +0200 Subject: transcript-table.js: sync active transcript with select_tt --- youtube/static/js/transcript-table.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js index 6b57911..ac81444 100644 --- a/youtube/static/js/transcript-table.js +++ b/youtube/static/js/transcript-table.js @@ -56,8 +56,12 @@ function renderCues() { function loadCues() { let tts = Q("video").textTracks; let tt = tts[select_tt.selectedIndex]; - for (let ttI of tts) if (ttI !== tt) ttI.mode = "disabled"; - if (tt.mode == "disabled") tt.mode = "hidden"; + let dst_mode = "hidden"; + for (let ttI of tts) { + if (ttI.mode === "showing") dst_mode = "showing"; + if (ttI !== tt) ttI.mode = "disabled"; + } + if (tt.mode == "disabled") tt.mode = dst_mode; var iC = setInterval(() => { if (tt.cues && tt.cues.length) { -- cgit v1.2.3 From 57978485ee80e13c6ac1a9f6377934be32c4c67a Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Sat, 12 Sep 2020 10:51:01 +0200 Subject: let jinja create the transcript element --- youtube/static/js/transcript-table.js | 20 +++----------------- youtube/templates/watch.html | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js index ac81444..fac33da 100644 --- a/youtube/static/js/transcript-table.js +++ b/youtube/static/js/transcript-table.js @@ -75,30 +75,17 @@ window.addEventListener('DOMContentLoaded', function() { let tts = Q("video").textTracks; if (!tts.length) return; - details_tt = document.createElement("details"); + details_tt = Q("details#transcript-box"); details_tt.addEventListener("toggle", () => { if (details_tt.open) loadCues(); }); - var s = document.createElement("summary"); - s.appendChild(text("Transcript")); - details_tt.appendChild(s); - - var divR = document.createElement("div"); - select_tt = document.createElement("select"); - for (let tt of tts) { - let option = document.createElement("option"); - option.appendChild(text(tt.label)); - select_tt.appendChild(option); - } + select_tt = Q("select#select-tt"); select_tt.selectedIndex = getDefaultTranscriptTrackIdx(); select_tt.addEventListener("change", loadCues); - divR.appendChild(select_tt); - table_tt = document.createElement("table"); - table_tt.id = "transcript-table"; + table_tt = Q("table#transcript-table"); table_tt.appendChild(text("loading..")); - divR.appendChild(table_tt); tts.addEventListener("change", (e) => { // console.log(e); @@ -112,6 +99,5 @@ window.addEventListener('DOMContentLoaded', function() { } }) - details_tt.appendChild(divR); Q(".side-videos").prepend(details_tt); }); diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 13e0620..2bfed73 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -590,6 +590,20 @@ Reload without invidious (for usage of new identity button). {% endif %} + {% if subtitle_sources %} + + Transcript + + + {% for source in subtitle_sources %} + {{ source['label'] }} + {% endfor %} + + + + + {% endif %} + {% if settings.related_videos_mode != 0 %} Related Videos -- cgit v1.2.3 From 0be0a59a2a3eef8da75f2bae57df72526598b2bf Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Sat, 12 Sep 2020 13:22:46 +0200 Subject: transcript-table: show transcript as contiguous text by default --- youtube/static/js/transcript-table.js | 77 +++++++++++++++++++++++------------ youtube/templates/watch.html | 1 + 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js index fac33da..acc2703 100644 --- a/youtube/static/js/transcript-table.js +++ b/youtube/static/js/transcript-table.js @@ -3,39 +3,64 @@ var details_tt, select_tt, table_tt; function renderCues() { var tt = Q("video").textTracks[select_tt.selectedIndex]; let cuesL = [...tt.cues]; - - clearNode(table_tt); - console.log("render cues..", tt.cues.length); - var tt_type = cuesL[0].text.startsWith(" \n"); - for (let i=0; i < cuesL.length; i++) { - let txt, startTime = tt.cues[i].startTime; - if (tt_type) { - if (i % 2) continue; - txt = tt.cues[i].text.split('\n')[1].replace(/<[\d:.]*?>(.*?)<\/c>/g, "$1"); - } else { - txt = tt.cues[i].text; + let rows; + + function forEachCue(cb) { + for (let i=0; i < cuesL.length; i++) { + let txt, startTime = tt.cues[i].startTime; + if (tt_type) { + if (i % 2) continue; + txt = tt.cues[i].text.split('\n')[1].replace(/<[\d:.]*?>(.*?)<\/c>/g, "$1"); + } else { + txt = tt.cues[i].text; + } + cb(startTime, txt); } + } - let tr, td, a; - tr = document.createElement("tr"); - - td = document.createElement("td") + function createA(startTime, txt, title=null) { a = document.createElement("a"); - a.appendChild(text(toMS(startTime))); + a.appendChild(text(txt)); a.href = "javascript:;"; // TODO: replace this with ?t parameter + if (title) a.title = title; a.addEventListener("click", (e) => { Q("video").currentTime = startTime; }) - td.appendChild(a); - tr.appendChild(td); - - td = document.createElement("td") - td.appendChild(text(txt)); - tr.appendChild(td); + return a; + } - table_tt.appendChild(tr);; - }; + clearNode(table_tt); + console.log("render cues..", tt.cues.length); + if (Q("input#transcript-use-table").checked) { + forEachCue((startTime, txt) => { + let tr, td, a; + tr = document.createElement("tr"); + + td = document.createElement("td") + td.appendChild(createA(startTime, toMS(startTime))); + tr.appendChild(td); + + td = document.createElement("td") + td.appendChild(text(txt)); + tr.appendChild(td); + + table_tt.appendChild(tr); + }); + rows = table_tt.rows; + } + else { + forEachCue((startTime, txt) => { + span = document.createElement("span"); + var idx = txt.indexOf(" "); + var [firstWord, rest] = [txt.slice(0, idx), txt.slice(idx)]; + + span.appendChild(createA(startTime, firstWord, toMS(startTime))); + if (rest) span.appendChild(text(rest + " ")); + table_tt.appendChild(span); + }); + rows = table_tt.childNodes; + } var lastActiveRow = null; function colorCurRow(e) { @@ -45,7 +70,7 @@ function renderCues() { if (lastActiveRow) lastActiveRow.style.backgroundColor = ""; if (idxT < 0) return; - var row = table_tt.rows[idxT]; + var row = rows[idxT]; row.style.backgroundColor = "#0cc12e42"; lastActiveRow = row; } @@ -99,5 +124,7 @@ window.addEventListener('DOMContentLoaded', function() { } }) + Q("input#transcript-use-table").addEventListener("change", renderCues); + Q(".side-videos").prepend(details_tt); }); diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 2bfed73..0e12d21 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -599,6 +599,7 @@ Reload without invidious (for usage of new identity button). {{ source['label'] }} {% endfor %} + -- cgit v1.2.3 From b3de26606dcdb374a98a494136d2c3f210fff838 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Tue, 15 Sep 2020 20:32:12 +0200 Subject: handle firefox' VTT parsing bug from #15: "[..] Firefox's VTT parsing [ignores] newlines. So if the cue starts with a newline, that cue will have blank text (a corollary is that the first sentence uttered will fail to display in the automatic captions [..])." --- youtube/static/js/transcript-table.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js index acc2703..d0028f7 100644 --- a/youtube/static/js/transcript-table.js +++ b/youtube/static/js/transcript-table.js @@ -4,6 +4,8 @@ function renderCues() { var tt = Q("video").textTracks[select_tt.selectedIndex]; let cuesL = [...tt.cues]; var tt_type = cuesL[0].text.startsWith(" \n"); + let ff_bug = false; + if (!cuesL[0].text.length) { ff_bug = true; tt_type = true }; let rows; function forEachCue(cb) { @@ -11,7 +13,8 @@ function renderCues() { let txt, startTime = tt.cues[i].startTime; if (tt_type) { if (i % 2) continue; - txt = tt.cues[i].text.split('\n')[1].replace(/<[\d:.]*?>(.*?)<\/c>/g, "$1"); + if (ff_bug && !tt.cues[i].text.length) txt = tt.cues[i+1].text; + else txt = tt.cues[i].text.split('\n')[1].replace(/<[\d:.]*?>(.*?)<\/c>/g, "$1"); } else { txt = tt.cues[i].text; } @@ -52,7 +55,7 @@ function renderCues() { else { forEachCue((startTime, txt) => { span = document.createElement("span"); - var idx = txt.indexOf(" "); + var idx = txt.indexOf(" ", 1); var [firstWord, rest] = [txt.slice(0, idx), txt.slice(idx)]; span.appendChild(createA(startTime, firstWord, toMS(startTime))); @@ -90,8 +93,8 @@ function loadCues() { var iC = setInterval(() => { if (tt.cues && tt.cues.length) { - renderCues(); clearInterval(iC); + renderCues(); } }, 100); } -- cgit v1.2.3 From 0830468bd13b59323f244978ae3e915bb715a949 Mon Sep 17 00:00:00 2001 From: zrose584 <57181548+zrose584@users.noreply.github.com> Date: Tue, 15 Sep 2020 20:57:11 +0200 Subject: style transcript div --- youtube/static/js/transcript-table.js | 2 +- youtube/templates/watch.html | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/youtube/static/js/transcript-table.js b/youtube/static/js/transcript-table.js index d0028f7..26d2948 100644 --- a/youtube/static/js/transcript-table.js +++ b/youtube/static/js/transcript-table.js @@ -103,7 +103,7 @@ window.addEventListener('DOMContentLoaded', function() { let tts = Q("video").textTracks; if (!tts.length) return; - details_tt = Q("details#transcript-box"); + details_tt = Q("details#transcript-details"); details_tt.addEventListener("toggle", () => { if (details_tt.open) loadCues(); }); diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index 0e12d21..8264eb8 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -313,6 +313,10 @@ table#transcript-table td, th { border: 1px solid #dddddd; } + div#transcript-div { + background-color: var(--interface-color); + padding: 5px; + } {% endblock style %} {% block main %} @@ -591,9 +595,9 @@ Reload without invidious (for usage of new identity button). {% endif %} {% if subtitle_sources %} - + Transcript - + {% for source in subtitle_sources %} {{ source['label'] }} -- cgit v1.2.3