face generator
This commit is contained in:
172
src/imageconverter/pinterest_face_portrait.ts
Normal file
172
src/imageconverter/pinterest_face_portrait.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import dotenv from 'dotenv';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { logger } from '../lib/logger';
|
||||
import { getPinUrlFromPinterest, downloadImageFromPin } from '../lib/pinterest';
|
||||
import { convertImage } from '../lib/image-converter';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const KEYWORDS = [
|
||||
'teen girl portrait',
|
||||
'woman portrait',
|
||||
'woman face close',
|
||||
'teen face close',
|
||||
'beautiful woman face closeup',
|
||||
'beautiful teen',
|
||||
'russian teen',
|
||||
'skandinavian teen',
|
||||
'uk teen',
|
||||
'asian teen',
|
||||
'east european teen',
|
||||
'cute teen',
|
||||
'beautiful woman',
|
||||
'russian woman',
|
||||
'skandinavian woman',
|
||||
'uk woman',
|
||||
'asian woman',
|
||||
'east european woman',
|
||||
'cute woman',];
|
||||
const TARGET_COUNT = Number(process.env.IMAGE_COUNT || 20);
|
||||
const PROMPT =
|
||||
`change camera angle to closeup face from image1,
|
||||
change background to light gray with faing gradient,
|
||||
change face angle to look at directry look at camera˛
|
||||
change lighting to soft light,
|
||||
change face expression to neautral expression,
|
||||
change age to 20 years old,
|
||||
`;
|
||||
|
||||
type ServerCfg = { baseUrl: string; outputDir: string; inputDir: string };
|
||||
|
||||
function getServerConfig(): ServerCfg {
|
||||
const candidates = [
|
||||
{ baseUrl: process.env.SERVER1_COMFY_BASE_URL, outputDir: process.env.SERVER1_COMFY_OUTPUT_DIR },
|
||||
//{ baseUrl: process.env.SERVER2_COMFY_BASE_URL, outputDir: process.env.SERVER2_COMFY_OUTPUT_DIR },
|
||||
].filter((s): s is { baseUrl: string; outputDir: string } => !!s.baseUrl && !!s.outputDir);
|
||||
|
||||
if (candidates.length === 0) {
|
||||
throw new Error(
|
||||
'No ComfyUI server configured. Please set SERVER1_COMFY_BASE_URL/OUTPUT_DIR or SERVER2_COMFY_BASE_URL/OUTPUT_DIR in .env'
|
||||
);
|
||||
}
|
||||
const chosen = candidates[0];
|
||||
const inputDir = chosen.outputDir.replace('output', 'input');
|
||||
return { ...chosen, inputDir };
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((res) => setTimeout(res, ms));
|
||||
}
|
||||
|
||||
async function ensureDir(p: string) {
|
||||
try {
|
||||
await fs.mkdir(p, { recursive: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function collectImages(keyword: string, total: number): Promise<string[]> {
|
||||
const results: string[] = [];
|
||||
// ensure local download dir exists (pinterest.ts also ensures it, but harmless here)
|
||||
await ensureDir(path.join(process.cwd(), 'download'));
|
||||
|
||||
while (results.length < total) {
|
||||
try {
|
||||
const pinUrl = await getPinUrlFromPinterest(keyword);
|
||||
if (!pinUrl) {
|
||||
logger.warn('No pin URL found, retrying...');
|
||||
await sleep(1500);
|
||||
continue;
|
||||
}
|
||||
const remaining = total - results.length;
|
||||
// attempt to grab up to 5 per pin to reduce churn
|
||||
const batchTarget = Math.min(5, remaining);
|
||||
const imgs = await downloadImageFromPin(pinUrl, batchTarget);
|
||||
if (imgs && imgs.length > 0) {
|
||||
results.push(...imgs);
|
||||
logger.info(`Downloaded ${imgs.length} image(s) from ${pinUrl}.Progress: ${results.length}/${total}`);
|
||||
} else {
|
||||
logger.warn(`Pin yielded no downloadable images: ${pinUrl}`);
|
||||
}
|
||||
await sleep(1000 + Math.random() * 1000);
|
||||
} catch (err) {
|
||||
logger.error('Error while collecting images:', err);
|
||||
await sleep(2000);
|
||||
}
|
||||
}
|
||||
return results.slice(0, total);
|
||||
}
|
||||
|
||||
async function processImages(imagePaths: string[], server: ServerCfg) {
|
||||
await ensureDir(server.inputDir);
|
||||
|
||||
for (const localImagePath of imagePaths) {
|
||||
const baseName = path.basename(localImagePath);
|
||||
const serverInputPath = path.join(server.inputDir, baseName);
|
||||
|
||||
try {
|
||||
// copy source image into ComfyUI input dir so workflow node can find it by filename
|
||||
await fs.copyFile(localImagePath, serverInputPath);
|
||||
logger.info(`Copied ${localImagePath} -> ${serverInputPath}`);
|
||||
|
||||
// Run conversion (sequential to avoid output race conditions)
|
||||
const generatedPath = await convertImage(
|
||||
PROMPT,
|
||||
baseName,
|
||||
server.baseUrl,
|
||||
server.outputDir,
|
||||
{ width: 720, height: 1280 } // portrait
|
||||
);
|
||||
logger.info(`Generated image: ${generatedPath}`);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to convert ${localImagePath}:`, err);
|
||||
} finally {
|
||||
// cleanup both server input copy and local downloaded file
|
||||
try {
|
||||
await fs.unlink(serverInputPath);
|
||||
} catch { }
|
||||
try {
|
||||
await fs.unlink(localImagePath);
|
||||
} catch { }
|
||||
await sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const server = getServerConfig();
|
||||
|
||||
// Infinite loop as requested
|
||||
while (true) {
|
||||
|
||||
for (const KEYWORD of KEYWORDS) {
|
||||
logger.info(`Starting Pinterest image conversion loop for keyword: "${KEYWORD}" (target ${TARGET_COUNT})`);
|
||||
try {
|
||||
const images = await collectImages(KEYWORD, TARGET_COUNT);
|
||||
logger.info(`Collected ${images.length} image(s). Starting conversion...`);
|
||||
await processImages(images, server);
|
||||
logger.info('Iteration completed. Sleeping before next cycle...');
|
||||
} catch (err) {
|
||||
logger.error('Iteration failed:', err);
|
||||
}
|
||||
|
||||
// brief pause between iterations
|
||||
await sleep(10000);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
logger.error('UnhandledRejection:', reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
logger.error('UncaughtException:', err);
|
||||
});
|
||||
|
||||
main().catch((e) => {
|
||||
logger.error('Fatal error:', e);
|
||||
});
|
||||
Reference in New Issue
Block a user