mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-11-09 14:04:58 +00:00
Allow uploading small gifs
This commit is contained in:
parent
d72653cf78
commit
40f57be0c7
10 changed files with 324 additions and 106 deletions
|
@ -1,20 +1,20 @@
|
|||
[server]
|
||||
address = '0.0.0.0:8080'
|
||||
worker_id = 'pict-rs-1'
|
||||
address = "0.0.0.0:8080"
|
||||
worker_id = "pict-rs-1"
|
||||
|
||||
[tracing.logging]
|
||||
format = 'normal'
|
||||
targets = 'warn,tracing_actix_web=info,actix_server=info,actix_web=info'
|
||||
format = "normal"
|
||||
targets = "warn,tracing_actix_web=info,actix_server=info,actix_web=info"
|
||||
|
||||
[tracing.console]
|
||||
buffer_capacity = 102400
|
||||
|
||||
[tracing.opentelemetry]
|
||||
service_name = 'pict-rs'
|
||||
targets = 'info'
|
||||
service_name = "pict-rs"
|
||||
targets = "info"
|
||||
|
||||
[old_db]
|
||||
path = '/mnt'
|
||||
path = "/mnt"
|
||||
|
||||
[media]
|
||||
max_width = 10000
|
||||
|
@ -24,16 +24,27 @@ max_file_size = 40
|
|||
max_frame_count = 900
|
||||
enable_silent_video = true
|
||||
enable_full_video = false
|
||||
video_codec = 'vp9'
|
||||
filters = ['blur', 'crop', 'identity', 'resize', 'thumbnail']
|
||||
video_codec = "vp9"
|
||||
filters = [
|
||||
"blur",
|
||||
"crop",
|
||||
"identity",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
]
|
||||
skip_validate_imports = false
|
||||
cache_duration = 168
|
||||
|
||||
[media.gif]
|
||||
max_width = 128
|
||||
max_height = 128
|
||||
max_area = 16384
|
||||
|
||||
[repo]
|
||||
type = 'sled'
|
||||
path = '/mnt/sled-repo'
|
||||
type = "sled"
|
||||
path = "/mnt/sled-repo"
|
||||
cache_capacity = 67108864
|
||||
|
||||
[store]
|
||||
type = 'filesystem'
|
||||
path = '/mnt/files'
|
||||
type = "filesystem"
|
||||
path = "/mnt/files"
|
||||
|
|
28
pict-rs.toml
28
pict-rs.toml
|
@ -196,6 +196,34 @@ skip_validate_imports = false
|
|||
# default: 168 (1 week)
|
||||
cache_duration = 168
|
||||
|
||||
## Gif configuration
|
||||
#
|
||||
# Making any of these bounds 0 will disable gif uploads
|
||||
[media.gif]
|
||||
# Optional: Maximum width in pixels for uploaded gifs
|
||||
# environment variable: PICTRS__MEDIA__GIF__MAX_WIDTH
|
||||
# default: 128
|
||||
#
|
||||
# If a gif does not fit within this bound, it will either be transcoded to a video or rejected,
|
||||
# depending on whether video uploads are enabled
|
||||
max_width = 128
|
||||
|
||||
# Optional: Maximum height in pixels for uploaded gifs
|
||||
# environment variable: PICTRS__MEDIA__GIF__MAX_HEIGHT
|
||||
# default: 128
|
||||
#
|
||||
# If a gif does not fit within this bound, it will either be transcoded to a video or rejected,
|
||||
# depending on whether video uploads are enabled
|
||||
max_height = 128
|
||||
|
||||
# Optional: Maximum area in pixels for uploaded gifs
|
||||
# environment variable: PICTRS__MEDIA__GIF__MAX_AREA
|
||||
# default: 16384 (128 * 128)
|
||||
#
|
||||
# If a gif does not fit within this bound, it will either be transcoded to a video or rejected,
|
||||
# depending on whether video uploads are enabled
|
||||
max_area = 16384
|
||||
|
||||
|
||||
## Database configuration
|
||||
[repo]
|
||||
|
|
|
@ -11,7 +11,9 @@ use config::Config;
|
|||
use defaults::Defaults;
|
||||
|
||||
pub(crate) use commandline::Operation;
|
||||
pub(crate) use file::{ConfigFile as Configuration, OpenTelemetry, Repo, Sled, Tracing};
|
||||
pub(crate) use file::{
|
||||
ConfigFile as Configuration, Media as MediaConfiguration, OpenTelemetry, Repo, Sled, Tracing,
|
||||
};
|
||||
pub(crate) use primitives::{
|
||||
AudioCodec, Filesystem, ImageFormat, LogFormat, ObjectStorage, Store, VideoCodec,
|
||||
};
|
||||
|
|
|
@ -52,6 +52,9 @@ impl Args {
|
|||
media_max_area,
|
||||
media_max_file_size,
|
||||
media_max_frame_count,
|
||||
media_gif_max_width,
|
||||
media_gif_max_height,
|
||||
media_gif_max_area,
|
||||
media_enable_silent_video,
|
||||
media_enable_full_video,
|
||||
media_video_codec,
|
||||
|
@ -66,6 +69,18 @@ impl Args {
|
|||
api_key,
|
||||
worker_id,
|
||||
};
|
||||
let gif = if media_gif_max_width.is_none()
|
||||
&& media_gif_max_height.is_none()
|
||||
&& media_gif_max_area.is_none()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(Gif {
|
||||
max_width: media_gif_max_width,
|
||||
max_height: media_gif_max_height,
|
||||
max_area: media_gif_max_area,
|
||||
})
|
||||
};
|
||||
let media = Media {
|
||||
preprocess_steps: media_preprocess_steps,
|
||||
skip_validate_imports: media_skip_validate_imports,
|
||||
|
@ -74,6 +89,7 @@ impl Args {
|
|||
max_area: media_max_area,
|
||||
max_file_size: media_max_file_size,
|
||||
max_frame_count: media_max_frame_count,
|
||||
gif,
|
||||
enable_silent_video: media_enable_silent_video,
|
||||
enable_full_video: media_enable_full_video,
|
||||
video_codec: media_video_codec,
|
||||
|
@ -322,6 +338,8 @@ struct Media {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
max_frame_count: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
gif: Option<Gif>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
enable_silent_video: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
enable_full_video: Option<bool>,
|
||||
|
@ -339,6 +357,17 @@ struct Media {
|
|||
cache_duration: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
struct Gif {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
max_width: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
max_height: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
max_area: Option<usize>,
|
||||
}
|
||||
|
||||
/// Run the pict-rs application
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
|
@ -431,6 +460,24 @@ struct Run {
|
|||
/// The maximum number of frames allowed for uploaded GIF and MP4s.
|
||||
#[arg(long)]
|
||||
media_max_frame_count: Option<usize>,
|
||||
/// Maximum width allowed for gif uploads.
|
||||
///
|
||||
/// If an upload exceeds this value, it will be transcoded to a video format or aborted,
|
||||
/// depending on whether video uploads are enabled.
|
||||
#[arg(long)]
|
||||
media_gif_max_width: Option<usize>,
|
||||
/// Maximum height allowed for gif uploads
|
||||
///
|
||||
/// If an upload exceeds this value, it will be transcoded to a video format or aborted,
|
||||
/// depending on whether video uploads are enabled.
|
||||
#[arg(long)]
|
||||
media_gif_max_height: Option<usize>,
|
||||
/// Maximum area allowed for gif uploads
|
||||
///
|
||||
/// If an upload exceeds this value, it will be transcoded to a video format or aborted,
|
||||
/// depending on whether video uploads are enabled.
|
||||
#[arg(long)]
|
||||
media_gif_max_area: Option<usize>,
|
||||
/// Whether to enable GIF and silent video uploads
|
||||
#[arg(long)]
|
||||
media_enable_silent_video: Option<bool>,
|
||||
|
|
|
@ -66,6 +66,7 @@ struct MediaDefaults {
|
|||
max_area: usize,
|
||||
max_file_size: usize,
|
||||
max_frame_count: usize,
|
||||
gif: GifDefaults,
|
||||
enable_silent_video: bool,
|
||||
enable_full_video: bool,
|
||||
video_codec: VideoCodec,
|
||||
|
@ -74,6 +75,14 @@ struct MediaDefaults {
|
|||
cache_duration: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
struct GifDefaults {
|
||||
max_height: usize,
|
||||
max_width: usize,
|
||||
max_area: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
|
@ -154,6 +163,7 @@ impl Default for MediaDefaults {
|
|||
max_area: 40_000_000,
|
||||
max_file_size: 40,
|
||||
max_frame_count: 900,
|
||||
gif: Default::default(),
|
||||
enable_silent_video: true,
|
||||
enable_full_video: false,
|
||||
video_codec: VideoCodec::Vp9,
|
||||
|
@ -171,6 +181,16 @@ impl Default for MediaDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for GifDefaults {
|
||||
fn default() -> Self {
|
||||
GifDefaults {
|
||||
max_height: 128,
|
||||
max_width: 128,
|
||||
max_area: 16384,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RepoDefaults {
|
||||
fn default() -> Self {
|
||||
Self::Sled(SledDefaults::default())
|
||||
|
|
|
@ -100,12 +100,15 @@ pub(crate) struct Media {
|
|||
|
||||
pub(crate) max_frame_count: usize,
|
||||
|
||||
pub(crate) gif: Gif,
|
||||
|
||||
pub(crate) enable_silent_video: bool,
|
||||
|
||||
pub(crate) enable_full_video: bool,
|
||||
|
||||
pub(crate) video_codec: VideoCodec,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) audio_codec: Option<AudioCodec>,
|
||||
|
||||
pub(crate) filters: BTreeSet<String>,
|
||||
|
@ -118,6 +121,15 @@ pub(crate) struct Media {
|
|||
pub(crate) cache_duration: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct Gif {
|
||||
pub(crate) max_width: usize,
|
||||
|
||||
pub(crate) max_height: usize,
|
||||
|
||||
pub(crate) max_area: usize,
|
||||
}
|
||||
|
||||
impl Media {
|
||||
pub(crate) fn preprocess_steps(&self) -> Option<&[(String, String)]> {
|
||||
static PREPROCESS_STEPS: OnceCell<Vec<(String, String)>> = OnceCell::new();
|
||||
|
|
217
src/ffmpeg.rs
217
src/ffmpeg.rs
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
config::{AudioCodec, ImageFormat, VideoCodec},
|
||||
config::{AudioCodec, ImageFormat, MediaConfiguration, VideoCodec},
|
||||
error::{Error, UploadError},
|
||||
magick::{Details, ValidInputType},
|
||||
process::Process,
|
||||
|
@ -8,6 +8,160 @@ use crate::{
|
|||
use actix_web::web::Bytes;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TranscodeOptions {
|
||||
input_format: VideoFormat,
|
||||
output: TranscodeOutputOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TranscodeOutputOptions {
|
||||
Gif,
|
||||
Video {
|
||||
video_codec: VideoCodec,
|
||||
audio_codec: Option<AudioCodec>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TranscodeOptions {
|
||||
pub(crate) fn new(
|
||||
media: &MediaConfiguration,
|
||||
details: &Details,
|
||||
input_format: VideoFormat,
|
||||
) -> Self {
|
||||
if let VideoFormat::Gif = input_format {
|
||||
if details.width <= media.gif.max_width
|
||||
&& details.height <= media.gif.max_height
|
||||
&& details.width * details.height <= media.gif.max_area
|
||||
{
|
||||
return Self {
|
||||
input_format,
|
||||
output: TranscodeOutputOptions::gif(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
input_format,
|
||||
output: TranscodeOutputOptions::video(media),
|
||||
}
|
||||
}
|
||||
|
||||
const fn input_file_extension(&self) -> &'static str {
|
||||
self.input_format.to_file_extension()
|
||||
}
|
||||
|
||||
const fn output_ffmpeg_video_codec(&self) -> &'static str {
|
||||
match self.output {
|
||||
TranscodeOutputOptions::Gif => "gif",
|
||||
TranscodeOutputOptions::Video { video_codec, .. } => video_codec.to_ffmpeg_codec(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn output_ffmpeg_audio_codec(&self) -> Option<&'static str> {
|
||||
match self.output {
|
||||
TranscodeOutputOptions::Video {
|
||||
audio_codec: Some(audio_codec),
|
||||
..
|
||||
} => Some(audio_codec.to_ffmpeg_codec()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
const fn output_ffmpeg_format(&self) -> &'static str {
|
||||
match self.output {
|
||||
TranscodeOutputOptions::Gif => "gif",
|
||||
TranscodeOutputOptions::Video { video_codec, .. } => {
|
||||
video_codec.to_output_format().to_ffmpeg_format()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn output_file_extension(&self) -> &'static str {
|
||||
match self.output {
|
||||
TranscodeOutputOptions::Gif => ".gif",
|
||||
TranscodeOutputOptions::Video { video_codec, .. } => {
|
||||
video_codec.to_output_format().to_file_extension()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<'a>(
|
||||
&self,
|
||||
input_path: &str,
|
||||
output_path: &'a str,
|
||||
) -> Result<Process, std::io::Error> {
|
||||
if let Some(audio_codec) = self.output_ffmpeg_audio_codec() {
|
||||
Process::run(
|
||||
"ffmpeg",
|
||||
&[
|
||||
"-i",
|
||||
input_path,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-c:a",
|
||||
audio_codec,
|
||||
"-c:v",
|
||||
self.output_ffmpeg_video_codec(),
|
||||
"-f",
|
||||
self.output_ffmpeg_format(),
|
||||
output_path,
|
||||
],
|
||||
)
|
||||
} else {
|
||||
Process::run(
|
||||
"ffmpeg",
|
||||
&[
|
||||
"-i",
|
||||
input_path,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-an",
|
||||
"-c:v",
|
||||
self.output_ffmpeg_video_codec(),
|
||||
"-f",
|
||||
self.output_ffmpeg_format(),
|
||||
output_path,
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn output_type(&self) -> ValidInputType {
|
||||
match self.output {
|
||||
TranscodeOutputOptions::Gif => ValidInputType::Gif,
|
||||
TranscodeOutputOptions::Video { video_codec, .. } => {
|
||||
ValidInputType::from_video_codec(video_codec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TranscodeOutputOptions {
|
||||
fn video(media: &MediaConfiguration) -> Self {
|
||||
Self::Video {
|
||||
video_codec: media.video_codec,
|
||||
audio_codec: if media.enable_full_video {
|
||||
Some(
|
||||
media
|
||||
.audio_codec
|
||||
.unwrap_or(media.video_codec.to_output_format().default_audio_codec()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const fn gif() -> Self {
|
||||
Self::Gif
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum VideoFormat {
|
||||
Gif,
|
||||
|
@ -145,9 +299,12 @@ const FORMAT_MAPPINGS: &[(&str, VideoFormat)] = &[
|
|||
("webm", VideoFormat::Webm),
|
||||
];
|
||||
|
||||
pub(crate) async fn input_type_bytes(input: Bytes) -> Result<Option<ValidInputType>, Error> {
|
||||
pub(crate) async fn input_type_bytes(
|
||||
input: Bytes,
|
||||
) -> Result<Option<(Details, ValidInputType)>, Error> {
|
||||
if let Some(details) = details_bytes(input).await? {
|
||||
return Ok(Some(details.validate_input()?));
|
||||
let input_type = details.validate_input()?;
|
||||
return Ok(Some((details, input_type)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
|
@ -264,19 +421,15 @@ fn parse_details_inner(
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(input))]
|
||||
pub(crate) async fn trancsocde_bytes(
|
||||
pub(crate) async fn transcode_bytes(
|
||||
input: Bytes,
|
||||
input_format: VideoFormat,
|
||||
permit_audio: bool,
|
||||
video_codec: VideoCodec,
|
||||
audio_codec: Option<AudioCodec>,
|
||||
transcode_options: TranscodeOptions,
|
||||
) -> Result<impl AsyncRead + Unpin, Error> {
|
||||
let input_file = crate::tmp_file::tmp_file(Some(input_format.to_file_extension()));
|
||||
let input_file = crate::tmp_file::tmp_file(Some(transcode_options.input_file_extension()));
|
||||
let input_file_str = input_file.to_str().ok_or(UploadError::Path)?;
|
||||
crate::store::file_store::safe_create_parent(&input_file).await?;
|
||||
|
||||
let output_file =
|
||||
crate::tmp_file::tmp_file(Some(video_codec.to_output_format().to_file_extension()));
|
||||
let output_file = crate::tmp_file::tmp_file(Some(transcode_options.output_file_extension()));
|
||||
let output_file_str = output_file.to_str().ok_or(UploadError::Path)?;
|
||||
crate::store::file_store::safe_create_parent(&output_file).await?;
|
||||
|
||||
|
@ -284,47 +437,7 @@ pub(crate) async fn trancsocde_bytes(
|
|||
tmp_one.write_from_bytes(input).await?;
|
||||
tmp_one.close().await?;
|
||||
|
||||
let output_format = video_codec.to_output_format();
|
||||
let audio_codec = audio_codec.unwrap_or_else(|| output_format.default_audio_codec());
|
||||
|
||||
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",
|
||||
audio_codec.to_ffmpeg_codec(),
|
||||
"-c:v",
|
||||
video_codec.to_ffmpeg_codec(),
|
||||
"-f",
|
||||
output_format.to_ffmpeg_format(),
|
||||
output_file_str,
|
||||
],
|
||||
)?
|
||||
} else {
|
||||
Process::run(
|
||||
"ffmpeg",
|
||||
&[
|
||||
"-i",
|
||||
input_file_str,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-vf",
|
||||
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-an",
|
||||
"-c:v",
|
||||
video_codec.to_ffmpeg_codec(),
|
||||
"-f",
|
||||
output_format.to_ffmpeg_format(),
|
||||
output_file_str,
|
||||
],
|
||||
)?
|
||||
};
|
||||
let process = transcode_options.execute(input_file_str, output_file_str)?;
|
||||
|
||||
process.wait().await?;
|
||||
tokio::fs::remove_file(input_file).await?;
|
||||
|
|
|
@ -59,16 +59,8 @@ where
|
|||
let bytes = aggregate(stream).await?;
|
||||
|
||||
tracing::trace!("Validating bytes");
|
||||
let (input_type, validated_reader) = crate::validate::validate_bytes(
|
||||
bytes,
|
||||
CONFIG.media.format,
|
||||
CONFIG.media.enable_silent_video,
|
||||
CONFIG.media.enable_full_video,
|
||||
CONFIG.media.video_codec,
|
||||
CONFIG.media.audio_codec,
|
||||
should_validate,
|
||||
)
|
||||
.await?;
|
||||
let (input_type, validated_reader) =
|
||||
crate::validate::validate_bytes(bytes, &CONFIG.media, should_validate).await?;
|
||||
|
||||
let processed_reader = if let Some(operations) = CONFIG.media.preprocess_steps() {
|
||||
if let Some(format) = input_type.to_format() {
|
||||
|
|
|
@ -45,7 +45,7 @@ pub(crate) enum ValidInputType {
|
|||
}
|
||||
|
||||
impl ValidInputType {
|
||||
fn as_str(self) -> &'static str {
|
||||
const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Mp4 => "MP4",
|
||||
Self::Webm => "WEBM",
|
||||
|
@ -56,7 +56,7 @@ impl ValidInputType {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_ext(self) -> &'static str {
|
||||
pub(crate) const fn as_ext(self) -> &'static str {
|
||||
match self {
|
||||
Self::Mp4 => ".mp4",
|
||||
Self::Webm => ".webm",
|
||||
|
@ -67,11 +67,11 @@ impl ValidInputType {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_video(self) -> bool {
|
||||
pub(crate) const fn is_video(self) -> bool {
|
||||
matches!(self, Self::Mp4 | Self::Webm | Self::Gif)
|
||||
}
|
||||
|
||||
fn video_hint(self) -> Option<&'static str> {
|
||||
const fn video_hint(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::Mp4 => Some(".mp4"),
|
||||
Self::Webm => Some(".webm"),
|
||||
|
@ -80,14 +80,14 @@ impl ValidInputType {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_video_codec(codec: VideoCodec) -> Self {
|
||||
pub(crate) const fn from_video_codec(codec: VideoCodec) -> Self {
|
||||
match codec {
|
||||
VideoCodec::Av1 | VideoCodec::Vp8 | VideoCodec::Vp9 => Self::Webm,
|
||||
VideoCodec::H264 | VideoCodec::H265 => Self::Mp4,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_format(format: ImageFormat) -> Self {
|
||||
pub(crate) const fn from_format(format: ImageFormat) -> Self {
|
||||
match format {
|
||||
ImageFormat::Jpeg => ValidInputType::Jpeg,
|
||||
ImageFormat::Png => ValidInputType::Png,
|
||||
|
@ -95,7 +95,7 @@ impl ValidInputType {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_format(self) -> Option<ImageFormat> {
|
||||
pub(crate) const fn to_format(self) -> Option<ImageFormat> {
|
||||
match self {
|
||||
Self::Jpeg => Some(ImageFormat::Jpeg),
|
||||
Self::Png => Some(ImageFormat::Png),
|
||||
|
@ -283,8 +283,10 @@ fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn input_type_bytes(input: Bytes) -> Result<ValidInputType, Error> {
|
||||
details_bytes(input, None).await?.validate_input()
|
||||
pub(crate) async fn input_type_bytes(input: Bytes) -> Result<(Details, ValidInputType), Error> {
|
||||
let details = details_bytes(input, None).await?;
|
||||
let input_type = details.validate_input()?;
|
||||
Ok((details, input_type))
|
||||
}
|
||||
|
||||
fn process_image(args: Vec<String>, format: ImageFormat) -> std::io::Result<Process> {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::{
|
||||
config::{AudioCodec, ImageFormat, VideoCodec},
|
||||
config::{ImageFormat, MediaConfiguration},
|
||||
either::Either,
|
||||
error::{Error, UploadError},
|
||||
ffmpeg::FileFormat,
|
||||
ffmpeg::{FileFormat, TranscodeOptions},
|
||||
magick::ValidInputType,
|
||||
};
|
||||
use actix_web::web::Bytes;
|
||||
|
@ -38,16 +38,12 @@ impl AsyncRead for UnvalidatedBytes {
|
|||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn validate_bytes(
|
||||
bytes: Bytes,
|
||||
prescribed_format: Option<ImageFormat>,
|
||||
enable_silent_video: bool,
|
||||
enable_full_video: bool,
|
||||
video_codec: VideoCodec,
|
||||
audio_codec: Option<AudioCodec>,
|
||||
media: &MediaConfiguration,
|
||||
validate: bool,
|
||||
) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> {
|
||||
let input_type =
|
||||
if let Some(input_type) = crate::ffmpeg::input_type_bytes(bytes.clone()).await? {
|
||||
input_type
|
||||
let (details, input_type) =
|
||||
if let Some(tup) = crate::ffmpeg::input_type_bytes(bytes.clone()).await? {
|
||||
tup
|
||||
} else {
|
||||
crate::magick::input_type_bytes(bytes.clone()).await?
|
||||
};
|
||||
|
@ -56,22 +52,17 @@ pub(crate) async fn validate_bytes(
|
|||
return Ok((input_type, Either::left(UnvalidatedBytes::new(bytes))));
|
||||
}
|
||||
|
||||
match (input_type.to_file_format(), prescribed_format) {
|
||||
match (input_type.to_file_format(), media.format) {
|
||||
(FileFormat::Video(video_format), _) => {
|
||||
if !(enable_silent_video || enable_full_video) {
|
||||
if !(media.enable_silent_video || media.enable_full_video) {
|
||||
return Err(UploadError::SilentVideoDisabled.into());
|
||||
}
|
||||
let transcode_options = TranscodeOptions::new(media, &details, video_format);
|
||||
|
||||
Ok((
|
||||
ValidInputType::from_video_codec(video_codec),
|
||||
transcode_options.output_type(),
|
||||
Either::right(Either::left(Either::left(
|
||||
crate::ffmpeg::trancsocde_bytes(
|
||||
bytes,
|
||||
video_format,
|
||||
enable_full_video,
|
||||
video_codec,
|
||||
audio_codec,
|
||||
)
|
||||
.await?,
|
||||
crate::ffmpeg::transcode_bytes(bytes, transcode_options).await?,
|
||||
))),
|
||||
))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue