From 61042c26faee164b51dda27561c9b67b34af8d9a Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Fri, 15 Feb 2019 22:59:40 -0500 Subject: initial implementation of video start/end times --- src/commands/playback/types.rs | 121 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) (limited to 'src/commands/playback/types.rs') diff --git a/src/commands/playback/types.rs b/src/commands/playback/types.rs index 63479fd..380ee9d 100644 --- a/src/commands/playback/types.rs +++ b/src/commands/playback/types.rs @@ -5,12 +5,13 @@ use std::{ time::Duration, }; +use chrono::Duration as CDuration; use either::{Either, Left, Right}; use serenity::{ client::bridge::voice::ClientVoiceManager, model::id::ChannelId, prelude::*, - voice::{LockedAudio, ytdl}, + voice::{LockedAudio}, }; use typemap::Key; @@ -41,6 +42,8 @@ pub struct PlayArgs { pub data: Either>, pub initiator: String, pub sender_channel: ChannelId, + pub start: Option, + pub end: Option, } #[derive(Clone)] @@ -119,7 +122,7 @@ impl PlayQueue { let src = match item.data { Left(ref url) => { - match ytdl(url) { + match ytdl(url, item.start, item.end) { Ok(src) => src, Err(e) => { error!("bad link: {}; {:?}", url, e); @@ -160,3 +163,117 @@ impl PlayQueue { }); } } + +use std::{ + io::{ + Read, + Result as IoResult, + BufReader, + }, + process::{ + Command, + Stdio, + Child, + } +}; + +use serenity::{ + voice::{ + AudioSource, + pcm, + } +}; +use serde_json::Value; +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); + } + } +} + + +// Copied from serenity +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(CDuration::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