Compare commits
2 Commits
f8adaf050e
...
c7279b4e8b
| Author | SHA1 | Date | |
|---|---|---|---|
| c7279b4e8b | |||
| d68c44de99 |
@ -3,6 +3,7 @@ import type { Page } from 'puppeteer';
|
||||
import dotenv from 'dotenv';
|
||||
import * as fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@ -30,7 +31,7 @@ const MAX_PIN_IDS_PER_TERM = 20; // target number of pin ids per genre/subgenre
|
||||
const SCROLL_DELAY_MS = 900; // delay between scrolls
|
||||
|
||||
const searchList: { genre: string; subGenre: string }[] = [
|
||||
{ genre: "abstract", subGenre: "3D Renderings" },
|
||||
/* { genre: "abstract", subGenre: "3D Renderings" },
|
||||
{ genre: "abstract", subGenre: "Abstract Portraits" },
|
||||
{ genre: "abstract", subGenre: "Collage" },
|
||||
{ genre: "abstract", subGenre: "Color Explosions" },
|
||||
@ -451,6 +452,134 @@ const searchList: { genre: string; subGenre: string }[] = [
|
||||
{ genre: "work", subGenre: "Athletes" },
|
||||
{ genre: "work", subGenre: "Chefs" },
|
||||
{ genre: "work", subGenre: "Craftsmen" },
|
||||
|
||||
{ genre: "girl", subGenre: "Casual Dresses" },
|
||||
{ genre: "girl", subGenre: "Mini Skirts" },
|
||||
{ genre: "girl", subGenre: "Long Skirts" },
|
||||
{ genre: "girl", subGenre: "Evening Gowns" },
|
||||
{ genre: "girl", subGenre: "School Uniforms" },
|
||||
{ genre: "girl", subGenre: "Business Suits" },
|
||||
{ genre: "girl", subGenre: "Cocktail Dresses" },
|
||||
{ genre: "girl", subGenre: "Boho Outfits" },
|
||||
{ genre: "girl", subGenre: "Streetwear" },
|
||||
{ genre: "girl", subGenre: "Traditional Costumes" },
|
||||
{ genre: "girl", subGenre: "Party Dresses" },
|
||||
{ genre: "girl", subGenre: "Swimwear" },
|
||||
{ genre: "girl", subGenre: "Sportswear" },
|
||||
{ genre: "girl", subGenre: "Lingerie" },
|
||||
{ genre: "girl", subGenre: "Pajamas" },
|
||||
{ genre: "girl", subGenre: "Gothic Outfits" },
|
||||
{ genre: "girl", subGenre: "Vintage Dresses" },
|
||||
{ genre: "girl", subGenre: "Winter Coats" },
|
||||
{ genre: "girl", subGenre: "Summer Outfits" },
|
||||
{ genre: "girl", subGenre: "Festival Outfits" },
|
||||
|
||||
{ genre: "woman", subGenre: "Evening Gowns" },
|
||||
{ genre: "woman", subGenre: "Cocktail Dresses" },
|
||||
{ genre: "woman", subGenre: "Ball Gowns" },
|
||||
{ genre: "woman", subGenre: "Business Attire" },
|
||||
{ genre: "woman", subGenre: "Formal Suits" },
|
||||
{ genre: "woman", subGenre: "Luxury Lingerie" },
|
||||
{ genre: "woman", subGenre: "Opera Dresses" },
|
||||
{ genre: "woman", subGenre: "Elegant Skirts" },
|
||||
{ genre: "woman", subGenre: "Designer Outfits" },
|
||||
{ genre: "woman", subGenre: "Classic Black Dress" },
|
||||
{ genre: "woman", subGenre: "High Fashion Couture" },
|
||||
{ genre: "woman", subGenre: "Vintage Elegance" },
|
||||
{ genre: "woman", subGenre: "Wedding Dresses" },
|
||||
{ genre: "woman", subGenre: "Chic Office Wear" },
|
||||
{ genre: "woman", subGenre: "Luxury Coats" },
|
||||
{ genre: "woman", subGenre: "Formal Evening Suits" },
|
||||
{ genre: "woman", subGenre: "Silk Dresses" },
|
||||
{ genre: "woman", subGenre: "Red Carpet Outfits" },
|
||||
{ genre: "woman", subGenre: "Classic Tailored Wear" },
|
||||
{ genre: "woman", subGenre: "Luxury Resort Wear" }
|
||||
|
||||
{ "genre": "woman", "subGenre": "Casual Dresses" },
|
||||
{ "genre": "woman", "subGenre": "Jeans and Blouses" },
|
||||
{ "genre": "woman", "subGenre": "Maxi Skirts" },
|
||||
{ "genre": "woman", "subGenre": "Casual Office Wear" },
|
||||
{ "genre": "woman", "subGenre": "Cardigans with Skirts" },
|
||||
{ "genre": "woman", "subGenre": "Casual Jumpsuits" },
|
||||
{ "genre": "woman", "subGenre": "Casual Knitwear" },
|
||||
{ "genre": "woman", "subGenre": "Linen Outfits" },
|
||||
{ "genre": "woman", "subGenre": "Weekend Outfits" },
|
||||
{ "genre": "woman", "subGenre": "Street Casual" },
|
||||
{ "genre": "woman", "subGenre": "Boho Casual" },
|
||||
{ "genre": "woman", "subGenre": "Smart Casual" },
|
||||
{ "genre": "woman", "subGenre": "Denim Skirts" },
|
||||
{ "genre": "woman", "subGenre": "Casual Midi Dresses" },
|
||||
{ "genre": "woman", "subGenre": "Summer Casual" },
|
||||
{ "genre": "woman", "subGenre": "Casual Sweaters" },
|
||||
{ "genre": "woman", "subGenre": "Casual Jackets" },
|
||||
{ "genre": "woman", "subGenre": "Everyday Casual Wear" },
|
||||
{ "genre": "woman", "subGenre": "Relaxed Home Wear" },
|
||||
{ "genre": "woman", "subGenre": "Travel Casual" },
|
||||
|
||||
{ "genre": "girl", "subGenre": "Casual Mini Skirts" },
|
||||
{ "genre": "girl", "subGenre": "T-Shirts and Shorts" },
|
||||
{ "genre": "girl", "subGenre": "Casual Sundresses" },
|
||||
{ "genre": "girl", "subGenre": "Sweaters with Skirts" },
|
||||
{ "genre": "girl", "subGenre": "Casual Hoodies" },
|
||||
{ "genre": "girl", "subGenre": "Casual Denim" },
|
||||
{ "genre": "girl", "subGenre": "Casual Rompers" },
|
||||
{ "genre": "girl", "subGenre": "School Casual" },
|
||||
{ "genre": "girl", "subGenre": "Casual Tops and Skirts" },
|
||||
{ "genre": "girl", "subGenre": "Leggings and Tees" },
|
||||
{ "genre": "girl", "subGenre": "Casual Tank Tops" },
|
||||
{ "genre": "girl", "subGenre": "Sporty Casual" },
|
||||
{ "genre": "girl", "subGenre": "Casual Pajamas" },
|
||||
{ "genre": "girl", "subGenre": "Casual Jackets" },
|
||||
{ "genre": "girl", "subGenre": "Simple Dresses" },
|
||||
{ "genre": "girl", "subGenre": "Casual Streetwear" },
|
||||
{ "genre": "girl", "subGenre": "Casual Overalls" },
|
||||
{ "genre": "girl", "subGenre": "Casual Knit Tops" },
|
||||
{ "genre": "girl", "subGenre": "Casual Party Wear" },
|
||||
{ "genre": "girl", "subGenre": "Weekend Casual" },
|
||||
{ "genre": "girl", "subGenre": "Anime School Uniform" },
|
||||
{ "genre": "girl", "subGenre": "Magical Girl Costume" },
|
||||
{ "genre": "girl", "subGenre": "Catgirl Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Maid Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Nurse Uniform" },
|
||||
{ "genre": "girl", "subGenre": "Sailor Suit" },
|
||||
{ "genre": "girl", "subGenre": "Fantasy Elf Costume" },
|
||||
{ "genre": "girl", "subGenre": "Vampire Girl Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Gothic Lolita Dress" },
|
||||
{ "genre": "girl", "subGenre": "Princess Dress" },
|
||||
{ "genre": "girl", "subGenre": "Warrior Girl Armor" },
|
||||
{ "genre": "girl", "subGenre": "Cyberpunk Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Steampunk Girl Costume" },
|
||||
{ "genre": "girl", "subGenre": "Fairy Wings Costume" },
|
||||
{ "genre": "girl", "subGenre": "Idol Singer Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Bunny Girl Costume" },
|
||||
{ "genre": "girl", "subGenre": "Magical Witch Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Samurai Girl Costume" },
|
||||
{ "genre": "girl", "subGenre": "Succubus Outfit" },
|
||||
{ "genre": "girl", "subGenre": "Video Game Heroine Cosplay" }
|
||||
*/
|
||||
|
||||
{ "genre": "epic", "subGenre": "woman" },
|
||||
{ "genre": "epic", "subGenre": "girl" },
|
||||
{ "genre": "epic", "subGenre": "human" },
|
||||
{ "genre": "epic", "subGenre": "man" },
|
||||
{ "genre": "epic", "subGenre": "architecture" },
|
||||
{ "genre": "epic", "subGenre": "animals" },
|
||||
{ "genre": "epic", "subGenre": "ethereal" },
|
||||
{ "genre": "epic", "subGenre": "gothic" },
|
||||
{ "genre": "epic", "subGenre": "dark" },
|
||||
{ "genre": "epic", "subGenre": "space" },
|
||||
{ "genre": "epic", "subGenre": "scene" },
|
||||
{ "genre": "epic", "subGenre": "black" },
|
||||
{ "genre": "epic", "subGenre": "colorful" },
|
||||
{ "genre": "epic", "subGenre": "bright" },
|
||||
{ "genre": "epic", "subGenre": "abstract" },
|
||||
{ "genre": "epic", "subGenre": "abstract color" },
|
||||
{ "genre": "epic", "subGenre": "room" },
|
||||
{ "genre": "epic", "subGenre": "building" },
|
||||
{ "genre": "epic", "subGenre": "wizard" },
|
||||
{ "genre": "epic", "subGenre": "future" },
|
||||
{ "genre": "epic", "subGenre": "landscape" },
|
||||
{ "genre": "epic", "subGenre": "stars" },
|
||||
];
|
||||
|
||||
function extractPinIdFromHref(href: string): string | null {
|
||||
@ -516,13 +645,48 @@ async function collectPinIdsForSearch(page: Page, query: string): Promise<string
|
||||
|
||||
await browser.close();
|
||||
|
||||
// Output JSON to file (generated/pinterest_keywords.json)
|
||||
// Merge results with existing src/pinterest_keywords.json (if present),
|
||||
// then write merged data to both src/pinterest_keywords.json and generated/pinterest_keywords.json
|
||||
const srcPath = path.join(process.cwd(), 'src', 'pinterest_keywords.json');
|
||||
const outDir = path.join(process.cwd(), 'generated');
|
||||
try {
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
const outPath = path.join(outDir, 'pinterest_keywords.json');
|
||||
await fs.writeFile(outPath, JSON.stringify(results, null, 2), 'utf-8');
|
||||
console.log(`Saved ${results.length} entries to ${outPath}`);
|
||||
// load existing entries from src/pinterest_keywords.json (if available)
|
||||
let existing: { genre: string; subGenre: string; pinIds: string[] }[] = [];
|
||||
try {
|
||||
const raw = await fs.readFile(srcPath, 'utf-8');
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) existing = parsed;
|
||||
} catch (e) {
|
||||
// file missing or invalid JSON -> start with empty existing array
|
||||
existing = [];
|
||||
}
|
||||
// Build map keyed by genre||subGenre to merge pinIds (dedupe)
|
||||
const map = new Map<string, { genre: string; subGenre: string; pinIds: string[] }>();
|
||||
for (const e of existing) {
|
||||
const key = `${e.genre}||${e.subGenre}`;
|
||||
map.set(key, { genre: e.genre, subGenre: e.subGenre, pinIds: Array.from(new Set(e.pinIds || [])) });
|
||||
}
|
||||
for (const r of results) {
|
||||
const key = `${r.genre}||${r.subGenre}`;
|
||||
const existingEntry = map.get(key);
|
||||
if (existingEntry) {
|
||||
// preserve existing order, then append any new ids from r
|
||||
const set = new Set(existingEntry.pinIds);
|
||||
for (const id of r.pinIds || []) set.add(id);
|
||||
existingEntry.pinIds = Array.from(set);
|
||||
} else {
|
||||
map.set(key, { genre: r.genre, subGenre: r.subGenre, pinIds: Array.from(new Set(r.pinIds || [])) });
|
||||
}
|
||||
}
|
||||
const merged = Array.from(map.values());
|
||||
// Write merged JSON to src and generated folder
|
||||
const writePromises = [
|
||||
fs.writeFile(srcPath, JSON.stringify(merged, null, 2), 'utf-8'),
|
||||
fs.writeFile(path.join(outDir, 'pinterest_keywords.json'), JSON.stringify(merged, null, 2), 'utf-8')
|
||||
];
|
||||
await Promise.all(writePromises);
|
||||
console.log(`Saved ${merged.length} entries to ${srcPath} and ${path.join(outDir, 'pinterest_keywords.json')}`);
|
||||
} catch (err) {
|
||||
console.error('Failed to write output file:', err);
|
||||
// Fallback to printing JSON to stdout
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ import puppeteer from 'puppeteer';
|
||||
import { VideoModel } from './lib/db/video';
|
||||
|
||||
dotenv.config();
|
||||
const RUN_ONCE = (process.env.RUN_ONCE || 'false').toLowerCase() === 'true';
|
||||
|
||||
const USE_REFERENCE_IMAGE = (process.env.USE_REFERENCE_IMAGE || 'true').toLowerCase() === 'true';
|
||||
|
||||
@ -69,16 +70,15 @@ async function callOpenAIWithFileAndExtract(imagePath: string, prompt: string, m
|
||||
return null;
|
||||
}
|
||||
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,
|
||||
},
|
||||
*/
|
||||
].filter((s): s is { baseUrl: string; outputDir: string } => !!s.baseUrl && !!s.outputDir);
|
||||
|
||||
interface GenerationTask {
|
||||
@ -135,6 +135,7 @@ Decide which single action type best fits this scene from the list:
|
||||
- micro animation (animate object but small movement)
|
||||
- big movement
|
||||
- impossible movement
|
||||
- Dance ( if its woman portrait )
|
||||
|
||||
Return exactly one JSON object and nothing else: { "actiontype": "..." }.
|
||||
Do not add commentary. Choose the single best option from the list above.
|
||||
@ -272,7 +273,7 @@ async function generateImageForTask(task: GenerationTask, server: { baseUrl: str
|
||||
imageFileName,
|
||||
baseUrl,
|
||||
outputDir,
|
||||
{ width: 720, height: 1280 }
|
||||
{ width: 1280, height: 720 }
|
||||
);
|
||||
return generatedImagePath;
|
||||
} else {
|
||||
@ -283,7 +284,7 @@ async function generateImageForTask(task: GenerationTask, server: { baseUrl: str
|
||||
baseUrl,
|
||||
outputDir,
|
||||
'qwen',
|
||||
{ width: 720, height: 1280 }
|
||||
{ width: 1280, height: 720 }
|
||||
);
|
||||
return generatedImagePath;
|
||||
}
|
||||
@ -379,7 +380,7 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
});
|
||||
*/
|
||||
allKeywords = allKeywords.filter(a => {
|
||||
return (a.genre == "city")
|
||||
return (a.genre == "epic")
|
||||
});
|
||||
|
||||
function shuffle<T>(arr: T[]): T[] {
|
||||
@ -471,7 +472,7 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
const numberOfPinIds = 20;
|
||||
const numberOfPinIds = Number(process.env.NUMBER_OF_PINIDS) || 20;
|
||||
// Build keywords list with single chosen pinId per selected subGenre
|
||||
const keywords: {
|
||||
genre: string; subGenre: string; pinIds: string[], videoInstructions?: string[]
|
||||
@ -493,16 +494,36 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
return;
|
||||
}
|
||||
|
||||
type pinIdsType = {
|
||||
pinId: string,
|
||||
genreSubGenre: { genre: string, subGenre: string, pinIds: string[], videoInstructions: string[] }
|
||||
};
|
||||
|
||||
while (true) {
|
||||
|
||||
const generationTasks: GenerationTask[] = [];
|
||||
const allPinIds: pinIdsType[] = keywords.reduce<pinIdsType[]>((acc, curr) => {
|
||||
const videoInstructions = curr.videoInstructions ?? [];
|
||||
for (const id of curr.pinIds ?? []) {
|
||||
acc.push({
|
||||
pinId: id,
|
||||
genreSubGenre: {
|
||||
genre: curr.genre,
|
||||
subGenre: curr.subGenre,
|
||||
pinIds: curr.pinIds,
|
||||
videoInstructions,
|
||||
},
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
const pickedUpPinIds: pinIdsType[] = shuffle(allPinIds).slice(0, 30);
|
||||
|
||||
for (const genreSubGenre of keywords) {
|
||||
for (const row of pickedUpPinIds) {
|
||||
|
||||
const { genre, subGenre } = genreSubGenre;
|
||||
|
||||
const pickedUpPinIds = shuffle<string>(genreSubGenre.pinIds).slice(0, 2);
|
||||
|
||||
for (const pinId of pickedUpPinIds) {
|
||||
const { genreSubGenre, pinId } = row;
|
||||
const genre = genreSubGenre.genre;
|
||||
const subGenre = genreSubGenre.subGenre;
|
||||
|
||||
const pin = `https://www.pinterest.com/pin/${pinId}/`;
|
||||
logger.info(`--- Starting processing for pin: ${pin} ---`);
|
||||
@ -535,7 +556,6 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Image Generation Phase ---
|
||||
logger.info(`--- Starting image generation for ${generationTasks.length} tasks ---`);
|
||||
@ -572,7 +592,7 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
videoFileName,
|
||||
server.baseUrl,
|
||||
server.outputDir,
|
||||
{ width: 720, height: 1280 }
|
||||
{ width: 1280, height: 720 }
|
||||
);
|
||||
|
||||
if (videoPath) {
|
||||
@ -618,5 +638,9 @@ async function getPinUrlFromPinterest(keyword: string): Promise<string | null> {
|
||||
}
|
||||
logger.info("--- Finished video generation ---");
|
||||
|
||||
if (RUN_ONCE) {
|
||||
logger.info('RUN_ONCE=true - exiting after a single iteration of generation.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user