2
0
Fork 0
mirror of https://git.asonix.dog/asonix/pict-rs synced 2024-12-22 19:31:35 +00:00

Enable selecting video and audio codecs for uploaded media

This commit is contained in:
asonix 2022-09-30 19:38:11 -05:00
parent f4542efcc1
commit 8eb2293808
15 changed files with 252 additions and 45 deletions

View file

@ -80,9 +80,13 @@ Options:
--media-max-frame-count <MEDIA_MAX_FRAME_COUNT> --media-max-frame-count <MEDIA_MAX_FRAME_COUNT>
The maximum number of frames allowed for uploaded GIF and MP4s The maximum number of frames allowed for uploaded GIF and MP4s
--media-enable-silent-video <MEDIA_ENABLE_SILENT_VIDEO> --media-enable-silent-video <MEDIA_ENABLE_SILENT_VIDEO>
Whether to enable GIF and silent MP4 uploads [possible values: true, false] Whether to enable GIF and silent video uploads [possible values: true, false]
--media-enable-full-video <MEDIA_ENABLE_FULL_VIDEO> --media-enable-full-video <MEDIA_ENABLE_FULL_VIDEO>
Whether to enable full MP4 uploads [possible values: true, false] Whether to enable full video uploads [possible values: true, false]
--media-video-codec <MEDIA_VIDEO_CODEC>
Enforce a specific video codec for uploaded videos [possible values: h264, h265, av1, vp8, vp9]
--media-audio-codec <MEDIA_AUDIO_CODEC>
Enforce a specific audio codec for uploaded videos [possible values: aac, opus, vorbis]
--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>

View file

@ -1,6 +1,7 @@
[server] [server]
address = '0.0.0.0:8080' address = '0.0.0.0:8080'
worker_id = 'pict-rs-1' worker_id = 'pict-rs-1'
[tracing.logging] [tracing.logging]
format = 'normal' format = 'normal'
targets = 'warn,tracing_actix_web=info,actix_server=info,actix_web=info' targets = 'warn,tracing_actix_web=info,actix_server=info,actix_web=info'
@ -23,6 +24,7 @@ max_file_size = 40
max_frame_count = 900 max_frame_count = 900
enable_silent_video = true enable_silent_video = true
enable_full_video = false enable_full_video = false
video_codec = 'h264'
filters = ['blur', 'crop', 'identity', 'resize', 'thumbnail'] filters = ['blur', 'crop', 'identity', 'resize', 'thumbnail']
skip_validate_imports = false skip_validate_imports = false
cache_duration = 168 cache_duration = 168

View file

@ -159,6 +159,26 @@ enable_silent_video = true
# default: false # default: false
enable_full_video = false enable_full_video = false
## Optional: set the default video codec
# environment variable: PICTRS__MEDIA__VIDEO_CODEC
# default: h264
#
# available options: av1, h264, h265, vp8, vp9
# this setting does nothing if video is not enabled
video_codec = "h264"
## Optional: set the default audio codec
# environment variable: PICTRS__MEDIA__AUDIO_CODEC
# default: empty
#
# available options: aac, opus, vorbis
# The audio codec is automatically selected based on video codec, but can be overriden
# av1, vp8, and vp9 map to opus
# h264 and h265 map to aac
# vorbis is not default for any codec
# this setting does nothing if full video is not enabled
audio_codec = "aac"
## Optional: set allowed filters for image processing ## Optional: set allowed filters for image processing
# environment variable: PICTRS__MEDIA__FILTERS # environment variable: PICTRS__MEDIA__FILTERS
# default: ['blur', 'crop', 'identity', 'resize', 'thumbnail'] # default: ['blur', 'crop', 'identity', 'resize', 'thumbnail']

View file

