From 9487dd3dbffd4646713ce4e0ec1d6210fc1a1f14 Mon Sep 17 00:00:00 2001 From: bytedream Date: Fri, 8 Dec 2023 22:27:12 +0100 Subject: [PATCH] Show ffmpeg progress (#270) --- Cargo.lock | 1 + crunchy-cli-core/Cargo.toml | 5 +- crunchy-cli-core/src/utils/download.rs | 142 +++++++++++++++++++------ crunchy-cli-core/src/utils/os.rs | 60 +++++++++++ 4 files changed, 176 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 897731f..fa5723f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,7 @@ dependencies = [ "indicatif", "lazy_static", "log", + "nix", "num_cpus", "regex", "reqwest", diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index 5771888..4f0912f 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -34,8 +34,11 @@ serde_plain = "1.0" shlex = "1.2" sys-locale = "0.3" tempfile = "3.8" -tokio = { version = "1.34", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.34", features = ["io-util", "macros", "net", "rt-multi-thread", "time"] } rustls-native-certs = { version = "0.6", optional = true } +[target.'cfg(not(target_os = "windows"))'.dependencies] +nix = { version = "0.27", features = ["fs"] } + [build-dependencies] chrono = "0.4" diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index de1aed0..c154103 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -1,16 +1,14 @@ use crate::utils::context::Context; use crate::utils::ffmpeg::FFmpegPreset; -use crate::utils::log::progress; -use crate::utils::os::{is_special_file, temp_directory, tempfile}; +use crate::utils::os::{is_special_file, temp_directory, temp_named_pipe, tempfile}; use anyhow::{bail, Result}; use chrono::NaiveTime; use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment}; use crunchyroll_rs::Locale; -use indicatif::{ProgressBar, ProgressFinish, ProgressStyle}; +use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle}; use log::{debug, warn, LevelFilter}; use regex::Regex; -use std::borrow::Borrow; -use std::borrow::BorrowMut; +use std::borrow::{Borrow, BorrowMut}; use std::cmp::Ordering; use std::collections::BTreeMap; use std::env; @@ -21,6 +19,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tempfile::TempPath; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::Mutex; use tokio::task::JoinSet; @@ -177,15 +176,19 @@ impl Downloader { let mut videos = vec![]; let mut audios = vec![]; let mut subtitles = vec![]; + let mut max_frames = 0f64; + let fmt_space = self + .formats + .iter() + .flat_map(|f| { + f.audios + .iter() + .map(|(_, locale)| format!("Downloading {} audio", locale).len()) + }) + .max() + .unwrap(); for (i, format) in self.formats.iter().enumerate() { - let fmt_space = format - .audios - .iter() - .map(|(_, locale)| format!("Downloading {} audio", locale).len()) - .max() - .unwrap(); - let video_path = self .download_video( ctx, @@ -232,7 +235,11 @@ impl Downloader { None }; - let len = get_video_length(&video_path)?; + let (len, fps) = get_video_stats(&video_path)?; + let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps; + if frames > max_frames { + max_frames = frames; + } for (subtitle, not_cc) in format.subtitles.iter() { if let Some(pb) = &progress_spinner { let mut progress_message = pb.message(); @@ -337,8 +344,14 @@ impl Downloader { } let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args(); + let fifo = temp_named_pipe()?; - let mut command_args = vec!["-y".to_string(), "-hide_banner".to_string()]; + let mut command_args = vec![ + "-y".to_string(), + "-hide_banner".to_string(), + "-vstats_file".to_string(), + fifo.name(), + ]; command_args.extend(input_presets); command_args.extend(input); command_args.extend(maps); @@ -433,8 +446,6 @@ impl Downloader { } } - let progress_handler = progress!("Generating output file"); - let ffmpeg = Command::new("ffmpeg") // pass ffmpeg stdout to real stdout only if output file is stdout .stdout(if dst.to_str().unwrap() == "-" { @@ -444,14 +455,26 @@ impl Downloader { }) .stderr(Stdio::piped()) .args(command_args) - .output()?; - if !ffmpeg.status.success() { - bail!("{}", String::from_utf8_lossy(ffmpeg.stderr.as_slice())) + .spawn()?; + let ffmpeg_progress = tokio::spawn(async move { + ffmpeg_progress( + max_frames as u64, + fifo, + format!("{:<1$}", "Generating output file", fmt_space + 1), + ) + .await + }); + + let result = ffmpeg.wait_with_output()?; + if !result.status.success() { + bail!("{}", String::from_utf8_lossy(result.stderr.as_slice())) + } + ffmpeg_progress.abort(); + match ffmpeg_progress.await { + Ok(r) => Ok(r?), + Err(e) if e.is_cancelled() => Ok(()), + Err(e) => Err(anyhow::Error::from(e)), } - - progress_handler.stop("Output file generated"); - - Ok(()) } async fn check_free_space( @@ -752,13 +775,10 @@ fn estimate_variant_file_size(variant_data: &VariantData, segments: &[VariantSeg (variant_data.bandwidth / 8) * segments.iter().map(|s| s.length.as_secs()).sum::() } -/// Get the length of a video. This is required because sometimes subtitles have an unnecessary entry -/// long after the actual video ends with artificially extends the video length on some video players. -/// To prevent this, the video length must be hard set. See -/// [crunchy-labs/crunchy-cli#32](https://github.com/crunchy-labs/crunchy-cli/issues/32) for more -/// information. -pub fn get_video_length(path: &Path) -> Result { +/// Get the length and fps of a video. +pub fn get_video_stats(path: &Path) -> Result<(NaiveTime, f64)> { let video_length = Regex::new(r"Duration:\s(?P