anycoder-db827fbd / index.html
HI7RAI's picture
Upload folder using huggingface_hub
2db0892 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Processing Workflow</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-dark: #1a1a1f;
--bg-card: #252530;
--bg-input: #1e1e28;
--text-primary: #e0e0e0;
--text-secondary: #888;
--border-color: #3a3a4a;
--accent: #6366f1;
--accent-hover: #818cf8;
--node-header-height: 28px;
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: var(--bg-dark);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
}
/* Header */
.header {
background: linear-gradient(135deg, #252530 0%, #1a1a1f 100%);
padding: 12px 24px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border-color);
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 100;
position: relative;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.logo {
font-size: 20px;
font-weight: 700;
background: linear-gradient(135deg, #6366f1, #a855f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.logo a {
text-decoration: none;
color: inherit;
}
.anycoder-link {
font-size: 12px;
color: var(--text-secondary);
text-decoration: none;
padding: 4px 10px;
background: rgba(99, 102, 241, 0.1);
border-radius: 12px;
border: 1px solid rgba(99, 102, 241, 0.3);
transition: all 0.3s ease;
}
.anycoder-link:hover {
background: rgba(99, 102, 241, 0.2);
border-color: rgba(99, 102, 241, 0.5);
}
.header-title {
font-size: 14px;
color: var(--text-secondary);
}
.header-actions {
display: flex;
gap: 12px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
transform: translateY(-1px);
}
.btn-secondary {
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--border-color);
}
/* Toolbar */
.toolbar {
background: var(--bg-card);
padding: 8px 16px;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid var(--border-color);
}
.toolbar-btn {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.toolbar-btn:hover {
background: var(--border-color);
color: var(--text-primary);
}
.toolbar-btn.active {
background: var(--accent);
color: white;
}
.toolbar-divider {
width: 1px;
height: 24px;
background: var(--border-color);
margin: 0 8px;
}
/* Main Workspace */
.workspace {
display: flex;
height: calc(100vh - 100px);
}
/* Sidebar */
.sidebar {
width: 260px;
background: var(--bg-card);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
transition: transform 0.3s ease;
}
.sidebar.collapsed {
transform: translateX(-260px);
}
.sidebar-header {
padding: 16px;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar-content {
flex: 1;
overflow-y: auto;
padding: 12px;
}
.node-template {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
cursor: grab;
transition: all 0.2s ease;
}
.node-template:hover {
border-color: var(--accent);
transform: translateX(4px);
}
.node-template-name {
font-weight: 500;
font-size: 13px;
margin-bottom: 4px;
}
.node-template-type {
font-size: 11px;
color: var(--text-secondary);
}
/* Canvas Area */
.canvas-container {
flex: 1;
position: relative;
overflow: hidden;
background:
radial-gradient(circle at 50% 50%, rgba(99, 102, 241, 0.03) 0%, transparent 50%),
linear-gradient(rgba(40, 40, 55, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(40, 40, 55, 0.3) 1px, transparent 1px);
background-size: 100% 100%, 30px 30px, 30px 30px;
}
.canvas {
position: absolute;
width: 100%;
height: 100%;
transform-origin: 0 0;
}
/* Groups */
.group {
position: absolute;
border: 2px dashed;
border-radius: 8px;
pointer-events: none;
}
.group-title {
position: absolute;
top: -20px;
left: 8px;
font-size: 12px;
font-weight: 600;
padding: 2px 8px;
border-radius: 4px;
white-space: nowrap;
}
/* Nodes */
.node {
position: absolute;
background: var(--bg-card);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
border: 1px solid var(--border-color);
min-width: 200px;
cursor: move;
transition: box-shadow 0.2s ease, border-color 0.2s ease;
user-select: none;
}
.node:hover {
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.5);
}
.node.selected {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3), 0 6px 30px rgba(0, 0, 0, 0.5);
}
.node-header {
height: var(--node-header-height);
padding: 0 12px;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 8px 8px 0 0;
font-size: 12px;
font-weight: 600;
}
.node-title {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.node-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.2s ease;
}
.node:hover .node-actions {
opacity: 1;
}
.node-action-btn {
width: 20px;
height: 20px;
border: none;
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease;
}
.node-action-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.node-action-btn.close:hover {
background: #ef4444;
}
.node-content {
padding: 8px 12px 12px;
}
/* Slots */
.node-slots {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.slots-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.slot {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
min-width: 100px;
}
.slot:hover {
background: rgba(255, 255, 255, 0.05);
}
.slot.input {
justify-content: flex-start;
}
.slot.output {
justify-content: flex-end;
flex-direction: row-reverse;
}
.slot-socket {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid;
flex-shrink: 0;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.slot-socket:hover {
transform: scale(1.3);
box-shadow: 0 0 10px currentColor;
}
.slot.input .slot-socket {
background: var(--bg-input);
}
.slot.output .slot-socket {
background: var(--bg-input);
}
.slot-label {
font-size: 11px;
color: var(--text-secondary);
white-space: nowrap;
}
.slot-type {
font-size: 9px;
padding: 1px 4px;
border-radius: 3px;
opacity: 0.7;
}
/* Widgets */
.node-widgets {
display: flex;
flex-direction: column;
gap: 8px;
}
.widget {
display: flex;
flex-direction: column;
gap: 4px;
}
.widget-label {
font-size: 10px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.widget input[type="text"],
.widget input[type="number"] {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 6px 10px;
color: var(--text-primary);
font-size: 12px;
width: 100%;
transition: border-color 0.2s ease;
}
.widget input:focus {
outline: none;
border-color: var(--accent);
}
.widget input[type="range"] {
width: 100%;
height: 4px;
border-radius: 2px;
background: var(--bg-input);
-webkit-appearance: none;
appearance: none;
cursor: pointer;
}
.widget input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
transition: transform 0.2s ease;
}
.widget input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
.widget select {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 6px 10px;
color: var(--text-primary);
font-size: 12px;
width: 100%;
cursor: pointer;
}
.widget button {
background: var(--accent);
border: none;
border-radius: 4px;
padding: 8px 16px;
color: white;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.widget button:hover {
background: var(--accent-hover);
}
.widget button:active {
transform: scale(0.98);
}
.widget-checkbox {
display: flex;
align-items: center;
gap: 8px;
}
.widget-checkbox input {
width: 16px;
height: 16px;
accent-color: var(--accent);
cursor: pointer;
}
.widget-checkbox label {
font-size: 12px;
cursor: pointer;
}
/* Connections SVG */
.connections-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.connection-path {
fill: none;
stroke-width: 2;
stroke-linecap: round;
transition: stroke-width 0.2s ease;
}
.connection-path:hover {
stroke-width: 3;
}
/* Mini Map */
.minimap {
position: absolute;
bottom: 20px;
right: 20px;
width: 200px;
height: 150px;
background: rgba(37, 37, 48, 0.9);
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
z-index: 50;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.minimap-canvas {
width: 100%;
height: 100%;
}
/* Properties Panel */
.properties-panel {
width: 300px;
background: var(--bg-card);
border-left: 1px solid var(--border-color);
display: flex;
flex-direction: column;
transition: transform 0.3s ease;
}
.properties-panel.collapsed {
transform: translateX(300px);
}
.properties-header {
padding: 16px;
border-bottom: 1px solid var(--border-color);
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
.properties-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.property-item {
margin-bottom: 16px;
}
.property-label {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 6px;
}
.property-value {
font-size: 14px;
padding: 8px 12px;
background: var(--bg-input);
border-radius: 4px;
border: 1px solid var(--border-color);
}
/* Status Bar */
.status-bar {
background: var(--bg-card);
padding: 6px 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--border-color);
font-size: 11px;
color: var(--text-secondary);
}
.status-items {
display: flex;
gap: 16px;
}
.status-item {
display: flex;
align-items: center;
gap: 6px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
}
/* Preview Panel */
.preview-panel {
position: absolute;
top: 20px;
right: 20px;
width: 320px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 8px;
z-index: 50;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
display: none;
}
.preview-panel.visible {
display: block;
}
.preview-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 600;
}
.preview-close {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 14px;
}
.preview-content {
padding: 16px;
}
.preview-canvas {
width: 100%;
aspect-ratio: 16/9;
background: var(--bg-input);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
}
.preview-controls {
display: flex;
gap: 8px;
margin-top: 12px;
}
/* Animations */
@keyframes nodeAppear {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.node {
animation: nodeAppear 0.3s ease;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-dark);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
/* Context Menu */
.context-menu {
position: fixed;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
padding: 8px 0;
z-index: 1000;
min-width: 180px;
display: none;
}
.context-menu.visible {
display: block;
}
.context-menu-item {
padding: 8px 16px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
transition: background 0.2s ease;
}
.context-menu-item:hover {
background: var(--border-color);
}
.context-menu-item i {
width: 16px;
color: var(--text-secondary);
}
.context-menu-divider {
height: 1px;
background: var(--border-color);
margin: 4px 0;
}
/* Tooltip */
.tooltip {
position: fixed;
background: var(--bg-dark);
border: 1px solid var(--border-color);
padding: 6px 10px;
border-radius: 4px;
font-size: 11px;
z-index: 1000;
pointer-events: none;
display: none;
}
.tooltip.visible {
display: block;
}
/* Responsive */
@media (max-width: 1024px) {
.sidebar {
position: absolute;
z-index: 50;
height: calc(100vh - 100px);
}
.properties-panel {
position: absolute;
right: 0;
height: calc(100vh - 100px);
}
}
@media (max-width: 768px) {
.header {
padding: 8px 16px;
}
.logo {
font-size: 16px;
}
.anycoder-link {
display: none;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-left">
<div class="logo">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">🎬 VideoFlow</a>
</div>
<span class="header-title">Video Processing Workflow</span>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
</div>
<div class="header-actions">
<button class="btn btn-secondary" onclick="exportWorkflow()">
<i class="fas fa-download"></i> Export
</button>
<button class="btn btn-secondary" onclick="importWorkflow()">
<i class="fas fa-upload"></i> Import
</button>
<button class="btn btn-primary" onclick="runWorkflow()">
<i class="fas fa-play"></i> Run
</button>
</div>
</header>
<!-- Toolbar -->
<div class="toolbar">
<button class="toolbar-btn" title="Zoom In" onclick="zoomIn()">
<i class="fas fa-plus"></i>
</button>
<button class="toolbar-btn" title="Zoom Out" onclick="zoomOut()">
<i class="fas fa-minus"></i>
</button>
<button class="toolbar-btn" title="Fit View" onclick="fitView()">
<i class="fas fa-expand"></i>
</button>
<button class="toolbar-btn" title="Grid" id="gridToggle" onclick="toggleGrid()">
<i class="fas fa-th"></i>
</button>
<div class="toolbar-divider"></div>
<button class="toolbar-btn" title="Add Node" onclick="showNodePalette()">
<i class="fas fa-plus-circle"></i>
</button>
<button class="toolbar-btn" title="Clear All" onclick="clearAll()">
<i class="fas fa-trash-alt"></i>
</button>
<div class="toolbar-divider"></div>
<button class="toolbar-btn" title="Play/Pause" id="playPauseBtn" onclick="togglePlayback()">
<i class="fas fa-play"></i>
</button>
<button class="toolbar-btn" title="Preview" onclick="togglePreview()">
<i class="fas fa-eye"></i>
</button>
</div>
<!-- Main Workspace -->
<div class="workspace">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<i class="fas fa-cube"></i> Node Library
</div>
<div class="sidebar-content">
<div class="node-template" draggable="true" data-type="VideoInput">
<div class="node-template-name"><i class="fas fa-video"></i> Video Input</div>
<div class="node-template-type">Input source for video files</div>
</div>
<div class="node-template" draggable="true" data-type="PrimitiveNode">
<div class="node-template-name"><i class="fas fa-hashtag"></i> Primitive</div>
<div class="node-template-type">Basic value input node</div>
</div>
<div class="node-template" draggable="true" data-type="VideoFrameSampler">
<div class="node-template-name"><i class="fas fa-images"></i> Frame Sampler</div>
<div class="node-template-type">Extract frames from video</div>
</div>
<div class="node-template" draggable="true" data-type="CanvasAutoCrop">
<div class="node-template-name"><i class="fas fa-crop-alt"></i> Auto Crop</div>
<div class="node-template-type">Automatically crop images</div>
</div>
<div class="node-template" draggable="true" data-type="VideoMatchMixer">
<div class="node-template-name"><i class="fas fa-layer-group"></i> Match Mixer</div>
<div class="node-template-type">Blend frames with formula</div>
</div>
<div class="node-template" draggable="true" data-type="CanvasEffect">
<div class="node-template-name"><i class="fas fa-magic"></i> Canvas Effect</div>
<div class="node-template-type">Apply image effects</div>
</div>
<div class="node-template" draggable="true" data-type="SaveVideo">
<div class="node-template-name"><i class="fas fa-save"></i> Save Video</div>
<div class="node-template-type">Export processed video</div>
</div>
<div class="node-template" draggable="true" data-type="PreviewVideo">
<div class="node-template-name"><i class="fas fa-play-circle"></i> Preview</div>
<div class="node-template-type">Preview output frames</div>
</div>
<div class="node-template" draggable="true" data-type="JavaScriptExecute">
<div class="node-template-name"><i class="fab fa-js"></i> JS Controller</div>
<div class="node-template-type">JavaScript execution</div>
</div>
<div class="node-template" draggable="true" data-type="CanvasDisplay">
<div class="node-template-name"><i class="fas fa-desktop"></i> Canvas Display</div>
<div class="node-template-type">Display processed frames</div>
</div>
</div>
</aside>
<!-- Canvas -->
<div class="canvas-container" id="canvasContainer">
<div class="canvas" id="canvas">
<svg class="connections-svg" id="connectionsSvg"></svg>
<div class="groups" id="groups"></div>
<div class="nodes" id="nodes"></div>
</div>
<!-- Mini Map -->
<div class="minimap" id="minimap">
<canvas class="minimap-canvas" id="minimapCanvas"></canvas>
</div>
<!-- Preview Panel -->
<div class="preview-panel" id="previewPanel">
<div class="preview-header">
<span>Preview Output</span>
<button class="preview-close" onclick="togglePreview()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="preview-content">
<div class="preview-canvas" id="previewCanvas">
<i class="fas fa-film" style="font-size: 48px; margin-bottom: 8px;"></i>
<div>Preview will appear here</div>
</div>
<div class="preview-controls">
<button class="btn btn-primary" style="flex: 1;" onclick="playPreview()">
<i class="fas fa-play"></i> Play
</button>
<button class="btn btn-secondary" onclick="pausePreview()">
<i class="fas fa-pause"></i>
</button>
<button class="btn btn-secondary" onclick="stopPreview()">
<i class="fas fa-stop"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Properties Panel -->
<aside class="properties-panel" id="propertiesPanel">
<div class="properties-header">
<span>Properties</span>
<button class="toolbar-btn" onclick="togglePropertiesPanel()">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="properties-content" id="propertiesContent">
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">
<i class="fas fa-info-circle" style="font-size: 24px; margin-bottom: 8px;"></i>
<div>Select a node to view properties</div>
</div>
</div>
</aside>
</div>
<!-- Status Bar -->
<footer class="status-bar">
<div class="status-items">
<div class="status-item">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Ready</span>
</div>
<div class="status-item">
<i class="fas fa-mouse-pointer"></i>
<span id="cursorPos">0, 0</span>
</div>
</div>
<div class="status-items">
<div class="status-item">
<i class="fas fa-layer-group"></i>
<span id="nodeCount">0 nodes</span>
</div>
<div class="status-item">
<i class="fas fa-link"></i>
<span id="linkCount">0 links</span>
</div>
<div class="status-item">
<i class="fas fa-search"></i>
<span id="zoomLevel">100%</span>
</div>
</div>
</footer>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="duplicateSelected()">
<i class="fas fa-copy"></i> Duplicate
</div>
<div class="context-menu-item" onclick="copySelected()">
<i class="fas fa-cut"></i> Copy
</div>
<div class="context-menu-item" onclick="pasteSelected()">
<i class="fas fa-paste"></i> Paste
</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" onclick="bringToFront()">
<i class="fas fa-arrow-up"></i> Bring to Front
</div>
<div class="context-menu-item" onclick="sendToBack()">
<i class="fas fa-arrow-down"></i> Send to Back
</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" onclick="deleteSelected()">
<i class="fas fa-trash"></i> Delete
</div>
</div>
<!-- Tooltip -->
<div class="tooltip" id="tooltip"></div>
<script>
// Workflow Data
let workflowData = null;
let nodes = [];
let links = [];
let groups = [];
let selectedNodes = [];
let currentZoom = 1;
let panOffset = { x: 0, y: 0 };
let isDragging = false;
let isPanning = false;
let dragStart = { x: 0, y: 0 };
let draggedNode = null;
let connectionStart = null;
let clipboard = null;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadWorkflowFromJSON();
setupEventListeners();
startRenderLoop();
});
// Load workflow from JSON
function loadWorkflowFromJSON() {
const jsonData = `<?json_output>`;
try {
workflowData = JSON.parse(jsonData.replace(/```json|```/g, '').trim());
if (workflowData.nodes) {
nodes = workflowData.nodes.map(node => ({
...node,
x: node.pos[0],
y: node.pos[1]
}));
}
if (workflowData.links) {
links = workflowData.links.map(link => ({
source: link[2],
target: link[0],
sourceOutput: link[3],
targetInput: link[1]
}));
}
if (workflowData.groups) {
groups = workflowData.groups;
}
updateStats();
renderAll();
} catch (e) {
console.error('Failed to load workflow:', e);
// Use default workflow data
initializeDefaultWorkflow();
}
}
function initializeDefaultWorkflow() {
// Default nodes
nodes = [
{
id: 10,
type: 'JavaScriptExecute',
title: 'JS Controller',
x: 100,
y: 100,
width: 400,
height: 150,
color: '#442266',
bgcolor: '#553377',
inputs: [],
outputs: [{ name: 'trigger', type: 'EVENT', slotIndex: 0 }],
widgets: [
{ name: 'video_button', label: 'Video Input Button', type: 'button', value: 'Upload Video' },
{ name: 'fps_input', label: 'FPS Input', type: 'number', value: 30, min: 1, max: 120 },
{ name: 'formula_input', label: 'JS Math Formula', type: 'text', value: 'Math.sin(x) * Math.cos(y)' },
{ name: 'rematch_button', label: 'Rematch Button', type: 'button', value: 'Rematch' }
]
},
{
id: 1,
type: 'VideoInput',
title: 'VideoInput',
x: 100,
y: 300,
width: 315,
height: 106,
color: '#323224',
bgcolor: '#434330',
inputs: [],
outputs: [{ name: 'VIDEO', type: 'VIDEO', slotIndex: 0 }],
widgets: [
{ name: 'video', label: 'Select Video', type: 'file' }
]
},
{
id: 2,
type: 'PrimitiveNode',
title: 'FPS_Input',
x: 100,
y: 450,
width: 200,
height: 50,
color: '#223322',
bgcolor: '#334433',
inputs: [],
outputs: [{ name: 'value', type: 'FLOAT', slotIndex: 0 }],
widgets: [
{ name: 'value', label: 'FPS', type: 'number', value: 30, min: 1, max: 120 }
]
},
{
id: 3,
type: 'VideoFrameSampler',
title: 'VideoFrameSampler',
x: 500,
y: 300,
width: 315,
height: 150,
color: '#222244',
bgcolor: '#333355',
inputs: [
{ name: 'video', type: 'VIDEO' },
{ name: 'fps', type: 'FLOAT' }
],
outputs: [
{ name: 'frames', type: 'IMAGE', slotIndex: 0 },
{ name: 'frame_count', type: 'INT', slotIndex: 1 }
],
widgets: [
{ name: 'start_frame', label: 'Start Frame', type: 'number', value: 0 },
{ name: 'max_frames', label: 'Max Frames', type: 'number', value: 100 }
]
},
{
id: 4,
type: 'PrimitiveNode',
title: 'Math_Formula_Input',
x: 500,
y: 500,
width: 280,
height: 50,
color: '#442222',
bgcolor: '#553333',
inputs: [],
outputs: [{ name: 'value', type: 'STRING', slotIndex: 0 }],
widgets: [
{ name: 'value', label: 'Math Formula (JS)', type: 'text', value: 'x * y' }
]
},
{
id: 5,
type: 'CanvasAutoCrop',
title: 'CanvasAutoCrop',
x: 900,
y: 300,
width: 315,
height: 120,
color: '#224422',
bgcolor: '#335533',
inputs: [{ name: 'images', type: 'IMAGE' }],
outputs: [
{ name: 'cropped_images', type: 'IMAGE', slotIndex: 0 },
{ name: 'crop_coords', type: 'INT', slotIndex: 1 }
],
widgets: [
{ name: 'canvas_width', label: 'Canvas Width', type: 'number', value: 512, step: 8 },
{ name: 'canvas_height', label: 'Canvas Height', type: 'number', value: 512, step: 8 },
{ name: 'crop_mode', label: 'Crop Mode', type: 'select', value: 'center', options: ['center', 'top', 'bottom', 'left', 'right'] }
]
},
{
id: 6,
type: 'VideoMatchMixer',
title: 'VideoMatchMixer',
x: 1300,
y: 300,
width: 350,
height: 200,
color: '#332266',
bgcolor: '#443377',
inputs: [
{ name: 'frames', type: 'IMAGE' },
{ name: 'formula', type: 'STRING' }
],
outputs: [
{ name: 'mixed_frames', type: 'IMAGE', slotIndex: 0 },
{ name: 'match_data', type: 'DATA', slotIndex: 1 }
],
widgets: [
{ name: 'match_strength', label: 'Match Strength', type: 'range', value: 0.5, min: 0, max: 1, step: 0.01 },
{ name: 'mix_mode', label: 'Mix Mode', type: 'select', value: 'multiply', options: ['multiply', 'screen', 'overlay', 'soft_light', 'hard_light'] },
{ name: 'rematch_on_click', label: 'Enable Rematch on Click', type: 'checkbox', value: true }
]
},
{
id: 7,
type: 'CanvasEffect',
title: 'CanvasEffect',
x: 1700,
y: 300,
width: 315,
height: 180,
color: '#224466',
bgcolor: '#335577',
inputs: [{ name: 'images', type: 'IMAGE' }],
outputs: [{ name: 'processed', type: 'IMAGE', slotIndex: 0 }],
widgets: [
{ name: 'brightness', label: 'Brightness', type: 'range', value: 0, min: -1, max: 1, step: 0.1 },
{ name: 'contrast', label: 'Contrast', type: 'range', value: 1, min: 0, max: 3, step: 0.1 },
{ name: 'saturation', label: 'Saturation', type: 'range', value: 1, min: 0, max: 3, step: 0.1 },
{ name: 'hue_shift', label: 'Hue Shift', type: 'range', value: 0, min: 0, max: 360, step: 1 }
]
},
{
id: 8,
type: 'SaveVideo',
title: 'SaveVideo',
x: 2100,
y: 300,
width: 315,
height: 140,
color: '#224422',
bgcolor: '#335533',
inputs: [
{ name: 'images', type: 'IMAGE' },
{ name: 'fps', type: 'FLOAT' }
],
outputs: [{ name: 'output_path', type: 'STRING', slotIndex: 0 }],
widgets: [
{ name: 'output_path', label: 'Output Path', type: 'text', value: './output/video_output.mp4' },
{ name: 'codec', label: 'Codec', type: 'select', value: 'libx264', options: ['libx264', 'libx265', 'vp9'] },
{ name: 'quality', label: 'CRF Quality', type: 'number', value: 23, min: 0, max: 51 }
]
},
{
id: 9,
type: 'PreviewVideo',
title: 'PreviewVideo',
x: 2100,
y: 500,
width: 315,
height: 80,
color: '#333333',
bgcolor: '#444444',
inputs: [{ name: 'images', type: 'IMAGE' }],
outputs: [],
widgets: []
},
{
id: 11,
type: 'CanvasDisplay',
title: 'CanvasDisplay',
x: 1700,
y: 550,
width: 350,
height: 200,
color: '#224444',
bgcolor: '#335555',
inputs: [{ name: 'images', type: 'IMAGE' }],
outputs: [{ name: 'canvas_data', type: 'DATA', slotIndex: 0 }],
widgets: [
{ name: 'auto_play', label: 'Auto Play', type: 'checkbox', value: true },
{ name: 'loop', label: 'Loop', type: 'checkbox', value: true }
]
}
];
links = [
{ source: 1, target: 3, sourceOutput: 'VIDEO', targetInput: 'video' },
{ source: 2, target: 3, sourceOutput: 'value', targetInput: 'fps' },
{ source: 2, target: 8, sourceOutput: 'value', targetInput: 'fps' },
{ source: 3, target: 5, sourceOutput: 'frames', targetInput: 'images' },
{ source: 4, target: 6, sourceOutput: 'value', targetInput: 'formula' },
{ source: 5, target: 6, sourceOutput: 'cropped_images', targetInput: 'frames' },
{ source: 6, target: 7, sourceOutput: 'mixed_frames', targetInput: 'images' },
{ source: 7