diff --git a/Cargo.lock b/Cargo.lock index fe64627..1709042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -298,6 +311,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -509,7 +528,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hlsget" -version = "0.3.2" +version = "0.4.0" dependencies = [ "anyhow", "bytes", @@ -518,6 +537,7 @@ dependencies = [ "futures-util", "http", "http-body-util", + "indicatif", "log", "m3u8-rs", "reqwest", @@ -819,6 +839,19 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1002,6 +1035,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.36.5" @@ -1105,6 +1144,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "proc-macro-utils" version = "0.10.0" @@ -1704,6 +1749,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "untrusted" version = "0.9.0" @@ -1850,6 +1901,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "7.0.1" diff --git a/Cargo.toml b/Cargo.toml index 198e559..4acfbbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hlsget" -version = "0.3.2" +version = "0.4.0" edition = "2021" [dependencies] @@ -11,6 +11,7 @@ env_logger = "0.11" futures-util = "0.3" http = "1.2" http-body-util = "0.1" +indicatif = "0.17" log = "0.4" m3u8-rs = "6.0" reqwest = "0.12" diff --git a/src/client.rs b/src/client.rs index 51cac4d..b8a9dc9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -17,6 +17,7 @@ use http::{ Uri, }; use http_body_util::BodyDataStream; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use log::{debug, error}; use reqwest::{redirect::Policy, Body}; use tokio::{fs::File, io::AsyncWriteExt as _, time::timeout}; @@ -54,6 +55,7 @@ pub(super) struct Client { client: HttpClient, default_headers: HeaderMap, body_timeout: Option, + progress: Option, } @@ -93,6 +95,7 @@ impl Client { header::USER_AGENT, HeaderValue::from_str(&( crate_name!().to_string() + "/" + crate_version!() )).unwrap() ); + let progress = None; Ok(Self { buffer , rate_limit @@ -100,7 +103,8 @@ impl Client { , timeout , client , default_headers - , body_timeout }) + , body_timeout + , progress }) } fn build_http_client( buffer: usize @@ -133,6 +137,11 @@ impl Client { }; } + pub(super) fn init_progress(mut self) -> Self { + self.progress = Some(MultiProgress::new()); + self + } + pub(super) fn set_body_timeout(mut self, timeout: Option) -> Self { self.body_timeout = timeout; self @@ -185,13 +194,15 @@ impl Client { , header::CONTENT_TYPE ) . or(Some("unknown".into())); - if let Some(content_length) = content_length { + let content_length = if let Some(content_length) = content_length { if from != 0 && content_length - 1 <= from { return Ok(DownloadState::Done { content_type, size: from as usize }); } + content_length } else { from = 0; - } + 0 + }; // - do the neccessry request. let headers = &mut headers.clone(); @@ -203,8 +214,11 @@ impl Client { // - open or create file let file = util::open_or_create(&response.status(), &filename).await; // - download Data - self.clone().store_body( file + let filename = filename.as_ref().as_os_str().to_string_lossy().to_string(); + self.clone().store_body( &filename + , file , from as usize + , content_length , content_type , response.body_mut() ).await } @@ -263,8 +277,10 @@ impl Client { } async fn store_body( self + , filename: &str , mut file: File , mut size: usize + , content_length: u64 , content_type: Option , body: &mut ClientBody ) -> DownloadResult { let mut body = BodyDataStream::new(body); @@ -272,12 +288,31 @@ impl Client { content_type: content_type.clone(), size }; + let bar_style = format!( "{} {{bar:40.cyan/blue}} {{decimal_bytes:>7}}/{{decimal_total_bytes:7}}" + , filename); + let bar_style = ProgressStyle::with_template(&bar_style) + . ok() + . map(|s| s.progress_chars("#=-")); + let bar = if let Some(bar_style) = bar_style { + self.progress.as_ref().map(|progress| { + progress.add( ProgressBar::new(content_length) + . with_style(bar_style) + . with_position(size as u64) ) + }) + } else { + None + }; loop { let data_future = body.next(); let data = if let Some(io_timeout) = self.body_timeout { // give timeout somehow... probably from client. timeout(io_timeout, data_future).await.map_err(|e| { + if let Some(bar) = &bar { + if let Some(progress) = &self.progress { + progress.remove(bar); + } + } error::DownloadError::from(e).set_state(&state) })? } else { @@ -286,10 +321,19 @@ impl Client { match data { None => break, - Some(Err(e)) => - Err(error::DownloadError::from(e).set_state(&state))?, + Some(Err(e)) => { + if let Some(bar) = &bar { + if let Some(progress) = &self.progress { + progress.remove(bar); + } + } + Err(error::DownloadError::from(e).set_state(&state))? + }, Some(Ok(data)) => { size += data.len(); + if let Some(bar) = &bar { + bar.set_position(size as u64); + } state = DownloadState::Partial { content_type: content_type.clone(), size diff --git a/src/m3u8_download.rs b/src/m3u8_download.rs index 6023e36..4ba2b56 100644 --- a/src/m3u8_download.rs +++ b/src/m3u8_download.rs @@ -147,11 +147,11 @@ impl M3u8Download { if waits.is_empty() { break } else { - info!("All {} tasks wait for unavailable service", waits.len()); + eprintln!("All {} tasks wait for unavailable service", waits.len()); let pause_time = waits . into_iter() . fold(self.time_wait, |a, w| w.min(a)); - info!("Sleep for {:?}", pause_time); + eprintln!("Sleep for {:?}", pause_time); sleep(pause_time).await; } } else { diff --git a/src/main.rs b/src/main.rs index ecfb649..fc0e555 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,6 +118,12 @@ async fn main() -> anyhow::Result<()> { client }; + let client = if log_level == "error" { + client.init_progress() + } else { + client + }; + let actor = ClientActorHandle::new(client, args.buffer + args.concurrency); info!("Get segments...");