refactor command generator
This commit is contained in:
parent
115c30466b
commit
ef892c6732
|
@ -22,7 +22,7 @@
|
||||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
* Disable this if you'd like to use dynamic types.
|
* Disable this if you'd like to use dynamic types.
|
||||||
*/
|
*/
|
||||||
"checkJs": true
|
"checkJs": false
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Use global.d.ts instead of compilerOptions.types
|
* Use global.d.ts instead of compilerOptions.types
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
videoValue = null;
|
videoValue = null;
|
||||||
rendering = true;
|
rendering = true;
|
||||||
try {
|
try {
|
||||||
if (log.trim() != '') log += "\n\n";
|
if (log.trim() != "") log += "\n\n";
|
||||||
for (let vid of $inputs) {
|
for (let vid of $inputs) {
|
||||||
await ffmpeg.writeFile(vid.name, await fetchFile("/" + vid.name));
|
await ffmpeg.writeFile(vid.name, await fetchFile("/" + vid.name));
|
||||||
}
|
}
|
||||||
|
|
128
src/stores.js
128
src/stores.js
|
@ -25,50 +25,59 @@ export function makeFilterArgs(f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
||||||
let hasVid = false;
|
|
||||||
let hasAud = false;
|
|
||||||
|
|
||||||
let finalCommand = [];
|
let finalCommand = [];
|
||||||
|
|
||||||
let filtergraph = [];
|
let filtergraph = [];
|
||||||
|
let labelIndex = 1;
|
||||||
const inputs = $nodes.filter((n) => n.nodeType == "input");
|
|
||||||
const outputs = $nodes.filter((n) => n.nodeType == "output");
|
|
||||||
|
|
||||||
const inputIds = {};
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
|
||||||
const inp = inputs[i];
|
|
||||||
inputIds[inp.id] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
const edgeIds = {};
|
const edgeIds = {};
|
||||||
for (let i = 0; i < $edges.length; i++) {
|
const inputIdMap = {};
|
||||||
const e = $edges[i];
|
|
||||||
edgeIds[e.id] = i + 1;
|
|
||||||
|
|
||||||
const source = $nodes.find((n) => n.id === e.source);
|
const inputs = $nodes.filter((n) => n.nodeType === "input");
|
||||||
const target = $nodes.find((n) => n.id === e.target);
|
const inputIds = inputs.map((n) => n.id);
|
||||||
|
const inputEdges = $edges.filter((e) => inputIds.includes(e.source));
|
||||||
|
const outputs = $nodes.filter((n) => n.nodeType === "output");
|
||||||
|
|
||||||
if (source && target) {
|
inputs.forEach((inp, i) => (inputIdMap[inp.id] = i));
|
||||||
|
|
||||||
if (source.nodeType === "input") {
|
function traverseEdges(edg, type) {
|
||||||
if (e.sourceHandle.includes("v")) {
|
const outEdges = $edges.filter((e) => e.source === edg.target && e.sourceHandle.includes(type));
|
||||||
edgeIds[e.id] = inputIds[source.id] + ":v";
|
|
||||||
|
let label;
|
||||||
|
|
||||||
|
const inNode = $nodes.find((n) => n.id === edg.source);
|
||||||
|
const outNode = $nodes.find((n) => n.id === edg.target);
|
||||||
|
|
||||||
|
if (inNode && outNode) {
|
||||||
|
if (inNode.nodeType === "input" && outNode.nodeType === "filter") {
|
||||||
|
label = inputIdMap[inNode.id] + ":" + edg.sourceHandle[0];
|
||||||
|
} else if (inNode.nodeType === "filter" && outNode.nodeType === "filter") {
|
||||||
|
label = labelIndex;
|
||||||
|
labelIndex++;
|
||||||
|
} else if (inNode.nodeType === "filter" && outNode.nodeType === "output") {
|
||||||
|
label = "out_" + type;
|
||||||
|
} else if (inNode.nodeType === "input" && outNode.nodeType === "output") {
|
||||||
|
label = "FILTERLESS_" + inputIdMap[inNode.id] + ":" + type;
|
||||||
|
} else {
|
||||||
|
label = "UNKNOWN";
|
||||||
}
|
}
|
||||||
if (e.sourceHandle.includes("a")) {
|
|
||||||
edgeIds[e.id] = inputIds[source.id] + ":a";
|
edgeIds[edg.id] = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let e2 of outEdges) {
|
||||||
|
traverseEdges(e2, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.nodeType === "output") {
|
for (let inp of inputEdges) {
|
||||||
const outType = e.targetHandle.includes("a") ? "aud_out" : "vid_out";
|
for (let t of ["v", "a"]) {
|
||||||
edgeIds[e.id] = outType;
|
if (inp.sourceHandle.includes(t)) {
|
||||||
|
traverseEdges(inp, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let n of $nodes.filter((n) => n.nodeType == "filter")) {
|
for (let n of $nodes.filter((n) => n.nodeType == "filter")) {
|
||||||
let cmd = "";
|
let cmd = { weight: 0, in: [], out: [], cmd: "" };
|
||||||
|
|
||||||
const outs = $edges.filter((e) => e.source == n.id);
|
const outs = $edges.filter((e) => e.source == n.id);
|
||||||
const ins = $edges.filter((e) => e.target == n.id);
|
const ins = $edges.filter((e) => e.target == n.id);
|
||||||
|
@ -77,16 +86,30 @@ export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
||||||
|
|
||||||
for (let i of ins) {
|
for (let i of ins) {
|
||||||
const eid = edgeIds[i.id];
|
const eid = edgeIds[i.id];
|
||||||
cmd += `[${eid}]`;
|
if (typeof eid == "string" && eid.includes(":")) cmd.weight = -1000;
|
||||||
|
cmd.in.push(eid);
|
||||||
}
|
}
|
||||||
cmd += makeFilterArgs(n.data);
|
|
||||||
|
cmd.cmd = makeFilterArgs(n.data);
|
||||||
|
|
||||||
for (let o of outs) {
|
for (let o of outs) {
|
||||||
const eid = edgeIds[o.id];
|
const eid = edgeIds[o.id];
|
||||||
cmd += `[${eid}]`;
|
if (typeof eid == "string" && eid.includes("out")) cmd.weight = 1000;
|
||||||
|
else cmd.weight = eid;
|
||||||
|
cmd.out.push(eid);
|
||||||
}
|
}
|
||||||
|
|
||||||
filtergraph.push(cmd);
|
filtergraph.push(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filtergraph.sort((a, b) => {
|
||||||
|
return a.weight - b.weight;
|
||||||
|
});
|
||||||
|
|
||||||
|
filtergraph = filtergraph.map((c) => {
|
||||||
|
return c.in.map((i) => `[${i}]`).join("") + c.cmd + c.out.map((i) => `[${i}]`).join("");
|
||||||
|
});
|
||||||
|
|
||||||
finalCommand.push("ffmpeg");
|
finalCommand.push("ffmpeg");
|
||||||
|
|
||||||
for (let inp of inputs) {
|
for (let inp of inputs) {
|
||||||
|
@ -94,26 +117,39 @@ export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
||||||
finalCommand.push(inp.data.name);
|
finalCommand.push(inp.data.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasVid = false;
|
||||||
|
let hasAud = false;
|
||||||
|
|
||||||
|
let mediaMaps = Object.values(edgeIds)
|
||||||
|
.map((eid) => {
|
||||||
|
if (String(eid).includes("FILTERLESS")) {
|
||||||
|
return eid.split("_")[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((m) => m !== null);
|
||||||
|
|
||||||
if (filtergraph.length > 0) {
|
if (filtergraph.length > 0) {
|
||||||
const fg = '"' + filtergraph.join(";") + '"';
|
let fg = `"${filtergraph.join(";")}"`;
|
||||||
|
|
||||||
|
// this crazy thing replaces stuff like [1];[1] with a comma!
|
||||||
|
fg = fg.replaceAll(/(\[\d+\]);\1(?!\[)/g, ",");
|
||||||
|
|
||||||
hasVid = fg.includes(":v]");
|
hasVid = fg.includes(":v]");
|
||||||
hasAud = fg.includes(":a]");
|
hasAud = fg.includes(":a]");
|
||||||
|
|
||||||
finalCommand.push("-filter_complex");
|
finalCommand.push("-filter_complex", fg);
|
||||||
finalCommand.push(fg);
|
|
||||||
|
|
||||||
finalCommand.push("-map");
|
|
||||||
if (hasAud) {
|
if (hasAud) {
|
||||||
finalCommand.push('"[aud_out]"');
|
finalCommand.push("-map", '"[out_a]"');
|
||||||
} else {
|
|
||||||
finalCommand.push("0:a");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finalCommand.push("-map");
|
|
||||||
if (hasVid) {
|
if (hasVid) {
|
||||||
finalCommand.push('"[vid_out]"');
|
finalCommand.push("-map", '"[out_v]"');
|
||||||
} else {
|
}
|
||||||
finalCommand.push("0:v");
|
|
||||||
|
for (let m of mediaMaps) {
|
||||||
|
finalCommand.push("-map", m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,11 +157,9 @@ export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
||||||
finalCommand.push(out.data.name);
|
finalCommand.push(out.data.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entireCommand = finalCommand.join(" ");
|
return finalCommand.join(" ");
|
||||||
return entireCommand;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const inputs = derived(nodes, ($nodes) => {
|
export const inputs = derived(nodes, ($nodes) => {
|
||||||
return $nodes.filter((n) => n.nodeType === "input").map((n) => n.data);
|
return $nodes.filter((n) => n.nodeType === "input").map((n) => n.data);
|
||||||
});
|
});
|
||||||
|
@ -176,7 +210,7 @@ nodes.subscribe(($nodes) => {
|
||||||
for (let inpNode of inputNodes) {
|
for (let inpNode of inputNodes) {
|
||||||
connectNode(inpNode, [...orderedNodes]);
|
connectNode(inpNode, [...orderedNodes]);
|
||||||
}
|
}
|
||||||
// console.log("new", newEdges);
|
|
||||||
edges.set(newEdges);
|
edges.set(newEdges);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect, test, describe } from "vitest";
|
import { expect, test, describe } from "vitest";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { nodes, edges, addNode, resetNodes, makeFilterArgs, previewCommand } from "../../src/stores.js";
|
import { addNode, resetNodes, makeFilterArgs, previewCommand } from "../../src/stores.js";
|
||||||
|
|
||||||
describe("Filter param builder", () => {
|
describe("Filter param builder", () => {
|
||||||
test("No params", () => {
|
test("No params", () => {
|
||||||
|
@ -58,7 +58,7 @@ describe("Command builder", () => {
|
||||||
resetNodes();
|
resetNodes();
|
||||||
addNode({ name: "filter", type: "V->V" }, "filter");
|
addNode({ name: "filter", type: "V->V" }, "filter");
|
||||||
expect(get(previewCommand)).toBe(
|
expect(get(previewCommand)).toBe(
|
||||||
`ffmpeg -i punch.mp4 -filter_complex "[0:v]filter[vid_out]" -map 0:a -map "[vid_out]" out.mp4`
|
`ffmpeg -i punch.mp4 -filter_complex "[0:v]filter[out_v]" -map "[out_v]" -map 0:a out.mp4`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -66,7 +66,36 @@ describe("Command builder", () => {
|
||||||
resetNodes();
|
resetNodes();
|
||||||
addNode({ name: "filter", type: "A->A" }, "filter");
|
addNode({ name: "filter", type: "A->A" }, "filter");
|
||||||
expect(get(previewCommand)).toBe(
|
expect(get(previewCommand)).toBe(
|
||||||
`ffmpeg -i punch.mp4 -filter_complex "[0:a]filter[aud_out]" -map "[aud_out]" -map 0:v out.mp4`
|
`ffmpeg -i punch.mp4 -filter_complex "[0:a]filter[out_a]" -map "[out_a]" -map 0:v out.mp4`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("One audio, one video filter", () => {
|
||||||
|
resetNodes();
|
||||||
|
addNode({ name: "afilter", type: "A->A" }, "filter");
|
||||||
|
addNode({ name: "vfilter", type: "V->V" }, "filter");
|
||||||
|
expect(get(previewCommand)).toBe(
|
||||||
|
`ffmpeg -i punch.mp4 -filter_complex "[0:a]afilter[out_a];[0:v]vfilter[out_v]" -map "[out_a]" -map "[out_v]" out.mp4`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("video filter chain", () => {
|
||||||
|
resetNodes();
|
||||||
|
addNode({ name: "vfilter", type: "V->V" }, "filter");
|
||||||
|
addNode({ name: "vfilter2", type: "V->V" }, "filter");
|
||||||
|
addNode({ name: "vfilter3", type: "V->V" }, "filter");
|
||||||
|
expect(get(previewCommand)).toBe(
|
||||||
|
`ffmpeg -i punch.mp4 -filter_complex "[0:v]vfilter,vfilter2,vfilter3[out_v]" -map "[out_v]" -map 0:a out.mp4`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("One audio, two video filters", () => {
|
||||||
|
resetNodes();
|
||||||
|
addNode({ name: "afilter", type: "A->A" }, "filter");
|
||||||
|
addNode({ name: "vfilter", type: "V->V" }, "filter");
|
||||||
|
addNode({ name: "vfilter2", type: "V->V" }, "filter");
|
||||||
|
expect(get(previewCommand)).toBe(
|
||||||
|
`ffmpeg -i punch.mp4 -filter_complex "[0:v]vfilter[1];[0:a]afilter[out_a];[1]vfilter2[out_v]" -map "[out_a]" -map "[out_v]" out.mp4`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue