test: add downloader test
This commit is contained in:
parent
ee3ae40395
commit
a3a1d9abf3
8 changed files with 180 additions and 55 deletions
|
|
@ -48,3 +48,9 @@ time.workspace = true
|
|||
lofty = { version = "0.21.0", optional = true }
|
||||
image = { version = "0.25.0", optional = true }
|
||||
smartcrop2 = { version = "0.3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
path_macro.workspace = true
|
||||
rstest.workspace = true
|
||||
serde_json.workspace = true
|
||||
temp_testdir = "0.2.3"
|
||||
|
|
|
|||
54
downloader/src/error.rs
Normal file
54
downloader/src/error.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use rustypipe::client::ClientType;
|
||||
|
||||
/// Error from the video downloader
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum DownloadError {
|
||||
/// RustyPipe error
|
||||
#[error("{0}")]
|
||||
RustyPipe(#[from] rustypipe::error::Error),
|
||||
/// Error from the HTTP client
|
||||
#[error("http error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
/// 403 error trying to download video
|
||||
#[error("YouTube returned 403 error")]
|
||||
Forbidden(ClientType),
|
||||
/// File IO error
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
/// FFmpeg returned an error
|
||||
#[error("FFmpeg error: {0}")]
|
||||
Ffmpeg(Cow<'static, str>),
|
||||
/// Error parsing ranges for progressive download
|
||||
#[error("Progressive download error: {0}")]
|
||||
Progressive(Cow<'static, str>),
|
||||
/// Video could not be downloaded because of invalid player data
|
||||
#[error("input error: {0}")]
|
||||
Input(Cow<'static, str>),
|
||||
/// Download target already exists
|
||||
#[error("file {0} already exists")]
|
||||
Exists(PathBuf),
|
||||
#[cfg(feature = "audiotag")]
|
||||
/// Audio tagging error
|
||||
#[error("Audio tag error: {0}")]
|
||||
AudioTag(Cow<'static, str>),
|
||||
/// Other error
|
||||
#[error("error: {0}")]
|
||||
Other(Cow<'static, str>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "audiotag")]
|
||||
impl From<lofty::error::LoftyError> for DownloadError {
|
||||
fn from(value: lofty::error::LoftyError) -> Self {
|
||||
Self::AudioTag(value.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "audiotag")]
|
||||
impl From<image::ImageError> for DownloadError {
|
||||
fn from(value: image::ImageError) -> Self {
|
||||
Self::AudioTag(value.to_string().into())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs, clippy::todo, clippy::dbg_macro)]
|
||||
|
||||
mod error;
|
||||
mod util;
|
||||
|
||||
use std::{
|
||||
|
|
@ -42,7 +43,7 @@ use rustypipe::model::{richtext::ToPlaintext, VideoDetails, VideoPlayerDetails};
|
|||
#[cfg(feature = "audiotag")]
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
pub use util::DownloadError;
|
||||
pub use error::DownloadError;
|
||||
|
||||
type Result<T> = core::result::Result<T, DownloadError>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +1,8 @@
|
|||
use std::{borrow::Cow, collections::BTreeMap, path::PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use reqwest::Url;
|
||||
use rustypipe::client::ClientType;
|
||||
|
||||
/// Error from the video downloader
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum DownloadError {
|
||||
/// RustyPipe error
|
||||
#[error("{0}")]
|
||||
RustyPipe(#[from] rustypipe::error::Error),
|
||||
/// Error from the HTTP client
|
||||
#[error("http error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
/// 403 error trying to download video
|
||||
#[error("YouTube returned 403 error")]
|
||||
Forbidden(ClientType),
|
||||
/// File IO error
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
/// FFmpeg returned an error
|
||||
#[error("FFmpeg error: {0}")]
|
||||
Ffmpeg(Cow<'static, str>),
|
||||
/// Error parsing ranges for progressive download
|
||||
#[error("Progressive download error: {0}")]
|
||||
Progressive(Cow<'static, str>),
|
||||
/// Video could not be downloaded because of invalid player data
|
||||
#[error("input error: {0}")]
|
||||
Input(Cow<'static, str>),
|
||||
/// Download target already exists
|
||||
#[error("file {0} already exists")]
|
||||
Exists(PathBuf),
|
||||
#[cfg(feature = "audiotag")]
|
||||
/// Audio tagging error
|
||||
#[error("Audio tag error: {0}")]
|
||||
AudioTag(Cow<'static, str>),
|
||||
/// Other error
|
||||
#[error("error: {0}")]
|
||||
Other(Cow<'static, str>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "audiotag")]
|
||||
impl From<lofty::error::LoftyError> for DownloadError {
|
||||
fn from(value: lofty::error::LoftyError) -> Self {
|
||||
Self::AudioTag(value.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "audiotag")]
|
||||
impl From<image::ImageError> for DownloadError {
|
||||
fn from(value: image::ImageError) -> Self {
|
||||
Self::AudioTag(value.to_string().into())
|
||||
}
|
||||
}
|
||||
use crate::DownloadError;
|
||||
|
||||
/// Split an URL into its base string and parameter map
|
||||
///
|
||||
|
|
|
|||
113
downloader/tests/tests.rs
Normal file
113
downloader/tests/tests.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use std::{fs, os::unix::fs::MetadataExt, path::Path, process::Command};
|
||||
|
||||
use path_macro::path;
|
||||
use rstest::{fixture, rstest};
|
||||
use rustypipe::{client::RustyPipe, model::AudioCodec, param::StreamFilter};
|
||||
use rustypipe_downloader::Downloader;
|
||||
use temp_testdir::TempDir;
|
||||
|
||||
/// Get a new RusttyPipe instance
|
||||
#[fixture]
|
||||
fn rp() -> RustyPipe {
|
||||
let vdata = std::env::var("YT_VDATA").ok();
|
||||
RustyPipe::builder()
|
||||
.strict()
|
||||
.storage_dir(path!(env!("CARGO_MANIFEST_DIR") / ".."))
|
||||
.visitor_data_opt(vdata)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn download_video(rp: RustyPipe) {
|
||||
let td = TempDir::default();
|
||||
let td_path = td.to_path_buf();
|
||||
|
||||
let dl = Downloader::builder().rustypipe(&rp).build();
|
||||
|
||||
let res = dl
|
||||
.id("UXqq0ZvbOnk")
|
||||
.to_dir(&td_path)
|
||||
.stream_filter(StreamFilter::new().video_max_res(480))
|
||||
.download()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
res.dest,
|
||||
path!(td_path / "CHARGE - Blender Open Movie [UXqq0ZvbOnk].mp4")
|
||||
);
|
||||
assert_eq!(res.player_data.details.id, "UXqq0ZvbOnk");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn download_music(rp: RustyPipe) {
|
||||
let td = TempDir::default();
|
||||
let td_path = td.to_path_buf();
|
||||
|
||||
let dl = Downloader::builder()
|
||||
.audio_tag()
|
||||
.crop_cover()
|
||||
.rustypipe(&rp)
|
||||
.build();
|
||||
|
||||
let res = dl
|
||||
.id("bVtv3st8bgc")
|
||||
.to_dir(&td_path)
|
||||
.stream_filter(
|
||||
StreamFilter::new()
|
||||
.no_video()
|
||||
.audio_codecs([AudioCodec::Opus]),
|
||||
)
|
||||
.download()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
res.dest,
|
||||
path!(td_path / "Lord of the Riffs [bVtv3st8bgc].opus")
|
||||
);
|
||||
assert_eq!(res.player_data.details.id, "bVtv3st8bgc");
|
||||
let fm = fs::metadata(&res.dest).unwrap();
|
||||
assert_gte(fm.size(), 6_000_000, "file size");
|
||||
assert_audio_meta(
|
||||
&res.dest,
|
||||
"Lord of the Riffs",
|
||||
"Alexander Nakarada - CreatorChords",
|
||||
"Lord of the Riffs",
|
||||
"2022-02-05",
|
||||
);
|
||||
}
|
||||
|
||||
/// Assert that number A is greater than or equal to number B
|
||||
#[track_caller]
|
||||
fn assert_gte<T: PartialOrd + std::fmt::Display>(a: T, b: T, msg: &str) {
|
||||
assert!(a >= b, "expected >= {b} {msg}, got {a}");
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_audio_meta(p: &Path, title: &str, artist: &str, album: &str, date: &str) {
|
||||
let res = Command::new("ffprobe")
|
||||
.args([
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-show_entries",
|
||||
"stream_tags",
|
||||
"-of",
|
||||
"json",
|
||||
])
|
||||
.arg(p)
|
||||
.output()
|
||||
.unwrap();
|
||||
if !res.status.success() {
|
||||
panic!("ffprobe error\n{}", String::from_utf8_lossy(&res.stderr))
|
||||
}
|
||||
let res_json = serde_json::from_slice::<serde_json::Value>(&res.stdout).unwrap();
|
||||
let tags = &res_json["streams"][0]["tags"];
|
||||
assert_eq!(tags["TITLE"].as_str(), Some(title));
|
||||
assert_eq!(tags["ARTIST"].as_str(), Some(artist));
|
||||
assert_eq!(tags["ALBUM"].as_str(), Some(album));
|
||||
assert_eq!(tags["DATE"].as_str(), Some(date));
|
||||
}
|
||||
Reference in a new issue