CLI: --format hevc / av1 write HEVC / AV1; an unknown comp is named (#154, #155)

#154: renderQueue.add (and render.addOutputModule) applied `format`, then
dropped it before applying the output path, so `--format hevc --out
x.mp4` saw the .mp4 extension and switched the module back to H.264
(and ProRes / VP9 for .mov / .webm). An explicit format now wins and a
mismatched extension follows it (x.webm → x.mp4 for AV1), as choosing a
format in the Output Module renames Output To; an extension that agrees
with the format keeps the name as given. The format error lists hevc and
av1 everywhere (one FORMATS string).

#155: a `comp` that names no composition (name or id) failed with "no
active composition". comp_id now reports it as `no composition "Nope"`
(EngineError::NoSuchComp), and comp_for_queue passes that on instead of
collapsing it.

Tests: an_explicit_format_wins_over_the_output_extension and
an_unknown_comp_is_named_in_the_error (engine). Checked with the CLI:
the files are hevc/hvc1 and av1/av01 and decode cleanly with ffmpeg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-07 12:28:12 +07:00
co-authored by Claude Opus 5.5
parent 21c0d33597
commit c4f8e50201
5 changed files with 77 additions and 28 deletions
+22 -23
View File
@@ -19,7 +19,7 @@ use crate::{EngineError, Result, Session, cmd, query};
fn can_add(s: &Session) -> std::result::Result<(), String> {
not_rendering(s)?;
if s.comp_for_queue(&Value::Null).is_some() { Ok(()) } else { Err("select or open a composition".into()) }
if s.comp_for_queue(&Value::Null).is_ok() { Ok(()) } else { Err("select or open a composition".into()) }
}
fn not_rendering(s: &Session) -> std::result::Result<(), String> {
if s.is_rendering() { Err("the render queue is rendering".into()) } else { Ok(()) }
@@ -202,6 +202,9 @@ fn apply_om_template(om: &mut OutputModule, t: &OutputModule) {
om.name = t.name.clone();
}
/// The `format` parameter's values.
const FORMATS: &str = "format: h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff";
/// Apply Output Module parameters (`template` first). `roi`: the viewer's region of interest
/// (captured by `crop.useRoi`).
fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option<[f64; 4]>, p: &Value, cmd: &str) -> Result<bool> {
@@ -213,7 +216,7 @@ fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option<
}
let before = om.clone();
if let Some(f) = str_p(p, "format") {
let f = OutputFormat::from_name(f).ok_or_else(|| bad(cmd, "format: h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff"))?;
let f = OutputFormat::from_name(f).ok_or_else(|| bad(cmd, FORMATS))?;
om.set_format(f);
}
if let Some(c) = str_p(p, "channels") {
@@ -357,13 +360,17 @@ fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option<
}
if let Some(o) = str_p(p, "output").or(str_p(p, "path")) {
om.output = o.to_string();
if p.get("format").is_none()
&& let Some(f) = OutputFormat::from_path(o).filter(|f| *f != om.format && !o.contains("[fileExtension]"))
{
// `out.mov` with an H.264 module → ProRes, like picking a file type in Output To.
let keep = om.output.clone();
om.set_format(f);
om.output = keep;
if let Some(f) = OutputFormat::from_path(o).filter(|f| *f != om.format && !o.contains("[fileExtension]")) {
if p.get("format").is_some() {
// An explicit format wins and the extension follows it, as choosing a format in
// the Output Module renames Output To (#154: `--format hevc --out x.mp4`).
om.set_format(om.format);
} else {
// `out.mov` with an H.264 module → ProRes, like picking a file type in Output To.
let keep = om.output.clone();
om.set_format(f);
om.output = keep;
}
}
}
if *om != before {
@@ -496,24 +503,20 @@ fn item_json(s: &Session, it: &RenderQueueItem, index: usize) -> Value {
}
fn add(s: &mut Session, p: &Value) -> Result<Value> {
let cid = s.comp_for_queue(p).ok_or(EngineError::NoComp)?;
let cid = s.comp_for_queue(p)?;
let mut it = RenderQueueItem::new(0, cid);
// New items start from the Movie Default templates (Edit ▸ Templates).
let templates = s.project.render_templates.clone();
it.settings = templates.default_render_settings(TemplateSlot::Movie);
it.output = templates.default_output_module(TemplateSlot::Movie);
if let Some(f) = str_p(p, "format") {
let f = OutputFormat::from_name(f).ok_or_else(|| bad("renderQueue.add", "format: h264|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff"))?;
let f = OutputFormat::from_name(f).ok_or_else(|| bad("renderQueue.add", FORMATS))?;
if f != it.output.format {
it.output = OutputModule::for_format(f);
}
}
apply_settings(&mut it.settings, &templates, p, "renderQueue.add")?;
let mut op = p.clone();
if let Some(o) = op.as_object_mut() {
o.remove("format");
}
apply_output(&mut it.output, &templates, s.state.region_of_interest, &op, "renderQueue.add")?;
apply_output(&mut it.output, &templates, s.state.region_of_interest, p, "renderQueue.add")?;
if let Some(l) = enum_p(p, "log", "renderQueue.add", RenderLog::parse, "errorsOnly|plusSettings|plusPerFrameInfo")? {
it.log = l;
}
@@ -704,7 +707,7 @@ fn add_output_module(s: &mut Session, p: &Value) -> Result<Value> {
let n = it.extra_outputs.len() + 2;
let mut om = it.output.clone();
if let Some(f) = str_p(p, "format") {
let f = OutputFormat::from_name(f).ok_or_else(|| bad("render.addOutputModule", "format: h264|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff"))?;
let f = OutputFormat::from_name(f).ok_or_else(|| bad("render.addOutputModule", FORMATS))?;
om = OutputModule::for_format(f);
}
if str_p(p, "output").is_none() && str_p(p, "path").is_none() {
@@ -714,11 +717,7 @@ fn add_output_module(s: &mut Session, p: &Value) -> Result<Value> {
None => format!("{}_{n}", om.output),
};
}
let mut op = p.clone();
if let Some(o) = op.as_object_mut() {
o.remove("format");
}
apply_output(&mut om, &s.project.render_templates, s.state.region_of_interest, &op, "render.addOutputModule")?;
apply_output(&mut om, &s.project.render_templates, s.state.region_of_interest, p, "render.addOutputModule")?;
s.edit("Add Output Module", None, |proj, _| {
let it = &mut proj.render_queue[i];
it.extra_outputs.push(om);
@@ -733,7 +732,7 @@ fn add_output_module(s: &mut Session, p: &Value) -> Result<Value> {
/// Composition ▸ Pre-render…: queue the comp with a lossless-with-alpha module whose post-render
/// action imports the result and replaces the comp's uses.
fn pre_render(s: &mut Session, p: &Value) -> Result<Value> {
let cid = s.comp_for_queue(p).ok_or(EngineError::NoComp)?;
let cid = s.comp_for_queue(p)?;
let mut it = RenderQueueItem::new(0, cid);
let templates = s.project.render_templates.clone();
it.settings = templates.default_render_settings(TemplateSlot::PreRender);