Skip to content

Audio Muting: Adding a Custom Site

Richard Frost edited this page Mar 18, 2024 · 4 revisions

Adding support for audio muting requires being able to identify the caption/subtitle text, as opposed to all other text on the page. Once text has been identified as a caption/subtitle, it can be checked for any words that should be filtered, and then ultimately muted. Because each site is a little different, there are different modes that can be used:

  • Element
    • Text is contained in HTML elements on the page
    • Best suited for sites with a predictable pattern of adding the next caption/subtitle text to the page
    • Requires all captions/subtitles to be included in the same mutation
  • ElementChild
    • Like Element mode, but also supports a parentSelector for identifying the caption/subtitle text
  • Text Only
    • Caption/subtitle text is contained in a plain #text element on the page
    • Requires a consistent parentSelector that contains the text element
  • Video TextTrack Cues
    • Caption/subtitle text is stored inside the HTML video element
    • Because this mode may have access to actual timing data, it is possible to adjust the sync with videoCueSync
  • Watcher:
    • Watch a specific HTML element (parent) for captions/subtitles
    • Useful when captions are not updated through a predictable way (different elements, getting replaced, etc)
    • While a video is playing, all other filtering is disabled
    • Less efficient, but sometimes the only way to filter some sites

To add support for a new site, the first step is identifying which mode is appropriate, and then include enough detail to precisely identify the subtitle text.

These instructions were tested with Google Chrome. You may need to alter these steps if you are using a different browser.

1. Identify Mode

  1. Pause a video on the desired site with the subtitles visible
  2. Open the browser's Developer Tools (F12, CTRL+SHIFT+I, or Right-click on the page and choose "Inspect").
  3. On the "Elements" tab, search for a word (CTRL+F) that is in the visible subtitle text, the less common the better.
  4. Look through results until you find the one that contains the rest of the subtitle text (Note: it may be split into multiple elements, especially if there are multiple lines)
  5. If you found the subtitle text element, proceed to Element/Text Mode
  6. If you didn't find the subtitle text in step 4, try the steps in the Video TextTrack Mode
  7. If you still haven't found a match, you can try these steps again on a different video, or a different set of words. If there are still no matches, you have likely found a site that uses a different mechanism for subtitles. Feel free to open an issue to request some help.

2. Element/Text Mode

  1. Click on the "Sources" tab in the Developer Tools. On the left panel click on "Content scripts" (You might have to click on the ">>" menu to see it), then expand "Advanced Profanity Filter" and then click on webFilter.js.

    DevTools - Content Script Sources

  2. Search (CTRL+F) for this line this.mutePage ? (inside the processAddedNode function definition)

  3. Right click on the line (left side of the pane) and select Add conditional breakpoint

    • Note: If you left click it will instantly add the breakpoint. You can then right-click it and select edit to add the condition.
  4. Use the following condition but replace 'word' with a visible word in the subtitle (keep the single quotes!):

    • e.textContent.toLowerCase().includes('word');

    DevTools - Conditional Breakpoint

  5. You will then need to put the video back a few seconds (before the subtitle in question is visible), and the let it play. When the breakpoint is hit, type e and press enter in the JavaScript console, and then right click on the element and select "Copy element" (or expand it and take a screenshot).

    Tip: If the JavaScript console isn't visible, press the ESC key once or twice. If it still doesn't appear you can use the Console tab at the top of the window.

    DevTools - Element Output

  6. If the node element is just #text, then you will need to find its closest parent element that can be targeted, and then use the Text-Only mode, something like this:

{
  "videosite.com": {
    "textParentSelector": "div.subtitles-container"
  }
}
  1. If node is not a plain text #text element, then use the available options in the Custom Audio Site Config Structure to help the filter know what text should be considered a subtitle. If you are unsure what to do, or need help, please create an issue that includes the site and the node (from step 5).

3. Video TextTrack Mode

While on the desired site, follow the first 2 steps here Identify Mode and then paste the following code in the Developer Tools console, but replace words to find on the first line with 2-3 words (make sure they are on the same line) in the currently visible subtitles.

textToFind = 'words to find';

videos = document.querySelectorAll('video');
if (videos.length) {
  videos.forEach((video) => {
    found = false;
    for (i = 0; i < video.textTracks.length; i++) {
      track = video.textTracks[i];
      for (j = 0; j < track.activeCues.length; j++) {
        activeCue = track.activeCues[j];
        if (activeCue.text.toLowerCase().includes(textToFind.toLowerCase())) {
          found = true;
          data = {};
          data[window.location.host] = {
            mode: 'cue',
            videoCueLanguage: track.language
          };
          console.log(`[APF] Found Video TextTrack match: "${activeCue.text}"`, video, track);
          console.log(`[APF] Possible config:\n${JSON.stringify(data, null, 2)}`);
          if (videos.length > 1) {
            console.log(`['APF'] Warning, ${videos.length} video elements found. You'll likely need to add a videoSelector to the rule.`);
          }
        }
      }
    }

    if (!found) {
      console.log('[APF] No match found in the Video Text Track', video);
    }
  });
} else {
  console.log('[APF] No videos found: Are you sure the there is a video on the page?');
}

You should see some output from the command, and if a match was found, you will also be presented with a possible configuration. You can take that config and put it in the Custom Audio Sites box found in the Audio tab of the extension's options page. If you see "No match found in the Video Text Track", that means that this mode probably won't work for the current video.

