Skip to content

Instantly share code, notes, and snippets.

@h-lunah
Last active March 17, 2025 15:48
Show Gist options
  • Save h-lunah/e6dfb2a86dc4726cc54d66eb4ec73580 to your computer and use it in GitHub Desktop.
Save h-lunah/e6dfb2a86dc4726cc54d66eb4ec73580 to your computer and use it in GitHub Desktop.
YouTube Captions Fixer
// ==UserScript==
// @name YouTube Captions Fix
// @namespace http://tampermonkey.net/
// @version 2025-03-13
// @description Blank out all instances of YouTube's censorship/sound detection in auto-generated captions
// @author luna (h-lunah)
// @match https://www.youtube.com/*
// @match https://www.youtube-nocookie.com/*
// @match https://m.youtube.com/*
// @match https://music.youtube.com/*
// @icon https://cdn.discordapp.com/emojis/1236984687126384640.png?v=2
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
/* match all instances of YouTube's censorship or sound detection
* example captions given substitute "fuck" (censored) for 1 or more spaces */
const censorRe = new RegExp(/\[(.*?)\]/gm); // censorship signatures, [Music], [Applause], [Laughter], etc. for any language
const spaceRe = new RegExp(/\s{2,}/gm); // spaces leftover after signature cleanup, eg. "well fuck you man"
const punctRe = new RegExp(/\s(?=[!?.])/gm); // spaces leftover before punctuation marks preceded by signature markers
const altRe = new RegExp(/^foreign$|^thank you$|^laughs$/gm); // alternatives for [Music] and [Laughter]
const removeCaptions = () => {
// interface with any caption element, even preview ones
var captionBox = document.querySelector(".captions-text");
var segmentBox = document.querySelector(".ytd-transcript-segment-renderer");
var captions = document.getElementsByClassName("ytp-caption-segment");
var transcriptCaptions = document.getElementsByClassName("segment-text");
// match detected captions
for ( const caption of captions ) {
caption.style.whiteSpace = "normal";
// blank out detected signatures
if ( caption.innerText.search(censorRe) !== -1 ) {
console.log(`removed: ${caption.innerText.match(censorRe).join(", ")}`);
caption.innerText = caption.innerText.replaceAll(censorRe, "");
}
// blank out other signatures for sound detection
if ( caption.innerText.search(altRe) !== -1 ) {
console.log(`removed alternative signature: ${caption.innerText.match(altRe)}`);
caption.innerText = caption.innerText.replaceAll(altRe, "");
}
// clean up leftovers after signature blanking
if ( caption.innerText.search(spaceRe) !== -1 ) {
console.log("removed double whitespace");
caption.innerText = caption.innerText.replaceAll(spaceRe, " ");
}
// clean up excess whitespace after [censors] followed by punctuation (human captions)
if ( caption.innerText.search(punctRe) !== -1 ) {
console.log("removed whitespace before punctutation mark");
caption.innerText = caption.innerText.replaceAll(punctRe, "");
}
if ( caption.innerText.search(/Click for settings/gm) !== -1 ) {
caption.innerText = caption.innerText.replaceAll(/Click for settings/gm, "These captions have been cleaned up");
}
/* remove fully blanked out caption lines
* applies to caption lines during non-spoken section of videos, eg. https://www.youtube.com/watch?v=1HJRGygc1yc&t=50s
*
* "fuck fuck fucking football"
* "[Music] " */
if ( !caption.innerText ) {
caption.parentElement.remove();
console.log("removed stray blank caption");
}
/* remove fully blanked captions
* applies to music-only videos, eg. https://www.youtube.com/watch?v=OpaKHVOEDiE
*
* "[Music]" */
if ( captionBox && !captionBox.childNodes.length ) {
captionBox.parentElement.remove();
console.log("removed blank caption box");
}
}
// match caption transcripts
for ( const transcriptCaption of transcriptCaptions ) {
if ( transcriptCaption.innerText.search(censorRe) !== -1 ) {
console.log(`removed: ${transcriptCaption.innerText.match(censorRe).join(", ")}`);
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(censorRe, "");
}
// blank out other signatures for sound detection
if ( transcriptCaption.innerText.search(altRe) !== -1 ) {
console.log(`removed alternative signature: ${transcriptCaption.innerText.match(altRe)}`);
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(altRe, "");
}
// clean up leftovers after signature blanking
if ( transcriptCaption.innerText.search(spaceRe) !== -1 ) {
console.log("removed double whitespace");
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(spaceRe, " ");
}
// clean up excess whitespace after [censors] followed by punctuation (human captions)
if ( transcriptCaption.innerText.search(punctRe) !== -1 ) {
console.log("removed whitespace before punctutation mark");
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(punctRe, "");
}
/* remove fully blanked out transcript lines
* applies to sections where there was only music/unknown sounds
* "[Music]" */
if ( !transcriptCaption.innerText ) {
transcriptCaption.parentNode.remove();
console.log("removed blank transcript box");
}
}
}
// Optimized removals are done using MutationObservers. You will not see any markers flash at all.
const setupObserver = () => {
// Make sure the <body> exists
if (document.body) {
const observer = new MutationObserver(() => removeCaptions());
// Observe the <body>
observer.observe(document.body, {
childList: true,
subtree: true
});
// Run filter on what's present, there isn't anything loaded at this stage though.
removeCaptions();
} else {
// Wait for <body> to appear
setTimeout(setupObserver, 10);
}
};
// Enable caption cleaning
setupObserver();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment