← back to Animate Museum Posts

src/contentGenerator.js

126 lines

import axios from 'axios';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
import { execSync } from 'child_process';
import { addBrandingToVideo } from './addBranding.js';
import { addScheduledPost, initDatabase } from './database.js';
import { createMorphingVideo } from './animateWithMorphing.js';
import { FAMOUS_PAINTINGS } from './artworks.js';

dotenv.config();

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const OUTPUT_DIR = path.join(__dirname, '..', 'output');
const THUMBNAILS_DIR = path.join(__dirname, '..', 'output', 'thumbnails');

// Ensure directories exist
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
if (!fs.existsSync(THUMBNAILS_DIR)) fs.mkdirSync(THUMBNAILS_DIR, { recursive: true });

// Use 50 famous paintings from artworks.js
const ARTWORKS = FAMOUS_PAINTINGS;

/**
 * Download image from URL
 */
async function downloadImage(url, filename) {
  const imagePath = path.join(OUTPUT_DIR, filename);

  const response = await axios.get(url, {
    responseType: 'arraybuffer',
    headers: { 'User-Agent': 'Mozilla/5.0 (compatible; ArtBot/1.0)' }
  });
  fs.writeFileSync(imagePath, response.data);
  return imagePath;
}

/**
 * Create morphing video using prompt-based models (cats/dogs transform)
 */
async function createAnimatedVideo(imagePath, artwork) {
  console.log(`Creating MORPHING video for: ${artwork.title}`);

  const startTime = Date.now();
  const result = await createMorphingVideo(imagePath, artwork);
  console.log(`  Generated in ${((Date.now() - startTime) / 1000).toFixed(1)}s with ${result.modelUsed}`);

  const videoPath = result.videoPath;
  const timestamp = Date.now();

  // Add branding (1s intro + 1s outro)
  console.log('  Adding GoodQuestion.AI branding...');
  await addBrandingToVideo(videoPath, { title: artwork.title, artist: artwork.artist });

  // Create thumbnail
  const thumbnailPath = path.join(THUMBNAILS_DIR, `thumb_${timestamp}.jpg`);
  execSync(`ffmpeg -y -i "${videoPath}" -ss 00:00:01 -vframes 1 "${thumbnailPath}"`, { stdio: 'pipe' });

  return { videoPath, thumbnailPath, motionSettings: result.motionSettings };
}

/**
 * Generate content for the next N hours
 * Posts every 20 minutes
 */
export async function generateScheduledContent(hoursAhead = 12) {
  await initDatabase();

  const postsPerHour = 3;  // Every 20 minutes
  const totalPosts = hoursAhead * postsPerHour;

  console.log(`\nGenerating ${totalPosts} posts for the next ${hoursAhead} hours...\n`);

  let artworkIndex = 0;
  const now = new Date();

  for (let i = 0; i < totalPosts; i++) {
    const artwork = ARTWORKS[artworkIndex % ARTWORKS.length];
    artworkIndex++;

    // Calculate scheduled time (every 20 minutes)
    const scheduledTime = new Date(now.getTime() + (i * 20 * 60 * 1000));

    console.log(`\n[${i + 1}/${totalPosts}] ${artwork.title}`);
    console.log(`  Scheduled for: ${scheduledTime.toISOString()}`);

    try {
      // Download image
      const imagePath = await downloadImage(
        artwork.imageUrl,
        `source_${artwork.title.replace(/\s+/g, '_').toLowerCase()}.jpg`
      );

      // Create video
      const { videoPath, thumbnailPath, motionSettings } = await createAnimatedVideo(imagePath, artwork);

      // Store in database
      const postId = await addScheduledPost({
        title: artwork.title,
        artist: artwork.artist,
        year: artwork.year,
        museum: artwork.museum,
        videoPath: videoPath,
        thumbnailPath: thumbnailPath,
        scheduledTime: scheduledTime,
        motionSettings: motionSettings
      });

      console.log(`  Saved to database with ID: ${postId}`);

    } catch (err) {
      console.error(`  Failed: ${err.message}`);
    }
  }

  console.log(`\n=== Content generation complete ===\n`);
}

// Run if called directly
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const hours = parseInt(process.argv[2]) || 3;
  generateScheduledContent(hours).catch(console.error);
}