mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-11-09 22:14:59 +00:00
Support audio in uploaded videos, allow webm uploads
This commit is contained in:
parent
c57a48db8a
commit
890478e794
7 changed files with 77 additions and 20 deletions
|
@ -20,7 +20,9 @@ max_width = 10000
|
|||
max_height = 10000
|
||||
max_area = 40000000
|
||||
max_file_size = 40
|
||||
max_frame_count = 900
|
||||
enable_silent_video = true
|
||||
enable_full_video = false
|
||||
filters = ['blur', 'crop', 'identity', 'resize', 'thumbnail']
|
||||
skip_validate_imports = false
|
||||
cache_duration = 168
|
||||
|
|
12
pict-rs.toml
12
pict-rs.toml
|
@ -142,13 +142,23 @@ max_area = 40000000
|
|||
# default: 40
|
||||
max_file_size = 40
|
||||
|
||||
## Optional: enable GIF and MP4 uploads (without sound)
|
||||
## Optional: max frame count
|
||||
# environment variable: PICTRS__MEDIA__MAX_FRAME_COUNT
|
||||
# default: # 900
|
||||
max_frame_count = 900
|
||||
|
||||
## Optional: enable GIF, MP4, and WEBM uploads (without sound)
|
||||
# environment variable: PICTRS__MEDIA__ENABLE_SILENT_VIDEO
|
||||
# default: true
|
||||
#
|
||||
# Set this to false to serve static images only
|
||||
enable_silent_video = true
|
||||
|
||||
## Optional: enable MP4, and WEBM uploads (with sound) and GIF (without sound)
|
||||
# environment variable: PICTRS__MEDIA__ENABLE_FULL_VIDEO
|
||||
# default: false
|
||||
enable_full_video = false
|
||||
|
||||
## Optional: set allowed filters for image processing
|
||||
# environment variable: PICTRS__MEDIA__FILTERS
|
||||
# default: ['blur', 'crop', 'identity', 'resize', 'thumbnail']
|
||||
|
|
|
@ -152,7 +152,7 @@ impl Default for MediaDefaults {
|
|||
max_height: 10_000,
|
||||
max_area: 40_000_000,
|
||||
max_file_size: 40,
|
||||
max_frame_count: 3_600,
|
||||
max_frame_count: 900,
|
||||
enable_silent_video: true,
|
||||
enable_full_video: false,
|
||||
filters: vec![
|
||||
|
|
|
@ -54,6 +54,7 @@ impl ThumbnailFormat {
|
|||
pub(crate) async fn to_mp4_bytes(
|
||||
input: Bytes,
|
||||
input_format: InputFormat,
|
||||
permit_audio: bool,
|
||||
) -> Result<impl AsyncRead + Unpin, Error> {
|
||||
let input_file = crate::tmp_file::tmp_file(Some(input_format.to_ext()));
|
||||
let input_file_str = input_file.to_str().ok_or(UploadError::Path)?;
|
||||
|
@ -67,9 +68,24 @@ pub(crate) async fn to_mp4_bytes(
|
|||
tmp_one.write_from_bytes(input).await?;
|
||||
tmp_one.close().await?;
|
||||
|
||||
let process = Process::run(
|
||||
"ffmpeg",
|
||||
&[
|
||||
let process = if permit_audio {
|
||||
Process::run("ffmpeg", &[
|
||||
"-i",
|
||||
input_file_str,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-c:a",
|
||||
"aac",
|
||||
"-c:v",
|
||||
"h264",
|
||||
"-f",
|
||||
"mp4",
|
||||
output_file_str,
|
||||
])?
|
||||
} else {
|
||||
Process::run("ffmpeg", &[
|
||||
"-i",
|
||||
input_file_str,
|
||||
"-pix_fmt",
|
||||
|
@ -77,13 +93,13 @@ pub(crate) async fn to_mp4_bytes(
|
|||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-an",
|
||||
"-codec",
|
||||
"-c:v",
|
||||
"h264",
|
||||
"-f",
|
||||
"mp4",
|
||||
output_file_str,
|
||||
],
|
||||
)?;
|
||||
])?
|
||||
};
|
||||
|
||||
process.wait().await?;
|
||||
tokio::fs::remove_file(input_file).await?;
|
||||
|
|
|
@ -74,6 +74,7 @@ where
|
|||
bytes,
|
||||
CONFIG.media.format,
|
||||
CONFIG.media.enable_silent_video,
|
||||
CONFIG.media.enable_full_video,
|
||||
should_validate,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -16,22 +16,29 @@ pub(crate) fn details_hint(alias: &Alias) -> Option<ValidInputType> {
|
|||
let ext = alias.extension()?;
|
||||
if ext.ends_with(".mp4") {
|
||||
Some(ValidInputType::Mp4)
|
||||
} else if ext.ends_with(".webm") {
|
||||
Some(ValidInputType::Webm)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn image_webp() -> mime::Mime {
|
||||
fn image_webp() -> mime::Mime {
|
||||
"image/webp".parse().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn video_mp4() -> mime::Mime {
|
||||
fn video_mp4() -> mime::Mime {
|
||||
"video/mp4".parse().unwrap()
|
||||
}
|
||||
|
||||
fn video_webm() -> mime::Mime {
|
||||
"video/webm".parse().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum ValidInputType {
|
||||
Mp4,
|
||||
Webm,
|
||||
Gif,
|
||||
Png,
|
||||
Jpeg,
|
||||
|
@ -42,6 +49,7 @@ impl ValidInputType {
|
|||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Mp4 => "MP4",
|
||||
Self::Webm => "WEBM",
|
||||
Self::Gif => "GIF",
|
||||
Self::Png => "PNG",
|
||||
Self::Jpeg => "JPEG",
|
||||
|
@ -52,6 +60,7 @@ impl ValidInputType {
|
|||
pub(crate) fn as_ext(self) -> &'static str {
|
||||
match self {
|
||||
Self::Mp4 => ".mp4",
|
||||
Self::Webm => ".webm",
|
||||
Self::Gif => ".gif",
|
||||
Self::Png => ".png",
|
||||
Self::Jpeg => ".jpeg",
|
||||
|
@ -59,8 +68,13 @@ impl ValidInputType {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_mp4(self) -> bool {
|
||||
matches!(self, Self::Mp4)
|
||||
fn video_hint(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::Mp4 => Some(".mp4"),
|
||||
Self::Webm => Some(".webm"),
|
||||
Self::Gif => Some(".gif"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_format(format: ImageFormat) -> Self {
|
||||
|
@ -119,8 +133,8 @@ pub(crate) async fn details_bytes(
|
|||
input: Bytes,
|
||||
hint: Option<ValidInputType>,
|
||||
) -> Result<Details, Error> {
|
||||
if hint.as_ref().map(|h| h.is_mp4()).unwrap_or(false) {
|
||||
let input_file = crate::tmp_file::tmp_file(Some(".mp4"));
|
||||
if let Some(hint) = hint.and_then(|hint| hint.video_hint()) {
|
||||
let input_file = crate::tmp_file::tmp_file(Some(hint));
|
||||
let input_file_str = input_file.to_str().ok_or(UploadError::Path)?;
|
||||
crate::store::file_store::safe_create_parent(&input_file).await?;
|
||||
|
||||
|
@ -157,8 +171,8 @@ pub(crate) async fn details_store<S: Store + 'static>(
|
|||
identifier: S::Identifier,
|
||||
hint: Option<ValidInputType>,
|
||||
) -> Result<Details, Error> {
|
||||
if hint.as_ref().map(|h| h.is_mp4()).unwrap_or(false) {
|
||||
let input_file = crate::tmp_file::tmp_file(Some(".mp4"));
|
||||
if let Some(hint) = hint.and_then(|hint| hint.video_hint()) {
|
||||
let input_file = crate::tmp_file::tmp_file(Some(hint));
|
||||
let input_file_str = input_file.to_str().ok_or(UploadError::Path)?;
|
||||
crate::store::file_store::safe_create_parent(&input_file).await?;
|
||||
|
||||
|
@ -249,6 +263,7 @@ fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
|
|||
|
||||
let mime_type = match format {
|
||||
"MP4" => video_mp4(),
|
||||
"WEBM" => video_webm(),
|
||||
"GIF" => mime::IMAGE_GIF,
|
||||
"PNG" => mime::IMAGE_PNG,
|
||||
"JPEG" => mime::IMAGE_JPEG,
|
||||
|
@ -323,6 +338,7 @@ impl Details {
|
|||
|
||||
let input_type = match (self.mime_type.type_(), self.mime_type.subtype()) {
|
||||
(mime::VIDEO, mime::MP4 | mime::MPEG) => ValidInputType::Mp4,
|
||||
(mime::VIDEO, subtype) if subtype.as_str() == "webm" => ValidInputType::Webm,
|
||||
(mime::IMAGE, mime::GIF) => ValidInputType::Gif,
|
||||
(mime::IMAGE, mime::PNG) => ValidInputType::Png,
|
||||
(mime::IMAGE, mime::JPEG) => ValidInputType::Jpeg,
|
||||
|
|
|
@ -41,6 +41,7 @@ pub(crate) async fn validate_image_bytes(
|
|||
bytes: Bytes,
|
||||
prescribed_format: Option<ImageFormat>,
|
||||
enable_silent_video: bool,
|
||||
enable_full_video: bool,
|
||||
validate: bool,
|
||||
) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> {
|
||||
let input_type = crate::magick::input_type_bytes(bytes.clone()).await?;
|
||||
|
@ -51,24 +52,35 @@ pub(crate) async fn validate_image_bytes(
|
|||
|
||||
match (prescribed_format, input_type) {
|
||||
(_, ValidInputType::Gif) => {
|
||||
if !enable_silent_video {
|
||||
if !(enable_silent_video || enable_full_video) {
|
||||
return Err(UploadError::SilentVideoDisabled.into());
|
||||
}
|
||||
Ok((
|
||||
ValidInputType::Mp4,
|
||||
Either::right(Either::left(
|
||||
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Gif).await?,
|
||||
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Gif, false).await?,
|
||||
)),
|
||||
))
|
||||
}
|
||||
(_, ValidInputType::Mp4) => {
|
||||
if !enable_silent_video {
|
||||
if !(enable_silent_video || enable_full_video) {
|
||||
return Err(UploadError::SilentVideoDisabled.into());
|
||||
}
|
||||
Ok((
|
||||
ValidInputType::Mp4,
|
||||
Either::right(Either::left(
|
||||
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4).await?,
|
||||
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4, enable_full_video).await?,
|
||||
)),
|
||||
))
|
||||
}
|
||||
(_, ValidInputType::Webm) => {
|
||||
if !(enable_silent_video || enable_full_video) {
|
||||
return Err(UploadError::SilentVideoDisabled.into());
|
||||
}
|
||||
Ok((
|
||||
ValidInputType::Mp4,
|
||||
Either::right(Either::left(
|
||||
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4, enable_full_video).await?,
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue