generated from fahricansecer/boilerplate-be
feat: SEO Power Engine backend updates and remove temp media files
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger, InternalServerErrorException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import { jsonrepair } from 'jsonrepair';
|
||||
|
||||
export interface ScriptGenerationInput {
|
||||
topic: string;
|
||||
@@ -105,6 +106,8 @@ export interface SeoMetadata {
|
||||
description: string;
|
||||
keywords: string[];
|
||||
hashtags: string[];
|
||||
trendingHashtags?: string[]; // Trend hashtag'ler (AI tahmini)
|
||||
estimatedSearchVolume?: string; // Anahtar kelimenin tahmini arama hacmi (AI tahmini)
|
||||
schemaMarkup: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -117,6 +120,8 @@ export interface GeneratedScript {
|
||||
hashtags: string[];
|
||||
};
|
||||
seo: SeoMetadata;
|
||||
seoTitleAlternatives: string[]; // 5 alternatif SEO başlığı (CTR sıralı)
|
||||
seoScore: number; // 0-100 arası SEO güç skoru
|
||||
scenes: GeneratedScene[];
|
||||
musicPrompt: string;
|
||||
musicStyle: string; // AudioCraft: genre/mood tanımı
|
||||
@@ -170,13 +175,45 @@ HUMAN WRITING (anti-AI detection):
|
||||
- Have opinions. React to facts, don't just report them
|
||||
- Acknowledge uncertainty: "I'm not sure how to feel about this" is more human than listing pros/cons neutrally
|
||||
|
||||
SEO OPTIMIZATION:
|
||||
- Video title: Primary keyword within first 3 words, under 60 characters
|
||||
- Description: 2-3 secondary keywords naturally woven in, 150-200 chars
|
||||
- Keywords: 8-12 LSI keywords related to the main topic
|
||||
- Hashtags: 5-8 hashtags, mix of broad (#Shorts) and niche-specific
|
||||
SEO OPTIMIZATION (CRITICAL — REVENUE DRIVER):
|
||||
|
||||
SEO TITLE STRATEGY — Generate exactly 5 alternative SEO-optimized titles:
|
||||
- Each title MUST use a DIFFERENT hook strategy:
|
||||
1. Curiosity Gap: "The [Topic] Secret Nobody Tells You 🤯"
|
||||
2. Data-Driven: "[Number] [Topic] Facts That Will Blow Your Mind 🔥"
|
||||
3. How-To/Listicle: "How [Topic] Actually Works (Explained) ⚡"
|
||||
4. Emotional: "Why [Topic] Changes Everything You Thought You Knew 😱"
|
||||
5. Contrarian: "[Common Belief About Topic] Is Dead Wrong 💀"
|
||||
- Primary keyword within the FIRST 3 WORDS of every title
|
||||
- Maximum 60 characters per title (YouTube optimal cut-off)
|
||||
- EMOJI STRATEGY: Each title MUST contain 1-2 strategic emojis (🔥⚡🤯😱💀🚀💡🎯✅❌) placed at the end or mid-title to boost CTR by 15-20%
|
||||
- Rank them by estimated CTR (Click-Through Rate) — BEST title FIRST
|
||||
- Titles must be in the TARGET LANGUAGE
|
||||
- The BEST title (index 0) will automatically become the project title
|
||||
|
||||
SEO SCORE — Calculate an seoScore (0-100) based on:
|
||||
- Keyword placement in title first 3 words: 30 points
|
||||
- Curiosity/click-bait factor: 25 points
|
||||
- Specificity (numbers, names, data): 20 points
|
||||
- Emotional trigger words: 15 points
|
||||
- Title length optimization (40-60 chars): 10 points
|
||||
|
||||
SEO KEYWORDS & SEARCH VOLUME:
|
||||
- Generate 12-15 LSI (Latent Semantic Indexing) keywords
|
||||
- For each keyword, estimate relative search volume as: "HIGH", "MEDIUM", or "LOW"
|
||||
- Provide this as the seo.estimatedSearchVolume field: a brief summary like "Primary keyword 'elon musk' has HIGH search volume (~500K monthly). Secondary keywords average MEDIUM volume."
|
||||
- Include both short-tail (1-2 words) and long-tail (3-5 words) keywords
|
||||
|
||||
HASHTAG STRATEGY:
|
||||
- Generate 5-8 standard hashtags (mix of broad like #Shorts and niche-specific)
|
||||
- Additionally generate 3-5 trendingHashtags: hashtags you estimate to be currently trending on YouTube/TikTok/Instagram based on the topic's virality, seasonality, and cultural relevance
|
||||
- Mark trending hashtags separately in seo.trendingHashtags array
|
||||
|
||||
SEO DESCRIPTION:
|
||||
- Meta description: 150-200 chars, includes 2-3 secondary keywords naturally woven in
|
||||
- Schema markup hint for VideoObject structured data
|
||||
|
||||
|
||||
MEASUREMENTS:
|
||||
- ALWAYS use the METRIC SYSTEM for any measurements (e.g. centimeters, meters, kilograms, liters) in the generated content.
|
||||
- NEVER use imperial units like feet or inches. Translate them to metric if present in the source.
|
||||
@@ -237,28 +274,29 @@ Mood reference options (use 2-3 per scene combined):
|
||||
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.
|
||||
Lighting is the single most important factor in image quality. Never leave it to chance. You MUST explicitly write "Lighting: [details]" in your prompt.
|
||||
|
||||
You must specify THREE lighting properties:
|
||||
You must specify THREE lighting properties explicitly:
|
||||
A) SOURCE — Where is the light coming from? (sun, neon signs, candle, spotlight, overcast sky, monitor glow)
|
||||
B) DIRECTION — From which side relative to camera? (from camera-left, backlighting from behind subject, overhead, from below, rim light from behind-right)
|
||||
C) QUALITY — How does the light feel? (soft and diffused through curtains, harsh and directional, dappled through tree leaves, warm golden, cold blue-white clinical)
|
||||
|
||||
❌ BAD: "good lighting" or just "golden hour"
|
||||
✅ GOOD: "Late golden hour sunlight raking across the scene from camera-right at a low 15-degree angle, casting long dramatic shadows to the left, warm amber (3200K) backlighting creating a bright rim-light halo around the subject's silhouette, fill light bouncing softly off a nearby white wall on camera-left"
|
||||
✅ GOOD: "Harsh overhead fluorescent tubes casting unflattering blue-white (6500K) light with hard-edged shadows directly below every object, a single warm desk lamp in the foreground creating a small pool of amber light that contrasts with the cold clinical environment"
|
||||
✅ GOOD: "Diffused overcast daylight filtering through floor-to-ceiling frosted glass panels, creating even, shadowless illumination with a soft pearl-gray quality, punctuated by a single beam of direct sunlight breaking through a gap in the clouds and hitting the subject's hands"
|
||||
✅ GOOD: "Lighting: Late golden hour sunlight raking across the scene from camera-right at a low 15-degree angle, casting long dramatic shadows to the left, warm amber (3200K) backlighting creating a bright rim-light halo around the subject's silhouette, fill light bouncing softly off a nearby white wall on camera-left"
|
||||
✅ GOOD: "Lighting: Harsh overhead fluorescent tubes casting unflattering blue-white (6500K) light with hard-edged shadows directly below every object, a single warm desk lamp in the foreground creating a small pool of amber light that contrasts with the cold clinical environment"
|
||||
✅ GOOD: "Lighting: Diffused overcast daylight filtering through floor-to-ceiling frosted glass panels, creating even, shadowless illumination with a soft pearl-gray quality, punctuated by a single beam of direct sunlight breaking through a gap in the clouds and hitting the subject's hands"
|
||||
|
||||
━━━ LAYER 4: COMPOSITION & CAMERA ━━━
|
||||
Tell exactly where the camera is, what lens is being used, and how the frame is organized.
|
||||
━━━ LAYER 4: COMPOSITION, CAMERA & LENS (Movement, Angle, Lens Type, Continuity) ━━━
|
||||
Tell exactly where the camera is, what explicit lens is being used, how the camera moves, and ensure it connects logically with the previous/next scenes. You MUST explicitly write "Camera & Lens: [details]" in your prompt.
|
||||
|
||||
A) CAMERA POSITION: "eye-level", "low angle looking up (worm's eye)", "high angle looking down (bird's eye)", "overhead flat lay", "Dutch angle 15-degree tilt", "POV through character's eyes"
|
||||
B) CAMERA DISTANCE: "extreme close-up on eyes", "medium close-up chest and up", "medium shot waist up", "full body shot", "wide establishing shot", "extreme wide showing entire landscape"
|
||||
C) CAMERA MOVEMENT: "static locked-off tripod", "slow push-in towards subject", "smooth dolly tracking left-to-right", "orbiting 360° around subject", "crane rising up", "handheld with subtle shake"
|
||||
D) FRAMING: "rule of thirds with subject on left intersect", "perfectly centered symmetrical", "framed through doorway", "leading lines converging to vanishing point", "negative space in upper third for text overlay"
|
||||
A) LENS DETAILS (MANDATORY): Specify the exact focal length and lens type. Examples: "24mm wide-angle lens", "50mm standard prime", "85mm portrait lens", "200mm telephoto compression", "vintage anamorphic lens", "macro lens", "fisheye lens".
|
||||
B) CAMERA POSITION/ANGLE: "eye-level", "low angle looking up (worm's eye)", "high angle looking down (bird's eye)", "overhead flat lay", "Dutch angle 15-degree tilt", "POV through character's eyes"
|
||||
C) CAMERA DISTANCE: "extreme close-up on eyes", "medium close-up chest and up", "medium shot waist up", "full body shot", "wide establishing shot", "extreme wide showing entire landscape"
|
||||
D) CAMERA MOVEMENT (MANDATORY): Specify EXACTLY how the camera moves during the scene. Examples: "slide in from left", "slow push-in (zoom in) towards subject's face", "arch in/orbiting 360° around subject", "smooth dolly tracking right", "crane rising up", "static locked-off tripod", "handheld with subtle shake"
|
||||
E) CONTINUITY: Camera movements must make sense across scenes. If Scene 1 ends with a zoom-in, Scene 2 could start with an extreme close-up. If Scene 1 is an establishing wide shot panning right, Scene 2 could be a medium tracking shot moving right.
|
||||
|
||||
❌ BAD: "wide shot of the city"
|
||||
✅ GOOD: "Extreme wide establishing shot from a drone at 300 meters altitude, camera slowly descending at 45-degree angle, the ancient temple complex positioned on the lower-right third of frame, leading lines of the river drawing the eye from lower-left foreground to the temple, vast jungle canopy filling the upper two-thirds creating a sense of overwhelming scale, a thin mist layer at the treeline adding depth separation between foreground and background"
|
||||
✅ GOOD: "Camera & Lens: Shot on a 24mm wide-angle lens. Extreme wide establishing shot from a drone at 300 meters altitude, smooth crane descending at 45-degree angle while slowly panning right. The ancient temple complex positioned on the lower-right third of frame, leading lines of the river drawing the eye to the temple."
|
||||
|
||||
ASPECT RATIO COMPOSITION GUIDE:
|
||||
• 9:16 (PORTRAIT — Shorts/Reels): Vertical framing, subject fills center-frame, use foreground-to-background depth, create visual interest through vertical stacking of elements, leave negative space in top or bottom third for text/subtitles
|
||||
@@ -407,7 +445,7 @@ NARRATION TEXT (IN TARGET LANGUAGE)
|
||||
• Scene 1: powerful hook creating instant curiosity
|
||||
• Build escalating intrigue through middle scenes
|
||||
• End with a thought-provoking statement
|
||||
• Word count: targetDuration × 2.5 words/second
|
||||
• Word count: targetDuration × 2 words/second (e.g., 120 words for 60s video)
|
||||
• Conversational, not academic — like explaining to a smart friend
|
||||
• Use rhetorical questions, surprising facts, emotional language
|
||||
|
||||
@@ -420,14 +458,17 @@ SUBTITLE TEXT (IN TARGET LANGUAGE)
|
||||
• Simplify complex narration into punchy visual text
|
||||
|
||||
═══════════════════════════════════
|
||||
SCENE STRUCTURE
|
||||
SCENE STRUCTURE & CONTINUITY
|
||||
═══════════════════════════════════
|
||||
|
||||
• Min 4 scenes, max 10 scenes
|
||||
• Scene 1 (HOOK): 2-4 seconds — instant attention
|
||||
• Middle scenes: 5-12 seconds each — build the story
|
||||
• Final scene (CLOSER): 3-6 seconds — memorable conclusion
|
||||
• Total duration: within ±5 seconds of targetDuration
|
||||
• AI video generators produce best results in 4-6 second bursts.
|
||||
• You MUST generate enough scenes to cover the targetDuration (targetDuration / 5 = approximate scene count).
|
||||
• For a 60-second video, you must generate 10 to 15 scenes.
|
||||
• Scene 1 (HOOK): 3-5 seconds — instant attention
|
||||
• Middle scenes: 4-6 seconds each — continuous visual flow
|
||||
• Final scene (CLOSER): 3-5 seconds — memorable conclusion
|
||||
• Total duration: within ±2 seconds of targetDuration
|
||||
• CONTINUITY: Ensure consecutive visual prompts maintain strict subject, lighting, and camera movement continuity so they blend seamlessly.
|
||||
|
||||
TRANSITION TYPES:
|
||||
• CUT — Quick, impactful. Most scene changes
|
||||
@@ -486,12 +527,17 @@ Describe ideal TTS voice with precision for ElevenLabs:
|
||||
SOCIAL MEDIA CONTENT
|
||||
═══════════════════════════════════
|
||||
|
||||
Generate platform-specific text:
|
||||
- youtubeTitle: Primary keyword first, under 60 chars, curiosity-driven
|
||||
- youtubeDescription: 500+ chars, include CTA, 2-3 secondary keywords, link placeholder
|
||||
- tiktokCaption: Under 150 chars, trending format, 3-5 hashtags
|
||||
- instagramCaption: Under 300 chars, emotional hook, 5 hashtags
|
||||
- twitterText: Under 280 chars, hot take format, 2 hashtags
|
||||
Generate platform-specific text (CTA-POWERED):
|
||||
- youtubeTitle: BEST title from the 5 seoTitleAlternatives, under 60 chars, with emoji
|
||||
- youtubeDescription: 500+ chars, structured EXACTLY as:
|
||||
→ First 2 lines: Hook text (this is what shows before "...more")
|
||||
→ 3-4 bullet points of value propositions with emojis
|
||||
→ CTA line in target language (e.g., "Beğen, abone ol ve bildirimleri aç 🔔")
|
||||
→ 3-5 related hashtags inline
|
||||
→ "Chapters:" section with timestamps for each scene if applicable
|
||||
- tiktokCaption: Under 150 chars, trending hook format + 3-5 hashtags, CTA: "Kaydet 📌" (in target lang)
|
||||
- instagramCaption: Under 300 chars, emotional hook + value + CTA: "Yorum yap 💬" (in target lang) + 10-15 hashtags at end
|
||||
- twitterText: Under 280 chars, hot take format + 2 hashtags, CTA: "RT et" (in target lang)
|
||||
|
||||
═══════════════════════════════════
|
||||
OUTPUT FORMAT — STRICT JSON ONLY
|
||||
@@ -508,10 +554,12 @@ Return ONLY valid JSON. No markdown. No backticks. No explanation.
|
||||
"hashtags": ["string"] — 5-8 hashtags WITHOUT #
|
||||
},
|
||||
"seo": {
|
||||
"title": "string — SEO-optimized title, primary keyword first, under 60 chars",
|
||||
"title": "string — BEST SEO-optimized title with emoji, primary keyword first, under 60 chars",
|
||||
"description": "string — meta description, 150-200 chars, includes secondary keywords",
|
||||
"keywords": ["string"] — 8-12 LSI keywords,
|
||||
"keywords": ["string"] — 12-15 LSI keywords (short-tail + long-tail mix),
|
||||
"hashtags": ["string"] — same as metadata.hashtags,
|
||||
"trendingHashtags": ["string"] — 3-5 hashtags estimated to be currently trending for this topic,
|
||||
"estimatedSearchVolume": "string — brief summary of keyword search volume estimates, e.g. 'Primary keyword X has HIGH volume (~500K/mo)'",
|
||||
"schemaMarkup": {
|
||||
"@type": "VideoObject",
|
||||
"name": "string",
|
||||
@@ -519,6 +567,8 @@ Return ONLY valid JSON. No markdown. No backticks. No explanation.
|
||||
"duration": "string — ISO 8601 format PT##S"
|
||||
}
|
||||
},
|
||||
"seoTitleAlternatives": ["string"] — exactly 5 alternative SEO titles with emojis, ranked by estimated CTR (best first),
|
||||
"seoScore": number — 0-100 SEO strength score based on: keyword placement (30), curiosity (25), specificity (20), emotion (15), length (10),
|
||||
"scenes": [
|
||||
{
|
||||
"order": 1,
|
||||
@@ -542,11 +592,11 @@ Return ONLY valid JSON. No markdown. No backticks. No explanation.
|
||||
"ambientSoundPrompts": ["string"] — 2-3 project-level ambient sound descriptions for AudioGen,
|
||||
"voiceStyle": "string — TTS characteristics for ElevenLabs",
|
||||
"socialContent": {
|
||||
"youtubeTitle": "string — under 60 chars",
|
||||
"youtubeDescription": "string — 500+ chars with CTA",
|
||||
"tiktokCaption": "string — under 150 chars",
|
||||
"instagramCaption": "string — under 300 chars",
|
||||
"twitterText": "string — under 280 chars"
|
||||
"youtubeTitle": "string — BEST title from seoTitleAlternatives, under 60 chars, with emoji",
|
||||
"youtubeDescription": "string — 500+ chars with CTA, bullet points, chapters",
|
||||
"tiktokCaption": "string — under 150 chars with CTA",
|
||||
"instagramCaption": "string — under 300 chars with CTA and 10-15 hashtags",
|
||||
"twitterText": "string — under 280 chars with CTA"
|
||||
}
|
||||
}`;
|
||||
|
||||
@@ -620,6 +670,99 @@ export class VideoAiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mevcut bir proje için 5 yeni SEO-optimized başlık üretir.
|
||||
* Her başlık farklı bir hook stratejisi kullanır ve emoji içerir.
|
||||
*/
|
||||
async generateAlternativeTitles(
|
||||
topic: string,
|
||||
currentTitle: string,
|
||||
language: string,
|
||||
keywords: string[],
|
||||
): Promise<{ titles: string[]; seoScore: number }> {
|
||||
this.logger.log(`SEO başlık üretimi başladı — Konu: "${topic}", Dil: ${language}`);
|
||||
|
||||
const titlePrompt = `Generate exactly 5 alternative SEO-optimized video titles for the following topic.
|
||||
|
||||
TOPIC: "${topic}"
|
||||
CURRENT TITLE: "${currentTitle}"
|
||||
LANGUAGE: ${language} (generate titles in THIS language)
|
||||
EXISTING KEYWORDS: ${keywords.join(', ')}
|
||||
|
||||
RULES:
|
||||
1. Each title MUST use a DIFFERENT hook strategy:
|
||||
- Curiosity Gap: Creates an information gap the viewer must fill
|
||||
- Data-Driven: Uses specific numbers, statistics, or data points
|
||||
- How-To/Listicle: Offers practical value or a numbered list
|
||||
- Emotional: Triggers strong emotional response (surprise, fear, excitement)
|
||||
- Contrarian: Challenges conventional wisdom or common beliefs
|
||||
|
||||
2. Each title MUST:
|
||||
- Be under 60 characters
|
||||
- Start with the primary keyword in the first 3 words
|
||||
- Include 1-2 strategic emojis (🔥⚡🤯😱💀🚀💡🎯✅❌)
|
||||
- Be significantly DIFFERENT from the current title
|
||||
- Be in ${language} language
|
||||
|
||||
3. Rank titles by estimated CTR (best title FIRST)
|
||||
|
||||
4. Calculate seoScore (0-100) for the best title:
|
||||
- Keyword in first 3 words: 30pts
|
||||
- Curiosity factor: 25pts
|
||||
- Specificity (numbers/names): 20pts
|
||||
- Emotional trigger: 15pts
|
||||
- Length (40-60 chars): 10pts
|
||||
|
||||
Return ONLY valid JSON:
|
||||
{
|
||||
"titles": ["best title", "second best", "third", "fourth", "fifth"],
|
||||
"seoScore": number
|
||||
}`;
|
||||
|
||||
try {
|
||||
const response = await this.genAI.models.generateContent({
|
||||
model: this.modelName,
|
||||
contents: titlePrompt,
|
||||
config: {
|
||||
temperature: 0.9,
|
||||
topP: 0.95,
|
||||
maxOutputTokens: 2048,
|
||||
responseMimeType: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const rawText = response.text ?? '';
|
||||
if (!rawText.trim()) {
|
||||
throw new InternalServerErrorException('Gemini API boş yanıt döndü.');
|
||||
}
|
||||
|
||||
let parsed: { titles: string[]; seoScore: number };
|
||||
try {
|
||||
parsed = JSON.parse(rawText);
|
||||
} catch {
|
||||
const repaired = jsonrepair(rawText);
|
||||
parsed = JSON.parse(repaired);
|
||||
}
|
||||
|
||||
// Validasyon
|
||||
if (!Array.isArray(parsed.titles) || parsed.titles.length === 0) {
|
||||
throw new Error('titles dizisi boş veya eksik');
|
||||
}
|
||||
|
||||
// En fazla 5 başlık, her biri 60 karakter
|
||||
parsed.titles = parsed.titles.slice(0, 5).map((t: string) => t.substring(0, 60));
|
||||
parsed.seoScore = Math.min(100, Math.max(0, parsed.seoScore || 75));
|
||||
|
||||
this.logger.log(`✅ ${parsed.titles.length} SEO başlığı üretildi — Skor: ${parsed.seoScore}`);
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
this.logger.error(`SEO başlık üretim hatası: ${error instanceof Error ? error.message : 'Bilinmeyen'}`);
|
||||
throw new InternalServerErrorException(
|
||||
`SEO başlık üretimi başarısız: ${error instanceof Error ? error.message : 'API hatası'}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uzun metinlerden (kitap, uzun makale vb.) potansiyel video konuları çıkarır.
|
||||
* Gemini 1.5 Flash kullanarak 3-4 çarpıcı YouTube video başlığı önerir.
|
||||
@@ -686,6 +829,9 @@ REQUIREMENTS:
|
||||
`- Visual prompts: ALWAYS in English (for AI image/video generation)\n` +
|
||||
`- Video style: ${input.videoStyle} — STRICTLY follow the Visual DNA Map for this style\n` +
|
||||
`- Aspect ratio: ${input.aspectRatio || 'PORTRAIT_9_16'} — ${aspectRatioGuide}\n` +
|
||||
`- SCENE MATH: Each scene must represent 4-6 seconds of video. Total scenes = Target duration / 5.\n` +
|
||||
`- NARRATION PACING: Strict limit of 2 words per second. For a ${input.targetDurationSeconds}s video, write exactly ~${input.targetDurationSeconds * 2} words total across all scenes.\n` +
|
||||
`- CONTINUITY: Consecutive scenes MUST logically flow into each other. Use matching camera angles, consistent lighting, and coherent subject transitions so the video clips stitch together perfectly.\n` +
|
||||
`- Make it viral-worthy, visually stunning, and intellectually captivating\n` +
|
||||
`- The first 2 seconds must hook the viewer immediately\n` +
|
||||
`- Write narration that sounds HUMAN — avoid AI writing patterns\n` +
|
||||
@@ -696,11 +842,12 @@ REQUIREMENTS:
|
||||
|
||||
// 5-Layer Architecture hatırlatması
|
||||
prompt += `\n═══ VISUAL PROMPT REQUIREMENTS (CRITICAL) ═══\n`;
|
||||
prompt += `Each visualPrompt MUST contain ALL 5 layers:\n`;
|
||||
prompt += `Each visualPrompt MUST contain ALL 5 layers AND explicit "Camera:" and "Lighting:" labels:\n`;
|
||||
prompt += `1. SUBJECT: Extreme specificity — materials, textures, spatial relationships, lived-in details\n`;
|
||||
prompt += `2. MOOD/REFERENCE: Film/art/photography references that define the visual universe\n`;
|
||||
prompt += `3. LIGHTING: Source + Direction + Quality (e.g. "golden hour from camera-right at 15°, warm amber 3200K")\n`;
|
||||
prompt += `4. COMPOSITION: Camera position + distance + movement + framing rules\n`;
|
||||
prompt += `3. LIGHTING: Explicitly label as "Lighting: [Source + Direction + Quality]" (e.g. "Lighting: golden hour from camera-right at 15°, warm amber 3200K")\n`;
|
||||
prompt += `4. COMPOSITION & CAMERA: Explicitly label as "Camera & Lens: [Lens type + Angle + Distance + MOVEMENT]" (e.g. "Camera & Lens: 50mm prime lens, low angle, medium shot, sliding in from left to right, orbiting the subject")\n`;
|
||||
prompt += ` -> CRITICAL: Ensure camera movements are logically consistent and flow smoothly between consecutive scenes.\n`;
|
||||
prompt += `5. FINISHING: DOF, film stock, color grade, texture, post-processing\n`;
|
||||
prompt += `Each visualPrompt MUST end with "Avoid: [list of things to avoid]"\n`;
|
||||
prompt += `Scene 1 establishes the visual world — all subsequent scenes maintain continuity.\n`;
|
||||
@@ -1282,37 +1429,32 @@ REQUIREMENTS:
|
||||
try {
|
||||
let cleanText = rawText.trim();
|
||||
|
||||
// Pass 1: Markdown code fence temizliği (başta, sonda, ortada)
|
||||
// Pass 1: Markdown code fence temizliği
|
||||
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 2: JSON bloğunu izole et
|
||||
const startIndex = cleanText.indexOf('{');
|
||||
const endIndex = cleanText.lastIndexOf('}');
|
||||
if (startIndex >= 0 && endIndex > startIndex) {
|
||||
cleanText = cleanText.substring(startIndex, endIndex + 1);
|
||||
}
|
||||
|
||||
// Pass 3: Trailing comma — JSON'da yasak ama Gemini bazen koyuyor
|
||||
cleanText = cleanText.replace(/,\s*([}\]])/g, '$1');
|
||||
|
||||
cleanText = cleanText.trim();
|
||||
|
||||
// İlk deneme: Direkt parse
|
||||
// Pass 3: jsonrepair ile onar ve parse et
|
||||
try {
|
||||
parsed = JSON.parse(cleanText);
|
||||
} catch {
|
||||
// Pass 4: Kesilmiş JSON kurtarma — son geçerli } veya ] bul
|
||||
const repaired = jsonrepair(cleanText);
|
||||
parsed = JSON.parse(repaired);
|
||||
} catch (repairError) {
|
||||
// Eğer jsonrepair doğrudan başaramazsa, en son geçerli kapamaya kadar kesip tekrar deneyelim
|
||||
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);
|
||||
if (lastBrace > 0) {
|
||||
const truncated = cleanText.substring(0, lastBrace + 1);
|
||||
this.logger.warn(`jsonrepair başarısız, JSON kesilmiş olabilir. Kurtarma deneniyor...`);
|
||||
const repairedTruncated = jsonrepair(truncated);
|
||||
parsed = JSON.parse(repairedTruncated);
|
||||
} else {
|
||||
throw new Error('Kurtarılabilir JSON bulunamadı');
|
||||
throw repairError;
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
|
||||
Reference in New Issue
Block a user