Skip to content

Instantly share code, notes, and snippets.

@opus-x
Last active August 23, 2023 15:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save opus-x/f00f7f8edde5bbc35fdbc90fcf5d94a2 to your computer and use it in GitHub Desktop.
Save opus-x/f00f7f8edde5bbc35fdbc90fcf5d94a2 to your computer and use it in GitHub Desktop.
YouTube Video Downloader for Go, NodeJS, Python and Ruby
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
// Get the video ID from command line arguments
if len(os.Args) < 2 {
fmt.Println("Please provide a YouTube video ID.")
return
}
videoID := os.Args[1]
// Fetch the video details
videoInfoURL := fmt.Sprintf("https://www.youtube.com/get_video_info?video_id=%s", videoID)
resp, err := http.Get(videoInfoURL)
if err != nil {
fmt.Println("Failed to fetch video details:", err)
return
}
defer resp.Body.Close()
// Parse the response to get the video title and download URL
videoInfo, err := parseVideoInfo(resp.Body)
if err != nil {
fmt.Println("Failed to parse video info:", err)
return
}
// Prepare the file name
fileName := fmt.Sprintf("%s.%s", sanitizeFileName(videoInfo.title), videoInfo.extension)
// Create the file
file, err := os.Create(fileName)
if err != nil {
fmt.Println("Failed to create file:", err)
return
}
defer file.Close()
// Download the video
resp, err = http.Get(videoInfo.downloadURL)
if err != nil {
fmt.Println("Failed to download video:", err)
return
}
defer resp.Body.Close()
// Create progress bar
progress := NewProgressBar(resp.ContentLength)
// Copy the response body to the file
_, err = io.Copy(io.MultiWriter(file, progress), resp.Body)
if err != nil {
fmt.Println("Failed to save video:", err)
return
}
fmt.Println("Video downloaded successfully!")
}
// Struct to hold video info
type videoInfo struct {
title string
extension string
downloadURL string
}
// Function to parse video info from response body
func parseVideoInfo(body io.Reader) (*videoInfo, error) {
var info videoInfo
buf := make([]byte, 1024)
n, err := body.Read(buf)
if err != nil {
return nil, err
}
queryString := string(buf[:n])
params, err := parseQueryString(queryString)
if err != nil {
return nil, err
}
title, ok := params["title"]
if !ok {
return nil, fmt.Errorf("video title not found in response")
}
info.title = title
urlEncodedFmtStreamMap, ok := params["url_encoded_fmt_stream_map"]
if !ok {
return nil, fmt.Errorf("video URL not found in response")
}
streams, err := parseQueryString(urlEncodedFmtStreamMap)
if err != nil {
return nil, err
}
url, ok := streams["url"]
if !ok {
return nil, fmt.Errorf("video URL not found in response")
}
info.downloadURL = url
sig, ok := streams["sig"]
if ok {
info.downloadURL += "&signature=" + sig
}
extension, ok := streams["type"]
if !ok {
return nil, fmt.Errorf("video extension not found in response")
}
extension = strings.Split(extension, ";")[0]
extension = strings.Split(extension, "/")[1]
info.extension = extension
return &info, nil
}
// Function to parse query string into key-value pairs
func parseQueryString(queryString string) (map[string]string, error) {
params := make(map[string]string)
queryString = strings.ReplaceAll(queryString, "&", "&")
queryParts := strings.Split(queryString, "&")
for _, part := range queryParts {
keyValue := strings.SplitN(part, "=", 2)
if len(keyValue) != 2 {
return nil, fmt.Errorf("invalid query string: %s", queryString)
}
params[keyValue[0]] = keyValue[1]
}
return params, nil
}
// Function to sanitize file name
func sanitizeFileName(fileName string) string {
invalidChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"}
for _, char := range invalidChars {
fileName = strings.ReplaceAll(fileName, char, "")
}
return fileName
}
// ProgressBar struct to display download progress
type ProgressBar struct {
total int64
progress int64
}
// NewProgressBar creates a new ProgressBar
func NewProgressBar(total int64) *ProgressBar {
return &ProgressBar{
total: total,
progress: 0,
}
}
// Write updates the progress bar with the number of bytes written
func (p *ProgressBar) Write(b []byte) (n int, err error) {
n = len(b)
p.progress += int64(n)
p.display()
return
}
// display prints the current progress bar status
func (p *ProgressBar) display() {
progress := int(float64(p.progress) / float64(p.total) * 100)
fmt.Printf("\rDownloading... %d%%", progress)
if progress == 100 {
fmt.Println()
}
}
const fs = require('fs');
const ytdl = require('ytdl-core');
const ProgressBar = require('progress');
const videoId = process.argv[2];
async function downloadVideo(videoId) {
try {
const videoInfo = await ytdl.getInfo(videoId);
const videoTitle = videoInfo.videoDetails.title;
const videoFormat = videoInfo.formats.find(format => format.hasVideo && format.hasAudio);
const progressBar = new ProgressBar('Downloading [:bar] :percent :etas', {
total: videoFormat.contentLength,
width: 30,
complete: '=',
incomplete: ' ',
});
const videoStream = ytdl(videoId, { format: videoFormat });
const writeStream = fs.createWriteStream(`${videoTitle}.${videoFormat.itag}.mp4`);
videoStream.on('data', (chunk) => {
progressBar.tick(chunk.length);
});
videoStream.pipe(writeStream);
writeStream.on('finish', () => {
console.log('Video downloaded successfully!');
});
writeStream.on('error', (error) => {
console.error('Error while downloading the video:', error);
});
} catch (error) {
console.error('Error:', error);
}
}
if (videoId) {
downloadVideo(videoId);
} else {
console.error('Please provide a video ID as an argument.');
}
import os
import sys
import urllib.request
from pytube import YouTube
from tqdm import tqdm
def download_video(video_id):
# Get YouTube video details
video_url = f"https://www.youtube.com/watch?v={video_id}"
video = YouTube(video_url)
# Get video title and extension
title = video.title
extension = video.streams.get_highest_resolution().mime_type.split("/")[-1]
# Create a progress bar
def progress_bar(chunk, file_handle, bytes_remaining):
file_size = video.streams.get_highest_resolution().filesize
progress = (1 - bytes_remaining / file_size) * 100
pbar.update(progress - pbar.n)
# Download the video
file_path = f"{title}.{extension}"
with tqdm(total=100, unit="%", ncols=60, bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}") as pbar:
video.register_on_progress_callback(progress_bar)
video.streams.get_highest_resolution().download(output_path=os.getcwd(), filename=title)
print(f"Video saved as: {file_path}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Please provide the video ID as an argument.")
sys.exit(1)
video_id = sys.argv[1]
download_video(video_id)
require 'open3'
def download_youtube_video(video_id)
command = "youtube-dl -o '%(title)s.%(ext)s' -q --no-warnings --newline https://www.youtube.com/watch?v=#{video_id}"
output = ''
progress = ''
Open3.popen3(command) do |_stdin, stdout, stderr, wait_thr|
while (line = stdout.gets)
if line.start_with?('[download]')
progress = line.scan(/\d+\.\d+/)[0].to_f
output += line
end
print_progress(progress) if progress != ''
end
output += stderr.read
exit_status = wait_thr.value
puts output if exit_status != 0
end
end
def print_progress(progress)
bar_length = 30
filled_length = (bar_length * progress).to_i
empty_length = bar_length - filled_length
print "\r["
print '=' * filled_length
print ' ' * empty_length
print "] #{(progress * 100).to_i}%"
end
video_id = ARGV[0]
download_youtube_video(video_id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment