anycoder-11a8f457 / index.html
HI7RAI's picture
Upload folder using huggingface_hub
49f5034 verified
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pro Video & Frame Werkzeug</title>
<!-- Icons importieren -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-color: #121212;
--panel-color: #1e1e1e;
--accent-color: #00e5ff;
--accent-secondary: #ff0055;
--text-color: #e0e0e0;
--input-bg: #2c2c2c;
--border-radius: 12px;
--font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
outline: none;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-family);
display: flex;
flex-direction: column;
min-height: 100vh;
overflow-x: hidden;
}
/* HEADER */
header {
background: linear-gradient(90deg, #0f0f0f, #1a1a1a);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #333;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
h1 {
font-size: 1.2rem;
text-transform: uppercase;
letter-spacing: 2px;
color: var(--accent-color);
}
.credits a {
color: var(--text-color);
text-decoration: none;
font-size: 0.9rem;
opacity: 0.7;
transition: opacity 0.3s;
}
.credits a:hover {
opacity: 1;
color: var(--accent-color);
}
/* MAIN LAYOUT */
main {
flex: 1;
display: grid;
grid-template-columns: 1fr 350px;
gap: 20px;
padding: 20px;
height: calc(100vh - 60px);
}
@media (max-width: 900px) {
main {
grid-template-columns: 1fr;
height: auto;
}
}
/* PREVIEW AREA (THE WORLD) */
.preview-container {
background-color: #000;
border-radius: var(--border-radius);
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #333;
perspective: 1000px; /* Für 3D Effekte */
}
#world {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
transform-style: preserve-3d;
transition: transform 0.1s ease-out; /* Smooth Gyro */
position: relative;
}
/* Das Grid der Bilder */
.image-grid {
display: grid;
gap: 10px;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
transform-style: preserve-3d;
transition: filter 0.2s;
}
.grid-item {
width: 80px;
height: 80px;
background-color: #333;
background-image: url('https://picsum.photos/seed/tech/100/100.jpg');
background-size: cover;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0,0,0,0.5);
transition: transform 0.3s;
}
.grid-item:hover {
transform: scale(1.1) translateZ(20px);
border: 2px solid var(--accent-color);
}
/* Video Overlay */
.video-overlay {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
display: flex;
gap: 10px;
}
.video-element {
width: 200px;
height: 112px;
background: #000;
border: 1px solid var(--accent-color);
display: none; /* Standardmäßig ausgeblendet, wird per JS gesteuert */
object-fit: cover;
}
.video-element.active {
display: block;
}
/* CONTROL PANEL */
.controls {
background-color: var(--panel-color);
border-radius: var(--border-radius);
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
border: 1px solid #333;
}
.control-group {
background: rgba(0,0,0,0.2);
padding: 15px;
border-radius: 8px;
border-left: 3px solid var(--accent-color);
}
.control-group h3 {
font-size: 0.9rem;
margin-bottom: 15px;
color: var(--accent-color);
display: flex;
align-items: center;
gap: 8px;
}
label {
display: block;
margin-bottom: 5px;
font-size: 0.8rem;
color: #aaa;
}
/* Inputs & Sliders */
input[type="range"] {
width: 100%;
margin-bottom: 10px;
accent-color: var(--accent-color);
}
input[type="number"], input[type="text"] {
width: 100%;
background: var(--input-bg);
border: 1px solid #444;
color: white;
padding: 8px;
border-radius: 4px;
margin-bottom: 10px;
}
input[type="file"] {
font-size: 0.8rem;
width: 100%;
margin-bottom: 10px;
}
/* Buttons */
.btn {
background: #333;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
margin-bottom: 5px;
}
.btn:hover {
background: #444;
}
.btn-primary {
background: var(--accent-color);
color: #000;
font-weight: bold;
}
.btn-primary:hover {
background: #00b8cc;
}
.btn-danger {
background: var(--accent-secondary);
color: white;
}
.btn.locked {
background: var(--accent-secondary);
box-shadow: inset 0 0 5px rgba(0,0,0,0.5);
}
/* Toggle Switch für Szenen */
.scene-toggle {
display: flex;
background: #000;
border-radius: 4px;
padding: 2px;
margin-bottom: 10px;
}
.scene-option {
flex: 1;
text-align: center;
padding: 8px;
cursor: pointer;
font-size: 0.8rem;
border-radius: 4px;
}
.scene-option.active {
background: var(--accent-color);
color: #000;
font-weight: bold;
}
/* HUD Overlay */
.hud {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 8px;
font-family: monospace;
font-size: 0.9rem;
pointer-events: none;
z-index: 100;
border: 1px solid #555;
}
.hud-row {
display: flex;
justify-content: space-between;
gap: 15px;
margin-bottom: 2px;
}
/* Audio Beat Visualizer Placeholder */
.beat-indicator {
width: 100%;
height: 10px;
background: #333;
border-radius: 5px;
overflow: hidden;
margin-top: 5px;
}
.beat-bar {
width: 0%;
height: 100%;
background: var(--accent-secondary);
transition: width 0.1s linear;
}
</style>
</head>
<body>
<header>
<h1><i class="fas fa-cut"></i> VideoCutter & AlignTool</h1>
<div class="credits">
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</div>
</header>
<main>
<!-- LINKER BEREICH: VORSCHAU -->
<div class="preview-container">
<div id="world">
<!-- Video Overlay (Szene 1 & 2) -->
<div class="video-overlay">
<video id="videoScene1" class="video-element active" muted loop playsinline></video>
<video id="videoScene2" class="video-element" muted loop playsinline></video>
</div>
<!-- Grid mit Einzelbildern -->
<div class="image-grid" id="imageGrid">
<!-- Dynamisch generierte Bilder -->
</div>
</div>
<!-- Gyro HUD -->
<div class="hud">
<div class="hud-row"><span>Gyro X:</span> <span id="hudX"></span></div>
<div class="hud-row"><span>Gyro Y:</span> <span id="hudY"></span></div>
<div class="hud-row"><span>Status:</span> <span id="hudStatus">Bereit</span></div>
</div>
</div>
<!-- RECHTER BEREICH: STEUERUNG -->
<div class="controls">
<!-- 1. VIDEO CUTTER -->
<div class="control-group">
<h3><i class="fas fa-film"></i> Video Cutter (2 Szenen)</h3>
<div class="scene-toggle">
<div class="scene-option active" onclick="switchScene(1)">Szene 1</div>
<div class="scene-option" onclick="switchScene(2)">Szene 2</div>
</div>
<label>Szene 1 Datei</label>
<input type="file" accept="video/*" onchange="loadVideo(this, 1)">
<label>Szene 2 Datei</label>
<input type="file" accept="video/*" onchange="loadVideo(this, 2)">
<button class="btn btn-primary" onclick="togglePlay()">
<i class="fas fa-play"></i> / <i class="fas fa-pause"></i> Wiedergabe
</button>
</div>
<!-- 2. RASTER & MATHE -->
<div class="control-group">
<h3><i class="fas fa-th"></i> Raster & Math (JS)</h3>
<label>Raster Wert (Gap): <span id="rasterValDisplay">0.00</span></label>
<input type="range" id="rasterInput" min="0" max="100" step="0.1">
<div style="font-size: 0.75rem; color: #888; margin-bottom: 10px;">
Mathematische Basis: <code>Math.pow(Math.PI, 3)</code> ≈ 31.006
</div>
<label>Gesamt Bilder (Anzahl)</label>
<input type="range" id="imgCountInput" min="1" max="50" value="12" oninput="updateGridCount()">
</div>
<!-- 3. AUDIO & BPM -->
<div class="control-group">
<h3><i class="fas fa-music"></i> Audio & BPM</h3>
<label>Audio Datei</label>
<input type="file" accept="audio/*" id="audioInput">
<label>BPM (Beats Per Minute)</label>
<input type="number" id="bpmInput" value="120" min="60" max="200">
<div class="beat-indicator">
<div class="beat-bar" id="beatBar"></div>
</div>
</div>
<!-- 4. LICHT & FARBE -->
<div class="control-group">
<h3><i class="fas fa-adjust"></i> Licht Kontrast / Farbe</h3>
<label>Helligkeit (Light)</label>
<input type="range" id="brightnessInput" min="0" max="200" value="100" oninput="updateFilters()">
<label>Kontrast</label>
<input type="range" id="contrastInput" min="0" max="200" value="100" oninput="updateFilters()">
<label>Sättigung (Color)</label>
<input type="range" id="saturateInput" min="0" max="200" value="100" oninput="updateFilters()">
</div>
<!-- 5. WINKEL / GYRO CONTROL -->
<div class="control-group">
<h3><i class="fas fa-sync-alt"></i> Winkel Ausrichtung</h3>
<button id="reqPermission" class="btn btn-primary">
<i class="fas fa-mobile-alt"></i> Gyro Berechtigung
</button>
<label>Geschwindigkeits-Multiplikator: <span id="multVal">1.0</span></label>
<input type="range" id="speedMult" min="0.1" max="5.0" step="0.1" value="1.0">
<label>Manueller Offset (Touchpoint)</label>
<input type="number" id="manualOffset" value="0">
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button id="lockXBtn" class="btn" onclick="toggleLock('x')">
<i class="fas fa-unlock"></i> X-Achse
</button>
<button id="lockYBtn" class="btn" onclick="toggleLock('y')">
<i class="fas fa-unlock"></i> Y-Achse
</button>
</div>
</div>
</div>
</main>
<script>
// --- 1. INITIALISIERUNG & HELPER ---
const world = document.getElementById('world');
const imageGrid = document.getElementById('imageGrid');
const rasterInput = document.getElementById('rasterInput');
const rasterValDisplay = document.getElementById('rasterValDisplay');
// Default Wert: Pi^3
const PI_CUBED = Math.pow(Math.PI, 3); // ~31.006
rasterInput.value = PI_CUBED;
rasterValDisplay.innerText = PI_CUBED.toFixed(3);
// Grid beim Start generieren
updateGridCount();
// --- 2. VIDEO CUTTER LOGIK ---
let currentScene = 1;
const vid1 = document.getElementById('videoScene1');
const vid2 = document.getElementById('videoScene2');
let isPlaying = false;
function loadVideo(input, sceneNum) {
const file = input.files[0];
if (file) {
const url = URL.createObjectURL(file);
const vid = sceneNum === 1 ? vid1 : vid2;
vid.src = url;
vid.load();
// Automatisch abspielen wenn Szene aktiv
if (currentScene === sceneNum && isPlaying) vid.play();
}
}
function switchScene(num) {
// UI Update
document.querySelectorAll('.scene-option').forEach((el, idx) => {
el.classList.toggle('active', idx + 1 === num);
});
// Video Update
if (currentScene === num) return; // Nichts tun wenn schon aktiv
// Altes pausieren
const oldVid = currentScene === 1 ? vid1 : vid2;
oldVid.classList.remove('active');
if(isPlaying) oldVid.pause();
currentScene = num;
const newVid = currentScene === 1 ? vid1 : vid2;
newVid.classList.add('active');
if(isPlaying) newVid.play();
}
function togglePlay() {
isPlaying = !isPlaying;
const activeVid = currentScene === 1 ? vid1 : vid2;
if (isPlaying) {
activeVid.play();
} else {
vid1.pause();
vid2.pause();
}
}
// --- 3. RASTER & GRID LOGIK (MATH) ---
rasterInput.addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
rasterValDisplay.innerText = val.toFixed(3);
updateGridGap(val);
});
function updateGridGap(val) {
// Wir teilen den Wert durch 10, damit es gut in px passt
imageGrid.style.gap = `${val / 10}px`;
}
// Initiale Anwendeung
updateGridGap(PI_CUBED);
function updateGridCount() {
const count = document.getElementById('imgCountInput').value;
imageGrid.innerHTML = '';
// Grid Spalten anpassen basierend auf Anzahl (quadratisch näherungsweise)
const cols = Math.ceil(Math.sqrt(count));
imageGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
for(let i=0; i<count; i++) {
const div = document.createElement('div');
div.className = 'grid-item';
// Zufälliger Seed für Bilder
div.style.backgroundImage = `url('https://picsum.photos/seed/${i + 50}/100/100.jpg')`;
imageGrid.appendChild(div);
}
}
// --- 4. AUDIO & BPM LOGIK ---
const audioInput = document.getElementById('audioInput');
const bpmInput = document.getElementById('bpmInput');
const beatBar = document.getElementById('beatBar');
let audioContext, audioSource, analyser;
let beatInterval;
audioInput.addEventListener('change', function() {
const file = this.files[0];
if (file) {
const url = URL.createObjectURL(file);
const audio = new Audio(url);
audio.loop = true;
// Simpler Metronom Visualisierung basierend auf BPM
startMetronome();
// Audio abspielen (optional, hier nur Logik-Setup)
audio.play().catch(e => console.log("Auto-play blocked"));
}
});
bpmInput.addEventListener('input', () => {
startMetronome();
});
function startMetronome() {
if (beatInterval) clearInterval(beatInterval);
const bpm = parseInt(bpmInput.value) || 120;
const intervalMs = (60 / bpm) * 1000;
beatInterval = setInterval(() => {
// Beat Animation
beatBar.style.width = '100%';
beatBar.style.opacity = '1';
setTimeout(() => {
beatBar.style.width = '0%';
beatBar.style.opacity = '0.5';
}, 100);
}, intervalMs);
}
// --- 5. LICHT & FARBE ---
function updateFilters() {
const b = document.getElementById('brightnessInput').value;
const c = document.getElementById('contrastInput').value;
const s = document.getElementById('saturateInput').value;
// Anwenden auf das Grid (Bilder)
imageGrid.style.filter = `brightness(${b}%) contrast(${c}%) saturate(${s}%)`;
}
// --- 6. GYROSKOP & WINKEL (USER CODE INTEGRATION) ---
// Elemente referenzieren
const hudX = document.getElementById('hudX');
const hudY = document.getElementById('hudY');
const speedInput = document.getElementById('speedMult');
const multValDisplay = document.getElementById('multVal');
const btnPerm = document.getElementById('reqPermission');
const offsetInput = document.getElementById('manualOffset');
const hudStatus = document.getElementById('hudStatus');
// Status Variablen
let state = {
currentX: 0, // Aktuelle Rotation X (finaler Wert für CSS)
currentY: 0, // Aktuelle Rotation Y (finaler Wert für CSS)
baseX: null, // Startwert beim Gyro-Start (Nullpunkt)
baseY: null,
lockedX: false, // Ist X fixiert?
lockedY: false, // Ist Y fixiert?
fixedValX: 0, // Der Wert, auf dem X fixiert wurde
fixedValY: 0, // Der Wert, auf dem Y fixiert wurde
multiplier: 1, // Geschwindigkeitsfaktor
manualOffset: 0 // Touchpoint Offset (manuelle Korrektur)
};
// --- KONFIGURATION UPDATES ---
speedInput.addEventListener('input', (e) => {
state.multiplier = parseFloat(e.target.value);
multValDisplay.innerText = state.multiplier;
});
offsetInput.addEventListener('input', (e) => {
state.manualOffset = parseInt(e.target.value);
updateView();
});
// --- LOCKING LOGIK (FIXIEREN) ---
window.toggleLock = function(axis) {
if (axis === 'x') {
state.lockedX = !state.lockedX;
const btn = document.getElementById('lockXBtn');
if (state.lockedX) {
state.fixedValX = state.currentX; // Speichere aktuellen IST-Wert als Fixpunkt
btn.classList.add('locked');
btn.innerHTML = `<i class="fas fa-lock"></i> X LOCKED`;
} else {
btn.classList.remove('locked');
btn.innerHTML = `<i class="fas fa-unlock"></i> X-Achse`;
}
}
else if (axis === 'y') {
state.lockedY = !state.lockedY;
const btn = document.getElementById('lockYBtn');
if (state.lockedY) {
state.fixedValY = state.currentY; // Speichere aktuellen IST-Wert
btn.classList.add('locked');
btn.innerHTML = `<i class="fas fa-lock"></i> Y LOCKED`;
} else {
btn.classList.remove('locked');
btn.innerHTML = `<i class="fas fa-unlock"></i> Y-Achse`;
}
}
}
// --- GYROSKOP LOGIK ---
function handleOrientation(event) {
// Beta = X-Achse (vor/zurück neigen) [-180, 180]
// Gamma = Y-Achse (links/rechts neigen) [-90, 90]
const xRaw = event.beta;
const yRaw = event.gamma;
// Wenn noch kein Basiswert (Kalibrierung beim Start), setze ihn
if (state.baseX === null) {
state.baseX = xRaw;
state.baseY = yRaw;
hudStatus.innerText = "Aktiv";
hudStatus.style.color = "#00e5ff";
}
// Differenz berechnen * Multiplier
const deltaX = (xRaw - state.baseX) * state.multiplier;
const deltaY = (yRaw - state.baseY) * state.multiplier;
// Berechne potenzielle neue Zielwerte
let newX = deltaX + state.manualOffset;
let newY = deltaY;
// --- ACHSEN FIXIERUNG PRÜFEN ---
// X-Achse Logic
if (!state.lockedX) {
state.currentX = newX;
} else {
state.currentX = state.fixedValX;
}
// Y-Achse Logic
if (!state.lockedY) {
state.currentY = newY;
} else {
state.currentY = state.fixedValY;
}
updateView();
}
// --- VISUALISIERUNG UPDATEN ---
function updateView() {
// Begrenzung der Winkel optional, um "Flip" zu vermeiden (z.B. -90 bis 90)
// Hier lassen wir es frei für 360 Grad Feeling
// CSS Transform anwenden
// rotateX für Beta, rotateY für Gamma
world.style.transform = `rotateX(${-state.currentX}deg) rotateY(${state.currentY}deg)`;
// HUD Updaten
hudX.innerText = Math.round(state.currentX) + "°";
hudY.innerText = Math.round(state.currentY) + "°";
}
// --- PERMISSION REQUEST (iOS 13+ support) ---
btnPerm.addEventListener('click', requestGyro);
function requestGyro() {
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
// iOS 13+
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
window.addEventListener('deviceorientation', handleOrientation);
btnPerm.style.display = 'none'; // Button ausblenden nach Erfolg
hudStatus.innerText = "Berechtigung erteilt";
} else {
alert('Gyro permission denied');
hudStatus.innerText = "Verweigert";
hudStatus.style.color = "red";
}
})
.catch(console.error);
} else {
// Android / ältere iOS / PC (DevTools Sensors)
window.addEventListener('deviceorientation', handleOrientation);
btnPerm.style.display = 'none';
hudStatus.innerText = "Lausche...";
// Für Debugging auf Desktop ohne Sensor:
console.log("Gyro Event Listener added (Sensors required for movement)");
}
}
</script>
</body>
</html>