@ -11,7 +11,9 @@ use defaults::Defaults;
pub(crate) use commandline::Operation; pub(crate) use commandline::Operation;
pub(crate) use file::{ConfigFile as Configuration, OpenTelemetry, Repo, Sled, Tracing}; pub(crate) use file::{ConfigFile as Configuration, OpenTelemetry, Repo, Sled, Tracing};
pub(crate) use primitives::{Filesystem, ImageFormat, LogFormat, ObjectStorage, Store}; pub(crate) use primitives::{
AudioCodec, Filesystem, ImageFormat, LogFormat, ObjectStorage, Store, VideoCodec,
};
pub(crate) fn configure() -> color_eyre::Result<(Configuration, Operation)> { pub(crate) fn configure() -> color_eyre::Result<(Configuration, Operation)> {
let Output { let Output {

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
config::primitives::{ImageFormat, LogFormat, Targets}, config::primitives::{AudioCodec, ImageFormat, LogFormat, Targets, VideoCodec},
serde_str::Serde, serde_str::Serde,
}; };
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@ -54,6 +54,8 @@ impl Args {
media_max_frame_count, media_max_frame_count,
media_enable_silent_video, media_enable_silent_video,
media_enable_full_video, media_enable_full_video,
media_video_codec,
media_audio_codec,
media_filters, media_filters,
media_format, media_format,
media_cache_duration, media_cache_duration,
@ -74,6 +76,8 @@ impl Args {
max_frame_count: media_max_frame_count, max_frame_count: media_max_frame_count,
enable_silent_video: media_enable_silent_video, enable_silent_video: media_enable_silent_video,
enable_full_video: media_enable_full_video, enable_full_video: media_enable_full_video,
video_codec: media_video_codec,
audio_codec: media_audio_codec,
filters: media_filters, filters: media_filters,
format: media_format, format: media_format,
cache_duration: media_cache_duration, cache_duration: media_cache_duration,
@ -322,6 +326,10 @@ struct Media {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
enable_full_video: Option<bool>, enable_full_video: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
video_codec: Option<VideoCodec>,
#[serde(skip_serializing_if = "Option::is_none")]
audio_codec: Option<AudioCodec>,
#[serde(skip_serializing_if = "Option::is_none")]
filters: Option<Vec<String>>, filters: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
format: Option<ImageFormat>, format: Option<ImageFormat>,
@ -423,12 +431,18 @@ struct Run {
/// The maximum number of frames allowed for uploaded GIF and MP4s. /// The maximum number of frames allowed for uploaded GIF and MP4s.
#[arg(long)] #[arg(long)]
media_max_frame_count: Option<usize>, media_max_frame_count: Option<usize>,
/// Whether to enable GIF and silent MP4 uploads /// Whether to enable GIF and silent video uploads
#[arg(long)] #[arg(long)]
media_enable_silent_video: Option<bool>, media_enable_silent_video: Option<bool>,
/// Whether to enable full MP4 uploads /// Whether to enable full video uploads
#[arg(long)] #[arg(long)]
media_enable_full_video: Option<bool>, media_enable_full_video: Option<bool>,
/// Enforce a specific video codec for uploaded videos
#[arg(long)]
media_video_codec: Option<VideoCodec>,
/// Enforce a specific audio codec for uploaded videos
#[arg(long)]
media_audio_codec: Option<AudioCodec>,
/// Which media filters should be enabled on the `process` endpoint /// Which media filters should be enabled on the `process` endpoint
#[arg(long)] #[arg(long)]
media_filters: Option<Vec<String>>, media_filters: Option<Vec<String>>,

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
config::primitives::{LogFormat, Targets}, config::primitives::{LogFormat, Targets, VideoCodec},
serde_str::Serde, serde_str::Serde,
}; };
use std::{net::SocketAddr, path::PathBuf}; use std::{net::SocketAddr, path::PathBuf};
@ -68,6 +68,7 @@ struct MediaDefaults {
max_frame_count: usize, max_frame_count: usize,
enable_silent_video: bool, enable_silent_video: bool,
enable_full_video: bool, enable_full_video: bool,
video_codec: VideoCodec,
filters: Vec<String>, filters: Vec<String>,
skip_validate_imports: bool, skip_validate_imports: bool,
cache_duration: i64, cache_duration: i64,
@ -155,6 +156,7 @@ impl Default for MediaDefaults {
max_frame_count: 900, max_frame_count: 900,
enable_silent_video: true, enable_silent_video: true,
enable_full_video: false, enable_full_video: false,
video_codec: VideoCodec::H264,
filters: vec![ filters: vec![
"blur".into(), "blur".into(),
"crop".into(), "crop".into(),

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
config::primitives::{ImageFormat, LogFormat, Store, Targets}, config::primitives::{AudioCodec, ImageFormat, LogFormat, Store, Targets, VideoCodec},
serde_str::Serde, serde_str::Serde,
}; };
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@ -104,6 +104,10 @@ pub(crate) struct Media {
pub(crate) enable_full_video: bool, pub(crate) enable_full_video: bool,
pub(crate) video_codec: VideoCodec,
pub(crate) audio_codec: Option<AudioCodec>,
pub(crate) filters: BTreeSet<String>, pub(crate) filters: BTreeSet<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]

View file

@ -45,6 +45,48 @@ pub(crate) enum ImageFormat {
Png, Png,
} }
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Deserialize,
serde::Serialize,
ValueEnum,
)]
#[serde(rename_all = "snake_case")]
pub(crate) enum VideoCodec {
H264,
H265,
Av1,
Vp8,
Vp9,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Deserialize,
serde::Serialize,
ValueEnum,
)]
#[serde(rename_all = "snake_case")]
pub(crate) enum AudioCodec {
Aac,
Opus,
Vorbis,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct Targets { pub(crate) struct Targets {
pub(crate) targets: tracing_subscriber::filter::Targets, pub(crate) targets: tracing_subscriber::filter::Targets,

View file

@ -1,4 +1,10 @@
use crate::{error::Error, magick::ValidInputType, serde_str::Serde, store::Store}; use crate::{
error::Error,
ffmpeg::InputFormat,
magick::{video_mp4, video_webm, ValidInputType},
serde_str::Serde,
store::Store,
};
use actix_web::web; use actix_web::web;
#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)]
@ -93,6 +99,22 @@ impl Details {
pub(crate) fn system_time(&self) -> std::time::SystemTime { pub(crate) fn system_time(&self) -> std::time::SystemTime {
self.created_at.into() self.created_at.into()
} }
pub(crate) fn to_input_format(&self) -> Option<InputFormat> {
if *self.content_type == mime::IMAGE_GIF {
return Some(InputFormat::Gif);
}
if *self.content_type == video_mp4() {
return Some(InputFormat::Mp4);
}
if *self.content_type == video_webm() {
return Some(InputFormat::Webm);
}
None
}
} }
impl From<MaybeHumanDate> for std::time::SystemTime { impl From<MaybeHumanDate> for std::time::SystemTime {

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
config::{AudioCodec, VideoCodec},
error::{Error, UploadError}, error::{Error, UploadError},
magick::{Details, ValidInputType}, magick::{Details, ValidInputType},
process::Process, process::Process,
@ -8,25 +9,31 @@ use actix_web::web::Bytes;
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
use tracing::instrument; use tracing::instrument;
#[derive(Copy, Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) enum InputFormat { pub(crate) enum InputFormat {
Gif, Gif,
Mp4, Mp4,
Webm, Webm,
} }
#[derive(Copy, Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) enum OutputFormat {
Mp4,
Webm,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum ThumbnailFormat { pub(crate) enum ThumbnailFormat {
Jpeg, Jpeg,
// Webp, // Webp,
} }
impl InputFormat { impl InputFormat {
fn to_ext(self) -> &'static str { const fn to_ext(self) -> &'static str {
match self { match self {
InputFormat::Gif => ".gif", Self::Gif => ".gif",
InputFormat::Mp4 => ".mp4", Self::Mp4 => ".mp4",
InputFormat::Webm => ".webm", Self::Webm => ".webm",
} }
} }
@ -40,23 +47,69 @@ impl InputFormat {
} }
impl ThumbnailFormat { impl ThumbnailFormat {
fn as_codec(self) -> &'static str { const fn as_ffmpeg_codec(self) -> &'static str {
match self { match self {
ThumbnailFormat::Jpeg => "mjpeg", Self::Jpeg => "mjpeg",
// ThumbnailFormat::Webp => "webp", // Self::Webp => "webp",
} }
} }
fn to_ext(self) -> &'static str { const fn to_ext(self) -> &'static str {
match self { match self {
ThumbnailFormat::Jpeg => ".jpeg", Self::Jpeg => ".jpeg",
// Self::Webp => ".webp",
} }
} }
fn as_format(self) -> &'static str { const fn as_ffmpeg_format(self) -> &'static str {
match self { match self {
ThumbnailFormat::Jpeg => "image2", Self::Jpeg => "image2",
// ThumbnailFormat::Webp => "webp", // Self::Webp => "webp",
}
}
}
impl OutputFormat {
const fn to_ffmpeg_format(self) -> &'static str {
match self {
Self::Mp4 => "mp4",
Self::Webm => "webm",
}
}
const fn default_audio_codec(self) -> AudioCodec {
match self {
Self::Mp4 => AudioCodec::Aac,
Self::Webm => AudioCodec::Opus,
}
}
}
impl VideoCodec {
const fn to_output_format(self) -> OutputFormat {
match self {
Self::H264 | Self::H265 => OutputFormat::Mp4,
Self::Av1 | Self::Vp8 | Self::Vp9 => OutputFormat::Webm,
}
}
const fn to_ffmpeg_codec(self) -> &'static str {
match self {
Self::Av1 => "av1",
Self::H264 => "h264",
Self::H265 => "hevc",
Self::Vp8 => "vp8",
Self::Vp9 => "vp9",
}
}
}
impl AudioCodec {
const fn to_ffmpeg_codec(self) -> &'static str {
match self {
Self::Aac => "aac",
Self::Opus => "opus",
Self::Vorbis => "vorbis",
} }
} }
} }
@ -184,11 +237,13 @@ fn parse_details_inner(
}) })
} }
#[tracing::instrument(name = "Convert to Mp4", skip(input))] #[tracing::instrument(name = "Transcode video", skip(input))]
pub(crate) async fn to_mp4_bytes( pub(crate) async fn trancsocde_bytes(
input: Bytes, input: Bytes,
input_format: InputFormat, input_format: InputFormat,
permit_audio: bool, permit_audio: bool,
video_codec: VideoCodec,
audio_codec: Option<AudioCodec>,
) -> Result<impl AsyncRead + Unpin, Error> { ) -> Result<impl AsyncRead + Unpin, Error> {
let input_file = crate::tmp_file::tmp_file(Some(input_format.to_ext())); 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)?; let input_file_str = input_file.to_str().ok_or(UploadError::Path)?;
@ -202,6 +257,9 @@ pub(crate) async fn to_mp4_bytes(
tmp_one.write_from_bytes(input).await?; tmp_one.write_from_bytes(input).await?;
tmp_one.close().await?; tmp_one.close().await?;
let output_format = video_codec.to_output_format();
let audio_codec = audio_codec.unwrap_or(output_format.default_audio_codec());
let process = if permit_audio { let process = if permit_audio {
Process::run( Process::run(
"ffmpeg", "ffmpeg",
@ -213,11 +271,11 @@ pub(crate) async fn to_mp4_bytes(
"-vf", "-vf",
"scale=trunc(iw/2)*2:trunc(ih/2)*2", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
"-c:a", "-c:a",
"aac", audio_codec.to_ffmpeg_codec(),
"-c:v", "-c:v",
"h264", video_codec.to_ffmpeg_codec(),
"-f", "-f",
"mp4", output_format.to_ffmpeg_format(),
output_file_str, output_file_str,
], ],
)? )?
@ -233,9 +291,9 @@ pub(crate) async fn to_mp4_bytes(
"scale=trunc(iw/2)*2:trunc(ih/2)*2", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
"-an", "-an",
"-c:v", "-c:v",
"h264", video_codec.to_ffmpeg_codec(),
"-f", "-f",
"mp4", output_format.to_ffmpeg_format(),
output_file_str, output_file_str,
], ],
)? )?
@ -281,9 +339,9 @@ pub(crate) async fn thumbnail<S: Store>(
"-vframes", "-vframes",
"1", "1",
"-codec", "-codec",
format.as_codec(), format.as_ffmpeg_codec(),
"-f", "-f",
format.as_format(), format.as_ffmpeg_format(),
output_file_str, output_file_str,
], ],
)?; )?;

