save changes
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import { downloadImagesFromPinterestPin } from './lib/downloader';
|
||||
import { callOpenAIWithFile } from './lib/openai';
|
||||
import { generateStyledVideo } from './lib/video-generator-styled';
|
||||
import { generateVideo } from './lib/video-generator';
|
||||
import { generateImage } from './lib/image-generator-mix-style';
|
||||
import { logger } from './lib/logger';
|
||||
import * as fs from 'fs/promises';
|
||||
import dotenv from 'dotenv';
|
||||
@ -28,7 +29,8 @@ interface GenerationTask {
|
||||
imagePrompt: string;
|
||||
videoPrompt: string;
|
||||
imageFileName: string;
|
||||
renamedImagePath: string;
|
||||
renamedImagePaths: string[];
|
||||
generatedImagePath?: string;
|
||||
genre: string;
|
||||
subGenre: string;
|
||||
scene: string;
|
||||
@ -36,17 +38,23 @@ interface GenerationTask {
|
||||
camera: string;
|
||||
}
|
||||
|
||||
async function getPromptsForImage(imagePath: string, pinUrl: string, genre: string, subGenre: string): Promise<GenerationTask | null> {
|
||||
async function getPromptsForImage(imagePaths: string[], pinUrl: string, genre: string, subGenre: string): Promise<GenerationTask | null> {
|
||||
const pinId = pinUrl.split('/').filter(Boolean).pop() || `pin_${Date.now()}`;
|
||||
const timestamp = new Date().getTime();
|
||||
const imageFileName = `${pinId}_${timestamp}.png`;
|
||||
const renamedImagePath = path.join(path.dirname(imagePath), imageFileName);
|
||||
|
||||
const renamedImagePaths = [];
|
||||
for (let i = 0; i < imagePaths.length; i++) {
|
||||
const renamedPath = path.join(path.dirname(imagePaths[i]), `${pinId}_${timestamp}_${i}.png`);
|
||||
await fs.rename(imagePaths[i], renamedPath);
|
||||
renamedImagePaths.push(renamedPath);
|
||||
}
|
||||
logger.debug(`Renamed source images to: ${renamedImagePaths.join(', ')}`);
|
||||
|
||||
const imageForPrompt = renamedImagePaths[Math.floor(Math.random() * renamedImagePaths.length)];
|
||||
|
||||
try {
|
||||
await fs.rename(imagePath, renamedImagePath);
|
||||
logger.debug(`Renamed ${imagePath} to ${renamedImagePath}`);
|
||||
|
||||
const promptResponse = await callOpenAIWithFile(renamedImagePath,
|
||||
const promptResponse = await callOpenAIWithFile(imageForPrompt,
|
||||
`Analyze the provided image and generate the following:
|
||||
1. 'scene': A description of the image's environment.
|
||||
2. 'action': A description of the main action occurring in the image.
|
||||
@ -66,100 +74,70 @@ async function getPromptsForImage(imagePath: string, pinUrl: string, genre: stri
|
||||
---
|
||||
`);
|
||||
const { scene, action, camera, image_prompt: imagePrompt, video_prompt: videoPrompt } = promptResponse;
|
||||
logger.info(`Image prompt for ${renamedImagePath}:`, imagePrompt);
|
||||
logger.info(`Video prompt for ${renamedImagePath}:`, videoPrompt);
|
||||
logger.info(`Image prompt for ${imageForPrompt}:`, imagePrompt);
|
||||
logger.info(`Video prompt for ${imageForPrompt}:`, videoPrompt);
|
||||
|
||||
return { pinUrl, imagePrompt, videoPrompt, imageFileName, renamedImagePath, genre, subGenre, scene, action, camera };
|
||||
return { pinUrl, imagePrompt, videoPrompt, imageFileName, renamedImagePaths, genre, subGenre, scene, action, camera };
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get prompts for ${renamedImagePath}:`, error);
|
||||
try {
|
||||
await fs.unlink(renamedImagePath);
|
||||
} catch (cleanupError) {
|
||||
// ignore
|
||||
logger.error(`Failed to get prompts for ${imageForPrompt}:`, error);
|
||||
for (const p of renamedImagePaths) {
|
||||
try {
|
||||
await fs.unlink(p);
|
||||
} catch (cleanupError) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateImageAndVideo(task: GenerationTask, server: { baseUrl: string; outputDir: string; }): Promise<{ imagePath: string; videoPath: string; } | null> {
|
||||
const { imagePrompt, videoPrompt, imageFileName, renamedImagePath } = task;
|
||||
async function generateImageForTask(task: GenerationTask, server: { baseUrl: string; outputDir: string; }): Promise<string | null> {
|
||||
const { imagePrompt, imageFileName, renamedImagePaths } = task;
|
||||
const { baseUrl, outputDir } = server;
|
||||
const inputDir = outputDir.replace("output", "input");
|
||||
|
||||
const sourceFileNames: string[] = [];
|
||||
try {
|
||||
const destPath = path.join(inputDir, imageFileName);
|
||||
await fs.copyFile(renamedImagePath, destPath);
|
||||
logger.info(`Copied ${renamedImagePath} to ${destPath}`);
|
||||
for (const sourcePath of renamedImagePaths) {
|
||||
const fileName = path.basename(sourcePath);
|
||||
const destPath = path.join(inputDir, fileName);
|
||||
await fs.copyFile(sourcePath, destPath);
|
||||
sourceFileNames.push(fileName);
|
||||
logger.info(`Copied ${sourcePath} to ${destPath}`);
|
||||
}
|
||||
|
||||
const videoFileName = imageFileName.replace('.png', '.mp4');
|
||||
const { videoPath, imagePath } = await generateStyledVideo(
|
||||
const generatedImagePath = await generateImage(
|
||||
imagePrompt,
|
||||
videoPrompt,
|
||||
sourceFileNames[0],
|
||||
sourceFileNames[1],
|
||||
imageFileName,
|
||||
videoFileName,
|
||||
baseUrl,
|
||||
outputDir,
|
||||
{ width: 720, height: 1280 }
|
||||
);
|
||||
path.join(outputDir, videoFileName);
|
||||
return { imagePath: imagePath, videoPath };
|
||||
return generatedImagePath;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to generate styled video for ${imageFileName} on server ${baseUrl}:`, error);
|
||||
logger.error(`Failed to generate image for ${imageFileName} on server ${baseUrl}:`, error);
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
await fs.unlink(renamedImagePath);
|
||||
logger.debug(`Deleted renamed source image: ${renamedImagePath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete renamed source image ${renamedImagePath}:`, error);
|
||||
for (const sourcePath of renamedImagePaths) {
|
||||
try {
|
||||
await fs.unlink(sourcePath);
|
||||
logger.debug(`Deleted source image: ${sourcePath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete source image ${sourcePath}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function worker(id: number, server: { baseUrl: string; outputDir: string; }, taskQueue: GenerationTask[]) {
|
||||
logger.info(`Worker ${id} started for server ${server.baseUrl}`);
|
||||
while (taskQueue.length > 0) {
|
||||
const task = taskQueue.shift();
|
||||
if (task) {
|
||||
logger.info(`Worker ${id} processing task: ${task.imageFileName}`);
|
||||
const result = await generateImageAndVideo(task, server);
|
||||
if (result) {
|
||||
try {
|
||||
const videoData = {
|
||||
genre: task.genre,
|
||||
sub_genre: task.subGenre,
|
||||
scene: task.scene,
|
||||
action: task.action,
|
||||
camera: task.camera,
|
||||
image_prompt: task.imagePrompt,
|
||||
video_prompt: task.videoPrompt,
|
||||
image_path: result.imagePath,
|
||||
video_path: result.videoPath,
|
||||
};
|
||||
const videoId = await VideoModel.create(videoData);
|
||||
logger.info(`Successfully saved video record to database with ID: ${videoId}`);
|
||||
|
||||
const newImageName = `${videoId}_${task.genre}_${task.subGenre}${path.extname(result.imagePath)}`;
|
||||
const newVideoName = `${videoId}_${task.genre}_${task.subGenre}${path.extname(result.videoPath)}`;
|
||||
|
||||
const newImagePath = path.join(path.dirname(result.imagePath), newImageName);
|
||||
const newVideoPath = path.join(path.dirname(result.videoPath), newVideoName);
|
||||
|
||||
await fs.rename(result.imagePath, newImagePath);
|
||||
await fs.rename(result.videoPath, newVideoPath);
|
||||
|
||||
await VideoModel.update(videoId, {
|
||||
image_path: newImagePath,
|
||||
video_path: newVideoPath,
|
||||
});
|
||||
logger.info(`Renamed files and updated database record for video ID: ${videoId}`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to save video record to database or rename files:', error);
|
||||
}
|
||||
for (const fileName of sourceFileNames) {
|
||||
try {
|
||||
const serverPath = path.join(inputDir, fileName);
|
||||
await fs.unlink(serverPath);
|
||||
logger.debug(`Deleted server image: ${serverPath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete server image ${fileName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info(`Worker ${id} finished.`);
|
||||
}
|
||||
|
||||
async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
@ -212,6 +190,7 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const generationTasks: GenerationTask[] = [];
|
||||
|
||||
for (const genreSubGenre of keywords) {
|
||||
|
||||
@ -235,8 +214,6 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const generationTasks: GenerationTask[] = [];
|
||||
|
||||
logger.info(`--- Starting processing for pin: ${pin} ---`);
|
||||
const downloadedImagePaths = await downloadImagesFromPinterestPin(pin);
|
||||
if (downloadedImagePaths.length === 0) {
|
||||
@ -244,36 +221,107 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const selectedImages = downloadedImagePaths.sort(() => 0.5 - Math.random()).slice(0, 1);
|
||||
const selectedImages = downloadedImagePaths.sort(() => 0.5 - Math.random()).slice(0, 2);
|
||||
logger.info(`--- Randomly selected ${selectedImages.length} images for processing ---`);
|
||||
|
||||
for (const imagePath of selectedImages) {
|
||||
const pinId = path.basename(imagePath, path.extname(imagePath)).split('_related_')[0];
|
||||
if (selectedImages.length === 2) {
|
||||
const pinId = path.basename(selectedImages[0], path.extname(selectedImages[0])).split('_related_')[0];
|
||||
const pinUrl = `https://www.pinterest.com/pin/${pinId}/`;
|
||||
const task = await getPromptsForImage(imagePath, pinUrl, genre, subGenre);
|
||||
const task = await getPromptsForImage(selectedImages, pinUrl, genre, subGenre);
|
||||
if (task) {
|
||||
generationTasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
const unselectedImages = downloadedImagePaths.filter(p => !selectedImages.includes(p));
|
||||
for (const imagePath of unselectedImages) {
|
||||
try {
|
||||
} else {
|
||||
logger.warn(`Skipping pin ${pin} as it did not have at least 2 images.`);
|
||||
for (const imagePath of selectedImages) {
|
||||
await fs.unlink(imagePath);
|
||||
logger.debug(`Deleted unselected image: ${imagePath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete unselected image ${imagePath}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (generationTasks.length > 0) {
|
||||
logger.info(`--- Starting parallel generation of ${generationTasks.length} tasks across ${servers.length} servers ---`);
|
||||
const workers = servers.map((server, index) => worker(index + 1, server, generationTasks));
|
||||
await Promise.all(workers);
|
||||
logger.info("--- Finished parallel generation ---");
|
||||
// --- Image Generation Phase ---
|
||||
logger.info(`--- Starting image generation for ${generationTasks.length} tasks ---`);
|
||||
for (const task of generationTasks) {
|
||||
const server = servers[Math.floor(Math.random() * servers.length)];
|
||||
const imagePath = await generateImageForTask(task, server);
|
||||
if (imagePath) {
|
||||
task.generatedImagePath = imagePath;
|
||||
}
|
||||
}
|
||||
logger.info("--- Finished image generation ---");
|
||||
|
||||
// --- Video Generation Phase ---
|
||||
logger.info(`--- Starting video generation for ${generationTasks.length} tasks ---`);
|
||||
for (const task of generationTasks) {
|
||||
if (!task.generatedImagePath) {
|
||||
logger.warn(`Skipping video generation for task ${task.imageFileName} as it has no generated image.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const server = servers[Math.floor(Math.random() * servers.length)];
|
||||
const inputDir = server.outputDir.replace("output", "input");
|
||||
const generatedImageName = path.basename(task.generatedImagePath);
|
||||
const serverImagePath = path.join(inputDir, generatedImageName);
|
||||
|
||||
try {
|
||||
await fs.copyFile(task.generatedImagePath, serverImagePath);
|
||||
logger.info(`Copied ${task.generatedImagePath} to ${serverImagePath}`);
|
||||
|
||||
const videoFileName = task.imageFileName.replace('.png', '.mp4');
|
||||
const videoPath = await generateVideo(
|
||||
task.videoPrompt,
|
||||
generatedImageName,
|
||||
videoFileName,
|
||||
server.baseUrl,
|
||||
server.outputDir,
|
||||
{ width: 720, height: 1280 }
|
||||
);
|
||||
|
||||
if (videoPath) {
|
||||
const videoData = {
|
||||
genre: task.genre,
|
||||
sub_genre: task.subGenre,
|
||||
scene: task.scene,
|
||||
action: task.action,
|
||||
camera: task.camera,
|
||||
image_prompt: task.imagePrompt,
|
||||
video_prompt: task.videoPrompt,
|
||||
image_path: task.generatedImagePath,
|
||||
video_path: videoPath,
|
||||
};
|
||||
const videoId = await VideoModel.create(videoData);
|
||||
logger.info(`Successfully saved video record to database with ID: ${videoId}`);
|
||||
|
||||
const newImageName = `${videoId}_${task.genre}_${task.subGenre}${path.extname(task.generatedImagePath)}`;
|
||||
const newVideoName = `${videoId}_${task.genre}_${task.subGenre}${path.extname(videoPath)}`;
|
||||
|
||||
const newImagePath = path.join(path.dirname(task.generatedImagePath), newImageName);
|
||||
const newVideoPath = path.join(path.dirname(videoPath), newVideoName);
|
||||
|
||||
await fs.rename(task.generatedImagePath, newImagePath);
|
||||
await fs.rename(videoPath, newVideoPath);
|
||||
|
||||
await VideoModel.update(videoId, {
|
||||
image_path: newImagePath,
|
||||
video_path: newVideoPath,
|
||||
});
|
||||
logger.info(`Renamed files and updated database record for video ID: ${videoId}`);
|
||||
|
||||
await fs.unlink(newImagePath);
|
||||
logger.info(`Deleted paired image: ${newImagePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('An error occurred during video generation or database operations:', error);
|
||||
} finally {
|
||||
try {
|
||||
await fs.unlink(serverImagePath);
|
||||
logger.debug(`Deleted server image: ${serverImagePath}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete server image ${serverImagePath}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("--- Finished video generation ---");
|
||||
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user