generated from fahricansecer/boilerplate-be
main
This commit is contained in:
@@ -2,10 +2,15 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../database/prisma.service';
|
||||
import { GeminiService } from '../../gemini/gemini.service';
|
||||
import { CreateSegmentDto, UpdateSegmentDto } from '../dto';
|
||||
|
||||
import { AnalysisService } from './analysis.service';
|
||||
|
||||
// AI_CONFIG is only used for model selection reference
|
||||
import {
|
||||
buildScriptOutlinePrompt,
|
||||
buildChapterSegmentPrompt,
|
||||
buildSegmentRewritePrompt,
|
||||
buildSegmentImagePrompt,
|
||||
calculateTargetWordCount,
|
||||
calculateEstimatedChapters,
|
||||
} from '../prompts';
|
||||
|
||||
/**
|
||||
* ScriptsService
|
||||
@@ -138,34 +143,24 @@ export class ScriptsService {
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
// Calculate target word count based on duration
|
||||
let targetWordCount = 840;
|
||||
if (project.targetDuration.includes('Short')) targetWordCount = 140;
|
||||
else if (project.targetDuration.includes('Standard')) targetWordCount = 840;
|
||||
else if (project.targetDuration.includes('Long')) targetWordCount = 1680;
|
||||
else if (project.targetDuration.includes('Deep Dive'))
|
||||
targetWordCount = 2800;
|
||||
// Calculate target metrics
|
||||
const targetWordCount = calculateTargetWordCount(project.targetDuration);
|
||||
const estimatedChapters = calculateEstimatedChapters(targetWordCount);
|
||||
|
||||
const estimatedChapters = Math.ceil(targetWordCount / 200);
|
||||
|
||||
// PHASE 1: Generate Outline
|
||||
const outlinePrompt = `
|
||||
Create a CONTENT OUTLINE.
|
||||
Topic: "${project.topic}"
|
||||
Logline: "${project.logline || ''}"
|
||||
Characters: ${characterContext}
|
||||
Styles: ${project.speechStyle.join(', ')}. Audience: ${project.targetAudience.join(', ')}.
|
||||
Format: ${project.contentType}. Target Duration: ${project.targetDuration}. Target Total Word Count: ${targetWordCount}.
|
||||
Generate exactly ${estimatedChapters} chapters.
|
||||
Material: ${sourceContext.substring(0, 15000)}
|
||||
Brief: ${briefContext}
|
||||
|
||||
Return JSON: {
|
||||
"title": "Title", "seoDescription": "Desc", "tags": ["tag1"],
|
||||
"thumbnailIdeas": ["Idea 1"],
|
||||
"chapters": [{ "title": "Chap 1", "focus": "Summary", "type": "Intro" }]
|
||||
}
|
||||
`;
|
||||
// PHASE 1: Generate Outline using prompt builder
|
||||
const outlinePromptData = buildScriptOutlinePrompt({
|
||||
topic: project.topic,
|
||||
logline: project.logline || '',
|
||||
characterContext,
|
||||
speechStyles: project.speechStyle,
|
||||
targetAudience: project.targetAudience,
|
||||
contentType: project.contentType,
|
||||
targetDuration: project.targetDuration,
|
||||
targetWordCount,
|
||||
estimatedChapters,
|
||||
sourceContext,
|
||||
briefContext,
|
||||
});
|
||||
|
||||
const outlineResp = await this.gemini.generateJSON<{
|
||||
title: string;
|
||||
@@ -173,10 +168,9 @@ export class ScriptsService {
|
||||
tags: string[];
|
||||
thumbnailIdeas: string[];
|
||||
chapters: { title: string; focus: string; type: string }[];
|
||||
}>(
|
||||
outlinePrompt,
|
||||
'{ title, seoDescription, tags, thumbnailIdeas, chapters }',
|
||||
);
|
||||
}>(outlinePromptData.prompt, outlinePromptData.schema, {
|
||||
temperature: outlinePromptData.temperature,
|
||||
});
|
||||
|
||||
const outlineData = outlineResp.data;
|
||||
|
||||
@@ -191,38 +185,30 @@ export class ScriptsService {
|
||||
},
|
||||
});
|
||||
|
||||
// PHASE 2: Generate each chapter
|
||||
// PHASE 2: Generate each chapter using prompt builder
|
||||
const generatedSegments: any[] = [];
|
||||
let timeOffset = 0;
|
||||
|
||||
for (let i = 0; i < outlineData.chapters.length; i++) {
|
||||
const chapter = outlineData.chapters[i];
|
||||
|
||||
const chapterPrompt = `
|
||||
Write Script Segment ${i + 1}/${outlineData.chapters.length}.
|
||||
Chapter: "${chapter.title}". Focus: ${chapter.focus}.
|
||||
Style: ${project.speechStyle.join(', ')}.
|
||||
Audience: ${project.targetAudience.join(', ')}.
|
||||
Characters: ${characterContext}.
|
||||
Target Length: ~200 words.
|
||||
Language: ${project.language}.
|
||||
|
||||
Return JSON Array: [{
|
||||
"segmentType": "${chapter.type || 'Body'}",
|
||||
"narratorScript": "Full text...",
|
||||
"visualDescription": "Detailed visual explanation...",
|
||||
"videoPrompt": "Cinematic shot of [subject], 4k...",
|
||||
"imagePrompt": "Hyper-realistic photo of [subject]...",
|
||||
"onScreenText": "Overlay text...",
|
||||
"stockQuery": "Pexels keyword",
|
||||
"audioCues": "SFX..."
|
||||
}]
|
||||
`;
|
||||
const chapterPromptData = buildChapterSegmentPrompt({
|
||||
chapterIndex: i,
|
||||
totalChapters: outlineData.chapters.length,
|
||||
chapterTitle: chapter.title,
|
||||
chapterFocus: chapter.focus,
|
||||
chapterType: chapter.type,
|
||||
speechStyles: project.speechStyle,
|
||||
targetAudience: project.targetAudience,
|
||||
characterContext,
|
||||
language: project.language,
|
||||
});
|
||||
|
||||
try {
|
||||
const segmentResp = await this.gemini.generateJSON<any[]>(
|
||||
chapterPrompt,
|
||||
'[{ segmentType, narratorScript, visualDescription, videoPrompt, imagePrompt, onScreenText, stockQuery, audioCues }]',
|
||||
chapterPromptData.prompt,
|
||||
chapterPromptData.schema,
|
||||
{ temperature: chapterPromptData.temperature },
|
||||
);
|
||||
|
||||
for (const seg of segmentResp.data) {
|
||||
@@ -293,30 +279,21 @@ export class ScriptsService {
|
||||
throw new NotFoundException(`Segment with ID ${segmentId} not found`);
|
||||
}
|
||||
|
||||
const prompt = `
|
||||
Rewrite this script segment.
|
||||
Current Text: "${segment.narratorScript}"
|
||||
Goal: Change style to "${newStyle}".
|
||||
Context: Topic is "${segment.project.topic}". Language: ${segment.project.language}.
|
||||
Principles: Show Don't Tell, Subtext.
|
||||
|
||||
Return JSON: {
|
||||
"narratorScript": "New text...",
|
||||
"visualDescription": "Updated visual...",
|
||||
"onScreenText": "Updated overlay...",
|
||||
"audioCues": "Updated audio..."
|
||||
}
|
||||
`;
|
||||
const promptData = buildSegmentRewritePrompt({
|
||||
currentScript: segment.narratorScript || '',
|
||||
newStyle,
|
||||
topic: segment.project.topic,
|
||||
language: segment.project.language,
|
||||
});
|
||||
|
||||
const rewriteResp = await this.gemini.generateJSON<{
|
||||
narratorScript: string;
|
||||
visualDescription: string;
|
||||
onScreenText: string;
|
||||
audioCues: string;
|
||||
}>(
|
||||
prompt,
|
||||
'{ narratorScript, visualDescription, onScreenText, audioCues }',
|
||||
);
|
||||
}>(promptData.prompt, promptData.schema, {
|
||||
temperature: promptData.temperature,
|
||||
});
|
||||
|
||||
const data = rewriteResp.data;
|
||||
const words = data.narratorScript
|
||||
@@ -359,25 +336,18 @@ export class ScriptsService {
|
||||
}
|
||||
|
||||
// 1. Generate/Refine Image Prompt using LLM
|
||||
const promptGenPrompt = `
|
||||
Create a detailed AI Image Generation Prompt and a Video Generation Prompt for this script segment.
|
||||
Topic: "${segment.project.topic}"
|
||||
Segment Content: "${segment.narratorScript}"
|
||||
Visual Context: "${segment.visualDescription}"
|
||||
|
||||
Goal: Create a highly detailed, cinematic, and artistic prompt optimized for tools like Midjourney, Flux, or Runway.
|
||||
Style: Cinematic, highly detailed, 8k, professional lighting.
|
||||
|
||||
Return JSON: {
|
||||
"imagePrompt": "Full detailed image prompt...",
|
||||
"videoPrompt": "Full detailed video prompt..."
|
||||
}
|
||||
`;
|
||||
const promptData = buildSegmentImagePrompt({
|
||||
topic: segment.project.topic,
|
||||
narratorScript: segment.narratorScript || '',
|
||||
visualDescription: segment.visualDescription || '',
|
||||
});
|
||||
|
||||
const prompts = await this.gemini.generateJSON<{
|
||||
imagePrompt: string;
|
||||
videoPrompt: string;
|
||||
}>(promptGenPrompt, '{ imagePrompt, videoPrompt }');
|
||||
}>(promptData.prompt, promptData.schema, {
|
||||
temperature: promptData.temperature,
|
||||
});
|
||||
|
||||
// 2. Use the new image prompt for generation
|
||||
const imageUrl = await this.analysisService.generateThumbnailImage(
|
||||
|
||||
Reference in New Issue
Block a user