pict-rs/src/ffmpeg.rs

166 lines
4.4 KiB
Rust
Raw Normal View History

2021-10-23 19:14:12 +00:00
use crate::{
formats::InternalVideoFormat,
2023-07-10 20:29:41 +00:00
process::{Process, ProcessError},
store::{Store, StoreError},
2021-10-23 19:14:12 +00:00
};
use tokio::io::AsyncRead;
2023-07-10 20:29:41 +00:00
#[derive(Clone, Copy, Debug)]
pub(crate) enum ThumbnailFormat {
Jpeg,
// Webp,
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum FfMpegError {
#[error("Error in ffmpeg process")]
Process(#[source] ProcessError),
#[error("Error reading output")]
Read(#[source] std::io::Error),
#[error("Error writing bytes")]
Write(#[source] std::io::Error),
#[error("Invalid output format")]
Json(#[source] serde_json::Error),
#[error("Error creating parent directory")]
CreateDir(#[source] crate::store::file_store::FileError),
#[error("Error reading file to stream")]
ReadFile(#[source] crate::store::file_store::FileError),
#[error("Error opening file")]
OpenFile(#[source] std::io::Error),
#[error("Error creating file")]
CreateFile(#[source] std::io::Error),
#[error("Error closing file")]
CloseFile(#[source] std::io::Error),
#[error("Error removing file")]
RemoveFile(#[source] std::io::Error),
#[error("Error in store")]
Store(#[source] StoreError),
#[error("Invalid file path")]
Path,
}
impl FfMpegError {
pub(crate) fn is_client_error(&self) -> bool {
// Failing validation or ffmpeg bailing probably means bad input
matches!(self, Self::Process(ProcessError::Status(_)))
2023-07-10 20:29:41 +00:00
}
pub(crate) fn is_not_found(&self) -> bool {
if let Self::Store(e) = self {
return e.is_not_found();
}
false
}
}
impl ThumbnailFormat {
const fn as_ffmpeg_codec(self) -> &'static str {
match self {
Self::Jpeg => "mjpeg",
// Self::Webp => "webp",
}
}
2022-10-01 01:00:14 +00:00
const fn to_file_extension(self) -> &'static str {
match self {
Self::Jpeg => ".jpeg",
// Self::Webp => ".webp",
}
}
const fn as_ffmpeg_format(self) -> &'static str {
match self {
Self::Jpeg => "image2",
// Self::Webp => "webp",
}
}
pub(crate) fn media_type(self) -> mime::Mime {
match self {
Self::Jpeg => mime::IMAGE_JPEG,
// Self::Webp => crate::formats::mimes::image_webp(),
}
}
}
#[tracing::instrument(skip(store))]
2021-10-23 04:48:56 +00:00
pub(crate) async fn thumbnail<S: Store>(
store: S,
from: S::Identifier,
input_format: InternalVideoFormat,
format: ThumbnailFormat,
2023-07-10 20:29:41 +00:00
) -> Result<impl AsyncRead + Unpin, FfMpegError> {
let input_file = crate::tmp_file::tmp_file(Some(input_format.file_extension()));
2023-07-10 20:29:41 +00:00
let input_file_str = input_file.to_str().ok_or(FfMpegError::Path)?;
crate::store::file_store::safe_create_parent(&input_file)
.await
2023-07-10 20:29:41 +00:00
.map_err(FfMpegError::CreateDir)?;
2021-10-23 19:14:12 +00:00
2022-10-01 01:00:14 +00:00
let output_file = crate::tmp_file::tmp_file(Some(format.to_file_extension()));
2023-07-10 20:29:41 +00:00
let output_file_str = output_file.to_str().ok_or(FfMpegError::Path)?;
crate::store::file_store::safe_create_parent(&output_file)
.await
2023-07-10 20:29:41 +00:00
.map_err(FfMpegError::CreateDir)?;
2021-10-23 19:14:12 +00:00
2023-07-10 20:29:41 +00:00
let mut tmp_one = crate::file::File::create(&input_file)
.await
.map_err(FfMpegError::CreateFile)?;
let stream = store
.to_stream(&from, None, None)
.await
.map_err(FfMpegError::Store)?;
2021-10-23 19:14:12 +00:00
tmp_one
2023-07-10 20:29:41 +00:00
.write_from_stream(stream)
.await
.map_err(FfMpegError::Write)?;
tmp_one.close().await.map_err(FfMpegError::CloseFile)?;
2021-10-23 19:14:12 +00:00
2021-10-23 04:48:56 +00:00
let process = Process::run(
"ffmpeg",
&[
2023-02-25 20:31:57 +00:00
"-hide_banner",
"-v",
"warning",
2021-10-23 04:48:56 +00:00
"-i",
input_file_str,
2023-02-04 23:47:03 +00:00
"-frames:v",
2021-10-23 04:48:56 +00:00
"1",
"-codec",
format.as_ffmpeg_codec(),
2021-10-23 04:48:56 +00:00
"-f",
format.as_ffmpeg_format(),
output_file_str,
2021-10-23 04:48:56 +00:00
],
2023-07-10 20:29:41 +00:00
)
.map_err(FfMpegError::Process)?;
2023-07-10 20:29:41 +00:00
process.wait().await.map_err(FfMpegError::Process)?;
tokio::fs::remove_file(input_file)
.await
.map_err(FfMpegError::RemoveFile)?;
2021-10-23 19:14:12 +00:00
2023-07-10 20:29:41 +00:00
let tmp_two = crate::file::File::open(&output_file)
.await
.map_err(FfMpegError::OpenFile)?;
let stream = tmp_two
.read_to_stream(None, None)
.await
2023-07-10 20:29:41 +00:00
.map_err(FfMpegError::ReadFile)?;
2021-10-23 19:14:12 +00:00
let reader = tokio_util::io::StreamReader::new(stream);
let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, output_file);
Ok(Box::pin(clean_reader))
}