mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-21 19:22:52 -05:00
Honor duration rounding in timeToFrames
Round nonzero duration frame counts away from zero while retaining absolute-time rounding, default argument selection and numeric coercion. Cover fractional durations, absolute controls, defaults and coercion. Sort expected paths in the existing footage test for temporary-directory portability. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
1 parent
549e9d8251
commit
36ebe2eada
3 files changed
+82
-2
No files matched your search
@@ -235,7 +235,8 @@ mod tests {
|
||||
paths.sort();
|
||||
// Windows joins the stored "media/..." onto the project's folder and leaves a path with mixed
|
||||
// separators; `Path` equality compares components, so both spellings are the same file.
|
||||
let expected = ["/nonexistent/ec/abs.png", clip.to_str().unwrap(), "media/ec-relative-gone.png"];
|
||||
let mut expected = ["/nonexistent/ec/abs.png", clip.to_str().unwrap(), "media/ec-relative-gone.png"];
|
||||
expected.sort();
|
||||
assert_eq!(paths.len(), expected.len(), "{paths:?}");
|
||||
for (got, want) in paths.iter().zip(expected) {
|
||||
assert_eq!(std::path::Path::new(got), std::path::Path::new(want), "{paths:?}");
|
||||
|
||||
@@ -261,7 +261,9 @@ function noise(x) {
|
||||
function timeToFrames(t, fps, isDuration) {
|
||||
if (t === undefined) t = time + thisComp.displayStartTime;
|
||||
if (fps === undefined) fps = 1 / __S.fd;
|
||||
return Math.floor(t * fps + 1e-6);
|
||||
var frames = t * fps;
|
||||
if (isDuration && frames !== 0) return frames < 0 ? Math.floor(frames) : Math.ceil(frames);
|
||||
return Math.floor(frames + 1e-6);
|
||||
}
|
||||
|
||||
function framesToTime(frames, fps) {
|
||||
|
||||
@@ -1285,3 +1285,80 @@ fn layer_control_property_stands_for_its_layer() {
|
||||
assert_close!(f.num("effect('Map')(1).transform.scale[0]", 0.0), 100.0);
|
||||
assert_close!(f.num("effect('Map')(1).value.name == 'B' ? 1 : 0", 0.0), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_to_frames_rounds_durations_away_from_zero() {
|
||||
let f = fx();
|
||||
// Adobe's public expression reference distinguishes durations from absolute times.
|
||||
// Expected integer counts are literal values, independent of the implementation.
|
||||
for (expression, expected) in [
|
||||
("timeToFrames(-0.01, 24, true)", -1.0),
|
||||
("timeToFrames(-0.05, 24, true)", -2.0),
|
||||
("timeToFrames(0, 24, true)", 0.0),
|
||||
("timeToFrames(-0.125, 24, true)", -3.0),
|
||||
("timeToFrames(0.125, 24, true)", 3.0),
|
||||
("timeToFrames(0.01, 24, true)", 1.0),
|
||||
("timeToFrames(0.05, 24, true)", 2.0),
|
||||
] {
|
||||
assert_eq!(f.num(expression, 0.0), expected, "{expression}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_to_frames_rounds_absolute_times_toward_negative_infinity() {
|
||||
let f = fx();
|
||||
for (expression, expected) in [
|
||||
("timeToFrames(-0.01, 24, false)", -1.0),
|
||||
("timeToFrames(-0.05, 24, false)", -2.0),
|
||||
("timeToFrames(0, 24, false)", 0.0),
|
||||
("timeToFrames(-0.125, 24, false)", -3.0),
|
||||
("timeToFrames(0.125, 24, false)", 3.0),
|
||||
("timeToFrames(0.01, 24, false)", 0.0),
|
||||
("timeToFrames(0.05, 24, false)", 1.0),
|
||||
] {
|
||||
assert_eq!(f.num(expression, 0.0), expected, "{expression}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_to_frames_preserves_default_arguments() {
|
||||
let f = fx();
|
||||
// The fixture's composition is 30 fps with displayStartTime = 0.
|
||||
for (expression, evaluation_time, expected) in [
|
||||
("timeToFrames(0.01, 24)", 0.0, 0.0),
|
||||
("timeToFrames(-0.01, 24)", 0.0, -1.0),
|
||||
("timeToFrames(0.05)", 0.0, 1.0),
|
||||
("timeToFrames(-0.05)", 0.0, -2.0),
|
||||
("timeToFrames()", 0.0, 0.0),
|
||||
("timeToFrames()", 0.05, 1.0),
|
||||
("timeToFrames()", -0.05, -2.0),
|
||||
("timeToFrames(undefined, 24, true)", 0.05, 2.0),
|
||||
("timeToFrames(0.05, undefined, true)", 0.0, 2.0),
|
||||
] {
|
||||
assert_eq!(f.num(expression, evaluation_time), expected, "{expression} at {evaluation_time}s");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_to_frames_preserves_coercion_and_small_durations() {
|
||||
let f = fx();
|
||||
// Duration rounding must not erase tiny nonzero values; absolute times keep the existing tolerance.
|
||||
for (expression, expected) in [
|
||||
("timeToFrames('0.05', '24', false)", 1.0),
|
||||
("timeToFrames('0.05', '24', true)", 2.0),
|
||||
("timeToFrames('-0.05', '24', true)", -2.0),
|
||||
("timeToFrames(null, 24, true)", 0.0),
|
||||
("timeToFrames(0.05, null, true)", 0.0),
|
||||
("timeToFrames(0.05, 0, true)", 0.0),
|
||||
("timeToFrames(1e-10, 24, true)", 1.0),
|
||||
("timeToFrames(-1e-10, 24, true)", -1.0),
|
||||
("timeToFrames(-1e-10, 24, false)", 0.0),
|
||||
("1 / timeToFrames(-0, 24, true) === Infinity ? 1 : 0", 1.0),
|
||||
("isNaN(timeToFrames(NaN, 24, true)) ? 1 : 0", 1.0),
|
||||
("timeToFrames(Infinity, 24, true) === Infinity ? 1 : 0", 1.0),
|
||||
("timeToFrames(-Infinity, 24, true) === -Infinity ? 1 : 0", 1.0),
|
||||
("var calls = 0; var t = {valueOf: function () { calls++; return 0.05; }}; timeToFrames(t, 24, true) + 100 * calls", 102.0),
|
||||
] {
|
||||
assert_eq!(f.num(expression, 0.0), expected, "{expression}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user