From 966a4934552a43d2a1de0e314bacd7bada0a6845 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Sat, 16 Feb 2019 14:52:54 -0500 Subject: clean up project structure - Move audio-related code into its own top-level module, separating out playback commands into their own file in `commands`. - Rename `sound` commands module to `sound_levels`. --- src/audio/ytdl.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/audio/ytdl.rs (limited to 'src/audio/ytdl.rs') diff --git a/src/audio/ytdl.rs b/src/audio/ytdl.rs new file mode 100644 index 0000000..d16d166 --- /dev/null +++ b/src/audio/ytdl.rs @@ -0,0 +1,117 @@ +/// This module is entirely adapted from the relevant code in Serenity. + +use std::{ + io::{ + Read, + Result as IoResult, + }, + process::{ + Command, + Stdio, + Child, + }, +}; + +use chrono::Duration; +use serde_json::Value; + +use serenity::{ + voice::{ + AudioSource, + pcm, + VoiceError, + } +}; + +use crate::Result; + + +struct ChildContainer(Child); + +impl Read for ChildContainer { + fn read(&mut self, buffer: &mut [u8]) -> IoResult { + self.0.stdout.as_mut().unwrap().read(buffer) + } +} + +impl Drop for ChildContainer { + fn drop (&mut self) { + if let Err(e) = self.0.kill() { + debug!("[Voice] Error awaiting child process: {:?}", e); + } + } +} + +pub fn ytdl(uri: &str, start: Option, end: Option) -> Result> { + let args = [ + "-f", + "webm[abr>0]/bestaudio/best", + "--no-playlist", + "--print-json", + "--skip-download", + uri, + ]; + + let out = Command::new("youtube-dl") + .args(&args) + .stdin(Stdio::null()) + .output()?; + + if !out.status.success() { + return Err(VoiceError::YouTubeDLRun(out).into()); + } + + let value = serde_json::from_reader(&out.stdout[..])?; + let mut obj = match value { + Value::Object(obj) => obj, + other => return Err(VoiceError::YouTubeDLProcessing(other).into()), + }; + + let uri = match obj.remove("url") { + Some(v) => match v { + Value::String(uri) => uri, + other => return Err(VoiceError::YouTubeDLUrl(other).into()), + }, + None => return Err(VoiceError::YouTubeDLUrl(Value::Object(obj)).into()), + }; + + let start = start.unwrap_or(Duration::zero()); + let start_str = format!("{:02}:{:02}:{:02}", start.num_hours(), start.num_minutes() % 60, start.num_seconds() % 60); + + let mut opts = vec! [ + "-f", + "s16le", + "-ac", + "2", // force stereo -- this may cause issues + "-ar", + "48000", + "-acodec", + "pcm_s16le", + "-ss", + &start_str, + ] + .into_iter() + .map(|s| s.to_owned()) + .collect::>(); + + match end { + Some(e) => { + opts.push("-to".to_owned()); + opts.push(format!("{:02}:{:02}:{:02}", e.num_hours(), e.num_minutes() % 60, e.num_seconds() % 60)); + }, + _ => {}, + } + + opts.push("-".to_owned()); + + let command = Command::new("ffmpeg") + .arg("-i") + .arg(uri) + .args(opts) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn()?; + + Ok(pcm(true, ChildContainer(command))) +} -- cgit v1.3.1