View file

@ -20,6 +20,8 @@ pub(crate) async fn generate<R: FullRepo, S: Store + 'static>(
alias: Alias, alias: Alias,
thumbnail_path: PathBuf, thumbnail_path: PathBuf,
thumbnail_args: Vec<String>, thumbnail_args: Vec<String>,
input_format: Option<InputFormat>,
thumbnail_format: Option<ThumbnailFormat>,
hash: R::Bytes, hash: R::Bytes,
) -> Result<(Details, Bytes), Error> { ) -> Result<(Details, Bytes), Error> {
let process_fut = process( let process_fut = process(
@ -29,6 +31,8 @@ pub(crate) async fn generate<R: FullRepo, S: Store + 'static>(
alias, alias,
thumbnail_path.clone(), thumbnail_path.clone(),
thumbnail_args, thumbnail_args,
input_format,
thumbnail_format,
hash.clone(), hash.clone(),
); );
@ -46,6 +50,8 @@ async fn process<R: FullRepo, S: Store + 'static>(
alias: Alias, alias: Alias,
thumbnail_path: PathBuf, thumbnail_path: PathBuf,
thumbnail_args: Vec<String>, thumbnail_args: Vec<String>,
input_format: Option<InputFormat>,
thumbnail_format: Option<ThumbnailFormat>,
hash: R::Bytes, hash: R::Bytes,
) -> Result<(Details, Bytes), Error> { ) -> Result<(Details, Bytes), Error> {
let permit = crate::PROCESS_SEMAPHORE.acquire().await; let permit = crate::PROCESS_SEMAPHORE.acquire().await;
@ -60,8 +66,8 @@ async fn process<R: FullRepo, S: Store + 'static>(
let reader = crate::ffmpeg::thumbnail( let reader = crate::ffmpeg::thumbnail(
store.clone(), store.clone(),
identifier, identifier,
InputFormat::Mp4, input_format.unwrap_or(InputFormat::Mp4),
ThumbnailFormat::Jpeg, thumbnail_format.unwrap_or(ThumbnailFormat::Jpeg),
) )
.await?; .await?;
let motion_identifier = store.save_async_read(reader).await?; let motion_identifier = store.save_async_read(reader).await?;

View file

@ -70,11 +70,13 @@ where
let bytes = aggregate(stream).await?; let bytes = aggregate(stream).await?;
tracing::debug!("Validating bytes"); tracing::debug!("Validating bytes");
let (input_type, validated_reader) = crate::validate::validate_image_bytes( let (input_type, validated_reader) = crate::validate::validate_bytes(
bytes, bytes,
CONFIG.media.format, CONFIG.media.format,
CONFIG.media.enable_silent_video, CONFIG.media.enable_silent_video,
CONFIG.media.enable_full_video, CONFIG.media.enable_full_video,
CONFIG.media.video_codec,
CONFIG.media.audio_codec,
should_validate, should_validate,
) )
.await?; .await?;

View file

@ -48,8 +48,6 @@ mod stream;
mod tmp_file; mod tmp_file;
mod validate; mod validate;
use crate::magick::ValidInputType;
use self::{ use self::{
backgrounded::Backgrounded, backgrounded::Backgrounded,
config::{Configuration, ImageFormat, Operation}, config::{Configuration, ImageFormat, Operation},
@ -58,7 +56,7 @@ use self::{
error::{Error, UploadError}, error::{Error, UploadError},
ingest::Session, ingest::Session,
init_tracing::init_tracing, init_tracing::init_tracing,
magick::details_hint, magick::{details_hint, ValidInputType},
middleware::{Deadline, Internal}, middleware::{Deadline, Internal},
queue::queue_generate, queue::queue_generate,
repo::{ repo::{
@ -597,6 +595,7 @@ async fn process<R: FullRepo, S: Store + 'static>(
let path_string = thumbnail_path.to_string_lossy().to_string(); let path_string = thumbnail_path.to_string_lossy().to_string();
let hash = repo.hash(&alias).await?; let hash = repo.hash(&alias).await?;
let identifier_opt = repo let identifier_opt = repo
.variant_identifier::<S::Identifier>(hash.clone(), path_string) .variant_identifier::<S::Identifier>(hash.clone(), path_string)
.await?; .await?;
@ -624,6 +623,8 @@ async fn process<R: FullRepo, S: Store + 'static>(
return ranged_file_resp(&store, identifier, range, details).await; return ranged_file_resp(&store, identifier, range, details).await;
} }
let original_details = ensure_details(&repo, &store, &alias).await?;
let (details, bytes) = generate::generate( let (details, bytes) = generate::generate(
&repo, &repo,
&store, &store,
@ -631,6 +632,8 @@ async fn process<R: FullRepo, S: Store + 'static>(
alias, alias,
thumbnail_path, thumbnail_path,
thumbnail_args, thumbnail_args,
original_details.to_input_format(),
None,
hash, hash,
) )
.await?; .await?;

View file

@ -149,6 +149,8 @@ async fn generate<R: FullRepo, S: Store + 'static>(
return Ok(()); return Ok(());
} }
let original_details = crate::ensure_details(repo, store, &source).await?;
crate::generate::generate( crate::generate::generate(
repo, repo,
store, store,
@ -156,6 +158,8 @@ async fn generate<R: FullRepo, S: Store + 'static>(
source, source,
process_path, process_path,
process_args, process_args,
original_details.to_input_format(),
None,
hash, hash,
) )
.await?; .await?;

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
config::ImageFormat, config::{AudioCodec, ImageFormat, VideoCodec},
either::Either, either::Either,
error::{Error, UploadError}, error::{Error, UploadError},
ffmpeg::InputFormat, ffmpeg::InputFormat,
@ -36,12 +36,14 @@ impl AsyncRead for UnvalidatedBytes {
} }
} }
#[instrument(name = "Validate image", skip(bytes))] #[instrument(name = "Validate media", skip(bytes))]
pub(crate) async fn validate_image_bytes( pub(crate) async fn validate_bytes(
bytes: Bytes, bytes: Bytes,
prescribed_format: Option<ImageFormat>, prescribed_format: Option<ImageFormat>,
enable_silent_video: bool, enable_silent_video: bool,
enable_full_video: bool, enable_full_video: bool,
video_codec: VideoCodec,
audio_codec: Option<AudioCodec>,
validate: bool, validate: bool,
) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> { ) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> {
let input_type = let input_type =
@ -63,7 +65,14 @@ pub(crate) async fn validate_image_bytes(
Ok(( Ok((
ValidInputType::Mp4, ValidInputType::Mp4,
Either::right(Either::left( Either::right(Either::left(
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Gif, false).await?, crate::ffmpeg::trancsocde_bytes(
bytes,
InputFormat::Gif,
false,
video_codec,
audio_codec,
)
.await?,
)), )),
)) ))
} }
@ -74,7 +83,14 @@ pub(crate) async fn validate_image_bytes(
Ok(( Ok((
ValidInputType::Mp4, ValidInputType::Mp4,
Either::right(Either::left( Either::right(Either::left(
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4, enable_full_video).await?, crate::ffmpeg::trancsocde_bytes(
bytes,
InputFormat::Mp4,
enable_full_video,
video_codec,
audio_codec,
)
.await?,
)), )),
)) ))
} }
@ -85,7 +101,13 @@ pub(crate) async fn validate_image_bytes(
Ok(( Ok((
ValidInputType::Mp4, ValidInputType::Mp4,
Either::right(Either::left( Either::right(Either::left(
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Webm, enable_full_video) crate::ffmpeg::trancsocde_bytes(
bytes,
InputFormat::Webm,
enable_full_video,
video_codec,
audio_codec,
)
.await?, .await?,
)), )),
)) ))