Skip to content

Commit 0f1fc2b

Browse files
Merge branch 'next' into fix/OV-326-close-menu-body-click-outside-open-modal
2 parents ed52161 + a592049 commit 0f1fc2b

File tree

144 files changed

+11139
-7386
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

144 files changed

+11139
-7386
lines changed

backend/.env.example

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ AWS_SECRET_ACCESS_KEY=see-in-slack
2626
AWS_S3_REGION=eu-north-1
2727
AWS_S3_BUCKET_NAME=bsa-2024-outreachvids
2828
AWS_CLOUDFRONT_DOMAIN_ID=d2tm5q3cg1nlwf
29+
AWS_CLOUDFRONT_DOMAIN_ID_FOR_RENDERED_VIDEO=SOME_SECRET_KEY
2930

3031
#
3132
# OPEN AI
@@ -48,3 +49,10 @@ ORIGIN=http://localhost:3000
4849
AZURE_SUBSCRIPTION_KEY=see-in-slack
4950
AZURE_SERVICE_REGION=see-in-slack
5051
AZURE_SERVICE_ENDPOINT=see-in-slack
52+
53+
#
54+
# REMOTION
55+
#
56+
REMOTION_LAMBDA_FUNCTION_NAME=SOME_SECRET_KEY
57+
REMOTION_SERVE_URL=SOME_SECRET_KEY
58+
REMOTION_BUCKET_NAME=SOME_SECRET_KEY

backend/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@fastify/static": "7.0.4",
3939
"@fastify/swagger": "8.15.0",
4040
"@fastify/swagger-ui": "4.0.1",
41+
"@remotion/lambda": "4.0.201",
4142
"bcrypt": "5.1.1",
4243
"convict": "6.2.4",
4344
"dotenv": "16.4.5",

backend/src/bundles/auth/auth.controller.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
BaseController,
1111
} from '~/common/controller/controller.js';
1212
import { ApiPath } from '~/common/enums/enums.js';
13-
import { HttpCode, HTTPMethod } from '~/common/http/http.js';
13+
import { HTTPCode, HTTPMethod } from '~/common/http/http.js';
1414
import { type Logger } from '~/common/logger/logger.js';
1515

1616
import { type AuthService } from './auth.service.js';
@@ -95,7 +95,7 @@ class AuthController extends BaseController {
9595
): Promise<ApiHandlerResponse> {
9696
return {
9797
payload: await this.authService.signIn(options.body),
98-
status: HttpCode.OK,
98+
status: HTTPCode.OK,
9999
};
100100
}
101101

@@ -144,7 +144,7 @@ class AuthController extends BaseController {
144144
}>,
145145
): Promise<ApiHandlerResponse> {
146146
return {
147-
status: HttpCode.CREATED,
147+
status: HTTPCode.CREATED,
148148
payload: await this.authService.signUp(options.body),
149149
};
150150
}

backend/src/bundles/auth/auth.service.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type UserSignInRequestDto,
88
type UserSignInResponseDto,
99
} from '~/bundles/users/users.js';
10-
import { HttpCode, HttpError } from '~/common/http/http.js';
10+
import { HTTPCode, HttpError } from '~/common/http/http.js';
1111
import { cryptService, tokenService } from '~/common/services/services.js';
1212