Adding a Custom Audio Site

You can add your custom audio sites by putting them in the Custom Audio Site box found in the Option's Audio page. They must be formatted as JSON.

You can override the built-in Audio Sites by adding your own entry for the desired site. To see all the built-in Audio Sites and their config: Open the extension's options page, click on the "Audio" tab, and then select the "View Supported Sites" button.

Here are some examples from the supported sites for reference.

Custom Audio Site Config Structure

All modes

Name Default Description
filterSubtitles true Filter/update text (Note: can causes issues on some sites)
iframe undefined true: only IFRAMES, false: no IFRAMES, undefined: all
muteMethod 0/user Override global muteMthod (0: tab, 1: video)
showSubtitles 0/user Show subtitles (0: All, 1: Filtered, 2: Unfiltered, 3: None)

Element Mode (Default)

Name Example Description Method
mode * "element" Enable the element mode
tagName * "DIV" The subtitle node element tag node.tagName
subtitleSelector ** "span > sub" Selector for the subtitle text node.querySelector()
className "myClass" Class name found on the subtitle node node.className.includes()
dataPropPresent "myData" Check for a data property to be there node.dataset.hasOwnProperty()
hasChildrenElements true Has children elements (Uncommon) node.childElementCount > 0
containsSelector "subs" Has matching children (Uncommon) node.querySelector()

* Required
** Used for Filtering

Example:

{
  "www.youtube.com": {
    "mode": "element",
    "className": "caption-window",
    "subtitleSelector": "span.ytp-caption-segment",
    "tagName": "DIV"
  }
}

Text-only Mode

Name Example Description Method
mode * "text" Enable the Text mode
parentSelector "div.subs" Check if node is a child of parent parent.contains(node)

Example:

{
  "www.sonycrackle.com": {
    "mode": "text",
    "parentSelector": "div.clpp-subtitles-container"
  }
}

Video Text Track Cues Mode

Name Example Description Method
mode * "cue" Enable video text track mode
videoCueLanguage "en" Text Track language textTrack.language
videoCueSync 1.5 [Beta] Adjust Sync +/- (in seconds) cue.startTime + videoCueSync
videoSelector "video.class" Selector used to find desired video document.querySelector('video')

* Required

Example:

{
  "www.tntdrama.com": {
    "mode": "cue",
    "videoSelector": "video.top-media-element"
  }
}

Watcher Mode

Name Example Description Method
mode * "watcher" Enable the Watcher mode
checkInterval 30 How often to check captions/subtitles
parentSelector "div.subs" Check if node is a child of parent parent.contains(node)
subtitleSelector "div.subs > p Selector for the subtitle text node.querySelector()

Example:

{
  "www.amazon.com": [
    {
      "checkInterval": 10,
      "mode": "watcher",
      "iframe": false,
      "parentSelector": "div.webPlayer div.persistentPanel",
      "showSubtitles": 0,
      "subtitleSelector": "div.webPlayer div.persistentPanel > div > div > div > p > span > span"
    }
  ]
}

Firefox

Although the Developer Tools in Firefox are different than Google Chrome, you should still be able to use it for this method. To find webFilter.js, open the Developer Tools, navigate to the "Debugger" tab. In the "Sources" tab on the left-hand side, right-click in that pane and click on "Expand all". It will be under a moz-extension://. Learn more about Debugging Content Scripts in Firefox.

Old Info

This information may still be useful, but is old

Element Mode Process Walk-through

When a change (mutation) happens on the page, the filter will:

  1. Check to see if the current element is a subtitle, and will proceed if all are true.
    • Check if the tagName is DIV
    • Check if the node has the caption-window class
    • Check if the node contains a child matching span.ytp-caption-segment
  2. If all above are true, then the filter will check the text inside the subtitleSelector if present, otherwise it will look at the text inside the node itself.
  3. If a blocked word is found, the subtitle will be filtered and the audio will be muted. If its already muted and a blocked word wasn't found, then it will be unmuted.

Once you have you have a Custom Site Config, you can put it to use by visiting the Audio tab in Advanced Profanity Filter's options page. If it doesn't work quite right, feel free to adjust it as necessary, or open an issue.

Adding to the Filter

Example: Adding Hulu Support

DevTools - Data Highlight

Subtitle Selectors:

This object is constructed of supported domains in the key (example: www.youtube.com), and a value of the child element that holds the actual subtitle text to filter.

  static readonly subtitleSelectors = {
    'www.youtube.com': 'span.ytp-caption-segment'
  }

See webAudio.ts for more examples.

Supported Node:

Check to see if the current node is a supported subtitle/caption node. This should be specific enough to not cause false-positives, but not check everything under the sun for performance reasons. In this example, we check the following:

  • The tag is a div
  • Includes a class named caption-window
  • Has at least 1 child element that is a span with a class named captions-text, which in turn has a span child with the class ytp-caption-segment.
static supportedNode(hostname: string, node: any): boolean {
  switch(hostname) {
    case 'www.youtube.com':
      return !!(node.tagName == 'DIV' && node.className.includes('caption-window') && node.querySelectorAll('span.captions-text span.ytp-caption-segment').length > 0);
  }

See webAudio.ts for more examples.