Skip to content

Commit c910d70

Browse files
authored
Playlist fixes, add YoutubeDl::get_stream function (#279)
* Fix(ytdl): Return all results when querying a URL * Publicly export `songbird::input::metadata::ytdl::Output` Closes: #277 * Make `YoutubeDl::query` public * Abstract getting streams from YoutubeDl into functions `get_stream` and `get_streams` * fmt, remove get_streams * Fix doc comment * fixup doc comments, export `metadata::ytdl::Output` as `YoutubeDlOutput` * fmt * export metadata * fixup! fixup doc comments, export `metadata::ytdl::Output` as `YoutubeDlOutput`
1 parent 8956352 commit c910d70

File tree

4 files changed

+70
-39
lines changed

4 files changed

+70
-39
lines changed

src/input/metadata/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
//! Metadata formats specific to [`crate::input::Compose`] types.
2+
13
use crate::error::JsonError;
24
use std::time::Duration;
35
use symphonia_core::{meta::Metadata as ContainerMetadata, probe::ProbedMetadata};
46

57
pub(crate) mod ffprobe;
6-
pub(crate) mod ytdl;
8+
mod ytdl;
9+
pub use ytdl::Output as YoutubeDlOutput;
710

811
use super::Parsed;
912

src/input/metadata/ytdl.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,49 @@
1+
//! `YoutubeDl` track metadata.
2+
13
use super::AuxMetadata;
24
use crate::constants::SAMPLE_RATE_RAW;
35
use serde::{Deserialize, Serialize};
46
use std::{collections::HashMap, time::Duration};
57

8+
/// Information returned by yt-dlp about a URL.
9+
///
10+
/// Returned by [`crate::input::YoutubeDl::query`].
611
#[derive(Deserialize, Serialize, Debug)]
712
pub struct Output {
13+
/// The main artist.
814
pub artist: Option<String>,
15+
/// The album name.
916
pub album: Option<String>,
17+
/// The channel name.
1018
pub channel: Option<String>,
19+
/// The duration of the stream in seconds.
1120
pub duration: Option<f64>,
21+
/// The size of the stream.
1222
pub filesize: Option<u64>,
23+
/// Required HTTP headers to fetch the track stream.
1324
pub http_headers: Option<HashMap<String, String>>,
25+
/// Release date of this track.
1426
pub release_date: Option<String>,
27+
/// The thumbnail URL for this track.
1528
pub thumbnail: Option<String>,
29+
/// The title of this track.
1630
pub title: Option<String>,
31+
/// The track name.
1732
pub track: Option<String>,
33+
/// The date this track was uploaded on.
1834
pub upload_date: Option<String>,
35+
/// The name of the uploader.
1936
pub uploader: Option<String>,
37+
/// The stream URL.
2038
pub url: String,
39+
/// The URL of the public-facing webpage for this track.
2140
pub webpage_url: Option<String>,
41+
/// The stream protocol.
2242
pub protocol: Option<String>,
2343
}
2444

2545
impl Output {
46+
/// Requests auxiliary metadata which can be accessed without parsing the file.
2647
pub fn as_aux_metadata(&self) -> AuxMetadata {
2748
let album = self.album.clone();
2849
let track = self.track.clone();

src/input/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ mod error;
5959
#[cfg(test)]
6060
pub mod input_tests;
6161
mod live_input;
62-
mod metadata;
62+
pub mod metadata;
6363
mod parsed;
6464
mod sources;
6565
pub mod utils;
@@ -70,7 +70,7 @@ pub use self::{
7070
compose::*,
7171
error::*,
7272
live_input::*,
73-
metadata::*,
73+
metadata::{AuxMetadata, Metadata},
7474
parsed::*,
7575
sources::*,
7676
};

src/input/sources/ytdl.rs

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::input::{
2-
metadata::ytdl::Output,
2+
metadata::YoutubeDlOutput,
33
AudioStream,
44
AudioStreamError,
55
AuxMetadata,
@@ -8,7 +8,6 @@ use crate::input::{
88
Input,
99
};
1010
use async_trait::async_trait;
11-
use either::Either;
1211
use reqwest::{
1312
header::{HeaderMap, HeaderName, HeaderValue},
1413
Client,
@@ -118,19 +117,19 @@ impl<'a> YoutubeDl<'a> {
118117
) -> Result<impl Iterator<Item = AuxMetadata>, AudioStreamError> {
119118
let n_results = n_results.unwrap_or(5);
120119

121-
Ok(match &self.query {
122-
// Safer to just return the metadata for the pointee if possible
123-
QueryType::Url(_) => Either::Left(std::iter::once(self.aux_metadata().await?)),
124-
QueryType::Search(_) => Either::Right(
125-
self.query(n_results)
126-
.await?
127-
.into_iter()
128-
.map(|v| v.as_aux_metadata()),
129-
),
130-
})
120+
Ok(self
121+
.query(n_results)
122+
.await?
123+
.into_iter()
124+
.map(|v| v.as_aux_metadata()))
131125
}
132126

133-
async fn query(&mut self, n_results: usize) -> Result<Vec<Output>, AudioStreamError> {
127+
/// Runs a search for the given query, returning a list of up to `n_results`
128+
/// possible matches.
129+
pub async fn query(
130+
&mut self,
131+
n_results: usize,
132+
) -> Result<Vec<YoutubeDlOutput>, AudioStreamError> {
134133
let query_str = self.query.as_cow_str(n_results);
135134
let ytdl_args = [
136135
"-j",
@@ -169,7 +168,7 @@ impl<'a> YoutubeDl<'a> {
169168
.split(|&b| b == b'\n')
170169
.filter(|&x| (!x.is_empty()))
171170
.map(serde_json::from_slice)
172-
.collect::<Result<Vec<Output>, _>>()
171+
.collect::<Result<Vec<YoutubeDlOutput>, _>>()
173172
.map_err(|e| AudioStreamError::Fail(Box::new(e)))?;
174173

175174
let meta = out
@@ -183,30 +182,15 @@ impl<'a> YoutubeDl<'a> {
183182

184183
Ok(out)
185184
}
186-
}
187-
188-
impl From<YoutubeDl<'static>> for Input {
189-
fn from(val: YoutubeDl<'static>) -> Self {
190-
Input::Lazy(Box::new(val))
191-
}
192-
}
193-
194-
#[async_trait]
195-
impl Compose for YoutubeDl<'_> {
196-
fn create(&mut self) -> Result<AudioStream<Box<dyn MediaSource>>, AudioStreamError> {
197-
Err(AudioStreamError::Unsupported)
198-
}
199185

200-
async fn create_async(
201-
&mut self,
186+
/// Get the audio stream from a [`YoutubeDlOutput`].
187+
pub async fn get_stream(
188+
&self,
189+
result: &YoutubeDlOutput,
202190
) -> Result<AudioStream<Box<dyn MediaSource>>, AudioStreamError> {
203-
// panic safety: `query` should have ensured > 0 results if `Ok`
204-
let mut results = self.query(1).await?;
205-
let result = results.swap_remove(0);
206-
207191
let mut headers = HeaderMap::default();
208192

209-
if let Some(map) = result.http_headers {
193+
if let Some(map) = &result.http_headers {
210194
headers.extend(map.iter().filter_map(|(k, v)| {
211195
Some((
212196
HeaderName::from_bytes(k.as_bytes()).ok()?,
@@ -219,20 +203,43 @@ impl Compose for YoutubeDl<'_> {
219203
match result.protocol.as_deref() {
220204
Some("m3u8_native") => {
221205
let mut req =
222-
HlsRequest::new_with_headers(self.client.clone(), result.url, headers);
206+
HlsRequest::new_with_headers(self.client.clone(), result.url.clone(), headers);
223207
req.create()
224208
},
225209
_ => {
226210
let mut req = HttpRequest {
227211
client: self.client.clone(),
228-
request: result.url,
212+
request: result.url.clone(),
229213
headers,
230214
content_length: result.filesize,
231215
};
232216
req.create_async().await
233217
},
234218
}
235219
}
220+
}
221+
222+
impl From<YoutubeDl<'static>> for Input {
223+
fn from(val: YoutubeDl<'static>) -> Self {
224+
Input::Lazy(Box::new(val))
225+
}
226+
}
227+
228+
#[async_trait]
229+
impl Compose for YoutubeDl<'_> {
230+
fn create(&mut self) -> Result<AudioStream<Box<dyn MediaSource>>, AudioStreamError> {
231+
Err(AudioStreamError::Unsupported)
232+
}
233+
234+
async fn create_async(
235+
&mut self,
236+
) -> Result<AudioStream<Box<dyn MediaSource>>, AudioStreamError> {
237+
// panic safety: `query` should have ensured > 0 results if `Ok`
238+
let mut results = self.query(1).await?;
239+
let result = results.swap_remove(0);
240+
241+
self.get_stream(&result).await
242+
}
236243

237244
fn should_create_async(&self) -> bool {
238245
true

0 commit comments

Comments
 (0)