1313
import { UserValidationMessage } from './enums/enums.js';
@@ -28,21 +28,21 @@ class AuthService {
2828
if (!user) {
2929
throw new HttpError({
3030
message: UserValidationMessage.WRONG_CREDENTIALS,
31-
status: HttpCode.BAD_REQUEST,
31+
status: HTTPCode.BAD_REQUEST,
3232
});
3333
}
3434

3535
const { passwordHash } = user.toNewObject();
3636

37-
const isPwdCorrect = cryptService.compareSyncPassword(
37+
const isPasswordCorrect = cryptService.compareSyncPassword(
3838
password,
3939
passwordHash,
4040
);
4141

42-
if (!isPwdCorrect) {
42+
if (!isPasswordCorrect) {
4343
throw new HttpError({
4444
message: UserValidationMessage.WRONG_CREDENTIALS,
45-
status: HttpCode.BAD_REQUEST,
45+
status: HTTPCode.BAD_REQUEST,
4646
});
4747
}
4848

@@ -60,7 +60,7 @@ class AuthService {
6060
if (emailExists) {
6161
throw new HttpError({
6262
message: UserValidationMessage.EMAIL_ALREADY_EXISTS,
63-
status: HttpCode.BAD_REQUEST,
63+
status: HTTPCode.BAD_REQUEST,
6464
});
6565
}
6666
const user = await this.userService.create(userRequestDto);

backend/src/bundles/avatar-videos/avatar-videos.controller.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type ApiHandlerResponse,
66
} from '~/common/controller/controller.js';
77
import { BaseController } from '~/common/controller/controller.js';
8-
import { HttpCode, HTTPMethod } from '~/common/http/http.js';
8+
import { HTTPCode, HTTPMethod } from '~/common/http/http.js';
99
import { type Logger } from '~/common/logger/logger.js';
1010

1111
import { type AvatarVideoService } from './avatar-videos.service.js';
@@ -77,6 +77,7 @@ class AvatarVideoController extends BaseController {
7777
}>,
7878
): Promise<ApiHandlerResponse> {
7979
const userId = (options.user as UserGetCurrentResponseDto).id;
80+
8081
const videoRecord = await this.avatarVideoService.createVideo({
8182
...options.body,
8283
userId,
@@ -88,13 +89,12 @@ class AvatarVideoController extends BaseController {
8889

8990
await this.avatarVideoService.submitAvatarsConfigs(
9091
avatarsConfigs,
91-
userId,
9292
videoRecord.id,
9393
);
9494

9595
return {
9696
payload: { status: ResponseStatus.SUBMITTED },
97-
status: HttpCode.CREATED,
97+
status: HTTPCode.CREATED,
9898
};
9999
}
100100
}

backend/src/bundles/avatar-videos/avatar-videos.service.ts

+95-39
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,54 @@
11
import { type VideoGetAllItemResponseDto } from 'shared';
2-
import { HttpCode, HttpError } from 'shared';
2+
import { HTTPCode, HttpError } from 'shared';
33
import { v4 as uuidv4 } from 'uuid';
44

55
import { type AvatarData } from '~/common/services/azure-ai/avatar-video/types/avatar-data.js';
66
import { type AzureAIService } from '~/common/services/azure-ai/azure-ai.service.js';
77
import { type FileService } from '~/common/services/file/file.service.js';
8+
import { type RemotionService } from '~/common/services/remotion/remotion.service.js';
9+
import { type RemotionAvatarScene } from '~/common/services/remotion/type/types.js';
810

911
import { type VideoService } from '../videos/video.service.js';
1012
import { REQUEST_DELAY } from './constants/constnats.js';
1113
import {
1214
GenerateAvatarResponseStatus,
1315
RenderVideoErrorMessage,
1416
} from './enums/enums.js';
15-
import { distributeScriptsToScenes, getFileName } from './helpers/helpers.js';
17+
import { generatedAvatarToRemotionScene } from './helpers/generated-avatars-to-remotion-scenes.helper.js';
18+
import {
19+
distributeScriptsToScenes,
20+
getFileName,
21+
getTotalDuration,
22+
} from './helpers/helpers.js';
1623
import {
1724
type Composition,
25+
type GeneratedAvatarData,
1826
type RenderAvatarVideoRequestDto,
1927
} from './types/types.js';
2028

21-
type HandleRenderVideoArguments = {
22-
videoRecordId: string;
23-
avatars: {
24-
id: string;
25-
url: string;
26-
}[];
29+
type Constructor = {
30+
azureAIService: AzureAIService;
31+
fileService: FileService;
32+
videoService: VideoService;
33+
remotionService: RemotionService;
2734
};
2835

2936
class AvatarVideoService {
3037
private azureAIService: AzureAIService;
3138
private fileService: FileService;
3239
private videoService: VideoService;
40+
private remotionService: RemotionService;
3341

34-
public constructor(
35-
azureAIService: AzureAIService,
36-
fileService: FileService,
37-
videoService: VideoService,
38-
) {
42+
public constructor({
43+
azureAIService,
44+
fileService,
45+
remotionService,
46+
videoService,
47+
}: Constructor) {
3948
this.azureAIService = azureAIService;
4049
this.fileService = fileService;
4150
this.videoService = videoService;
51+
this.remotionService = remotionService;
4252
}
4353

4454
private async saveAvatarVideo(url: string, id: string): Promise<string> {
@@ -70,7 +80,6 @@ class AvatarVideoService {
7080

7181
public async submitAvatarsConfigs(
7282
configs: AvatarData[],
73-
userId: string,
7483
recordId: string,
7584
): Promise<string[]> {
7685
try {
@@ -87,25 +96,24 @@ class AvatarVideoService {
8796
return response.id;
8897
});
8998

90-
this.checkAvatarsProcessing(ids, userId, recordId).catch(() => {
99+
this.checkAvatarsProcessing(ids, recordId).catch(() => {
91100
throw new HttpError({
92101
message: RenderVideoErrorMessage.RENDER_ERROR,
93-
status: HttpCode.BAD_REQUEST,
102+
status: HTTPCode.BAD_REQUEST,
94103
});
95104
});
96105

97106
return ids;
98107
} catch {
99108
throw new HttpError({
100109
message: RenderVideoErrorMessage.RENDER_ERROR,
101-
status: HttpCode.BAD_REQUEST,
110+
status: HTTPCode.BAD_REQUEST,
102111
});
103112
}
104113
}
105114

106115
public async checkAvatarsProcessing(
107116
ids: string[],
108-
userId: string,
109117
videoRecordId: string,
110118
): Promise<void> {
111119
try {
@@ -116,20 +124,18 @@ class AvatarVideoService {
116124
);
117125

118126
await this.handleSuccessfulAvatarsGeneration({
119-
avatars: response,
127+
generatedAvatars: response,
120128
videoRecordId,
121129
});
122130
} catch {
123131
throw new HttpError({
124132
message: RenderVideoErrorMessage.RENDER_ERROR,
125-
status: HttpCode.BAD_REQUEST,
133+
status: HTTPCode.BAD_REQUEST,
126134
});
127135
}
128136
}
129137

130-
private checkAvatarStatus(
131-
id: string,
132-
): Promise<{ id: string; url: string }> {
138+
private checkAvatarStatus(id: string): Promise<GeneratedAvatarData> {
133139
return new Promise((resolve, reject) => {
134140
const interval = setInterval(() => {
135141
this.azureAIService
@@ -140,7 +146,12 @@ class AvatarVideoService {
140146
GenerateAvatarResponseStatus.SUCCEEDED
141147
) {
142148
clearInterval(interval);
143-
resolve({ id, url: response.outputs.result });
149+
resolve({
150+
id,
151+
url: response.outputs.result,
152+
durationInMilliseconds:
153+
response.properties.durationInMilliseconds,
154+
});
144155
} else if (
145156
response.status ===
146157
GenerateAvatarResponseStatus.FAILED
@@ -149,7 +160,7 @@ class AvatarVideoService {
149160
new HttpError({
150161
message:
151162
RenderVideoErrorMessage.RENDER_ERROR,
152-
status: HttpCode.BAD_REQUEST,
163+
status: HTTPCode.BAD_REQUEST,
153164
}),
154165
);
155166
clearInterval(interval);
@@ -159,7 +170,7 @@ class AvatarVideoService {
159170
reject(
160171
new HttpError({
161172
message: RenderVideoErrorMessage.RENDER_ERROR,
162-
status: HttpCode.BAD_REQUEST,
173+
status: HTTPCode.BAD_REQUEST,
163174
}),
164175
);
165176
clearInterval(interval);
@@ -170,36 +181,81 @@ class AvatarVideoService {
170181

171182
private async handleSuccessfulAvatarsGeneration({
172183
videoRecordId,
173-
avatars,
174-
}: HandleRenderVideoArguments): Promise<void> {
175-
// TODO: REPLACE THIS LOGIC WITH RENDER VIDEO
176-
// TODO: NOTIFY USER
177-
const firstAvatarId = avatars[0]?.id;
178-
const url = avatars[0]?.url;
184+
generatedAvatars,
185+
}: {
186+
videoRecordId: string;
187+
generatedAvatars: GeneratedAvatarData[];
188+
}): Promise<void> {
189+
const scenes = generatedAvatarToRemotionScene(generatedAvatars);
190+
const scenesWithSavedAvatars = await this.saveGeneratedAvatar(scenes);
191+
192+
const renderId = await this.remotionService.renderVideo({
193+
scenes: scenesWithSavedAvatars,
194+
totalDurationInFrames: getTotalDuration(scenesWithSavedAvatars),
195+
});
196+
197+
const url =
198+
await this.remotionService.getRemotionRenderProgress(renderId);
179199

180-
if (!firstAvatarId || !url) {
200+
await this.removeGeneratedAvatars(generatedAvatars);
201+
await this.removeAvatarsFromBucket(generatedAvatars);
202+
203+
if (!url) {
181204
return;
182205
}
206+
// TODO: NOTIFY USER
207+
await this.updateVideoRecord(videoRecordId, url);
208+
}
183209

184-
const savedUrl = await this.saveAvatarVideo(url, firstAvatarId);
185-
210+
private async updateVideoRecord(
211+
videoRecordId: string,
212+
videoUrl: string,
213+
): Promise<void> {
186214
const videoData = await this.videoService.update(videoRecordId, {
187-
url: savedUrl,
215+
url: videoUrl,
188216
});
189217

190218
if (!videoData) {
191219
throw new HttpError({
192220
message: RenderVideoErrorMessage.NOT_SAVED,
193-
status: HttpCode.BAD_REQUEST,
221+
status: HTTPCode.BAD_REQUEST,
194222
});
195223
}
224+
}
196225

197-
await Promise.all(
198-
avatars.map((avatar) => {
226+
private async removeGeneratedAvatars(
227+
generatedAvatars: GeneratedAvatarData[],
228+
): Promise<unknown> {
229+
return Promise.all(
230+
generatedAvatars.map((avatar) => {
199231
return this.azureAIService.removeAvatarVideo(avatar.id);
200232
}),
201233
);
202234
}
235+
236+
private async saveGeneratedAvatar(
237+
generatedAvatars: RemotionAvatarScene[],
238+
): Promise<RemotionAvatarScene[]> {
239+
return Promise.all(
240+
generatedAvatars.map(async (avatar) => {
241+
return {
242+
durationInFrames: avatar.durationInFrames,
243+
id: avatar.id,
244+
url: await this.saveAvatarVideo(avatar.url, avatar.id),
245+
};
246+
}),
247+
);
248+
}
249+
250+
private async removeAvatarsFromBucket(
251+
generatedAvatars: GeneratedAvatarData[],
252+
): Promise<unknown> {
253+
return Promise.all(
254+
generatedAvatars.map((avatar) => {
255+
return this.fileService.deleteFile(getFileName(avatar.id));
256+
}),
257+
);
258+
}
203259
}
204260

205261
export { AvatarVideoService };

0 commit comments

Comments
 (0)