Merge branch 'master' of https://git.yasue.org/ken/RandomVideoMaker
This commit is contained in:
@ -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 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 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).
|
||||
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}`);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user