mk-dl-bot_legacy/src/dl/yt_dlp.rs

141 lines
3.4 KiB
Rust

use core::fmt;
use serde::Deserialize;
use serde_json;
use std::{process::ExitStatus, str::Utf8Error};
use tokio::process::Command;
#[derive(Deserialize, Debug)]
pub struct YtDlpFormat {
pub format_id: String,
pub format_note: Option<String>,
pub audio_channels: Option<u8>,
pub width: Option<u16>,
pub height: Option<u16>,
pub ext: String,
pub vcodec: Option<String>,
pub acodec: Option<String>,
pub vbr: Option<f32>,
pub abr: Option<f32>,
}
impl YtDlpFormat {
pub fn process(&mut self) {
if self.acodec.as_ref().is_some_and(|v| v == "none") {
self.acodec = None
}
if self.vcodec.as_ref().is_some_and(|v| v == "none") {
self.vcodec = None
}
}
fn str_option<T>(opt: &Option<T>) -> String
where
T: ToString,
{
if let Some(value) = opt {
value.to_string()
} else {
"None".to_string()
}
}
}
impl fmt::Display for YtDlpFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"id\t{}\text\t{}\tw\t{}\th\t{}\tvcodec\t{}\tvbr\t{}\tacodec\t{}\tabr\t{}",
self.format_id,
self.ext,
Self::str_option(&self.width),
Self::str_option(&self.height),
Self::str_option(&self.vcodec),
Self::str_option(&self.vbr),
Self::str_option(&self.acodec),
Self::str_option(&self.abr)
)
}
}
#[derive(Deserialize, Debug)]
pub struct YtDlpInfo {
pub id: String,
pub title: String,
pub formats: Vec<YtDlpFormat>,
}
impl YtDlpInfo {
pub fn parse(json: &[u8]) -> Result<YtDlpInfo, serde_json::Error> {
let mut info: YtDlpInfo = serde_json::from_slice(json)?;
for format in &mut info.formats {
format.process()
}
Ok(info)
}
pub fn best_video_format(&self) -> Option<&str> {
//self.formats
// .iter()
todo!()
}
}
#[derive(Debug)]
pub enum YtDlpError {
CommandError(std::io::Error),
UtfError(Utf8Error),
ErrorMessage(String),
JsonError,
}
impl From<std::io::Error> for YtDlpError {
fn from(value: std::io::Error) -> Self {
Self::CommandError(value)
}
}
impl From<Utf8Error> for YtDlpError {
fn from(value: Utf8Error) -> Self {
Self::UtfError(value)
}
}
impl From<serde_json::Error> for YtDlpError {
fn from(_value: serde_json::Error) -> Self {
Self::JsonError
}
}
impl fmt::Display for YtDlpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use YtDlpError as YTE;
match self {
YTE::CommandError(e) => write!(f, "Command::new - {}", e),
YTE::UtfError(_) => write!(f, "Error while decoding UTF8"),
YTE::ErrorMessage(msg) => write!(f, "yt-dlp error - {}", msg),
YTE::JsonError => write!(f, "json parsing error"),
}
}
}
pub struct YtDlp {}
impl YtDlp {
pub async fn load_info(url: &str) -> Result<YtDlpInfo, YtDlpError> {
let output = Command::new("python")
.args(["-m", "yt_dlp", url, "-j"])
.output()
.await?;
if !output.status.success() {
let message = std::str::from_utf8(&output.stderr)?;
return Err(YtDlpError::ErrorMessage(message.to_string()));
}
Ok(YtDlpInfo::parse(&output.stdout)?)
}
}