Merge pull request #627 from MarekSpodBiedry/issue-446

This commit is contained in:
Claude 2026-10-10 18:51:15 +00:00
commit 8a65313226
No known key found for this signature in database
2 changed files with 23 additions and 1 deletions

View File

@ -157,6 +157,14 @@ struct Work {
nested_switches: bool,
}
/// Where a relative render output goes when there is no default folder and the project is unsaved:
/// `~/Movies` when it exists, else the home folder itself.
pub(crate) fn default_output_base(home: Option<std::path::PathBuf>) -> Option<std::path::PathBuf> {
let home = home.filter(|h| !h.as_os_str().is_empty())?;
let movies = home.join("Movies");
Some(if movies.is_dir() { movies } else { home })
}
/// Bits per channel an output format writes for a project bit depth (named in the file when
/// Settings ▸ Export ▸ Append Bit Depth to File Name is on).
pub fn output_bits(format: OutputFormat, depth: effectcraft_project::BitDepth) -> u32 {
@ -324,7 +332,7 @@ impl Session {
/// 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
/// the project's folder (an unsaved project's against ~/Movies or home). `None` when the comp is gone or the
/// output is empty ("Needs Output").
pub fn resolve_output(&self, item: &RenderQueueItem) -> Option<String> {
let comp = self.project.comp(item.comp)?;
@ -351,6 +359,10 @@ impl Session {
let base = (!folder.is_empty()).then(|| std::path::PathBuf::from(folder));
let base =
base.or_else(|| self.path.as_deref().and_then(|s| std::path::Path::new(s).parent().map(|d| d.to_path_buf())).filter(|d| !d.as_os_str().is_empty()));
// Unsaved project: ~/Movies, else the home folder (as FilmCraft's exports do). The working directory
// is `/` for an app started from the macOS Finder, where nothing can be written (#446).
let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")).map(std::path::PathBuf::from);
let base = base.or_else(|| default_output_base(home));
// (the web has no working directory: relative outputs land in its virtual root)
let base = base.or_else(|| std::env::current_dir().ok()).or_else(|| cfg!(target_arch = "wasm32").then(|| "/".into()))?;
Some(base.join(path).to_string_lossy().to_string())

View File

@ -682,3 +682,13 @@ fn dynamic_menus_and_window_viewer_labels() {
assert!(labels.iter().any(|x| x == l), "{l}");
}
}
#[test]
fn unsaved_project_renders_to_movies_or_home_not_the_working_directory() {
// A Finder-launched app has `/` as its working directory, where renders fail (#446).
let home = tmp("home446");
assert_eq!(crate::render_queue::default_output_base(Some(home.clone())), Some(home.clone()));
std::fs::create_dir_all(home.join("Movies")).unwrap();
assert_eq!(crate::render_queue::default_output_base(Some(home.clone())), Some(home.join("Movies")));
assert_eq!(crate::render_queue::default_output_base(None), None);
}