fix merge
This commit is contained in:
commit
7eca60a425
|
@ -14,8 +14,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
||||
"@xyflow/svelte": "^0.0.12",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-dnd-action": "^0.9.25",
|
||||
"svelvet": "^8.1.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
|
@ -465,6 +467,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-1.0.8.tgz",
|
||||
"integrity": "sha512-HQ6aJlYpWLq1f9AiApJl0aOIXlJUtuhBOYfSfv5rt3XNYkCBveojtnL6FvOVpJ2gEJ2wqgMW8xOHkLVYAbXghg=="
|
||||
},
|
||||
"node_modules/@svelte-put/shortcut": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-3.0.0.tgz",
|
||||
"integrity": "sha512-nZg3pwpTi9wUsvQPlqOzEsxZcF2jmY5j+VBq/20IUjjd2OpM92XqZAga0PCCjE6OuEobOt58UMnC2QZgOvk0tQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.5.tgz",
|
||||
|
@ -504,12 +512,300 @@
|
|||
"vite": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz",
|
||||
"integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-array": "*",
|
||||
"@types/d3-axis": "*",
|
||||
"@types/d3-brush": "*",
|
||||
"@types/d3-chord": "*",
|
||||
"@types/d3-color": "*",
|
||||
"@types/d3-contour": "*",
|
||||
"@types/d3-delaunay": "*",
|
||||
"@types/d3-dispatch": "*",
|
||||
"@types/d3-drag": "*",
|
||||
"@types/d3-dsv": "*",
|
||||
"@types/d3-ease": "*",
|
||||
"@types/d3-fetch": "*",
|
||||
"@types/d3-force": "*",
|
||||
"@types/d3-format": "*",
|
||||
"@types/d3-geo": "*",
|
||||
"@types/d3-hierarchy": "*",
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-path": "*",
|
||||
"@types/d3-polygon": "*",
|
||||
"@types/d3-quadtree": "*",
|
||||
"@types/d3-random": "*",
|
||||
"@types/d3-scale": "*",
|
||||
"@types/d3-scale-chromatic": "*",
|
||||
"@types/d3-selection": "*",
|
||||
"@types/d3-shape": "*",
|
||||
"@types/d3-time": "*",
|
||||
"@types/d3-time-format": "*",
|
||||
"@types/d3-timer": "*",
|
||||
"@types/d3-transition": "*",
|
||||
"@types/d3-zoom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.6.tgz",
|
||||
"integrity": "sha512-NHkizg870sKYQn45oZT5ItoHqcgRgJD7KAiWZp4Udc6YdrFH2W0tZ2vv4shRHP+SXHoJ1G8B4I1GWb5oQSGypA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-axis": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.3.tgz",
|
||||
"integrity": "sha512-SE3x/pLO/+GIHH17mvs1uUVPkZ3bHquGzvZpPAh4yadRy71J93MJBpgK/xY8l9gT28yTN1g9v3HfGSFeBMmwZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-brush": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.3.tgz",
|
||||
"integrity": "sha512-MQ1/M/B5ifTScHSe5koNkhxn2mhUPqXjGuKjjVYckplAPjP9t2I2sZafb/YVHDwhoXWZoSav+Q726eIbN3qprA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-chord": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.3.tgz",
|
||||
"integrity": "sha512-keuSRwO02c7PBV3JMWuctIfdeJrVFI7RpzouehvBWL4/GGUB3PBNg/9ZKPZAgJphzmS2v2+7vr7BGDQw1CAulw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-contour": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.3.tgz",
|
||||
"integrity": "sha512-x7G/tdDZt4m09XZnG2SutbIuQqmkNYqR9uhDMdPlpJbcwepkEjEWG29euFcgVA1k6cn92CHdDL9Z+fOnxnbVQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-array": "*",
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-delaunay": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
|
||||
"integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-dispatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.3.tgz",
|
||||
"integrity": "sha512-Df7KW3Re7G6cIpIhQtqHin8yUxUHYAqiE41ffopbmU5+FifYUNV7RVyTg8rQdkEagg83m14QtS8InvNb95Zqug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-drag": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.3.tgz",
|
||||
"integrity": "sha512-82AuQMpBQjuXeIX4tjCYfWjpm3g7aGCfx6dFlxX2JlRaiME/QWcHzBsINl7gbHCODA2anPYlL31/Trj/UnjK9A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-dsv": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.2.tgz",
|
||||
"integrity": "sha512-DooW5AOkj4AGmseVvbwHvwM/Ltu0Ks0WrhG6r5FG9riHT5oUUTHz6xHsHqJSVU8ZmPkOqlUEY2obS5C9oCIi2g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
|
||||
"integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-fetch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.3.tgz",
|
||||
"integrity": "sha512-/EsDKRiQkby3Z/8/AiZq8bsuLDo/tYHnNIZkUpSeEHWV7fHUl6QFBjvMPbhkKGk9jZutzfOkGygCV7eR/MkcXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-dsv": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-force": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.5.tgz",
|
||||
"integrity": "sha512-EGG+IWx93ESSXBwfh/5uPuR9Hp8M6o6qEGU7bBQslxCvrdUBQZha/EFpu/VMdLU4B0y4Oe4h175nSm7p9uqFug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-format": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
|
||||
"integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-geo": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.4.tgz",
|
||||
"integrity": "sha512-kmUK8rVVIBPKJ1/v36bk2aSgwRj2N/ZkjDT+FkMT5pgedZoPlyhaG62J+9EgNIgUXE6IIL0b7bkLxCzhE6U4VQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-hierarchy": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.3.tgz",
|
||||
"integrity": "sha512-GpSK308Xj+HeLvogfEc7QsCOcIxkDwLhFYnOoohosEzOqv7/agxwvJER1v/kTC+CY1nfazR0F7gnHo7GE41/fw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
|
||||
"integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-polygon": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz",
|
||||
"integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-quadtree": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz",
|
||||
"integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-random": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz",
|
||||
"integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.4.tgz",
|
||||
"integrity": "sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-scale-chromatic": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz",
|
||||
"integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.6.tgz",
|
||||
"integrity": "sha512-2ACr96USZVjXR9KMD9IWi1Epo4rSDKnUtYn6q2SPhYxykvXTw9vR77lkFNruXVg4i1tzQtBxeDMx0oNvJWbF1w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.2.tgz",
|
||||
"integrity": "sha512-NN4CXr3qeOUNyK5WasVUV8NCSAx/CRVcwcb0BuuS1PiTqwIm6ABi1SyasLZ/vsVCFDArF+W4QiGzSry1eKYQ7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
|
||||
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-time-format": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz",
|
||||
"integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
|
||||
"integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-transition": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.4.tgz",
|
||||
"integrity": "sha512-512a4uCOjUzsebydItSXsHrPeQblCVk8IKjqCUmrlvBWkkVh3donTTxmURDo1YPwIVDh5YVwCAO6gR4sgimCPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-zoom": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.4.tgz",
|
||||
"integrity": "sha512-cqkuY1ah9ZQre2POqjSLcM8g40UVya/qwEUrNYP2/rCVljbmqKCVcv+ebvwhlI5azIbSEL7m+os6n+WlYA43aA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
||||
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@xyflow/svelte": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-0.0.12.tgz",
|
||||
"integrity": "sha512-BXwOauzjInjPBdJb1GaKVCYIxyznQKzuEltnPb3i3/EO9GJHx1xcD/c8aBUBBnAG7EWBKtuiyDGk/EBW8MEmsg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@svelte-put/shortcut": "^3.0.0",
|
||||
"@xyflow/system": "0.0.2",
|
||||
"classcat": "^5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xyflow/system": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.2.tgz",
|
||||
"integrity": "sha512-xtDf35E4Bwmt9IutmhNLna/vMNN3uRA/kwYUYTAifnWSCUaKENBqMOX4xAGNljBJX1yjo7FNlMRgn347raN69Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-drag": "^3.0.1",
|
||||
"@types/d3-selection": "^3.0.3",
|
||||
"@types/d3-zoom": "^3.0.1",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||
|
@ -540,6 +836,12 @@
|
|||
"dequal": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/classcat": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.4.tgz",
|
||||
"integrity": "sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/code-red": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
|
||||
|
@ -566,6 +868,111 @@
|
|||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-drag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-selection": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-transition": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-ease": "1 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"d3-selection": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-zoom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "2 - 3",
|
||||
"d3-transition": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
@ -842,6 +1249,15 @@
|
|||
"svelte": "^3.19.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelvet": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/svelvet/-/svelvet-8.1.0.tgz",
|
||||
"integrity": "sha512-rH67tgb7e2aTBQZBCW+V5hSvulLwvzBiOml9Dzdz2ATkgQr58mHi2WtlQFOOb+eZt6zH/J10a2MataC29Qdpuw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"svelte": ">=3.59.2 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
||||
"@xyflow/svelte": "^0.0.12",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-dnd-action": "^0.9.25",
|
||||
"svelvet": "^8.1.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vite": "^4.4.5"
|
||||
},
|
||||
|
|
260
src/App.svelte
260
src/App.svelte
|
@ -1,10 +1,17 @@
|
|||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { inputs, output, filters } from "./stores.js";
|
||||
import Input from "./Input.svelte";
|
||||
import Output from "./Output.svelte";
|
||||
import {
|
||||
selectedFilter,
|
||||
addNode,
|
||||
nodes,
|
||||
inputs,
|
||||
output,
|
||||
filters,
|
||||
previewCommand,
|
||||
} from "./stores.js";
|
||||
import Filter from "./Filter.svelte";
|
||||
import FilterPicker from "./FilterPicker.svelte";
|
||||
import Graph from "./Graph.svelte";
|
||||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
||||
import { fetchFile, toBlobURL } from "@ffmpeg/util";
|
||||
import { dndzone } from "svelte-dnd-action";
|
||||
|
@ -17,7 +24,7 @@
|
|||
const ffmpeg = new FFmpeg();
|
||||
|
||||
let command = "";
|
||||
let videoValue = "/" + $inputs[0];
|
||||
let videoValue = "/" + $inputs[0].name;
|
||||
let ffmpegLoaded = false;
|
||||
let rendering = false;
|
||||
let log = "";
|
||||
|
@ -25,7 +32,7 @@
|
|||
let commandRef;
|
||||
|
||||
function newInput() {
|
||||
$inputs = [...$inputs, "punch.mp4"];
|
||||
addNode({ name: "punch.mp4" }, "input");
|
||||
}
|
||||
|
||||
function render() {
|
||||
|
@ -42,15 +49,24 @@
|
|||
rendering = true;
|
||||
try {
|
||||
for (let vid of $inputs) {
|
||||
await ffmpeg.writeFile(vid, await fetchFile("/" + vid));
|
||||
await ffmpeg.writeFile(vid.name, await fetchFile("/" + vid.name));
|
||||
}
|
||||
const clist = commandList();
|
||||
let clist = $previewCommand
|
||||
.replaceAll('"', "")
|
||||
.replace("ffmpeg", "")
|
||||
.split(" ")
|
||||
.filter((i) => i.trim() != "");
|
||||
console.log("command", clist);
|
||||
// command.push("-pix_fmt");
|
||||
// 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" }));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
log += "Failed";
|
||||
}
|
||||
rendering = false;
|
||||
|
@ -58,7 +74,6 @@
|
|||
|
||||
async function loadFFmpeg() {
|
||||
ffmpeg.on("log", ({ message: msg }) => {
|
||||
// console.log(msg);
|
||||
log += msg + "\n";
|
||||
logbox.scrollTop = logbox.scrollHeight;
|
||||
});
|
||||
|
@ -78,68 +93,13 @@
|
|||
ffmpegLoaded = true;
|
||||
}
|
||||
|
||||
function updateCommand() {
|
||||
const cInputs = $inputs.map((i) => `-i ${i}`).join(" ");
|
||||
|
||||
const cOutput = $output;
|
||||
|
||||
const cFilters = $filters.map(makeFilterArgs).join(",");
|
||||
|
||||
let out = `ffmpeg ${cInputs}`;
|
||||
|
||||
if (cFilters) out += ` -filter_complex "${cFilters}"`;
|
||||
|
||||
out += ` ${cOutput}`;
|
||||
|
||||
command = out;
|
||||
return out;
|
||||
}
|
||||
|
||||
function makeFilterArgs(f) {
|
||||
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;
|
||||
}
|
||||
|
||||
function commandList() {
|
||||
let command = [];
|
||||
for (let vid of $inputs) {
|
||||
command.push("-i");
|
||||
command.push(vid);
|
||||
}
|
||||
// const audioFilters = $filters.filter(f => f.type[0] === "A").map(makeFilterArgs);
|
||||
// const videoFilters = $filters.filter(f => f.type[0] === "V").map(makeFilterArgs);
|
||||
|
||||
const cFilters = $filters.map(makeFilterArgs).join(",");
|
||||
|
||||
if (cFilters.length > 0) {
|
||||
command.push("-filter_complex");
|
||||
command.push(cFilters);
|
||||
}
|
||||
|
||||
command.push("-pix_fmt");
|
||||
command.push("yuv420p");
|
||||
command.push("out.mp4");
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
function handleFilterSort(e) {
|
||||
filters.set(e.detail.items);
|
||||
}
|
||||
|
||||
inputs.subscribe(updateCommand);
|
||||
output.subscribe(updateCommand);
|
||||
filters.subscribe(updateCommand);
|
||||
// inputs.subscribe(updateCommand);
|
||||
// output.subscribe(updateCommand);
|
||||
// filters.subscribe(updateCommand);
|
||||
|
||||
onMount(async () => {
|
||||
loadFFmpeg();
|
||||
|
@ -149,49 +109,49 @@
|
|||
<main>
|
||||
<section class="header">
|
||||
<h1>FFmpeg Explorer</h1>
|
||||
<p>
|
||||
A tool to help you explore <a href="https://www.ffmpeg.org/" target="_blank">FFmpeg</a>
|
||||
filters and options. To use: select one or more input videos (there are currently two options),
|
||||
export and add some filters, and then hit "render" to preview the output in browser. Note: this
|
||||
is a work in progress, many things may still be broken! Only audio to audio and video to video
|
||||
filters are included. If it hangs/crashes refresh the page. Post issues/feedback to
|
||||
<a href="https://github.com/antiboredom/ffmpeg-explorer/" target="_blank"
|
||||
>GitHub</a
|
||||
>. By <a href="https://lav.io" target="_blank">Sam Lavigne</a>.
|
||||
</p>
|
||||
<div class="help">
|
||||
<p>
|
||||
A tool to help you explore <a href="https://www.ffmpeg.org/" target="_blank">FFmpeg</a>
|
||||
filters. To use:
|
||||
</p>
|
||||
<ol>
|
||||
<li>Add filters from the list on the left.</li>
|
||||
<li>Click on filters in the center panel to edit options.</li>
|
||||
<li>Hit "render" to preview the output in browser.</li>
|
||||
<li>For more complex filtergraphs, disable "automatic layout."</li>
|
||||
</ol>
|
||||
<p>
|
||||
Note: this is a work in progress, many things may still be broken! If it hangs/crashes
|
||||
refresh the page. Post issues/feedback to
|
||||
<a href="https://github.com/antiboredom/ffmpeg-explorer/" target="_blank">GitHub</a>. By
|
||||
<a href="https://lav.io" target="_blank">Sam Lavigne</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<!-- {message} -->
|
||||
<section class="command">
|
||||
<h3>Output Command</h3>
|
||||
<div class="inner-command">
|
||||
<textarea readonly class="actual-command" bind:this={commandRef}>{command}</textarea>
|
||||
<textarea readonly class="actual-command" bind:this={commandRef} on:click={() => commandRef.select()}>{$previewCommand}</textarea>
|
||||
<div>
|
||||
<button on:click={copyCommand}>Copy Command</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="inputs">
|
||||
<div class="section-header">
|
||||
<h3>Inputs</h3>
|
||||
<button on:click={newInput}>Add Input</button>
|
||||
</div>
|
||||
{#each $inputs as inp, index}
|
||||
<Input bind:filename={inp} {index} />
|
||||
{/each}
|
||||
</section>
|
||||
|
||||
<section class="log">
|
||||
<h3>FFmpeg Log</h3>
|
||||
<textarea readonly class="the-log" bind:this={logbox}>{log}</textarea>
|
||||
</section>
|
||||
|
||||
<section class="preview">
|
||||
{#if rendering}
|
||||
<div class="rendering-video"><span>Rendering...</span></div>
|
||||
{/if}
|
||||
<video controls src={videoValue} />
|
||||
<div style="text-align: right;margin-top:5px;">
|
||||
<div class="vid-holder">
|
||||
{#if rendering}
|
||||
<div class="rendering-video"><span>Rendering...</span></div>
|
||||
{/if}
|
||||
<video controls src={videoValue} />
|
||||
</div>
|
||||
<div style="text-align: right;padding-top:5px;">
|
||||
<button on:click={render} disabled={!ffmpegLoaded || rendering}>
|
||||
{#if ffmpegLoaded}
|
||||
{#if rendering}
|
||||
|
@ -206,44 +166,39 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="output">
|
||||
<h3>Output</h3>
|
||||
<Output bind:filename={$output} />
|
||||
</section>
|
||||
|
||||
<section class="filters">
|
||||
<h3>Filters (click to add)</h3>
|
||||
<div class="inner-filters">
|
||||
<div class="filter-picker">
|
||||
<FilterPicker select={"video"} />
|
||||
</div>
|
||||
<div
|
||||
class="filters-holder"
|
||||
use:dndzone={{ items: $filters }}
|
||||
on:consider={handleFilterSort}
|
||||
on:finalize={handleFilterSort}
|
||||
>
|
||||
{#each $filters as f (f.id)}
|
||||
<div class="filter">
|
||||
<Filter bind:filter={f} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="filter-picker">
|
||||
<FilterPicker select={"video"} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph">
|
||||
<Graph />
|
||||
</section>
|
||||
|
||||
<section class="filter-editor">
|
||||
{#if $selectedFilter && $nodes.length > 0 && $nodes[$selectedFilter]}
|
||||
<Filter bind:filter={$nodes[$selectedFilter].data} />
|
||||
{/if}
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: 300px 1fr 1fr 1fr 1fr 300px;
|
||||
grid-template-areas:
|
||||
"hdr cmd cmd"
|
||||
"inp log prv"
|
||||
"out log prv"
|
||||
"flt flt flt";
|
||||
"hdr log log log prv prv"
|
||||
"hdr cmd cmd cmd prv prv"
|
||||
"flt gra gra gra gra edt";
|
||||
/* grid-template-rows: 16% 17% 77%; */
|
||||
grid-template-rows: 15% 15% calc(70% - 40px);
|
||||
padding: 10px;
|
||||
grid-gap: 20px;
|
||||
padding: 20px;
|
||||
height: 100vh;
|
||||
border: 1px solid blue;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
section {
|
||||
|
@ -257,6 +212,7 @@
|
|||
|
||||
.header {
|
||||
grid-area: hdr;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.command {
|
||||
|
@ -265,17 +221,26 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
grid-area: inp;
|
||||
}
|
||||
|
||||
.preview {
|
||||
grid-area: prv;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.output {
|
||||
grid-area: out;
|
||||
.preview video {
|
||||
width: 100%;
|
||||
/* object-fit: contain; */
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vid-holder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.log {
|
||||
|
@ -284,19 +249,25 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inner-filters {
|
||||
.filters {
|
||||
grid-area: flt;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.graph {
|
||||
grid-area: gra;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.filters {
|
||||
grid-area: flt;
|
||||
.filter-editor {
|
||||
grid-area: edt;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.filter-picker {
|
||||
max-height: 500px;
|
||||
width: 400px;
|
||||
|
||||
position: sticky;
|
||||
/* max-height: 500px; */
|
||||
/* width: 400px; */
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: scroll;
|
||||
|
@ -331,14 +302,20 @@
|
|||
.inner-command {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px 0px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
textarea {
|
||||
outline: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.actual-command {
|
||||
border: none;
|
||||
margin-right: 10px;
|
||||
resize: none;
|
||||
flex: 1;
|
||||
font: inherit;
|
||||
padding: 5px;
|
||||
|
@ -353,6 +330,7 @@
|
|||
.the-log {
|
||||
border: none;
|
||||
resize: none;
|
||||
padding: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
.rendering-video {
|
||||
|
@ -368,6 +346,17 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
ol {
|
||||
margin: 5px 0px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
ol li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1400px) {
|
||||
.filters-holder {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
@ -397,9 +386,6 @@
|
|||
padding: 10px;
|
||||
box-shadow: 2px 2px 0px #000;
|
||||
}
|
||||
.inner-filters {
|
||||
display: block;
|
||||
}
|
||||
.filter-picker {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { filters } from "./stores.js";
|
||||
import { filters, removeNode } from "./stores.js";
|
||||
|
||||
export let filter = {
|
||||
name: "",
|
||||
|
@ -7,12 +7,10 @@
|
|||
description: "",
|
||||
};
|
||||
|
||||
let show = false;
|
||||
let show = true;
|
||||
|
||||
function remove() {
|
||||
const index = $filters.findIndex((f) => f.id === filter.id);
|
||||
$filters.splice(index, 1);
|
||||
$filters = $filters;
|
||||
removeNode(filter.id);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
|
@ -29,12 +27,6 @@
|
|||
<div class="filter-holder">
|
||||
<div class="head">
|
||||
<div class="name"><h3>{filter.name}</h3></div>
|
||||
<div>
|
||||
{#if filter.params && filter.params.length > 0}
|
||||
<button on:click={() => (show = !show)}>{show ? "Hide" : "Show"} Options</button>
|
||||
{/if}
|
||||
<button on:click={remove}>X</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="description">
|
||||
|
@ -95,6 +87,8 @@
|
|||
/* border: 1px solid #999; */
|
||||
border: 1px solid var(--b1);
|
||||
/* box-shadow: 5px 5px 0px #000; */
|
||||
overflow-y: scroll;
|
||||
flex: 1;
|
||||
}
|
||||
.filter-holder,
|
||||
input,
|
||||
|
@ -125,7 +119,11 @@
|
|||
}
|
||||
.p-value {
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
input[type="range"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<script>
|
||||
import uFuzzy from "@leeoniya/ufuzzy";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import FILTERS from "./filters.json";
|
||||
import { filters } from "./stores.js";
|
||||
import { addNode } from "./stores.js";
|
||||
|
||||
export let select = "video";
|
||||
$: selectedFilters = selectFilters(select);
|
||||
|
@ -26,15 +25,7 @@
|
|||
}
|
||||
|
||||
function add(f) {
|
||||
const newFilter = { ...f, filterId: f.id, id: uuidv4() };
|
||||
if (f.params) {
|
||||
newFilter.params = f.params.map((p) => {
|
||||
p.value = null;
|
||||
if (p.default != null) p.value = p.default;
|
||||
return p;
|
||||
});
|
||||
}
|
||||
$filters = [...$filters, newFilter];
|
||||
addNode(f, "filter");
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
@ -56,7 +47,7 @@
|
|||
|
||||
<div class="holder">
|
||||
<div class="search">
|
||||
<input placeholder="Search Filters" on:keyup={update} bind:value={q} type="text" />
|
||||
<input placeholder="Search Filters" on:keyup={update} bind:value={q} type="text" /><button on:click={() => {reset(); update();}}>X</button>
|
||||
<select on:change={reset} bind:value={select}>
|
||||
<option value="video">Video Filters</option>
|
||||
<option value="audio">Audio Filters</option>
|
||||
|
@ -82,12 +73,16 @@
|
|||
}
|
||||
.search {
|
||||
display: flex;
|
||||
justify-items: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
button {
|
||||
margin-left: 1px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.type {
|
||||
color: #999;
|
||||
font-size: 0.8em;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<script>
|
||||
import { useSvelteFlow } from "@xyflow/svelte";
|
||||
import {nodes, auto} from './stores.js';
|
||||
|
||||
const { zoomIn, zoomOut, setZoom, fitView, setCenter, setViewport, getViewport, viewport } =
|
||||
useSvelteFlow();
|
||||
|
||||
nodes.subscribe((n) => {
|
||||
if ($auto) fitView();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<script>
|
||||
import { addNode, nodes, edges, auto, selectedFilter } from "./stores.js";
|
||||
import {
|
||||
SvelteFlowProvider,
|
||||
SvelteFlow,
|
||||
Controls,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
} from "@xyflow/svelte";
|
||||
import Node from "./nodes/Node.svelte";
|
||||
import FitComp from "./FitComp.svelte";
|
||||
|
||||
const nodeTypes = {
|
||||
ffmpeg: Node,
|
||||
};
|
||||
|
||||
function onClick(e) {
|
||||
if (e.detail.nodeType === "filter") {
|
||||
const newSelected = $nodes.findIndex((n) => n.id === e.detail.id);
|
||||
if (newSelected > -1) {
|
||||
$selectedFilter = newSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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="flow">
|
||||
<div style="height: 100%; width: 100%">
|
||||
<SvelteFlow
|
||||
nodesConnectable={!auto}
|
||||
panOnDrag={!auto}
|
||||
edgesUpdatable={!auto}
|
||||
connectOnClick={true}
|
||||
nodesFocusable={!auto}
|
||||
edgesFocusable={!auto}
|
||||
on:nodeclick={onClick}
|
||||
{nodeTypes}
|
||||
{nodes}
|
||||
{edges}
|
||||
snapGrid={[10, 10]}
|
||||
fitView
|
||||
>
|
||||
<Controls />
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
</SvelteFlow>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SvelteFlowProvider>
|
||||
|
||||
<style>
|
||||
.holder {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.flow {
|
||||
flex: 1;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,138 @@
|
|||
<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,11 +1,11 @@
|
|||
<script>
|
||||
import { inputs } from "./stores.js";
|
||||
import { removeNode } from "./stores.js";
|
||||
export let filename = "punch.mp4";
|
||||
export let id;
|
||||
export let index;
|
||||
|
||||
function remove() {
|
||||
$inputs.splice(index, 1);
|
||||
$inputs = $inputs;
|
||||
removeNode(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
95
src/app.css
95
src/app.css
|
@ -2,12 +2,12 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
:root {
|
||||
/* --b1: #004dff; */
|
||||
/* --b2: #f19696b3; */
|
||||
--b1: #004dff;
|
||||
--b2: #ffdadab3;
|
||||
/* --b1: #ff0000; */
|
||||
/* --b2: #b6e3f2b3; */
|
||||
/* --b1: #004dff; */
|
||||
/* --b2: #f19696b3; */
|
||||
--b1: #004dff;
|
||||
--b2: #ffdadab3;
|
||||
/* --b1: #ff0000; */
|
||||
/* --b2: #b6e3f2b3; */
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -15,8 +15,8 @@ a {
|
|||
}
|
||||
|
||||
a:hover {
|
||||
background-color: var(--b1);
|
||||
color: #fff;
|
||||
background-color: var(--b1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -24,6 +24,11 @@ body {
|
|||
font:
|
||||
16px Times,
|
||||
serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
}
|
||||
textrea,
|
||||
select,
|
||||
|
@ -32,26 +37,26 @@ button {
|
|||
font: inherit;
|
||||
}
|
||||
|
||||
button, input:not([type="range"]) , select {
|
||||
border: 1px solid var(--b1);
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 0px var(--b2);
|
||||
button,
|
||||
input:not([type="range"]),
|
||||
select {
|
||||
border: 1px solid var(--b1);
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 0px var(--b2);
|
||||
}
|
||||
button:active {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
video {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Removes default focus */
|
||||
|
@ -62,21 +67,21 @@ input[type="range"]:focus {
|
|||
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
|
||||
/* slider track */
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
background-color: var(--b1);
|
||||
border-radius: 0;
|
||||
height: 0.3rem;
|
||||
background-color: var(--b1);
|
||||
border-radius: 0;
|
||||
height: 0.3rem;
|
||||
}
|
||||
|
||||
/* slider thumb */
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; /* Override default look */
|
||||
appearance: none;
|
||||
margin-top: -5px; /* Centers thumb on the track */
|
||||
appearance: none;
|
||||
margin-top: -5px; /* Centers thumb on the track */
|
||||
|
||||
/*custom styles*/
|
||||
background-color: var(--b1);
|
||||
height: 15px;
|
||||
width: 10px;
|
||||
/*custom styles*/
|
||||
background-color: var(--b1);
|
||||
height: 15px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
input[type="range"]:focus::-webkit-slider-thumb {
|
||||
|
@ -86,21 +91,29 @@ input[type="range"]:focus::-webkit-slider-thumb {
|
|||
/******** Firefox styles ********/
|
||||
/* slider track */
|
||||
input[type="range"]::-moz-range-track {
|
||||
background-color: var(--b1);
|
||||
border-radius: 0;
|
||||
height: 0.3rem;
|
||||
background-color: var(--b1);
|
||||
border-radius: 0;
|
||||
height: 0.3rem;
|
||||
}
|
||||
|
||||
/* slider thumb */
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
border: none; /*Removes extra border that FF applies*/
|
||||
border-radius: 0; /*Removes default border-radius that FF applies*/
|
||||
border: none; /*Removes extra border that FF applies*/
|
||||
border-radius: 0; /*Removes default border-radius that FF applies*/
|
||||
|
||||
/*custom styles*/
|
||||
background-color: var(--b1);
|
||||
height: 15px;
|
||||
width: 10px;
|
||||
/*custom styles*/
|
||||
background-color: var(--b1);
|
||||
height: 15px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
input[type="range"]:focus::-moz-range-thumb {
|
||||
}
|
||||
.svelte-flow__node {
|
||||
border-radius: 0px !important;
|
||||
border: 1px solid var(--b1) !important;
|
||||
}
|
||||
|
||||
.svelte-flow__attribution {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<script lang="ts">
|
||||
import { Handle, Position } from "@xyflow/svelte";
|
||||
import { removeNode } from "../stores.js";
|
||||
|
||||
export let data = { nodeType: "", name: "", inputs: [], outputs: [] };
|
||||
export let id;
|
||||
|
||||
function remove() {
|
||||
removeNode(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="node {data.nodeType}">
|
||||
<div class="head">
|
||||
<div class="node-type">{data.nodeType}</div>
|
||||
<button on:click={remove}>X</button>
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if data.nodeType == "input"}
|
||||
<select bind:value={data.name}>
|
||||
<option value="punch.mp4">punch.mp4</option>
|
||||
<option value="shoe.mp4">shoe.mp4</option>
|
||||
</select>
|
||||
{:else}
|
||||
{data.name}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#each data.inputs as inp, index}
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={inp + "_" + index}
|
||||
class="handle {inp}"
|
||||
style="top: {index * 12 + 4}px; left: -7px;">{inp}</Handle
|
||||
>
|
||||
{/each}
|
||||
{#each data.outputs as out, index}
|
||||
<Handle
|
||||
type="source"
|
||||
id={out + "_" + index}
|
||||
position={Position.Right}
|
||||
class="handle {out}"
|
||||
style="top: {index * 12 + 4}px; left: 107%;">{out}</Handle
|
||||
>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
:global(:root) {
|
||||
--edge-color: var(--b1) !important;
|
||||
--edge-color-selected: black;
|
||||
}
|
||||
:global(.svelte-flow__node) {
|
||||
box-shadow: 2px 2px 0px var(--b2);
|
||||
}
|
||||
:global(.svelte-flow__node.selected) {
|
||||
outline: 1px solid var(--b1) !important;
|
||||
}
|
||||
:global(.handle) {
|
||||
width: 10px !important;
|
||||
height: 10px !important;
|
||||
border: 1px solid var(--b1) !important;
|
||||
border-radius: 0px !important;
|
||||
background-color: white !important;
|
||||
font-size: 10px;
|
||||
line-height: 6px;
|
||||
}
|
||||
:global(.handle.a) {
|
||||
/* border: 1px solid var(--b2) !important; */
|
||||
}
|
||||
.node {
|
||||
padding: 5px;
|
||||
}
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.head button {
|
||||
font-size: 10px;
|
||||
line-height: 8px;
|
||||
padding: 2px 2px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.node-type {
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
}
|
||||
.node.input {
|
||||
}
|
||||
.node.filter {
|
||||
}
|
||||
.node.output {
|
||||
}
|
||||
</style>
|
410
src/stores.js
410
src/stores.js
|
@ -1,5 +1,407 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { writable, derived, get } from "svelte/store";
|
||||
|
||||
export const inputs = writable(["punch.mp4"]);
|
||||
export const output = writable("out.mp4");
|
||||
export const filters = writable([]);
|
||||
// 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");
|
||||
|
||||
function makeFilterArgs(f) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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) => n.id === e.source);
|
||||
const target = $nodes.find((n) => n.id === e.target);
|
||||
|
||||
if (source && target) {
|
||||
|
||||
if (source.nodeType === "input") {
|
||||
if (e.sourceHandle.includes("v")) {
|
||||
edgeIds[e.id] = inputIds[source.id] + ":v";
|
||||
}
|
||||
if (e.sourceHandle.includes("a")) {
|
||||
edgeIds[e.id] = inputIds[source.id] + ":a";
|
||||
}
|
||||
}
|
||||
|
||||
if (target.nodeType === "output") {
|
||||
const outType = e.targetHandle.includes("a") ? "aud_out" : "vid_out";
|
||||
edgeIds[e.id] = outType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let n of $nodes.filter((n) => n.nodeType == "filter")) {
|
||||
let 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];
|
||||
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);
|
||||
}
|
||||
|
||||
if (filtergraph.length > 0) {
|
||||
const fg = '"' + filtergraph.join(";") + '"';
|
||||
hasVid = fg.includes(":v]");
|
||||
hasAud = fg.includes(":a]");
|
||||
|
||||
finalCommand.push("-filter_complex");
|
||||
finalCommand.push(fg);
|
||||
|
||||
finalCommand.push("-map");
|
||||
if (hasAud) {
|
||||
finalCommand.push('"[aud_out]"');
|
||||
} else {
|
||||
finalCommand.push("0:a");
|
||||
}
|
||||
|
||||
if (hasVid) {
|
||||
finalCommand.push("-map");
|
||||
finalCommand.push('"[vid_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 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;
|
||||
|
||||
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]);
|
||||
}
|
||||
// console.log("new", newEdges);
|
||||
edges.set(newEdges);
|
||||
});
|
||||
|
||||
export function addNode(data, type) {
|
||||
let ins = [];
|
||||
let outs = [];
|
||||
|
||||
if (type === "input") {
|
||||
outs = ["v", "a"];
|
||||
} else if (type === "output") {
|
||||
ins = ["v", "a"];
|
||||
} else if (type === "filter") {
|
||||
const [_ins, _outs] = data.type.split("->");
|
||||
ins = _ins.toLowerCase().split("");
|
||||
outs = _outs.toLowerCase().split("");
|
||||
if (data.params) {
|
||||
data.params = data.params.map((p) => {
|
||||
p.value = null;
|
||||
if (p.default != null) p.value = p.default;
|
||||
return p;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for (let n of _nodes) {
|
||||
if (n.nodeType === "filter") {
|
||||
let _w = prev && prev.width ? prev.width : w;
|
||||
n.position = { x: prev ? prev.position.x + _w + margin : 0, y: -50 };
|
||||
prev = n;
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.nodeType === "filter") {
|
||||
selectedFilter.set(_nodes.length - 1);
|
||||
}
|
||||
return _nodes;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function removeNode(id) {
|
||||
nodes.update((_nodes) => {
|
||||
const index = _nodes.findIndex((n) => n.id === 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;
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue