Render Queue: output files end in their format's extension once (#293)

Renders on macOS came out as `Comp 1.mov.ecproj`, and an image
sequence's every frame as `Comp 1_00001.png.ecproj`; another output
was `Comp 2.mov.mov`.

Cause: the save dialogs for anything but a project went through the
project dialog hook, which filters to `.ecproj`; macOS's save panel
appends the allowed extension to any other name. 0.4.0's Render Queue
Choose File… used it (#192 moved Output To to its own dialog since),
and Save Frame As, Save Current Preview, Export Lottie, Save a Copy As
XML, Collect Files, Watch Folder and the Premiere export form's Browse
still did. Nothing then fixed the name: Output To kept `.ecproj` (no
format claims it), and changing the format replaced only the last
extension (`Comp 2.mov.ecproj` → `Comp 2.mov.mov`). `[compName]` of a
comp named `Comp 2.mov` also doubled it.

Fix: `Hooks::save_dialog` asks the dialog for the default name's own
extension (`pick_save_file`), the project dialog only for projects or
on hosts without another (the web); the desktop dialog has no filter
for a name without an extension (folders). `OutputFormat::with_extension`
makes an output path end in the format's extension exactly once
(project extensions and repeats of the format's dropped, a missing one
added; `logo.png.mov` kept), used by Output To, format changes and
`resolve_output`, so existing projects and templates render to the
right name too. Output To picks the file type from the extension
before a stray `.ecproj`.

Test: output_names_end_in_the_format_extension_once (project),
output_to_ends_in_the_format_extension_once (engine) and
save_dialogs_offer_the_file_extension (ui); the engine and ui tests
fail with the fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-09 16:51:12 +07:00
co-authored by Claude Opus 5.5
parent a62a298f0c
commit be926adb92
9 changed files with 191 additions and 30 deletions
+4 -2
View File
@@ -321,7 +321,8 @@ impl Session {
.unwrap_or_else(|| "Untitled Project".into())
}
/// The output path of a queue item: template tokens expanded, relative paths resolved against
/// The output path of a queue item: template tokens expanded, ending in the format's
/// extension exactly once ([`OutputFormat::with_extension`]), relative paths resolved against
/// the project's folder (or the working directory). `None` when the comp is gone or the
/// output is empty ("Needs Output").
pub fn resolve_output(&self, item: &RenderQueueItem) -> Option<String> {
@@ -338,7 +339,8 @@ impl Session {
let pname = self.project_name();
let vars =
TemplateVars { comp_name: &name, project_name: &pname, width: w, height: h, frame_rate: rate.as_f64(), start_frame: f0, end_frame: f0 + n - 1 };
let p = expand_template(tpl, item.output.format, &vars);
// The format's extension exactly once, whatever the name or a save dialog added (#293).
let p = item.output.format.with_extension(&expand_template(tpl, item.output.format, &vars));
let path = std::path::Path::new(&p);
if path.is_absolute() {
return Some(p);