1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
use byteorder::{LittleEndian, ReadBytesExt};
use serde_json;
use std::ffi::OsStr;
use std::io::{ErrorKind as IoErrorKind, Read, Result as IoResult};
use std::process::{Child, Command, Stdio};
use super::{AudioSource, VoiceError};
use internal::prelude::*;
struct ChildContainer(Child);
impl Read for ChildContainer {
fn read(&mut self, buffer: &mut [u8]) -> IoResult<usize> {
self.0.stdout.as_mut().unwrap().read(buffer)
}
}
struct PcmSource<R: Read + Send + 'static>(bool, R);
impl<R: Read + Send> AudioSource for PcmSource<R> {
fn is_stereo(&mut self) -> bool { self.0 }
fn read_frame(&mut self, buffer: &mut [i16]) -> Option<usize> {
for (i, v) in buffer.iter_mut().enumerate() {
*v = match self.1.read_i16::<LittleEndian>() {
Ok(v) => v,
Err(ref e) => {
return if e.kind() == IoErrorKind::UnexpectedEof {
Some(i)
} else {
None
}
},
}
}
Some(buffer.len())
}
}
/// Opens an audio file through `ffmpeg` and creates an audio source.
pub fn ffmpeg<P: AsRef<OsStr>>(path: P) -> Result<Box<AudioSource>> {
let path = path.as_ref();
/// Will fail if the path is not to a file on the fs. Likely a YouTube URI.
let is_stereo = is_stereo(path).unwrap_or(false);
let stereo_val = if is_stereo { "2" } else { "1" };
let args = [
"-f",
"s16le",
"-ac",
stereo_val,
"-ar",
"48000",
"-acodec",
"pcm_s16le",
"-",
];
let command = Command::new("ffmpeg")
.arg("-i")
.arg(path)
.args(&args)
.stderr(Stdio::null())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()?;
Ok(pcm(is_stereo, ChildContainer(command)))
}
/// Creates a PCM audio source.
pub fn pcm<R: Read + Send + 'static>(is_stereo: bool, reader: R) -> Box<AudioSource> {
Box::new(PcmSource(is_stereo, reader))
}
/// Creates a streamed audio source with `youtube-dl` and `ffmpeg`.
pub fn ytdl(uri: &str) -> Result<Box<AudioSource>> {
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(Error::Voice(VoiceError::YouTubeDLRun(out)));
}
let value = serde_json::from_reader(&out.stdout[..])?;
let mut obj = match value {
Value::Object(obj) => obj,
other => return Err(Error::Voice(VoiceError::YouTubeDLProcessing(other))),
};
let uri = match obj.remove("url") {
Some(v) => match v {
Value::String(uri) => uri,
other => return Err(Error::Voice(VoiceError::YouTubeDLUrl(other))),
},
None => return Err(Error::Voice(VoiceError::YouTubeDLUrl(Value::Object(obj)))),
};
ffmpeg(&uri)
}
fn is_stereo(path: &OsStr) -> Result<bool> {
let args = ["-v", "quiet", "-of", "json", "-show-streams", "-i"];
let out = Command::new("ffprobe")
.args(&args)
.arg(path)
.stdin(Stdio::null())
.output()?;
let value: Value = serde_json::from_reader(&out.stdout[..])?;
let streams = value
.as_object()
.and_then(|m| m.get("streams"))
.and_then(|v| v.as_array())
.ok_or(Error::Voice(VoiceError::Streams))?;
let check = streams.iter().any(|stream| {
let channels = stream
.as_object()
.and_then(|m| m.get("channels").and_then(|v| v.as_i64()));
channels == Some(2)
});
Ok(check)
}
|