<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: wimdenherder</title>
    <description>The latest articles on DEV Community by wimdenherder (@wimdenherder).</description>
    <link>https://dev.to/wimdenherder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F764934%2Ffa6df4ed-77d8-4a55-9f99-26bb9af98d97.png</url>
      <title>DEV Community: wimdenherder</title>
      <link>https://dev.to/wimdenherder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wimdenherder"/>
    <language>en</language>
    <item>
      <title>Download YouTube subtitles with a few lines of code</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Sat, 06 Jan 2024 09:05:11 +0000</pubDate>
      <link>https://dev.to/wimdenherder/download-the-subs-with-a-few-lines-of-code-2k0</link>
      <guid>https://dev.to/wimdenherder/download-the-subs-with-a-few-lines-of-code-2k0</guid>
      <description>&lt;p&gt;Paste the following in the javascript developer console while being on a YouTube video page. It will download the subtitles to a .txt file. You can change the language simply on the line on the bottom&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getSubs(langCode = 'en') {
  let ct = JSON.parse((await (await fetch(window.location.href)).text()).split('ytInitialPlayerResponse = ')[1].split(';var')[0]).captions.playerCaptionsTracklistRenderer.captionTracks, findCaptionUrl = x =&amp;gt; ct.find(y =&amp;gt; y.vssId.indexOf(x) === 0)?.baseUrl, firstChoice = findCaptionUrl("." + langCode), url = firstChoice ? firstChoice + "&amp;amp;fmt=json3" : (findCaptionUrl(".") || findCaptionUrl("a." + langCode) || ct[0].baseUrl) + "&amp;amp;fmt=json3&amp;amp;tlang=" + langCode;
  return (await (await fetch(url)).json()).events.map(x =&amp;gt; ({...x, text: x.segs?.map(x =&amp;gt; x.utf8)?.join(" ")?.replace(/\n/g,' ')?.replace(/♪|'|"|\.{2,}|\&amp;lt;[\s\S]*?\&amp;gt;|\{[\s\S]*?\}|\[[\s\S]*?\]/g,'')?.trim() || ''}));
}
async function logSubs(langCode) {
  const subs = await getSubs(langCode);
  const text = subs.map(x =&amp;gt; x.text).join('\n');
  console.log(text);
  return text;
}
async function downloadSubs(langCode) {
  const text = await logSubs(langCode);
  const blob = new Blob([text], {type: 'text/plain'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'subs.txt';
  a.click();
  URL.revokeObjectURL(url);
}

downloadSubs('en');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieving the subtitles (getSubs function)
&lt;/h3&gt;

&lt;p&gt;If you type &lt;code&gt;await getSubs('de')&lt;/code&gt; in the console (assuming you pasted the code above), you will see an array with all subtitles in german! You can ask for any language, because the YouTube API just translates everything for you, cool he! We don't even have to translate it.  &lt;/p&gt;

&lt;p&gt;Let's break down the code into smaller parts to better understand each step:&lt;/p&gt;

&lt;p&gt;Retrieve the caption tracks object (ct):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let ct = JSON.parse((await (await fetch(window.location.href)).text()).split('ytInitialPlayerResponse = ')[1].split(';var')[0]).captions.playerCaptionsTracklistRenderer.captionTracks;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are on a YouTube page in the dev console, type &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; and big chance you'll find an object with lots of information (including subtitles, click "captions") about this video! &lt;br&gt;
I decided to retrieve this variable directly from the video page's HTML and extract the ytInitialPlayerResponse from it. Why? Because, if you watch another video, the global variable &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; does not get updated automatically. You cannot rely on it, you have to fetch the new video page and extract the object from it. &lt;/p&gt;

&lt;p&gt;Define a helper function (findCaptionUrl):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let findCaptionUrl = x =&amp;gt; ct.find(y =&amp;gt; y.vssId.indexOf(x) === 0)?.baseUrl;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helper function searches in the caption tracks and returns the base URL of the right caption track. &lt;br&gt;
The &lt;code&gt;vssId&lt;/code&gt; looks very similar to the languageCode as we will see in the following line.&lt;/p&gt;

&lt;p&gt;Build the subtitles URL (url):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let firstChoice = findCaptionUrl("." + langCode);
let url = firstChoice ? firstChoice + "&amp;amp;fmt=json3" : (findCaptionUrl(".") || findCaptionUrl("a." + langCode) || ct[0].baseUrl) + "&amp;amp;fmt=json3&amp;amp;tlang=" + langCode;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order of preference we would like to retrieve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a written subtitle in our language (our &lt;code&gt;firstChoice&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;any written subtitle (that we translate to our language)&lt;/li&gt;
&lt;li&gt;an automatic subtitle in our language&lt;/li&gt;
&lt;li&gt;any subtitle (that we translate to our language)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that the URL also includes the format (json3) and translate language (tlang) query parameters. The Youtube API does this for us :-)&lt;/p&gt;

&lt;p&gt;You can skip this detail: If we find an automatic subtitle in our language (3.), we will mute the voice-over in this case automatically, because the video is already in the preferred language. This happens by coincidence, because if you translate (tlang=) to its own language it returns blank subtitles :-)&lt;/p&gt;

&lt;p&gt;Fetch and process the subtitles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (await (await fetch(url)).json()).events.map(x =&amp;gt; ({...x, text: x.segs?.map(x =&amp;gt; x.utf8)?.join(" ")?.replace(/
/g,' ')?.replace(/♪|'|"|.{2,}|&amp;lt;[sS]*?&amp;gt;|{[sS]*?}|[[sS]*?]/g,'')?.trim() || ''}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetches the subtitles from the previously constructed URL&lt;/li&gt;
&lt;li&gt;parses them as JSON&lt;/li&gt;
&lt;li&gt;processes the events to extract the text of each subtitle. &lt;/li&gt;
&lt;li&gt;It also cleans the text by removing unnecessary characters and formatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down the code into smaller parts to better understand each step:&lt;/p&gt;

&lt;p&gt;Fetch the subtitles and convert them to JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await (await fetch(url)).json()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the line fetches the subtitles from the provided URL and converts the response into a JSON object.&lt;/p&gt;

&lt;p&gt;Process the events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.events.map(x =&amp;gt; ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the line maps over the events array of the JSON object to process each event.&lt;/p&gt;

&lt;p&gt;Create a new object for each event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;({...x, text: ...})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each event, a new object is created that includes all the original properties of the event (...x) and a new text property that will contain the cleaned subtitle text.&lt;/p&gt;

&lt;p&gt;Extract and clean the subtitle text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x.segs?.map(x =&amp;gt; x.utf8)?.join(" ")?.replace(/
/g,' ')?.replace(/♪|'|"|.{2,}|&amp;lt;[sS]*?&amp;gt;|{[sS]*?}|[[sS]*?]/g,'')?.trim() || ''
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the line extracts the subtitle text from the event's segs property and performs the following operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maps over the segs array and retrieves the utf8 property of each segment.&lt;/li&gt;
&lt;li&gt;Joins the segments into a single string with spaces between them.&lt;/li&gt;
&lt;li&gt;Replaces newline characters with spaces.&lt;/li&gt;
&lt;li&gt;Removes special characters, quotes, multiple periods, and any content within angle brackets, curly braces, or square brackets.&lt;/li&gt;
&lt;li&gt;Trims whitespace from the beginning and end of the string.
PS: If any of these operations fail (e.g., because the segs property is undefined), an empty string is returned (|| '').&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Youtube adblocker does not work? Solve it with 6 lines of code (December 2023)</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Wed, 13 Dec 2023 07:28:10 +0000</pubDate>
      <link>https://dev.to/wimdenherder/youtube-adblocker-does-not-work-solve-it-with-6-lines-of-code-december-2023-27dn</link>
      <guid>https://dev.to/wimdenherder/youtube-adblocker-does-not-work-solve-it-with-6-lines-of-code-december-2023-27dn</guid>
      <description>&lt;p&gt;I used to have a chrome extension Adblock to block YouTube video's but somwhere november 2023 it stopped working. YouTube noticed its activity and blocked the full experience whatsoever. &lt;/p&gt;

&lt;p&gt;So I tried to code it myself and it turned out to be just 6 lines of code that does the trick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setInterval(async () =&amp;gt; {
    if(!document.querySelector('.ad-showing')) return;
    document.querySelector('video').currentTime = 1000;
    await (async () =&amp;gt; new Promise(r =&amp;gt; setTimeout(r, 100)))();
    document.querySelector('.ytp-ad-skip-button-slot')?.click();
}, 300);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what it does&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checks if an ad is displayed&lt;/li&gt;
&lt;li&gt;skips advertisement to 1000 seconds&lt;/li&gt;
&lt;li&gt;waits 0,1 sec&lt;/li&gt;
&lt;li&gt;clicks skip button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Around this you will find the setInterval that executes the function every 0,3 sec&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Chrome Extension
&lt;/h2&gt;

&lt;p&gt;This is the ChatGPT prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a chrome extension that executes the following script on a youtube page. No pop up. No background script. Manifest version 3.&lt;br&gt;
setInterval(async () =&amp;gt; {&lt;br&gt;
    if(!document.querySelector('.ad-showing')) return;&lt;br&gt;
    document.querySelector('video').currentTime = 1000;&lt;br&gt;
    await (async () =&amp;gt; new Promise(r =&amp;gt; setTimeout(r, 100)))();"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click this link to find &lt;a href="https://chat.openai.com/share/4ccd22e1-9ef2-40a8-89ac-231ab3ad2239"&gt;ChatGPT's instruction&lt;/a&gt; how to create and install the Chrome Extension. It's really simple!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Loop a YouTube Video with 2 lines of code</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Wed, 13 Dec 2023 06:30:32 +0000</pubDate>
      <link>https://dev.to/wimdenherder/loop-a-youtube-video-with-2-lines-of-code-2m3o</link>
      <guid>https://dev.to/wimdenherder/loop-a-youtube-video-with-2-lines-of-code-2m3o</guid>
      <description>&lt;p&gt;Copy paste this in the developer console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v = document.querySelector('video')
v.onended = () =&amp;gt; v.currentTime = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A very simple trick to have your video be looped forever! :-)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Creating Freddie Mercury's voice with javascript</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Tue, 04 Jul 2023 08:31:05 +0000</pubDate>
      <link>https://dev.to/wimdenherder/creating-freddie-mercurys-voice-with-javascript-3dje</link>
      <guid>https://dev.to/wimdenherder/creating-freddie-mercurys-voice-with-javascript-3dje</guid>
      <description>&lt;p&gt;PS: Listen to the result here: &lt;a href="https://freddievoicejavascript.web.app/"&gt;https://freddievoicejavascript.web.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've always been fascinated with Freddie Mercury's voice (leadsinger of Queen). Especially, there is a moment in 'Who wants to live forever' on the high B note, where his voice has a golden glitter over it.  &lt;/p&gt;

&lt;p&gt;If you click here, it starts at the high B note!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://www.youtube.com/watch?t=188&amp;amp;v=_Jtpf8N5IDE&amp;amp;feature=youtu.be" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--EUuy1hOn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ytimg.com/vi/_Jtpf8N5IDE/hqdefault.jpg" height="360" class="m-0" width="480"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://www.youtube.com/watch?t=188&amp;amp;v=_Jtpf8N5IDE&amp;amp;feature=youtu.be" rel="noopener noreferrer" class="c-link"&gt;
          Queen - Who Wants To Live Forever (Official Video) - YouTube
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Taken from A Kind Of Magic, 1986.Click here to buy the DVD with this video at the Official Queen Store:http://www.queenonlinestore.comThe official 'Who Wants...
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--zaLyD-jh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.youtube.com/s/desktop/afaf5292/img/favicon.ico" width="16" height="16"&gt;
        youtube.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Analysing this note with spectrogram reveals that it's due to the overtones why this particular note has such a heavenly sound. Where you associate heaven with up in the skies, you hear the high overtones as heavenly sounds. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gYCQ3zm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8vpgb57qjh0ocvm3k9k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gYCQ3zm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8vpgb57qjh0ocvm3k9k.jpeg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Analysing this musical note through the lens of a spectrogram unveils a fascinating insight: the sublime, ethereal quality of this note stems from its overtones. The association of heaven with celestial heights echoes in the auditory experience of the high overtones, translating into what can be described as aural divinity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lpcadln---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6o44gdrjhaoh2xa5g3b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lpcadln---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6o44gdrjhaoh2xa5g3b.jpg" alt="Image description" width="692" height="1184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I analysed the frequencies and came to this array of [frequency, volume]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let freddie = [[500,1],[1000, 40], [1500, 5], [2000, 10],[2500,10],[3000,15],[3500,10],[4000,5]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention to the ratio's here.  &lt;/p&gt;

&lt;p&gt;First we have 500 Herz, this has a very weak volume of 1%. Let me explain this rare oddity. Freddie's voice actually makes the ventricular folds vibrate along with the vocal folds one octave lower (!!).  The ventricular folds This is a technique only known in throat singing, but really a rarity for pop/rock singers. &lt;/p&gt;

&lt;p&gt;What they discovered was that he likely employed subharmonics, a singing style where the ventricular folds vibrate along with the vocal folds. Most humans never speak or sing with their ventricular folds unless they’re Tuvan throat singers, so the fact that this popular rock vocalist was probably dealing with subharmonics is pretty incredible.&lt;/p&gt;

&lt;p&gt;Then we have 1000 Herz. This is the high B note, that you actually hear, the real note. The rest are overtones that give rise to the sound pallet, that golden shine over his voice.&lt;/p&gt;

&lt;p&gt;What is remarkable is that the 3000 Herz is a very strong overtone. This is the ratio 3:2 to the octave 2000 herz. In music harmony it's called the fifth. This is what gives the voice the golden glitter! And it's 37,5% (15/40) of the original volume of the basic tone, and that property of Freddie's voice is really special. It makes his voice the unique voice in the world. Especially considering that it's also in the high range. &lt;/p&gt;

&lt;p&gt;Then I added another typical feature of Freddie's voice. His pretty fast vibrato at 7 Herz. I added also some random aberrations to the vibrato to make it sound more human. &lt;br&gt;
Here is the script that you can just run in your browser (for example in the javascript console):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// this imitates the spectrogram of freddie mercury's voice in who wants to live forever

async function playFrequencies(baseFrequency, duration, frequencyVolumeArray, vibratoDepthPercent = 2, vibratoSpeed = 7, maxRandomness = 0.2) {
  // Create AudioContext
  let audioCtx = new (window.AudioContext || window.webkitAudioContext)();

  let pitchRatio = baseFrequency / frequencyVolumeArray[0][0];
  console.log('pitchRatio', pitchRatio)

  // Create a list of promises
  let promises = frequencyVolumeArray.map((freqVol) =&amp;gt; {
      // Extract the frequency and volume
      let frequency = freqVol[0];
      let volume = freqVol[1] / 100; // Convert percentage to decimal

      // Create oscillator for the frequency
      let oscillator = audioCtx.createOscillator();
      oscillator.frequency.value = pitchRatio * frequency; // Set base frequency

      // Create a GainNode to control the volume
      let gainNode = audioCtx.createGain();
      gainNode.gain.value = volume;

      // Create vibrato oscillator and gain
      let vibrato = audioCtx.createOscillator();
      let vGain = audioCtx.createGain();

      // Connect the vibrato to the main oscillator frequency
      vibrato.connect(vGain);
      vGain.connect(oscillator.frequency);

      // Connect the oscillator to the gain node
      oscillator.connect(gainNode);

      // Connect the gain node to the output destination
      gainNode.connect(audioCtx.destination);

      // Function to update vibrato parameters
      const updateVibrato = () =&amp;gt; {
          let randomDepthVariation = (Math.random() * 2 - 1) * maxRandomness; // -5% to +5%
          let randomSpeedVariation = (Math.random() * 2 - 1) * maxRandomness; // -5% to +5%
          let adjustedVibratoDepth = vibratoDepthPercent * (1 + randomDepthVariation);
          let adjustedVibratoSpeed = vibratoSpeed * (1 + randomSpeedVariation);
          vibrato.frequency.value = adjustedVibratoSpeed; // Vibrato frequency
          vGain.gain.value = frequency * (adjustedVibratoDepth / 100); // Vibrato depth
      };

      // Start the oscillator and vibrato
      oscillator.start();
      vibrato.start();

      // Update vibrato parameters every 0.5 seconds
      let vibratoInterval = setInterval(updateVibrato, 500);

      // Return a promise that resolves after the sound finishes
      return new Promise((resolve) =&amp;gt; {
          setTimeout(function() {
              clearInterval(vibratoInterval);
              resolve();
          }, duration * 1000);
      });
  });

  // Wait for all sounds to finish
  await Promise.all(promises);

  // Close the AudioContext
  audioCtx.close();
}

let freddie = [[500,1],[1000, 40], [1500, 5], [2000, 10],[2500,10],[3000,15],[3500,10],[4000,5]]

// Play frequencies with their respective volumes, with vibrato depth of 2% and speed of 7 Hz
await playFrequencies(500, 1, freddie, 2, 7, 0.2);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can just copy this javascript in the browser (javascript console) or run it in the  tag on a page. You&amp;amp;#39;ll hear the heavenly sounds :-D&amp;lt;/p&amp;gt;
&lt;/p&gt;

</description>
    </item>
    <item>
      <title>This 15-Line JavaScript Script Dubs Videos in Any Language!</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Sun, 30 Apr 2023 13:36:39 +0000</pubDate>
      <link>https://dev.to/wimdenherder/this-15-line-javascript-script-dubs-videos-in-any-language-fen</link>
      <guid>https://dev.to/wimdenherder/this-15-line-javascript-script-dubs-videos-in-any-language-fen</guid>
      <description>&lt;p&gt;Let's get straight to the point, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a random YouTube video, for example &lt;a href="https://www.youtube.com/watch?v=w3WNYmNLqyA"&gt;this one about me&lt;/a&gt; ;-) or any video with subtitles (automatic subtitles are fine too!)&lt;/li&gt;
&lt;li&gt;Open up the developer console (in Chrome: F12)&lt;/li&gt;
&lt;li&gt;Paste &lt;a href="https://raw.githubusercontent.com/wimdenherder/dub-youtube-in-any-language/main/dubbify.js"&gt;the 15-line script&lt;/a&gt;, and you'll hear everything in Italian!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, if you want another language, open up the developer console and just type &lt;code&gt;lang = 'en'&lt;/code&gt; and the script will update the language automatically!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lang = 'en'  // English
lang = 'es'  // Spanish
lang = 'fr'  // French
lang = 'de'  // German
lang = 'it'  // Italian
lang = 'pt'  // Portuguese
lang = 'ru'  // Russian
lang = 'zh'  // Chinese
lang = 'ja'  // Japanese
lang = 'ko'  // Korean
lang = 'ar'  // Arabic
lang = 'nl'  // Dutch, my native language
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also switch to other video's, it will keep working.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does this short script actually work?
&lt;/h2&gt;

&lt;p&gt;Thank you for your interest! It's a very simple script, that consists of four parts. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Variables declaration and initialization. I'm using global variables to keep track of the current subtitle, language and subtitles.&lt;/li&gt;
&lt;li&gt;The getSubs function, which retrieves the subtitles in the desired language.&lt;/li&gt;
&lt;li&gt;The speak function, which speaks the right text at the right time using the Speech Synthesis API.&lt;/li&gt;
&lt;li&gt;We will execute the speak function every 0,05 seconds
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let lastIndex = -1, lang = currentLang = "nl", ...
async function getSubs(langCode) {
  ...
}
const speak = async () =&amp;gt; {
  ...
}
setInterval(speak, 50); // every 0,05 sec

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Retrieving the subtitles
&lt;/h3&gt;

&lt;p&gt;If you type &lt;code&gt;await getSubs('de')&lt;/code&gt; in the console (assuming you pasted the 15-line code above), you will see an array with all subtitles in german! You can ask for any language, because the YouTube API just translates everything for you, cool he! We don't even have to translate it.  &lt;/p&gt;

&lt;p&gt;Let's break down the code into smaller parts to better understand each step:&lt;/p&gt;

&lt;p&gt;Retrieve the caption tracks object (ct):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let ct = JSON.parse((await (await fetch(window.location.href)).text()).split('ytInitialPlayerResponse = ')[1].split(';var')[0]).captions.playerCaptionsTracklistRenderer.captionTracks;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are on a YouTube page in the dev console, type &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; and big chance you'll find an object with lots of information (including subtitles, click "captions") about this video! &lt;br&gt;
I decided to retrieve this variable directly from the video page's HTML and extract the ytInitialPlayerResponse from it. Why? Because, if you watch another video, the global variable &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; does not get updated automatically. You cannot rely on it, you have to fetch the new video page and extract the object from it. &lt;/p&gt;

&lt;p&gt;Define a helper function (findCaptionUrl):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let findCaptionUrl = x =&amp;gt; ct.find(y =&amp;gt; y.vssId.indexOf(x) === 0)?.baseUrl;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helper function searches in the caption tracks and returns the base URL of the right caption track. &lt;br&gt;
The &lt;code&gt;vssId&lt;/code&gt; looks very similar to the languageCode as we will see in the following line.&lt;/p&gt;

&lt;p&gt;Build the subtitles URL (url):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let firstChoice = findCaptionUrl("." + langCode);
let url = firstChoice ? firstChoice + "&amp;amp;fmt=json3" : (findCaptionUrl(".") || findCaptionUrl("a." + langCode) || ct[0].baseUrl) + "&amp;amp;fmt=json3&amp;amp;tlang=" + langCode;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order of preference we would like to retrieve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a written subtitle in our language (our &lt;code&gt;firstChoice&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;any written subtitle (that we translate to our language)&lt;/li&gt;
&lt;li&gt;an automatic subtitle in our language&lt;/li&gt;
&lt;li&gt;any subtitle (that we translate to our language)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that the URL also includes the format (json3) and translate language (tlang) query parameters. The Youtube API does this for us :-)&lt;/p&gt;

&lt;p&gt;You can skip this detail: If we find an automatic subtitle in our language (3.), we will mute the voice-over in this case automatically, because the video is already in the preferred language. This happens by coincidence, because if you translate (tlang=) to its own language it returns blank subtitles :-)&lt;/p&gt;

&lt;p&gt;Fetch and process the subtitles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (await (await fetch(url)).json()).events.map(x =&amp;gt; ({...x, text: x.segs?.map(x =&amp;gt; x.utf8)?.join(" ")?.replace(/
/g,' ')?.replace(/♪|'|"|.{2,}|&amp;lt;[sS]*?&amp;gt;|{[sS]*?}|[[sS]*?]/g,'')?.trim() || ''}));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetches the subtitles from the previously constructed URL&lt;/li&gt;
&lt;li&gt;parses them as JSON&lt;/li&gt;
&lt;li&gt;processes the events to extract the text of each subtitle. &lt;/li&gt;
&lt;li&gt;It also cleans the text by removing unnecessary characters and formatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down the code into smaller parts to better understand each step:&lt;/p&gt;

&lt;p&gt;Fetch the subtitles and convert them to JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await (await fetch(url)).json()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the line fetches the subtitles from the provided URL and converts the response into a JSON object.&lt;/p&gt;

&lt;p&gt;Process the events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.events.map(x =&amp;gt; ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the line maps over the events array of the JSON object to process each event.&lt;/p&gt;

&lt;p&gt;Create a new object for each event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;({...x, text: ...})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each event, a new object is created that includes all the original properties of the event (...x) and a new text property that will contain the cleaned subtitle text.&lt;/p&gt;

&lt;p&gt;Extract and clean the subtitle text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x.segs?.map(x =&amp;gt; x.utf8)?.join(" ")?.replace(/
/g,' ')?.replace(/♪|'|"|.{2,}|&amp;lt;[sS]*?&amp;gt;|{[sS]*?}|[[sS]*?]/g,'')?.trim() || ''
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part of the line extracts the subtitle text from the event's segs property and performs the following operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maps over the segs array and retrieves the utf8 property of each segment.&lt;/li&gt;
&lt;li&gt;Joins the segments into a single string with spaces between them.&lt;/li&gt;
&lt;li&gt;Replaces newline characters with spaces.&lt;/li&gt;
&lt;li&gt;Removes special characters, quotes, multiple periods, and any content within angle brackets, curly braces, or square brackets.&lt;/li&gt;
&lt;li&gt;Trims whitespace from the beginning and end of the string.
PS: If any of these operations fail (e.g., because the segs property is undefined), an empty string is returned (|| '').&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Analyze the speak Function
&lt;/h3&gt;

&lt;p&gt;Now that we have the text, we want to let it be spoken! The speak function is an asynchronous function that manages dubbing the video. Its goal is to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find &amp;amp; speak the current subtitle&lt;/li&gt;
&lt;li&gt;synchronize the video with voice&lt;/li&gt;
&lt;li&gt;check if the video's URL or language has changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down the code into smaller parts to better understand each step:&lt;/p&gt;

&lt;p&gt;Update the subtitles if the video URL or language has changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (location.href !== currentUrl || currentLang !== lang) (currentUrl = location.href) &amp;amp;&amp;amp; (currentLang = lang) &amp;amp;&amp;amp; (subs = await getSubs(lang));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the current subtitle based on the video's playback time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const currentIndex = subs.findIndex(x =&amp;gt; x.text &amp;amp;&amp;amp; x.tStartMs &amp;lt;= 1000 * vid.currentTime &amp;amp;&amp;amp; x.tStartMs + x.dDurationMs &amp;gt;= 1000 * vid.currentTime);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return early if the script can't find a subtitle (the current subtitle index is -1) or we're still speaking the same subtitle (it's the same as the last subtitle index):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ([-1, lastIndex].includes(currentIndex)) return;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the previous subtitle is still being spoken, we will pause the video as long as the voice is defined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (voice) return vid.pause();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there is no voice from previous subtitle, we can resume video playback and create a new SpeechSynthesisUtterance with the current subtitle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vid.play();
voice = new SpeechSynthesisUtterance(subs[(lastIndex = currentIndex)].text);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the language and event listeners for the SpeechSynthesisUtterance and adjust the video volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;voice.lang = lang;
voice.onend = () =&amp;gt; (vid.volume = baseVolume || 1) &amp;amp;&amp;amp; (voice = null);
vid.volume = 0.1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the speech synthesis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;speechSynthesis.speak(voice);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion:
&lt;/h3&gt;

&lt;p&gt;In this tutorial, we explored the getSubs (get subtitles) and speak function, which is responsible for synchronizing the video playback and the Speech Synthesis API to dub YouTube videos. You can see how simple and short a script can be! I hope it inspires you!&lt;/p&gt;

&lt;p&gt;Note: The quality of the dubbing may vary depending on the available voices for the Speech Synthesis API in your browser and the quality of the subtitles.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Impersonating a google service account</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Fri, 31 Mar 2023 20:39:54 +0000</pubDate>
      <link>https://dev.to/wimdenherder/impersonating-a-google-service-account-15ne</link>
      <guid>https://dev.to/wimdenherder/impersonating-a-google-service-account-15ne</guid>
      <description>&lt;p&gt;PS: this blog is not very clear, just a reminder for myself in the future, when I encounter something similar. Sorry!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a google cloud project&lt;/li&gt;
&lt;li&gt;Enable Drive API&lt;/li&gt;
&lt;li&gt;Create service account credentials&lt;/li&gt;
&lt;li&gt;Login as admin in your domain and&lt;/li&gt;
&lt;li&gt;Follow the steps here:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/identity/protocols/oauth2/service-account#httprest"&gt;https://developers.google.com/identity/protocols/oauth2/service-account#httprest&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  install
&lt;/h2&gt;

&lt;p&gt;npm init &lt;br&gt;
npm install googleapis &lt;br&gt;
npm install google-auth-library &lt;/p&gt;

&lt;p&gt;create a file &lt;code&gt;index.js&lt;/code&gt; and replace the email and credentials path below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { google } = require('googleapis');
const { GoogleAuth } = require('google-auth-library');
const fs = require('fs');

async function listFiles(auth) {
  const drive = google.drive({ version: 'v3', auth });

  try {
    const response = await drive.files.list({
      pageSize: 10, // Adjust this value to control the number of files returned per request (max: 1000)
      fields: 'nextPageToken, files(id, name, mimeType)',
    });

    const files = response.data.files;
    if (files.length) {
      console.log('Files:');
      files.forEach((file) =&amp;gt; {
        console.log(`${file.name} (${file.id})`);
      });
    } else {
      console.log('No files found.');
    }
  } catch (error) {
    console.error('Error fetching file list:', error);
  }
}

async function main() {
  const SERVICE_ACCOUNT_FILE = 'YOURCREDENTIALS.json'; // Replace with the actual path to your service account credentials JSON file

  const auth = new GoogleAuth({
    keyFile: SERVICE_ACCOUNT_FILE,
    scopes: ['https://www.googleapis.com/auth/drive'],
    clientOptions: {
      subject: 'EMAIL@DOMAIN.COM' // Replace with email to impersonate
    },
  });

  const client = await auth.getClient();
  listFiles(client);
}

main();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was hard to find the right way, so that's why I wrote this blog to help myself and others!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to toonify yourself</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Wed, 25 Jan 2023 21:58:41 +0000</pubDate>
      <link>https://dev.to/wimdenherder/how-to-toonify-yourself-3756</link>
      <guid>https://dev.to/wimdenherder/how-to-toonify-yourself-3756</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkas00lk80k2hhzwrxey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkas00lk80k2hhzwrxey.png" alt="Image description" width="800" height="1081"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Artificial intelligence is rapidly advancing, and it is now possible to utilize it to create a cartoon version of oneself. One can visit the website &lt;a href="https://huggingface.co/spaces/PKUWilliamYang/VToonify" rel="noopener noreferrer"&gt;https://huggingface.co/spaces/PKUWilliamYang/VToonify&lt;/a&gt; to experiment with this technology. However, it should be noted that the server may be overwhelmed, leading to difficulties in accessing the video feature.&lt;/p&gt;

&lt;p&gt;To overcome this issue, one can use the colab link and should first convert the video to the .mov format using the following command, which requires the installation of ffmpeg on one's computer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffmpeg -i &amp;lt;YOURVIDEO&amp;gt; -c:v libx264 -profile:v main -pix_fmt yuv420p -s 1280x720 -b:v 9710k -r 30 -g 30 -keyint_min 30 -c:a aac -ac 1 -ar 48000 -b:a 242k -strict -2 &amp;lt;OUTPUTNAME&amp;gt;.mov&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is the link to the google colab page: &lt;a href="https://colab.research.google.com/github/williamyang1991/VToonify/blob/master/notebooks/inference_playground.ipynb" rel="noopener noreferrer"&gt;https://colab.research.google.com/github/williamyang1991/VToonify/blob/master/notebooks/inference_playground.ipynb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should execute the necessary steps, including uploading the video and rescaling it.&lt;/p&gt;

&lt;p&gt;To download the resulting video, the traditional right-click method may not work. Instead, one must use the Chrome Developer Console by pressing cmd+shift+c and clicking on the video element to find the path, which will be in the format of "temp/output9f832f8huh". &lt;/p&gt;

&lt;p&gt;Go to sidebar on the left with files&lt;br&gt;
click .. to go to parent folder, and then open folder temp and the output file, click the dots to open menu to download it&lt;/p&gt;

&lt;p&gt;Now mix it with original audio&lt;br&gt;
&lt;code&gt;ffmpeg -i &amp;lt;originalvideo&amp;gt;.mov -i &amp;lt;toonify-video&amp;gt;.mov -c:a copy -c:v copy -map 0:a -map 1:v -y &amp;lt;outputfile&amp;gt;.mov&lt;/code&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>This is own software that I run very often</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Sun, 15 Jan 2023 05:53:06 +0000</pubDate>
      <link>https://dev.to/wimdenherder/this-is-own-software-that-i-run-very-often-4oem</link>
      <guid>https://dev.to/wimdenherder/this-is-own-software-that-i-run-very-often-4oem</guid>
      <description>&lt;p&gt;The following are apps that I wrote for myself and actually use super often. They really increase my well-being, so I thought I share it with you, just for inspiration. &lt;/p&gt;

&lt;h2&gt;
  
  
  YouTok
&lt;/h2&gt;

&lt;p&gt;I made a tiktok version of YouTube. You can scroll down and the video's play immediately. This works really well for music and subjects you want to explore. You can log in and save favorites. It's also very addictive. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wDVl08wa6XU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Tune The Day
&lt;/h2&gt;

&lt;p&gt;This is a time management bot, that I use daily. A friend has even used it continuously since March 2019. You can log activities via a Telegram bot. The bot offers a menu with your most popular activities. Just a click and it starts logging. It has also lots of text-based inputs, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;french    (I start French right now)&lt;/li&gt;
&lt;li&gt;20 code  (I started to code 20 minutes ago)&lt;/li&gt;
&lt;li&gt;14:00 guitar    (you can reset and go back 24 hours)&lt;/li&gt;
&lt;li&gt;/viewWeeklyTotals or short .vwt&lt;/li&gt;
&lt;li&gt;/zeur  (see your daily totals so far)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dz1rG8ai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wba3oyq4kx1btkf1tyzc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dz1rG8ai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wba3oyq4kx1btkf1tyzc.jpg" alt="Image description" width="488" height="924"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dubbify
&lt;/h2&gt;

&lt;p&gt;This chrome extension creates automatic translated voice-over for YouTube and NPO.nl. Really nice if you want to learn a language. I also made algorithms that teach you by going through video's step by step, for example: french-french-english and then word by word french-english. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P9IXtk79--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9oo6vbivlzqg051drlfs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P9IXtk79--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9oo6vbivlzqg051drlfs.jpg" alt="Image description" width="380" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NPO dubbify
&lt;/h2&gt;

&lt;p&gt;Same for dutch television npo.nl&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/HCbk5oLP8xA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT-for-YouTube
&lt;/h2&gt;

&lt;p&gt;This chrome extension delivers a summary of the video (based on subtitles). It splits the video up in the max size that GPT can handle. You can even 'zoom in' on the summary. There is also an option to see timestamps in the summary, so that you can easily go to that part&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AZtan9jg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1k2uir1la55ir2cfq7r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AZtan9jg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1k2uir1la55ir2cfq7r.jpg" alt="Image description" width="880" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT-for-any-site
&lt;/h2&gt;

&lt;p&gt;It summarizes any page. You can ask questions about the page (custom prompt)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ND5ruYAn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwrwa8xms6x8m09doksz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ND5ruYAn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwrwa8xms6x8m09doksz.jpg" alt="Image description" width="880" height="735"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Music lessons scheduling
&lt;/h2&gt;

&lt;p&gt;For my schools Guitar Academy and Drum Academy I use a spreadsheet scheduling system that automates many things: sending payment e-mails, scheduling e-mails, processing changes, etc. It gave me a lot of free time and taught me Google Apps Script. &lt;/p&gt;

&lt;h2&gt;
  
  
  YouTube Quality Ranking
&lt;/h2&gt;

&lt;p&gt;This chrome extension helped me find so much great content on YouTube! I adjusted an existing Thumbnail rating extension. In my version I'm calculating likes / views. This is a so much better indication of quality: how many liked it? Instead of likes vs dislikes.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E19kLBWw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuoxxp2pyq3id7m4vm6b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E19kLBWw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuoxxp2pyq3id7m4vm6b.jpg" alt="Image description" width="880" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  YouTube Mp3 from the terminal
&lt;/h2&gt;

&lt;p&gt;You type in &lt;code&gt;mp3 + url&lt;/code&gt; and it downloads the mp3. You can't go any quicker than that. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K3mDQvBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wq8zkzhmnjn0p75n2p6e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K3mDQvBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wq8zkzhmnjn0p75n2p6e.jpg" alt="Image description" width="880" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backingtrack machine
&lt;/h2&gt;

&lt;p&gt;An interface that guides you to find the right backing tracks for jazz, gypsy, rock, metal. You can choose from a list of standards and it starts immediately. You can also explore solists over the songs. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TY3qE6Ni--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhddj5aiy3hqaofnlrgs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TY3qE6Ni--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhddj5aiy3hqaofnlrgs.jpg" alt="Image description" width="880" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Whatsapp translator
&lt;/h2&gt;

&lt;p&gt;It automatically translates all messages. Really great if you're learning a new language!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l1shnTmm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1xequjnp2z2erq9dr1l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l1shnTmm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1xequjnp2z2erq9dr1l.jpg" alt="Image description" width="880" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3F6RsxGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqgekqwxlw89p9vgo438.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3F6RsxGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqgekqwxlw89p9vgo438.jpg" alt="Image description" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dubchat
&lt;/h2&gt;

&lt;p&gt;This is a continuation of fantastic tutorial. Now I'm using this as my own chat-app and I've added bots, autoplayed preloaded video, translated and spoken (text to speech) messages, a profile page and funny sounds. Only problem: nobody else is active on the app ;-)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6eKKoLHI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/snx30xp1qb692fndvh8d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6eKKoLHI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/snx30xp1qb692fndvh8d.jpg" alt="Image description" width="880" height="891"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PS: I wrote a blog why you should build your own software here: &lt;a href="https://www.linkedin.com/pulse/why-i-build-everything-myself-wim-den-herder/"&gt;https://www.linkedin.com/pulse/why-i-build-everything-myself-wim-den-herder/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPT-for-YouTube is based on GPT-for-Google&lt;/li&gt;
&lt;li&gt;GPT-for-anything is based on summarize.site chrome extension&lt;/li&gt;
&lt;li&gt;Dubchat is based on dev lama's tutorial chatapp in firebase react&lt;/li&gt;
&lt;li&gt;YouTube Quality Ranking is an adjustment of Thumbnail Rating Bar for YouTube&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>How to fetch and play a mp3 from play.ht AI voices with the web Fetch API</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Sat, 14 Jan 2023 20:48:16 +0000</pubDate>
      <link>https://dev.to/wimdenherder/how-to-fetch-and-play-a-mp3-from-playht-ai-voices-with-the-web-fetch-api-a7i</link>
      <guid>https://dev.to/wimdenherder/how-to-fetch-and-play-a-mp3-from-playht-ai-voices-with-the-web-fetch-api-a7i</guid>
      <description>&lt;p&gt;Yes, it's possible to get and play the mp3 of the AI voices of play.ht! Just in your browser, just client-side. You can open up developer console and copy paste the code. &lt;/p&gt;

&lt;p&gt;You have to replace &lt;strong&gt;SECRETKEY&lt;/strong&gt; and &lt;strong&gt;USERID&lt;/strong&gt; below with your own ones. You log in and find them here: &lt;a href="https://play.ht/app/api-access" rel="noopener noreferrer"&gt;https://play.ht/app/api-access&lt;/a&gt;&lt;br&gt;
or via menu: Accessibility -&amp;gt; Integrations -&amp;gt; API Access&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
async function speak(text) {
  return new Promise(async (resolve, reject) =&amp;gt; {
    try {
      const response = await (await fetch('https://play.ht/api/v1/convert', {
        method: 'POST',
        headers: {
          'Authorization': 'SECRETKEY',
          'X-User-ID': 'USERID',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          "voice": "en-US-MichelleNeural",
          "content": text.split(/\.|\?|!/),
          "title": "Testing Italian api convertion"
        })
      })).json();
      const mp3Url = `
      https://media.play.ht/full_${response.transcriptionId}.mp3`;
      console.log(mp3Url);
      while(true) {
        console.log(`trying again...`);
        if((await fetch(mp3Url)).status === 200) {
          snd = new Audio(mp3Url);
          snd.play();
          snd.onended = resolve;
          break;
        }
      }
    } catch (err) {
      reject(err);
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Finding the right mp3 url
&lt;/h3&gt;

&lt;p&gt;I discovered the mp3 URL "&lt;a href="https://media.play.ht/full_$%7Bresponse.transcriptionId%7D.mp3" rel="noopener noreferrer"&gt;https://media.play.ht/full_${response.transcriptionId}.mp3&lt;/a&gt;" by logging into the play.ht website, navigating to the 'files' section in the menu on the left, and playing the audio fragments. This URL was not included in the play.ht documentation. I also noted that the URL provided in the documentation for fetching the mp3 file, "./articleStatus?transcriptionId={transcriptionId}", did not work and returned a "Forbidden" error. &lt;/p&gt;

&lt;h3&gt;
  
  
  A loop until the mp3 is available
&lt;/h3&gt;

&lt;p&gt;The mp3 is not immediately available, so I made a while-loop to try again and again!&lt;/p&gt;

&lt;h3&gt;
  
  
  Promise
&lt;/h3&gt;

&lt;p&gt;The function is a promise, so that you can also await it. This can be useful in applications, so that you know when the text has been spoken&lt;/p&gt;

&lt;h3&gt;
  
  
  Voice
&lt;/h3&gt;

&lt;p&gt;You can change the voice from a list &lt;a href="https://playht.github.io/api-docs-generator/#standard-api-voices" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In the script above just replace &lt;strong&gt;en-US-MichelleNeural&lt;/strong&gt; with the voice name. &lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Fetch youtube api for free (in dev console)</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Fri, 21 Oct 2022 18:38:43 +0000</pubDate>
      <link>https://dev.to/wimdenherder/fetch-youtube-api-for-free-in-dev-console-2nmi</link>
      <guid>https://dev.to/wimdenherder/fetch-youtube-api-for-free-in-dev-console-2nmi</guid>
      <description>&lt;p&gt;You have to be on the youtube.com website, open the dev console, and then you can actually find results from the youtube api for free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function fetchYtInitialData(url) {
  let pageHtml = await (await fetch(url)).text(), result;
  try { return JSON.parse(pageHtml.split('var ytInitialData = ')[1].split(';&amp;lt;/script&amp;gt;')[0]); } catch(err) { console.error(err) }
}

async function freeYoutubeApiCall(query) {
  ytInitialData = await fetchYtInitialData('https://www.youtube.com/results?search_query=' + query);
  const videoData = [];
  ytInitialData.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents.forEach(y =&amp;gt; y.itemSectionRenderer?.contents.forEach(x =&amp;gt; { 
      if(x.videoRenderer) videoData.push(x.videoRenderer);
    })
  );
  return videoData;
}

let results = await freeYoutubeApiCall('rick roll');
results;
results.map(x =&amp;gt; x.videoId);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Get the view count of YouTube Video (without api key) and title and anything else</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Sun, 19 Jun 2022 06:44:14 +0000</pubDate>
      <link>https://dev.to/wimdenherder/get-the-view-count-of-youtube-video-without-api-key-3gp0</link>
      <guid>https://dev.to/wimdenherder/get-the-view-count-of-youtube-video-without-api-key-3gp0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; this only works in your browser in developers console if you're on the youtube page (otherwise you'll get a cors error). This is super useful for chrome extensions in which you execute content-script.js on the youtube page!&lt;/p&gt;

&lt;p&gt;You can get the &lt;strong&gt;view count&lt;/strong&gt; of a YouTube video &lt;strong&gt;without an api key&lt;/strong&gt;. Here is the secret formula and quick solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getViews(videoId) {
  const response = await fetch(`https://www.youtube.com/watch?v=${videoId}`);
  const html = await response.text();
  const viewCount = html?.split('viewCount":"')?.[1]?.split('"')?.[0];
  return viewCount ? parseInt(viewCount) : null;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
]&lt;/p&gt;
&lt;h2&gt;
  
  
  Get more data from the video
&lt;/h2&gt;

&lt;p&gt;Every Youtube page has a very useful ytInitialPlayerResponse variable. In this you can find subtitles, view counts, etc. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to any YouTube video&lt;/li&gt;
&lt;li&gt;Open developer console&lt;/li&gt;
&lt;li&gt;Refresh the page (important)&lt;/li&gt;
&lt;li&gt;past &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; in the developer console&lt;/li&gt;
&lt;li&gt;you'll have lots of info at your disposal now!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuge7rm2ll2v1yqp86ah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuge7rm2ll2v1yqp86ah.png" alt="Image description" width="562" height="986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The view count is under videoDetails. In my solution i'm just searching (with &lt;code&gt;split&lt;/code&gt;) on &lt;code&gt;viewCount: "&lt;/code&gt; and I'm taking the first result. &lt;/p&gt;

&lt;p&gt;It would be better to be able to work with this &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; variable. I found out that it's this part up to &lt;code&gt;var meta = document.createElement('meta')&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JSON.parse(html.split('ytInitialPlayerResponse = ')[1].split(`;var meta = document.createElement('meta')`)[0])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could get the complete video data with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getYouTubeVideoData(videoId) {
  const response = await fetch(`https://www.youtube.com/watch?v=${videoId}`);
  const html = await response.text();
  const ytInitialPlayerResponse = JSON.parse(html.split('ytInitialPlayerResponse = ')[1].split(`;var meta = document.createElement('meta')`)[0]);
  return ytInitialPlayerResponse;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get video Title
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getYouTubeVideoTitle(videoId) {
  const response = await fetch(`https://www.youtube.com/watch?v=${videoId}`);
  const html = await response.text();
  const ytInitialPlayerResponse = JSON.parse(html.split('ytInitialPlayerResponse = ')[1].split(`;var meta = document.createElement('meta')`)[0]);
  return ytInitialPlayerResponse.videoDetails.title;
}

await getYouTubeVideoTitle('dQw4w9WgXcQ');
// will return 'Rick Astley - Never Gonna Give You Up (Official Music Video)'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can return any parameter from the &lt;code&gt;ytInitialPlayerResponse&lt;/code&gt; object:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futxh4zi1e7iqp8wdk776.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futxh4zi1e7iqp8wdk776.png" alt="Image description" width="512" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could even retrieve the subtitles in two steps&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// if no languageCode is given, then return first subtitle from the list
async function getYouTubeSubtitle(videoId, languageCode) {
  const response = await fetch(`https://www.youtube.com/watch?v=${videoId}`);
  const html = await response.text();
  const ytInitialPlayerResponse = JSON.parse(html.split('ytInitialPlayerResponse = ')[1].split(`;var meta = document.createElement('meta')`)[0]);
  const captionTracks = ytInitialPlayerResponse.captions. playerCaptionsTracklistRenderer.captionTracks;
  const captionTrack = languageCode ? captionTracks.find(c =&amp;gt; c.languageCode === languageCode) : captionTracks[0];
  return await fetch(captionTrack.baseUrl);
}

await getYouTubeSubtitle('dQw4w9WgXcQ');
// will return 'Rick Astley - Never Gonna Give You Up (Official Music Video)'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Run npm packages in developers console on any site</title>
      <dc:creator>wimdenherder</dc:creator>
      <pubDate>Sun, 29 May 2022 14:05:29 +0000</pubDate>
      <link>https://dev.to/wimdenherder/run-npm-packages-in-developers-console-on-any-site-2a78</link>
      <guid>https://dev.to/wimdenherder/run-npm-packages-in-developers-console-on-any-site-2a78</guid>
      <description>&lt;p&gt;If you want to test a npm package directly in the browser, there is a way to do this!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07ehdirz6javfgj71bou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07ehdirz6javfgj71bou.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How?
&lt;/h1&gt;

&lt;p&gt;The tric is to load a javascript file programmatically in the developers console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const script = document.createElement('script');
script.src = 'localhost:666/bundle.js';
document.body.appendChild(script);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only what is &lt;strong&gt;bundle.js&lt;/strong&gt;?  &lt;/p&gt;

&lt;p&gt;That's a bundle that we will create in a minute with &lt;code&gt;browserify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And what is &lt;strong&gt;localhost:666/&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a local server that you will serve (later in this tutorial), because chrome browser does not allow local files to be loaded. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let's start building
&lt;/h2&gt;

&lt;p&gt;You can replace &lt;code&gt;cheerio&lt;/code&gt; with any npm package you like (but not all of them work in the browser)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open up a new directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm init -y&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm i -D browserify http-server&lt;/code&gt; (as dev dependencies)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm i cheerio&lt;/code&gt; (or any package you want to use)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touch main.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code .&lt;/code&gt; (launch visual code studio)&lt;/li&gt;
&lt;li&gt;in main.js edit the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cheerio = require("cheerio");
window.cheerio = cheerio; // this makes it available in the console later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;in package.json add the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "serve": "browserify main.js -o build/bundle.js &amp;amp;&amp;amp; http-server -p 666 build/"
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;open browser&lt;/li&gt;
&lt;li&gt;check if this loads: localhost:666/bundle.js&lt;/li&gt;
&lt;li&gt;if so copy paste this to Chrome's developers console (on any page)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const script = document.createElement('script');
script.src = 'localhost:666/bundle.js';
document.body.appendChild(script);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now play around in your developer console with the npm package, in our case:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const $ = cheerio.load(document.body.innerHTML);
$('a');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PS: Not all npm packages work, I tried &lt;code&gt;request&lt;/code&gt; but it gave an error in the developer console. But nowadays I would recommend to use the new standard &lt;code&gt;fetch&lt;/code&gt; for this.&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/wimdenherder/npm-in-the-browser" rel="noopener noreferrer"&gt;You can clone this repo also!&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
