duck
duck

Reputation: 62

How to make subtitles change automatically on youtube?⁣

//represents clicked buttons (to turn on and change subtitle)
const settinsgBtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls > button.ytp-button.ytp-settings-button.ytp-hd-quality-badge");
const translateBtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls > button.ytp-subtitles-button.ytp-button");
const langueContentBtn = document.querySelectorAll(".ytp-menuitem-content")[2];
const autoTranslateBtn = document.querySelectorAll(".ytp-menuitem-label")[2];
const myLangueBtn = document.querySelectorAll('.ytp-menuitem-label')[80]

//It checks every second whether the buttons are on the screen and clicks the button if there is.
//Each button makes another button appear on the screen.
const checkElements = setInterval(clicker, 1000);

let count = 1;
function clicker() {
    if (count === 1 && translateBtn) {
      translateBtn.click(); count++;
    } 
    else if (count === 2 && settinsgBtn) {
      settinsgBtn.click(); count++;
    } 
    else if (count === 3 && langueContentBtn) {
      langueContentBtn.click(); count++;
    } 
    else if (count === 4 && autoTranslateBtn) {
      autoTranslateBtn.click(); count++;
    } 
    else if (count === 5 && myLangueBtn) {
      myLangueBtn.click(); count = 0;
      clearInterval(checkElements);
    }
}

clicker();

I want the youtube video to press the subtitle button and translate it into my language from the settings, but the variables are not defined immediately. If the button to be clicked is not visible at first, the variable returns undefined value. It works when I redefine the variable after the button appears. I tried without defining the variable but it again doesn't work after the 2nd click.⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣ And MutationObserver method is very complex, maybe you have a method.

Upvotes: 3

Views: 779

Answers (2)

Vadim
Vadim

Reputation: 3439

You can resolve this by using MutationObserver, that can notify about changes of the DOM:

function changeSubtitleLanguage(language) {
  let steps = 0;
  const subtitleBtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls > button.ytp-button.ytp-subtitles-button");
  const settingBtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls > button.ytp-button.ytp-settings-button");
  // helper function:
  // select by css selector,
  // or by selector & text
  function selectItem(node, selector, text) {
    if (text) {
      return Array.from(node.querySelectorAll(selector))
       .find(el => el.innerText.includes(text));
    } else {
      return node.querySelector(selector);
    }
  }
  const observer = new MutationObserver((records) => {
    const subtitleChosen = selectItem(document, '.ytp-subtitles-button.ytp-button[aria-pressed="true"]');
    // check if subtitle chosen
    if (subtitleChosen && steps === 0) {
      steps += 1;
      settingBtn.click();
    }
    const subtitleMenuItem = selectItem(document, '.ytp-menuitem-label span', 'Subtitles/CC');
    // check if subtitle item is
    // available in menu panel
    if (subtitleMenuItem && steps === 1) {
      steps += 1;
      subtitleMenuItem.click();
    }
    const autoTranslateMenuItem = selectItem(document, '.ytp-menuitem-label', 'Auto-translate');
    // check if autotranslate item is
    // available in menu panel
    if (autoTranslateMenuItem && steps === 2) {
      steps += 1;
      autoTranslateMenuItem.click();
    }
    const languageMenuItem = selectItem(document, '.ytp-menuitem-label', language);
    // check if language item is
    // available in menu panel
    if (languageMenuItem && steps === 3) {
      // stop observer,
      // when accomplished
      observer.disconnect();
      languageMenuItem.click();
    }
  })
  observer.observe(
    // observe parent of .ytp-settings-menu
    selectItem(document, '.html5-video-player'), 
    {
      childList: true, 
      subtree: true
    }
  );
  if (subtitleBtn && settingBtn &&
      !selectItem(document, '.ytp-subtitles-button.ytp-button[aria-pressed="true"]')
  ) {
    // if necessary, the 'subtitleBtn'
    // can also be put inside 
    // an observer callback
    subtitleBtn.click();
  }
}

