mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-03 15:08:31 -06:00
Project: layer name suffixes in one pass; template names bounded and never a parent folder
unique_layer_name tried each numeric suffix against every layer, so importing many layers of one name was cubic. The suffixes in use are now gathered once. sanitize kept '.' and '..' (a comp name used as a path component in an output template could point at the parent folder) and copied a name of any length into every template token; dot-only names become underscores and names are cut to 255 characters.
This commit is contained in:
@@ -271,7 +271,15 @@ impl Comp {
|
||||
return base.to_string();
|
||||
}
|
||||
let stem = base.trim_end_matches(|c: char| c.is_ascii_digit()).trim_end();
|
||||
(2..).map(|i| format!("{stem} {i}")).find(|n| !self.layers.iter().any(|l| &l.name == n)).unwrap_or_default()
|
||||
// The suffixes in use, gathered in one pass: trying each candidate against every layer
|
||||
// made importing many layers of one name cubic.
|
||||
let taken: std::collections::HashSet<u64> = self
|
||||
.layers
|
||||
.iter()
|
||||
.filter_map(|l| l.name.strip_prefix(stem)?.strip_prefix(' '))
|
||||
.filter_map(|s| s.parse::<u64>().ok().filter(|n| n.to_string() == s))
|
||||
.collect();
|
||||
(2..).find(|i| !taken.contains(i)).map(|i| format!("{stem} {i}")).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1529,7 +1529,11 @@ pub fn numbered_template(template: &str, n: u32) -> String {
|
||||
|
||||
/// Make a name safe as a file name component.
|
||||
pub fn sanitize(name: &str) -> String {
|
||||
name.chars().map(|c| if matches!(c, '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|') { '_' } else { c }).collect()
|
||||
// At most a file name's worth (255 characters): a template repeating a long name token
|
||||
// must not expand to gigabytes.
|
||||
let s: String = name.chars().take(255).map(|c| if matches!(c, '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|') { '_' } else { c }).collect();
|
||||
// `.` and `..` would name the folder itself or its parent when the token is a path component.
|
||||
if !s.is_empty() && s.chars().all(|c| c == '.') { "_".repeat(s.len()) } else { s }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1540,6 +1544,14 @@ mod tests {
|
||||
fn templates_and_sequences() {
|
||||
let v = TemplateVars { comp_name: "Main/Title", project_name: "P", width: 1920, height: 1080, frame_rate: 29.97, start_frame: 0, end_frame: 9 };
|
||||
assert_eq!(expand_template(DEFAULT_TEMPLATE, OutputFormat::H264, &v), "Main_Title.mp4");
|
||||
// A dot-only name is not a parent-folder component; a long one is cut to a file name.
|
||||
for name in ["..", "."] {
|
||||
let v = TemplateVars { comp_name: name, ..v };
|
||||
assert_eq!(expand_template("out/[compName]/x.[fileExtension]", OutputFormat::Gif, &v), format!("out/{}/x.gif", "_".repeat(name.len())));
|
||||
}
|
||||
let long = "n".repeat(100_000);
|
||||
let v2 = TemplateVars { comp_name: &long, ..v };
|
||||
assert_eq!(expand_template(&"[compName]".repeat(100), OutputFormat::Gif, &v2).len(), 255 * 100);
|
||||
assert_eq!(expand_template("[compName]_[width]x[height]@[frameRate].[fileExtension]", OutputFormat::Gif, &v), "Main_Title_1920x1080@29.97.gif");
|
||||
assert_eq!(sequence_path("out/a_[#####].png", 42), "out/a_00042.png");
|
||||
assert_eq!(sequence_path("a###.tif", 7), "a007.tif");
|
||||
|
||||
@@ -119,6 +119,14 @@ fn unique_names_and_cycles() {
|
||||
let c = p.comp(cid).unwrap();
|
||||
assert_eq!(c.unique_layer_name("Red Solid 1"), "Red Solid 2");
|
||||
assert_eq!(c.unique_layer_name("Shape Layer 1"), "Shape Layer 1");
|
||||
// A suffix written another way (`02`) is not the number taken.
|
||||
let mut d = c.clone();
|
||||
for n in ["Red Solid 02", "Red Solid 3"] {
|
||||
let mut l = d.layers[0].clone();
|
||||
l.name = n.into();
|
||||
d.layers.push(l);
|
||||
}
|
||||
assert_eq!(d.unique_layer_name("Red Solid 1"), "Red Solid 2");
|
||||
assert!(p.comp_contains(cid, cid));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user