This commit is contained in:
2025-09-22 18:12:27 +02:00
3 changed files with 1420 additions and 20 deletions

View File

@ -5,7 +5,8 @@ import fs from 'fs/promises';
import { logger } from '../lib/logger';
import { callOpenAI } from '../lib/openai';
import { callLMStudio } from '../lib/lmstudio';
import { generateImage as generateFaceImage } from '../lib/image-generator-face';
// import { generateImage as generateFaceImage } from '../lib/image-generator-face'; // Removed
import { generateImage } from '../lib/image-generator'; // Added
dotenv.config();
@ -43,8 +44,8 @@ interface Server {
name: string;
}
const DEFAULT_SIZE: Size = { width: 720, height: 1280 };
const FOLDER = process.argv[2] || process.env.MUSICSPOT_FOLDER || 'zagreb';
const DEFAULT_SIZE: Size = { width: 1280, height: 720 };
const FOLDER = process.argv[2] || process.env.MUSICSPOT_FOLDER || 'infinitydance';
const FOLDER_SAFE = FOLDER.replace(/[/\\?%*:|"<>]/g, '_');
const FACE_SRC = path.resolve(`src/musicspot_generator/${FOLDER}/face.png`);
const GENERATED_DIR = path.resolve('generated');
@ -84,14 +85,16 @@ async function ensureDirs() {
await fs.mkdir(GENERATED_DIR, { recursive: true });
}
async function copyFaceToServers(servers: Server[]): Promise<string> {
async function copyFaceToServers(servers: Server[]): Promise<string | undefined> {
const faceFileName = 'face.png';
// Validate face source
try {
await fs.access(FACE_SRC);
} catch {
throw new Error(`Face image not found at ${FACE_SRC}`);
// If face.png doesn't exist, we don't need to copy anything.
// The caller should handle this case.
return undefined;
}
for (const srv of servers) {
@ -113,14 +116,25 @@ function buildImagePromptRequest(
character: MusicSpotCharacter,
scene: MusicSpotScene,
cut: MusicSpotCut,
cameraIntent: string
cameraIntent: string,
hasFaceImage: boolean // Added parameter
): string {
let promptInstructions = `
Write "imagePrompt" in around 110140 words to generate a still portrait image (720x1280 vertical).
`;
if (hasFaceImage) {
promptInstructions += `Keep a consistent character identity using the provided face image (identity preservation), but do not mention any camera brand/model.
`;
} else {
promptInstructions += `Do not use any face image for identity preservation.
`;
}
return `
Return exactly one JSON object, nothing else: { "imagePrompt": "Cinematic realistic photo, (camera framing),(character),(pose),(time),(location),(outfit),(action),(lighting)" }.
Write "imagePrompt" in around 110140 words to generate a still portrait image (720x1280 vertical).
Keep a consistent character identity using the provided face image (identity preservation), but do not mention any camera brand/model.
${promptInstructions}
Describe clearly and concretely:
- Character: ${character.bodyType}; hair: ${character.hairStyle}
- Camera framing/composition intention: ${cameraIntent}
@ -178,6 +192,16 @@ async function main() {
try {
await ensureDirs();
// Check if face.png exists
let faceImageExists = false;
try {
await fs.access(FACE_SRC);
faceImageExists = true;
logger.info(`Face image found at ${FACE_SRC}`);
} catch {
logger.warn(`Face image not found at ${FACE_SRC}. Images will be generated without face conditioning.`);
}
// Load scenes.json
const configRaw = await fs.readFile(path.resolve(`src/musicspot_generator/${FOLDER}/scenes.json`), 'utf-8');
const cfg: MusicSpotConfig = JSON.parse(configRaw);
@ -187,8 +211,13 @@ async function main() {
return;
}
// Ensure face.png in each server's input
const faceFileName = await copyFaceToServers(servers);
// Ensure face.png in each server's input, only if it exists
let faceFileName: string | undefined = undefined;
if (faceImageExists) {
faceFileName = await copyFaceToServers(servers);
} else {
logger.info('Skipping copyFaceToServers as face image does not exist.');
}
// Generate images only (no video here). Intended to be run first.
let imageTaskIndex = 0;
@ -218,7 +247,8 @@ async function main() {
// 1) Generate image prompt for this camera
logger.info(`Scene ${scene.sceneId} - Cut ${cut.cutId} - Cam${variantIndex}: generating image prompt...`);
const imgPromptReq = buildImagePromptRequest(cfg.character, scene, cut, cameraIntent);
// Pass faceImageExists to buildImagePromptRequest
const imgPromptReq = buildImagePromptRequest(cfg.character, scene, cut, cameraIntent, faceImageExists);
let imagePrompt: string;
try {
imagePrompt = await getImagePrompt(imgPromptReq);
@ -229,18 +259,20 @@ async function main() {
continue;
}
// 2) Generate one image using face conditioning for this specific camera
// 2) Generate one image using the new generateImage function
const serverForImage = pickServer(servers, imageTaskIndex++);
logger.info(`Generating image (${imgFileName}) on ${serverForImage.name}...`);
try {
// Use only the face file name for the workflow image input (Comfy expects it in its input dir)
const finalImagePath = await generateFaceImage(
`Realistic photo, ultra detailed, high contrast, ${imagePrompt}`,
faceFileName,
// Use the generic generateImage function from image-generator.ts
// The faceFileName is no longer passed as an argument, but its existence
// influenced the imagePrompt.
const finalImagePath = await generateImage(
imagePrompt, // The prompt now contains face instructions if faceImageExists is true
imgFileName,
serverForImage.baseUrl!,
serverForImage.outputDir!,
'flux', // Use default imageModel ('qwen')
DEFAULT_SIZE
);
logger.info(`Image generated: ${finalImagePath}`);

File diff suppressed because it is too large Load Diff

View File

@ -42,8 +42,8 @@ interface Server {
name: string;
}
const DEFAULT_SIZE: Size = { width: 720, height: 1280 };
const FOLDER = process.argv[2] || process.env.MUSICSPOT_FOLDER || 'zagreb';
const DEFAULT_SIZE: Size = { width: 1280, height: 720 };
const FOLDER = process.argv[2] || process.env.MUSICSPOT_FOLDER || 'infinitydance';
const FOLDER_SAFE = FOLDER.replace(/[/\\?%*:|"<>]/g, '_');
const GENERATED_DIR = path.resolve('generated');
@ -234,7 +234,7 @@ async function main() {
serverForVideo.baseUrl!,
serverForVideo.outputDir!,
DEFAULT_SIZE,
false,
true,
true
);