Skip to content

Instantly share code, notes, and snippets.

@daniilS
Last active November 15, 2020 17:04
Show Gist options
  • Save daniilS/32ddbd3557bf34385ab6539bd4154007 to your computer and use it in GitHub Desktop.
Save daniilS/32ddbd3557bf34385ab6539bd4154007 to your computer and use it in GitHub Desktop.
Skips annoying silences in lecture recordings, and suppresses noise
// ==UserScript==
// @name Lecture recording silence and noise remover
// @namespace https://gist.github.com/daniilS/
// @downloadUrl https://gist.github.com/daniilS/32ddbd3557bf34385ab6539bd4154007/raw/silenceRemover.user.js
// @version 2.0.0
// @description Puts more effort into making online lectures tolerable than the rest of the chemistry department combined
// @author daniilS
// @include *://*.panopto.*/Panopto/Pages/Viewer.aspx*
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// ==/UserScript==
(function(){
'use strict';
//global variables so that you can also change them through the console. These should really be sliders in a browser extension popup if I had done this properly
unsafeWindow.threshold = -28; //silence threshold in decibels, video will get sped up below this threshold
unsafeWindow.maxGap = 0.300; //max silence length in seconds, video will be sped up after this length of silence
unsafeWindow.normalSpeed = 1; //default normal video playback rate, set to whatever you normally watch videos at. Can also be changed the way you normally change speed in Panopto
unsafeWindow.fastSpeed = 4; //playback rate during silences, values above 4 sometimes make the audio go weird in Chrome, set it to the highest value your browser can handle
unsafeWindow.disableScript = false; //set to true to disable the script
unsafeWindow.verboseScript = false; //set to true if you want your console spammed to see if the extension is working
unsafeWindow.filterQ = 1.1; //Q-factor of the band-pass filter, applied when enabling noise supression
unsafeWindow.filterFrequency = 300; //frequency band of the band-pass filter, applied when enabling noise supression
let filter;
let waitForVideo = function(){
if(document.getElementsByClassName("video-js").length < 1){
setTimeout(waitForVideo, 100);
return;
}
//check if the browser is Firefox and warn the user if it is
//the bug in question: https://bugzilla.mozilla.org/show_bug.cgi?id=1517199
if(unsafeWindow.Panopto.Core.Browser.isFirefox){
alert("Due to a known but unfixed bug in Firefox, the silence remover script doesn't work. Please try a different browser (Chrome recommended).");
return;
}
if(document.getElementsByClassName("video-js")[0].paused){
setTimeout(waitForVideo, 100);
return;
}
let context = new AudioContext();
let videoElement = document.getElementsByClassName("video-js")[0]; //audio, or video of the lecturer's face, or the screen if there's only one video
let videoElement1 = document.getElementsByClassName("video-js")[1] || {}; //video of the screen, if separate
let video = context.createMediaElementSource(videoElement);
let analyser = context.createAnalyser(); //yes I know this should really be an AudioWorkletNode but this was less effort
analyser.fftSize = 512;
filter = context.createBiquadFilter();
filter.Q.value = 0;
filter.frequency.value = unsafeWindow.filterFrequency;
filter.type = 'bandpass';
filter.connect(analyser);
analyser.connect(context.destination);
video.connect(filter);
let getLoudness = function(){
let array = new Float32Array(analyser.fftSize);
analyser.getFloatTimeDomainData(array);
let max = 0;
for (let i = 0; i < array.length; i++){
let a = Math.abs(array[i]);
max = Math.max(max, a);
}
let dB = 10 * Math.log10(max);
return dB;
}
let lastLoudness = unsafeWindow.threshold;
let timeOffset = undefined;
let changeSpeed = function(newSpeed){
document.getElementById("playButton").click(); //pause the video in an attempt to prevent desync
videoElement.playbackRate = newSpeed;
videoElement1.playbackRate = newSpeed;
document.getElementById("playButton").click();
}
let silenceDetector = function(){
if(videoElement.paused || unsafeWindow.disableScript){
setTimeout(silenceDetector, 20);
return;
}
let loudness = getLoudness();
if(loudness < unsafeWindow.threshold){
if(lastLoudness > unsafeWindow.threshold){
lastSilence = videoElement.currentTime;
}
else{
if(videoElement.currentTime - lastSilence >= unsafeWindow.maxGap){
if(videoElement.playbackRate != unsafeWindow.fastSpeed){
changeSpeed(unsafeWindow.fastSpeed);
}
if(unsafeWindow.verboseScript){
console.log("forwarding");
}
}
}
}
else{
if(videoElement.playbackRate != unsafeWindow.normalSpeed){
changeSpeed(unsafeWindow.normalSpeed);
}
}
lastLoudness = loudness;
setTimeout(silenceDetector, 20);
}
let lastSilence = videoElement.currentTime;
silenceDetector();
}
let waitForSpeedButtons = function(){
if(document.getElementsByClassName("play-speed").length < 1){
setTimeout(waitForSpeedButtons, 100);
return;
}
for(let speedButton of document.getElementsByClassName("play-speed")){
speedButton.onclick = function(e){
unsafeWindow.normalSpeed = unsafeWindow.PanoptoViewer.PlaySpeed[e.target.id];
}
}
}
waitForVideo();
waitForSpeedButtons();
GM_registerMenuCommand("Enable noise suppression", function(){filter.Q.value = unsafeWindow.filterQ; filter.frequency.value = unsafeWindow.filterFrequency;});
GM_registerMenuCommand("Disable noise suppression", function(){filter.Q.value = 0;});
GM_registerMenuCommand("Enable silence skipper", function(){unsafeWindow.disableScript = false;});
GM_registerMenuCommand("Disable silence skipper", function(){unsafeWindow.disableScript = true;});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment