clean up and add delete edges

This commit is contained in:
Sam Lavigne 2023-08-25 14:03:32 -04:00
parent 79ddc8edb6
commit f32b46a7c5
14 changed files with 95 additions and 413 deletions

View File

@ -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" }));

63
src/ButtonEdge.svelte Normal file
View File

@ -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>

View File

@ -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;

View File

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"}

View File

@ -1,11 +0,0 @@
<script>
export let filename = "";
</script>
<input bind:value={filename} />
<style>
input {
width: 100%;
}
</style>

View File

@ -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;
});
}