diff --git a/prisma/migrations/20260405180153_add_cinematic_reference/migration.sql b/prisma/migrations/20260405180153_add_cinematic_reference/migration.sql new file mode 100644 index 0000000..ede3a2f --- /dev/null +++ b/prisma/migrations/20260405180153_add_cinematic_reference/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "cinematicReference" VARCHAR(200); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ac09385..0ab5ab6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -264,6 +264,7 @@ model Project { language String @default("tr") @db.VarChar(5) // ISO 639-1 aspectRatio AspectRatio @default(PORTRAIT_9_16) videoStyle VideoStyle @default(CINEMATIC) + cinematicReference String? @db.VarChar(200) targetDuration Int @default(60) // saniye // SEO & Social Content (skill-enhanced) diff --git a/src/modules/projects/dto/project.dto.ts b/src/modules/projects/dto/project.dto.ts index 6821d77..e93ef31 100644 --- a/src/modules/projects/dto/project.dto.ts +++ b/src/modules/projects/dto/project.dto.ts @@ -77,6 +77,12 @@ export class CreateProjectDto { @IsOptional() videoStyle?: VideoStyleDto; + @ApiPropertyOptional({ description: 'Sinematik stil referansı (örn: Wes Anderson, Matrix)' }) + @IsString() + @IsOptional() + @MaxLength(200) + cinematicReference?: string; + @ApiPropertyOptional({ description: 'Hedef video süresi (saniye)', example: 60, @@ -138,6 +144,12 @@ export class UpdateProjectDto { @IsOptional() videoStyle?: VideoStyleDto; + @ApiPropertyOptional({ description: 'Sinematik stil referansı (örn: Wes Anderson, Matrix)' }) + @IsString() + @IsOptional() + @MaxLength(200) + cinematicReference?: string; + @ApiPropertyOptional({ description: 'Hedef video süresi (saniye)' }) @IsInt() @IsOptional() @@ -189,6 +201,12 @@ export class CreateFromTweetDto { @IsOptional() videoStyle?: VideoStyleDto; + @ApiPropertyOptional({ description: 'Sinematik stil referansı (örn: Wes Anderson, Matrix)' }) + @IsString() + @IsOptional() + @MaxLength(200) + cinematicReference?: string; + @ApiPropertyOptional({ description: 'Hedef video süresi (saniye)', default: 60 }) @IsInt() @IsOptional() diff --git a/src/modules/projects/projects.service.ts b/src/modules/projects/projects.service.ts index 7e123a9..18c3451 100644 --- a/src/modules/projects/projects.service.ts +++ b/src/modules/projects/projects.service.ts @@ -201,6 +201,7 @@ export class ProjectsService { targetDurationSeconds: project.targetDuration, language: project.language, videoStyle: project.videoStyle, + cinematicReference: (project as any).cinematicReference || undefined, seoKeywords: (project as any).seoKeywords || [], referenceUrl: (project as any).referenceUrl || undefined, }); @@ -404,6 +405,7 @@ export class ProjectsService { targetDurationSeconds: project.targetDuration, language: project.language, videoStyle: project.videoStyle, + cinematicReference: (project as any).cinematicReference || undefined, seoKeywords: [], referenceUrl: dto.tweetUrl, sourceTweet: { @@ -580,7 +582,7 @@ Sadece bu tek sahneyi üret. JSON formatında: } async generateSceneImage(userId: string, projectId: string, sceneId: string, customPrompt?: string) { - const project = await this.findOne(projectId, userId); + const project = await this.findOne(userId, projectId); const scene = project.scenes.find((s) => s.id === sceneId); if (!scene) { throw new NotFoundException('Sahne bulunamadı'); @@ -602,9 +604,8 @@ Sadece bu tek sahneyi üret. JSON formatında: const mappedRatio = aspectRatioMap[project.aspectRatio] || '9:16'; // Görüntüyü üret - const imageResult = await this.geminiService.generateImageForScene( - scene.visualPrompt, - project.videoStyle, + const imageResult = await this.geminiService.generateImage( + `${scene.visualPrompt}. Style: ${project.videoStyle}`, mappedRatio, ); @@ -648,7 +649,7 @@ Sadece bu tek sahneyi üret. JSON formatında: } async upscaleSceneImage(userId: string, projectId: string, sceneId: string) { - const project = await this.findOne(projectId, userId); + const project = await this.findOne(userId, projectId); const scene = project.scenes.find((s) => s.id === sceneId); if (!scene) { throw new NotFoundException('Sahne bulunamadı'); diff --git a/src/modules/video-ai/video-ai.service.ts b/src/modules/video-ai/video-ai.service.ts index 26733c1..7ef7f09 100644 --- a/src/modules/video-ai/video-ai.service.ts +++ b/src/modules/video-ai/video-ai.service.ts @@ -7,6 +7,7 @@ export interface ScriptGenerationInput { targetDurationSeconds: number; language: string; videoStyle: string; + cinematicReference?: string; aspectRatio?: string; // PORTRAIT_9_16 | LANDSCAPE_16_9 | SQUARE_1_1 referenceUrl?: string; seoKeywords?: string[]; @@ -595,7 +596,7 @@ export class VideoAiService { const script = this.parseAndValidateScript(rawText); const humanizedScript = this.applyHumanizerPass(script); - const enrichedScript = this.enrichVisualPrompts(humanizedScript, input.videoStyle, input.aspectRatio); + const enrichedScript = this.enrichVisualPrompts(humanizedScript, input.videoStyle, input.cinematicReference, input.aspectRatio); this.logger.log( `✅ Senaryo üretildi — "${enrichedScript.metadata.title}", ` + @@ -791,9 +792,10 @@ export class VideoAiService { private enrichVisualPrompts( script: GeneratedScript, videoStyle: string, + cinematicReference?: string, aspectRatio?: string, ): GeneratedScript { - const styleDNA = this.getStyleDNA(videoStyle); + 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'; for (let i = 0; i < script.scenes.length; i++) { @@ -885,14 +887,14 @@ export class VideoAiService { /** * Video stiline göre varsayılan görsel DNA değerlerini döndürür. */ - private getStyleDNA(videoStyle: string): StyleDNA { + private getStyleDNA(videoStyle: string, cinematicReference?: string): StyleDNA { const dnaMap: Record = { CINEMATIC: { - reference: 'Denis Villeneuve and Roger Deakins cinematography', - lighting: 'Dramatic key-and-fill lighting with a single strong motivated source casting deep sculpted shadows.', - lens: 'Shot on 35mm anamorphic lens with shallow depth of field f/2.0 and characteristic oval bokeh.', - color: 'Teal-and-orange blockbuster color grade with desaturated midtones and crushed blacks.', - texture: 'Subtle Kodak Vision3 film grain, anamorphic horizontal lens flare, slight vignette darkening corners.', + 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.', }, DOCUMENTARY: { reference: 'National Geographic and Planet Earth II', diff --git a/test-gemini-image.ts b/test-gemini-image.ts new file mode 100644 index 0000000..c1caf79 --- /dev/null +++ b/test-gemini-image.ts @@ -0,0 +1,26 @@ +import { GoogleGenAI } from '@google/genai'; +import * as dotenv from 'dotenv'; + +dotenv.config(); + +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + +async function listModels() { + console.log('Listing models...'); + try { + let hasImagen = false; + let hasPreview = false; + // Iterate over pagination using valid SDK method if possible, but let's just use REST if not found. + // However, the new `@google/genai` has ai.models.list() + const response = await ai.models.list(); + for await (const m of response) { + if (m.name.includes('image') || m.name.includes('imagen')) { + console.log(m.name); + } + } + } catch (error) { + console.error('Error listing models:', error); + } +} + +listModels();