mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-12 00:42:36 +00:00
Import: a model's left-out parts reach the user as an import warning (Model::warnings, Importer::probe_noting)
This commit is contained in:
@@ -319,15 +319,19 @@ impl Prober {
|
||||
return f.map(|footage| Probed { name, footage, ask_alpha: false, warning: None }).map_err(fail);
|
||||
}
|
||||
let importer = self.importer.as_ref().ok_or_else(|| fail("media import is not available in this build".into()))?;
|
||||
let mut f = crate::sequence::probe(importer.as_ref(), src, self.sequence_rate).map_err(fail)?;
|
||||
let mut warning = None;
|
||||
// What the file's own import left out (a model's textures over the limits) is a warning too.
|
||||
let (mut f, mut warning) = match src {
|
||||
Source::File(_) => importer.probe_noting(path).map_err(fail)?,
|
||||
_ => (crate::sequence::probe(importer.as_ref(), src, self.sequence_rate).map_err(fail)?, None),
|
||||
};
|
||||
// Settings ▸ Import ▸ Report Missing Frames (numbered sequences).
|
||||
if f.kind == FootageKind::Sequence
|
||||
&& !f.alphabetical
|
||||
&& self.report_missing_frames
|
||||
&& let Some(gaps) = crate::sequence::missing_report(&f.sequence)
|
||||
{
|
||||
warning = Some(format!("{name}: {gaps} (the previous frame is held)"));
|
||||
let gaps = format!("{name}: {gaps} (the previous frame is held)");
|
||||
warning = Some(warning.map_or(gaps.clone(), |w| format!("{w}; {gaps}")));
|
||||
}
|
||||
// Settings ▸ Import ▸ Interpret Unlabeled Alpha As: images only (a 3D model renders its
|
||||
// own alpha, and audio and data have none).
|
||||
|
||||
@@ -106,6 +106,11 @@ pub trait Importer: Send + Sync {
|
||||
/// Probe one file and return footage metadata. Image sequences are the engine's
|
||||
/// ([`sequence`]): it groups numbered stills and probes their first file here.
|
||||
fn probe(&self, path: &str) -> std::result::Result<effectcraft_project::Footage, String>;
|
||||
/// [`probe`](Self::probe), and what the import left out of the file (a 3D model's textures
|
||||
/// over the limits), which the import shows the user.
|
||||
fn probe_noting(&self, path: &str) -> std::result::Result<(effectcraft_project::Footage, Option<String>), String> {
|
||||
self.probe(path).map(|f| (f, None))
|
||||
}
|
||||
/// A footage file's bytes are now at `path` (extracted from a template): hosts whose media
|
||||
/// layer reads from memory (the browser) register them; file-based ones need nothing.
|
||||
fn register(&self, _path: &str, _data: &[u8]) {}
|
||||
|
||||
@@ -20,6 +20,9 @@ impl Importer for MediaImporter {
|
||||
fn probe(&self, path: &str) -> Result<Footage, String> {
|
||||
effectcraft_media::probe(path).map_err(|e| e.to_string())
|
||||
}
|
||||
fn probe_noting(&self, path: &str) -> Result<(Footage, Option<String>), String> {
|
||||
effectcraft_media::probe_noting(path).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Render Queue export via `effectcraft-export`: to the file system, or to `sink` (the web app
|
||||
|
||||
@@ -33,7 +33,7 @@ mod webp_anim;
|
||||
/// Video decoder counters (time in decoders, samples decoded, seeks, cache hits), for benchmarks.
|
||||
pub use filmcraft_codecs::{GopStats, gop_stats};
|
||||
pub use pool::{DEFAULT_BUDGET, MediaPool, PoolStats};
|
||||
pub use probe::{DEFAULT_SEQUENCE_RATE, probe, probe_bytes, probe_model};
|
||||
pub use probe::{DEFAULT_SEQUENCE_RATE, probe, probe_bytes, probe_model, probe_noting};
|
||||
|
||||
/// Errors from probing or decoding footage.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@@ -43,11 +43,25 @@ fn side_file_reader(model: &Path) -> impl Fn(&str) -> Option<Vec<u8>> + use<> {
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// What the last model probed on this thread left out (see [`probe_noting`]).
|
||||
static MODEL_WARNING: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// [`probe`], and what the import left out of the file (a 3D model's textures over the limits),
|
||||
/// for the user.
|
||||
pub fn probe_noting(path: impl AsRef<Path>) -> Result<(Footage, Option<String>)> {
|
||||
MODEL_WARNING.with(|w| w.borrow_mut().take());
|
||||
let f = probe(path)?;
|
||||
Ok((f, MODEL_WARNING.with(|w| w.borrow_mut().take())))
|
||||
}
|
||||
|
||||
/// Probe a 3D model (glTF 2.0 / OBJ / STL / FBX): a [`FootageKind::Model`] item whose duration is its
|
||||
/// longest animation. `resolve` reads sibling resources.
|
||||
pub fn probe_model(path: &str, bytes: &[u8], resolve: &dyn Fn(&str) -> Option<Vec<u8>>) -> Result<Footage> {
|
||||
let m = effectcraft_model::load(path, bytes, resolve).map_err(|e| MediaError::Decode(format!("{path}: {e}")))?;
|
||||
let s = m.summary();
|
||||
MODEL_WARNING.with(|w| *w.borrow_mut() = (!m.warnings.is_empty()).then(|| format!("{path}: {}", m.warnings.join("; "))));
|
||||
let dur = s.animations.iter().map(|a| a.1).fold(0.0f64, f64::max);
|
||||
let codec = match effectcraft_model::format_of(path) {
|
||||
Some("obj") => "OBJ",
|
||||
|
||||
@@ -536,7 +536,7 @@ pub(crate) fn parse_with_budget(bytes: &[u8], resolve: &dyn Fn(&str) -> Option<V
|
||||
.and_then(|e| e.get("KHR_lights_punctual"))
|
||||
.map(|e| arr(e, "lights").iter().enumerate().map(|(i, l)| light(l, i)).collect())
|
||||
.unwrap_or_default();
|
||||
Ok(Model { meshes, nodes, roots, materials, textures, skins, animations, cameras, lights })
|
||||
Ok(Model { meshes, nodes, roots, materials, textures, skins, animations, cameras, lights, warnings: Vec::new() })
|
||||
}
|
||||
|
||||
fn name_or(v: &Value, d: String) -> String {
|
||||
|
||||
@@ -512,6 +512,8 @@ pub struct Model {
|
||||
pub cameras: Vec<ModelCamera>,
|
||||
/// Punctual lights embedded in the file (glTF `KHR_lights_punctual`).
|
||||
pub lights: Vec<ModelLight>,
|
||||
/// Parts of the file the import left out (textures over the limits), for the user.
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
/// A camera embedded in a model file.
|
||||
|
||||
Reference in New Issue
Block a user