Better identify video uploads with ffprobe

This commit is contained in:
asonix 2022-09-25 19:34:51 -05:00
parent 21e3e63ac3
commit 5449bb82f1
3 changed files with 67 additions and 10 deletions

View file

@ -2,47 +2,58 @@ use crate::{
error::{Error, UploadError},
process::Process,
store::Store,
magick::ValidInputType,
};
use actix_web::web::Bytes;
use tokio::io::AsyncRead;
use tokio::io::{AsyncRead, AsyncReadExt};
use tracing::instrument;
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub(crate) enum InputFormat {
Gif,
Mp4,
Webm,
}
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub(crate) enum ThumbnailFormat {
Jpeg,
// Webp,
}
impl InputFormat {
fn to_ext(&self) -> &'static str {
fn to_ext(self) -> &'static str {
match self {
InputFormat::Gif => ".gif",
InputFormat::Mp4 => ".mp4",
InputFormat::Webm => ".webm",
}
}
pub(crate) fn to_valid_input_type(self) -> ValidInputType {
match self {
Self::Gif => ValidInputType::Gif,
Self::Mp4 => ValidInputType::Mp4,
Self::Webm => ValidInputType::Webm,
}
}
}
impl ThumbnailFormat {
fn as_codec(&self) -> &'static str {
fn as_codec(self) -> &'static str {
match self {
ThumbnailFormat::Jpeg => "mjpeg",
// ThumbnailFormat::Webp => "webp",
}
}
fn to_ext(&self) -> &'static str {
fn to_ext(self) -> &'static str {
match self {
ThumbnailFormat::Jpeg => ".jpeg",
}
}
fn as_format(&self) -> &'static str {
fn as_format(self) -> &'static str {
match self {
ThumbnailFormat::Jpeg => "image2",
// ThumbnailFormat::Webp => "webp",
@ -50,6 +61,48 @@ impl ThumbnailFormat {
}
}
const FORMAT_MAPPINGS: &[(&str, InputFormat)] = &[
("gif", InputFormat::Gif),
("mp4", InputFormat::Mp4),
("webm", InputFormat::Webm),
];
pub(crate) async fn input_type_bytes(
input: Bytes,
) -> Result<Option<InputFormat>, Error> {
let input_file = crate::tmp_file::tmp_file(None);
let input_file_str = input_file.to_str().ok_or(UploadError::Path)?;
crate::store::file_store::safe_create_parent(&input_file).await?;
let mut tmp_one = crate::file::File::create(&input_file).await?;
tmp_one.write_from_bytes(input).await?;
tmp_one.close().await?;
let process = Process::run("ffprobe", &[
"-v",
"quiet",
"-show_entries",
"format=format_name",
"-of",
"default=noprint_wrappers=1:nokey=1",
input_file_str,
])?;
let mut output = Vec::new();
process.read().read_to_end(&mut output).await?;
let formats = String::from_utf8_lossy(&output);
tracing::info!("FORMATS: {}", formats);
for (k, v) in FORMAT_MAPPINGS {
if formats.contains(k) {
return Ok(Some(*v))
}
}
Ok(None)
}
#[tracing::instrument(name = "Convert to Mp4", skip(input))]
pub(crate) async fn to_mp4_bytes(
input: Bytes,

View file

@ -68,7 +68,7 @@ where
let span = tracing::error_span!("Error deleting files");
span.in_scope(|| {
for error in errors {
tracing::error!("{}", format!("{}" error));
tracing::error!("{}", format!("{}", error));
}
});
}

View file

@ -44,7 +44,11 @@ pub(crate) async fn validate_image_bytes(
enable_full_video: bool,
validate: bool,
) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> {
let input_type = crate::magick::input_type_bytes(bytes.clone()).await?;
let input_type = if let Some(input_type) = crate::ffmpeg::input_type_bytes(bytes.clone()).await? {
input_type.to_valid_input_type()
} else {
crate::magick::input_type_bytes(bytes.clone()).await?
};
if !validate {
return Ok((input_type, Either::left(UnvalidatedBytes::new(bytes))));
@ -80,7 +84,7 @@ pub(crate) async fn validate_image_bytes(
Ok((
ValidInputType::Mp4,
Either::right(Either::left(
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4, enable_full_video).await?,
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Webm, enable_full_video).await?,
)),
))
}