clean up and add delete edges
This commit is contained in:
parent
79ddc8edb6
commit
f32b46a7c5
|
@ -2,11 +2,8 @@
|
|||
import { onMount } from "svelte";
|
||||
import {
|
||||
selectedFilter,
|
||||
addNode,
|
||||
nodes,
|
||||
inputs,
|
||||
output,
|
||||
filters,
|
||||
previewCommand,
|
||||
} from "./stores.js";
|
||||
import Filter from "./Filter.svelte";
|
||||
|
@ -56,7 +53,6 @@
|
|||
// command.push("yuv420p");
|
||||
// command.push("out.mp4");
|
||||
await ffmpeg.exec(clist, TIMEOUT);
|
||||
// await ffmpeg.exec(["-f", "lavfi", "-i", "color=size=1280x720:rate=25:color=red", "-t", "5", "out.mp4"])
|
||||
const data = await ffmpeg.readFile("out.mp4");
|
||||
rendering = false;
|
||||
videoValue = URL.createObjectURL(new Blob([data.buffer], { type: "video/mp4" }));
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<script>
|
||||
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from "@xyflow/svelte";
|
||||
import { auto, removeEdge } from "./stores.js";
|
||||
|
||||
export let id;
|
||||
export let sourceX;
|
||||
export let sourceY;
|
||||
export let targetX;
|
||||
export let targetY;
|
||||
export let sourcePosition;
|
||||
export let targetPosition;
|
||||
export let markerEnd;
|
||||
|
||||
$: positions = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
});
|
||||
|
||||
$: edgePath = positions[0];
|
||||
$: labelX = positions[1];
|
||||
$: labelY = positions[2];
|
||||
|
||||
function onClick(e) {
|
||||
e.stopPropagation();
|
||||
removeEdge(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<BaseEdge path={edgePath} {markerEnd} />
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style="position: absolute; transform: translate(-50%, -50%) translate({labelX}px,{labelY}px); fontSize: 12; pointer-events: all"
|
||||
class="nodrag nopan"
|
||||
>
|
||||
{#if !$auto}
|
||||
<button class="edgebutton" on:click={onClick}>×</button>
|
||||
{/if}
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
|
||||
<style>
|
||||
.edgebutton {
|
||||
padding: 0;
|
||||
top: -3px;
|
||||
position: relative;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: pink;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 8px;
|
||||
line-height: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* .edgebutton:hover { */
|
||||
/* box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.08); */
|
||||
/* } */
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { filters, removeNode } from "./stores.js";
|
||||
import { removeNode } from "./stores.js";
|
||||
|
||||
export let filter = {
|
||||
name: "",
|
||||
|
@ -9,10 +9,6 @@
|
|||
|
||||
let show = true;
|
||||
|
||||
function remove() {
|
||||
removeNode(filter.id);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
for (let p of filter.params) {
|
||||
p.value = null;
|
||||
|
|
|
@ -7,13 +7,18 @@
|
|||
Background,
|
||||
BackgroundVariant,
|
||||
} from "@xyflow/svelte";
|
||||
import Node from "./nodes/Node.svelte";
|
||||
import Node from "./Node.svelte";
|
||||
import FitComp from "./FitComp.svelte";
|
||||
import ButtonEdge from "./ButtonEdge.svelte";
|
||||
|
||||
const nodeTypes = {
|
||||
ffmpeg: Node,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
default: ButtonEdge,
|
||||
};
|
||||
|
||||
function onClick(e) {
|
||||
if (e.detail.nodeType === "filter") {
|
||||
const newSelected = $nodes.findIndex((n) => n.id === e.detail.id);
|
||||
|
@ -23,22 +28,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
function addInput() {
|
||||
addNode({ name: "shoe.mp4" }, "input");
|
||||
}
|
||||
|
||||
function addInput() {
|
||||
addNode({ name: "shoe.mp4" }, "input");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<SvelteFlowProvider>
|
||||
<div class="holder">
|
||||
<FitComp />
|
||||
<div class="nav">
|
||||
<button on:click={addInput}>Add Input</button>
|
||||
<label for="auto"
|
||||
><input id="auto" type="checkbox" bind:checked={$auto} />Automatic Layout</label
|
||||
>
|
||||
</div>
|
||||
<div class="nav">
|
||||
<button on:click={addInput}>Add Input</button>
|
||||
<label for="auto"
|
||||
><input id="auto" type="checkbox" bind:checked={$auto} />Automatic Layout</label
|
||||
>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<div style="height: 100%; width: 100%">
|
||||
<SvelteFlow
|
||||
|
@ -48,8 +52,10 @@
|
|||
connectOnClick={true}
|
||||
nodesFocusable={!auto}
|
||||
edgesFocusable={!auto}
|
||||
deleteKey={0}
|
||||
on:nodeclick={onClick}
|
||||
{nodeTypes}
|
||||
{edgeTypes}
|
||||
{nodes}
|
||||
{edges}
|
||||
snapGrid={[10, 10]}
|
||||
|
@ -67,10 +73,10 @@
|
|||
.holder {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.flow {
|
||||
flex: 1;
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
<script>
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { nodes, edges } from "./stores.js";
|
||||
import { Anchor, Node, Svelvet} from "svelvet";
|
||||
|
||||
function onConnect(e) {
|
||||
console.log(e);
|
||||
const sourceAnchor = e.detail.sourceAnchor;
|
||||
const targetAnchor = e.detail.targetAnchor;
|
||||
const sourceNode = e.detail.sourceNode;
|
||||
const targetNode = e.detail.targetNode;
|
||||
// console.log(e);
|
||||
// console.log(sourceNode.id, "->", targetNode.id)
|
||||
// console.log(sourceAnchor.id, "->", targetAnchor.id)
|
||||
edges.update((eds) => {
|
||||
eds.push({
|
||||
id: uuidv4(),
|
||||
sourceAnchor: sourceAnchor.id,
|
||||
targetAnchor: targetAnchor.id,
|
||||
source: sourceNode.id,
|
||||
target: targetNode.id,
|
||||
});
|
||||
return eds;
|
||||
});
|
||||
}
|
||||
|
||||
function onDisconnect(e) {
|
||||
console.log('dis', e);
|
||||
const sourceAnchor = e.detail.sourceAnchor.id;
|
||||
const targetAnchor = e.detail.targetAnchor.id;
|
||||
const source = e.detail.sourceNode.id;
|
||||
const target = e.detail.targetNode.id;
|
||||
|
||||
// console.log(sourceNode.id, "-/>", targetNode.id)
|
||||
// console.log(sourceAnchor.id, "-/>", targetAnchor.id)
|
||||
|
||||
edges.update((eds) => {
|
||||
const index = eds.findIndex(
|
||||
(e) => e.sourceAnchor === sourceAnchor && e.targetAnchor === targetAnchor
|
||||
);
|
||||
eds.splice(index, 1);
|
||||
return eds;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Svelvet
|
||||
id="my-canvas"
|
||||
width={800}
|
||||
height={500}
|
||||
snapTo={25}
|
||||
on:disconnection={onDisconnect}
|
||||
on:connection={onConnect}
|
||||
>
|
||||
{#each $nodes as n, index}
|
||||
<Node
|
||||
inputs={n.data.inputs.length}
|
||||
outputs={n.data.outputs.length}
|
||||
id={n.id}
|
||||
bind:position={n.position}
|
||||
>
|
||||
<div class="node">
|
||||
<div class="header">
|
||||
{n.data.name}
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="input-anchors">
|
||||
{#each n.data.inputs as inp, index}
|
||||
<Anchor id={inp + "_" + index} let:linked let:connecting let:hovering input>
|
||||
<div class:linked class:hovering class:connecting class="anchor in {inp}">{inp}</div>
|
||||
</Anchor>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="output-anchors">
|
||||
{#each n.data.outputs as out}
|
||||
<Anchor id={out + "_" + index} let:linked let:connecting let:hovering output>
|
||||
<div class:linked class:hovering class:connecting class="anchor in {out}">{out}</div>
|
||||
</Anchor>
|
||||
{/each}
|
||||
</div>
|
||||
</Node>
|
||||
{/each}
|
||||
</Svelvet>
|
||||
|
||||
<style>
|
||||
.node {
|
||||
box-sizing: border-box;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--b1);
|
||||
font: 12px Times, serif;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.output-anchors {
|
||||
position: absolute;
|
||||
right: -16px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.input-anchors {
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.anchor {
|
||||
background-color: #fff;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
line-height: 12px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
font-family: Times, serif;
|
||||
border: solid 1px white;
|
||||
border-color: var(--b1);
|
||||
}
|
||||
.hovering {
|
||||
scale: 1.2;
|
||||
}
|
||||
.linked {
|
||||
background-color: rgb(17, 214, 17) !important;
|
||||
}
|
||||
.connecting {
|
||||
background-color: goldenrod;
|
||||
}
|
||||
</style>
|
|
@ -1,30 +0,0 @@
|
|||
<script>
|
||||
import { removeNode } from "./stores.js";
|
||||
export let filename = "punch.mp4";
|
||||
export let id;
|
||||
export let index;
|
||||
|
||||
function remove() {
|
||||
removeNode(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<select bind:value={filename}>
|
||||
<option value="punch.mp4">punch.mp4</option>
|
||||
<option value="shoe.mp4">shoe.mp4</option>
|
||||
</select>
|
||||
<button on:click={remove}>X</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
}
|
||||
select {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
|
@ -1,63 +0,0 @@
|
|||
<script>
|
||||
export let showModal; // boolean
|
||||
|
||||
let dialog; // HTMLDialogElement
|
||||
|
||||
$: if (dialog && showModal) dialog.showModal();
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions -->
|
||||
<dialog
|
||||
bind:this={dialog}
|
||||
on:close={() => (showModal = false)}
|
||||
on:click|self={() => dialog.close()}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div on:click|stopPropagation>
|
||||
<slot name="header" />
|
||||
<slot />
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<button autofocus on:click={() => dialog.close()}>Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
max-width: 600px;
|
||||
min-width: 600px;
|
||||
border-radius: 0.2em;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
dialog > div {
|
||||
padding: 1em;
|
||||
}
|
||||
dialog[open] {
|
||||
animation: zoom 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
@keyframes zoom {
|
||||
from {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
dialog[open]::backdrop {
|
||||
animation: fade 0.2s ease-out;
|
||||
}
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
button {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Handle, Position } from "@xyflow/svelte";
|
||||
import { removeNode } from "../stores.js";
|
||||
import { removeNode } from "./stores.js";
|
||||
|
||||
export let data = { nodeType: "", name: "", inputs: [], outputs: [] };
|
||||
export let id;
|
||||
|
@ -13,7 +13,9 @@
|
|||
<div class="node {data.nodeType}">
|
||||
<div class="head">
|
||||
<div class="node-type">{data.nodeType}</div>
|
||||
<button on:click={remove}>X</button>
|
||||
{#if data.nodeType != "output"}
|
||||
<button on:click={remove}>X</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if data.nodeType == "input"}
|
|
@ -1,11 +0,0 @@
|
|||
<script>
|
||||
export let filename = "";
|
||||
</script>
|
||||
|
||||
<input bind:value={filename} />
|
||||
|
||||
<style>
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
155
src/stores.js
155
src/stores.js
|
@ -1,16 +1,11 @@
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
import { writable, derived, get } from "svelte/store";
|
||||
|
||||
// export const inputs = writable([]);
|
||||
// export const output = writable("out.mp4");
|
||||
// export const filters = writable([]);
|
||||
export const nodes = writable([]);
|
||||
export const edges = writable([]);
|
||||
export const auto = writable(true);
|
||||
export const selectedFilter = writable();
|
||||
|
||||
const PREFIX = "";
|
||||
|
||||
addNode({ name: "punch.mp4" }, "input");
|
||||
addNode({ name: "out.mp4" }, "output");
|
||||
|
||||
|
@ -29,100 +24,7 @@ function makeFilterArgs(f) {
|
|||
return fCommand;
|
||||
}
|
||||
|
||||
export const previewCommandSvelvet = derived([edges, nodes], ([$edges, $nodes]) => {
|
||||
// [0:v]f1=val,f2=val[out] -map out
|
||||
// [0:v]f1=val,f2=val[1];[1][1:v]overlay[out] -map out`
|
||||
|
||||
let finalCommand = [];
|
||||
|
||||
let filtergraph = [];
|
||||
|
||||
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 = {};
|
||||
for (let i = 0; i < $edges.length; i++) {
|
||||
const e = $edges[i];
|
||||
edgeIds[e.id] = i + 1;
|
||||
|
||||
const source = $nodes.find((n) => PREFIX + n.id === e.source);
|
||||
const target = $nodes.find((n) => PREFIX + n.id === e.target);
|
||||
|
||||
if (!source || !target) continue;
|
||||
|
||||
if (source.nodeType === "input") {
|
||||
if (e.sourceAnchor.startsWith("A-v")) {
|
||||
edgeIds[e.id] = inputIds[source.id] + ":v";
|
||||
}
|
||||
if (e.sourceAnchor.startsWith("A-a")) {
|
||||
edgeIds[e.id] = inputIds[source.id] + ":a";
|
||||
}
|
||||
}
|
||||
|
||||
if (target.nodeType === "output") {
|
||||
edgeIds[e.id] = "out";
|
||||
}
|
||||
}
|
||||
|
||||
for (let n of $nodes.filter((n) => n.nodeType == "filter")) {
|
||||
let cmd = "";
|
||||
|
||||
const outs = $edges.filter((e) => e.source == PREFIX + n.id);
|
||||
const ins = $edges.filter((e) => e.target == PREFIX + n.id);
|
||||
|
||||
if (outs.length == 0 && ins.length == 0) continue;
|
||||
|
||||
for (let i of ins) {
|
||||
const eid = edgeIds[i.id];
|
||||
cmd += `[${eid}]`;
|
||||
}
|
||||
cmd += makeFilterArgs(n.data);
|
||||
for (let o of outs) {
|
||||
const eid = edgeIds[o.id];
|
||||
cmd += `[${eid}]`;
|
||||
}
|
||||
filtergraph.push(cmd);
|
||||
}
|
||||
|
||||
finalCommand.push("ffmpeg");
|
||||
|
||||
for (let inp of inputs) {
|
||||
finalCommand.push("-i");
|
||||
finalCommand.push(inp.data.name);
|
||||
}
|
||||
|
||||
finalCommand.push("-filter_complex");
|
||||
|
||||
finalCommand.push('"' + filtergraph.join(";") + '"');
|
||||
|
||||
for (let out of outputs) {
|
||||
finalCommand.push("-map");
|
||||
finalCommand.push('"[out]"');
|
||||
}
|
||||
|
||||
for (let inp of inputs) {
|
||||
finalCommand.push("-map");
|
||||
finalCommand.push(inputIds[inp.id] + ":a");
|
||||
}
|
||||
|
||||
for (let out of outputs) {
|
||||
finalCommand.push(out.data.name);
|
||||
}
|
||||
|
||||
const entireCommand = finalCommand.join(" ");
|
||||
return entireCommand;
|
||||
});
|
||||
|
||||
export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
||||
// [0:v]f1=val,f2=val[out] -map out
|
||||
// [0:v]f1=val,f2=val[1];[1][1:v]overlay[out] -map out`
|
||||
|
||||
let hasVid = false;
|
||||
let hasAud = false;
|
||||
|
||||
|
@ -213,11 +115,6 @@ export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
|||
}
|
||||
}
|
||||
|
||||
// for (let inp of inputs) {
|
||||
// finalCommand.push("-map");
|
||||
// finalCommand.push(inputIds[inp.id] + ":a");
|
||||
// }
|
||||
|
||||
for (let out of outputs) {
|
||||
finalCommand.push(out.data.name);
|
||||
}
|
||||
|
@ -226,43 +123,11 @@ export const previewCommand = derived([edges, nodes], ([$edges, $nodes]) => {
|
|||
return entireCommand;
|
||||
});
|
||||
|
||||
export const previewCommandOld = derived(nodes, ($nodes) => {
|
||||
const cInputs = $nodes
|
||||
.filter((n) => n.nodeType === "input")
|
||||
.map((i) => `-i ${i.data.name}`)
|
||||
.join(" ");
|
||||
|
||||
const cOutput = $nodes
|
||||
.filter((n) => n.nodeType === "output")
|
||||
.map((i) => `${i.data.name}`)
|
||||
.join(" ");
|
||||
|
||||
const cFilters = $nodes
|
||||
.filter((n) => n.nodeType === "filter")
|
||||
.map((n) => n.data)
|
||||
.map(makeFilterArgs)
|
||||
.join(",");
|
||||
|
||||
let out = `ffmpeg ${cInputs}`;
|
||||
|
||||
if (cFilters) out += ` -filter_complex "${cFilters}"`;
|
||||
|
||||
out += ` ${cOutput}`;
|
||||
return out;
|
||||
});
|
||||
|
||||
export const inputs = derived(nodes, ($nodes) => {
|
||||
return $nodes.filter((n) => n.nodeType === "input").map((n) => n.data);
|
||||
});
|
||||
|
||||
export const filters = derived(nodes, ($nodes) => {
|
||||
return $nodes.filter((n) => n.nodeType === "filter").map((n) => n.data);
|
||||
});
|
||||
|
||||
export const output = derived(nodes, ($nodes) => {
|
||||
return $nodes.filter((n) => n.nodeType === "output").map((n) => n.data);
|
||||
});
|
||||
|
||||
nodes.subscribe(($nodes) => {
|
||||
const isAuto = get(auto);
|
||||
if (!isAuto) return;
|
||||
|
@ -384,8 +249,6 @@ export function addNode(data, type) {
|
|||
}
|
||||
return _nodes;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function removeNode(id) {
|
||||
|
@ -394,14 +257,12 @@ export function removeNode(id) {
|
|||
_nodes.splice(index, 1);
|
||||
return _nodes;
|
||||
});
|
||||
|
||||
edges.update((_edges) => {
|
||||
for (let i = _edges.length - 1; i--; i >= 0) {
|
||||
const e = _edges[i];
|
||||
if ("N-" + e.source === id || "N-" + e.target === id) {
|
||||
_edges.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return _edges;
|
||||
});
|
||||
}
|
||||
|
||||
export function removeEdge(id) {
|
||||
edges.update((_edges) => {
|
||||
const index = _edges.findIndex((e) => e.id === id);
|
||||
_edges.splice(index, 1);
|
||||
return _edges;
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue