begin implementing 1080p downloading (requires both video and audio downloaded separately and joined)

This commit is contained in:
mykola2312 2024-03-16 05:23:55 +02:00
parent db5bc74de7
commit b316fe8387
3 changed files with 93 additions and 4 deletions

View file

@ -71,15 +71,19 @@ pub async fn download(url: &str) -> Result<String, DownloadError> {
let av = match info.best_av_format() {
Some(av) => av,
None => {
event!(Level::WARN, "no best format found for {}, reverting to default", url);
event!(
Level::WARN,
"no best format found for {}, reverting to default",
url
);
match info.default_format() {
Some(format) => format,
None => {
event!(Level::ERROR, "no formats found for {}", url);
return Err(DownloadError::NoFormatFound)
return Err(DownloadError::NoFormatFound);
}
}
},
}
};
let output_path = make_download_path(&info, &av)?;

View file

@ -37,6 +37,38 @@ impl FFMpeg {
Ok(())
}
pub async fn join_audio_video(
video_path: &str,
audio_path: &str,
abr: u16,
output_path: &str,
) -> Result<(), SpawnError> {
let abr = format!("{}k", abr);
let output = spawn(
"ffmpeg",
&[
"-i",
video_path,
"-i",
audio_path,
"-c",
"copy",
"-map",
"0:v:0",
"-map",
"1:a:0",
"-c:a",
"aac",
"-b:a",
&abr,
output_path,
],
)
.await?;
Ok(())
}
}
#[cfg(test)]

View file

@ -22,8 +22,20 @@ pub struct YtDlpFormat {
struct VideoFormat<'a> {
pub format: &'a YtDlpFormat,
pub format_note: &'a String,
pub width: u16,
pub height: u16,
pub vbr: f32,
}
impl<'a> VideoFormat<'a> {
pub fn is_mp4(&self) -> bool {
self.format.ext == "mp4"
}
pub fn is_premium(&self) -> bool {
self.format_note.contains("Premium")
}
}
struct AudioFormat<'a> {
@ -79,7 +91,7 @@ pub struct YtDlpInfo {
}
impl YtDlpInfo {
const H_LIMIT: u16 = 720;
const H_LIMIT: u16 = 1080;
pub fn parse(json: &[u8]) -> Result<YtDlpInfo, serde_json::Error> {
let mut info: YtDlpInfo = serde_json::from_slice(json)?;
@ -102,6 +114,10 @@ impl YtDlpInfo {
}
}
#[deprecated(
since = "0.1.1",
note = "for YouTube download audio and video separately"
)]
pub fn best_av_format(&self) -> Option<&YtDlpFormat> {
let format = self
.formats
@ -110,8 +126,10 @@ impl YtDlpInfo {
if f.vcodec.is_some() && f.acodec.is_some() {
Some(VideoFormat {
format: &f,
format_note: f.format_note.as_ref()?,
width: f.width?,
height: f.height?,
vbr: f.vbr?,
})
} else {
None
@ -148,6 +166,31 @@ impl YtDlpInfo {
}
}
}
pub fn best_video_format(&self) -> Option<&YtDlpFormat> {
let format = self
.formats
.iter()
.filter_map(|f| {
Some(VideoFormat {
format: f,
format_note: f.format_note.as_ref()?,
width: f.width?,
height: f.height?,
vbr: f.vbr?,
})
})
.filter(|f| f.height <= Self::H_LIMIT && f.is_mp4() && !f.is_premium())
.max_by_key(|f| OrderedFloat(f.vbr));
match format {
Some(vf) => Some(vf.format),
None => {
event!(Level::ERROR, "no video format for {}", self.id);
None
}
}
}
}
#[derive(Debug)]
@ -250,4 +293,14 @@ mod tests {
let video = info.best_audio_format().unwrap();
assert_eq!(video.format_id, "140");
}
#[tokio::test]
async fn best_video_format() {
dotenv::from_filename(".env.test").unwrap();
let info = YtDlp::load_info(env::var("TEST_URL").unwrap().as_str())
.await
.unwrap();
let video = info.best_video_format().unwrap();
assert_eq!(video.format_id, "137");
}
}