Skip to content

Instantly share code, notes, and snippets.

@empathicqubit
Last active October 29, 2021 02:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save empathicqubit/5fd13e279b4470fe219c72602a4f5748 to your computer and use it in GitHub Desktop.
Save empathicqubit/5fd13e279b4470fe219c72602a4f5748 to your computer and use it in GitHub Desktop.
Subtitle cycling script - DOESN'T WORK YET
// ==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