diff --git a/src/comfyworkflows/generate_image_mix_style.json b/src/comfyworkflows/generate_image_mix_style.json index f582723..1dbc7ee 100644 --- a/src/comfyworkflows/generate_image_mix_style.json +++ b/src/comfyworkflows/generate_image_mix_style.json @@ -23,8 +23,8 @@ }, "3": { "inputs": { - "width": 320, - "height": 640, + "width": 720, + "height": 1280, "batch_size": 1 }, "class_type": "EmptySD3LatentImage", @@ -34,7 +34,7 @@ }, "4": { "inputs": { - "seed": 803508963683741, + "seed": 844515265883614, "steps": 20, "cfg": 1, "sampler_name": "euler", @@ -45,7 +45,7 @@ 0 ], "positive": [ - "18", + "11", 0 ], "negative": [ @@ -127,7 +127,7 @@ }, "11": { "inputs": { - "strength": 0.20000000000000004, + "strength": 0.6000000000000001, "conditioning": [ "14", 0 @@ -157,7 +157,7 @@ }, "13": { "inputs": { - "image": "fd2dc3bbc879703b03abf892d4189667.jpg" + "image": "7a103725df2576b79c7306f0d3050991.jpg" }, "class_type": "LoadImage", "_meta": { @@ -166,7 +166,7 @@ }, "14": { "inputs": { - "text": "Ethereal realistic girl with flowing blue hair, glowing sparkles and stardust, elegant backless dress shimmering with cosmic light, dreamy profile pose, soft glowing skin, fantasy night atmosphere, luminous and magical aesthetic", + "text": "scify movie scene", "clip": [ "1", 0 @@ -189,52 +189,5 @@ "_meta": { "title": "Save Image" } - }, - "16": { - "inputs": { - "image": "a09ee3d44fff05f20c88976555f8fa10.jpg" - }, - "class_type": "LoadImage", - "_meta": { - "title": "Load Image 2" - } - }, - "17": { - "inputs": { - "crop": "center", - "clip_vision": [ - "10", - 0 - ], - "image": [ - "16", - 0 - ] - }, - "class_type": "CLIPVisionEncode", - "_meta": { - "title": "CLIP Vision Encode" - } - }, - "18": { - "inputs": { - "strength": 0.20000000000000004, - "conditioning": [ - "11", - 0 - ], - "style_model": [ - "8", - 0 - ], - "clip_vision_output": [ - "17", - 0 - ] - }, - "class_type": "ApplyStyleModelAdjust", - "_meta": { - "title": "Apply Style Model (Adjusted)" - } } } \ No newline at end of file diff --git a/src/generateImage.ts b/src/generateImage.ts index 986a6dc..096b2ba 100644 --- a/src/generateImage.ts +++ b/src/generateImage.ts @@ -14,10 +14,10 @@ interface VideoRecord { } const servers = [ - /*{ + { 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, @@ -61,7 +61,7 @@ async function worker(server: any) { while (true) { await sleep(Math.random() * 3000); // Random delay const videosToProcess = (await query( - "SELECT * FROM video WHERE (video_path IS NULL OR video_path = '') AND modified_at < '2025-08-22' LIMIT 1" + "SELECT * FROM video WHERE (image_path IS NULL OR image_path = '') LIMIT 1" )) as any[]; if (videosToProcess.length === 0) { diff --git a/src/lib/image-generator-mix-style.ts b/src/lib/image-generator-mix-style.ts index 703f0d3..6af5d9b 100644 --- a/src/lib/image-generator-mix-style.ts +++ b/src/lib/image-generator-mix-style.ts @@ -30,7 +30,7 @@ async function generateImage( workflow['13']['inputs']['image'] = imageName1; // Set image name - workflow['16']['inputs']['image'] = imageName2; + //workflow['16']['inputs']['image'] = imageName2; workflow['3']['inputs']['width'] = size.width; workflow['3']['inputs']['height'] = size.height; diff --git a/src/pinterest_keywords.json b/src/pinterest_keywords.json index 030cdcc..53fd3ae 100644 --- a/src/pinterest_keywords.json +++ b/src/pinterest_keywords.json @@ -3043,7 +3043,7 @@ }, { "genre": "fantasy", - "subGenre": "ddreamy room", + "subGenre": "dreamy room", "pinIds": [ "2533343538161690", "22236591907669199", @@ -3645,6 +3645,34 @@ "64387469667053244" ] }, + { + "genre": "fantasy", + "subGenre": "Magics", + "pinIds": [ + "44332377578826021", + "914862421098890", + "105201341292428725", + "396035361002191644", + "41517627812390162", + "1618549864404055", + "31877109857926612", + "3518505954100471", + "35114072091147253", + "716846465726941759", + "17170042324986575", + "492649953385330", + "2111131072434296", + "11329436558460911", + "281543723928763", + "6122149487574205", + "1337074889544820", + "8655424281626310", + "4292562140536462", + "1970393583505897", + "37436240649695593", + "44332377578826021" + ] + }, { "genre": "fashion", "subGenre": "Accessories", diff --git a/src/piterest_styletransfer_video.ts b/src/piterest_styletransfer_video.ts index 0d14776..b5e68fd 100644 --- a/src/piterest_styletransfer_video.ts +++ b/src/piterest_styletransfer_video.ts @@ -1,7 +1,8 @@ import { downloadImagesFromPinterestPin } from './lib/downloader'; import { callOpenAIWithFile } from './lib/openai'; import { generateVideo } from './lib/video-generator'; -import { generateImage } from './lib/image-generator-mix-style'; +import { generateImage as generateImageMixStyle } from './lib/image-generator-mix-style'; +import { generateImage as generateImage } from './lib/image-generator'; import { logger } from './lib/logger'; import * as fs from 'fs/promises'; import dotenv from 'dotenv'; @@ -11,6 +12,8 @@ import { VideoModel } from './lib/db/video'; dotenv.config(); +const USE_REFERENCE_IMAGE = (process.env.USE_REFERENCE_IMAGE || 'true').toLowerCase() === 'true'; + // Utility: extract JSON substring from a text. // Tries fenced ```json``` blocks first, otherwise extracts first {...} span. @@ -248,32 +251,47 @@ async function generateImageForTask(task: GenerationTask, server: { baseUrl: str const sourceFileNames: string[] = []; try { - 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}`); + if (USE_REFERENCE_IMAGE) { + // Copy renamed source images to the server input directory + 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}`); + } + + // generateImageMixStyle expects two source files; if we only have one, pass the same one twice + const srcA = sourceFileNames[0]; + const srcB = sourceFileNames[1] || sourceFileNames[0]; + + const generatedImagePath = await generateImageMixStyle( + imagePrompt, + srcA, + srcB, + imageFileName, + baseUrl, + outputDir, + { width: 720, height: 1280 } + ); + return generatedImagePath; + } else { + // Use Pinterest images only to create the prompt; generate final image using the single-image generator + const generatedImagePath = await generateImage( + imagePrompt, + imageFileName, + baseUrl, + outputDir, + 'qwen', + { width: 720, height: 1280 } + ); + return generatedImagePath; } - - // generateImage expects two source files; if we only have one, pass the same one twice - const srcA = sourceFileNames[0]; - const srcB = sourceFileNames[1] || sourceFileNames[0]; - - const generatedImagePath = await generateImage( - imagePrompt, - srcA, - srcB, - imageFileName, - baseUrl, - outputDir, - { width: 720, height: 1280 } - ); - return generatedImagePath; } catch (error) { logger.error(`Failed to generate image for ${imageFileName} on server ${baseUrl}:`, error); return null; } finally { + // cleanup local renamed images and any files copied to the server input dir for (const sourcePath of renamedImagePaths) { try { await fs.unlink(sourcePath); @@ -338,8 +356,30 @@ async function getPinUrlFromPinterest(keyword: string): Promise { return; } + /* allKeywords = allKeywords.filter(a => { - return (a.genre == "sports" && a.subGenre == "Motocross") + return (a.genre == "city" && a.subGenre == "Bridges") || + (a.genre == "city" && a.subGenre == "Castles") || + (a.genre == "city" && a.subGenre == "Cathedrals") || + (a.genre == "city" && a.subGenre == "Factories") || + (a.genre == "city" && a.subGenre == "Futuristic Cities") || + (a.genre == "city" && a.subGenre == "Historic Towns") || + (a.genre == "city" && a.subGenre == "Libraries") || + (a.genre == "city" && a.subGenre == "Markets") || + (a.genre == "city" && a.subGenre == "Modern Plazas") || + (a.genre == "city" && a.subGenre == "Museums") || + (a.genre == "city" && a.subGenre == "Palaces") || + (a.genre == "city" && a.subGenre == "Residential Blocks") || + (a.genre == "city" && a.subGenre == "Skylines") || + (a.genre == "city" && a.subGenre == "Stadiums") || + (a.genre == "city" && a.subGenre == "Street Cafes") || + (a.genre == "city" && a.subGenre == "Urban Parks") || + (a.genre == "city" && a.subGenre == "Skyscrapers") || + (a.genre == "city" && a.subGenre == "Slums") + }); + */ + allKeywords = allKeywords.filter(a => { + return (a.genre == "city") }); function shuffle(arr: T[]): T[] { @@ -350,9 +390,10 @@ async function getPinUrlFromPinterest(keyword: string): Promise { return arr; } - const selectedEntries = shuffle(allKeywords.slice()).slice(0, Math.min(20, allKeywords.length)); + //const selectedEntries = shuffle(allKeywords.slice()).slice(0, Math.min(20, allKeywords.length)); + const selectedEntries = allKeywords; - // Download up to `count` images from a pin URL by opening the pin page and scrolling up to 5 times to trigger lazy loading + // Download up to `count` images from a pin URL by opening the pin page and scro lling up to 5 times to trigger lazy loading // Returns an array of saved image paths (may be empty) async function downloadOneImageFromPin(pinUrl: string, count: number = 1): Promise { const browser = await puppeteer.launch({ headless: false }); @@ -459,7 +500,9 @@ async function getPinUrlFromPinterest(keyword: string): Promise { const { genre, subGenre } = genreSubGenre; - for (const pinId of genreSubGenre.pinIds) { + const pickedUpPinIds = shuffle(genreSubGenre.pinIds).slice(0, 2); + + for (const pinId of pickedUpPinIds) { const pin = `https://www.pinterest.com/pin/${pinId}/`; logger.info(`--- Starting processing for pin: ${pin} ---`);