Compare commits
2 Commits
f63395fca3
...
3b9ebd0325
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b9ebd0325 | |||
| 8532a33988 |
@ -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 110–140 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 110–140 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}`);
|
||||||
|
|||||||
1368
src/musicspot_generator/infinitydance/scenes.json
Normal file
1368
src/musicspot_generator/infinitydance/scenes.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user