Can someone help me with a problem? I can't do the video export function for an animation making application.
import { frameManager } from "./frameManager.js";
let isExporting = false;
let overlay = null;
function showOverlay(message, progress = null) {
if (!overlay) overlay = document.getElementById("export-overlay");
if (overlay) {
const messageP = overlay.querySelector("p") || (() => {
const p = document.createElement("p");
overlay.appendChild(p);
return p;
})();
messageP.textContent = message;
// Adaugă o bară de progres dacă nu există
let progressBar = overlay.querySelector(".export-progress");
if (progress !== null) {
if (!progressBar) {
progressBar = document.createElement("div");
progressBar.className = "export-progress";
progressBar.style.cssText = `
width: 100%;
height: 20px;
background: #444;
border-radius: 10px;
margin-top: 15px;
overflow: hidden;
`;
const fill = document.createElement("div");
fill.className = "export-progress-fill";
fill.style.cssText = `
height: 100%;
width: 0%;
background: linear-gradient(90deg, #00ff88, #00aaff);
transition: width 0.3s;
`;
progressBar.appendChild(fill);
overlay.appendChild(progressBar);
}
const fill = progressBar.querySelector(".export-progress-fill");
if (fill) fill.style.width = `${progress}%`;
}
overlay.classList.remove("hidden");
}
}
function hideOverlay() {
if (overlay) overlay.classList.add("hidden");
}
// ============================================
// CAPTURE FRAME CA IMAGE - VERSIUNE CARE PĂSTREAZĂ PROPORȚIILE
// ============================================
function captureFrameAsImage(width, height) {
return new Promise((resolve, reject) => {
const svg = document.getElementById("drawing-surface");
if (!svg) {
reject(new Error("SVG surface not found"));
return;
}
// Clonează SVG-ul
const svgClone = svg.cloneNode(true);
// ===== SCHIMBARE IMPORTANTĂ =====
// Păstrăm viewBox-ul original, nu îl forțăm
// Doar setăm width și height la dimensiunile dorite
svgClone.setAttribute("width", width);
svgClone.setAttribute("height", height);
// Lasă viewBox-ul așa cum e în original
// Asta păstrează proporțiile și pozițiile elementelor
// Serializează
let svgString = new XMLSerializer().serializeToString(svgClone);
// Adaugă namespace dacă lipsește
if (!svgString.includes('xmlns="http://www.w3.org/2000/svg"')) {
svgString = svgString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
}
const img = new Image();
img.onload = () => {
console.log(`✅ Imagine încărcată: ${img.width}x${img.height}`);
resolve(img);
};
img.onerror = (e) => {
console.error("Eroare încărcare SVG:", e);
reject(new Error("Nu s-a putut încărca SVG-ul"));
};
// Codifică SVG corect pentru URL
try {
const encoded = btoa(unescape(encodeURIComponent(svgString)));
img.src = `data:image/svg+xml;base64,${encoded}`;
} catch (err) {
reject(err);
}
});
}
// ============================================
// FUNCȚIE DE TEST - AFLĂ DIMENSIUNILE REALE
// ============================================
async function testSVGDimensions() {
const svg = document.getElementById("drawing-surface");
console.log("🔍 SVG original:", {
width: svg.getAttribute('width'),
height: svg.getAttribute('height'),
viewBox: svg.getAttribute('viewBox'),
clientWidth: svg.clientWidth,
clientHeight: svg.clientHeight
});
// Testează captureFrameAsImage
const img = await captureFrameAsImage(1920, 1080);
console.log("📸 Imagine generată:", {
width: img.width,
height: img.height,
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight
});
// Desenează pe un canvas temporar să vezi
const testCanvas = document.createElement('canvas');
testCanvas.width = 1920;
testCanvas.height = 1080;
const ctx = testCanvas.getContext('2d');
ctx.drawImage(img, 0, 0, 1920, 1080);
// Adaugă canvasul în pagină să vezi
testCanvas.style.position = 'fixed';
testCanvas.style.top = '10px';
testCanvas.style.right = '10px';
testCanvas.style.width = '400px';
testCanvas.style.border = '2px solid red';
document.body.appendChild(testCanvas);
return "Test complet. Vezi canvasul roșu în colțul dreapta sus.";
}
// ============================================
// EXPORT VIDEO PRINCIPAL
// ============================================
export async function exportVideoPro({
width,
height,
fps = null,
quality = 'high'
} = {}) {
if (isExporting) {
alert("Exportul este deja în curs!");
return;
}
const totalFrames = frameManager.getFrameCount();
if (totalFrames === 0) {
alert("Nu există frame-uri pentru export!");
return;
}
const targetFPS = fps || frameManager.getFPS();
try {
isExporting = true;
showOverlay(Pregătire export ${width}x${height}..., 0);
// Creează canvas VIZIBIL (nu OffscreenCanvas pentru MediaRecorder)
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.style.position = 'fixed';
canvas.style.top = '-9999px';
canvas.style.left = '-9999px';
canvas.style.pointerEvents = 'none';
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d", { alpha: false });
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, width, height);
showOverlay(`Capturez ${totalFrames} frame-uri...`, 5);
// Colectează frame-uri ca Images
const frames = [];
for (let i = 0; i < totalFrames; i++) {
showOverlay(`Capturez frame ${i+1}/${totalFrames}`, 5 + ((i)/totalFrames) * 45);
// Încarcă frame-ul
await frameManager.setActiveFrame(i);
// Capturează SVG ca Image
const img = await captureFrameAsImage(width, height);
frames.push(img);
}
showOverlay(`Creez video cu ${totalFrames} frame-uri la ${targetFPS} FPS...`, 50);
// Configurare MediaRecorder
const stream = canvas.captureStream(targetFPS);
// Alege cel mai bun codec disponibil
let mimeType = 'video/webm;codecs=vp9';
if (!MediaRecorder.isTypeSupported(mimeType)) {
mimeType = 'video/webm;codecs=vp8';
}
if (!MediaRecorder.isTypeSupported(mimeType)) {
mimeType = 'video/webm';
}
console.log("📹 Folosesc:", mimeType);
// Calculează bitrate-ul în funcție de calitate
const bitrate = quality === 'high' ? 50_000_000 : 20_000_000;
const recorder = new MediaRecorder(stream, {
mimeType: mimeType,
videoBitsPerSecond: bitrate
});
const chunks = [];
recorder.ondataavailable = (e) => {
if (e.data.size > 0) chunks.push(e.data);
};
// Pornește înregistrarea
recorder.start();
// Rulează frame-urile
for (let i = 0; i < frames.length; i++) {
// Desenează frame-ul
ctx.drawImage(frames[i], 0, 0, width, height);
// Actualizează progres
showOverlay(`Render frame ${i+1}/${frames.length}`, 50 + ((i+1)/frames.length) * 45);
// Așteaptă pentru următorul frame
await new Promise(r => setTimeout(r, 1000 / targetFPS));
}
// Oprește înregistrarea
recorder.stop();
// Așteaptă să se termine procesarea
await new Promise((resolve) => {
recorder.onstop = () => {
// Creează blob-ul final
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
// Curăță canvas-ul temporar
document.body.removeChild(canvas);
// Salvează
const a = document.createElement("a");
a.href = url;
a.download = `animatie_${width}x${height}_${targetFPS}fps.webm`;
a.click();
showOverlay(`Finalizat! Salvare fișier...`, 100);
setTimeout(() => {
URL.revokeObjectURL(url);
isExporting = false;
hideOverlay();
alert(`✅ Export reușit!
• Rezoluție: ${width}x${height}
• Frame-uri: ${totalFrames}
• FPS: ${targetFPS}
• Calitate: ${quality}
• Dimensiune: ${(blob.size / 1024 / 1024).toFixed(1)} MB`);
}, 1000);
resolve();
};
});
} catch (err) {
console.error("❌ Eroare export:", err);
alert(Eroare export: ${err.message});
isExporting = false;
hideOverlay();
}
}
// ============================================
// INITIALIZARE
// ============================================
export function initExport() {
console.log("✅ Export module initialized");
const exportBtn = document.getElementById("export-video-btn");
if (exportBtn) {
exportBtn.addEventListener("click", () => {
// ===== NOU: Ia dimensiunea REALĂ a canvasului =====
const canvasElement = document.getElementById("drawing-surface");
const rect = canvasElement.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
console.log("📏 Dimensiuni canvas REAL:", width, "x", height);
exportVideoPro({
width: width,
height: height,
quality: 'high'
});
});
console.log("🎬 Video export button connected");
}
}
Top comments (0)