mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-25 12:01:24 +00:00
Guard against failed commands not erroring
This commit is contained in:
parent
688990d0cd
commit
943c4d5b58
5 changed files with 92 additions and 5 deletions
|
@ -111,6 +111,9 @@ pub(crate) enum FfMpegError {
|
||||||
|
|
||||||
#[error("Invalid file path")]
|
#[error("Invalid file path")]
|
||||||
Path,
|
Path,
|
||||||
|
|
||||||
|
#[error("Output from ffmpeg was empty")]
|
||||||
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FfMpegError {
|
impl FfMpegError {
|
||||||
|
@ -118,6 +121,7 @@ impl FfMpegError {
|
||||||
// Failing validation or ffmpeg bailing probably means bad input
|
// Failing validation or ffmpeg bailing probably means bad input
|
||||||
matches!(self, Self::ValidateDetails(_))
|
matches!(self, Self::ValidateDetails(_))
|
||||||
|| matches!(self, Self::Process(ProcessError::Status(_)))
|
|| matches!(self, Self::Process(ProcessError::Status(_)))
|
||||||
|
|| matches!(self, Self::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_not_found(&self) -> bool {
|
pub(crate) fn is_not_found(&self) -> bool {
|
||||||
|
@ -482,6 +486,10 @@ async fn alpha_pixel_formats() -> Result<HashSet<String>, FfMpegError> {
|
||||||
.await
|
.await
|
||||||
.map_err(FfMpegError::Read)?;
|
.map_err(FfMpegError::Read)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(FfMpegError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
let formats: PixelFormatOutput = serde_json::from_slice(&output).map_err(FfMpegError::Json)?;
|
let formats: PixelFormatOutput = serde_json::from_slice(&output).map_err(FfMpegError::Json)?;
|
||||||
|
|
||||||
Ok(parse_pixel_formats(formats))
|
Ok(parse_pixel_formats(formats))
|
||||||
|
@ -562,6 +570,11 @@ where
|
||||||
.read_to_end(&mut output)
|
.read_to_end(&mut output)
|
||||||
.await
|
.await
|
||||||
.map_err(FfMpegError::Read)?;
|
.map_err(FfMpegError::Read)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(FfMpegError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
tokio::fs::remove_file(input_file_str)
|
tokio::fs::remove_file(input_file_str)
|
||||||
.await
|
.await
|
||||||
.map_err(FfMpegError::RemoveFile)?;
|
.map_err(FfMpegError::RemoveFile)?;
|
||||||
|
@ -643,6 +656,11 @@ async fn pixel_format(input_file: &str) -> Result<String, FfMpegError> {
|
||||||
.read_to_end(&mut output)
|
.read_to_end(&mut output)
|
||||||
.await
|
.await
|
||||||
.map_err(FfMpegError::Read)?;
|
.map_err(FfMpegError::Read)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(FfMpegError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
Ok(String::from_utf8_lossy(&output).trim().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/ffmpeg/ffprobe_6_0_animated_webp_details.json
Normal file
14
src/ffmpeg/ffprobe_6_0_animated_webp_details.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"programs": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"width": 0,
|
||||||
|
"height": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": {
|
||||||
|
"format_name": "webp_pipe"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{Details, DetailsOutput, PixelFormatOutput};
|
use super::{Details, DetailsOutput, PixelFormatOutput};
|
||||||
|
|
||||||
fn details_tests() -> [(&'static str, Option<Details>); 9] {
|
fn details_tests() -> [(&'static str, Option<Details>); 10] {
|
||||||
[
|
[
|
||||||
("avif", None),
|
("avif", None),
|
||||||
(
|
(
|
||||||
|
@ -43,6 +43,7 @@ fn details_tests() -> [(&'static str, Option<Details>); 9] {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
("webp", None),
|
("webp", None),
|
||||||
|
("animated_webp", None),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,9 @@ pub(crate) enum MagickError {
|
||||||
|
|
||||||
#[error("Invalid file path")]
|
#[error("Invalid file path")]
|
||||||
Path,
|
Path,
|
||||||
|
|
||||||
|
#[error("Output from ImageMagick was empty")]
|
||||||
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MagickError {
|
impl MagickError {
|
||||||
|
@ -54,6 +57,7 @@ impl MagickError {
|
||||||
// Failing validation or imagemagick bailing probably means bad input
|
// Failing validation or imagemagick bailing probably means bad input
|
||||||
matches!(self, Self::ValidateDetails(_))
|
matches!(self, Self::ValidateDetails(_))
|
||||||
|| matches!(self, Self::Process(ProcessError::Status(_)))
|
|| matches!(self, Self::Process(ProcessError::Status(_)))
|
||||||
|
|| matches!(self, Self::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_not_found(&self) -> bool {
|
pub(crate) fn is_not_found(&self) -> bool {
|
||||||
|
@ -246,6 +250,10 @@ pub(crate) async fn details_bytes(
|
||||||
.await
|
.await
|
||||||
.map_err(MagickError::Read)?;
|
.map_err(MagickError::Read)?;
|
||||||
|
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Err(MagickError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
let details_output: Vec<DetailsOutput> =
|
let details_output: Vec<DetailsOutput> =
|
||||||
serde_json::from_slice(&bytes).map_err(MagickError::Json)?;
|
serde_json::from_slice(&bytes).map_err(MagickError::Json)?;
|
||||||
|
|
||||||
|
@ -315,6 +323,10 @@ pub(crate) async fn details_store<S: Store + 'static>(
|
||||||
.await
|
.await
|
||||||
.map_err(MagickError::Read)?;
|
.map_err(MagickError::Read)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(MagickError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
let details_output: Vec<DetailsOutput> =
|
let details_output: Vec<DetailsOutput> =
|
||||||
serde_json::from_slice(&output).map_err(MagickError::Json)?;
|
serde_json::from_slice(&output).map_err(MagickError::Json)?;
|
||||||
|
|
||||||
|
@ -333,6 +345,11 @@ pub(crate) async fn details_file(path_str: &str) -> Result<Details, MagickError>
|
||||||
.read_to_end(&mut output)
|
.read_to_end(&mut output)
|
||||||
.await
|
.await
|
||||||
.map_err(MagickError::Read)?;
|
.map_err(MagickError::Read)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(MagickError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
tokio::fs::remove_file(path_str)
|
tokio::fs::remove_file(path_str)
|
||||||
.await
|
.await
|
||||||
.map_err(MagickError::RemoveFile)?;
|
.map_err(MagickError::RemoveFile)?;
|
||||||
|
|
|
@ -15,7 +15,7 @@ use tokio::{
|
||||||
use tracing::{Instrument, Span};
|
use tracing::{Instrument, Span};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct StatusError;
|
struct StatusError(ExitStatus);
|
||||||
|
|
||||||
pub(crate) struct Process {
|
pub(crate) struct Process {
|
||||||
child: Child,
|
child: Child,
|
||||||
|
@ -38,6 +38,7 @@ pin_project_lite::pin_project! {
|
||||||
err_recv: Receiver<std::io::Error>,
|
err_recv: Receiver<std::io::Error>,
|
||||||
err_closed: bool,
|
err_closed: bool,
|
||||||
handle: DropHandle,
|
handle: DropHandle,
|
||||||
|
eof: bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ impl Process {
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
let _ = tx.send(std::io::Error::new(
|
let _ = tx.send(std::io::Error::new(
|
||||||
std::io::ErrorKind::Other,
|
std::io::ErrorKind::Other,
|
||||||
&StatusError,
|
StatusError(status),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +178,7 @@ impl Process {
|
||||||
err_recv: rx,
|
err_recv: rx,
|
||||||
err_closed: false,
|
err_closed: false,
|
||||||
handle: DropHandle { inner: handle },
|
handle: DropHandle { inner: handle },
|
||||||
|
eof: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,18 +196,53 @@ where
|
||||||
|
|
||||||
let err_recv = this.err_recv;
|
let err_recv = this.err_recv;
|
||||||
let err_closed = this.err_closed;
|
let err_closed = this.err_closed;
|
||||||
|
let eof = this.eof;
|
||||||
let inner = this.inner;
|
let inner = this.inner;
|
||||||
|
|
||||||
if !*err_closed {
|
if !*err_closed {
|
||||||
if let Poll::Ready(res) = Pin::new(err_recv).poll(cx) {
|
if let Poll::Ready(res) = Pin::new(err_recv).poll(cx) {
|
||||||
*err_closed = true;
|
*err_closed = true;
|
||||||
|
|
||||||
if let Ok(err) = res {
|
if let Ok(err) = res {
|
||||||
return Poll::Ready(Err(err));
|
return Poll::Ready(Err(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *eof {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner.poll_read(cx, buf)
|
if !*eof {
|
||||||
|
let before_size = buf.filled().len();
|
||||||
|
|
||||||
|
return match inner.poll_read(cx, buf) {
|
||||||
|
Poll::Ready(Ok(())) => {
|
||||||
|
if buf.filled().len() == before_size {
|
||||||
|
*eof = true;
|
||||||
|
|
||||||
|
if !*err_closed {
|
||||||
|
// reached end of stream & haven't received process signal
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
*eof = true;
|
||||||
|
|
||||||
|
Poll::Ready(Err(e))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if *err_closed && *eof {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +254,7 @@ impl Drop for DropHandle {
|
||||||
|
|
||||||
impl std::fmt::Display for StatusError {
|
impl std::fmt::Display for StatusError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
write!(f, "Command failed with bad status")
|
write!(f, "Command failed with bad status: {}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue