save changes

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