Make your own YouTube Downloader

By Rohan Mukherjee

April 8th, 2020

image

Bored during the quarantine? Want to watch your favorite YouTube videos or play your favorite songs on loop, but don’t want to spend all your data on it? Here’s how you can write code to download it.

Disclaimer

Downloading videos from YouTube is against their policy. The purpose of the article is just to demonstrate how you can download content from the Internet, and to give you an insight into the working of the YouTube API.

Description

Every video on YouTube has a unique ID. The link to a YouTube video generally looks like:

https://www.youtube.com/watch?v=fJ9rUzIMcZQ

The series of characters fJ9rUzIMcZQ in this link represents the unique ID of this video, given by the GET query parameter v.

1) Get information about the video

A route in the YouTube API that might be very useful is /get_video_info?video_id=${videoId}&eurl={eurl}. This route sends back data about the video, which includes the videoId, title, length in seconds, links from where the video can be streamed in different qualities, etc. The eurlis an important query parameter without which the video might return a playability status 'UNPLAYABLE'. An eurlis of the format:

https://youtube.googleapis.com/v/${videoId}

The data returned from the /get_video_info route can be parsed using the following code snippet.

import axios from "axios";
import { URLSearchParams } from "url";

async function getVideoInfo() {
  const response = await axios.get(
    `https://www.youtube.com/get_video_info?video_id=${videoId}&el=embedded&eurl=${eurl}&sts=18333`
  );
  const parsedResponse = Object.fromEntries(new URLSearchParams(response.data));
}

getVideoInfo();

2) Parse the information

The player_response field in the obtained parsedResponse is the field we mainly require, particularly player_response.playabilityStatusplayer_response.videoDetails, and player_response.streamingData. A function may be written which returns only the required data.

import axios from 'axios';
import { URLSearchParams } from 'url';

import VideoInfo from './models/VideoInfo';

public static async getVideoInfo(videoId: string): Promise<VideoInfo> {
	const videoIdRegex = /^[\w_-]+$/;
	const eurl = `https://youtube.googleapis.com/v/${videoId}`;
	if (!videoIdRegex.test(videoId)) {
		throw new Error('Invalid videoId.');
	}

	const response = await axios.get(`https://www.youtube.com/get_video_info?video_id=${videoId}&el=embedded&eurl=${eurl}&sts=18333`);

	const parsedResponse = Object.fromEntries(new URLSearchParams(response.data));

	const jsonResponse = JSON.parse(parsedResponse.player_response);
	const { playabilityStatus, videoDetails, streamingData } = jsonResponse;
	const videoInfo = <VideoInfo> { playabilityStatus, videoDetails, streamingData };

	return videoInfo;
}

The player_response field in the obtained parsedResponse is the field we mainly require, particularly player_response.playabilityStatusplayer_response.videoDetails, and player_response.streamingData. A function may be written which returns only the required data.This videoInfo object has all the information you need to show details about the video or to download it. Now, you can get information about the video from videoInfo.videoDetails, such as the title (videoInfo.videoDetails.title), description (videoInfo.videoDetails.description), etc.

To fetch the streaming links of these videos, you need the following two keys from the videoInfo object:

  • videoInfo.streamingData.formats

  • videoInfo.streamingData.adaptiveFormats

3) Download content from streaming links

The links contained in the formats array are used to stream videos having both audio and video. However, there are only a few options you have here, for example you may have to choose between 720p and 360p. You can now download content from a link of your choice (from videoInfo.streamingData.formats[index].url) using a simple downloader function.

import axios from "axios";

import Headers from "./models/Headers";

async function download(url: string, filename: string, headers: Headers) {
  return new Promise((resolve, reject) => {
    axios({
      method: "get",
      url,
      responseType: "stream",
      headers,
    }).then((response) => {
      response.data
        .pipe(fs.createWriteStream(filename))
        .on("finish", (err: Error) => {
          if (err) reject(err);
          else resolve();
        });
    });
  });
}

axios — Download content from url

In adaptiveFormats you can find some links which stream just the video (without the audio) and some which stream just the audio. Here, there are more options to choose from, starting from 144p to 1080p or higher, with various codecs and mime-types.

So, if you want to download a 1080p video, you may download the 1080p video-only stream and an audio-only stream, and then merge them using ‘ffmpeg’, as follows:

import ffmpeg from "fluent-ffmpeg";

export default async function mergeStreams(
  videoFile: string,
  audioFile: string,
  outputFile: string
) {
  return new Promise((resolve, reject) => {
    ffmpeg(videoFile)
      .input(audioFile)
      .saveToFile(outputFile)
      .on("error", (err) => {
        reject(err);
      })
      .on("end", () => {
        logger.info("Finished merging!");
        resolve();
      });
  });
}

ffmpeg — Merge audio and video files

4) Doesn’t work for some videos?

Now, here's the catch. You might not be able to download some videos in this way. The videos that have a field called videoInfo.streamingData.formats[index].url can be downloaded in this way, because in these links, a GET parameter sig is present. However, in the videoInfo of some videos, instead of this url key in the formats and adaptiveFormats array elements, a key named cipher is present. Now this cipher contains 3 things:

  • sp: The signature parameter

  • s: The signature data

  • url: The streaming link

This parameter s has to be passed through a series of functions (reverses, swaps and splits) before it can be attached to the URL. These functions can be obtained from the JS files in the sources on https://www.youtube.com/watch?v=${videoId}. The resultant value obtained may be attached to the url using the query parameter as specified in the value of sp. So, if sp = sig, the query parameter will be attached in the fashion https://.../...&sig=sig. This link will finally fetch you the requested video/audio stream.

The code snippets were obtained from here.



Continue Learning