changeSubtitleLanguage('Bulgarian');

Upvotes: 1

Gabriele Petrioli
Gabriele Petrioli

Reputation: 195992

If the elements are not initially loaded, and they get added after each action, you should re-query for the elements.

Here is a solution where you query the document each time before doing the checks.

You also do not need to manually call the clicker at the end, since you have fired an interval already.

//represents clicked buttons (to turn on and change subtitle)
let settinsgBtn;
let translateBtn;
let langueContentBtn;
let autoTranslateBtn;
let myLangueBtn;

function getElements() {
  settinsgBtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls > button.ytp-button.ytp-settings-button.ytp-hd-quality-badge");
  translateBtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls > button.ytp-subtitles-button.ytp-button");
  langueContentBtn = document.querySelectorAll(".ytp-menuitem-content")[2];
  autoTranslateBtn = document.querySelectorAll(".ytp-menuitem-label")[2];
  myLangueBtn = document.querySelectorAll('.ytp-menuitem-label')[80]
}

//It checks every second whether the buttons are on the screen and clicks the button if there is.
//Each button makes another button appear on the screen.
const checkElements = setInterval(clicker, 1000);

let count = 1;

function clicker() {
  getElements();
  if (count === 1 && translateBtn) {
    translateBtn.click();
    count++;
  } else if (count === 2 && settinsgBtn) {
    settinsgBtn.click();
    count++;
  } else if (count === 3 && langueContentBtn) {
    langueContentBtn.click();
    count++;
  } else if (count === 4 && autoTranslateBtn) {
    autoTranslateBtn.click();
    count++;
  } else if (count === 5 && myLangueBtn) {
    myLangueBtn.click();
    count = 0;
    clearInterval(checkElements);
  }
}


A more flexible approach that work with the actual labels of the buttons would be something like

function setSubtitlesToLanguage(languageText = 'English', fallbackToAutotranslate = false) {
  const settingsButton = document.querySelector('button.ytp-settings-button');
  const subtitlesButton = document.querySelector('button.ytp-subtitles-button');
  if (!settingsButton || !subtitlesButton) {
    setTimeout(setSubtitlesToLanguage, 500, languageText, fallbackToAutogenerated);
    return;
  }
  // enable subtitles
  if (subtitlesButton.getAttribute('aria-pressed') === 'false') {
    subtitlesButton.click();
  }
  // open settings
  settingsButton.click();

  const settingOptions = [...document.querySelectorAll('.ytp-settings-menu .ytp-panel .ytp-menuitem')];
  const subtitlesOption = settingOptions.find(node => node.textContent.includes('Subtitles/CC'));
  // open subtitles menu
  subtitlesOption.click();
  const subtitles = [...document.querySelectorAll('.ytp-settings-menu .ytp-panel .ytp-menuitem')];
  const targetSubtitle = subtitles.find(node => node.textContent.includes(languageText));
  if (targetSubtitle) {
    targetSubtitle.click();
    return;
  }
  if (fallbackToAutotranslate) {
    const autoTranslate = subtitles.find(node => node.textContent.includes('Auto-translate'));
    if (autoTranslate) {
      autoTranslate.click();
      setAutoTranslateLanguage(languageText);
    }
  }
}

function setAutoTranslateLanguage(languageText) {
  const autoSubstitles = [...document.querySelectorAll('.ytp-settings-menu .ytp-panel .ytp-menuitem')];
  if (!autoSubstitles.length) {
    setTimeout(setAutoTranslateLanguage, 500, languageText);
  }
  const targetAutoSubtitle = autoSubstitles.find(node => node.textContent.includes(languageText));
  if (targetAutoSubtitle) {
    console.log('starting auto-translate');
    targetAutoSubtitle.click();
  }
}

// usage
setSubtitlesToLanguage('Greek', true);

Upvotes: 1

Related Questions