use crate::config::Config;
use anyhow::{Context, Result};
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, COOKIE, RANGE};
use std::fs;
use std::path::Path;
use tokio::io::AsyncWriteExt;
pub async fn download_video(url: &str, config: &Config) -> Result<()> {
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::custom(|attempt| {
attempt.follow()
}))
.build()?;
let mut headers = HeaderMap::new();
for (k, v) in &config.http.headers {
headers.insert(
HeaderName::from_bytes(k.as_bytes())?,
HeaderValue::from_str(v)?,
);
}
if !config.http.cookie.is_empty() {
headers.insert(COOKIE, HeaderValue::from_str(&config.http.cookie)?);
}
headers.insert(RANGE, HeaderValue::from_static("bytes=0-"));
let output_path = Path::new(&config.http.output_dir);
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent)?;
}
let temp_path = if let Some(file_stem) = output_path.file_stem() {
let mut temp_name = file_stem.to_string_lossy().to_string();
if let Some(extension) = output_path.extension() {
temp_name = format!("{}.part.{}", temp_name, extension.to_string_lossy());
} else {
temp_name = format!("{}.part", temp_name);
}
if let Some(parent) = output_path.parent() {
parent.join(temp_name)
} else {
Path::new(&temp_name).to_path_buf()
}
} else {
let parent = output_path.parent().unwrap_or_else(|| Path::new("."));
parent.join("video.part.mp4")
};
let resp = client
.get(url)
.headers(headers)
.send()
.await
.with_context(|| format!("Failed to GET {}", url))?;
let total_size = resp.content_length();
let pb = match total_size {
Some(size) => ProgressBar::new(size),
None => ProgressBar::new_spinner(),
};
pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
.unwrap()
.progress_chars("#>-"),
);
let mut file = tokio::fs::File::create(&temp_path).await?;
let mut stream = resp.bytes_stream();
let mut downloaded: u64 = 0;
let _chunk_size = config.http.chunk_size;
use futures_util::StreamExt;
while let Some(chunk_result) = stream.next().await {
let chunk = chunk_result?;
file.write_all(&chunk).await?;
downloaded += chunk.len() as u64;
pb.set_position(downloaded);
}
pb.finish_with_message(format!("Downloaded {} bytes", downloaded));
file.flush().await?;
drop(file);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
tokio::fs::rename(&temp_path, &output_path).await?;
Ok(())
}