save changes

This commit is contained in:
2025-09-17 20:00:13 +02:00
parent 0521b28626
commit d68c44de99
3 changed files with 4006 additions and 550 deletions

View File

@ -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

View File

@ -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;
}
}
})();