mirror of
https://git.asonix.dog/asonix/pict-rs.git
synced 2024-11-28 20:41:00 +00:00
Use tmp files for mp4s
This commit is contained in:
parent
6f04595c3b
commit
26a2401027
9 changed files with 323 additions and 54 deletions
|
@ -1,4 +1,8 @@
|
||||||
use crate::{error::Error, process::Process, store::Store};
|
use crate::{
|
||||||
|
error::{Error, UploadError},
|
||||||
|
process::Process,
|
||||||
|
store::Store,
|
||||||
|
};
|
||||||
use actix_web::web::Bytes;
|
use actix_web::web::Bytes;
|
||||||
use tokio::io::AsyncRead;
|
use tokio::io::AsyncRead;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
@ -16,10 +20,10 @@ pub(crate) enum ThumbnailFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputFormat {
|
impl InputFormat {
|
||||||
fn as_format(&self) -> &'static str {
|
fn to_ext(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
InputFormat::Gif => "gif_pipe",
|
InputFormat::Gif => ".gif",
|
||||||
InputFormat::Mp4 => "mp4",
|
InputFormat::Mp4 => ".mp4",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +36,12 @@ impl ThumbnailFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
match self {
|
||||||
ThumbnailFormat::Jpeg => "singlejpeg",
|
ThumbnailFormat::Jpeg => "singlejpeg",
|
||||||
|
@ -40,19 +50,27 @@ impl ThumbnailFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_mp4_bytes(
|
pub(crate) async fn to_mp4_bytes(
|
||||||
input: Bytes,
|
input: Bytes,
|
||||||
input_format: InputFormat,
|
input_format: InputFormat,
|
||||||
) -> std::io::Result<impl AsyncRead + Unpin> {
|
) -> 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)?;
|
||||||
|
crate::store::file_store::safe_create_parent(&input_file).await?;
|
||||||
|
|
||||||
|
let output_file = crate::tmp_file::tmp_file(Some(".mp4"));
|
||||||
|
let output_file_str = output_file.to_str().ok_or(UploadError::Path)?;
|
||||||
|
crate::store::file_store::safe_create_parent(&output_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(
|
let process = Process::run(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
&[
|
&[
|
||||||
"-f",
|
|
||||||
input_format.as_format(),
|
|
||||||
"-i",
|
"-i",
|
||||||
"pipe:",
|
&input_file_str,
|
||||||
"-movflags",
|
|
||||||
"faststart+frag_keyframe+empty_moov",
|
|
||||||
"-pix_fmt",
|
"-pix_fmt",
|
||||||
"yuv420p",
|
"yuv420p",
|
||||||
"-vf",
|
"-vf",
|
||||||
|
@ -62,11 +80,19 @@ pub(crate) fn to_mp4_bytes(
|
||||||
"h264",
|
"h264",
|
||||||
"-f",
|
"-f",
|
||||||
"mp4",
|
"mp4",
|
||||||
"pipe:",
|
&output_file_str,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(process.bytes_read(input).unwrap())
|
process.wait().await?;
|
||||||
|
tokio::fs::remove_file(input_file).await?;
|
||||||
|
|
||||||
|
let tmp_two = crate::file::File::open(&output_file).await?;
|
||||||
|
let stream = tmp_two.read_to_stream(None, None).await?;
|
||||||
|
let reader = tokio_util::io::StreamReader::new(stream);
|
||||||
|
let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, output_file);
|
||||||
|
|
||||||
|
Ok(Box::pin(clean_reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(name = "Create video thumbnail")]
|
#[instrument(name = "Create video thumbnail")]
|
||||||
|
@ -75,23 +101,46 @@ pub(crate) async fn thumbnail<S: Store>(
|
||||||
from: S::Identifier,
|
from: S::Identifier,
|
||||||
input_format: InputFormat,
|
input_format: InputFormat,
|
||||||
format: ThumbnailFormat,
|
format: ThumbnailFormat,
|
||||||
) -> Result<impl AsyncRead + Unpin, Error> {
|
) -> Result<impl AsyncRead + Unpin, Error>
|
||||||
|
where
|
||||||
|
Error: From<S::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)?;
|
||||||
|
crate::store::file_store::safe_create_parent(&input_file).await?;
|
||||||
|
|
||||||
|
let output_file = crate::tmp_file::tmp_file(Some(format.to_ext()));
|
||||||
|
let output_file_str = output_file.to_str().ok_or(UploadError::Path)?;
|
||||||
|
crate::store::file_store::safe_create_parent(&output_file).await?;
|
||||||
|
|
||||||
|
let mut tmp_one = crate::file::File::create(&input_file).await?;
|
||||||
|
tmp_one
|
||||||
|
.write_from_stream(store.to_stream(&from, None, None).await?)
|
||||||
|
.await?;
|
||||||
|
tmp_one.close().await?;
|
||||||
|
|
||||||
let process = Process::run(
|
let process = Process::run(
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
&[
|
&[
|
||||||
"-f",
|
|
||||||
input_format.as_format(),
|
|
||||||
"-i",
|
"-i",
|
||||||
"pipe:",
|
&input_file_str,
|
||||||
"-vframes",
|
"-vframes",
|
||||||
"1",
|
"1",
|
||||||
"-codec",
|
"-codec",
|
||||||
format.as_codec(),
|
format.as_codec(),
|
||||||
"-f",
|
"-f",
|
||||||
format.as_format(),
|
format.as_format(),
|
||||||
"pipe:",
|
&output_file_str,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(process.store_read(store, from).unwrap())
|
process.wait().await?;
|
||||||
|
tokio::fs::remove_file(input_file).await?;
|
||||||
|
|
||||||
|
let tmp_two = crate::file::File::open(&output_file).await?;
|
||||||
|
let stream = tmp_two.read_to_stream(None, None).await?;
|
||||||
|
let reader = tokio_util::io::StreamReader::new(stream);
|
||||||
|
let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, output_file);
|
||||||
|
|
||||||
|
Ok(Box::pin(clean_reader))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ pub(crate) use tokio_file::File;
|
||||||
mod tokio_file {
|
mod tokio_file {
|
||||||
use crate::{store::file_store::FileError, Either};
|
use crate::{store::file_store::FileError, Either};
|
||||||
use actix_web::web::{Bytes, BytesMut};
|
use actix_web::web::{Bytes, BytesMut};
|
||||||
use futures_util::stream::Stream;
|
use futures_util::stream::{Stream, StreamExt};
|
||||||
use std::{io::SeekFrom, path::Path};
|
use std::{io::SeekFrom, path::Path, pin::Pin};
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||||
|
|
||||||
|
@ -35,6 +35,22 @@ mod tokio_file {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn write_from_stream<S>(&mut self, mut stream: S) -> std::io::Result<()>
|
||||||
|
where
|
||||||
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
|
{
|
||||||
|
// SAFETY: pinned stream shadows original stream so it cannot be moved
|
||||||
|
let mut stream = unsafe { Pin::new_unchecked(&mut stream) };
|
||||||
|
|
||||||
|
while let Some(res) = stream.next().await {
|
||||||
|
let mut bytes = res?;
|
||||||
|
|
||||||
|
self.inner.write_all_buf(&mut bytes).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn write_from_async_read<R>(
|
pub(crate) async fn write_from_async_read<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
|
@ -46,6 +62,10 @@ mod tokio_file {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn close(self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn read_to_async_write<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
pub(crate) async fn read_to_async_write<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
W: AsyncWrite + Unpin + ?Sized,
|
W: AsyncWrite + Unpin + ?Sized,
|
||||||
|
@ -112,7 +132,7 @@ mod tokio_file {
|
||||||
mod io_uring {
|
mod io_uring {
|
||||||
use crate::store::file_store::FileError;
|
use crate::store::file_store::FileError;
|
||||||
use actix_web::web::Bytes;
|
use actix_web::web::Bytes;
|
||||||
use futures_util::stream::Stream;
|
use futures_util::stream::{Stream, StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
fs::Metadata,
|
fs::Metadata,
|
||||||
|
@ -182,6 +202,50 @@ mod io_uring {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn write_from_stream<S>(&mut self, mut stream: S) -> std::io::Result<()>
|
||||||
|
where
|
||||||
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
|
{
|
||||||
|
// SAFETY: pinned stream shadows original stream so it cannot be moved
|
||||||
|
let mut stream = unsafe { Pin::new_unchecked(&mut stream) };
|
||||||
|
let mut cursor: u64 = 0;
|
||||||
|
|
||||||
|
while let Some(res) = stream.next().await {
|
||||||
|
let bytes = res?;
|
||||||
|
let mut buf = bytes.to_vec();
|
||||||
|
|
||||||
|
let len = buf.len();
|
||||||
|
let mut position = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if position == len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let position_u64: u64 = position.try_into().unwrap();
|
||||||
|
let (res, slice) = self
|
||||||
|
.write_at(buf.slice(position..len), cursor + position_u64)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let n = res?;
|
||||||
|
if n == 0 {
|
||||||
|
return Err(std::io::ErrorKind::UnexpectedEof.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
position += n;
|
||||||
|
|
||||||
|
buf = slice.into_inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
let position: u64 = position.try_into().unwrap();
|
||||||
|
cursor += position;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.sync_all().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn write_from_async_read<R>(
|
pub(crate) async fn write_from_async_read<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
|
@ -232,6 +296,10 @@ mod io_uring {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn close(self) -> std::io::Result<()> {
|
||||||
|
self.inner.close().await
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn read_to_async_write<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
pub(crate) async fn read_to_async_write<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
W: AsyncWrite + Unpin + ?Sized,
|
W: AsyncWrite + Unpin + ?Sized,
|
|
@ -57,6 +57,10 @@ impl ValidInputType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_mp4(&self) -> bool {
|
||||||
|
matches!(self, Self::Mp4)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn from_format(format: Format) -> Self {
|
pub(crate) fn from_format(format: Format) -> Self {
|
||||||
match format {
|
match format {
|
||||||
Format::Jpeg => ValidInputType::Jpeg,
|
Format::Jpeg => ValidInputType::Jpeg,
|
||||||
|
@ -79,11 +83,40 @@ pub(crate) fn clear_metadata_bytes_read(input: Bytes) -> std::io::Result<impl As
|
||||||
Ok(process.bytes_read(input).unwrap())
|
Ok(process.bytes_read(input).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn convert_bytes_read(
|
||||||
|
input: Bytes,
|
||||||
|
format: Format,
|
||||||
|
) -> std::io::Result<impl AsyncRead + Unpin> {
|
||||||
|
let process = Process::run(
|
||||||
|
"magick",
|
||||||
|
&[
|
||||||
|
"convert",
|
||||||
|
"-",
|
||||||
|
"-strip",
|
||||||
|
format!("{}:-", format.to_magick_format()).as_str(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(process.bytes_read(input).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(name = "Getting details from input bytes", skip(input))]
|
#[instrument(name = "Getting details from input bytes", skip(input))]
|
||||||
pub(crate) async fn details_bytes(
|
pub(crate) async fn details_bytes(
|
||||||
input: Bytes,
|
input: Bytes,
|
||||||
hint: Option<ValidInputType>,
|
hint: Option<ValidInputType>,
|
||||||
) -> Result<Details, Error> {
|
) -> 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"));
|
||||||
|
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?;
|
||||||
|
|
||||||
|
return details_file(input_file_str).await;
|
||||||
|
}
|
||||||
|
|
||||||
let last_arg = if let Some(expected_format) = hint {
|
let last_arg = if let Some(expected_format) = hint {
|
||||||
format!("{}:-", expected_format.to_str())
|
format!("{}:-", expected_format.to_str())
|
||||||
} else {
|
} else {
|
||||||
|
@ -104,29 +137,29 @@ pub(crate) async fn details_bytes(
|
||||||
parse_details(s)
|
parse_details(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn convert_bytes_read(
|
|
||||||
input: Bytes,
|
|
||||||
format: Format,
|
|
||||||
) -> std::io::Result<impl AsyncRead + Unpin> {
|
|
||||||
let process = Process::run(
|
|
||||||
"magick",
|
|
||||||
&[
|
|
||||||
"convert",
|
|
||||||
"-",
|
|
||||||
"-strip",
|
|
||||||
format!("{}:-", format.to_magick_format()).as_str(),
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(process.bytes_read(input).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn details_store<S: Store>(
|
pub(crate) async fn details_store<S: Store>(
|
||||||
store: S,
|
store: S,
|
||||||
identifier: S::Identifier,
|
identifier: S::Identifier,
|
||||||
expected_format: Option<ValidInputType>,
|
hint: Option<ValidInputType>,
|
||||||
) -> Result<Details, Error> {
|
) -> Result<Details, Error>
|
||||||
let last_arg = if let Some(expected_format) = expected_format {
|
where
|
||||||
|
Error: From<S::Error>,
|
||||||
|
{
|
||||||
|
if hint.as_ref().map(|h| h.is_mp4()).unwrap_or(false) {
|
||||||
|
let input_file = crate::tmp_file::tmp_file(Some(".mp4"));
|
||||||
|
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_stream(store.to_stream(&identifier, None, None).await?)
|
||||||
|
.await?;
|
||||||
|
tmp_one.close().await?;
|
||||||
|
|
||||||
|
return details_file(input_file_str).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_arg = if let Some(expected_format) = hint {
|
||||||
format!("{}:-", expected_format.to_str())
|
format!("{}:-", expected_format.to_str())
|
||||||
} else {
|
} else {
|
||||||
"-".to_owned()
|
"-".to_owned()
|
||||||
|
@ -147,6 +180,23 @@ pub(crate) async fn details_store<S: Store>(
|
||||||
parse_details(s)
|
parse_details(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn details_file(path_str: &str) -> Result<Details, Error> {
|
||||||
|
let process = Process::run(
|
||||||
|
"magick",
|
||||||
|
&["identify", "-ping", "-format", "%w %h | %m\n", &path_str],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut reader = process.read().unwrap();
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
reader.read_to_end(&mut output).await?;
|
||||||
|
tokio::fs::remove_file(path_str).await?;
|
||||||
|
|
||||||
|
let s = String::from_utf8_lossy(&output);
|
||||||
|
|
||||||
|
parse_details(s)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
|
fn parse_details(s: std::borrow::Cow<'_, str>) -> Result<Details, Error> {
|
||||||
let mut lines = s.lines();
|
let mut lines = s.lines();
|
||||||
let first = lines.next().ok_or(UploadError::UnsupportedFormat)?;
|
let first = lines.next().ok_or(UploadError::UnsupportedFormat)?;
|
||||||
|
|
|
@ -28,6 +28,7 @@ mod either;
|
||||||
mod error;
|
mod error;
|
||||||
mod exiftool;
|
mod exiftool;
|
||||||
mod ffmpeg;
|
mod ffmpeg;
|
||||||
|
mod file;
|
||||||
mod init_tracing;
|
mod init_tracing;
|
||||||
mod magick;
|
mod magick;
|
||||||
mod map_error;
|
mod map_error;
|
||||||
|
@ -37,6 +38,7 @@ mod process;
|
||||||
mod processor;
|
mod processor;
|
||||||
mod range;
|
mod range;
|
||||||
mod store;
|
mod store;
|
||||||
|
mod tmp_file;
|
||||||
mod upload_manager;
|
mod upload_manager;
|
||||||
mod validate;
|
mod validate;
|
||||||
|
|
||||||
|
@ -821,6 +823,8 @@ where
|
||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
crate::tmp_file::remove_tmp_dir().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,14 @@ impl Process {
|
||||||
cmd.spawn().map(|child| Process { child, span })
|
cmd.spawn().map(|child| Process { child, span })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn wait(mut self) -> std::io::Result<()> {
|
||||||
|
let status = self.child.wait().await?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, &StatusError));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn bytes_read(mut self, mut input: Bytes) -> Option<impl AsyncRead + Unpin> {
|
pub(crate) fn bytes_read(mut self, mut input: Bytes) -> Option<impl AsyncRead + Unpin> {
|
||||||
let mut stdin = self.child.stdin.take()?;
|
let mut stdin = self.child.stdin.take()?;
|
||||||
let stdout = self.child.stdout.take()?;
|
let stdout = self.child.stdout.take()?;
|
||||||
|
@ -103,6 +111,39 @@ impl Process {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read(mut self) -> Option<impl AsyncRead + Unpin> {
|
||||||
|
let stdout = self.child.stdout.take()?;
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let span = self.spawn_span();
|
||||||
|
let mut child = self.child;
|
||||||
|
let handle = actix_rt::spawn(
|
||||||
|
async move {
|
||||||
|
match child.wait().await {
|
||||||
|
Ok(status) => {
|
||||||
|
if !status.success() {
|
||||||
|
let _ = tx
|
||||||
|
.send(std::io::Error::new(std::io::ErrorKind::Other, &StatusError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let _ = tx.send(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.instrument(span),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(ProcessRead {
|
||||||
|
inner: stdout,
|
||||||
|
span: self.span,
|
||||||
|
err_recv: rx,
|
||||||
|
err_closed: false,
|
||||||
|
handle: DropHandle { inner: handle },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn store_read<S: Store>(
|
pub(crate) fn store_read<S: Store>(
|
||||||
mut self,
|
mut self,
|
||||||
store: S,
|
store: S,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::store::Store;
|
use crate::{file::File, store::Store};
|
||||||
use actix_web::web::Bytes;
|
use actix_web::web::Bytes;
|
||||||
use futures_util::stream::Stream;
|
use futures_util::stream::Stream;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -10,10 +10,8 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::{debug, error, instrument};
|
use tracing::{debug, error, instrument};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod file;
|
|
||||||
mod file_id;
|
mod file_id;
|
||||||
mod restructure;
|
mod restructure;
|
||||||
use file::File;
|
|
||||||
pub(crate) use file_id::FileId;
|
pub(crate) use file_id::FileId;
|
||||||
|
|
||||||
// - Settings Tree
|
// - Settings Tree
|
||||||
|
|
58
src/tmp_file.rs
Normal file
58
src/tmp_file.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::io::AsyncRead;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
static TMP_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||||
|
let dir = std::env::temp_dir().join(Uuid::new_v4().to_string());
|
||||||
|
std::fs::create_dir(&dir).unwrap();
|
||||||
|
dir
|
||||||
|
});
|
||||||
|
|
||||||
|
struct TmpFile(PathBuf);
|
||||||
|
|
||||||
|
impl Drop for TmpFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
actix_rt::spawn(tokio::fs::remove_file(self.0.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project_lite::pin_project! {
|
||||||
|
pub(crate) struct TmpFileCleanup<R> {
|
||||||
|
#[pin]
|
||||||
|
inner: R,
|
||||||
|
|
||||||
|
file: TmpFile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tmp_file(ext: Option<&str>) -> PathBuf {
|
||||||
|
if let Some(ext) = ext {
|
||||||
|
TMP_DIR.join(format!("{}{}", Uuid::new_v4(), ext))
|
||||||
|
} else {
|
||||||
|
TMP_DIR.join(Uuid::new_v4().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn remove_tmp_dir() -> std::io::Result<()> {
|
||||||
|
tokio::fs::remove_dir_all(&*TMP_DIR).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cleanup_tmpfile<R: AsyncRead>(reader: R, file: PathBuf) -> TmpFileCleanup<R> {
|
||||||
|
TmpFileCleanup {
|
||||||
|
inner: reader,
|
||||||
|
file: TmpFile(file),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: AsyncRead> AsyncRead for TmpFileCleanup<R> {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
buf: &mut tokio::io::ReadBuf<'_>,
|
||||||
|
) -> std::task::Poll<std::io::Result<()>> {
|
||||||
|
let this = self.as_mut().project();
|
||||||
|
|
||||||
|
this.inner.poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
|
@ -585,7 +585,10 @@ impl Details {
|
||||||
store: S,
|
store: S,
|
||||||
identifier: S::Identifier,
|
identifier: S::Identifier,
|
||||||
expected_format: Option<ValidInputType>,
|
expected_format: Option<ValidInputType>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
Error: From<S::Error>,
|
||||||
|
{
|
||||||
let details = crate::magick::details_store(store, identifier, expected_format).await?;
|
let details = crate::magick::details_store(store, identifier, expected_format).await?;
|
||||||
|
|
||||||
Ok(Details::now(
|
Ok(Details::now(
|
||||||
|
|
|
@ -47,17 +47,15 @@ pub(crate) async fn validate_image_bytes(
|
||||||
match (prescribed_format, input_type) {
|
match (prescribed_format, input_type) {
|
||||||
(_, ValidInputType::Gif) => Ok((
|
(_, ValidInputType::Gif) => Ok((
|
||||||
ValidInputType::Mp4,
|
ValidInputType::Mp4,
|
||||||
Either::right(Either::left(crate::ffmpeg::to_mp4_bytes(
|
Either::right(Either::left(
|
||||||
bytes,
|
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Gif).await?,
|
||||||
InputFormat::Gif,
|
)),
|
||||||
)?)),
|
|
||||||
)),
|
)),
|
||||||
(_, ValidInputType::Mp4) => Ok((
|
(_, ValidInputType::Mp4) => Ok((
|
||||||
ValidInputType::Mp4,
|
ValidInputType::Mp4,
|
||||||
Either::right(Either::left(crate::ffmpeg::to_mp4_bytes(
|
Either::right(Either::left(
|
||||||
bytes,
|
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4).await?,
|
||||||
InputFormat::Mp4,
|
)),
|
||||||
)?)),
|
|
||||||
)),
|
)),
|
||||||
(Some(Format::Jpeg) | None, ValidInputType::Jpeg) => Ok((
|
(Some(Format::Jpeg) | None, ValidInputType::Jpeg) => Ok((
|
||||||
ValidInputType::Jpeg,
|
ValidInputType::Jpeg,
|
||||||
|
|
Loading…
Reference in a new issue