Skip to content

Instantly share code, notes, and snippets.

@VirtualPirate
Created November 7, 2024 13:00
Show Gist options
  • Save VirtualPirate/bbaaf2b8d34991b6c7ad8e3694bb6dca to your computer and use it in GitHub Desktop.
Save VirtualPirate/bbaaf2b8d34991b6c7ad8e3694bb6dca to your computer and use it in GitHub Desktop.
This is a rust program that downloads a file in multi segments. Making multiple connections to the url, download all the segments in parallel and merge the downloaded segments to the final file
[package]
name = "multipart-downloader"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.12.9", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
futures = "0.3"
use futures::future::join_all;
use futures::TryStreamExt;
use std::path::Path;
use tokio::fs as async_fs;
use tokio::io::{AsyncWriteExt, BufWriter};
struct DownloadSegment {
start: u64,
end: u64,
index: usize,
}
async fn get_file_size(url: &str) -> Result<u64, reqwest::Error> {
let client = reqwest::Client::new();
let response = client.head(url).send().await?;
let content_length = response
.headers()
.get(reqwest::header::CONTENT_LENGTH)
.and_then(|val| val.to_str().ok())
.and_then(|val| val.parse::<u64>().ok())
.unwrap_or(0);
Ok(content_length)
}
async fn download_segment(
url: &str,
segment: DownloadSegment,
output_dir: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let DownloadSegment { start, end, index } = segment;
println!("Segment Download started start: {} end: {}", start, end);
let output_path = format!("{}/part_{}", output_dir, index);
let mut file = BufWriter::new(async_fs::File::create(&output_path).await?);
let client = reqwest::Client::new();
let range_header = format!("bytes={}-{}", start, end);
let response = client.get(url).header("Range", range_header).send().await?;
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.try_next().await? {
file.write_all(&chunk).await?;
}
file.flush().await?;
println!("Downloaded segment {} (bytes {}-{})", index, start, end);
Ok(output_path)
}
async fn download_file(
url: &str,
segments: usize,
output_file_path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let file_size = get_file_size(url).await?;
let segment_size = (file_size as f64 / segments as f64).ceil() as u64;
let output_dir = Path::new(output_file_path).parent().unwrap();
let mut segment_paths = Vec::new();
let mut segment_futures = Vec::new();
for index in 0..segments {
let start = index as u64 * segment_size;
let end = if index == segments - 1 {
file_size - 1
} else {
start + segment_size - 1
};
let segment = DownloadSegment { start, end, index };
let url = url.to_string();
let output_dir = output_dir.to_str().unwrap().to_string();
segment_futures.push(async move { download_segment(&url, segment, &output_dir).await });
}
let results = join_all(segment_futures).await;
for result in results {
match result {
Ok(path) => segment_paths.push(path),
Err(e) => return Err(e),
}
}
// Merge all parts
segment_paths.sort();
let mut write_stream = async_fs::File::create(output_file_path).await?;
for segment_path in segment_paths {
let data = async_fs::read(&segment_path).await?;
write_stream.write_all(&data).await?;
async_fs::remove_file(segment_path).await?; // Delete segment after merging
}
println!("Download complete: {}", output_file_path);
Ok(())
}
#[tokio::main]
async fn main() {
let url = "https://rr4---sn-qxaeenl6.googlevideo.com/videoplayback?expire=1731000837&ei=pKUsZ_OXNpKJ9fwP3NO2kAc&ip=34.96.33.87&id=o-AKkWUuNwaTJIgO01fTpJnQs7F2_458w2KqLEX4xhDQjF&itag=137&source=youtube&requiressl=yes&xpc=EgVo2aDSNQ%3D%3D&met=1730979237%2C&mh=DQ&mm=31%2C29&mn=sn-qxaeenl6%2Csn-qxaelned&ms=au%2Crdu&mv=u&mvi=4&pl=27&rms=au%2Cau&vprv=1&svpuc=1&mime=video%2Fmp4&rqh=1&gir=yes&clen=101803581&dur=635.720&lmt=1726430152436097&mt=1730979090&fvip=2&keepalive=yes&fexp=51312688%2C51326932&c=IOS&txp=5535434&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cvprv%2Csvpuc%2Cmime%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&sig=AJfQdSswRAIgWLZzJ20EWzch7xywvZuuRRk5sxubZli9CNkRIRMx6QICIENKy_k_PjSWKYVF0tV7eSyzbEIlebNcA4qGkMqepbKN&lsparams=met%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Crms&lsig=ACJ0pHgwRAIgC3OHLLVbbxFtUgic47v2ENYwkJYbSt0lrNWpIveaHOQCIF4wLMfSZmaCmYPrytRux6dBgRpCo_Cqdq3riZNNl-am";
let output_file_path = Path::new("./largefile.mp4");
match download_file(url, 10, output_file_path.to_str().unwrap()).await {
Ok(_) => println!("File downloaded successfully"),
Err(error) => eprintln!("Download failed: {:?}", error),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment