Skip to content

Instantly share code, notes, and snippets.

@celunah
Last active August 16, 2026 13:04
Show Gist options
  • Select an option

  • Save celunah/e6dfb2a86dc4726cc54d66eb4ec73580 to your computer and use it in GitHub Desktop.

Select an option

Save celunah/e6dfb2a86dc4726cc54d66eb4ec73580 to your computer and use it in GitHub Desktop.
YouTube Captions Fixer
// ==UserScript==
// @name YouTube Captions Fix
// @namespace http://tampermonkey.net/
// @version 2026-08-16
// @description Blank out all instances of YouTube's censorship/sound detection in auto-generated captions, works for all caption generations.
// @author luna (@celunah)
// @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|n|you)$|\\bheat(?:\\s+up\\s+here)?\\.?(?!\\w)",
"gmi"
); // alternatives for [Music] and [Laughter], for older and newer ASR systems, may affect some genuine captions
const shouldLog = true; // set to 'true' to log all changes made to the console
const legacyMode = false; // set to 'true' to remove puncutation, capitalization and >> markers
const log = (text) => { if (shouldLog) console.log(text); };
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("timeline-item-view-model"),
...document.getElementsByClassName("segment-text")
];
// match detected captions
for ( const caption of captions ) {
// removes leading spaces from some caption lines
caption.style.whiteSpace = "normal";
// blank out detected signatures
if ( caption.innerText.search(censorRe) !== -1 ) {
log(`removed: ${caption.innerText.match(censorRe).join(", ")}`);
caption.innerText = caption.innerText.replaceAll(censorRe, "");
}
// blank out other signatures for sound detection
if ( caption.innerText.toLowerCase().search(altRe) !== -1 ) {
log(`removed alternative signature: ${caption.innerText.match(altRe)}`);
caption.innerText = caption.innerText.toLowerCase().replaceAll(altRe, "");
}
// clean up leftovers after signature blanking
if ( caption.innerText.search(spaceRe) !== -1 ) {
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 ) {
log("removed whitespace before punctutation mark");
caption.innerText = caption.innerText.replaceAll(punctRe, "");
}
if ( caption.innerText.trim() === ">>" ) {
log("removed blank new-speaker line");
caption.innerText = caption.innerText.trim().replaceAll(">>", "");
}
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 ) {
// .remove() [fucking] sucks, use .style.background = "transparent"; instead
caption.parentElement.style.background = "transparent";
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 === 1 ) {
// .remove() [fucking] sucks, use .style.background = "transparent"; instead
captionBox.parentElement.style.background = "transparent";
log("removed blank caption box");
}
if (legacyMode === true) {
const originalText = caption.innerText;
const newText = originalText.toLowerCase().replaceAll(/(?<![\p{L}\p{N}])([^\p{L}\p{N}\s'\-])|([^\p{L}\p{N}\s'\-])(?![\p{L}\p{N}])/gu, '');
if (newText !== originalText) {
caption.innerText = newText;
log("converted caption to legacy format");
}
}
}
for ( const transcriptCaption of transcriptCaptions ) {
// censor, sound, etc. markers
if ( transcriptCaption.innerText.search(censorRe) !== -1 ) {
log(`removed: ${transcriptCaption.innerText.match(censorRe).join(", ")}`);
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(censorRe, "");
}
// "Heat.", "Heat up here.", "foreign", "thank you", etc.
if ( transcriptCaption.innerText.search(altRe) !== -1 ) {
log(`removed alternative signature: ${transcriptCaption.innerText.match(altRe)}`);
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(altRe, "");
}
// whitespace cleanup
if ( transcriptCaption.innerText.search(spaceRe) !== -1 ) {
log("removed double whitespace");
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(spaceRe, " ");
}
// punctuation to whitespace cleanup
if ( transcriptCaption.innerText.search(punctRe) !== -1 ) {
log("removed whitespace before punctutation mark");
transcriptCaption.innerText = transcriptCaption.innerText.replaceAll(punctRe, "");
}
/* empty lines
* applies to sections where there were only markers or "Heat.", etc.
* "[Music]" */
if ( !transcriptCaption.innerText ) {
transcriptCaption.parentNode.style.background = "transparent";
log("removed blank transcript box");
}
}
}
// Optimized removals are done using MutationObservers. You will not see any markers flash at all.
const setupObserver = () => {
if (document.body) {
const observer = new MutationObserver(() => removeCaptions());
observer.observe(document.body, {
childList: true,
subtree: true
});
removeCaptions();
} else {
setTimeout(setupObserver, 10);
}
};
setupObserver();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment