mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 19:31:35 +00:00
Use json for ffmpeg, imagemagick details parsing
This commit is contained in:
parent
fa74e3c82d
commit
1694f49436
23 changed files with 11742 additions and 99 deletions
|
@ -66,6 +66,9 @@ pub(crate) enum UploadError {
|
||||||
#[error("Error in store")]
|
#[error("Error in store")]
|
||||||
Store(#[source] crate::store::StoreError),
|
Store(#[source] crate::store::StoreError),
|
||||||
|
|
||||||
|
#[error("Error parsing image details")]
|
||||||
|
ParseDetails(#[from] crate::magick::ParseDetailsError),
|
||||||
|
|
||||||
#[error("Provided process path is invalid")]
|
#[error("Provided process path is invalid")]
|
||||||
ParsePath,
|
ParsePath,
|
||||||
|
|
||||||
|
|
123
src/ffmpeg.rs
123
src/ffmpeg.rs
|
@ -1,3 +1,6 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{AudioCodec, ImageFormat, MediaConfiguration, VideoCodec},
|
config::{AudioCodec, ImageFormat, MediaConfiguration, VideoCodec},
|
||||||
error::{Error, UploadError},
|
error::{Error, UploadError},
|
||||||
|
@ -368,6 +371,22 @@ pub(crate) async fn details_bytes(input: Bytes) -> Result<Option<Details>, Error
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct PixelFormatOutput {
|
||||||
|
pixel_formats: Vec<PixelFormat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct PixelFormat {
|
||||||
|
name: String,
|
||||||
|
flags: Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Flags {
|
||||||
|
alpha: usize,
|
||||||
|
}
|
||||||
|
|
||||||
async fn alpha_pixel_formats() -> Result<HashSet<String>, Error> {
|
async fn alpha_pixel_formats() -> Result<HashSet<String>, Error> {
|
||||||
let process = Process::run(
|
let process = Process::run(
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
|
@ -378,33 +397,49 @@ async fn alpha_pixel_formats() -> Result<HashSet<String>, Error> {
|
||||||
"pixel_format=name:flags=alpha",
|
"pixel_format=name:flags=alpha",
|
||||||
"-of",
|
"-of",
|
||||||
"compact=p=0",
|
"compact=p=0",
|
||||||
|
"-print_format",
|
||||||
|
"json",
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
process.read().read_to_end(&mut output).await?;
|
process.read().read_to_end(&mut output).await?;
|
||||||
let output = String::from_utf8_lossy(&output);
|
|
||||||
let formats = output
|
let formats: PixelFormatOutput = serde_json::from_slice(&output)?;
|
||||||
.split('\n')
|
|
||||||
.filter_map(|format| {
|
Ok(parse_pixel_formats(formats))
|
||||||
if format.is_empty() {
|
}
|
||||||
|
|
||||||
|
fn parse_pixel_formats(formats: PixelFormatOutput) -> HashSet<String> {
|
||||||
|
formats
|
||||||
|
.pixel_formats
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|PixelFormat { name, flags }| {
|
||||||
|
if flags.alpha == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !format.ends_with('1') {
|
Some(name)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(
|
|
||||||
format
|
|
||||||
.trim_start_matches("name=")
|
|
||||||
.trim_end_matches("|flags:alpha=1")
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
Ok(formats)
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct DetailsOutput {
|
||||||
|
streams: [Stream; 1],
|
||||||
|
format: Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Stream {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
nb_read_frames: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Format {
|
||||||
|
format_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(f))]
|
#[tracing::instrument(skip(f))]
|
||||||
|
@ -435,46 +470,35 @@ where
|
||||||
"stream=width,height,nb_read_frames:format=format_name",
|
"stream=width,height,nb_read_frames:format=format_name",
|
||||||
"-of",
|
"-of",
|
||||||
"default=noprint_wrappers=1:nokey=1",
|
"default=noprint_wrappers=1:nokey=1",
|
||||||
|
"-print_format",
|
||||||
|
"json",
|
||||||
input_file_str,
|
input_file_str,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
process.read().read_to_end(&mut output).await?;
|
process.read().read_to_end(&mut output).await?;
|
||||||
let output = String::from_utf8_lossy(&output);
|
|
||||||
tokio::fs::remove_file(input_file_str).await?;
|
tokio::fs::remove_file(input_file_str).await?;
|
||||||
|
|
||||||
|
let output: DetailsOutput = serde_json::from_slice(&output)?;
|
||||||
|
|
||||||
parse_details(output)
|
parse_details(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_details(output: std::borrow::Cow<'_, str>) -> Result<Option<Details>, Error> {
|
fn parse_details(output: DetailsOutput) -> Result<Option<Details>, Error> {
|
||||||
tracing::debug!("OUTPUT: {}", output);
|
tracing::debug!("OUTPUT: {:?}", output);
|
||||||
|
|
||||||
let mut lines = output.lines();
|
let [stream] = output.streams;
|
||||||
|
let Format { format_name } = output.format;
|
||||||
let width = match lines.next() {
|
|
||||||
Some(line) => line,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let height = match lines.next() {
|
|
||||||
Some(line) => line,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let frames = match lines.next() {
|
|
||||||
Some(line) => line,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let formats = match lines.next() {
|
|
||||||
Some(line) => line,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (k, v) in FORMAT_MAPPINGS {
|
for (k, v) in FORMAT_MAPPINGS {
|
||||||
if formats.contains(k) {
|
if format_name.contains(k) {
|
||||||
return parse_details_inner(width, height, frames, *v);
|
return parse_details_inner(
|
||||||
|
stream.width,
|
||||||
|
stream.height,
|
||||||
|
stream.nb_read_frames.as_deref(),
|
||||||
|
*v,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,14 +506,15 @@ fn parse_details(output: std::borrow::Cow<'_, str>) -> Result<Option<Details>, E
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_details_inner(
|
fn parse_details_inner(
|
||||||
width: &str,
|
width: usize,
|
||||||
height: &str,
|
height: usize,
|
||||||
frames: &str,
|
frames: Option<&str>,
|
||||||
format: VideoFormat,
|
format: VideoFormat,
|
||||||
) -> Result<Option<Details>, Error> {
|
) -> Result<Option<Details>, Error> {
|
||||||
let width = width.parse().map_err(|_| UploadError::UnsupportedFormat)?;
|
let frames = frames
|
||||||
let height = height.parse().map_err(|_| UploadError::UnsupportedFormat)?;
|
.map(|frames| frames.parse().map_err(|_| UploadError::UnsupportedFormat))
|
||||||
let frames = frames.parse().map_err(|_| UploadError::UnsupportedFormat)?;
|
.transpose()?
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
// Probably a still image. ffmpeg thinks AVIF is an mp4
|
// Probably a still image. ffmpeg thinks AVIF is an mp4
|
||||||
if frames == 1 {
|
if frames == 1 {
|
||||||
|
|
15
src/ffmpeg/ffprobe_6_0_avif_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_avif_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"nb_read_frames": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "mov,mp4,m4a,3gp,3g2,mj2"
|
||||||
|
}
|
||||||
|
}
|
15
src/ffmpeg/ffprobe_6_0_gif_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_gif_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 160,
|
||||||
|
"height": 227,
|
||||||
|
"nb_read_frames": "28"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "gif"
|
||||||
|
}
|
||||||
|
}
|
15
src/ffmpeg/ffprobe_6_0_jpeg_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_jpeg_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"nb_read_frames": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "image2"
|
||||||
|
}
|
||||||
|
}
|
14
src/ffmpeg/ffprobe_6_0_jxl_details.json
Normal file
14
src/ffmpeg/ffprobe_6_0_jxl_details.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 0,
|
||||||
|
"height": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "jpegxl_pipe"
|
||||||
|
}
|
||||||
|
}
|
15
src/ffmpeg/ffprobe_6_0_mp4_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_mp4_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 852,
|
||||||
|
"height": 480,
|
||||||
|
"nb_read_frames": "35364"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "mov,mp4,m4a,3gp,3g2,mj2"
|
||||||
|
}
|
||||||
|
}
|
1336
src/ffmpeg/ffprobe_6_0_pixel_formats.json
Normal file
1336
src/ffmpeg/ffprobe_6_0_pixel_formats.json
Normal file
File diff suppressed because it is too large
Load diff
15
src/ffmpeg/ffprobe_6_0_png_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_png_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 450,
|
||||||
|
"height": 401,
|
||||||
|
"nb_read_frames": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "png_pipe"
|
||||||
|
}
|
||||||
|
}
|
15
src/ffmpeg/ffprobe_6_0_webm_av1_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_webm_av1_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 112,
|
||||||
|
"height": 112,
|
||||||
|
"nb_read_frames": "27"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "matroska,webm"
|
||||||
|
}
|
||||||
|
}
|
15
src/ffmpeg/ffprobe_6_0_webm_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_webm_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 640,
|
||||||
|
"height": 480,
|
||||||
|
"nb_read_frames": "34650"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "matroska,webm"
|
||||||
|
}
|
||||||
|
}
|
15
src/ffmpeg/ffprobe_6_0_webp_details.json
Normal file
15
src/ffmpeg/ffprobe_6_0_webp_details.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"nb_read_frames": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "webp_pipe"
|
||||||
|
}
|
||||||
|
}
|
132
src/ffmpeg/tests.rs
Normal file
132
src/ffmpeg/tests.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
use super::{Details, DetailsOutput, PixelFormatOutput};
|
||||||
|
|
||||||
|
fn details_tests() -> [(&'static str, Option<Details>); 9] {
|
||||||
|
[
|
||||||
|
("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::magick::video_mp4(),
|
||||||
|
width: 852,
|
||||||
|
height: 480,
|
||||||
|
frames: Some(35364),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
("png", None),
|
||||||
|
(
|
||||||
|
"webm",
|
||||||
|
Some(Details {
|
||||||
|
mime_type: crate::magick::video_webm(),
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
frames: Some(34650),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"webm_av1",
|
||||||
|
Some(Details {
|
||||||
|
mime_type: crate::magick::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}");
|
||||||
|
}
|
||||||
|
}
|
115
src/magick.rs
115
src/magick.rs
|
@ -1,3 +1,6 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ImageFormat, VideoCodec},
|
config::{ImageFormat, VideoCodec},
|
||||||
error::{Error, UploadError},
|
error::{Error, UploadError},
|
||||||
|
@ -123,7 +126,7 @@ impl ValidInputType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub(crate) struct Details {
|
pub(crate) struct Details {
|
||||||
pub(crate) mime_type: mime::Mime,
|
pub(crate) mime_type: mime::Mime,
|
||||||
pub(crate) width: usize,
|
pub(crate) width: usize,
|
||||||
|
@ -175,18 +178,33 @@ pub(crate) async fn details_bytes(
|
||||||
"-".to_owned()
|
"-".to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let process = Process::run(
|
let process = Process::run("magick", &["convert", "-ping", &last_arg, "JSON:"])?;
|
||||||
"magick",
|
|
||||||
&["identify", "-ping", "-format", "%w %h | %m\n", &last_arg],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut reader = process.bytes_read(input);
|
let mut reader = process.bytes_read(input);
|
||||||
|
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
reader.read_to_end(&mut bytes).await?;
|
reader.read_to_end(&mut bytes).await?;
|
||||||
let s = String::from_utf8_lossy(&bytes);
|
|
||||||
|
|
||||||
parse_details(s)
|
let details_output: Vec<DetailsOutput> = serde_json::from_slice(&bytes)?;
|
||||||
|
|
||||||
|
parse_details(details_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct DetailsOutput {
|
||||||
|
image: Image,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Image {
|
||||||
|
format: String,
|
||||||
|
geometry: Geometry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Geometry {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(store))]
|
#[tracing::instrument(skip(store))]
|
||||||
|
@ -217,27 +235,21 @@ pub(crate) async fn details_store<S: Store + 'static>(
|
||||||
"-".to_owned()
|
"-".to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let process = Process::run(
|
let process = Process::run("magick", &["convert", "-ping", &last_arg, "JSON:"])?;
|
||||||
"magick",
|
|
||||||
&["identify", "-ping", "-format", "%w %h | %m\n", &last_arg],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut reader = process.store_read(store, identifier);
|
let mut reader = process.store_read(store, identifier);
|
||||||
|
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
reader.read_to_end(&mut output).await?;
|
reader.read_to_end(&mut output).await?;
|
||||||
|
|
||||||
let s = String::from_utf8_lossy(&output);
|
let details_output: Vec<DetailsOutput> = serde_json::from_slice(&output)?;
|
||||||
|
|
||||||
parse_details(s)
|
parse_details(details_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub(crate) async fn details_file(path_str: &str) -> Result<Details, Error> {
|
pub(crate) async fn details_file(path_str: &str) -> Result<Details, Error> {
|
||||||
let process = Process::run(
|
let process = Process::run("magick", &["convert", "-ping", path_str, "JSON:"])?;
|
||||||
"magick",
|
|
||||||
&["identify", "-ping", "-format", "%w %h | %m\n", path_str],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut reader = process.read();
|
let mut reader = process.read();
|
||||||
|
|
||||||
|
@ -245,46 +257,49 @@ pub(crate) async fn details_file(path_str: &str) -> Result<Details, Error> {
|
||||||
reader.read_to_end(&mut output).await?;
|
reader.read_to_end(&mut output).await?;
|
||||||
tokio::fs::remove_file(path_str).await?;
|
tokio::fs::remove_file(path_str).await?;
|
||||||
|
|
||||||
let s = String::from_utf8_lossy(&output);
|
let details_output: Vec<DetailsOutput> = serde_json::from_slice(&output)?;
|
||||||
|
|
||||||
parse_details(s)
|
parse_details(details_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
|
#[derive(Debug, thiserror::Error)]
|
||||||
let frames = s.lines().count();
|
pub(crate) enum ParseDetailsError {
|
||||||
|
#[error("No frames present in image")]
|
||||||
|
NoFrames,
|
||||||
|
|
||||||
let mut lines = s.lines();
|
#[error("Multiple image formats used in same file")]
|
||||||
let first = lines.next().ok_or(UploadError::UnsupportedFormat)?;
|
MixedFormats,
|
||||||
|
|
||||||
let mut segments = first.split('|');
|
#[error("Format is unsupported: {0}")]
|
||||||
|
Unsupported(String),
|
||||||
|
}
|
||||||
|
|
||||||
let dimensions = segments
|
fn parse_details(details_output: Vec<DetailsOutput>) -> Result<Details, Error> {
|
||||||
.next()
|
let frames = details_output.len();
|
||||||
.ok_or(UploadError::UnsupportedFormat)?
|
|
||||||
.trim();
|
|
||||||
tracing::debug!("dimensions: {}", dimensions);
|
|
||||||
let mut dims = dimensions.split(' ');
|
|
||||||
let width = dims
|
|
||||||
.next()
|
|
||||||
.ok_or(UploadError::UnsupportedFormat)?
|
|
||||||
.trim()
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| UploadError::UnsupportedFormat)?;
|
|
||||||
let height = dims
|
|
||||||
.next()
|
|
||||||
.ok_or(UploadError::UnsupportedFormat)?
|
|
||||||
.trim()
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| UploadError::UnsupportedFormat)?;
|
|
||||||
|
|
||||||
let format = segments
|
if frames == 0 {
|
||||||
.next()
|
return Err(ParseDetailsError::NoFrames.into());
|
||||||
.ok_or(UploadError::UnsupportedFormat)?
|
}
|
||||||
.trim();
|
|
||||||
|
let width = details_output
|
||||||
|
.iter()
|
||||||
|
.map(|details| details.image.geometry.width)
|
||||||
|
.max()
|
||||||
|
.expect("Nonempty vector");
|
||||||
|
let height = details_output
|
||||||
|
.iter()
|
||||||
|
.map(|details| details.image.geometry.height)
|
||||||
|
.max()
|
||||||
|
.expect("Nonempty vector");
|
||||||
|
|
||||||
|
let format = details_output[0].image.format.as_str();
|
||||||
tracing::debug!("format: {}", format);
|
tracing::debug!("format: {}", format);
|
||||||
|
|
||||||
if !lines.all(|item| item.ends_with(format)) {
|
if !details_output
|
||||||
return Err(UploadError::UnsupportedFormat.into());
|
.iter()
|
||||||
|
.all(|details| &details.image.format == format)
|
||||||
|
{
|
||||||
|
return Err(ParseDetailsError::MixedFormats.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mime_type = match format {
|
let mime_type = match format {
|
||||||
|
@ -296,7 +311,7 @@ fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
|
||||||
"JXL" => image_jxl(),
|
"JXL" => image_jxl(),
|
||||||
"PNG" => mime::IMAGE_PNG,
|
"PNG" => mime::IMAGE_PNG,
|
||||||
"WEBP" => image_webp(),
|
"WEBP" => image_webp(),
|
||||||
_ => return Err(UploadError::UnsupportedFormat.into()),
|
e => return Err(ParseDetailsError::Unsupported(String::from(e)).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Details {
|
Ok(Details {
|
||||||
|
@ -368,7 +383,7 @@ impl Details {
|
||||||
(mime::IMAGE, subtype) if subtype.as_str() == "jxl" => ValidInputType::Jxl,
|
(mime::IMAGE, subtype) if subtype.as_str() == "jxl" => ValidInputType::Jxl,
|
||||||
(mime::IMAGE, mime::PNG) => ValidInputType::Png,
|
(mime::IMAGE, mime::PNG) => ValidInputType::Png,
|
||||||
(mime::IMAGE, subtype) if subtype.as_str() == "webp" => ValidInputType::Webp,
|
(mime::IMAGE, subtype) if subtype.as_str() == "webp" => ValidInputType::Webp,
|
||||||
_ => return Err(UploadError::UnsupportedFormat.into()),
|
_ => return Err(ParseDetailsError::Unsupported(self.mime_type.to_string()).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(input_type)
|
Ok(input_type)
|
||||||
|
|
122
src/magick/magick_7_1_1_avif_details.json
Normal file
122
src/magick/magick_7_1_1_avif_details.json
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
[{
|
||||||
|
"version": "1.0",
|
||||||
|
"image": {
|
||||||
|
"name": "out.avif",
|
||||||
|
"baseName": "out.avif",
|
||||||
|
"permissions": 644,
|
||||||
|
"format": "AVIF",
|
||||||
|
"formatDescription": "AV1 Image File Format",
|
||||||
|
"mimeType": "image/avif",
|
||||||
|
"class": "DirectClass",
|
||||||
|
"geometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"units": "Undefined",
|
||||||
|
"type": "Bilevel",
|
||||||
|
"baseType": "Undefined",
|
||||||
|
"endianness": "Undefined",
|
||||||
|
"colorspace": "sRGB",
|
||||||
|
"depth": 1,
|
||||||
|
"baseDepth": 8,
|
||||||
|
"channelDepth": {
|
||||||
|
"red": 1,
|
||||||
|
"green": 1,
|
||||||
|
"blue": 1
|
||||||
|
},
|
||||||
|
"pixels": 6220800,
|
||||||
|
"imageStatistics": {
|
||||||
|
"Overall": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channelStatistics": {
|
||||||
|
"red": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renderingIntent": "Perceptual",
|
||||||
|
"gamma": 0.454545,
|
||||||
|
"chromaticity": {
|
||||||
|
"redPrimary": {
|
||||||
|
"x": 0.64,
|
||||||
|
"y": 0.33
|
||||||
|
},
|
||||||
|
"greenPrimary": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 0.6
|
||||||
|
},
|
||||||
|
"bluePrimary": {
|
||||||
|
"x": 0.15,
|
||||||
|
"y": 0.06
|
||||||
|
},
|
||||||
|
"whitePrimary": {
|
||||||
|
"x": 0.3127,
|
||||||
|
"y": 0.329
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matteColor": "#BDBDBDBDBDBD",
|
||||||
|
"backgroundColor": "#FFFFFFFFFFFF",
|
||||||
|
"borderColor": "#DFDFDFDFDFDF",
|
||||||
|
"transparentColor": "#000000000000",
|
||||||
|
"interlace": "None",
|
||||||
|
"intensity": "Undefined",
|
||||||
|
"compose": "Over",
|
||||||
|
"pageGeometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"dispose": "Undefined",
|
||||||
|
"iterations": 0,
|
||||||
|
"compression": "Undefined",
|
||||||
|
"orientation": "TopLeft",
|
||||||
|
"properties": {
|
||||||
|
"signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
},
|
||||||
|
"tainted": false,
|
||||||
|
"filesize": "8427B",
|
||||||
|
"numberPixels": "2.0736M",
|
||||||
|
"pixelsPerSecond": "9.82581GB",
|
||||||
|
"userTime": "0.000u",
|
||||||
|
"elapsedTime": "0:01.000",
|
||||||
|
"version": "ImageMagick 7.1.1-11 Q16-HDRI x86_64 f04a7eb33:20230528 https://imagemagick.org"
|
||||||
|
}
|
||||||
|
}]
|
3269
src/magick/magick_7_1_1_gif_details.json
Normal file
3269
src/magick/magick_7_1_1_gif_details.json
Normal file
File diff suppressed because it is too large
Load diff
125
src/magick/magick_7_1_1_jpeg_details.json
Normal file
125
src/magick/magick_7_1_1_jpeg_details.json
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
[{
|
||||||
|
"version": "1.0",
|
||||||
|
"image": {
|
||||||
|
"name": "out.jpg",
|
||||||
|
"baseName": "out.jpg",
|
||||||
|
"permissions": 644,
|
||||||
|
"format": "JPEG",
|
||||||
|
"formatDescription": "Joint Photographic Experts Group JFIF format",
|
||||||
|
"mimeType": "image/jpeg",
|
||||||
|
"class": "DirectClass",
|
||||||
|
"geometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"units": "Undefined",
|
||||||
|
"type": "Bilevel",
|
||||||
|
"baseType": "Undefined",
|
||||||
|
"endianness": "Undefined",
|
||||||
|
"colorspace": "sRGB",
|
||||||
|
"depth": 1,
|
||||||
|
"baseDepth": 8,
|
||||||
|
"channelDepth": {
|
||||||
|
"red": 1,
|
||||||
|
"green": 1,
|
||||||
|
"blue": 1
|
||||||
|
},
|
||||||
|
"pixels": 6220800,
|
||||||
|
"imageStatistics": {
|
||||||
|
"Overall": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channelStatistics": {
|
||||||
|
"red": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renderingIntent": "Perceptual",
|
||||||
|
"gamma": 0.454545,
|
||||||
|
"chromaticity": {
|
||||||
|
"redPrimary": {
|
||||||
|
"x": 0.64,
|
||||||
|
"y": 0.33
|
||||||
|
},
|
||||||
|
"greenPrimary": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 0.6
|
||||||
|
},
|
||||||
|
"bluePrimary": {
|
||||||
|
"x": 0.15,
|
||||||
|
"y": 0.06
|
||||||
|
},
|
||||||
|
"whitePrimary": {
|
||||||
|
"x": 0.3127,
|
||||||
|
"y": 0.329
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matteColor": "#BDBDBDBDBDBD",
|
||||||
|
"backgroundColor": "#FFFFFFFFFFFF",
|
||||||
|
"borderColor": "#DFDFDFDFDFDF",
|
||||||
|
"transparentColor": "#000000000000",
|
||||||
|
"interlace": "None",
|
||||||
|
"intensity": "Undefined",
|
||||||
|
"compose": "Over",
|
||||||
|
"pageGeometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"dispose": "Undefined",
|
||||||
|
"iterations": 0,
|
||||||
|
"compression": "JPEG",
|
||||||
|
"quality": 92,
|
||||||
|
"orientation": "Undefined",
|
||||||
|
"properties": {
|
||||||
|
"jpeg:colorspace": "2",
|
||||||
|
"jpeg:sampling-factor": "1x1,1x1,1x1",
|
||||||
|
"signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
},
|
||||||
|
"tainted": false,
|
||||||
|
"filesize": "93100B",
|
||||||
|
"numberPixels": "2.0736M",
|
||||||
|
"pixelsPerSecond": "12.0946GB",
|
||||||
|
"userTime": "0.000u",
|
||||||
|
"elapsedTime": "0:01.000",
|
||||||
|
"version": "ImageMagick 7.1.1-11 Q16-HDRI x86_64 f04a7eb33:20230528 https://imagemagick.org"
|
||||||
|
}
|
||||||
|
}]
|
122
src/magick/magick_7_1_1_jxl_details.json
Normal file
122
src/magick/magick_7_1_1_jxl_details.json
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
[{
|
||||||
|
"version": "1.0",
|
||||||
|
"image": {
|
||||||
|
"name": "out.jxl",
|
||||||
|
"baseName": "out.jxl",
|
||||||
|
"permissions": 644,
|
||||||
|
"format": "JXL",
|
||||||
|
"formatDescription": "JPEG XL (ISO/IEC 18181)",
|
||||||
|
"mimeType": "image/jxl",
|
||||||
|
"class": "DirectClass",
|
||||||
|
"geometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"units": "Undefined",
|
||||||
|
"type": "Bilevel",
|
||||||
|
"baseType": "Undefined",
|
||||||
|
"endianness": "Undefined",
|
||||||
|
"colorspace": "sRGB",
|
||||||
|
"depth": 1,
|
||||||
|
"baseDepth": 8,
|
||||||
|
"channelDepth": {
|
||||||
|
"red": 1,
|
||||||
|
"green": 1,
|
||||||
|
"blue": 1
|
||||||
|
},
|
||||||
|
"pixels": 6220800,
|
||||||
|
"imageStatistics": {
|
||||||
|
"Overall": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channelStatistics": {
|
||||||
|
"red": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renderingIntent": "Perceptual",
|
||||||
|
"gamma": 0.454545,
|
||||||
|
"chromaticity": {
|
||||||
|
"redPrimary": {
|
||||||
|
"x": 0.64,
|
||||||
|
"y": 0.33
|
||||||
|
},
|
||||||
|
"greenPrimary": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 0.6
|
||||||
|
},
|
||||||
|
"bluePrimary": {
|
||||||
|
"x": 0.15,
|
||||||
|
"y": 0.06
|
||||||
|
},
|
||||||
|
"whitePrimary": {
|
||||||
|
"x": 0.3127,
|
||||||
|
"y": 0.329
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matteColor": "#BDBDBDBDBDBD",
|
||||||
|
"backgroundColor": "#FFFFFFFFFFFF",
|
||||||
|
"borderColor": "#DFDFDFDFDFDF",
|
||||||
|
"transparentColor": "#000000000000",
|
||||||
|
"interlace": "None",
|
||||||
|
"intensity": "Undefined",
|
||||||
|
"compose": "Over",
|
||||||
|
"pageGeometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"dispose": "Undefined",
|
||||||
|
"iterations": 0,
|
||||||
|
"compression": "Undefined",
|
||||||
|
"orientation": "TopLeft",
|
||||||
|
"properties": {
|
||||||
|
"signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
},
|
||||||
|
"tainted": false,
|
||||||
|
"filesize": "42934B",
|
||||||
|
"numberPixels": "2.0736M",
|
||||||
|
"pixelsPerSecond": "3.79105GB",
|
||||||
|
"userTime": "0.000u",
|
||||||
|
"elapsedTime": "0:01.000",
|
||||||
|
"version": "ImageMagick 7.1.1-11 Q16-HDRI x86_64 f04a7eb33:20230528 https://imagemagick.org"
|
||||||
|
}
|
||||||
|
}]
|
2305
src/magick/magick_7_1_1_mp4_details.json
Normal file
2305
src/magick/magick_7_1_1_mp4_details.json
Normal file
File diff suppressed because it is too large
Load diff
158
src/magick/magick_7_1_1_png_details.json
Normal file
158
src/magick/magick_7_1_1_png_details.json
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
[{
|
||||||
|
"version": "1.0",
|
||||||
|
"image": {
|
||||||
|
"name": "/home/asonix/Pictures/shenzi/Bad_under_Mufasa.png",
|
||||||
|
"baseName": "Bad_under_Mufasa.png",
|
||||||
|
"permissions": 644,
|
||||||
|
"format": "PNG",
|
||||||
|
"formatDescription": "Portable Network Graphics",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"class": "DirectClass",
|
||||||
|
"geometry": {
|
||||||
|
"width": 497,
|
||||||
|
"height": 694,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
"x": 118.11,
|
||||||
|
"y": 118.11
|
||||||
|
},
|
||||||
|
"printSize": {
|
||||||
|
"x": 4.20794,
|
||||||
|
"y": 5.87588
|
||||||
|
},
|
||||||
|
"units": "PixelsPerCentimeter",
|
||||||
|
"type": "Bilevel",
|
||||||
|
"baseType": "Undefined",
|
||||||
|
"endianness": "Undefined",
|
||||||
|
"colorspace": "sRGB",
|
||||||
|
"depth": 1,
|
||||||
|
"baseDepth": 8,
|
||||||
|
"channelDepth": {
|
||||||
|
"alpha": 1,
|
||||||
|
"red": 1,
|
||||||
|
"green": 1,
|
||||||
|
"blue": 1
|
||||||
|
},
|
||||||
|
"pixels": 1379672,
|
||||||
|
"imageStatistics": {
|
||||||
|
"Overall": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channelStatistics": {
|
||||||
|
"alpha": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"red": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 0,
|
||||||
|
"mean": 0,
|
||||||
|
"median": 0,
|
||||||
|
"standardDeviation": 0,
|
||||||
|
"kurtosis": -3,
|
||||||
|
"skewness": 0,
|
||||||
|
"entropy": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alpha": "#00000000",
|
||||||
|
"renderingIntent": "Perceptual",
|
||||||
|
"gamma": 0.454545,
|
||||||
|
"chromaticity": {
|
||||||
|
"redPrimary": {
|
||||||
|
"x": 0.64,
|
||||||
|
"y": 0.33
|
||||||
|
},
|
||||||
|
"greenPrimary": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 0.6
|
||||||
|
},
|
||||||
|
"bluePrimary": {
|
||||||
|
"x": 0.15,
|
||||||
|
"y": 0.06
|
||||||
|
},
|
||||||
|
"whitePrimary": {
|
||||||
|
"x": 0.3127,
|
||||||
|
"y": 0.329
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matteColor": "#BDBDBDBDBDBD",
|
||||||
|
"backgroundColor": "#FFFFFFFFFFFF",
|
||||||
|
"borderColor": "#DFDFDFDFDFDF",
|
||||||
|
"transparentColor": "#000000000000",
|
||||||
|
"interlace": "None",
|
||||||
|
"intensity": "Undefined",
|
||||||
|
"compose": "Over",
|
||||||
|
"pageGeometry": {
|
||||||
|
"width": 497,
|
||||||
|
"height": 694,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"dispose": "Undefined",
|
||||||
|
"iterations": 0,
|
||||||
|
"compression": "Zip",
|
||||||
|
"orientation": "Undefined",
|
||||||
|
"properties": {
|
||||||
|
"icc:copyright": "Public Domain",
|
||||||
|
"icc:description": "Glimpse built-in sRGB",
|
||||||
|
"icc:manufacturer": "GIMP",
|
||||||
|
"icc:model": "sRGB",
|
||||||
|
"png:IHDR.bit-depth-orig": "8",
|
||||||
|
"png:IHDR.bit_depth": "8",
|
||||||
|
"png:IHDR.color-type-orig": "6",
|
||||||
|
"png:IHDR.color_type": "6 (RGBA)",
|
||||||
|
"png:IHDR.interlace_method": "0 (Not interlaced)",
|
||||||
|
"png:IHDR.width,height": "497, 694",
|
||||||
|
"png:tIME": "2020-09-03T00:14:24Z",
|
||||||
|
"signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"icc": {
|
||||||
|
"length": 680
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tainted": false,
|
||||||
|
"filesize": "269655B",
|
||||||
|
"numberPixels": "344918",
|
||||||
|
"pixelsPerSecond": "1.13565GB",
|
||||||
|
"userTime": "0.000u",
|
||||||
|
"elapsedTime": "0:01.000",
|
||||||
|
"version": "ImageMagick 7.1.1-11 Q16-HDRI x86_64 f04a7eb33:20230528 https://imagemagick.org"
|
||||||
|
}
|
||||||
|
}]
|
3682
src/magick/magick_7_1_1_webm_details.json
Normal file
3682
src/magick/magick_7_1_1_webm_details.json
Normal file
File diff suppressed because it is too large
Load diff
122
src/magick/magick_7_1_1_webp_details.json
Normal file
122
src/magick/magick_7_1_1_webp_details.json
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
[{
|
||||||
|
"version": "1.0",
|
||||||
|
"image": {
|
||||||
|
"name": "/home/asonix/Pictures/shenzi/1242635_1372997258512_full.webp",
|
||||||
|
"baseName": "1242635_1372997258512_full.webp",
|
||||||
|
"permissions": 644,
|
||||||
|
"format": "WEBP",
|
||||||
|
"formatDescription": "WebP Image Format",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"class": "DirectClass",
|
||||||
|
"geometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"units": "Undefined",
|
||||||
|
"type": "Bilevel",
|
||||||
|
"baseType": "Undefined",
|
||||||
|
"endianness": "Undefined",
|
||||||
|
"colorspace": "sRGB",
|
||||||
|
"depth": 1,
|
||||||
|
"baseDepth": 8,
|
||||||
|
"channelDepth": {
|
||||||
|
"red": 8,
|
||||||
|
"green": 8,
|
||||||
|
"blue": 1
|
||||||
|
},
|
||||||
|
"pixels": 6220800,
|
||||||
|
"imageStatistics": {
|
||||||
|
"Overall": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 244,
|
||||||
|
"mean": 99.8391,
|
||||||
|
"median": 121.333,
|
||||||
|
"standardDeviation": 43.645,
|
||||||
|
"kurtosis": -0.145553,
|
||||||
|
"skewness": 0.984726,
|
||||||
|
"entropy": 0.701104
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channelStatistics": {
|
||||||
|
"red": {
|
||||||
|
"min": 34,
|
||||||
|
"max": 244,
|
||||||
|
"mean": 166.595,
|
||||||
|
"median": 196,
|
||||||
|
"standardDeviation": 70.8386,
|
||||||
|
"kurtosis": -1.65286,
|
||||||
|
"skewness": -0.357383,
|
||||||
|
"entropy": 0.725609
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 106,
|
||||||
|
"mean": 70.0406,
|
||||||
|
"median": 88,
|
||||||
|
"standardDeviation": 30.5371,
|
||||||
|
"kurtosis": -1.49345,
|
||||||
|
"skewness": -0.455794,
|
||||||
|
"entropy": 0.704115
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"mean": 62.8818,
|
||||||
|
"median": 80,
|
||||||
|
"standardDeviation": 29.5594,
|
||||||
|
"kurtosis": -1.51232,
|
||||||
|
"skewness": -0.389386,
|
||||||
|
"entropy": 0.673587
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renderingIntent": "Perceptual",
|
||||||
|
"gamma": 0.454545,
|
||||||
|
"chromaticity": {
|
||||||
|
"redPrimary": {
|
||||||
|
"x": 0.64,
|
||||||
|
"y": 0.33
|
||||||
|
},
|
||||||
|
"greenPrimary": {
|
||||||
|
"x": 0.3,
|
||||||
|
"y": 0.6
|
||||||
|
},
|
||||||
|
"bluePrimary": {
|
||||||
|
"x": 0.15,
|
||||||
|
"y": 0.06
|
||||||
|
},
|
||||||
|
"whitePrimary": {
|
||||||
|
"x": 0.3127,
|
||||||
|
"y": 0.329
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matteColor": "#BDBDBDBDBDBD",
|
||||||
|
"backgroundColor": "#FFFFFFFFFFFF",
|
||||||
|
"borderColor": "#DFDFDFDFDFDF",
|
||||||
|
"transparentColor": "#000000000000",
|
||||||
|
"interlace": "None",
|
||||||
|
"intensity": "Undefined",
|
||||||
|
"compose": "Over",
|
||||||
|
"pageGeometry": {
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"dispose": "Undefined",
|
||||||
|
"iterations": 0,
|
||||||
|
"compression": "Undefined",
|
||||||
|
"orientation": "Undefined",
|
||||||
|
"properties": {
|
||||||
|
"signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
},
|
||||||
|
"tainted": false,
|
||||||
|
"filesize": "51106B",
|
||||||
|
"numberPixels": "2.0736M",
|
||||||
|
"pixelsPerSecond": "23.1706MB",
|
||||||
|
"userTime": "0.080u",
|
||||||
|
"elapsedTime": "0:01.089",
|
||||||
|
"version": "ImageMagick 7.1.1-11 Q16-HDRI x86_64 f04a7eb33:20230528 https://imagemagick.org"
|
||||||
|
}
|
||||||
|
}]
|
93
src/magick/tests.rs
Normal file
93
src/magick/tests.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue