Add jpegxl and avif support

This commit is contained in:
asonix 2023-06-21 17:05:35 -05:00
parent e925f2ba58
commit 6d2aef8cc0
5 changed files with 53 additions and 16 deletions

View file

@ -121,7 +121,7 @@ Options:
--media-filters <MEDIA_FILTERS> --media-filters <MEDIA_FILTERS>
Which media filters should be enabled on the `process` endpoint Which media filters should be enabled on the `process` endpoint
--media-format <MEDIA_FORMAT> --media-format <MEDIA_FORMAT>
Enforce uploaded media is transcoded to the provided format [possible values: jpeg, webp, png] Enforce uploaded media is transcoded to the provided format [possible values: avif, jpeg, jxl, png, webp]
-h, --help -h, --help
Print help information (use `--help` for more detail) Print help information (use `--help` for more detail)
``` ```
@ -383,7 +383,7 @@ pict-rs offers the following endpoints:
aspect ratio. For example, a 1600x900 image cropped with a 1x1 aspect ratio will become 900x900. A aspect ratio. For example, a 1600x900 image cropped with a 1x1 aspect ratio will become 900x900. A
1600x1100 image cropped with a 16x9 aspect ratio will become 1600x900. 1600x1100 image cropped with a 16x9 aspect ratio will become 1600x900.
Supported `ext` file extensions include `png`, `jpg`, and `webp` Supported `ext` file extensions include `avif`, `jpg`, `jxl`, `png`, and `webp`
An example of usage could be An example of usage could be
``` ```

View file

@ -188,10 +188,10 @@ filters = ['blur', 'crop', 'identity', 'resize', 'thumbnail']
# environment variable: PICTRS__MEDIA__FORMAT # environment variable: PICTRS__MEDIA__FORMAT
# default: empty # default: empty
# #
# available options: png, jpeg, webp # available options: avif, png, jpeg, jxl, webp
# When set, all uploaded still images will be converted to this file type. If you care about file # When set, all uploaded still images will be converted to this file type. For balancing quality vs
# size, setting this to 'webp' is probably the best option. By default, images are stored in their # file size vs browser support, 'avif', 'jxl', and 'webp' should be considered. By default, images
# original file type. # are stored in their original file type.
format = "webp" format = "webp"
## Optional: whether to validate images uploaded through the `import` endpoint ## Optional: whether to validate images uploaded through the `import` endpoint

View file

@ -40,9 +40,11 @@ pub(crate) enum LogFormat {
)] )]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub(crate) enum ImageFormat { pub(crate) enum ImageFormat {
Avif,
Jpeg, Jpeg,
Webp, Jxl,
Png, Png,
Webp,
} }
#[derive( #[derive(
@ -158,7 +160,9 @@ impl ImageFormat {
pub(crate) fn as_magick_format(self) -> &'static str { pub(crate) fn as_magick_format(self) -> &'static str {
match self { match self {
Self::Avif => "AVIF",
Self::Jpeg => "JPEG", Self::Jpeg => "JPEG",
Self::Jxl => "JXL",
Self::Png => "PNG", Self::Png => "PNG",
Self::Webp => "WEBP", Self::Webp => "WEBP",
} }
@ -166,7 +170,9 @@ impl ImageFormat {
pub(crate) fn as_ext(self) -> &'static str { pub(crate) fn as_ext(self) -> &'static str {
match self { match self {
Self::Avif => ".avif",
Self::Jpeg => ".jpeg", Self::Jpeg => ".jpeg",
Self::Jxl => ".jxl",
Self::Png => ".png", Self::Png => ".png",
Self::Webp => ".webp", Self::Webp => ".webp",
} }
@ -243,7 +249,9 @@ impl FromStr for ImageFormat {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() { match s.to_lowercase().as_str() {
"avif" => Ok(Self::Avif),
"jpeg" | "jpg" => Ok(Self::Jpeg), "jpeg" | "jpg" => Ok(Self::Jpeg),
"jxl" => Ok(Self::Jxl),
"png" => Ok(Self::Png), "png" => Ok(Self::Png),
"webp" => Ok(Self::Webp), "webp" => Ok(Self::Webp),
other => Err(format!("Invalid variant: {other}")), other => Err(format!("Invalid variant: {other}")),

View file

@ -222,7 +222,9 @@ impl ValidInputType {
Self::Gif => FileFormat::Video(VideoFormat::Gif), Self::Gif => FileFormat::Video(VideoFormat::Gif),
Self::Mp4 => FileFormat::Video(VideoFormat::Mp4), Self::Mp4 => FileFormat::Video(VideoFormat::Mp4),
Self::Webm => FileFormat::Video(VideoFormat::Webm), Self::Webm => FileFormat::Video(VideoFormat::Webm),
Self::Avif => FileFormat::Image(ImageFormat::Avif),
Self::Jpeg => FileFormat::Image(ImageFormat::Jpeg), Self::Jpeg => FileFormat::Image(ImageFormat::Jpeg),
Self::Jxl => FileFormat::Image(ImageFormat::Jxl),
Self::Png => FileFormat::Image(ImageFormat::Png), Self::Png => FileFormat::Image(ImageFormat::Png),
Self::Webp => FileFormat::Image(ImageFormat::Webp), Self::Webp => FileFormat::Image(ImageFormat::Webp),
} }
@ -472,7 +474,7 @@ fn parse_details(output: std::borrow::Cow<'_, str>) -> Result<Option<Details>, E
for (k, v) in FORMAT_MAPPINGS { for (k, v) in FORMAT_MAPPINGS {
if formats.contains(k) { if formats.contains(k) {
return Ok(Some(parse_details_inner(width, height, frames, *v)?)); return parse_details_inner(width, height, frames, *v);
} }
} }
@ -484,17 +486,22 @@ fn parse_details_inner(
height: &str, height: &str,
frames: &str, frames: &str,
format: VideoFormat, format: VideoFormat,
) -> Result<Details, Error> { ) -> Result<Option<Details>, Error> {
let width = width.parse().map_err(|_| UploadError::UnsupportedFormat)?; let width = width.parse().map_err(|_| UploadError::UnsupportedFormat)?;
let height = height.parse().map_err(|_| UploadError::UnsupportedFormat)?; let height = height.parse().map_err(|_| UploadError::UnsupportedFormat)?;
let frames = frames.parse().map_err(|_| UploadError::UnsupportedFormat)?; let frames = frames.parse().map_err(|_| UploadError::UnsupportedFormat)?;
Ok(Details { // Probably a still image. ffmpeg thinks AVIF is an mp4
if frames == 1 {
return Ok(None);
}
Ok(Some(Details {
mime_type: format.to_mime(), mime_type: format.to_mime(),
width, width,
height, height,
frames: Some(frames), frames: Some(frames),
}) }))
} }
async fn pixel_format(input_file: &str) -> Result<String, Error> { async fn pixel_format(input_file: &str) -> Result<String, Error> {

View file

@ -22,6 +22,14 @@ pub(crate) fn details_hint(alias: &Alias) -> Option<ValidInputType> {
} }
} }
fn image_avif() -> mime::Mime {
"image/avif".parse().unwrap()
}
fn image_jxl() -> mime::Mime {
"image/jxl".parse().unwrap()
}
fn image_webp() -> mime::Mime { fn image_webp() -> mime::Mime {
"image/webp".parse().unwrap() "image/webp".parse().unwrap()
} }
@ -39,8 +47,10 @@ pub(crate) enum ValidInputType {
Mp4, Mp4,
Webm, Webm,
Gif, Gif,
Png, Avif,
Jpeg, Jpeg,
Jxl,
Png,
Webp, Webp,
} }
@ -50,8 +60,10 @@ impl ValidInputType {
Self::Mp4 => "MP4", Self::Mp4 => "MP4",
Self::Webm => "WEBM", Self::Webm => "WEBM",
Self::Gif => "GIF", Self::Gif => "GIF",
Self::Png => "PNG", Self::Avif => "AVIF",
Self::Jpeg => "JPEG", Self::Jpeg => "JPEG",
Self::Jxl => "JXL",
Self::Png => "PNG",
Self::Webp => "WEBP", Self::Webp => "WEBP",
} }
} }
@ -61,8 +73,10 @@ impl ValidInputType {
Self::Mp4 => ".mp4", Self::Mp4 => ".mp4",
Self::Webm => ".webm", Self::Webm => ".webm",
Self::Gif => ".gif", Self::Gif => ".gif",
Self::Png => ".png", Self::Avif => ".avif",
Self::Jpeg => ".jpeg", Self::Jpeg => ".jpeg",
Self::Jxl => ".jxl",
Self::Png => ".png",
Self::Webp => ".webp", Self::Webp => ".webp",
} }
} }
@ -89,7 +103,9 @@ impl ValidInputType {
pub(crate) const fn from_format(format: ImageFormat) -> Self { pub(crate) const fn from_format(format: ImageFormat) -> Self {
match format { match format {
ImageFormat::Avif => ValidInputType::Avif,
ImageFormat::Jpeg => ValidInputType::Jpeg, ImageFormat::Jpeg => ValidInputType::Jpeg,
ImageFormat::Jxl => ValidInputType::Jxl,
ImageFormat::Png => ValidInputType::Png, ImageFormat::Png => ValidInputType::Png,
ImageFormat::Webp => ValidInputType::Webp, ImageFormat::Webp => ValidInputType::Webp,
} }
@ -97,7 +113,9 @@ impl ValidInputType {
pub(crate) const fn to_format(self) -> Option<ImageFormat> { pub(crate) const fn to_format(self) -> Option<ImageFormat> {
match self { match self {
Self::Avif => Some(ImageFormat::Avif),
Self::Jpeg => Some(ImageFormat::Jpeg), Self::Jpeg => Some(ImageFormat::Jpeg),
Self::Jxl => Some(ImageFormat::Jxl),
Self::Png => Some(ImageFormat::Png), Self::Png => Some(ImageFormat::Png),
Self::Webp => Some(ImageFormat::Webp), Self::Webp => Some(ImageFormat::Webp),
_ => None, _ => None,
@ -273,8 +291,10 @@ fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
"MP4" => video_mp4(), "MP4" => video_mp4(),
"WEBM" => video_webm(), "WEBM" => video_webm(),
"GIF" => mime::IMAGE_GIF, "GIF" => mime::IMAGE_GIF,
"PNG" => mime::IMAGE_PNG, "AVIF" => image_avif(),
"JPEG" => mime::IMAGE_JPEG, "JPEG" => mime::IMAGE_JPEG,
"JXL" => image_jxl(),
"PNG" => mime::IMAGE_PNG,
"WEBP" => image_webp(), "WEBP" => image_webp(),
_ => return Err(UploadError::UnsupportedFormat.into()), _ => return Err(UploadError::UnsupportedFormat.into()),
}; };
@ -343,8 +363,10 @@ impl Details {
(mime::VIDEO, mime::MP4 | mime::MPEG) => ValidInputType::Mp4, (mime::VIDEO, mime::MP4 | mime::MPEG) => ValidInputType::Mp4,
(mime::VIDEO, subtype) if subtype.as_str() == "webm" => ValidInputType::Webm, (mime::VIDEO, subtype) if subtype.as_str() == "webm" => ValidInputType::Webm,
(mime::IMAGE, mime::GIF) => ValidInputType::Gif, (mime::IMAGE, mime::GIF) => ValidInputType::Gif,
(mime::IMAGE, mime::PNG) => ValidInputType::Png, (mime::IMAGE, subtype) if subtype.as_str() == "avif" => ValidInputType::Avif,
(mime::IMAGE, mime::JPEG) => ValidInputType::Jpeg, (mime::IMAGE, mime::JPEG) => ValidInputType::Jpeg,
(mime::IMAGE, subtype) if subtype.as_str() == "jxl" => ValidInputType::Jxl,
(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(UploadError::UnsupportedFormat.into()),
}; };