140 lines
3.6 KiB
JavaScript
Executable File
140 lines
3.6 KiB
JavaScript
Executable File
const video = document.getElementsByClassName("video")[0]
|
|
video.classList.add("hasCustomCaptions")
|
|
|
|
// Elements
|
|
const customStyle = document.createElement("style")
|
|
customStyle.setAttribute("type", "text/css")
|
|
document.head.appendChild(customStyle)
|
|
|
|
const captionBox = document.createElement("div")
|
|
captionBox.className = "caption-box"
|
|
video.parentNode.appendChild(captionBox)
|
|
|
|
const captionInner = document.createElement("div")
|
|
captionInner.className = "caption-inner"
|
|
captionBox.appendChild(captionInner)
|
|
|
|
// Parseing
|
|
const regexVVTSource = /::cue\(([^\. \)])+([^\)]*)\)/g
|
|
const regexCueText = /<([^\. />]+)([^>]+?)>/g
|
|
|
|
function parseVVTSource(s) {
|
|
s = s.split("\nStyle:\n")[1].split("\n##\n")[0]
|
|
for (const match of s.matchAll(regexVVTSource)) {
|
|
const wholeSelector = match[0]
|
|
const className = match[1]
|
|
const details = match[2] || null
|
|
|
|
s = s.replace(wholeSelector, `.caption-box .cue-container ${className}${details ? `[data-details="${details}"]` : ""}`)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
function parseCueText(t) {
|
|
if (!t)
|
|
return t
|
|
for (const match of t.matchAll(regexCueText)) {
|
|
const wholeElement = match[0]
|
|
const className = match[1]
|
|
const details = match[2]
|
|
|
|
t = t.replace(wholeElement, `<${className} data-details="${details}">`)
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
// Detect caption switching
|
|
let lastTrack = null
|
|
for (const track of video.textTracks) {
|
|
track.sourceElement = video.querySelector(`track[label="${track.label}"]`)
|
|
|
|
function parseCaptionStyle(raw) {
|
|
if (raw.indexOf("\nStyle:\n") > -1)
|
|
return parseVVTSource(raw)
|
|
return null
|
|
}
|
|
|
|
function applyCaptionStyle(track) {
|
|
customStyle.innerHTML = track.style
|
|
}
|
|
|
|
// Eugh
|
|
track.addEventListener("cuechange", () => {
|
|
lastTrack = track
|
|
|
|
if (track.raw == undefined) {
|
|
fetch(track.sourceElement.src)
|
|
.then(r => r.text()).then(r => {
|
|
track.style = parseCaptionStyle(r)
|
|
if (lastTrack == track && track.style)
|
|
applyCaptionStyle(track)
|
|
})
|
|
} else if (track.style)
|
|
applyCaptionStyle(track)
|
|
})
|
|
}
|
|
|
|
// Functionality
|
|
for (const track of video.textTracks)
|
|
track.addEventListener("cuechange", () => {
|
|
// Remove previous cues TODO: also do when captions turned off
|
|
captionInner.innerHTML = ""
|
|
|
|
let cues = []
|
|
for (const c of track.activeCues) {
|
|
const cueContainer = document.createElement("div")
|
|
cueContainer.className = "cue-container"
|
|
captionInner.appendChild(cueContainer)
|
|
|
|
cueContainer.innerHTML = parseCueText(c.text)
|
|
console.log(c)
|
|
|
|
// align: "center"
|
|
// line: "auto"
|
|
// lineAlign: "start"
|
|
// position: "auto" / 89
|
|
// positionAlign: "auto"
|
|
// size: 100
|
|
// snapToLines: true
|
|
//
|
|
// console.log(c.position)
|
|
// cueContainer.style.position = c.position == "auto" ? "auto" : `${c.position}%`
|
|
|
|
const asHTML = c.getCueAsHTML()
|
|
// console.log(track.sourceElement)
|
|
console.log(asHTML)
|
|
|
|
cueContainer.style.textAlign = c.align
|
|
|
|
/*
|
|
position: absolute;
|
|
writing-mode: horizontal-tb;
|
|
top: 86.2709%;
|
|
left: 78%;
|
|
width: 22%;
|
|
height: auto;
|
|
overflow-wrap: break-word;
|
|
white-space: pre-line;
|
|
font: 53.9px sans-serif;
|
|
color: rgb(255, 255, 255);
|
|
text-align: center;
|
|
unicode-bidi: plaintext;
|
|
*/
|
|
|
|
// for (const child of asHTML.children)
|
|
// cueContainer.appendChild(child.cloneNode(true))
|
|
|
|
// captionInner.innerHTML = c.getCueAsHTML()
|
|
// const cue = document.createElement("span")
|
|
// cue.className = "cue"
|
|
// cueContainer.appendChild(cue)
|
|
|
|
// console.log(`NEW CUE:\n${c.align}, ${c.line}, ${c.lineAlign}, ${c.position}, ${c.positionAlign}, ${c.region}, ${c.size}, ${c.snapToLines}, ${c.text}, ${c.vertical}`)
|
|
// console.log(c.getCueAsHTML())
|
|
}
|
|
|
|
// captionRow.innerText = "hi"
|
|
})
|