← 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);
}