This commit is contained in:
2025-10-02 07:35:43 +02:00
17 changed files with 3801 additions and 4372 deletions

View File

@ -15,3 +15,12 @@ Use this file src\lib\openai.ts
for just run prompt for just run prompt
- async function callOpenAIWithFile(imagePath: string, prompt: string): Promise<any> - async function callOpenAIWithFile(imagePath: string, prompt: string): Promise<any>
for send file to llm for send file to llm
Please construct prompt to return json alswasy for calling llm api to generate text.
If nothing specified add following instructin in the given prompt
Return the result in this forket
{"result":""}
Then extract the result param in program you generate, don't change the original function

View File

@ -10,7 +10,8 @@
"db:schema": "ts-node src/schema.ts", "db:schema": "ts-node src/schema.ts",
"db:test": "ts-node src/testmysql.ts", "db:test": "ts-node src/testmysql.ts",
"infinity:start": "ts-node src/infinityvideo_generator/start.ts", "infinity:start": "ts-node src/infinityvideo_generator/start.ts",
"convert:pinterest-face": "ts-node src/imageconverter/pinterest_face_portrait.ts" "convert:pinterest-face": "ts-node src/imageconverter/pinterest_face_portrait.ts",
"tool:generate-video-from-input": "ts-node src/tools/generateVideoFromInput.ts"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

View File

