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:
parent
6ac64055cc
commit
a456da66a4
2 changed files with 81 additions and 4 deletions
|
|
@ -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!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue