use super::{
children_to_reader,
error::{Error, Result},
Codec,
Container,
Input,
Metadata,
};
use serde_json::Value;
use std::{
io::{BufRead, BufReader, Read},
process::{Command, Stdio},
};
use tokio::{process::Command as TokioCommand, task};
use tracing::trace;
const YOUTUBE_DL_COMMAND: &str = if cfg!(feature = "youtube-dlc") {
"youtube-dlc"
} else if cfg!(feature = "yt-dlp") {
"yt-dlp"
} else {
"youtube-dl"
};
pub async fn ytdl(uri: impl AsRef<str>) -> Result<Input> {
_ytdl(uri.as_ref(), &[]).await
}
pub(crate) async fn _ytdl(uri: &str, pre_args: &[&str]) -> Result<Input> {
let ytdl_args = [
"--print-json",
"-f",
"webm[abr>0]/bestaudio/best",
"-R",
"infinite",
"--no-playlist",
"--ignore-config",
"--no-warnings",
uri,
"-o",
"-",
];
let ffmpeg_args = [
"-f",
"s16le",
"-ac",
"2",
"-ar",
"48000",
"-acodec",
"pcm_f32le",
"-",
];
let mut youtube_dl = Command::new(YOUTUBE_DL_COMMAND)
.args(&ytdl_args)
.stdin(Stdio::null())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let stderr = youtube_dl.stderr.take();
let (returned_stderr, value) = task::spawn_blocking(move || {
let mut s = stderr.unwrap();
let out: Result<Value> = {
let mut o_vec = vec![];
let mut serde_read = BufReader::new(s.by_ref());
if let Ok(len) = serde_read.read_until(0xA, &mut o_vec) {
serde_json::from_slice(&o_vec[..len]).map_err(|err| Error::Json {
error: err,
parsed_text: std::str::from_utf8(&o_vec).unwrap_or_default().to_string(),
})
} else {
Result::Err(Error::Metadata)
}
};
(s, out)
})
.await
.map_err(|_| Error::Metadata)?;
youtube_dl.stderr = Some(returned_stderr);
let taken_stdout = youtube_dl.stdout.take().ok_or(Error::Stdout)?;
let ffmpeg = Command::new("ffmpeg")
.args(pre_args)
.arg("-i")
.arg("-")
.args(&ffmpeg_args)
.stdin(taken_stdout)
.stderr(Stdio::null())
.stdout(Stdio::piped())
.spawn()?;
let metadata = Metadata::from_ytdl_output(value?);
trace!("ytdl metadata {:?}", metadata);
Ok(Input::new(
true,
children_to_reader::<f32>(vec![youtube_dl, ffmpeg]),
Codec::FloatPcm,
Container::Raw,
Some(metadata),
))
}
pub(crate) async fn _ytdl_metadata(uri: &str) -> Result<Metadata> {
let ytdl_args = [
"-j",
"-f",
"webm[abr>0]/bestaudio/best",
"-R",
"infinite",
"--no-playlist",
"--ignore-config",
"--no-warnings",
uri,
"-o",
"-",
];
let youtube_dl_output = TokioCommand::new(YOUTUBE_DL_COMMAND)
.args(&ytdl_args)
.stdin(Stdio::null())
.output()
.await?;
let o_vec = youtube_dl_output.stderr;
let end = (&o_vec)
.iter()
.position(|el| *el == 0xA)
.unwrap_or_else(|| o_vec.len());
let value = serde_json::from_slice(&o_vec[..end]).map_err(|err| Error::Json {
error: err,
parsed_text: std::str::from_utf8(&o_vec).unwrap_or_default().to_string(),
})?;
let metadata = Metadata::from_ytdl_output(value);
Ok(metadata)
}
pub async fn ytdl_search(name: impl AsRef<str>) -> Result<Input> {
ytdl(&format!("ytsearch1:{}", name.as_ref())).await
}