From 9cffe2162adcec7c9bda9a78d702e583d6770e3f Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sun, 22 Mar 2026 12:33:48 +0100 Subject: [PATCH] feat: add direct binary streaming for audio bots and media applications --- README.md | 31 +++++++++++++++++++++++++++++++ src/lib.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/README.md b/README.md index 407e30e..deacd02 100644 --- a/README.md +++ b/README.md @@ -86,5 +86,36 @@ async fn main() -> Result<(), Box> { } ``` +### Discord Bots / Media Streaming (Direct Binary Stream) +If you are building an application like a Discord bot, you often want to stream the raw binary audio data chunk-by-chunk to an engine like `ffmpeg` or `songbird` instead of downloading the whole file first. + +```rust +use ytd_rs::YtDlp; +use tokio::io::AsyncReadExt; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut stream = YtDlp::new("https://www.youtube.com/watch?v=uTO0KnDsVH0") + .best_audio() + .download_to_stream() + .await?; + + let mut buffer = [0; 1024]; + loop { + let bytes_read = stream.stdout().read(&mut buffer).await?; + if bytes_read == 0 { break; } // EOF + + // Process your raw chunk of audio data here + // e.g., send it to your bot's audio engine + // send_to_audio_engine(&buffer[..bytes_read]); + } + + stream.wait().await?; + Ok(()) +} +``` + +**Alternative for Discord Bots:** You can also use `.get_info().await?` to extract the direct media URL (`infos[0].url`) and pass that directly to your audio engine, which saves bot bandwidth! + ## License MIT diff --git a/src/lib.rs b/src/lib.rs index a9eb9e1..1b0dff6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,30 @@ impl YtDlpChild { } } +/// A running yt-dlp process that streams raw media bytes directly to standard output. +pub struct YtDlpStream { + child: tokio::process::Child, + stdout: tokio::process::ChildStdout, +} + +impl YtDlpStream { + /// Returns a mutable reference to the standard output to read bytes. + /// You can use this with `tokio::io::AsyncReadExt` methods like `read_buf`. + pub fn stdout(&mut self) -> &mut tokio::process::ChildStdout { + &mut self.stdout + } + + /// Waits for the process to complete and checks its status. + pub async fn wait(mut self) -> Result<()> { + let status = self.child.wait().await?; + if status.success() { + Ok(()) + } else { + Err(YtDlpError::Failure("Process failed".to_string())) + } + } +} + /// The main builder for configuring and running yt-dlp. #[derive(Clone, Debug, Default)] pub struct YtDlp { @@ -288,6 +312,20 @@ impl YtDlp { Ok(YtDlpChild { child, stdout }) } + /// Executes yt-dlp and streams the raw media binary data to standard output. + /// This automatically sets `--output -` and gives you access to the async stdout reader. + pub async fn download_to_stream(&self) -> Result { + let mut clone = self.clone(); + clone = clone.arg_with("--output", "-"); + let mut child = clone.spawn_yt_dlp(false).await?; + + let stdout = child.stdout.take().ok_or_else(|| { + YtDlpError::Failure("Failed to capture standard output".to_string()) + })?; + + Ok(YtDlpStream { child, stdout }) + } + /// Executes yt-dlp with --dump-json and parses the output into VideoInfo. pub async fn get_info(&self) -> Result> { let output = self.spawn_yt_dlp(true).await?.wait_with_output().await?;