mirror of
https://github.com/crunchy-labs/crunchy-cli.git
synced 2026-01-21 04:02:00 -06:00
Check for remaining disk space (#150)
This commit is contained in:
parent
baa6ca5018
commit
94df245e4e
3 changed files with 110 additions and 5 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -355,6 +355,7 @@ dependencies = [
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"derive_setters",
|
"derive_setters",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"fs2",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -675,6 +676,16 @@ version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541"
|
checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs2"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ crunchyroll-rs = { version = "0.3", features = ["dash-stream"] }
|
||||||
ctrlc = "3.2"
|
ctrlc = "3.2"
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
derive_setters = "0.1"
|
derive_setters = "0.1"
|
||||||
|
fs2 = "0.4"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
use crate::utils::context::Context;
|
use crate::utils::context::Context;
|
||||||
use crate::utils::ffmpeg::FFmpegPreset;
|
use crate::utils::ffmpeg::FFmpegPreset;
|
||||||
use crate::utils::log::progress;
|
use crate::utils::log::progress;
|
||||||
use crate::utils::os::tempfile;
|
use crate::utils::os::{is_special_file, temp_directory, tempfile};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use chrono::NaiveTime;
|
use chrono::NaiveTime;
|
||||||
use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment};
|
use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment};
|
||||||
use crunchyroll_rs::Locale;
|
use crunchyroll_rs::Locale;
|
||||||
use indicatif::{ProgressBar, ProgressFinish, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressFinish, ProgressStyle};
|
||||||
use log::{debug, LevelFilter};
|
use log::{debug, warn, LevelFilter};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::env;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::{mpsc, Arc, Mutex};
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -99,6 +100,32 @@ impl Downloader {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download(mut self, ctx: &Context, dst: &Path) -> Result<()> {
|
pub async fn download(mut self, ctx: &Context, dst: &Path) -> Result<()> {
|
||||||
|
// `.unwrap_or_default()` here unless https://doc.rust-lang.org/stable/std/path/fn.absolute.html
|
||||||
|
// gets stabilized as the function might throw error on weird file paths
|
||||||
|
let required = self.check_free_space(dst).await.unwrap_or_default();
|
||||||
|
if let Some((path, tmp_required)) = &required.0 {
|
||||||
|
let kb = (*tmp_required as f64) / 1024.0;
|
||||||
|
let mb = kb / 1024.0;
|
||||||
|
let gb = mb / 1024.0;
|
||||||
|
warn!(
|
||||||
|
"You may have not enough disk space to store temporary files. The temp directory ({}) should have at least {}{} free space",
|
||||||
|
path.to_string_lossy(),
|
||||||
|
if gb < 1.0 { mb.ceil().to_string() } else { format!("{:.2}", gb) },
|
||||||
|
if gb < 1.0 { "MB" } else { "GB" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if let Some((path, dst_required)) = &required.1 {
|
||||||
|
let kb = (*dst_required as f64) / 1024.0;
|
||||||
|
let mb = kb / 1024.0;
|
||||||
|
let gb = mb / 1024.0;
|
||||||
|
warn!(
|
||||||
|
"You may have not enough disk space to store the output file. The directory {} should have at least {}{} free space",
|
||||||
|
path.to_string_lossy(),
|
||||||
|
if gb < 1.0 { mb.ceil().to_string() } else { format!("{:.2}", gb) },
|
||||||
|
if gb < 1.0 { "MB" } else { "GB" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(audio_sort_locales) = &self.audio_sort {
|
if let Some(audio_sort_locales) = &self.audio_sort {
|
||||||
self.formats.sort_by(|a, b| {
|
self.formats.sort_by(|a, b| {
|
||||||
audio_sort_locales
|
audio_sort_locales
|
||||||
|
|
@ -335,6 +362,69 @@ impl Downloader {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn check_free_space(
|
||||||
|
&self,
|
||||||
|
dst: &Path,
|
||||||
|
) -> Result<(Option<(PathBuf, u64)>, Option<(PathBuf, u64)>)> {
|
||||||
|
let mut all_variant_data = vec![];
|
||||||
|
for format in &self.formats {
|
||||||
|
all_variant_data.push(&format.video.0);
|
||||||
|
all_variant_data.extend(format.audios.iter().map(|(a, _)| a))
|
||||||
|
}
|
||||||
|
let mut estimated_required_space: u64 = 0;
|
||||||
|
for variant_data in all_variant_data {
|
||||||
|
// nearly no overhead should be generated with this call(s) as we're using dash as
|
||||||
|
// stream provider and generating the dash segments does not need any fetching of
|
||||||
|
// additional (http) resources as hls segments would
|
||||||
|
let segments = variant_data.segments().await?;
|
||||||
|
|
||||||
|
// sum the length of all streams up
|
||||||
|
estimated_required_space += estimate_variant_file_size(variant_data, &segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tmp_stat = fs2::statvfs(temp_directory()).unwrap();
|
||||||
|
let mut dst_file = if dst.is_absolute() {
|
||||||
|
dst.to_path_buf()
|
||||||
|
} else {
|
||||||
|
env::current_dir()?.join(dst)
|
||||||
|
};
|
||||||
|
for ancestor in dst_file.ancestors() {
|
||||||
|
if ancestor.exists() {
|
||||||
|
dst_file = ancestor.to_path_buf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let dst_stat = fs2::statvfs(&dst_file).unwrap();
|
||||||
|
|
||||||
|
let mut tmp_space = tmp_stat.available_space();
|
||||||
|
let mut dst_space = dst_stat.available_space();
|
||||||
|
|
||||||
|
// this checks if the partition the two directories are located on are the same to prevent
|
||||||
|
// that the space fits both file sizes each but not together. this is done by checking the
|
||||||
|
// total space if each partition and the free space of each partition (the free space can
|
||||||
|
// differ by 10MB as some tiny I/O operations could be performed between the two calls which
|
||||||
|
// are checking the disk space)
|
||||||
|
if tmp_stat.total_space() == dst_stat.total_space()
|
||||||
|
&& (tmp_stat.available_space() as i64 - dst_stat.available_space() as i64).abs() < 10240
|
||||||
|
{
|
||||||
|
tmp_space *= 2;
|
||||||
|
dst_space *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tmp_required = None;
|
||||||
|
let mut dst_required = None;
|
||||||
|
|
||||||
|
if tmp_space < estimated_required_space {
|
||||||
|
tmp_required = Some((temp_directory(), estimated_required_space))
|
||||||
|
}
|
||||||
|
if (!is_special_file(dst) && dst.to_string_lossy() != "-")
|
||||||
|
&& dst_space < estimated_required_space
|
||||||
|
{
|
||||||
|
dst_required = Some((dst_file, estimated_required_space))
|
||||||
|
}
|
||||||
|
Ok((tmp_required, dst_required))
|
||||||
|
}
|
||||||
|
|
||||||
async fn download_video(
|
async fn download_video(
|
||||||
&self,
|
&self,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
|
@ -395,8 +485,7 @@ pub async fn download_segments(
|
||||||
let count = Arc::new(Mutex::new(0));
|
let count = Arc::new(Mutex::new(0));
|
||||||
|
|
||||||
let progress = if log::max_level() == LevelFilter::Info {
|
let progress = if log::max_level() == LevelFilter::Info {
|
||||||
let estimated_file_size =
|
let estimated_file_size = estimate_variant_file_size(variant_data, &segments);
|
||||||
(variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>();
|
|
||||||
|
|
||||||
let progress = ProgressBar::new(estimated_file_size)
|
let progress = ProgressBar::new(estimated_file_size)
|
||||||
.with_style(
|
.with_style(
|
||||||
|
|
@ -555,6 +644,10 @@ pub async fn download_segments(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn estimate_variant_file_size(variant_data: &VariantData, segments: &Vec<VariantSegment>) -> u64 {
|
||||||
|
(variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::<u64>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video
|
/// Add `ScaledBorderAndShadows: yes` to subtitles; without it they look very messy on some video
|
||||||
/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66)
|
/// players. See [crunchy-labs/crunchy-cli#66](https://github.com/crunchy-labs/crunchy-cli/issues/66)
|
||||||
/// for more information.
|
/// for more information.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue