Fix tests, add animated_webp test

This commit is contained in:
asonix 2023-07-13 14:34:40 -05:00
parent 3d0857ff65
commit 27451971a6
31 changed files with 3991 additions and 265 deletions

View File

@ -9,6 +9,7 @@ use crate::{
store::Store,
};
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Discovery {
pub(crate) input: InputFile,
pub(crate) width: u16,
@ -16,6 +17,7 @@ pub(crate) struct Discovery {
pub(crate) frames: Option<u32>,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct DiscoveryLite {
pub(crate) format: InternalFormat,
pub(crate) width: u16,

View File

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use std::{collections::HashSet, sync::OnceLock};
use crate::{
@ -14,13 +17,16 @@ use tokio::io::AsyncReadExt;
use super::{Discovery, DiscoveryLite};
const MP4: &str = "mp4";
const WEBP: &str = "webp_pipe";
const FFMPEG_FORMAT_MAPPINGS: &[(&str, InternalFormat)] = &[
("apng", InternalFormat::Animation(AnimationFormat::Apng)),
("gif", InternalFormat::Animation(AnimationFormat::Gif)),
("mp4", InternalFormat::Video(InternalVideoFormat::Mp4)),
(MP4, InternalFormat::Video(InternalVideoFormat::Mp4)),
("png_pipe", InternalFormat::Image(ImageFormat::Png)),
("webm", InternalFormat::Video(InternalVideoFormat::Webm)),
("webp_pipe", InternalFormat::Image(ImageFormat::Webp)),
(WEBP, InternalFormat::Image(ImageFormat::Webp)),
];
#[derive(Debug, serde::Deserialize)]
@ -229,7 +235,7 @@ where
let output: FfMpegDiscovery = serde_json::from_slice(&output).map_err(FfMpegError::Json)?;
parse_discovery_ffmpeg(output)
parse_discovery(output)
}
async fn pixel_format<F, Fut>(f: F) -> Result<String, FfMpegError>
@ -321,9 +327,7 @@ fn parse_pixel_formats(formats: PixelFormatOutput) -> HashSet<String> {
.collect()
}
fn parse_discovery_ffmpeg(
discovery: FfMpegDiscovery,
) -> Result<Option<DiscoveryLite>, FfMpegError> {
fn parse_discovery(discovery: FfMpegDiscovery) -> Result<Option<DiscoveryLite>, FfMpegError> {
let FfMpegDiscovery {
streams:
[FfMpegStream {
@ -340,7 +344,7 @@ fn parse_discovery_ffmpeg(
{
let frames = nb_read_frames.and_then(|frames| frames.parse().ok());
if *name == "mp4" && frames.map(|nb| nb == 1).unwrap_or(false) {
if *name == MP4 && frames.map(|nb| nb == 1).unwrap_or(false) {
// Might be AVIF, ffmpeg incorrectly detects AVIF as single-framed mp4 even when
// animated
@ -348,11 +352,11 @@ fn parse_discovery_ffmpeg(
format: InternalFormat::Animation(AnimationFormat::Avif),
width,
height,
frames,
frames: None,
}));
}
if *name == "webp" && (frames.is_none() || width == 0 || height == 0) {
if *name == WEBP && (frames.is_none() || width == 0 || height == 0) {
// Might be Animated Webp, ffmpeg incorrectly detects animated webp as having no frames
// and 0 dimensions
@ -368,7 +372,7 @@ fn parse_discovery_ffmpeg(
format: *value,
width,
height,
frames,
frames: frames.and_then(|frames| if frames > 1 { Some(frames) } else { None }),
}));
}

View File

@ -0,0 +1,14 @@
{
"programs": [
],
"streams": [
{
"width": 0,
"height": 0
}
],
"format": {
"format_name": "webp_pipe"
}
}

View File

@ -0,0 +1,177 @@
use crate::formats::{AnimationFormat, ImageFormat, InternalFormat, InternalVideoFormat};
use super::{DiscoveryLite, FfMpegDiscovery, PixelFormatOutput};
fn details_tests() -> [(&'static str, Option<DiscoveryLite>); 11] {
[
(
"animated_webp",
Some(DiscoveryLite {
format: InternalFormat::Animation(AnimationFormat::Webp),
width: 0,
height: 0,
frames: None,
}),
),
(
"apng",
Some(DiscoveryLite {
format: InternalFormat::Animation(AnimationFormat::Apng),
width: 112,
height: 112,
frames: Some(27),
}),
),
(
"avif",
Some(DiscoveryLite {
format: InternalFormat::Animation(AnimationFormat::Avif),
width: 1920,
height: 1080,
frames: None,
}),
),
(
"gif",
Some(DiscoveryLite {
format: InternalFormat::Animation(AnimationFormat::Gif),
width: 160,
height: 227,
frames: Some(28),
}),
),
("jpeg", None),
("jxl", None),
(
"mp4",
Some(DiscoveryLite {
format: InternalFormat::Video(InternalVideoFormat::Mp4),
width: 852,
height: 480,
frames: Some(35364),
}),
),
(
"png",
Some(DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Png),
width: 450,
height: 401,
frames: None,
}),
),
(
"webm",
Some(DiscoveryLite {
format: InternalFormat::Video(InternalVideoFormat::Webm),
width: 640,
height: 480,
frames: Some(34650),
}),
),
(
"webm_av1",
Some(DiscoveryLite {
format: InternalFormat::Video(InternalVideoFormat::Webm),
width: 112,
height: 112,
frames: Some(27),
}),
),
(
"webp",
Some(DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Webp),
width: 1920,
height: 1080,
frames: None,
}),
),
]
}
#[test]
fn parse_discovery() {
for (case, expected) in details_tests() {
let string = std::fs::read_to_string(format!(
"./src/discover/ffmpeg/ffprobe_6_0_{case}_details.json"
))
.expect("Read file");
let json: FfMpegDiscovery = serde_json::from_str(&string).expect("Valid json");
let output = super::parse_discovery(json).expect("Parsed details");
assert_eq!(output, expected);
}
}
const ALPHA_PIXEL_FORMATS: &[&str] = &[
"pal8",
"argb",
"rgba",
"abgr",
"bgra",
"yuva420p",
"ya8",
"yuva422p",
"yuva444p",
"yuva420p9be",
"yuva420p9le",
"yuva422p9be",
"yuva422p9le",
"yuva444p9be",
"yuva444p9le",
"yuva420p10be",
"yuva420p10le",
"yuva422p10be",
"yuva422p10le",
"yuva444p10be",
"yuva444p10le",
"yuva420p16be",
"yuva420p16le",
"yuva422p16be",
"yuva422p16le",
"yuva444p16be",
"yuva444p16le",
"rgba64be",
"rgba64le",
"bgra64be",
"bgra64le",
"ya16be",
"ya16le",
"gbrap",
"gbrap16be",
"gbrap16le",
"ayuv64le",
"ayuv64be",
"gbrap12be",
"gbrap12le",
"gbrap10be",
"gbrap10le",
"gbrapf32be",
"gbrapf32le",
"yuva422p12be",
"yuva422p12le",
"yuva444p12be",
"yuva444p12le",
"vuya",
"rgbaf16be",
"rgbaf16le",
"rgbaf32be",
"rgbaf32le",
];
#[test]
fn parse_pixel_formats() {
let formats = std::fs::read_to_string("./src/discover/ffmpeg/ffprobe_6_0_pixel_formats.json")
.expect("Read file");
let json: PixelFormatOutput = serde_json::from_str(&formats).expect("Valid json");
let output = super::parse_pixel_formats(json);
for format in ALPHA_PIXEL_FORMATS {
assert!(output.contains(*format), "Doesn't contain {format}");
}
}

View File

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use actix_web::web::Bytes;
use futures_util::Stream;
use tokio::io::AsyncReadExt;
@ -27,6 +30,24 @@ struct Geometry {
height: u16,
}
impl Discovery {
fn lite(self) -> DiscoveryLite {
let Discovery {
input,
width,
height,
frames,
} = self;
DiscoveryLite {
format: input.internal_format(),
width,
height,
frames,
}
}
}
pub(super) async fn discover_bytes_lite(bytes: Bytes) -> Result<DiscoveryLite, MagickError> {
discover_file_lite(move |mut file| async move {
file.write_from_bytes(bytes)
@ -154,19 +175,7 @@ where
F: FnOnce(crate::file::File) -> Fut,
Fut: std::future::Future<Output = Result<crate::file::File, MagickError>>,
{
let Discovery {
input,
width,
height,
frames,
} = discover_file(f).await?;
Ok(DiscoveryLite {
format: input.internal_format(),
width,
height,
frames,
})
discover_file(f).await.map(Discovery::lite)
}
async fn discover_file<F, Fut>(f: F) -> Result<Discovery, MagickError>
@ -346,6 +355,12 @@ fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickErro
})
}
}
"WEBM" => Ok(Discovery {
input: InputFile::Video(VideoFormat::Webm { alpha: true }),
width,
height,
frames: Some(frames),
}),
otherwise => todo!("Error {otherwise}"),
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
use crate::formats::{AnimationFormat, ImageFormat, InternalFormat, InternalVideoFormat};
use super::{DiscoveryLite, MagickDiscovery};
fn details_tests() -> [(&'static str, DiscoveryLite); 9] {
[
(
"animated_webp",
DiscoveryLite {
format: InternalFormat::Animation(AnimationFormat::Webp),
width: 112,
height: 112,
frames: Some(27),
},
),
(
"avif",
DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Avif),
width: 1920,
height: 1080,
frames: None,
},
),
(
"gif",
DiscoveryLite {
format: InternalFormat::Animation(AnimationFormat::Gif),
width: 414,
height: 261,
frames: Some(17),
},
),
(
"jpeg",
DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Jpeg),
width: 1920,
height: 1080,
frames: None,
},
),
(
"jxl",
DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Jxl),
width: 1920,
height: 1080,
frames: None,
},
),
(
"mp4",
DiscoveryLite {
format: InternalFormat::Video(InternalVideoFormat::Mp4),
width: 414,
height: 261,
frames: Some(17),
},
),
(
"png",
DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Png),
width: 497,
height: 694,
frames: None,
},
),
(
"webm",
DiscoveryLite {
format: InternalFormat::Video(InternalVideoFormat::Webm),
width: 112,
height: 112,
frames: Some(27),
},
),
(
"webp",
DiscoveryLite {
format: InternalFormat::Image(ImageFormat::Webp),
width: 1920,
height: 1080,
frames: None,
},
),
]
}
#[test]
fn parse_discovery() {
for (case, expected) in details_tests() {
let string = std::fs::read_to_string(format!(
"./src/discover/magick/magick_7_1_1_{case}_details.json"
))
.expect("Read file");
let json: Vec<MagickDiscovery> = serde_json::from_str(&string).expect("Valid json");
let output = super::parse_discovery(json).expect("Parsed details").lite();
assert_eq!(output, expected);
}
}

