Last active
August 23, 2023 15:50
-
-
Save opus-x/f00f7f8edde5bbc35fdbc90fcf5d94a2 to your computer and use it in GitHub Desktop.
YouTube Video Downloader for Go, NodeJS, Python and Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.'); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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