← back to Animate Museum Posts
scripts/test-wave-moderate.js
127 lines
#!/usr/bin/env node
/**
* Test Great Wave with MODERATE motion
* motion_bucket_id: 127 (middle) instead of 255 (max)
* Waves should MOVE but not wash away the image
*/
import Replicate from 'replicate';
import axios from 'axios';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import sharp from 'sharp';
import dotenv from 'dotenv';
import { execSync } from 'child_process';
import { postToX } from '../src/postToX.js';
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.join(__dirname, '..');
const OUTPUT_DIR = path.join(projectRoot, 'output');
async function main() {
console.log(`
╔═══════════════════════════════════════════════════════════╗
║ THE GREAT WAVE - MODERATE MOTION TEST ║
║ motion_bucket_id: 127 (half of max) ║
║ cond_aug: 0.02 (default, less variation) ║
╚═══════════════════════════════════════════════════════════╝
`);
const replicate = new Replicate({
auth: process.env.REPLICATE_API_TOKEN,
});
// Use existing source image
let imagePath = path.join(OUTPUT_DIR, 'great_wave_source.jpg');
if (!fs.existsSync(imagePath)) {
console.log('Downloading The Great Wave...');
const imageUrl = 'https://images.metmuseum.org/CRDImages/as/original/DP141063.jpg';
const response = await axios.get(imageUrl, {
responseType: 'arraybuffer',
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; ArtBot/1.0)' }
});
fs.writeFileSync(imagePath, response.data);
}
// Prepare image
const optimizedPath = path.join(OUTPUT_DIR, 'wave_moderate_opt.jpg');
await sharp(imagePath)
.resize(1024, 576, { fit: 'cover', position: 'center' })
.jpeg({ quality: 95 })
.toFile(optimizedPath);
const imageBuffer = fs.readFileSync(optimizedPath);
const base64Image = imageBuffer.toString('base64');
const dataUri = `data:image/jpeg;base64,${base64Image}`;
console.log('Settings (MODERATE):');
console.log(' - motion_bucket_id: 127 (half of max 255)');
console.log(' - cond_aug: 0.02 (low variation)');
console.log(' - fps: 8 (smooth playback)');
console.log('\nGenerating... (60-120s)\n');
const startTime = Date.now();
const output = await replicate.run(
"stability-ai/stable-video-diffusion:3f0457e4619daac51203dedb472816fd4af51f3149fa7a9e0b5ffcf1b8172438",
{
input: {
input_image: dataUri,
video_length: "25_frames_with_svd_xt",
sizing_strategy: "maintain_aspect_ratio",
frames_per_second: 8,
motion_bucket_id: 127, // MODERATE motion (half of max)
cond_aug: 0.02, // Low variation - preserve image
decoding_t: 14,
seed: Math.floor(Math.random() * 1000000)
}
}
);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`Generated in ${elapsed}s`);
// Download video
const videoResponse = await axios.get(output, { responseType: 'arraybuffer' });
const timestamp = Date.now();
const videoPath = path.join(OUTPUT_DIR, `wave_moderate_${timestamp}.mp4`);
fs.writeFileSync(videoPath, videoResponse.data);
// Convert to H.264
const h264Path = videoPath.replace('.mp4', '_h264.mp4');
execSync(`ffmpeg -y -i "${videoPath}" -c:v libx264 -preset fast -crf 23 -pix_fmt yuv420p -movflags +faststart -an "${h264Path}"`, { stdio: 'pipe' });
fs.unlinkSync(videoPath);
fs.renameSync(h264Path, videoPath);
const stats = fs.statSync(videoPath);
console.log(`\nVideo created: ${videoPath}`);
console.log(`Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
// Cleanup
fs.unlinkSync(optimizedPath);
// Post to Twitter
console.log('\nPosting to Twitter...');
const animationResult = {
animatedVideo: videoPath,
artworkMetadata: {
title: "The Great Wave off Kanagawa",
artist: "Katsushika Hokusai",
year: "1831",
museum: "Moderate Motion Test - Waves should move naturally"
},
isAnimated: true
};
const result = await postToX(animationResult);
console.log(`\nPosted! Tweet ID: ${result.data.id}`);
console.log(`URL: https://twitter.com/goodquestionai/status/${result.data.id}`);
console.log('\nCheck if waves are moving naturally without distorting the image!');
}
main().catch(console.error);