anycoder-2017e85e / index.html
HI7RAI's picture
Upload folder using huggingface_hub
36e4916 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pro Video Aligner & Cutter</title>
<style>
:root {
--bg-dark: #0a0a0f;
--bg-panel: #16161e;
--accent: #00f2ff;
--accent-secondary: #bc13fe;
--text-main: #e0e0e0;
--text-dim: #888;
--grid-color: rgba(0, 242, 255, 0.15);
--border: 1px solid rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
body {
background-color: var(--bg-dark);
color: var(--text-main);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Header */
header {
padding: 15px 20px;
background: var(--bg-panel);
border-bottom: var(--border);
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
}
h1 {
font-size: 1.2rem;
letter-spacing: 1px;
text-transform: uppercase;
background: linear-gradient(90deg, var(--accent), var(--accent-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.anycoder-link a {
color: var(--text-dim);
text-decoration: none;
font-size: 0.85rem;
transition: color 0.3s;
}
.anycoder-link a:hover {
color: var(--accent);
}
/* Main Layout */
.workspace {
display: grid;
grid-template-columns: 350px 1fr;
height: calc(100vh - 60px);
}
/* Sidebar Controls */
.sidebar {
background: var(--bg-panel);
border-right: var(--border);
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.control-group {
background: rgba(0,0,0,0.2);
padding: 15px;
border-radius: 8px;
border: var(--border);
}
.control-group h3 {
font-size: 0.8rem;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 12px;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 5px;
}
.input-row {
margin-bottom: 10px;
}
label {
display: block;
font-size: 0.8rem;
margin-bottom: 5px;
}
input[type="range"] {
width: 100%;
background: transparent;
cursor: pointer;
}
input[type="file"] {
font-size: 0.8rem;
color: var(--text-dim);
width: 100%;
}
input[type="number"], input[type="text"] {
background: #222;
border: 1px solid #444;
color: var(--accent);
padding: 5px;
width: 100%;
border-radius: 4px;
}
.math-display {
font-family: 'Courier New', monospace;
color: var(--accent-secondary);
font-weight: bold;
text-align: center;
padding: 10px;
background: rgba(188, 19, 254, 0.1);
border-radius: 4px;
}
/* Stage / Canvas Area */
.stage {
position: relative;
display: flex;
flex-direction: column;
background: #000;
overflow: hidden;
justify-content: center;
align-items: center;
}
.canvas-container {
position: relative;
max-width: 90%;
max-height: 70vh;
box-shadow: 0 0 30px rgba(0,0,0,0.5);
}
canvas {
display: block;
max-width: 100%;
max-height: 100%;
}
/* Raster Overlay */
.raster-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 20px 20px; /* Dynamic via JS */
border: 1px solid var(--accent);
opacity: 0.5;
display: none; /* Toggled via JS */
}
/* Timeline / Scenes */
.timeline-area {
height: 120px;
background: var(--bg-panel);
border-top: var(--border);
padding: 10px 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.timeline-controls {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
button {
background: #333;
color: white;
border: none;
padding: 5px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
transition: 0.2s;
}
button:hover {
background: var(--accent);
color: #000;
}
button.active {
background: var(--accent-secondary);
}
input[type="range"].timeline-slider {
width: 100%;
}
.scene-markers {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: var(--text-dim);
margin-top: 5px;
}
/* BPM Visualizer */
#audioViz {
width: 100%;
height: 60px;
background: #111;
margin-top: 10px;
border-radius: 4px;
}
/* Responsive */
@media (max-width: 768px) {
.workspace {
grid-template-columns: 1fr;
grid-template-rows: 1fr 250px;
}
.sidebar {
order: 2;
overflow-y: scroll;
}
}
</style>
</head>
<body>
<header>
<h1>FrameAlign Pro /// System</h1>
<div class="anycoder-link">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
</div>
</header>
<div class="workspace">
<!-- Sidebar Controls -->
<aside class="sidebar">
<!-- 1. File Inputs -->
<div class="control-group">
<h3>1. Source Input</h3>
<div class="input-row">
<label>Video File (MP4/MOV)</label>
<input type="file" id="videoInput" accept="video/*">
</div>
<div class="input-row">
<label>Audio File (Sync)</label>
<input type="file" id="audioInput" accept="audio/*">
</div>
</div>
<!-- 2. Math JS Default Pi^3 -->
<div class="control-group">
<h3>2. Math Core (π³)</h3>
<div class="math-display" id="mathOutput">π³ = 31.006</div>
<div class="input-row" style="margin-top:10px;">
<label>Calculated Offset (Auto)</label>
<" id="mathinput type="textOffset" readonly>
</div>
</div>
<!-- 3. Licht / Contrast / Color -->
<div class="control-group">
<h3>3. Licht & Color</h3>
<div class="input-row">
<label>Exposure</label>
<input type="range" id="brightness" min="0" max="200" value="100">
</div>
<div class="input-row">
<label>Contrast</label>
<input type="range" id="contrast" min="0" max="200" value="100">
</div>
<div class="input-row">
<label>Color Shift (Hue)</label>
<input type="range" id="hue" min="0" max="360" value="0">
</div>
<div class="input-row">
<label>Saturation</label>
<input type="range" id="saturation" min="0" max="200" value="100">
</div>
</div>
<!-- 4. Raster & Alignment -->
<div class="control-group">
<h3>4. Raster & Align</h3>
<div class="input-row">
<label>Show Raster</label>
<input type="checkbox" id="showRaster" checked>
</div>
<div class="input-row">
<label>Raster Grid Size (px)</label>
<input type="range" id="gridSize" min="10" max="100" value="50">
</div>
<div class="input-row">
<label>Frame X Position</label>
<input type="range" id="posX" min="-100" max="100" value="0">
</div>
<div class="input-row">
<label>Frame Y Position</label>
<input type="range" id="posY" min="-100" max="100" value="0">
</div>
<!-- 5. Angle Alignment -->
<div class="input-row">
<label>Rotation Angle (Best Fit)</label>
<input type="range" id="rotation" min="-45" max="45" value="0" step="0.1">
</div>
</div>
<!-- Total Settings -->
<div class="control-group">
<h3>Global Settings</h3>
<div class="input-row">
<label>BPM (Audio Sync)</label>
<input type="number" id="bpmInput" value="120">
</div>
<div class="input-row">
<label>Total Images to Extract</label>
<input type="number" id="totalFrames" value="10">
</div>
<button id="exportBtn">Process Sequence</button>
</div>
</aside>
<!-- Main Stage -->
<main class="stage">
<div class="canvas-container">
<canvas id="mainCanvas"></canvas>
<div class="raster-overlay" id="rasterOverlay"></div>
</div>
<!-- Hidden Video Element for source -->
<video id="sourceVideo" style="display:none;" loop crossorigin="anonymous"></video>
</main>
</div>
<!-- Timeline / Audio Viz -->
<div class="timeline-area">
<div class="timeline-controls">
<button id="playPauseBtn">Play</button>
<button id="setScene1Btn">Set Scene A Start</button>
<button id="setScene2Btn">Set Scene B End</button>
<button id="snapGridBtn">Snap to Grid</button>
</div>
<input type="range" id="timeline" class="timeline-slider" min="0" max="100" value="0" step="0.01">
<div class="scene-markers">
<span>Start: <span id="scene1Val">0.0</span>s</span>
<span id="currentTimeDisplay">00:00.00</span>
<span>End: <span id="scene2Val">0.0</span>s</span>
</div>
<canvas id="audioViz"></canvas>
</div>
<script>
// --- Constants & State ---
const PI_CUBED = Math.pow(Math.PI, 3); // ~31.00627668
const state = {
isPlaying: false,
duration: 0,
scene1: 0,
scene2: 100,
bpm: 120,
audioCtx: null,
analyser: null,
audioSource: null
};
// --- Elements ---
const video = document.getElementById('sourceVideo');
const canvas = document.getElementById('mainCanvas');
const ctx = canvas.getContext('2d');
const rasterOverlay = document.getElementById('rasterOverlay');
const mathOutput = document.getElementById('mathOutput');
const mathOffset = document.getElementById('mathOffset');
const audioVizCanvas = document.getElementById('audioViz');
const audioVizCtx = audioVizCanvas.getContext('2d');
// Sliders & Inputs
const inputs = {
brightness: document.getElementById('brightness'),
contrast: document.getElementById('contrast'),
hue: document.getElementById('hue'),
saturation: document.getElementById('saturation'),
posX: document.getElementById('posX'),
posY: document.getElementById('posY'),
rotation: document.getElementById('rotation'),
gridSize: document.getElementById('gridSize'),
timeline: document.getElementById('timeline'),
bpm: document.getElementById('bpmInput')
};
// --- Initialization ---
function init() {
// Math Calculation Display
mathOutput.textContent = `π³ = ${PI_CUBED.toFixed(4)}`;
mathOffset.value = (PI_CUBED / 100).toFixed(4); // Example usage of value
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Start Loop
requestAnimationFrame(renderLoop);
// Setup Audio Viz
setupAudioViz();
}
function resizeCanvas() {
// Maintain aspect ratio or fit container
const container = document.querySelector('.canvas-container');
canvas.width = container.clientWidth;
canvas.height = container.clientWidth * 0.5625; // 16:9 aspect
rasterOverlay.style.width = canvas.width + 'px';
rasterOverlay.style.height = canvas.height + 'px';
}
// --- Event Listeners ---
// File Handling
document.getElementById('videoInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if(file) {
const url = URL.createObjectURL(file);
video.src = url;
video.onloadedmetadata = () => {
state.duration = video.duration;
inputs.timeline.max = state.duration;
// Set scene 2 default to end
state.scene2 = state.duration;
updateSceneDisplay();
};
}
});
document.getElementById('audioInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if(file && !state.audioCtx) {
initAudioContext();
}
if(state.audioSource) {
state.audioSource.disconnect(); // Cleanup old
}
const url = URL.createObjectURL(file);
const audioEl = new Audio(url);
audioEl.loop = true;
audioEl.play();
if(state.audioCtx) {
state.audioSource = state.audioCtx.createMediaElementSource(audioEl);
state.audioSource.connect(state.analyser);
state.analyser.connect(state.audioCtx.destination);
}
});
// Controls Update
Object.keys(inputs).forEach(key => {
inputs[key].addEventListener('input', (e) => {
if(key === 'timeline') {
video.currentTime = inputs.timeline.value;
} else if (key === 'bpm') {
state.bpm = inputs.bpm.value;
} else if (key === 'gridSize') {
const size = inputs.gridSize.value;
rasterOverlay.style.backgroundSize = `${size}px ${size}px`;
}
});
});
document.getElementById('playPauseBtn').addEventListener('click', () => {
if(video.paused) {
video.play();
state.isPlaying = true;
document.getElementById('playPauseBtn').textContent = "Pause";
document.getElementById('playPauseBtn').classList.add('active');
} else {
video.pause();
state.isPlaying = false;
document.getElementById('playPauseBtn').textContent = "Play";
document.getElementById('playPauseBtn').classList.remove('active');
}
});
document.getElementById('setScene1Btn').addEventListener('click', () => {
state.scene1 = video.currentTime;
updateSceneDisplay();
});
document.getElementById('setScene2Btn').addEventListener('click', () => {
state.scene2 = video.currentTime;
updateSceneDisplay();
});
document.getElementById('showRaster').addEventListener('change', (e) => {
rasterOverlay.style.display = e.target.checked ? 'block' : 'none';
});
document.getElementById('exportBtn').addEventListener('click', () => {
alert(`Processing sequence based on π³ (${PI_CUBED.toFixed(2)})\nBPM: ${state.bpm}\nScenes: ${state.scene1.toFixed(1)}s - ${state.scene2.toFixed(1)}s`);
});
// --- Rendering & Logic ---
function updateSceneDisplay() {
document.getElementById('scene1Val').textContent = state.scene1.toFixed(2);
document.getElementById('scene2Val').textContent = state.scene2.toFixed(2);
// Visual marker logic could go here
}
function renderLoop() {
// 1. Update Timeline Input if playing manually
if(!video.paused && !video.ended) {
inputs.timeline.value = video.currentTime;
}
// Format time display
const current = video.currentTime || 0;
document.getElementById('currentTimeDisplay').textContent =
new Date(current * 1000).toISOString().substr(14, 5);
// 2. Draw Video to Canvas with Filters
// Clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Save context for transformations
ctx.save();
// Center for rotation
ctx.translate(canvas.width/2, canvas.height/2);
// Apply Rotation
ctx.rotate(inputs.rotation.value * Math.PI / 180);
// Apply Position (Pan)
ctx.translate(parseInt(inputs.posX.value), parseInt(inputs.posY.value));
// Apply CSS Filters via Context Filter (Modern Browsers)
const br = inputs.brightness.value;
const co = inputs.contrast.value;
const hu = inputs.hue.value;
const sa = inputs.saturation.value;
ctx.filter = `brightness(${br}%) contrast(${co}%) hue-rotate(${hu}deg) saturate(${sa}%)`;
// Draw Video Scaled to fit canvas
// We draw the video in the center, scaled to cover or contain. Here: contain logic.
const vRatio = video.videoWidth / video.videoHeight;
const cRatio = canvas.width / canvas.height;
let drawW, drawH;
if (vRatio > cRatio) {
drawW = canvas.width;
drawH = canvas.width / vRatio;
} else {
drawH = canvas.height;
drawW = canvas.height * vRatio;
}
if(video.readyState >= 2) { // HAVE_CURRENT_DATA
ctx.drawImage(video, -drawW/2, -drawH/2, drawW, drawH);
} else {
// Placeholder text if no video
ctx.fillStyle = "#333";
ctx.fillRect(-drawW/2, -drawH/2, drawW, drawH);
ctx.fillStyle = "#555";
ctx.font = "20px Arial";
ctx.textAlign = "center";
ctx.fillText("No Video Source", 0, 0);
}
ctx.restore();
// Draw Scene Cut Lines (Overlay)
drawSceneLines();
requestAnimationFrame(renderLoop);
}
function drawSceneLines() {
// Only draw if video is loaded
if(!state.duration) return;
// Calculate positions relative to timeline width
const timelineWidth = document.querySelector('.timeline-area').clientWidth - 40; // approx
const p1 = (state.scene1 / state.duration) * 100;
const p2 = (state.scene2 / state.duration) * 100;
// We can't draw DOM elements easily in canvas loop without creating them,
// but we can manipulate the timeline slider colors via CSS or JS.
// Instead, let's visualize it on the canvas briefly or just rely on the sidebar logic.
// Since the request asked for a visual tool, let's highlight the cut areas on the canvas border.
}
// --- Audio Analysis (Web Audio API) ---
function initAudioContext() {
if (!state.audioCtx) {
state.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
state.analyser = state.audioCtx.createAnalyser();
state.analyser.fftSize = 256;
renderAudioViz();
}
}
function renderAudioViz() {
if(!state.audioCtx) return;
requestAnimationFrame(renderAudioViz);
const bufferLength = state.analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
state.analyser.getByteFrequencyData(dataArray);
audioVizCtx.fillStyle = '#111';
audioVizCtx.fillRect(0, 0, audioVizCanvas.width, audioVizCanvas.height);
const barWidth = (audioVizCanvas.width / bufferLength) * 2.5;
let barHeight;
let x = 0;
for(let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i] / 2; // Scale down
// Color based on Math PI logic
const hue = (i * PI_CUBED) % 360;
audioVizCtx.fillStyle = `hsl(${hue}, 100%, 50%)`;
audioVizCtx.fillRect(x, audioVizCanvas.height - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
// Start
init();
</script>
</body>
</html>