From a456da66a46c203328420b9f89dce64ce5131fa3 Mon Sep 17 00:00:00 2001 From: mykola2312 Date: Sun, 31 Mar 2024 17:41:07 +0300 Subject: [PATCH] begin working on proper stdout / stderr piping because we need it for yt-dlp file output and proper stderr parsing on the fly (for progress bars in future) --- src/dl/spawn.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ src/dl/yt_dlp.rs | 13 ++++++--- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/dl/spawn.rs b/src/dl/spawn.rs index fa4b61c..c136f15 100644 --- a/src/dl/spawn.rs +++ b/src/dl/spawn.rs @@ -1,12 +1,18 @@ use core::fmt; use std::process::Output; use std::str::Utf8Error; +use std::{fs::OpenOptions, process::Stdio}; +use tokio::io::AsyncReadExt; use tokio::process::Command; use tracing::{event, Level}; +use super::tmpfile::TmpFile; + #[derive(Debug)] pub enum SpawnError { CommandError(std::io::Error), + NoStdErr, + PipeError(std::io::Error), UtfError(Utf8Error), ErrorMessage(String), } @@ -28,6 +34,8 @@ impl fmt::Display for SpawnError { use SpawnError as FE; match self { FE::CommandError(e) => write!(f, "Command::new - {}", e), + FE::NoStdErr => write!(f, "spawned process has closed stderr!"), + FE::PipeError(e) => write!(f, "pipe error - {}", e), FE::UtfError(_) => write!(f, "Error while decoding UTF8"), FE::ErrorMessage(msg) => write!(f, "ffmpeg error - {}", msg), } @@ -51,3 +59,67 @@ pub async fn spawn(program: &str, args: &[&str]) -> Result { Ok(output) } + +pub async fn spawn_pipe( + program: &str, + args: &[&str], + output_file: &TmpFile, +) -> Result<(), SpawnError> { + { + let cmd_args = args.join(" "); + event!(Level::INFO, "{} {}", program, cmd_args); + } + + let output_file = OpenOptions::new() + .create(true) + .write(true) + .open(&output_file.path) + .map_err(|e| SpawnError::PipeError(e))?; + + let mut process = Command::new(program) + .args(args) + .stdout(output_file) + .stderr(Stdio::piped()) + .spawn()?; + let mut stderr = process.stderr.take().ok_or(SpawnError::NoStdErr)?; + + let result = process.wait().await?; + + if !result.success() { + let mut data: Vec = Vec::new(); + stderr.read_to_end(&mut data).await?; + + let message = std::str::from_utf8(&data)?; + return Err(SpawnError::ErrorMessage(message.to_string())); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::dl::spawn::{spawn_pipe, SpawnError}; + use crate::dl::tmpfile::TmpFile; + + #[tokio::test] + async fn test_spawn_pipe() { + let stdout = TmpFile::new("stdout.test").unwrap(); + let result = spawn_pipe( + "python", + &[ + "-c", + "import sys; print(file=sys.stderr, 'stderr test'); sys.exit(1)", + ], + &stdout, + ) + .await; + + assert_eq!(true, result.is_err()); + if let Err(e) = result { + match e { + SpawnError::ErrorMessage(msg) => assert_eq!("stderr test", msg), + _ => panic!("SpawnError is not ErrorMessage!") + } + } + } +} \ No newline at end of file diff --git a/src/dl/yt_dlp.rs b/src/dl/yt_dlp.rs index ae8495a..f76d7b0 100644 --- a/src/dl/yt_dlp.rs +++ b/src/dl/yt_dlp.rs @@ -1,4 +1,4 @@ -use super::spawn::{spawn, SpawnError}; +use super::spawn::{spawn, spawn_pipe, SpawnError}; use super::tmpfile::{TmpFile, TmpFileError}; use core::fmt; use ordered_float::OrderedFloat; @@ -254,21 +254,26 @@ impl YtDlp { } pub async fn download(url: &str, info: &YtDlpInfo) -> Result { - let file = TmpFile::new(&format!("{}.bin", info.id))?; + let file = TmpFile::new(&info.id)?; - spawn( + // since yt-dlp tend to randomly choose filename we can't rely on it, + // and instead output to stdout and then pipe to our file + // that way we can avoid bugs related to filename confusion + let output = spawn_pipe( "python", &[ "-m", "yt_dlp", url, "-o", - &file.path, + "-", "--force-overwrites", "--no-exec", ], + &file, ) .await?; + dbg!(output); match file.exists() { true => Ok(file),