Recently, I just learnt Rust and using it to write a simple youtube downloader with reference from node-ytdl. In this blog, I would like to share the code and how did I make it. You can find the full source code here.
Add these dependencies to
Install development environment
I am using Windows 10 andscoop
package manager. Therefore, I use the following commands.
- Run
scoop install rustup-msvc
to installrustup
. - Run
setx "%path%;%USERPROFILE%\\scoop\\persist\\rustup\\.cargo\\bin"
to addrustup
to the path. - Restart termial (git-bash in my case) and check the installation with
rustup --version; rustc --version; cargo --version
- Export custom
RUST_HOME
:export RUSTUP_HOME=$HOME/scoop/persist/rustup/.cargo/bin/rustup
- Install a toolchain for
rustup
:rustup toolchain install stable-x86_64-pc-windows-msvc
Setup project
Runcargo new simple_rust_youtube_downloader --bin && cd simple_rust_youtube_downloader
to create and navigate to the project.
Add these dependencies to Cargo.toml file
... [dependencies] reqwest = { version = "0.11.2", features = ["blocking", "json"] } url = { version = "2.2.0" } serde_urlencoded = { version = "0.7.0" } json = { version = "0.12.4" }
reqwest
to make http requests.url and serde_urlencoded
to interact with urls.json
is not the most popular json library but I found it very easy to use.
Code and comments
use std::{collections::HashMap, fs::File, io::prelude::Write, process::Command, time::Duration}; use url::Url; fn main() {//Set the time out long enough to download a video. let client = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(10 * 60)) .build() .unwrap();//Get first argument from command line as input either a video link or a video id. let mut input = std::env::args().nth(1).expect("no vid id given"); let mut video_id = "".to_owned();//Check if the the input contains http/https => link else => id. if (&input).starts_with("https://") || (&input).starts_with("http://") { match Url::parse(&input) { Ok(parsed_url) => { let hash_query: HashMap<_ _=""> = parsed_url.query_pairs().into_owned().collect(); video_id = hash_query.get("v").unwrap().as_str().to_owned(); } Err(e) => eprintln!("{:?}", e), } } else { video_id = input; } let info_url = format!("https://youtube.com/get_video_info?video_id={}&html5=1", video_id); match client.get(&info_url).send().unwrap().text() { Ok(v) => { let resp_map = serde_urlencoded::from_str::>(&v).unwrap() as HashMap ; let json_player_resp = json::parse(&resp_map.get:: (&"player_response").unwrap() as &str).unwrap(); let streaming_data = &json_player_resp["streamingData"]["formats"]; let video_details = &json_player_resp["videoDetails"]; println!("video_details: {}", &video_details["title"].to_string()); let file_name = format!("{}.mp4", &video_details["title"].to_string()); let normalized_file_name: String = format!( "{}", file_name .chars() .map(|x| match x { ':' => '-', '"' => ' ', '/' => ' ', '\\' => ' ', '*' => ' ', '?' => ' ', '<' => ' ', '>' => ' ', '|' => ' ', _ => x, }) .collect:: () ); println!("normalized_file_name: {}", &normalized_file_name); if std::path::Path::new(&normalized_file_name).exists() { println!("file exist"); return; } let mut file = File::create(&normalized_file_name).unwrap(); match (&streaming_data)[streaming_data.len() - 1]["url"].as_str() { Some(url) => { println!("streaming_data: {:?}", &url); let downloaded_video = client.get(url).send().unwrap().bytes().unwrap(); file.write_all(&downloaded_video).unwrap(); } None => { download_ciphered_video(&normalized_file_name, &video_id); } } } Err(e) => println!("error parsing header: {:?}", e), } } //Function to use node package ytdl to download ciphered video link. fn download_ciphered_video(normalize_file_name: &str, video_id: &str) { Command::new("node") .arg("node_modules/ytdl/bin/ytdl.js") .arg(video_id) .arg("-o") .arg(normalize_file_name) .arg("-q") .arg("22") .output() .expect("failed to execute process"); }
The above code will:
- Receive input as video link or video id.
- Fetch the information from
info_url
"https://youtube.com/get_video_info?video_id=<video_id>" - Choose to download
.mp4
format to a file with file name being extracted from the title of the video. If the file is already exist then the program will exit without doing anything. - This project will only handle the easy path to download youtube video. For the more complicated case, I am using
node-ytdl
. Therefore, there is a need to runnpm install ytdl to the folder where the executable program built.
- Run test project
npm install ytdl && cargo run -- https://www.youtube.com/watch?v=EToeZxIPdKg
This is my "hello world" project in rust. I will need to learn more about idiomatic rust. So far, my feeling working with rust is that it is strict and easy. It is easy because the compiler print very details error messages so that I can search and fix those errors. The downside, in my opinion, is the very slow compile duration. I think rust is fun and worth learning more.
Reference:
node youtube download https://github.com/fent/node-ytdl
- ninjahoahong
Comments
Post a Comment