Skip to content

Commit

Permalink
feat(resolver/engine): add yt-dlp support
Browse files Browse the repository at this point in the history
  • Loading branch information
pan93412 committed Jan 16, 2022
1 parent 7264783 commit 045ddd7
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions unm_resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = "2021"
anyhow = "1.0.52"
async-trait = "0.1.52"
http = "0.2.6"
log = "0.4.14"
once_cell = "1.9.0"
rayon = "1.5.1"
reqwest = { version = "0.11.8", features = ["json", "brotli", "deflate", "gzip"] }
Expand Down
1 change: 1 addition & 0 deletions unm_resolver/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod bilibili;
pub mod pyncm;
pub mod ytdlp;

pub use async_trait::async_trait;
use reqwest::Proxy;
Expand Down
60 changes: 60 additions & 0 deletions unm_resolver/src/engine/ytdlp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! UNM Resolver [Engine]: yt-dlp
//!
//! It can fetch audio from YouTube.

use serde::Deserialize;

use super::{Provider, Song, Proxy};

/// The response that `yt-dlp` will return.
#[derive(Deserialize)]
struct YtDlpResponse {
/// The audio URL.
url: String,
}

/// The search and track engine powered by `yt-dlp`.
pub struct YtDlpEngine;

#[async_trait::async_trait]
impl Provider for YtDlpEngine {
// TODO: allow specifying proxy
async fn check(&self, info: &Song, _: Option<Proxy>) -> anyhow::Result<Option<String>> {
Ok(fetch_from_youtube(&info.keyword()).await?.map(|r| r.url))
}
}

/// Get the response from `yt-dlp`.
///
/// ```plain
/// yt-dlp -f bestaudio --dump-json ytsearch1:{keyword}
/// -f bestaudio choose the best quality of the audio
/// --dump-json dump the information as JSON without downloading it
/// ```
async fn fetch_from_youtube(keyword: &str) -> anyhow::Result<Option<YtDlpResponse>> {
let mut cmd = tokio::process::Command::new("yt-dlp");

let child = cmd
.args(&["-f", "bestaudio", "--dump-json"])
.arg(format!("ytsearch1:{keyword}"))
.kill_on_drop(true)
.output()
.await?;

if child.status.success() {
let response = String::from_utf8_lossy(&child.stdout);

Ok(if response.is_empty() {
None
} else {
let json = serde_json::from_str::<'_, YtDlpResponse>(&response)?;
Some(json)
})
} else {
log::error!("Failed to run `youtube-dl`.");
log::error!("Code: {code:?}", code = child.status.code());
log::error!("Stderr: {}", String::from_utf8_lossy(&child.stderr));

Err(anyhow::anyhow!("Failed to run `youtube-dl`."))
}
}

0 comments on commit 045ddd7

Please sign in to comment.