initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# kdenlive-easer
|
||||
|
||||
An ease generator for KDEnlive using cubic bezier curves.
|
||||
205
index.js
Normal file
205
index.js
Normal file
@@ -0,0 +1,205 @@
|
||||
const BezierEasing = require("bezier-easing");
|
||||
const { program } = require("commander");
|
||||
|
||||
const KEYFRAME_BASE = { value: "" };
|
||||
|
||||
program
|
||||
.requiredOption("-c, --curve <ax,ay,bx,by>", "the cubic bezier curve to use, in the format of CSS curves", "0.23, 1, 0.32, 1")
|
||||
.requiredOption("-vr, --value-range <a..b>", "the starting and ending value of the ease.\nan empty value on either side will resolve to the values of the first and last frame, respectively.\nif no stdin is supplied, both of those will be 0")
|
||||
.requiredOption("-fr, --frame-range <a..b>", "the start and end frames for the ease.\nlike -vr, an empty value will resolve to the first and last frames from stdin, but will not work without -i")
|
||||
.requiredOption("-p, --property <p>", "the property to apply the ease to; can be x, y, w, or h")
|
||||
.option("-s, --skip <frames>", "skip every <frames> frames", 0)
|
||||
.option("-rh, --reverse-horizontal", "reverse the curve horizontally")
|
||||
.option("-rv, --reverse-vertical", "reverse the curve vertically")
|
||||
.option("-o, --output-plain", "output the values in this format: frameNumber:value")
|
||||
.option("-i, --stdin", "read the base keyframe data from stdin")
|
||||
.option("-r, --round", "round all generated values")
|
||||
.addHelpText("beforeAll", "kdenease.js - generate eases for kdenlive using a cubic bezier curve\n")
|
||||
.addHelpText("after", "you can use -i to read keyframe data from stdin and overwrite the selected values to the generated values.");
|
||||
|
||||
function decodeKeyframes(kfString) {
|
||||
let out = {};
|
||||
for (let kf of kfString.split(";")) {
|
||||
kf = kf.split("=");
|
||||
const frame = parseInt(kf[0]);
|
||||
const [x, y, w, h] = kf[1].split(" ");
|
||||
out[frame] = { x: x, y: y, w: w, h: h };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function encodeKeyframes(kfs) {
|
||||
let out = [];
|
||||
for (let e in kfs) {
|
||||
out.push(`${e}=${kfs[e].x} ${kfs[e].y} ${kfs[e].w} ${kfs[e].h}`);
|
||||
}
|
||||
return out.join(";");
|
||||
}
|
||||
|
||||
function calculate(
|
||||
ax,
|
||||
ay,
|
||||
bx,
|
||||
by,
|
||||
valueStart,
|
||||
valueEnd,
|
||||
frameStart,
|
||||
frameEnd,
|
||||
skip,
|
||||
round
|
||||
) {
|
||||
let out = {};
|
||||
const ease = BezierEasing(ax, ay, bx, by);
|
||||
for (let frame = frameStart; frame <= frameEnd; frame += skip + 1) {
|
||||
const scaled = (frame - frameStart) / (frameEnd - frameStart);
|
||||
const newVal = valueStart + ((valueEnd - valueStart) * ease(scaled));
|
||||
out[frame] = round ? Math.round(newVal) : newVal;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function waitForStdin() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let out = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => { out += chunk });
|
||||
process.stdin.on("end", () => { resolve(out) });
|
||||
});
|
||||
}
|
||||
|
||||
function reverseCurve([ax, ay, bx, by], horizontal, vertical) {
|
||||
if (horizontal) {
|
||||
const temp = bx;
|
||||
bx = 0.5 + (0.5 - ax);
|
||||
ax = 0.5 + (0.5 - temp);
|
||||
}
|
||||
|
||||
if (vertical) {
|
||||
const temp = by;
|
||||
by = 0.5 + (0.5 - ay);
|
||||
ay = 0.5 + (0.5 - by);
|
||||
}
|
||||
|
||||
return [ax, ay, bx, by];
|
||||
}
|
||||
|
||||
function panic(a) {
|
||||
console.error(a);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function getFirstLastFrames(values) {
|
||||
const keys = Object.keys(values);
|
||||
return [
|
||||
parseInt(firstFrame = keys[0]),
|
||||
parseInt(lastFrame = keys[keys.length - 1])
|
||||
];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
program.parse();
|
||||
const o = program.opts();
|
||||
|
||||
if (!["x", "y", "w", "h"].includes(o.property)) {
|
||||
panic("property not in x, y, w, or h");
|
||||
}
|
||||
|
||||
let baseJSON = {
|
||||
"in": 0,
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "transition.geometry",
|
||||
"out": 150,
|
||||
"type": 6
|
||||
};
|
||||
|
||||
let firstFrame, lastFrame;
|
||||
|
||||
if (o.stdin) {
|
||||
baseJSON = JSON.parse(await waitForStdin())[0];
|
||||
baseJSON.value = decodeKeyframes(baseJSON.value);
|
||||
console.dir(baseJSON);
|
||||
[firstFrame, lastFrame] = getFirstLastFrames(baseJSON.value);
|
||||
}
|
||||
|
||||
const [frameStart, frameEnd] = (() => {
|
||||
let vals = ` ${o.frameRange} `.split("..");
|
||||
if (vals.includes(" ") && !o.stdin) {
|
||||
panic("cannot resolve frame from nonexistent input!\nif you meant to resolve to the start or end frame, remember to pipe in keyframe data from Kdenlive and use -i.\notherwise, you made a typo");
|
||||
}
|
||||
|
||||
return [
|
||||
parseInt(vals[0] == " " ? firstFrame : vals[0]),
|
||||
parseInt(vals[1] == " " ? lastFrame : vals[1])
|
||||
];
|
||||
})();
|
||||
|
||||
if (!o.stdin) {
|
||||
baseJSON.value = {};
|
||||
for (
|
||||
let frame = parseInt(frameStart);
|
||||
frame <= frameEnd;
|
||||
frame += o.skip + 1
|
||||
) {
|
||||
baseJSON.value[frame] = { x: 0, y: 0, w: 0, h: 0 };
|
||||
}
|
||||
[firstFrame, lastFrame] = getFirstLastFrames(baseJSON.value);
|
||||
}
|
||||
|
||||
const [ax, ay, bx, by] = reverseCurve(
|
||||
o.curve.split(",").map((e) => parseInt(e)),
|
||||
o.reverseHorizontal,
|
||||
o.reverseVertical
|
||||
);
|
||||
|
||||
const [valueStart, valueEnd] = (() => {
|
||||
let vals = ` ${o.valueRange} `.split(".."); // spaces are to allow for empty values
|
||||
return [
|
||||
parseInt(vals[0] == " "
|
||||
? baseJSON.value[firstFrame][o.property]
|
||||
: vals[0]
|
||||
),
|
||||
parseInt(vals[1] == " "
|
||||
? baseJSON.value[lastFrame][o.property]
|
||||
: vals[1]
|
||||
)
|
||||
];
|
||||
})();
|
||||
|
||||
console.dir({
|
||||
valueRange: [valueStart, valueEnd],
|
||||
frameRange: [frameStart, frameEnd]
|
||||
});
|
||||
|
||||
const newValues = calculate(
|
||||
ax,
|
||||
ay,
|
||||
bx,
|
||||
by,
|
||||
valueStart,
|
||||
valueEnd,
|
||||
frameStart,
|
||||
frameEnd,
|
||||
o.skip,
|
||||
o.round
|
||||
);
|
||||
|
||||
console.dir(newValues);
|
||||
|
||||
if (o.outputPlain) {
|
||||
for (let frame in newValues) {
|
||||
console.log(`${frame}:${newValues[frame]}`);
|
||||
}
|
||||
} else {
|
||||
for (let frame in newValues) {
|
||||
//baseJSON.value[frame][o.property] = newValues[frame];
|
||||
console.dir(baseJSON.value[frame]);
|
||||
}
|
||||
|
||||
baseJSON.value = encodeKeyframes(baseJSON.value);
|
||||
|
||||
console.log(JSON.stringify([baseJSON]));
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
11
kf.json
Normal file
11
kf.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"in": 8231,
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "transition.geometry",
|
||||
"out": 8397,
|
||||
"type": 6,
|
||||
"value": "8231=-475 0 0 0;8232=-1161 0 0 0;8233=-1242 0 0 0;8234=-1282 0 0 0;8235=-1305 0 0 0;8236=-1319 0 0 0;8237=-1328 0 0 0;8238=-1334 0 0 0;8239=-1338 0 0 0;8240=-1341 0 0 0;8241=-1342 0 0 0;8242=-1343 0 0 0;8243=-1344 0 0 0;8244=-1344 0 0 0;8245=-1344 0 0 0;8246=-1344 0 0 0;8397=-475 0 0 0;8398=-1161 0 0 0;8399=-1242 0 0 0;8400=-1282 0 0 0;8401=-1305 0 0 0;8402=-1319 0 0 0;8403=-1328 0 0 0;8404=-1334 0 0 0;8405=-1338 0 0 0;8406=-1341 0 0 0;8407=-1342 0 0 0;8408=-1343 0 0 0;8409=-1344 0 0 0;8410=-1344 0 0 0;8411=-1344 0 0 0;8412=-1344 0 0 0"
|
||||
}
|
||||
]
|
||||
42
package-lock.json
generated
Normal file
42
package-lock.json
generated
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "cubic-easing-gen",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cubic-easing-gen",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bezier-easing": "^2.1.0",
|
||||
"commander": "^8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
|
||||
},
|
||||
"commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
|
||||
}
|
||||
}
|
||||
}
|
||||
15
package.json
Normal file
15
package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "cubic-easing-gen",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bezier-easing": "^2.1.0",
|
||||
"commander": "^8.3.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user