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 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<Output, SpawnError> {
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 core::fmt;
use ordered_float::OrderedFloat;
@ -254,21 +254,26 @@ impl YtDlp {
}
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",
&[
"-m",
"yt_dlp",
url,
"-o",
&file.path,
"-",
"--force-overwrites",
"--no-exec",
],
&file,
)
.await?;
dbg!(output);
match file.exists() {
true => Ok(file),