begin implementing 1080p downloading (requires both video and audio downloaded separately and joined)
This commit is contained in:
parent
240b05c03c
commit
578d236c64
3 changed files with 93 additions and 4 deletions
10
src/dl.rs
10
src/dl.rs
|
|
@ -71,15 +71,19 @@ pub async fn download(url: &str) -> Result<String, DownloadError> {
|
||||||
let av = match info.best_av_format() {
|
let av = match info.best_av_format() {
|
||||||
Some(av) => av,
|
Some(av) => av,
|
||||||
None => {
|
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() {
|
match info.default_format() {
|
||||||
Some(format) => format,
|
Some(format) => format,
|
||||||
None => {
|
None => {
|
||||||
event!(Level::ERROR, "no formats found for {}", url);
|
event!(Level::ERROR, "no formats found for {}", url);
|
||||||
return Err(DownloadError::NoFormatFound)
|
return Err(DownloadError::NoFormatFound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_path = make_download_path(&info, &av)?;
|
let output_path = make_download_path(&info, &av)?;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,38 @@ impl FFMpeg {
|
||||||
|
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,20 @@ pub struct YtDlpFormat {
|
||||||
|
|
||||||
struct VideoFormat<'a> {
|
struct VideoFormat<'a> {
|
||||||
pub format: &'a YtDlpFormat,
|
pub format: &'a YtDlpFormat,
|
||||||
|
pub format_note: &'a String,
|
||||||
pub width: u16,
|
pub width: u16,
|
||||||
pub height: 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> {
|
struct AudioFormat<'a> {
|
||||||
|
|
@ -79,7 +91,7 @@ pub struct YtDlpInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YtDlpInfo {
|
impl YtDlpInfo {
|
||||||
const H_LIMIT: u16 = 720;
|
const H_LIMIT: u16 = 1080;
|
||||||
|
|
||||||
pub fn parse(json: &[u8]) -> Result<YtDlpInfo, serde_json::Error> {
|
pub fn parse(json: &[u8]) -> Result<YtDlpInfo, serde_json::Error> {
|
||||||
let mut info: YtDlpInfo = serde_json::from_slice(json)?;
|
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> {
|
pub fn best_av_format(&self) -> Option<&YtDlpFormat> {
|
||||||
let format = self
|
let format = self
|
||||||
.formats
|
.formats
|
||||||
|
|
@ -110,8 +126,10 @@ impl YtDlpInfo {
|
||||||
if f.vcodec.is_some() && f.acodec.is_some() {
|
if f.vcodec.is_some() && f.acodec.is_some() {
|
||||||
Some(VideoFormat {
|
Some(VideoFormat {
|
||||||
format: &f,
|
format: &f,
|
||||||
|
format_note: f.format_note.as_ref()?,
|
||||||
width: f.width?,
|
width: f.width?,
|
||||||
height: f.height?,
|
height: f.height?,
|
||||||
|
vbr: f.vbr?,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
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)]
|
#[derive(Debug)]
|
||||||
|
|
@ -250,4 +293,14 @@ mod tests {
|
||||||
let video = info.best_audio_format().unwrap();
|
let video = info.best_audio_format().unwrap();
|
||||||
assert_eq!(video.format_id, "140");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue