Make your own YouTube Downloader

ā€¢

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.playabilityStatus,Ā player_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.playabilityStatus,Ā player_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

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();
        });
    });
  });
}

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();
      });
  });
}

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.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics