0

Just to give some context, i'm trying to download an entire playlist on youtube by fetching the data from the youtube API, which will return an object containing all the ids and titles in the playlist. Then i have a function to download a single video, which i'm trying to use inside a for loop to go through all ids on the playlist, and i need to come up with some way to wait the full completion of a function before the next iteration, like a queue.

Function to download the video:

async function downloadVideo (videoId, videoTitle) {

  const mp3String = `yt-dlp -f bestaudio --extract-audio --audio-format mp3 --audio-quality 0 -o ./music/reggae/"%(title)s.%(ext)s" ${videoId}`
  console.log(`Download started. videoname: ${videoTitle}`);
  const child = exec(mp3String, (err, res) => {
    
    if (err) return console.log(err);

  })
  child.on('exit', () => {
      console.log(`Download finished at: ./${videoTitle}.mp3`)
      return
  });
  
}

Function to download the entire playlist:

async function downloadEntirePlaylist (playlistID) {
  
  playlistData = await (fetchPlaylist(playlistID)) // returns all ids and titles from playlist

  for (let i=0;i<playlistData.idList.length;i++) {
    await downloadVideo(playlistData.idList[i], playlistData.videoTitles[i])
    
  }
  // download -> wait till completion -> start another download ->> ...finish entire playlist and return

}

I tried using the await to actually stop the code before finishing the loop so downloadVideo() can actually finish before the loop ends, but it's not working...

On the output, they all start almost at the same time and the conversion of each one also starts basically together, and that's a big issue when it comes to playlists with more videos, since my pc starts frying and errors begin to stack.

Download started. videoname: C418 - Haggstrom - Minecraft Volume Alpha
Download started. videoname: C418 - Wet Hands - Minecraft Volume Alpha
Download started. videoname: C418 - Dry Hands - Minecraft Volume Alpha
Download started. videoname: C418 - Droopy likes your Face - Minecraft Volume Alpha
Download finished at: ./C418 - Droopy likes your Face - Minecraft Volume Alpha.mp3
Download finished at: ./C418 - Wet Hands - Minecraft Volume Alpha.mp3
Download finished at: ./C418 - Haggstrom - Minecraft Volume Alpha.mp3
Download finished at: ./C418 - Dry Hands - Minecraft Volume Alpha.mp3
beetlej
  • 3
  • 3
  • 2
    `downloadVideo()` (which downloads audio only) doesn't wait for anything asynchronous in its body and therefor immediately returns `undefined` (wrapped in a resolved `Promise`) – Andreas Jan 12 '23 at 18:03
  • 1
    You probably need to promisify `downloadVideo` and return the promise (with resolve linked to the `exit` event). – Ben Aston Jan 12 '23 at 18:11
  • i tried to wrap the downloadVideo (now downloadAudio) function around promisify, and now it download and finishes the first video, then stop the entire for loop at all. Any ideas to work around that? Thanks – beetlej Jan 12 '23 at 18:46

1 Answers1

0

You probably need to promisify downloadVideo.

Something like this will download one video at a time, sequentially. Untested, of course.

const downloadCmd = (id) => 
    `yt-dlp -f bestaudio --extract-audio \
--audio-format mp3 --audio-quality 0 \
-o ./music/reggae/"%(title)s.%(ext)s" ${id}`

const downloadVideo = (id) =>
  new Promise((resolve, reject) =>
    exec(downloadCmd(id), (err, res) => 
      err 
        ? reject(err) 
        : resolve(res)))
async function downloadPlaylist(id) { 
  for(const id of (await fetchPlaylist(id)).idList) 
    await downloadVideo(id)

  console.log(`Playlist ${id} downloaded.`)
}

Ben Aston
  • 53,718
  • 65
  • 205
  • 331