Keep better track of gif/mp4 thumbnail

This commit is contained in:
Aode (Lion) 2021-10-20 21:36:18 -05:00
parent 1767942e31
commit f67aeb92fa
4 changed files with 132 additions and 118 deletions

View file

@ -6,12 +6,6 @@ pub(crate) struct Error {
kind: UploadError, kind: UploadError,
} }
impl Error {
pub(crate) fn kind(&self) -> &UploadError {
&self.kind
}
}
impl std::fmt::Debug for Error { impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}\n", self.kind) write!(f, "{}\n", self.kind)
@ -75,6 +69,9 @@ pub(crate) enum UploadError {
#[error(transparent)] #[error(transparent)]
StripPrefix(#[from] std::path::StripPrefixError), StripPrefix(#[from] std::path::StripPrefixError),
#[error("Provided process path is invalid")]
ParsePath,
#[error("Failed to acquire the semaphore")] #[error("Failed to acquire the semaphore")]
Semaphore, Semaphore,

View file

@ -388,7 +388,7 @@ async fn prepare_process(
let processed_name = format!("{}.{}", name, ext); let processed_name = format!("{}.{}", name, ext);
let (thumbnail_path, thumbnail_args) = let (thumbnail_path, thumbnail_args) =
self::processor::build_chain(&operations, processed_name); self::processor::build_chain(&operations, processed_name)?;
Ok((format, name, thumbnail_path, thumbnail_args)) Ok((format, name, thumbnail_path, thumbnail_args))
} }
@ -465,40 +465,11 @@ async fn process(
return ranged_file_resp(real_path, range, details).await; return ranged_file_resp(real_path, range, details).await;
} }
let mut original_path = manager.path_from_filename(name.clone()).await?; let original_path = manager.still_path_from_filename(name.clone()).await?;
let thumbnail_path2 = thumbnail_path.clone(); let thumbnail_path2 = thumbnail_path.clone();
let process_fut = async { let process_fut = async {
let thumbnail_path = thumbnail_path2; let thumbnail_path = thumbnail_path2;
// Create and save a JPG for motion images (gif, mp4)
if let Some((updated_path, exists)) =
self::processor::prepare_image(original_path.clone()).await?
{
original_path = updated_path.clone();
if exists.is_new() {
// Save the transcoded file in another task
debug!("Spawning storage task");
let manager2 = manager.clone();
let name = name.clone();
let span = tracing::info_span!(
parent: None,
"Storing variant info",
path = &tracing::field::debug(&updated_path),
name = &tracing::field::display(&name),
);
span.follows_from(Span::current());
actix_rt::spawn(
async move {
if let Err(e) = manager2.store_variant(None, &updated_path, &name).await {
error!("Error storing variant, {}", e);
return;
}
}
.instrument(span),
);
}
}
let permit = PROCESS_SEMAPHORE.acquire().await?; let permit = PROCESS_SEMAPHORE.acquire().await?;

View file

@ -1,13 +1,6 @@
use crate::{ use crate::error::{Error, UploadError};
error::{Error, UploadError}, use std::path::PathBuf;
ffmpeg::ThumbnailFormat, use tracing::instrument;
};
use std::path::{Path, PathBuf};
use tracing::{debug, error, instrument};
fn ptos(path: &Path) -> Result<String, Error> {
Ok(path.to_str().ok_or(UploadError::Path)?.to_owned())
}
pub(crate) trait Processor { pub(crate) trait Processor {
const NAME: &'static str; const NAME: &'static str;
@ -164,88 +157,41 @@ impl Processor for Blur {
} }
#[instrument] #[instrument]
pub(crate) fn build_chain(args: &[(String, String)], filename: String) -> (PathBuf, Vec<String>) { pub(crate) fn build_chain(
fn parse<P: Processor>(key: &str, value: &str) -> Option<P> { args: &[(String, String)],
filename: String,
) -> Result<(PathBuf, Vec<String>), Error> {
fn parse<P: Processor>(key: &str, value: &str) -> Result<Option<P>, UploadError> {
if key == P::NAME { if key == P::NAME {
return P::parse(key, value); return Ok(Some(P::parse(key, value).ok_or(UploadError::ParsePath)?));
} }
None Ok(None)
} }
macro_rules! parse { macro_rules! parse {
($inner:expr, $x:ident, $k:expr, $v:expr) => {{ ($inner:expr, $x:ident, $k:expr, $v:expr) => {{
if let Some(processor) = parse::<$x>($k, $v) { if let Some(processor) = parse::<$x>($k, $v)? {
return (processor.path($inner.0), processor.command($inner.1)); return Ok((processor.path($inner.0), processor.command($inner.1)));
}; };
}}; }};
} }
let (path, args) = let (path, args) =
args.into_iter() args.into_iter()
.fold((PathBuf::default(), vec![]), |inner, (name, value)| { .fold(Ok((PathBuf::default(), vec![])), |inner, (name, value)| {
parse!(inner, Identity, name, value); if let Ok(inner) = inner {
parse!(inner, Thumbnail, name, value); parse!(inner, Identity, name, value);
parse!(inner, Resize, name, value); parse!(inner, Thumbnail, name, value);
parse!(inner, Crop, name, value); parse!(inner, Resize, name, value);
parse!(inner, Blur, name, value); parse!(inner, Crop, name, value);
parse!(inner, Blur, name, value);
debug!("Skipping {}: {}, invalid", name, value); Err(Error::from(UploadError::ParsePath))
} else {
inner
}
})?;
inner Ok((path.join(filename), args))
});
(path.join(filename), args)
}
fn is_motion(s: &str) -> bool {
s.ends_with(".gif") || s.ends_with(".mp4")
}
pub(crate) enum Exists {
Exists,
New,
}
impl Exists {
pub(crate) fn is_new(&self) -> bool {
matches!(self, Exists::New)
}
}
pub(crate) async fn prepare_image(
original_file: PathBuf,
) -> Result<Option<(PathBuf, Exists)>, Error> {
let original_path_str = ptos(&original_file)?;
let jpg_path = format!("{}.jpg", original_path_str);
let jpg_path = PathBuf::from(jpg_path);
if tokio::fs::metadata(&jpg_path).await.is_ok() {
return Ok(Some((jpg_path, Exists::Exists)));
}
if is_motion(&original_path_str) {
let orig_path = original_path_str.clone();
let tmpfile = crate::tmp_file();
crate::safe_create_parent(&tmpfile).await?;
let res = crate::ffmpeg::thumbnail(orig_path, &tmpfile, ThumbnailFormat::Jpeg).await;
if let Err(e) = res {
error!("transcode error: {:?}", e);
tokio::fs::remove_file(&tmpfile).await?;
return Err(e);
}
return match crate::safe_move_file(tmpfile, jpg_path.clone()).await {
Err(e) if matches!(e.kind(), UploadError::FileExists) => {
Ok(Some((jpg_path, Exists::Exists)))
}
Err(e) => Err(e),
_ => Ok(Some((jpg_path, Exists::New))),
};
}
Ok(None)
} }

View file

@ -1,11 +1,16 @@
use crate::{ use crate::{
config::Format, config::Format,
error::{Error, UploadError}, error::{Error, UploadError},
ffmpeg::ThumbnailFormat,
migrate::{alias_id_key, alias_key, alias_key_bounds, LatestDb}, migrate::{alias_id_key, alias_key, alias_key_bounds, LatestDb},
}; };
use actix_web::web; use actix_web::web;
use sha2::Digest; use sha2::Digest;
use std::{path::PathBuf, sync::Arc}; use std::{
ops::{Deref, DerefMut},
path::PathBuf,
sync::Arc,
};
use storage_path_generator::{Generator, Path}; use storage_path_generator::{Generator, Path};
use tracing::{debug, error, info, instrument, warn, Span}; use tracing::{debug, error, info, instrument, warn, Span};
use tracing_futures::Instrument; use tracing_futures::Instrument;
@ -34,6 +39,7 @@ pub(super) use session::UploadManagerSession;
// - Path Tree // - Path Tree
// - filename -> relative path // - filename -> relative path
// - filename / relative variant path -> relative variant path // - filename / relative variant path -> relative variant path
// - filename / motion -> relative motion path
// - Settings Tree // - Settings Tree
// - last-path -> last generated path // - last-path -> last generated path
// - fs-restructure-01-complete -> bool // - fs-restructure-01-complete -> bool
@ -111,6 +117,76 @@ impl UploadManager {
Ok(manager) Ok(manager)
} }
pub(crate) async fn still_path_from_filename(
&self,
filename: String,
) -> Result<PathBuf, Error> {
let path = self.path_from_filename(filename.clone()).await?;
let details =
if let Some(details) = self.variant_details(path.clone(), filename.clone()).await? {
details
} else {
Details::from_path(&path).await?
};
if !details.is_motion() {
return Ok(path);
}
if let Some(motion_path) = self.motion_path(&filename).await? {
return Ok(motion_path);
}
let jpeg_path = self.next_directory()?.join(&filename);
crate::safe_create_parent(&jpeg_path).await?;
let permit = crate::PROCESS_SEMAPHORE.acquire().await;
let res = crate::ffmpeg::thumbnail(&path, &jpeg_path, ThumbnailFormat::Jpeg).await;
drop(permit);
if let Err(e) = res {
error!("transcode error: {:?}", e);
self.remove_path(&jpeg_path).await?;
return Err(e);
}
self.store_motion_path(&filename, &jpeg_path).await?;
Ok(jpeg_path)
}
async fn motion_path(&self, filename: &str) -> Result<Option<PathBuf>, Error> {
let path_tree = self.inner.path_tree.clone();
let motion_key = format!("{}/motion", filename);
let opt = web::block(move || path_tree.get(motion_key.as_bytes())).await??;
if let Some(ivec) = opt {
return Ok(Some(
self.inner.root_dir.join(String::from_utf8(ivec.to_vec())?),
));
}
Ok(None)
}
async fn store_motion_path(
&self,
filename: &str,
path: impl AsRef<std::path::Path>,
) -> Result<(), Error> {
let path_bytes = self
.generalize_path(path.as_ref())?
.to_str()
.ok_or(UploadError::Path)?
.as_bytes()
.to_vec();
let motion_key = format!("{}/motion", filename);
let path_tree = self.inner.path_tree.clone();
web::block(move || path_tree.insert(motion_key.as_bytes(), path_bytes)).await??;
Ok(())
}
#[instrument(skip(self))] #[instrument(skip(self))]
pub(crate) async fn path_from_filename(&self, filename: String) -> Result<PathBuf, Error> { pub(crate) async fn path_from_filename(&self, filename: String) -> Result<PathBuf, Error> {
let path_tree = self.inner.path_tree.clone(); let path_tree = self.inner.path_tree.clone();
@ -125,7 +201,12 @@ impl UploadManager {
#[instrument(skip(self))] #[instrument(skip(self))]
async fn store_path(&self, filename: String, path: &std::path::Path) -> Result<(), Error> { async fn store_path(&self, filename: String, path: &std::path::Path) -> Result<(), Error> {
let path_bytes = path.to_str().ok_or(UploadError::Path)?.as_bytes().to_vec(); let path_bytes = self
.generalize_path(path)?
.to_str()
.ok_or(UploadError::Path)?
.as_bytes()
.to_vec();
let path_tree = self.inner.path_tree.clone(); let path_tree = self.inner.path_tree.clone();
web::block(move || path_tree.insert(filename.as_bytes(), path_bytes)).await??; web::block(move || path_tree.insert(filename.as_bytes(), path_bytes)).await??;
Ok(()) Ok(())
@ -536,6 +617,11 @@ impl<T> Serde<T> {
} }
impl Details { impl Details {
fn is_motion(&self) -> bool {
self.content_type.type_() == "video"
|| self.content_type.type_() == "image" && self.content_type.subtype() == "gif"
}
#[tracing::instrument("Details from bytes", skip(input))] #[tracing::instrument("Details from bytes", skip(input))]
pub(crate) async fn from_bytes(input: web::Bytes) -> Result<Self, Error> { pub(crate) async fn from_bytes(input: web::Bytes) -> Result<Self, Error> {
let details = crate::magick::details_bytes(input).await?; let details = crate::magick::details_bytes(input).await?;
@ -606,6 +692,20 @@ fn delete_key(alias: &str) -> String {
format!("{}/delete", alias) format!("{}/delete", alias)
} }
impl<T> Deref for Serde<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for Serde<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl std::fmt::Debug for UploadManager { impl std::fmt::Debug for UploadManager {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("UploadManager").finish() f.debug_struct("UploadManager").finish()