mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 19:31:35 +00:00
Add jpegxl and avif support
This commit is contained in:
parent
e925f2ba58
commit
6d2aef8cc0
5 changed files with 53 additions and 16 deletions
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}")),
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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()),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue