#886: say why footage the decoders can't read shows no picture

Problem: a movie whose profile the software decoders lack (H.264 High 4:2:2 / 10-bit)
probes fine, then every frame read fails; MediaPool logged the error per frame and returned
None, so the layer rendered black with no sign why in the viewer, Project panel or for agents.

Fix: MediaPool records the reason once per file (codec from the probe plus the decoder's text
without error wrappers; I/O errors stay with missing-footage handling), logs it once, and
exposes it through the new FootageSource::problem. The Composition viewer shows it on the
affected layer, the Project panel marks the item with a warning whose tooltip is the reason,
and project.summary / footage.info carry a "problem" field. Cleared when the file is
forgotten; only set when decoding really failed, so a hardware decoder that handles the file
shows nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76
2026-10-11 18:49:54 +02:00
co-authored by Claude Sonnet 5.5
parent 67434e9038
commit 74b95b0a9d
9 changed files with 232 additions and 5 deletions
+5 -1
View File
@@ -206,8 +206,12 @@ fn info(s: &mut Session, _: &Value) -> Result<Value> {
let item = s.project.item(v.item);
let name = item.map(|i| i.name.clone()).unwrap_or_default();
let frame = fr.frame_at(v.time);
let problem = match item.map(|i| &i.kind) {
Some(ItemKind::Footage(f)) => s.footage.problem(f),
_ => None,
};
Ok(
json!({"item": v.item.0, "name": name, "time": v.time.seconds(), "frame": frame, "timecode": timecode(item, fr, frame), "in": v.in_point.map(|t| t.seconds()), "out": v.out_point.map(|t| t.seconds()), "duration": dur.map(|d| d.seconds()), "frameRate": fr.as_f64()}),
json!({"item": v.item.0, "name": name, "time": v.time.seconds(), "frame": frame, "timecode": timecode(item, fr, frame), "in": v.in_point.map(|t| t.seconds()), "out": v.out_point.map(|t| t.seconds()), "duration": dur.map(|d| d.seconds()), "frameRate": fr.as_f64(), "problem": problem}),
)
}
+7
View File
@@ -46,6 +46,13 @@ fn summary(s: &mut Session, _: &Value) -> Result<Value> {
o["layers"] = json!(c.layers.len());
o["frameRate"] = json!(c.frame_rate.as_f64());
}
// Footage that shows no picture although its file is there (a codec or profile the
// decoders lack): why, so an agent can tell the user.
if let ItemKind::Footage(f) = &i.kind
&& let Some(why) = s.footage.problem(f)
{
o["problem"] = json!(why);
}
o
})
.collect();
+33
View File
@@ -629,3 +629,36 @@ fn media_browser_over_a_virtual_tree() {
assert_eq!(*b.actions.lock().unwrap(), ["openFolder"]);
assert!(s.execute("mediaBrowser.action", json!({})).is_err());
}
/// Footage the source can't decode is reported with its reason by `project.summary` and
/// `footage.info`, so an agent can tell the user why the picture is black.
#[test]
fn footage_that_cannot_be_decoded_reports_why() {
struct Broken;
impl FootageSource for Broken {
fn frame(&self, _: ItemId, _: &Footage, _: Tick) -> Option<Arc<Image>> {
None
}
fn problem(&self, f: &Footage) -> Option<String> {
Some(format!("{} isn't supported by the built-in decoder", f.codec))
}
}
let mut s = Session { footage: Arc::new(Broken), ..Default::default() };
s.execute("comp.new", json!({"name": "C", "width": 64, "height": 36, "duration": 2})).unwrap();
let f = Footage {
kind: FootageKind::Video,
path: "a.mp4".into(),
codec: "H.264 (High 4:2:2)".into(),
width: 64,
height: 36,
has_video: true,
..Default::default()
};
let item = std::sync::Arc::make_mut(&mut s.project).add_item("Clip", Default::default(), None, ItemKind::Footage(f));
let sum = s.execute("project.summary", json!({})).unwrap();
let it = sum["items"].as_array().unwrap().iter().find(|i| i["id"] == json!(item.0)).unwrap();
assert_eq!(it["problem"], json!("H.264 (High 4:2:2) isn't supported by the built-in decoder"));
assert!(sum["items"].as_array().unwrap().iter().filter(|i| i["id"] != json!(item.0)).all(|i| i.get("problem").is_none()), "only footage reports one");
s.execute("footage.open", json!({"item": item.0})).unwrap();
assert_eq!(s.execute("footage.info", json!({})).unwrap()["problem"], it["problem"]);
}