Files
RandomVideoMaker/src/tools/vton_generator.ts
2025-10-05 11:56:31 +02:00

157 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as fs from 'fs';
import * as path from 'path';
import { convertImageVton, convertImage } from '../lib/image-converter';
import * as dotenv from 'dotenv';
import sharp from 'sharp';
dotenv.config();
const clothesDir = 'D:\\projects\\random_video_maker\\input';
const outputDir = 'generated';
const comfyBaseUrl = process.env.SERVER1_COMFY_BASE_URL;
const comfyOutputDir = process.env.SERVER1_COMFY_OUTPUT_DIR;
function getNextIndex(directory: string): number {
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
return 0;
}
const dirs = fs.readdirSync(directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
const vtonDirs = dirs.filter(dir => dir.startsWith('vton_'));
if (vtonDirs.length === 0) {
return 0;
}
const indices = vtonDirs.map(dir => {
const match = dir.match(/vton_(\d+)/);
return match ? parseInt(match[1], 10) : -1;
});
return Math.max(...indices) + 1;
}
function getRandomFile(directory: string): string {
const files = fs.readdirSync(directory).filter(file => /\.(jpg|png|jpeg)$/i.test(file));
if (files.length === 0) {
throw new Error(`No image files found in directory: ${directory}`);
}
const randomFile = files[Math.floor(Math.random() * files.length)];
return path.join(directory, randomFile);
}
async function generateVtonImages() {
if (!comfyBaseUrl || !comfyOutputDir) {
throw new Error("ComfyUI URL or Output Directory is not set in environment variables.");
}
let index = getNextIndex(outputDir);
const comfyInputDir = comfyOutputDir.replace("output", "input");
while (true) { // Infinite loop
const iterationDir = path.join(outputDir, `vton_${index}`);
fs.mkdirSync(iterationDir, { recursive: true });
try {
const personOrigPath = getRandomFile(clothesDir);
const clothOrigPath = getRandomFile(clothesDir);
fs.copyFileSync(personOrigPath, path.join(iterationDir, '1-personOrig.png'));
fs.copyFileSync(clothOrigPath, path.join(iterationDir, '3-clothOrig.png'));
const personOrigFileName = path.basename(personOrigPath);
const clothOrigFileName = path.basename(clothOrigPath);
fs.copyFileSync(personOrigPath, path.join(comfyInputDir, personOrigFileName));
fs.copyFileSync(clothOrigPath, path.join(comfyInputDir, clothOrigFileName));
console.log(`Processing person: ${personOrigPath}, cloth: ${clothOrigPath}`);
const cleanePersonImagePath = await convertImage("请把姿势改成站立的,转换成全身照片。去掉衣服,只保留白色运动文胸和白色短裤。双脚保持赤脚。背景为浅灰色。", personOrigFileName, comfyBaseUrl, comfyOutputDir, { width: 720, height: 1280 });
fs.copyFileSync(cleanePersonImagePath, path.join(iterationDir, '2-personCleaned.png'));
const cleanedPersonFileName = path.basename(cleanePersonImagePath);
fs.copyFileSync(cleanePersonImagePath, path.join(comfyInputDir, cleanedPersonFileName));
const cleanedClothImagePath = await convertImage("请将图1中的上衣、下装和配饰分别提取出来放到同一个浅灰色的背景上。", clothOrigFileName, comfyBaseUrl, comfyOutputDir, { width: 720, height: 1280 });
fs.copyFileSync(cleanedClothImagePath, path.join(iterationDir, '4-clothCleaned.png'));
const cleanedClothFileName = path.basename(cleanedClothImagePath);
fs.copyFileSync(cleanedClothImagePath, path.join(comfyInputDir, cleanedClothFileName));
const outputFilename = `vton_final_${index}.png`;
const generatedImagePath = await convertImageVton(cleanedPersonFileName, cleanedClothFileName, outputFilename, comfyBaseUrl, comfyOutputDir, { width: 720, height: 1280 });
if (generatedImagePath) {
fs.copyFileSync(generatedImagePath, path.join(iterationDir, '5-finalResult.png'));
console.log(`Generated image saved to ${generatedImagePath}`);
// --- Create composite image ---
const imagePaths = [
path.join(iterationDir, '1-personOrig.png'),
path.join(iterationDir, '3-clothOrig.png'),
path.join(iterationDir, '2-personCleaned.png'),
path.join(iterationDir, '4-clothCleaned.png'),
path.join(iterationDir, '5-finalResult.png')
];
const resizedImages = [];
let totalWidth = 10; // Initial left margin
const resizedHeight = 720;
for (const imagePath of imagePaths) {
const image = sharp(imagePath);
const metadata = await image.metadata();
if (!metadata.width || !metadata.height) {
throw new Error(`Could not get metadata for image ${imagePath}`);
}
const resizedWidth = Math.round((metadata.width / metadata.height) * resizedHeight);
const resizedImageBuffer = await image.resize(resizedWidth, resizedHeight).toBuffer();
resizedImages.push({
buffer: resizedImageBuffer,
width: resizedWidth
});
totalWidth += resizedWidth + 10; // Add image width and right margin
}
const compositeOps = [];
let currentLeft = 10; // Start with left margin
for (const img of resizedImages) {
compositeOps.push({
input: img.buffer,
top: 10, // 10px top margin
left: currentLeft
});
currentLeft += img.width + 10; // Move to the next position
}
await sharp({
create: {
width: totalWidth,
height: 740,
channels: 4,
background: { r: 255, g: 255, b: 255, alpha: 1 }
}
})
.composite(compositeOps)
.toFile(path.join(iterationDir, 'process.png'));
console.log(`Generated composite image process.png in ${iterationDir}`);
// --- End of composite image creation ---
index++;
} else {
console.error(`Failed to generate image for index ${index}`);
}
} catch (error) {
console.error("An error occurred during image generation:", error);
// Optional: wait for a bit before retrying to avoid spamming errors
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
}
generateVtonImages().catch(console.error);