@ -0,0 +1,229 @@
{
"1": {
"inputs": {
"clip_name1": "clip_l.safetensors",
"clip_name2": "t5xxl_fp16.safetensors",
"type": "flux",
"device": "default"
},
"class_type": "DualCLIPLoader",
"_meta": {
"title": "DualCLIPLoader"
}
},
"2": {
"inputs": {
"unet_name": "flux1-krea-dev_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"class_type": "UNETLoader",
"_meta": {
"title": "Load Diffusion Model"
}
},
"3": {
"inputs": {
"width": 720,
"height": 1280,
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "EmptySD3LatentImage"
}
},
"4": {
"inputs": {
"seed": 445107772143446,
"steps": 20,
"cfg": 1,
"sampler_name": "euler",
"scheduler": "simple",
"denoise": 1,
"model": [
"2",
0
],
"positive": [
"11",
0
],
"negative": [
"5",
0
],
"latent_image": [
"3",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"5": {
"inputs": {
"conditioning": [
"14",
0
]
},
"class_type": "ConditioningZeroOut",
"_meta": {
"title": "ConditioningZeroOut"
}
},
"6": {
"inputs": {
"samples": [
"4",
0
],
"vae": [
"12",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"8": {
"inputs": {
"style_model_name": "flux1-redux-dev.safetensors"
},
"class_type": "StyleModelLoader",
"_meta": {
"title": "Load Style Model"
}
},
"9": {
"inputs": {
"crop": "center",
"clip_vision": [
"10",
0
],
"image": [
"13",
0
]
},
"class_type": "CLIPVisionEncode",
"_meta": {
"title": "CLIP Vision Encode"
}
},
"10": {
"inputs": {
"clip_name": "sigclip_vision_patch14_384.safetensors"
},
"class_type": "CLIPVisionLoader",
"_meta": {
"title": "Load CLIP Vision"
}
},
"11": {
"inputs": {
"strength": 0.30000000000000004,
"conditioning": [
"14",
0
],
"style_model": [
"8",
0
],
"clip_vision_output": [
"9",
0
]
},
"class_type": "ApplyStyleModelAdjust",
"_meta": {
"title": "Apply Style Model (Adjusted)"
}
},
"12": {
"inputs": {
"vae_name": "ae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "Load VAE"
}
},
"13": {
"inputs": {
"image": "281543725739981_1759177922955_0.png"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image 1"
}
},
"14": {
"inputs": {
"text": "realistic photo of woman, wavy long blong hair, fullbody shot,, A dynamic dance scene begins with a distorted glitch effect mirroring the images grayscale aesthetic, quickly transitioning into a vibrant, fast-paced choreography featuring dancers in similar pale makeup and unsettling expressions. The music is electronic with heavy bass and industrial elements. The camera work should be kinetic and disorienting, utilizing quick cuts and unconventional angles, emphasizing the feeling of being trapped or haunted. The dance evolves from frantic movements to controlled yet eerie poses that echo the images gesture of covering the face. The setting changes between a stark white room similar to the image's background and abstract digital landscapes.",
"clip": [
"1",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"15": {
"inputs": {
"filename_prefix": "STYLEDVIDEOMAKER",
"images": [
"20",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
},
"19": {
"inputs": {
"image": "face.png"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image"
}
},
"20": {
"inputs": {
"enabled": true,
"swap_model": "inswapper_128.onnx",
"facedetection": "retinaface_resnet50",
"face_restore_model": "GPEN-BFR-2048.onnx",
"face_restore_visibility": 1,
"codeformer_weight": 1,
"detect_gender_input": "no",
"detect_gender_source": "no",
"input_faces_index": "0",
"source_faces_index": "0",
"console_log_level": 1,
"input_image": [
"6",
0
],
"source_image": [
"19",
0
]
},
"class_type": "ReActorFaceSwap",
"_meta": {
"title": "ReActor 🌌 Fast Face Swap"
}
}
}

View File

@ -30,7 +30,7 @@ async function generateImage(
workflow['13']['inputs']['image'] = imageName1; workflow['13']['inputs']['image'] = imageName1;
// Set image name // Set image name
//workflow['16']['inputs']['image'] = imageName2; workflow['16']['inputs']['image'] = imageName2;
workflow['3']['inputs']['width'] = size.width; workflow['3']['inputs']['width'] = size.width;
workflow['3']['inputs']['height'] = size.height; workflow['3']['inputs']['height'] = size.height;

View File

@ -0,0 +1,78 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
interface ImageSize {
width: number;
height: number;
}
async function generateImage(
prompt: string,
faceImage: string,
styleImage: string,
newFileName: string,
comfyBaseUrl: string,
comfyOutputDir: string,
size: ImageSize = { width: 720, height: 1280 }
): Promise<string> {
const COMFY_BASE_URL = comfyBaseUrl.replace(/\/$/, '');
const COMFY_OUTPUT_DIR = comfyOutputDir;
let workflow;
workflow = JSON.parse(await fs.readFile('src/comfyworkflows/generate_image_style_faceswap.json', 'utf-8'));
workflow['14']['inputs']['text'] = prompt;
// Set image name
workflow['13']['inputs']['image'] = styleImage;
// Set image name
workflow['19']['inputs']['image'] = faceImage;
workflow['3']['inputs']['width'] = size.width;
workflow['3']['inputs']['height'] = size.height;
const response = await axios.post(`${COMFY_BASE_URL}/prompt`, { prompt: workflow });
const promptId = response.data.prompt_id;
let history;
do {
await new Promise(resolve => setTimeout(resolve, 1000));
const historyResponse = await axios.get(`${COMFY_BASE_URL}/history/${promptId}`);
history = historyResponse.data[promptId];
} while (!history || Object.keys(history.outputs).length === 0);
const files = await fs.readdir(COMFY_OUTPUT_DIR!);
const generatedFiles = files.filter(file => file.startsWith('STYLEDVIDEOMAKER'));
const fileStats = await Promise.all(
generatedFiles.map(async (file) => {
const stat = await fs.stat(path.join(COMFY_OUTPUT_DIR!, file));
return { file, mtime: stat.mtime };
})
);
fileStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
const latestFile = fileStats[0].file;
const newFilePath = path.resolve('./generated', newFileName);
await fs.mkdir('./generated', { recursive: true });
const sourcePath = path.join(COMFY_OUTPUT_DIR!, latestFile);
try {
await fs.unlink(newFilePath);
} catch (err) {
// ignore if not exists
}
await fs.copyFile(sourcePath, newFilePath);
return newFilePath;
}
export { generateImage };

View File

@ -108,6 +108,7 @@ async function callLMStudioAPIWithFile(imagePath: string, prompt: string): Promi
return JSON.parse(arrayMatch[0]); return JSON.parse(arrayMatch[0]);
} }
} }
return content;
} else { } else {
logger.error('Unexpected API response:', data); logger.error('Unexpected API response:', data);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -1,19 +1,28 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import crypto from 'crypto'; import crypto from 'crypto';
import { generateImage } from '../../lib/image-generator'; import { generateImage } from '../../lib/image-generator-style-faceswap';
import { logger } from '../../lib/logger'; import { logger } from '../../lib/logger';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
const scenesFilePath = path.resolve(process.cwd(), 'src/musicspot_generator/v2/scenes.json'); const scenesFilePath = path.resolve(process.cwd(), 'src/musicspot_generator/v2/scenes.json');
const faceFilePath = path.resolve(process.cwd(), 'src/musicspot_generator/v2/face.png');
const GENERATED_DIR = path.resolve('generated'); const GENERATED_DIR = path.resolve('generated');
const DEFAULT_SIZE = { width: 1280, height: 720 }; const DEFAULT_SIZE = { width: 1280, height: 720 };
interface Scene { interface Scene {
scene: string; scene: string;
imagePrompt: string; imagePrompt: {
description: string,
style: string,
lighting: string,
outfit: string,
location: string,
poses: string,
angle: string,
};
videoPromp: string; videoPromp: string;
baseImagePath: string; baseImagePath: string;
} }
@ -43,11 +52,12 @@ async function generatePhotos() {
try { try {
await generateImage( await generateImage(
scene.imagePrompt, `realistic photo of woman, wavy long brown hair, fullbody shot, ${scene.imagePrompt.location},${scene.imagePrompt.angle},${scene.imagePrompt.lighting},${scene.imagePrompt.outfit}`,
faceFilePath,
scene.baseImagePath,
imgFileName, imgFileName,
COMFY_BASE_URL, COMFY_BASE_URL,
COMFY_OUTPUT_DIR, COMFY_OUTPUT_DIR,
'flux',
DEFAULT_SIZE DEFAULT_SIZE
); );
logger.info(`Successfully generated photo: ${imgFileName}`); logger.info(`Successfully generated photo: ${imgFileName}`);

View File

@ -4,9 +4,9 @@ import { callLMStudioAPIWithFile } from '../../lib/lmstudio';
import { logger } from '../../lib/logger'; import { logger } from '../../lib/logger';
const promptInstructions = ` const promptInstructions = `
Video prompt: No slowmotion, Be creative and generate dynamic dance scene. Video prompt: No slowmotion, Be creative and generate gengle action scene.
`; `;
6
const inputDir = path.resolve(process.cwd(), 'input'); const inputDir = path.resolve(process.cwd(), 'input');
const outputFilePath = path.resolve(process.cwd(), 'src/musicspot_generator/v2/scenes.json'); const outputFilePath = path.resolve(process.cwd(), 'src/musicspot_generator/v2/scenes.json');

View File

@ -21,6 +21,8 @@ async function processScenes() {
const scenes: Scene[] = JSON.parse(scenesData).scenes; const scenes: Scene[] = JSON.parse(scenesData).scenes;
for (const scene of scenes) { for (const scene of scenes) {
try {
const hash = crypto.createHash('sha256').update(scene.baseImagePath).digest('hex'); const hash = crypto.createHash('sha256').update(scene.baseImagePath).digest('hex');
const imageFileName = `${hash}.png`; const imageFileName = `${hash}.png`;
const imagePath = path.join(generatedFolderPath, imageFileName); const imagePath = path.join(generatedFolderPath, imageFileName);
@ -48,6 +50,10 @@ async function processScenes() {
} else { } else {
console.warn(`Image not found for scene ${scene.scene}: ${imagePath}`); console.warn(`Image not found for scene ${scene.scene}: ${imagePath}`);
} }
} catch (e) {
continue;
}
} }
} catch (error) { } catch (error) {
console.error('Error processing scenes:', error); console.error('Error processing scenes:', error);

View File

@ -0,0 +1,68 @@
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import { generateVideo } from '../../lib/video-generator';
import { callLMStudioAPIWithFile } from '../../lib/lmstudio';
import dotenv from 'dotenv';
dotenv.config();
const inputFolderPath = path.join(__dirname, '..', '..', '..', 'input');
const generatedFolderPath = path.join(__dirname, '..', '..', '..', 'generated');
async function processImages() {
try {
const imageFiles = fs.readdirSync(inputFolderPath);
for (const imageFile of imageFiles) {
const imagePath = path.join(inputFolderPath, imageFile);
try {
const hash = crypto.createHash('sha256').update(imageFile).digest('hex');
const outputVideoFileName = `${hash}.mp4`;
const outputVideoPath = path.join(generatedFolderPath, outputVideoFileName);
if (fs.existsSync(outputVideoPath)) {
console.log(`Video already exists for image ${imageFile}, skipping.`);
continue;
}
console.log(`Generating video prompt for image ${imageFile}...`);
const promptResult = await callLMStudioAPIWithFile(
imagePath,
`
Generate a short, dancing video prompt for an image located at ${imagePath}.
Return the result in this format: {"result":""}
Instruction:
- Find best dancing expression based on the photo
`);
const videoPrompt = promptResult.result;
console.log(`Video prompt ${videoPrompt}`);
if (!videoPrompt) {
console.error(`Could not generate video prompt for image ${imageFile}`);
continue;
}
console.log(`Generating video for image ${imageFile}...`);
await generateVideo(
videoPrompt,
imagePath,
outputVideoPath,
process.env.COMFY_BASE_URL!,
process.env.COMFY_OUTPUT_DIR!,
{ width: 1280, height: 720 }
);
console.log(`Video for image ${imageFile} saved to ${outputVideoPath}`);
} catch (e) {
console.error(`Error processing image ${imageFile}:`, e);
continue;
}
}
} catch (error) {
console.error('Error processing images:', error);
}
}
processImages();

View File

@ -13,8 +13,8 @@ const PINS_TO_COLLECT = 5;
// Hard-coded user prompt // Hard-coded user prompt
const HARDCODED_USER_PROMPT = process.env.HARDCODED_USER_PROMPT || ` const HARDCODED_USER_PROMPT = process.env.HARDCODED_USER_PROMPT || `
Generate 20 keywords for photos of group of people dancing together focus on street and urban style. All keywords shoudld contain \"group horizontal\" and what you create. Generate 20 keywords for photos of a girl in scary scene for music video for haloween. All keywords should containe girl in a scene.
Example output : ["group horizontal hiphop dance","group horizontal modern dance","",... and 20 items in array] Example output : ["a girl in grave yard,"a girl in scary forest","",... and 20 items in array]
`; `;
async function getPinUrlsFromPinterest(keyword: string, scrollCount = SCROLL_SEARCH, limit = PINS_TO_COLLECT): Promise<string[]> { async function getPinUrlsFromPinterest(keyword: string, scrollCount = SCROLL_SEARCH, limit = PINS_TO_COLLECT): Promise<string[]> {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import dotenv from 'dotenv';
import { callLMStudioAPIWithFile, callLmstudio } from '../lib/lmstudio';
import { convertImage } from '../lib/image-converter';
import { logger } from '../lib/logger';
dotenv.config();
const COMFY_BASE_URL = process.env.SERVER2_COMFY_BASE_URL;
const COMFY_OUTPUT_DIR = process.env.SERVER2_COMFY_OUTPUT_DIR;
const INPUT_DIR = './input';
const GENERATED_DIR = './generated';
async function batchConvert() {
if (!COMFY_BASE_URL || !COMFY_OUTPUT_DIR) {
throw new Error('ComfyUI server details are not defined in the .env file');
}
try {
await fs.mkdir(GENERATED_DIR, { recursive: true });
const files = await fs.readdir(INPUT_DIR);
for (const file of files) {
try {
const inputFile = path.join(INPUT_DIR, file);
logger.info(`Processing ${inputFile}`);
const firstPrompt = `
Read the file in the photo and detect the main subject. Extract the following information:
- what is the main subject in one word
- describe character in 5 words
- describe background in 5 words
- 3 ideas to make this character's look, 2 or 3 words per idea
Return the result in this format:
{ "character":"", "characterDescription": "", "background":"", "idea":"idea1, idea2, idea3"}
`;
const imageInfo = await callLMStudioAPIWithFile(inputFile, firstPrompt);
const { character, idea, background } = imageInfo;
const ideas = idea.split(',').map((i: string) => i.trim());
const secondPrompt = `
Generate a prompt to convert the photo to a group dancing photo.
I need only the prompt in this format, main subject is ${character}
{"result":"
Change the camera angle to a full-body shot, place many ${character} in the scene messily,
Change lighting to silhouette lighting,
Change the style for each body using these ideas: ${ideas.join(', ')},
Change color of each body,
Change the background in horror movie style of ${background},
Overall photo should be realistic and spooky,
"} `;
const promptResult = await callLmstudio(secondPrompt);
const finalPrompt = promptResult.result;
const inputFolderFullPath = COMFY_OUTPUT_DIR.replace("output", "input");
const serverInputFile = path.join(inputFolderFullPath, file);
await fs.copyFile(inputFile, serverInputFile);
logger.info(`Generating image for ${file} with prompt: ${finalPrompt}`);
const generatedFile = await convertImage(finalPrompt, file, COMFY_BASE_URL, COMFY_OUTPUT_DIR);
const finalOutputPath = path.join(GENERATED_DIR, path.basename(generatedFile));
await fs.copyFile(generatedFile, finalOutputPath);
logger.info(`Saved converted image to ${finalOutputPath}`);
} catch (e) {
logger.error('An error occurred during batch conversion:', e);
continue;
}
}
} catch (error) {
logger.error('An error occurred during batch conversion:', error);
}
}
batchConvert();

View File

@ -0,0 +1,74 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import { generateVideo } from '../lib/video-generator';
import dotenv from 'dotenv';
dotenv.config();
const inputFolder = './input';
const prompt = "a girl making a cute pose, static camera";
b
async function main() {
try {
const files = await fs.readdir(inputFolder);
const pngFiles = files.filter(file => path.extname(file).toLowerCase() === '.png');
if (pngFiles.length === 0) {
console.log('No PNG files found in the input folder.');
return;
}
const comfyBaseUrl = process.env.SERVER2_COMFY_BASE_URL;
const comfyOutputDir = process.env.SERVER2_COMFY_OUTPUT_DIR;
if (!comfyBaseUrl || !comfyOutputDir) {
console.error('Please define SERVER1_COMFY_BASE_URL and SERVER1_COMFY_OUTPUT_DIR in your .env file.');
return;
}
const comfyInputDir = comfyOutputDir.replace('output', 'input');
for (const file of pngFiles) {
const inputImagePath = path.join(inputFolder, file);
const comfyInputImagePath = path.join(comfyInputDir, file);
console.log(`Processing ${file}...`);
try {
await fs.access(inputImagePath);
// Copy file to comfy input directory
await fs.copyFile(inputImagePath, comfyInputImagePath);
console.log(`Copied ${file} to ComfyUI input directory.`);
} catch (e) {
console.error(`Error copying file ${file}:`, e);
continue;
}
const newFileName = `${path.parse(file).name}.mp4`;
try {
const generatedVideoPath = await generateVideo(
prompt,
file, // Pass only the filename as per instructions
newFileName,
comfyBaseUrl,
comfyOutputDir
);
console.log(`Successfully generated video for ${file} at: ${generatedVideoPath}`);
} catch (e: any) {
if (e.code === 'ECONNREFUSED' || e.code === 'ETIMEDOUT') {
console.error(`\nError: Connection to ComfyUI server at ${comfyBaseUrl} failed.`);
console.error('Please ensure the ComfyUI server is running and accessible.');
break;
} else {
console.error(`An error occurred while generating video for ${file}:`, e);
}
}
}
} catch (error) {
console.error('An unexpected error occurred:', error);
}
}
main();

View File

@ -3,20 +3,57 @@ import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';
import { logger } from '../lib/logger'; import { logger } from '../lib/logger';
import { getPinUrlFromPinterest, downloadImageFromPin } from '../lib/pinterest'; import { getPinUrlFromPinterest, downloadImageFromPin } from '../lib/pinterest';
import { convertImage } from '../lib/image-converter'; import { generateImage } from '../lib/image-generator-face';
import { callLMStudioAPIWithFile } from '../lib/lmstudio';
dotenv.config(); dotenv.config();
const MODE: "keywords" | "pinIds" = "keywords";
const KEYWORDS = [ const KEYWORDS = [
'fullbody pose cute ', 'a girl in scary forest',
'fullbody pose model ', 'a girl in graveyard',
'fullbody pose woman ', ''];
'fullbody pose idol ',
'fullbody pose kawaii', const pinIDs = [
'fullbody pose japanese', "22377329393970367",
'fullbody pose kawaii sit', "18999629674210230",
'fullbody pose model sit', "3166662232983784",
'fullbody pose cute sit',]; "291537775902572408",
"2744449769232655",
"9429480465939847",
"34058540926328062",
"1071153092617107265",
"6825836928646465",
"1407443629997072",
"333407178685095962",
"15833036184288417",
"6825836928284784",
"2181499815469509",
"199706564723106062",
"1759287348280571",
"56083957854040032",
"3025924743999802",
"2955556001576084",
"1407443627212889",
"836965911982723974",
"97460779431981493",
"282600945363725869",
"/59532026387104913",
"70437490453979",
"152489137384620437",
"50947039528553588",
"73042825197955754",
"624593042089280419",
"351912466315529",
"624030092104188250",
"21673641951379251",
"27021666506512503",
"3377768467678091",
"985231163409578",
"17240411068654164"
]
const TARGET_COUNT = Number(process.env.IMAGE_COUNT || 20); const TARGET_COUNT = Number(process.env.IMAGE_COUNT || 20);
const PROMPT = const PROMPT =
` `
@ -24,6 +61,12 @@ const PROMPT =
change background to light gray with faing gradient, change background to light gray with faing gradient,
change clothes to shite sports bra and shite cotton short pants change clothes to shite sports bra and shite cotton short pants
`; `;
const LmStudioPrompt = `
describe the image in 50 words including, scene, lighting, describe character(s), add some beautiful accent like light, fog, starts, lamps, whatever suits with scene.
then return in this format
{"prompt":""}
`;
type ServerCfg = { baseUrl: string; outputDir: string; inputDir: string }; type ServerCfg = { baseUrl: string; outputDir: string; inputDir: string };
@ -60,6 +103,7 @@ async function collectImages(keyword: string, total: number): Promise<string[]>
// ensure local download dir exists (pinterest.ts also ensures it, but harmless here) // ensure local download dir exists (pinterest.ts also ensures it, but harmless here)
await ensureDir(path.join(process.cwd(), 'download')); await ensureDir(path.join(process.cwd(), 'download'));
if (MODE == "keywords") {
while (results.length < total) { while (results.length < total) {
try { try {
const pinUrl = await getPinUrlFromPinterest(keyword); const pinUrl = await getPinUrlFromPinterest(keyword);
@ -84,13 +128,40 @@ async function collectImages(keyword: string, total: number): Promise<string[]>
await sleep(2000); await sleep(2000);
} }
} }
} else if (MODE == "pinIds") {
while (results.length < total) {
const shuffledPinIds = pinIDs.slice().sort(() => 0.5 - Math.random());
const pinUrl = `https://www.pinterest.com/pin/${shuffledPinIds[0]}`;
try {
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); return results.slice(0, total);
} }
let imageIndex = 0;
async function processImages(imagePaths: string[], server: ServerCfg) { async function processImages(imagePaths: string[], server: ServerCfg) {
await ensureDir(server.inputDir); await ensureDir(server.inputDir);
for (const localImagePath of imagePaths) { for (const localImagePath of imagePaths) {
const response = await callLMStudioAPIWithFile(localImagePath, LmStudioPrompt);
const prompt = response.prompt;
const baseName = path.basename(localImagePath); const baseName = path.basename(localImagePath);
const serverInputPath = path.join(server.inputDir, baseName); const serverInputPath = path.join(server.inputDir, baseName);
@ -100,14 +171,16 @@ async function processImages(imagePaths: string[], server: ServerCfg) {
logger.info(`Copied ${localImagePath} -> ${serverInputPath}`); logger.info(`Copied ${localImagePath} -> ${serverInputPath}`);
// Run conversion (sequential to avoid output race conditions) // Run conversion (sequential to avoid output race conditions)
const generatedPath = await convertImage( const generatedPath = await generateImage(
PROMPT, `ultra realistic photo, ${prompt}`,
baseName, baseName,
`monster_${imageIndex}.png`,
server.baseUrl, server.baseUrl,
server.outputDir, server.outputDir,
{ width: 720, height: 1280 } // portrait { width: 1280, height: 720 } // portrait
); );
logger.info(`Generated image: ${generatedPath}`); logger.info(`Generated image: ${generatedPath}`);
imageIndex++;
} catch (err) { } catch (err) {
logger.error(`Failed to convert ${localImagePath}:`, err); logger.error(`Failed to convert ${localImagePath}:`, err);
} finally { } finally {
@ -125,6 +198,16 @@ async function processImages(imagePaths: string[], server: ServerCfg) {
async function main() { async function main() {
const server = getServerConfig(); const server = getServerConfig();
const files = await fs.readdir(path.join(process.cwd(), 'generated'));
const monsterFiles = files.filter((f) => f.startsWith('monster_'));
if (monsterFiles.length > 0) {
const latestFile = monsterFiles.sort().pop();
if (latestFile) {
const latestIndex = parseInt(latestFile.replace('monster_', '').replace('.png', ''));
imageIndex = latestIndex + 1;
}
}
// Infinite loop as requested // Infinite loop as requested
while (true) { while (true) {

View File

@ -1,17 +1,23 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { convertImageVton } from '../lib/image-converter'; import { convertImageVton, convertImage } from '../lib/image-converter';
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
dotenv.config(); dotenv.config();
<<<<<<< HEAD
const modelsBodyDir = 'D:\\CatsEye\\long videos\\vton-demo\\VTON\\models_body'; const modelsBodyDir = 'D:\\CatsEye\\long videos\\vton-demo\\VTON\\models_body';
const clothesDir = 'D:\\CatsEye\\long videos\\vton-demo\\VTON\\clothes'; const clothesDir = 'D:\\CatsEye\\long videos\\vton-demo\\VTON\\clothes';
const posesDir = 'D:\\CatsEye\\long videos\\vton-demo\\VTON\\poses'; const posesDir = 'D:\\CatsEye\\long videos\\vton-demo\\VTON\\poses';
=======
const clothesDir = 'C:\\Users\\fm201\\Documents\\VTON\\\clothes';
const modelPath = 'C:\\Users\\fm201\\Documents\\VTON\\models\\Jessica_body.png';
const posesDir = 'C:\\Users\\fm201\\Documents\\VTON\\\poses';
>>>>>>> bdca42e82102a00f771ecf58b4ff0673dbd218af
const outputDir = 'generated'; const outputDir = 'generated';
const comfyBaseUrl = process.env.SERVER1_COMFY_BASE_URL; const comfyBaseUrl = process.env.SERVER2_COMFY_BASE_URL;
const comfyOutputDir = process.env.SERVER1_COMFY_OUTPUT_DIR; const comfyOutputDir = process.env.SERVER2_COMFY_OUTPUT_DIR;
function getNextIndex(directory: string): number { function getNextIndex(directory: string): number {
if (!fs.existsSync(directory)) { if (!fs.existsSync(directory)) {