generated from fahricansecer/boilerplate-be
@@ -220,18 +220,17 @@ Every image belongs to a visual universe. Define that universe with specific ref
|
||||
|
||||
❌ BAD: "dark and moody"
|
||||
❌ BAD: "cinematic look"
|
||||
✅ GOOD: "Blade Runner 2049 color palette with heavy teal-and-amber contrast, Denis Villeneuve visual language, desolate monumental scale"
|
||||
✅ GOOD: "Wes Anderson symmetry with pastel Easter-egg color palette, The Grand Budapest Hotel framing, whimsical yet melancholic tone, perfectly centered subjects"
|
||||
✅ GOOD: "National Geographic documentary realism, Planet Earth II visual texture, intimate close-up wildlife photography, David Attenborough's visual signature"
|
||||
✅ GOOD: "Studio Ghibli hand-painted watercolor backgrounds with lush green landscapes, Hayao Miyazaki cloudscapes, magical realism atmosphere"
|
||||
✅ GOOD: "[Detailed color palette from the chosen style], [Director/Cinematographer name] visual language, desolate monumental scale"
|
||||
✅ GOOD: "[Famous Director] symmetry with pastel color palette, whimsical yet melancholic tone, perfectly centered subjects"
|
||||
✅ GOOD: "National Geographic documentary realism, intimate close-up photography, true-to-life rendering"
|
||||
|
||||
Mood reference options (use 2-3 per scene combined):
|
||||
• Film references: "Blade Runner", "Interstellar", "Mad Max: Fury Road", "Spirited Away", "2001: A Space Odyssey", "The Grand Budapest Hotel", "Tenet", "Dune"
|
||||
• Film references: Refer to the provided Cinematic Style DNA.
|
||||
• Photography references: "Annie Leibovitz portrait lighting", "National Geographic close-up", "Sebastião Salgado black-and-white photojournalism", "Steve McCurry color richness"
|
||||
• Art movements: "Renaissance chiaroscuro", "Impressionist broken color", "Art Deco geometry", "Japanese ukiyo-e woodblock", "Bauhaus minimalism", "Surrealist Dalí dreamscapes"
|
||||
• Color systems: "70s Kodachrome warm tones", "Fujifilm Velvia saturated", "muted Scandinavian palette", "cyberpunk neon (magenta/teal/violet)", "earthy terracotta and sage"
|
||||
|
||||
CRITICAL RULE: Once you establish a visual universe in Scene 1, ALL subsequent scenes MUST stay within that same visual world. If Scene 1 is Blade Runner, Scene 5 cannot suddenly look like Wes Anderson.
|
||||
CRITICAL RULE: Once you establish a visual universe in Scene 1 (based on the provided Style DNA), ALL subsequent scenes MUST stay within that EXACT same visual world. Do not randomly switch styles between scenes.
|
||||
|
||||
━━━ LAYER 3: LIGHTING (Source, Direction, Quality) ━━━
|
||||
Lighting is the single most important factor in image quality. Never leave it to chance.
|
||||
@@ -887,14 +886,14 @@ export class VideoAiService {
|
||||
/**
|
||||
* Video stiline göre varsayılan görsel DNA değerlerini döndürür.
|
||||
*/
|
||||
private getStyleDNA(videoStyle: string, cinematicReference?: string): StyleDNA {
|
||||
public getStyleDNA(videoStyle: string, cinematicReference?: string): StyleDNA {
|
||||
const dnaMap: Record<string, StyleDNA> = {
|
||||
CINEMATIC: {
|
||||
reference: cinematicReference ? `${cinematicReference} visual style and cinematography` : 'Denis Villeneuve and Roger Deakins cinematography',
|
||||
lighting: cinematicReference ? `Iconic lighting setup matching the ${cinematicReference} cinematic style` : 'Dramatic key-and-fill lighting with a single strong motivated source casting deep sculpted shadows.',
|
||||
lens: cinematicReference ? `Signature camera lens choice and depth of field suitable for ${cinematicReference}` : 'Shot on 35mm anamorphic lens with shallow depth of field f/2.0 and characteristic oval bokeh.',
|
||||
color: cinematicReference ? `Color grading and palette uniquely associated with ${cinematicReference}` : 'Teal-and-orange blockbuster color grade with desaturated midtones and crushed blacks.',
|
||||
texture: cinematicReference ? `Film grain and visual texture evoking the ${cinematicReference} experience` : 'Subtle Kodak Vision3 film grain, anamorphic horizontal lens flare, slight vignette darkening corners.',
|
||||
reference: cinematicReference ? `${cinematicReference} visual style and cinematography` : 'High-end cinematic production with professional cinematography techniques',
|
||||
lighting: cinematicReference ? `Iconic lighting setup matching the ${cinematicReference} cinematic style` : 'Dramatic key-and-fill lighting with motivated light sources, sculpted shadows, and cinematic depth.',
|
||||
lens: cinematicReference ? `Signature camera lens choice and depth of field suitable for ${cinematicReference}` : 'Shot on 35mm anamorphic lens with cinematic depth of field and professional framing.',
|
||||
color: cinematicReference ? `Color grading and palette uniquely associated with ${cinematicReference}` : 'Professional cinematic color grading with balanced contrast, natural skin tones, and atmospheric depth.',
|
||||
texture: cinematicReference ? `Film grain and visual texture evoking the ${cinematicReference} experience` : 'Subtle organic film grain, natural lens characteristics, cinematic light bloom on practical sources.',
|
||||
},
|
||||
DOCUMENTARY: {
|
||||
reference: 'National Geographic and Planet Earth II',
|
||||
@@ -1227,15 +1226,45 @@ export class VideoAiService {
|
||||
let parsed: GeneratedScript;
|
||||
try {
|
||||
let cleanText = rawText.trim();
|
||||
if (cleanText.startsWith('```json')) cleanText = cleanText.slice(7);
|
||||
if (cleanText.startsWith('```')) cleanText = cleanText.slice(3);
|
||||
if (cleanText.endsWith('```')) cleanText = cleanText.slice(0, -3);
|
||||
|
||||
// Pass 1: Markdown code fence temizliği (başta, sonda, ortada)
|
||||
cleanText = cleanText.replace(/^```(?:json)?\s*/i, '');
|
||||
cleanText = cleanText.replace(/\s*```\s*$/i, '');
|
||||
// Ortada kalan fence'leri de temizle
|
||||
cleanText = cleanText.replace(/```(?:json)?\s*/gi, '');
|
||||
|
||||
// Pass 2: BOM ve kontrol karakterleri temizliği
|
||||
cleanText = cleanText.replace(/^\uFEFF/, '');
|
||||
// JSON-dışı kontrol karakterleri kaldır (tab, newline, CR hariç)
|
||||
cleanText = cleanText.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
|
||||
|
||||
// Pass 3: Trailing comma — JSON'da yasak ama Gemini bazen koyuyor
|
||||
cleanText = cleanText.replace(/,\s*([}\]])/g, '$1');
|
||||
|
||||
cleanText = cleanText.trim();
|
||||
parsed = JSON.parse(cleanText);
|
||||
} catch {
|
||||
this.logger.error(`JSON parse hatası: ${rawText.substring(0, 500)}`);
|
||||
|
||||
// İlk deneme: Direkt parse
|
||||
try {
|
||||
parsed = JSON.parse(cleanText);
|
||||
} catch {
|
||||
// Pass 4: Kesilmiş JSON kurtarma — son geçerli } veya ] bul
|
||||
const lastBrace = cleanText.lastIndexOf('}');
|
||||
const lastBracket = cleanText.lastIndexOf(']');
|
||||
const cutPoint = Math.max(lastBrace, lastBracket);
|
||||
|
||||
if (cutPoint > 0) {
|
||||
const truncated = cleanText.substring(0, cutPoint + 1);
|
||||
this.logger.warn(`JSON kesilmiş olabilir — son ${cleanText.length - cutPoint - 1} karakter atıldı, kurtarma deneniyor...`);
|
||||
parsed = JSON.parse(truncated);
|
||||
} else {
|
||||
throw new Error('Kurtarılabilir JSON bulunamadı');
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
this.logger.error(`JSON parse hatası — İlk 1000 karakter:\n${rawText.substring(0, 1000)}`);
|
||||
this.logger.error(`Parse error: ${parseError instanceof Error ? parseError.message : parseError}`);
|
||||
throw new InternalServerErrorException(
|
||||
'AI yanıtı geçerli JSON formatında değil.',
|
||||
'AI yanıtı geçerli JSON formatında değil. Lütfen "Yeniden Üret" butonuyla tekrar deneyin.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1282,6 +1311,55 @@ export class VideoAiService {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tekil görsel prompt'u stil DNA ile zenginleştirir.
|
||||
* regenerateScene gibi metotlardan dönen ham visualPrompt'a
|
||||
* 5-Layer Architecture™ özelliklerini uygular.
|
||||
*/
|
||||
enrichSingleVisualPrompt(
|
||||
visualPrompt: string,
|
||||
videoStyle: string,
|
||||
cinematicReference?: string,
|
||||
aspectRatio?: string,
|
||||
isHookScene: boolean = false,
|
||||
): string {
|
||||
const styleDNA = this.getStyleDNA(videoStyle, cinematicReference);
|
||||
const defaultNegative = 'Avoid: text overlays, watermarks, brand logos, recognizable celebrity faces, distorted anatomy, extra fingers, blurry faces, stock photo aesthetic, oversaturated CGI plastic look, generic clip art, UI elements';
|
||||
let vp = visualPrompt;
|
||||
const wordCount = vp.split(/\s+/).length;
|
||||
const minWords = isHookScene ? 80 : 50;
|
||||
|
||||
// 1. Minimum kelime kontrolü — eksikse stil DNA'sından zenginleştir
|
||||
if (wordCount < minWords) {
|
||||
vp = this.padVisualPrompt(vp, styleDNA, minWords, isHookScene);
|
||||
}
|
||||
|
||||
// 2. Visual continuity anchor
|
||||
// Önceki prefix'i temizle ki eski stiller yapışıp kalmasın (örn: Blade Runner)
|
||||
vp = vp.replace(/^Continuing the .*? visual language (established in previous scenes|from previous scenes)?:\s*/i, '');
|
||||
|
||||
if (!isHookScene) {
|
||||
vp = `Continuing the ${styleDNA.reference} visual language established in previous scenes: ${vp}`;
|
||||
}
|
||||
|
||||
// 3. Aspect ratio hint
|
||||
if (aspectRatio && !vp.toLowerCase().includes('framing') && !vp.toLowerCase().includes('composition')) {
|
||||
const arHint = aspectRatio === 'PORTRAIT_9_16'
|
||||
? 'Vertical framing optimized for mobile viewing.'
|
||||
: aspectRatio === 'LANDSCAPE_16_9'
|
||||
? 'Wide cinematic horizontal composition.'
|
||||
: 'Square centered symmetrical framing.';
|
||||
vp = `${vp} ${arHint}`;
|
||||
}
|
||||
|
||||
// 4. Negative prompt
|
||||
if (!vp.toLowerCase().includes('avoid:')) {
|
||||
vp = `${vp} ${defaultNegative}`;
|
||||
}
|
||||
|
||||
return vp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tekil sahne yeniden üretimi — sınırlı bağlam ile sadece 1 sahne üretir.
|
||||
*/
|
||||
@@ -1321,4 +1399,100 @@ export class VideoAiService {
|
||||
throw new InternalServerErrorException('Sahne yeniden üretilemedi.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Projenin tüm görsel promptlarını yeni bir stil DNA'sıyla yeniden yazar.
|
||||
* Narration ve ID'leri korur, sadece görsel betimlemeleri AI ile baştan yazar.
|
||||
*/
|
||||
async rewriteAllVisualPrompts(
|
||||
scenes: { id: string; order: number; narrationText: string; visualPrompt: string }[],
|
||||
videoStyle: string,
|
||||
cinematicReference?: string,
|
||||
aspectRatio?: string,
|
||||
): Promise<{ id: string; visualPrompt: string }[]> {
|
||||
if (!this.genAI) {
|
||||
throw new InternalServerErrorException('AI servisi etkin değil.');
|
||||
}
|
||||
|
||||
const styleDNA = this.getStyleDNA(videoStyle, cinematicReference);
|
||||
|
||||
const contextPrompt = `
|
||||
You are an expert cinematographer. I have a video script with ${scenes.length} scenes.
|
||||
I changed the project's visual style. I need you to REWRITE ONLY the "visualPrompt" for each scene so they perfectly match the new style.
|
||||
DO NOT change the scene IDs.
|
||||
|
||||
═══ NEW STYLE DNA ═══
|
||||
Reference: ${styleDNA.reference}
|
||||
Lighting: ${styleDNA.lighting}
|
||||
Lens: ${styleDNA.lens}
|
||||
Color: ${styleDNA.color}
|
||||
Texture: ${styleDNA.texture}
|
||||
═════════════════════
|
||||
|
||||
SCENES:
|
||||
${JSON.stringify(
|
||||
scenes.map((s) => ({
|
||||
id: s.id,
|
||||
order: s.order,
|
||||
narrationText: s.narrationText,
|
||||
oldVisualPrompt: s.visualPrompt,
|
||||
})),
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
|
||||
CRITICAL:
|
||||
- Return valid JSON only.
|
||||
- The visual prompts must be extremely descriptive, 50+ words, containing lighting, mood, color, and lens info.
|
||||
- Write purely in English.
|
||||
- Do NOT include any text overlays or watermarks in the prompts.
|
||||
|
||||
OUTPUT FORMAT (JSON ONLY, NO MARKDOWN FENCES):
|
||||
{
|
||||
"scenes": [
|
||||
{
|
||||
"id": "SCENE-ID-HERE",
|
||||
"visualPrompt": "new highly detailed cinematic visual prompt here..."
|
||||
}
|
||||
]
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await this.genAI.models.generateContent({
|
||||
model: this.modelName,
|
||||
contents: contextPrompt,
|
||||
config: {
|
||||
responseMimeType: 'application/json',
|
||||
temperature: 0.7,
|
||||
},
|
||||
});
|
||||
|
||||
const rawText = response.text || '';
|
||||
const cleaned = rawText.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
|
||||
const parsed = JSON.parse(cleaned);
|
||||
|
||||
if (!parsed.scenes || !Array.isArray(parsed.scenes)) {
|
||||
throw new Error('Geçersiz yanıt yapısı.');
|
||||
}
|
||||
|
||||
return parsed.scenes.map((s: any) => {
|
||||
// Zenginleştir (continuity anchor + aspect ratio padding vs)
|
||||
const enrichedVp = this.enrichSingleVisualPrompt(
|
||||
s.visualPrompt,
|
||||
videoStyle,
|
||||
cinematicReference,
|
||||
aspectRatio,
|
||||
false,
|
||||
);
|
||||
return {
|
||||
id: s.id,
|
||||
visualPrompt: enrichedVp,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`Toplu prompt yenileme hatası: ${error}`);
|
||||
throw new InternalServerErrorException('Visual promptlar yenilenemedi.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user