DEV Community

Marius Marian
Marius Marian

Posted on

HTML/SVG/JAVASCRIPT

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");
Enter fullscreen mode Exit fullscreen mode

}
}

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);
}
Enter fullscreen mode Exit fullscreen mode

});
}

// ============================================
// 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!
Enter fullscreen mode Exit fullscreen mode

• Rezoluție: ${width}x${height}
• Frame-uri: ${totalFrames}
• FPS: ${targetFPS}
• Calitate: ${quality}
• Dimensiune: ${(blob.size / 1024 / 1024).toFixed(1)} MB`);
}, 1000);

    resolve();
  };
});
Enter fullscreen mode Exit fullscreen mode

} 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");
Enter fullscreen mode Exit fullscreen mode

}
}

Top comments (0)