mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-29 04:26:55 -06:00
Merge #2185: Project Settings: Frame Count, Start at 0 or Start at 1 (@marcoarnulfo)
This commit is contained in:
@@ -665,6 +665,15 @@ fn project_settings(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
proj.settings.time_display = effectcraft_project::TimeDisplayStyle::parse(t)
|
||||
.ok_or_else(|| bad("file.projectSettings", format!("timeDisplay: timecode|frames|feet35|feet16, not `{t}`")))?;
|
||||
}
|
||||
if let Some(f) = p.get("frameStart") {
|
||||
// Frame Count: After Effects offers Start at 0 and Start at 1 only. Scripts and dialog
|
||||
// number fields may send `1.0`.
|
||||
proj.settings.frame_start = match f.as_f64() {
|
||||
Some(0.0) => 0,
|
||||
Some(1.0) => 1,
|
||||
_ => return Err(bad(cmd, format!("frameStart: 0|1, not `{f}`"))),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(serde_json::to_value(&s.project.settings).unwrap_or_default())
|
||||
@@ -745,7 +754,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Project Settings...",
|
||||
["File"],
|
||||
Some("Cmd+Alt+Shift+K"),
|
||||
"{bitDepth?: 8|16|32, colorEngine?: adobe|ocio, workingSpace?: none|srgb|rec709|rec2020|p3|acescg|aces2065, workingGamma?: 2.2|2.4 (with no working space), linearize?, blendLinear?, hdr?: clip|compand|toneMap, outputSpace?: srgb|rec709|rec2020|p3|rec2100pq|rec2100hlg, renderer?: gpu|software, timeDisplay?: timecode|frames|feet35|feet16}",
|
||||
"{bitDepth?: 8|16|32, colorEngine?: adobe|ocio, workingSpace?: none|srgb|rec709|rec2020|p3|acescg|aces2065, workingGamma?: 2.2|2.4 (with no working space), linearize?, blendLinear?, hdr?: clip|compand|toneMap, outputSpace?: srgb|rec709|rec2020|p3|rec2100pq|rec2100hlg, renderer?: gpu|software, timeDisplay?: timecode|frames|feet35|feet16, frameStart?: 0|1 (Frame Count: Start at 0 / Start at 1)}",
|
||||
always,
|
||||
project_settings
|
||||
),
|
||||
|
||||
@@ -28,6 +28,20 @@ pub fn display_time(s: &Session, comp: &Comp, t: Tick) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// A length (a layer's Duration) in the project's Time Display Style: a count of frames, so
|
||||
/// neither Start Numbering Frames At nor the comp's Start Timecode moves it.
|
||||
pub fn display_duration(s: &Session, comp: &Comp, len: Tick) -> String {
|
||||
let fr = comp.frame_rate;
|
||||
let n = fr.frame_at(len);
|
||||
match s.project.settings.time_display {
|
||||
TimeDisplayStyle::Frames => format!("{n:05}"),
|
||||
TimeDisplayStyle::Feet35 | TimeDisplayStyle::Feet16 => {
|
||||
effectcraft_time::format_feet_frames(n, s.project.settings.time_display.frames_per_foot().unwrap_or(16))
|
||||
}
|
||||
TimeDisplayStyle::Timecode => effectcraft_time::format_timecode_ae(n, fr, false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a time typed in the project's Time Display Style (relative `+n`/`-n` from frame
|
||||
/// `current`) into a comp frame.
|
||||
pub fn parse_display_time(s: &Session, comp: &Comp, text: &str, current: i64) -> std::result::Result<i64, String> {
|
||||
|
||||
@@ -613,6 +613,51 @@ fn time_set_reads_frames_style_digits_as_a_frame_count() {
|
||||
assert_eq!(r["display"], "00100");
|
||||
}
|
||||
|
||||
/// Project Settings ▸ Time Display Style ▸ Frames ▸ Frame Count: Start at 0 / Start at 1
|
||||
/// (`app.project.framesCountType`), undoable and saved with the project (#1807).
|
||||
#[test]
|
||||
fn project_settings_frame_count_starts_at_one() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Count", "width": 64, "height": 64, "frameRate": 30, "duration": 2})).unwrap();
|
||||
s.execute("file.projectSettings", json!({"timeDisplay": "frames"})).unwrap();
|
||||
assert_eq!(s.execute("time.set", json!({"frame": 12})).unwrap()["display"], "00012");
|
||||
|
||||
s.execute("file.projectSettings", json!({"frameStart": 1})).unwrap();
|
||||
assert_eq!(s.execute("time.set", json!({"frame": 12})).unwrap()["display"], "00013");
|
||||
for bad in [json!(2), json!(-1), json!("one")] {
|
||||
assert!(s.execute("file.projectSettings", json!({"frameStart": bad})).is_err(), "{bad}");
|
||||
}
|
||||
assert_eq!(s.project.settings.frame_start, 1);
|
||||
|
||||
let dir = std::env::temp_dir().join(format!("ec-frame-start-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("count.ecproj").to_string_lossy().to_string();
|
||||
s.execute("file.save", json!({"path": path})).unwrap();
|
||||
let mut reopened = Session::default();
|
||||
reopened.execute("file.open", json!({"path": path})).unwrap();
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
assert_eq!(reopened.project.settings.frame_start, 1);
|
||||
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(s.execute("time.set", json!({"frame": 12})).unwrap()["display"], "00012");
|
||||
}
|
||||
|
||||
/// A Duration is a count of frames: Start at 1 and the comp's Start Timecode number positions,
|
||||
/// not lengths (#1807).
|
||||
#[test]
|
||||
fn durations_ignore_the_frame_count_and_the_start_timecode() {
|
||||
use crate::commands::time::display_duration;
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Len", "width": 64, "height": 64, "frameRate": 30, "duration": 10, "startTimecode": "0:00:01:00"})).unwrap();
|
||||
s.execute("file.projectSettings", json!({"frameStart": 1})).unwrap();
|
||||
let comp = s.active_comp().unwrap().clone();
|
||||
let len = comp.frame_rate.tick_of(30);
|
||||
for (style, want) in [("frames", "00030"), ("feet35", "0001+14"), ("timecode", "0:00:01:00")] {
|
||||
s.execute("file.projectSettings", json!({"timeDisplay": style})).unwrap();
|
||||
assert_eq!(display_duration(&s, &comp, len), want, "{style}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn separate_dimensions_split_and_rejoin() {
|
||||
let (mut s, l) = setup();
|
||||
|
||||
Reference in New Issue
Block a user