ffmpeg-explorer/src/stores.js

362 lines
9.2 KiB
JavaScript
Raw Normal View History

2023-08-22 12:05:49 -04:00
import { v4 as uuidv4 } from "uuid";
2023-08-22 21:57:53 -04:00
import { writable, derived, get } from "svelte/store";
2023-08-22 12:05:49 -04:00
2023-08-22 21:57:53 -04:00
export const nodes = writable([]);
export const edges = writable([]);
2023-08-24 12:09:55 -04:00
export const auto = writable(true);
2023-08-31 12:46:24 -04:00
export const doFit = writable(0);
2023-08-24 23:17:49 -04:00
export const selectedFilter = writable();
2023-08-22 12:05:49 -04:00
2023-08-29 11:36:57 -04:00
export const INPUTNAMES = [
2023-08-30 18:01:10 -04:00
{ name: "punch.mp4", url: "/punch.mp4", ext: "mp4", outputs: ["v", "a"], inputs: [] },
{ name: "shoe.mp4", url: "/shoe.mp4", ext: "mp4", outputs: ["v", "a"], inputs: [] },
2023-08-29 11:36:57 -04:00
];
export const OUTPUTNAMES = [
2023-08-30 18:01:10 -04:00
{ name: "out.mp4", ext: "mp4", inputs: ["v", "a"], outputs: [] },
{ name: "out.gif", ext: "gif", inputs: ["v"], outputs: [] },
2023-08-29 11:36:57 -04:00
];
2023-08-30 18:01:10 -04:00
export const inputNames = writable(INPUTNAMES);
export const outputNames = writable(OUTPUTNAMES);
addNode({ ...INPUTNAMES[0] }, "input");
addNode({ ...OUTPUTNAMES[0] }, "output");
2023-08-22 12:05:49 -04:00
2023-08-28 11:31:32 -04:00
export function makeFilterArgs(f) {
2023-08-30 18:01:10 -04:00
let fCommand = f.name;
if (f.params && f.params.length > 0) {
let params = f.params
.map((p) => {
if (p.value === "" || p.value === null || p.value === p.default) return null;
return `${p.name}=${p.value}`;
})
.filter((p) => p !== null)
.join(":");
if (params) fCommand += "=" + params;
}
return fCommand;
2023-08-22 12:05:49 -04:00
}
2023-08-24 14:14:32 -04:00
export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
2023-08-30 18:01:10 -04:00
let finalCommand = [];
let filtergraph = [];
let labelIndex = 1;
const edgeIds = {};
const inputIdMap = {};
const inputs = $nodes.filter((n) => n.nodeType === "input");
const inputIds = inputs.map((n) => n.id);
const inputEdges = $edges.filter((e) => inputIds.includes(e.source));
const outputs = $nodes.filter((n) => n.nodeType === "output");
// create edge labels for each input
inputs.forEach((inp, i) => (inputIdMap[inp.id] = i));
// create edge labels for each filter
function traverseEdges(edg, type) {
2023-09-01 16:37:39 -04:00
// const outEdges = $edges.filter((e) => e.source === edg.target && (e.sourceHandle.includes(type) || e.sourceHandle.includes("n")));
const outEdges = $edges.filter((e) => e.source === edg.target);
2023-08-30 18:01:10 -04:00
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";
}
edgeIds[edg.id] = label;
}
for (let e2 of outEdges) {
traverseEdges(e2, type);
}
}
for (let inp of inputEdges) {
for (let t of ["v", "a"]) {
if (inp.sourceHandle.includes(t)) {
traverseEdges(inp, t);
}
}
}
for (let n of $nodes.filter((n) => n.nodeType == "filter")) {
let cmd = { weight: 0, in: [], out: [], cmd: "" };
const outs = $edges.filter((e) => e.source == n.id);
const ins = $edges.filter((e) => e.target == n.id);
if (outs.length == 0 && ins.length == 0) continue;
for (let i of ins) {
const eid = edgeIds[i.id];
2023-09-01 16:52:16 -04:00
if (eid) {
if (typeof eid == "string" && eid.includes(":")) cmd.weight = -1000;
2023-09-02 11:03:58 -04:00
else cmd.weight = eid;
2023-09-01 16:52:16 -04:00
cmd.in.push(eid);
}
2023-08-30 18:01:10 -04:00
}
cmd.cmd = makeFilterArgs(n.data);
for (let o of outs) {
const eid = edgeIds[o.id];
2023-09-01 16:52:16 -04:00
if (eid) {
if (typeof eid == "string" && eid.includes("out")) cmd.weight = 1000;
cmd.out.push(eid);
}
2023-08-30 18:01:10 -04:00
}
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");
for (let inp of inputs) {
finalCommand.push("-i");
finalCommand.push(inp.data.name);
}
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) {
let fg = `"${filtergraph.join(";")}"`;
// this crazy thing replaces stuff like [1];[1] with a comma!
2023-09-02 11:03:58 -04:00
fg = fg.replaceAll(/(?<!])(\[\d+\]);\1(?!\[)/g, ",");
2023-08-30 18:01:10 -04:00
finalCommand.push("-filter_complex", fg);
2023-09-01 16:52:16 -04:00
if (fg.includes("[out_a]")) {
2023-08-30 18:01:10 -04:00
finalCommand.push("-map", '"[out_a]"');
}
2023-09-01 16:52:16 -04:00
if (fg.includes("[out_v]")) {
2023-08-30 18:01:10 -04:00
finalCommand.push("-map", '"[out_v]"');
}
for (let m of mediaMaps) {
finalCommand.push("-map", m);
}
}
for (let out of outputs) {
finalCommand.push(out.data.name);
}
2023-08-29 01:09:36 -04:00
2023-08-30 18:01:10 -04:00
return finalCommand;
2023-08-29 01:09:36 -04:00
});
2023-08-22 21:57:53 -04:00
export const inputs = derived(nodes, ($nodes) => {
2023-08-30 18:01:10 -04:00
return $nodes.filter((n) => n.nodeType === "input").map((n) => n.data);
2023-08-22 21:57:53 -04:00
});
2023-08-29 11:36:57 -04:00
export const outputs = derived(nodes, ($nodes) => {
2023-08-30 18:01:10 -04:00
return $nodes.filter((n) => n.nodeType === "output").map((n) => n.data);
2023-08-29 11:36:57 -04:00
});
2023-08-24 14:14:32 -04:00
nodes.subscribe(($nodes) => {
2023-08-30 18:01:10 -04:00
const isAuto = get(auto);
if (!isAuto) return;
const outputNodes = $nodes.filter((n) => n.nodeType === "output");
const inputNodes = $nodes.filter((n) => n.nodeType === "input");
const filterNodes = $nodes.filter((n) => n.nodeType === "filter");
const orderedNodes = [].concat(filterNodes, outputNodes).filter((n) => n != undefined);
const filled = [];
let newEdges = [];
function connectNode(n1, rest) {
for (let i = 0; i < n1.data.outputs.length; i++) {
const edgeType = n1.data.outputs[i];
for (let j = 0; j < rest.length; j++) {
let found = false;
const n2 = rest[j];
for (let k = 0; k < n2.data.inputs.length; k++) {
const targetEdgeType = n2.data.inputs[k];
if (edgeType === targetEdgeType && !filled.includes(n2.id + k)) {
newEdges.push({
id: uuidv4(),
type: "default",
source: n1.id,
target: n2.id,
sourceHandle: edgeType + "_" + i,
targetHandle: edgeType + "_" + k,
});
filled.push(n2.id + k);
found = true;
break;
}
}
if (found) break;
}
}
const nextNode = rest.shift();
if (nextNode) {
connectNode(nextNode, rest);
}
}
for (let inpNode of inputNodes) {
connectNode(inpNode, [...orderedNodes]);
}
edges.set(newEdges);
2023-08-24 14:14:32 -04:00
});
2023-08-27 11:47:41 -04:00
export function addNode(_data, type) {
2023-08-30 18:01:10 -04:00
const data = JSON.parse(JSON.stringify(_data));
let ins = [];
let outs = [];
if (type === "input") {
outs = ["v", "a"];
} else if (type === "output") {
ins = ["v", "a"];
} else if (type === "filter") {
if (data.params) {
data.params = data.params.map((p) => {
p.value = null;
if (p.default != null) p.value = p.default;
return p;
});
}
2023-09-03 16:21:45 -04:00
ins = data.inputs;
outs = data.outputs;
2023-08-30 18:01:10 -04:00
}
data.nodeType = type;
data.inputs = ins;
data.outputs = outs;
let node = {
id: uuidv4(),
type: "ffmpeg",
data: data,
nodeType: type,
position: { x: 0, y: 0 },
};
nodes.update((_nodes) => {
_nodes.push(node);
2023-09-25 19:30:11 -04:00
_nodes = autoLayout(_nodes);
2023-08-30 18:01:10 -04:00
2023-09-03 16:21:45 -04:00
if (node.nodeType === "filter") {
selectedFilter.set(_nodes.length - 1);
}
return _nodes;
});
}
2023-08-30 18:01:10 -04:00
2023-09-03 16:21:45 -04:00
function autoLayout(_nodes) {
const isAuto = get(auto);
if (isAuto) {
const w = 120;
const h = 50;
const margin = 50;
let prev = null;
for (let n of _nodes) {
if (n.nodeType === "input") {
n.position = { x: 0, y: prev ? prev.position.y + h + margin : 0 };
prev = n;
2023-08-30 18:01:10 -04:00
}
2023-09-03 16:21:45 -04:00
}
2023-08-30 18:01:10 -04:00
2023-09-03 16:21:45 -04:00
for (let n of _nodes) {
if (n.nodeType === "filter") {
let _w = prev && prev.width ? prev.width : w;
2023-09-25 19:30:11 -04:00
let _x = prev ? prev.position.x + _w + margin : 0;
let _y = -50;
if (n.data.inputs && n.data.inputs[0] && n.data.inputs[0] === "a") {
_y = 50;
}
n.position = { x: _x, y: _y };
2023-09-03 16:21:45 -04:00
prev = n;
2023-08-30 18:01:10 -04:00
}
2023-09-03 16:21:45 -04:00
}
2023-08-30 18:01:10 -04:00
2023-09-03 16:21:45 -04:00
for (let n of _nodes) {
if (n.nodeType === "output") {
let _w = prev && prev.width ? prev.width : w;
n.position = { x: prev ? prev.position.x + _w + margin : 0, y: 0 };
2023-08-30 18:01:10 -04:00
}
}
2023-09-03 16:21:45 -04:00
}
return _nodes;
}
export function copyNode(node) {
2023-09-25 19:30:11 -04:00
const oldId = node.id;
2023-09-03 16:21:45 -04:00
node = JSON.parse(JSON.stringify(node));
2023-09-25 19:30:11 -04:00
node.id = uuidv4();
2023-09-03 16:21:45 -04:00
nodes.update((_nodes) => {
2023-09-25 19:30:11 -04:00
_nodes.push(node);
2023-09-03 16:21:45 -04:00
const index = _nodes.findIndex((n) => n.id === oldId);
_nodes.splice(index, 1);
2023-09-25 19:30:11 -04:00
_nodes = autoLayout(_nodes);
2023-09-03 16:21:45 -04:00
2023-08-30 18:01:10 -04:00
if (node.nodeType === "filter") {
selectedFilter.set(_nodes.length - 1);
}
return _nodes;
});
2023-08-22 21:57:53 -04:00
}
2023-08-22 12:05:49 -04:00
2023-08-28 12:13:42 -04:00
export function resetNodes() {
2023-08-30 18:01:10 -04:00
nodes.set([]);
addNode({ name: "punch.mp4" }, "input");
addNode({ name: "out.mp4" }, "output");
2023-08-28 12:13:42 -04:00
}
2023-08-22 21:57:53 -04:00
export function removeNode(id) {
2023-08-30 18:01:10 -04:00
nodes.update((_nodes) => {
const index = _nodes.findIndex((n) => n.id === id);
_nodes.splice(index, 1);
return _nodes;
});
2023-08-25 14:03:32 -04:00
}
2023-08-24 12:09:55 -04:00
2023-08-25 14:03:32 -04:00
export function removeEdge(id) {
2023-08-30 18:01:10 -04:00
edges.update((_edges) => {
const index = _edges.findIndex((e) => e.id === id);
_edges.splice(index, 1);
return _edges;
});
2023-08-22 12:05:49 -04:00
}