Browse Source

Add progress bars for ts downloads

main v0.4.0
Georg Hopp 11 months ago
parent
commit
9dbcdeaaee
Signed by: ghopp GPG Key ID: 4C5D226768784538
  1. 63
      Cargo.lock
  2. 3
      Cargo.toml
  3. 56
      src/client.rs
  4. 4
      src/m3u8_download.rs
  5. 6
      src/main.rs

63
Cargo.lock

@ -256,6 +256,19 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -298,6 +311,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -509,7 +528,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "hlsget" name = "hlsget"
version = "0.3.2"
version = "0.4.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -518,6 +537,7 @@ dependencies = [
"futures-util", "futures-util",
"http", "http",
"http-body-util", "http-body-util",
"indicatif",
"log", "log",
"m3u8-rs", "m3u8-rs",
"reqwest", "reqwest",
@ -819,6 +839,19 @@ dependencies = [
"hashbrown", "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]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.10.1" version = "2.10.1"
@ -1002,6 +1035,12 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.5" version = "0.36.5"
@ -1105,6 +1144,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "portable-atomic"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]] [[package]]
name = "proc-macro-utils" name = "proc-macro-utils"
version = "0.10.0" version = "0.10.0"
@ -1704,6 +1749,12 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@ -1850,6 +1901,16 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "which" name = "which"
version = "7.0.1" version = "7.0.1"

3
Cargo.toml

@ -1,6 +1,6 @@
[package] [package]
name = "hlsget" name = "hlsget"
version = "0.3.2"
version = "0.4.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
@ -11,6 +11,7 @@ env_logger = "0.11"
futures-util = "0.3" futures-util = "0.3"
http = "1.2" http = "1.2"
http-body-util = "0.1" http-body-util = "0.1"
indicatif = "0.17"
log = "0.4" log = "0.4"
m3u8-rs = "6.0" m3u8-rs = "6.0"
reqwest = "0.12" reqwest = "0.12"

56
src/client.rs

@ -17,6 +17,7 @@ use http::{
Uri, Uri,
}; };
use http_body_util::BodyDataStream; use http_body_util::BodyDataStream;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use log::{debug, error}; use log::{debug, error};
use reqwest::{redirect::Policy, Body}; use reqwest::{redirect::Policy, Body};
use tokio::{fs::File, io::AsyncWriteExt as _, time::timeout}; use tokio::{fs::File, io::AsyncWriteExt as _, time::timeout};
@ -54,6 +55,7 @@ pub(super) struct Client {
client: HttpClient, client: HttpClient,
default_headers: HeaderMap, default_headers: HeaderMap,
body_timeout: Option<Duration>, body_timeout: Option<Duration>,
progress: Option<MultiProgress>,
} }
@ -93,6 +95,7 @@ impl Client {
header::USER_AGENT, header::USER_AGENT,
HeaderValue::from_str(&( crate_name!().to_string() + "/" HeaderValue::from_str(&( crate_name!().to_string() + "/"
+ crate_version!() )).unwrap() ); + crate_version!() )).unwrap() );
let progress = None;
Ok(Self { buffer Ok(Self { buffer
, rate_limit , rate_limit
@ -100,7 +103,8 @@ impl Client {
, timeout , timeout
, client , client
, default_headers , default_headers
, body_timeout })
, body_timeout
, progress })
} }
fn build_http_client( buffer: usize 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<Duration>) -> Self { pub(super) fn set_body_timeout(mut self, timeout: Option<Duration>) -> Self {
self.body_timeout = timeout; self.body_timeout = timeout;
self self
@ -185,13 +194,15 @@ impl Client {
, header::CONTENT_TYPE ) , header::CONTENT_TYPE )
. or(Some("unknown".into())); . 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 { if from != 0 && content_length - 1 <= from {
return Ok(DownloadState::Done { content_type, size: from as usize }); return Ok(DownloadState::Done { content_type, size: from as usize });
} }
content_length
} else { } else {
from = 0; from = 0;
}
0
};
// - do the neccessry request. // - do the neccessry request.
let headers = &mut headers.clone(); let headers = &mut headers.clone();
@ -203,8 +214,11 @@ impl Client {
// - open or create file // - open or create file
let file = util::open_or_create(&response.status(), &filename).await; let file = util::open_or_create(&response.status(), &filename).await;
// - download Data // - 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 , from as usize
, content_length
, content_type , content_type
, response.body_mut() ).await , response.body_mut() ).await
} }
@ -263,8 +277,10 @@ impl Client {
} }
async fn store_body( self async fn store_body( self
, filename: &str
, mut file: File , mut file: File
, mut size: usize , mut size: usize
, content_length: u64
, content_type: Option<String> , content_type: Option<String>
, body: &mut ClientBody ) -> DownloadResult { , body: &mut ClientBody ) -> DownloadResult {
let mut body = BodyDataStream::new(body); let mut body = BodyDataStream::new(body);
@ -272,12 +288,31 @@ impl Client {
content_type: content_type.clone(), content_type: content_type.clone(),
size 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 { loop {
let data_future = body.next(); let data_future = body.next();
let data = if let Some(io_timeout) = self.body_timeout { let data = if let Some(io_timeout) = self.body_timeout {
// give timeout somehow... probably from client. // give timeout somehow... probably from client.
timeout(io_timeout, data_future).await.map_err(|e| { 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) error::DownloadError::from(e).set_state(&state)
})? })?
} else { } else {
@ -286,10 +321,19 @@ impl Client {
match data { match data {
None => break, 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)) => { Some(Ok(data)) => {
size += data.len(); size += data.len();
if let Some(bar) = &bar {
bar.set_position(size as u64);
}
state = DownloadState::Partial { state = DownloadState::Partial {
content_type: content_type.clone(), content_type: content_type.clone(),
size size

4
src/m3u8_download.rs

@ -147,11 +147,11 @@ impl M3u8Download {
if waits.is_empty() { if waits.is_empty() {
break break
} else { } else {
info!("All {} tasks wait for unavailable service", waits.len());
eprintln!("All {} tasks wait for unavailable service", waits.len());
let pause_time = waits let pause_time = waits
. into_iter() . into_iter()
. fold(self.time_wait, |a, w| w.min(a)); . fold(self.time_wait, |a, w| w.min(a));
info!("Sleep for {:?}", pause_time);
eprintln!("Sleep for {:?}", pause_time);
sleep(pause_time).await; sleep(pause_time).await;
} }
} else { } else {

6
src/main.rs

@ -118,6 +118,12 @@ async fn main() -> anyhow::Result<()> {
client client
}; };
let client = if log_level == "error" {
client.init_progress()
} else {
client
};
let actor = ClientActorHandle::new(client, args.buffer + args.concurrency); let actor = ClientActorHandle::new(client, args.buffer + args.concurrency);
info!("Get segments..."); info!("Get segments...");

Loading…
Cancel
Save