← back to Goodquestion Ai

video-html/batch-generate.js

111 lines

#!/usr/bin/env node
/**
 * batch-generate.js — Run generate-video.js for all configs in configs/
 *
 * Processes sequentially (Puppeteer + ffmpeg are memory-heavy).
 * Logs progress, timing, and any failures.
 *
 * Usage:
 *   node batch-generate.js                # Run all
 *   node batch-generate.js --from 5       # Start from config 5
 *   node batch-generate.js --only 3,7,12  # Only specific configs
 */

const fs = require('fs');
const path = require('path');
const { generateVideo } = require('./generate-video');

const CONFIGS_DIR = path.join(__dirname, 'configs');
const VIDEOS_DIR = '/root/Projects/goodquestion-ai/videos';

fs.mkdirSync(VIDEOS_DIR, { recursive: true });

function parseArgs() {
  const args = process.argv.slice(2);
  const opts = { from: 1, only: null };
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--from' && args[i + 1]) opts.from = parseInt(args[++i], 10);
    if (args[i] === '--only' && args[i + 1]) opts.only = args[++i].split(',').map(Number);
  }
  return opts;
}

async function main() {
  const opts = parseArgs();
  const configFiles = fs.readdirSync(CONFIGS_DIR)
    .filter(f => f.endsWith('.json'))
    .sort();

  let toProcess = configFiles;

  if (opts.only) {
    toProcess = configFiles.filter((f, i) => opts.only.includes(i + 1));
  } else if (opts.from > 1) {
    toProcess = configFiles.slice(opts.from - 1);
  }

  console.log('╔══════════════════════════════════════════════════════╗');
  console.log('║        BATCH VIDEO GENERATION — 18 Blog Posts       ║');
  console.log('╚══════════════════════════════════════════════════════╝');
  console.log(`\nConfigs to process: ${toProcess.length}`);
  console.log(`Output directory: ${VIDEOS_DIR}\n`);

  const results = [];
  const batchStart = Date.now();

  for (let i = 0; i < toProcess.length; i++) {
    const file = toProcess[i];
    const configPath = path.join(CONFIGS_DIR, file);
    const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));

    console.log(`\n${'█'.repeat(60)}`);
    console.log(`█  VIDEO ${i + 1}/${toProcess.length}: ${config.title}`);
    console.log(`█  Config: ${file}`);
    console.log(`${'█'.repeat(60)}`);

    const videoStart = Date.now();
    try {
      const output = await generateVideo(config);
      const elapsed = ((Date.now() - videoStart) / 1000).toFixed(1);
      results.push({ file, title: config.title, status: 'OK', time: elapsed, output });
      console.log(`\n✓ Completed in ${elapsed}s`);
    } catch (err) {
      const elapsed = ((Date.now() - videoStart) / 1000).toFixed(1);
      results.push({ file, title: config.title, status: 'FAIL', time: elapsed, error: err.message });
      console.error(`\n✗ FAILED after ${elapsed}s: ${err.message}`);
    }
  }

  // Summary
  const totalTime = ((Date.now() - batchStart) / 1000).toFixed(1);
  const ok = results.filter(r => r.status === 'OK').length;
  const fail = results.filter(r => r.status === 'FAIL').length;

  console.log('\n\n' + '═'.repeat(60));
  console.log('BATCH COMPLETE');
  console.log('═'.repeat(60));
  console.log(`Total time: ${totalTime}s (${(totalTime / 60).toFixed(1)} min)`);
  console.log(`Success: ${ok}/${results.length}`);
  if (fail > 0) console.log(`Failed: ${fail}`);

  console.log('\nResults:');
  results.forEach((r, i) => {
    const icon = r.status === 'OK' ? '✓' : '✗';
    console.log(`  ${icon} ${String(i + 1).padStart(2)}. ${r.title} (${r.time}s)`);
    if (r.status === 'FAIL') console.log(`       Error: ${r.error}`);
  });

  // Count output files
  const outputFiles = fs.readdirSync(VIDEOS_DIR).filter(f => f.endsWith('.mp4'));
  const landscapeCount = outputFiles.filter(f => f.includes('-landscape')).length;
  const verticalCount = outputFiles.filter(f => f.includes('-vertical')).length;
  console.log(`\nOutput files: ${outputFiles.length} total (${landscapeCount} landscape + ${verticalCount} vertical)`);

  if (fail > 0) process.exit(1);
}

main().catch(err => {
  console.error('Batch failed:', err);
  process.exit(1);
});