View File

@ -1,6 +1,3 @@
#[cfg(test)]
mod tests;
use crate::{
formats::InternalVideoFormat,
process::{Process, ProcessError},

View File

@ -1,141 +0,0 @@
use super::{Details, DetailsOutput, PixelFormatOutput};
fn details_tests() -> [(&'static str, Option<Details>); 10] {
[
(
"apng",
Some(Details {
mime_type: crate::formats::mimes::image_apng(),
width: 112,
height: 112,
frames: Some(27),
}),
),
("avif", None),
(
"gif",
Some(Details {
mime_type: mime::IMAGE_GIF,
width: 160,
height: 227,
frames: Some(28),
}),
),
("jpeg", None),
("jxl", None),
(
"mp4",
Some(Details {
mime_type: crate::formats::mimes::video_mp4(),
width: 852,
height: 480,
frames: Some(35364),
}),
),
("png", None),
(
"webm",
Some(Details {
mime_type: crate::formats::mimes::video_webm(),
width: 640,
height: 480,
frames: Some(34650),
}),
),
(
"webm_av1",
Some(Details {
mime_type: crate::formats::mimes::video_webm(),
width: 112,
height: 112,
frames: Some(27),
}),
),
("webp", None),
]
}
#[test]
fn parse_details() {
for (case, expected) in details_tests() {
let string =
std::fs::read_to_string(format!("./src/ffmpeg/ffprobe_6_0_{case}_details.json"))
.expect("Read file");
let json: DetailsOutput = serde_json::from_str(&string).expect("Valid json");
let output = super::parse_details(json).expect("Parsed details");
assert_eq!(output, expected);
}
}
const ALPHA_PIXEL_FORMATS: &[&str] = &[
"pal8",
"argb",
"rgba",
"abgr",
"bgra",
"yuva420p",
"ya8",
"yuva422p",
"yuva444p",
"yuva420p9be",
"yuva420p9le",
"yuva422p9be",
"yuva422p9le",
"yuva444p9be",
"yuva444p9le",
"yuva420p10be",
"yuva420p10le",
"yuva422p10be",
"yuva422p10le",
"yuva444p10be",
"yuva444p10le",
"yuva420p16be",
"yuva420p16le",
"yuva422p16be",
"yuva422p16le",
"yuva444p16be",
"yuva444p16le",
"rgba64be",
"rgba64le",
"bgra64be",
"bgra64le",
"ya16be",
"ya16le",
"gbrap",
"gbrap16be",
"gbrap16le",
"ayuv64le",
"ayuv64be",
"gbrap12be",
"gbrap12le",
"gbrap10be",
"gbrap10le",
"gbrapf32be",
"gbrapf32le",
"yuva422p12be",
"yuva422p12le",
"yuva444p12be",
"yuva444p12le",
"vuya",
"rgbaf16be",
"rgbaf16le",
"rgbaf32be",
"rgbaf32le",
];
#[test]
fn parse_pixel_formats() {
let formats =
std::fs::read_to_string("./src/ffmpeg/ffprobe_6_0_pixel_formats.json").expect("Read file");
let json: PixelFormatOutput = serde_json::from_str(&formats).expect("Valid json");
let output = super::parse_pixel_formats(json);
for format in ALPHA_PIXEL_FORMATS {
assert!(output.contains(*format), "Doesn't contain {format}");
}
}

View File

@ -17,14 +17,14 @@ pub(crate) struct PrescribedFormats {
pub(crate) allow_audio: bool,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum InputFile {
Image(ImageInput),
Animation(AnimationInput),
Video(VideoFormat),
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub(crate) enum InternalFormat {
Image(ImageFormat),
Animation(AnimationFormat),

View File

@ -1,6 +1,3 @@
#[cfg(test)]
mod tests;
use crate::{
formats::ProcessableFormat,
process::{Process, ProcessError},

View File

@ -1,93 +0,0 @@
use super::{Details, DetailsOutput};
fn details_tests() -> [(&'static str, Details); 8] {
[
(
"avif",
Details {
mime_type: super::image_avif(),
width: 1920,
height: 1080,
frames: None,
},
),
(
"gif",
Details {
mime_type: mime::IMAGE_GIF,
width: 414,
height: 261,
frames: Some(17),
},
),
(
"jpeg",
Details {
mime_type: mime::IMAGE_JPEG,
width: 1920,
height: 1080,
frames: None,
},
),
(
"jxl",
Details {
mime_type: super::image_jxl(),
width: 1920,
height: 1080,
frames: None,
},
),
(
"mp4",
Details {
mime_type: super::video_mp4(),
width: 414,
height: 261,
frames: Some(17),
},
),
(
"png",
Details {
mime_type: mime::IMAGE_PNG,
width: 497,
height: 694,
frames: None,
},
),
(
"webm",
Details {
mime_type: super::video_webm(),
width: 112,
height: 112,
frames: Some(27),
},
),
(
"webp",
Details {
mime_type: super::image_webp(),
width: 1920,
height: 1080,
frames: None,
},
),
]
}
#[test]
fn parse_details() {
for (case, expected) in details_tests() {
let string =
std::fs::read_to_string(format!("./src/magick/magick_7_1_1_{case}_details.json"))
.expect("Read file");
let json: Vec<DetailsOutput> = serde_json::from_str(&string).expect("Valid json");
let output = super::parse_details(json).expect("Parsed details");
assert_eq!(output, expected);
}
}