const { SubtitleStream } = MatroskaSubtitles const { SubtitleParser } = MatroskaSubtitles function subStream(stream) { // subtitle parsing with seeking support if (playerData.subtitleStream) { playerData.subtitleStream = new SubtitleStream(playerData.subtitleStream) } else { playerData.subtitleStream = new SubtitleStream() playerData.subtitleStream.once('tracks', pTracks => { bcap.removeAttribute("disabled") playerData.headers = [] pTracks.forEach(track => { if (track.type != "ass") { // overwrite webvtt header with custom one track.header = `[V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Style: Default,${Object.values(subtitle1list.options).filter(item => item.value == settings.subtitle1)[0].innerText} ` } playerData.headers[track.number] = track playerData.subtitles[track.number] = new Set() if (!playerData.selectedHeader) playerData.selectedHeader = track.number }) }) } playerData.subtitleStream.on('subtitle', (subtitle, trackNumber) => { if (playerData.headers && !playerData.parsed) { if (playerData.headers[trackNumber].type == "webvtt") convertSub(subtitle) let formatSub = "Dialogue: " + (subtitle.layer || 0) + "," + new Date(subtitle.time).toISOString().slice(12, -1).slice(0, -1) + "," + new Date(subtitle.time + subtitle.duration).toISOString().slice(12, -1).slice(0, -1) + "," + (subtitle.style || "Default") + "," + (subtitle.name || "") + "," + (subtitle.marginL || "0") + "," + (subtitle.marginR || "0") + "," + (subtitle.marginV || "0") + "," + (subtitle.effect || "") + "," + subtitle.text if (!playerData.subtitles[trackNumber].has(formatSub)) { playerData.subtitles[trackNumber].add(formatSub) if (playerData.selectedHeader == trackNumber) renderSubs.call(null, trackNumber) } } }) playerData.subtitleStream.on('file', file => { if (file.mimetype == ("application/x-truetype-font" || "application/font-woff")) playerData.fonts.push(window.URL.createObjectURL(new Blob([file.data], { type: file.mimetype }))) }) stream.pipe(playerData.subtitleStream) } let octopusTimeout function renderSubs(trackNumber) { if (!playerData.octopusInstance) { let options = { video: video, subContent: trackNumber ? playerData.headers[trackNumber].header.slice(0, -1) + Array.from(playerData.subtitles[trackNumber]).join("\n") : playerData.headers[3].header.slice(0, -1), lossyRender: true, fonts: playerData.fonts.length == 0 ? ["https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fBBc4.woff2"] : playerData.fonts, workerUrl: 'js/subtitles-octopus-worker.js', debug: true, timeOffset: 0 }; playerData.octopusInstance = new SubtitlesOctopus(options); } else { if (!octopusTimeout) { octopusTimeout = setTimeout(() => { octopusTimeout = undefined if (playerData.octopusInstance) playerData.octopusInstance.setTrack(trackNumber ? playerData.headers[trackNumber].header.slice(0, -1) + Array.from(playerData.subtitles[trackNumber]).join("\n") : playerData.headers[3].header.slice(0, -1)) }, 1000) } } } function convertSub(subtitle) { // converts vtt subtitles to ssa ones let matches = subtitle.text.match(/<[^>]+>/g); // create array of all tags if (matches) matches.forEach(match => { if (/<\//.test(match)) { // check if its a closing tag subtitle.text = subtitle.text.replace(match, match.replace("", "0}")) } else { subtitle.text = subtitle.text.replace(match, match.replace("<", "{\\").replace(">", "1}")) } }) //replace all html special tags with normal ones subtitle.text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, "\\h") } function postDownload(file) { // parse subtitles fully after a download is finished if (file.name.endsWith(".mkv") || file.name.endsWith(".webm")) { let parser = new SubtitleParser(), subtitles = [], headers = [] parser.once('tracks', pTracks => { pTracks.forEach(track => { if (track.type != "ass") { // overwrite webvtt header with custom one track.header = `[V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Style: Default,${Object.values(subtitle1list.options).filter(item => item.value == settings.subtitle1)[0].innerText} ` } headers[track.number] = track subtitles[track.number] = new Set() }) }) parser.on('subtitle', (subtitle, trackNumber) => { if (headers[trackNumber].type == "webvtt") convertSub(subtitle) subtitles[trackNumber].add("Dialogue: " + (subtitle.layer || 0) + "," + new Date(subtitle.time).toISOString().slice(12, -1).slice(0, -1) + "," + new Date(subtitle.time + subtitle.duration).toISOString().slice(12, -1).slice(0, -1) + "," + (subtitle.style || "Default") + "," + (subtitle.name || "") + "," + (subtitle.marginL || "0") + "," + (subtitle.marginR || "0") + "," + (subtitle.marginV || "0") + "," + (subtitle.effect || "") + "," + subtitle.text) }) parser.on('finish', () => { playerData.subtitles = subtitles playerData.headers = headers playerData.parsed = 1 playerData.subtitleStream = undefined renderSubs.call(null, playerData.selectedHeader) parser = undefined }); file.createReadStream().pipe(parser) } }