Last active
October 29, 2021 02:30
-
-
Save empathicqubit/5fd13e279b4470fe219c72602a4f5748 to your computer and use it in GitHub Desktop.
Subtitle cycling script - DOESN'T WORK YET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Audio Cycler | |
// @namespace http://entan.gl/ | |
// @version 0.1 | |
// @description Change the audio language every time the subtitles change. | |
// @author EmpathicQubit | |
// @match https://www.disneyplus.com/*/video/* | |
// @icon https://www.google.com/s2/favicons?domain=disneyplus.com | |
// @require https://unpkg.com/m3u8-parser@4.7.0/dist/m3u8-parser.min.js | |
// @require https://cdnjs.cloudflare.com/ajax/libs/vtt.js/0.13.0/vtt.js | |
// @license MIT | |
// @run-at document-end | |
// @grant none | |
// ==/UserScript== | |
(async() => { | |
'use strict'; | |
const sourceLanguage = 'en'; | |
const targetLanguage = 'de'; | |
// FIXME | |
const sourceDisplayName = 'English'; | |
const targetDisplayName = 'Deutsch'; | |
let sourceLanguageCues = []; | |
let targetLanguageCues = []; | |
debugger; | |
const getCuesForLanguage = async (manifestUrl, manifest, langCode) => { | |
const subGroups = manifest.mediaGroups.SUBTITLES; | |
const mainSubs = Object.values(subGroups)[0]; | |
let lang; | |
for(const l in mainSubs) { | |
lang = mainSubs[l]; | |
if(lang.language == langCode && !lang.forced) { | |
break; | |
} | |
} | |
if(!lang) { | |
throw new Error('Language does not exist: ' + langCode); | |
} | |
const earl = new URL(manifestUrl); | |
const subtitleUrl = earl.protocol + '//' + earl.host + earl.pathname + '/../' + lang.uri; | |
const mText = await fetch(subtitleUrl, { | |
credentials: 'include', | |
}).then(r => r.text()); | |
const mTextParser = new m3u8Parser.Parser(); | |
mTextParser.push(mText); | |
mTextParser.end(); | |
const segs = mTextParser.manifest.segments.filter(x => !x.discontinuity); | |
const vttParser = new WebVTT.Parser(window, WebVTT.StringDecoder()); | |
const cues = []; | |
vttParser.oncue = (cue) => { | |
cues.push(cue); | |
}; | |
vttParser.onparsingerror = function(e) {}; | |
for(const seg of segs) { | |
const segUrlBuilder = new URL(subtitleUrl); | |
const segUrl = segUrlBuilder.protocol + '//' + segUrlBuilder.host + segUrlBuilder.pathname + '/../' + seg.uri; | |
const segText = await fetch(segUrl, { | |
credentials: 'include', | |
}).then(r => r.text()); | |
vttParser.parse(segText); | |
vttParser.flush(); | |
} | |
return cues; | |
}; | |
const initializeSubtitles = async (mData) => { | |
const mText = await fetch(mData.url, { | |
credentials: 'include', | |
}).then(r => r.text()); | |
const mTextParser = new m3u8Parser.Parser(); | |
mTextParser.push(mText); | |
mTextParser.end(); | |
sourceLanguageCues = await getCuesForLanguage(mData.url, mTextParser.manifest, sourceLanguage); | |
targetLanguageCues = await getCuesForLanguage(mData.url, mTextParser.manifest, targetLanguage); | |
}; | |
let found = false; | |
// Catch M3U8 files, stolen from | |
const listensend = (open) => { | |
console.log("listensend"); | |
var newOpen = function(...args) { | |
if(!found && args.length>=2){ | |
if(args[1].indexOf(".m3u8")>0) { | |
found = true; | |
// m3u8 url | |
console.log("m3u8 found : "+args[1]); | |
const mData = { | |
url: args[1], | |
baseurl: document.location.href.substring(0,document.location.href.lastIndexOf('/')+1), | |
} | |
// We don't await this because we don't care if it breaks. | |
initializeSubtitles(mData); | |
console.log('FOUND M3U', mData); | |
} | |
} | |
switch(args.length){ | |
case 1: | |
open.call(this,args[0]); | |
break; | |
case 2: | |
open.call(this,args[0],args[1]); | |
break; | |
case 3: | |
open.call(this,args[0],args[1],args[2]); | |
break; | |
} | |
} | |
if(typeof unsafeWindow !== "undefined"){ | |
console.log("Window state : unsafe"); | |
let define = Object.defineProperty; | |
let exported = exportFunction(newOpen, window); | |
define(unsafeWindow.XMLHttpRequest.prototype, "open", {value: exported}); | |
} | |
else { | |
console.log("Window state : safe"); | |
XMLHttpRequest.prototype.open = newOpen; | |
} | |
} | |
listensend(XMLHttpRequest.prototype.open); | |
let currentLang = sourceDisplayName; | |
let lastCue = null; | |
let currentTime = -1; | |
const timeUpdated = e => { | |
if(!sourceLanguageCues.length || !targetLanguageCues.length) { | |
return; | |
} | |
currentTime = e.target.currentTime; | |
let cue; | |
for(cue of sourceLanguageCues) { | |
if(cue.startTime <= currentTime && currentTime <= cue.endTime) { | |
break; | |
} | |
cue = null; | |
} | |
if(!cue || lastCue == cue) { | |
return; | |
} | |
lastCue = cue; | |
console.log(cue); | |
if(currentLang == sourceDisplayName) { | |
targetLangSelector.click(); | |
currentLang = targetDisplayName; | |
} | |
else { | |
sourceLangSelector.click(); | |
currentLang = sourceDisplayName; | |
} | |
}; | |
let videoPlayer = null; | |
let langSelectors = null; | |
let targetLangSelector = null; | |
let sourceLangSelector = null; | |
const ready = new MutationObserver(e => { | |
videoPlayer = document.querySelector('.btm-media-client-element'); | |
langSelectors = document.querySelectorAll('#audioTrackPicker label'); | |
if(!videoPlayer || !langSelectors.length) { | |
return; | |
} | |
for(const lang of langSelectors) { | |
const text = lang.textContent; | |
if(text.includes(targetDisplayName)) { | |
targetLangSelector = lang; | |
} | |
else if(text.includes(sourceDisplayName)) { | |
sourceLangSelector = lang; | |
} | |
} | |
ready.disconnect(); | |
videoPlayer.addEventListener('timeupdate', timeUpdated); | |
}); | |
ready.observe(document.body, {childList: true, subtree: true }); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment