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)

This commit is contained in:
mykola2312 2024-03-31 17:41:07 +03:00
parent 6ac64055cc
commit a456da66a4
2 changed files with 81 additions and 4 deletions

View file

@ -1,12 +1,18 @@
use core::fmt; use core::fmt;
use std::process::Output; use std::process::Output;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::{fs::OpenOptions, process::Stdio};
use tokio::io::AsyncReadExt;
use tokio::process::Command; use tokio::process::Command;
use tracing::{event, Level}; use tracing::{event, Level};
use super::tmpfile::TmpFile;
#[derive(Debug)] #[derive(Debug)]
pub enum SpawnError { pub enum SpawnError {
CommandError(std::io::Error), CommandError(std::io::Error),
NoStdErr,
PipeError(std::io::Error),
UtfError(Utf8Error), UtfError(Utf8Error),
ErrorMessage(String), ErrorMessage(String),
} }
@ -28,6 +34,8 @@ impl fmt::Display for SpawnError {
use SpawnError as FE; use SpawnError as FE;
match self { match self {
FE::CommandError(e) => write!(f, "Command::new - {}", e), 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::UtfError(_) => write!(f, "Error while decoding UTF8"),
FE::ErrorMessage(msg) => write!(f, "ffmpeg error - {}", msg), FE::ErrorMessage(msg) => write!(f, "ffmpeg error - {}", msg),
} }
@ -51,3 +59,67 @@ pub async fn spawn(program: &str, args: &[&str]) -> Result<Output, SpawnError> {
Ok(output) 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<u8> = 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!")
}
}
}
}

View file

@ -1,4 +1,4 @@
use super::spawn::{spawn, SpawnError}; use super::spawn::{spawn, spawn_pipe, SpawnError};
use super::tmpfile::{TmpFile, TmpFileError}; use super::tmpfile::{TmpFile, TmpFileError};
use core::fmt; use core::fmt;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
@ -254,21 +254,26 @@ impl YtDlp {
} }
pub async fn download(url: &str, info: &YtDlpInfo) -> Result<TmpFile, YtDlpError> { pub async fn download(url: &str, info: &YtDlpInfo) -> Result<TmpFile, YtDlpError> {
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", "python",
&[ &[
"-m", "-m",
"yt_dlp", "yt_dlp",
url, url,
"-o", "-o",
&file.path, "-",
"--force-overwrites", "--force-overwrites",
"--no-exec", "--no-exec",
], ],
&file,
) )
.await?; .await?;
dbg!(output);
match file.exists() { match file.exists() {
true => Ok(file), true => Ok(file),