main
Some checks failed
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-04-05 21:11:00 +03:00
parent 9486f86cca
commit 6627c213ec
6 changed files with 63 additions and 13 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "cinematicReference" VARCHAR(200);

View File

@@ -264,6 +264,7 @@ model Project {
language String @default("tr") @db.VarChar(5) // ISO 639-1 language String @default("tr") @db.VarChar(5) // ISO 639-1
aspectRatio AspectRatio @default(PORTRAIT_9_16) aspectRatio AspectRatio @default(PORTRAIT_9_16)
videoStyle VideoStyle @default(CINEMATIC) videoStyle VideoStyle @default(CINEMATIC)
cinematicReference String? @db.VarChar(200)
targetDuration Int @default(60) // saniye targetDuration Int @default(60) // saniye
// SEO & Social Content (skill-enhanced) // SEO & Social Content (skill-enhanced)

View File

@@ -77,6 +77,12 @@ export class CreateProjectDto {
@IsOptional() @IsOptional()
videoStyle?: VideoStyleDto; videoStyle?: VideoStyleDto;
@ApiPropertyOptional({ description: 'Sinematik stil referansı (örn: Wes Anderson, Matrix)' })
@IsString()
@IsOptional()
@MaxLength(200)
cinematicReference?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Hedef video süresi (saniye)', description: 'Hedef video süresi (saniye)',
example: 60, example: 60,
@@ -138,6 +144,12 @@ export class UpdateProjectDto {
@IsOptional() @IsOptional()
videoStyle?: VideoStyleDto; videoStyle?: VideoStyleDto;
@ApiPropertyOptional({ description: 'Sinematik stil referansı (örn: Wes Anderson, Matrix)' })
@IsString()
@IsOptional()
@MaxLength(200)
cinematicReference?: string;
@ApiPropertyOptional({ description: 'Hedef video süresi (saniye)' }) @ApiPropertyOptional({ description: 'Hedef video süresi (saniye)' })
@IsInt() @IsInt()
@IsOptional() @IsOptional()
@@ -189,6 +201,12 @@ export class CreateFromTweetDto {
@IsOptional() @IsOptional()
videoStyle?: VideoStyleDto; 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 }) @ApiPropertyOptional({ description: 'Hedef video süresi (saniye)', default: 60 })
@IsInt() @IsInt()
@IsOptional() @IsOptional()

View File

@@ -201,6 +201,7 @@ export class ProjectsService {
targetDurationSeconds: project.targetDuration, targetDurationSeconds: project.targetDuration,
language: project.language, language: project.language,
videoStyle: project.videoStyle, videoStyle: project.videoStyle,
cinematicReference: (project as any).cinematicReference || undefined,
seoKeywords: (project as any).seoKeywords || [], seoKeywords: (project as any).seoKeywords || [],
referenceUrl: (project as any).referenceUrl || undefined, referenceUrl: (project as any).referenceUrl || undefined,
}); });
@@ -404,6 +405,7 @@ export class ProjectsService {
targetDurationSeconds: project.targetDuration, targetDurationSeconds: project.targetDuration,
language: project.language, language: project.language,
videoStyle: project.videoStyle, videoStyle: project.videoStyle,
cinematicReference: (project as any).cinematicReference || undefined,
seoKeywords: [], seoKeywords: [],
referenceUrl: dto.tweetUrl, referenceUrl: dto.tweetUrl,
sourceTweet: { sourceTweet: {
@@ -580,7 +582,7 @@ Sadece bu tek sahneyi üret. JSON formatında:
} }
async generateSceneImage(userId: string, projectId: string, sceneId: string, customPrompt?: string) { 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); const scene = project.scenes.find((s) => s.id === sceneId);
if (!scene) { if (!scene) {
throw new NotFoundException('Sahne bulunamadı'); throw new NotFoundException('Sahne bulunamadı');
@@ -602,9 +604,8 @@ Sadece bu tek sahneyi üret. JSON formatında:
const mappedRatio = aspectRatioMap[project.aspectRatio] || '9:16'; const mappedRatio = aspectRatioMap[project.aspectRatio] || '9:16';
// Görüntüyü üret // Görüntüyü üret
const imageResult = await this.geminiService.generateImageForScene( const imageResult = await this.geminiService.generateImage(
scene.visualPrompt, `${scene.visualPrompt}. Style: ${project.videoStyle}`,
project.videoStyle,
mappedRatio, mappedRatio,
); );
@@ -648,7 +649,7 @@ Sadece bu tek sahneyi üret. JSON formatında:
} }
async upscaleSceneImage(userId: string, projectId: string, sceneId: string) { 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); const scene = project.scenes.find((s) => s.id === sceneId);
if (!scene) { if (!scene) {
throw new NotFoundException('Sahne bulunamadı'); throw new NotFoundException('Sahne bulunamadı');

View File

@@ -7,6 +7,7 @@ export interface ScriptGenerationInput {
targetDurationSeconds: number; targetDurationSeconds: number;
language: string; language: string;
videoStyle: string; videoStyle: string;
cinematicReference?: string;
aspectRatio?: string; // PORTRAIT_9_16 | LANDSCAPE_16_9 | SQUARE_1_1 aspectRatio?: string; // PORTRAIT_9_16 | LANDSCAPE_16_9 | SQUARE_1_1
referenceUrl?: string; referenceUrl?: string;
seoKeywords?: string[]; seoKeywords?: string[];
@@ -595,7 +596,7 @@ export class VideoAiService {
const script = this.parseAndValidateScript(rawText); const script = this.parseAndValidateScript(rawText);
const humanizedScript = this.applyHumanizerPass(script); 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( this.logger.log(
`✅ Senaryo üretildi — "${enrichedScript.metadata.title}", ` + `✅ Senaryo üretildi — "${enrichedScript.metadata.title}", ` +
@@ -791,9 +792,10 @@ export class VideoAiService {
private enrichVisualPrompts( private enrichVisualPrompts(
script: GeneratedScript, script: GeneratedScript,
videoStyle: string, videoStyle: string,
cinematicReference?: string,
aspectRatio?: string, aspectRatio?: string,
): GeneratedScript { ): 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'; 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++) { 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. * 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<string, StyleDNA> = { const dnaMap: Record<string, StyleDNA> = {
CINEMATIC: { CINEMATIC: {
reference: 'Denis Villeneuve and Roger Deakins cinematography', reference: cinematicReference ? `${cinematicReference} visual style and cinematography` : 'Denis Villeneuve and Roger Deakins cinematography',
lighting: 'Dramatic key-and-fill lighting with a single strong motivated source casting deep sculpted shadows.', 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: 'Shot on 35mm anamorphic lens with shallow depth of field f/2.0 and characteristic oval bokeh.', 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: 'Teal-and-orange blockbuster color grade with desaturated midtones and crushed blacks.', color: cinematicReference ? `Color grading and palette uniquely associated with ${cinematicReference}` : '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.', 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: { DOCUMENTARY: {
reference: 'National Geographic and Planet Earth II', reference: 'National Geographic and Planet Earth II',

26
test-gemini-image.ts Normal file
View File

@@ -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();