Render Queue: Output To an .mp4 file keeps an HEVC or AV1 module (#179)

apply_output switched the format whenever the Output To extension mapped
to a different format, and `.mp4` maps to H.264 only, so
renderQueue.setOutput with an .mp4 path (even the item's own default
name) turned an HEVC or AV1 module into H.264 and the render wrote an
H.264 stream.

The extension now changes the format only when the current format
doesn't write it (OutputFormat::extension): HEVC, AV1 and H.264 all write
.mp4, so they stay; `.mov` on H.264 still picks ProRes and `.mp4` on
ProRes still picks H.264.

Test: output_to_keeps_a_format_that_writes_the_extension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-07 15:48:17 +07:00
co-authored by Claude Opus 5.5
parent 5d2083b64c
commit 247187872d
2 changed files with 29 additions and 1 deletions
+2 -1
View File
@@ -360,7 +360,8 @@ 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 let Some(f) = OutputFormat::from_path(o).filter(|f| *f != om.format && !o.contains("[fileExtension]")) {
// Only an extension the format doesn't write changes it: `.mp4` is H.264's, HEVC's and AV1's.
if let Some(f) = OutputFormat::from_path(o).filter(|f| f.extension() != om.format.extension() && !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`).
+27
View File
@@ -83,6 +83,33 @@ fn an_explicit_format_wins_over_the_output_extension() {
assert_eq!((om.format, om.output.as_str()), (OutputFormat::PngSequence, "/out/still.png"));
}
/// Output To only changes the format when the format can't write the new extension: HEVC and AV1
/// write `.mp4` too, so they stay (#179). Other extensions still pick a file type.
#[test]
fn output_to_keeps_a_format_that_writes_the_extension() {
let mut s = session();
let cases = [
("hevc", "/out/x.mp4", OutputFormat::Hevc, "/out/x.mp4"),
("av1", "/out/x.mp4", OutputFormat::Av1, "/out/x.mp4"),
("h264", "/out/x.mov", OutputFormat::ProRes, "/out/x.mov"),
("prores", "/out/x.mp4", OutputFormat::H264, "/out/x.mp4"),
("h264", "/out/x.mp4", OutputFormat::H264, "/out/x.mp4"),
];
for (format, out, want, path) in cases {
let a = s.execute("renderQueue.add", json!({"format": format})).unwrap();
let r = s.execute("renderQueue.setOutput", json!({"item": a["item"], "path": out})).unwrap();
let om = &s.project.render_queue.last().unwrap().output;
assert_eq!((om.format, om.output.as_str()), (want, path), "{format} → {out}");
if OutputFormat::from_name(format) == Some(want) {
assert_eq!(r["outputModuleSummary"], a["outputModuleSummary"], "{format} → {out}: the module is unchanged");
}
}
// The module's own default name (HEVC resolves to `Main.mp4`) changes nothing either.
let a = s.execute("renderQueue.add", json!({"format": "hevc"})).unwrap();
let r = s.execute("renderQueue.setOutput", json!({"item": a["item"], "path": a["outputPath"]})).unwrap();
assert_eq!((&r["output"]["format"], &r["outputModuleSummary"]), (&json!("Hevc"), &a["outputModuleSummary"]), "{r}");
}
/// A `comp` that names no composition says so, rather than "no active composition" (#155).
#[test]
fn an_unknown_comp_is_named_in_the_error() {