Skip to content

Commit 5f8b668

Browse files
committed
Merge remote-tracking branch 'origin/next' into task/OV-311-save-video-draft
2 parents dab9fbc + a592049 commit 5f8b668

File tree

149 files changed

+11189
-7429
lines changed

Some content is hidden

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

149 files changed

+11189
-7429
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

+2-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';
@@ -93,13 +93,12 @@ class AvatarVideoController extends BaseController {
9393

9494
await this.avatarVideoService.submitAvatarsConfigs(
9595
avatarsConfigs,
96-
userId,
9796
videoRecord.id,
9897
);
9998

10099
return {
101100
payload: { status: ResponseStatus.SUBMITTED },
102-
status: HttpCode.CREATED,
101+
status: HTTPCode.CREATED,
103102
};
104103
}
105104
}

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> {
@@ -81,7 +91,6 @@ class AvatarVideoService {
8191

8292
public async submitAvatarsConfigs(
8393
configs: AvatarData[],
84-
userId: string,
8594
recordId: string,
8695
): Promise<string[]> {
8796
try {
@@ -98,25 +107,24 @@ class AvatarVideoService {
98107
return response.id;
99108
});
100109

101-
this.checkAvatarsProcessing(ids, userId, recordId).catch(() => {
110+
this.checkAvatarsProcessing(ids, recordId).catch(() => {
102111
throw new HttpError({
103112
message: RenderVideoErrorMessage.RENDER_ERROR,
104-
status: HttpCode.BAD_REQUEST,
113+
status: HTTPCode.BAD_REQUEST,
105114
});
106115
});
107116

108117
return ids;
109118
} catch {
110119
throw new HttpError({
111120
message: RenderVideoErrorMessage.RENDER_ERROR,
112-
status: HttpCode.BAD_REQUEST,
121+
status: HTTPCode.BAD_REQUEST,
113122
});
114123
}
115124
}
116125

117126
public async checkAvatarsProcessing(
118127
ids: string[],
119-
userId: string,
120128
videoRecordId: string,
121129
): Promise<void> {
122130
try {
@@ -127,20 +135,18 @@ class AvatarVideoService {
127135
);
128136

129137
await this.handleSuccessfulAvatarsGeneration({
130-
avatars: response,
138+
generatedAvatars: response,
131139
videoRecordId,
132140
});
133141
} catch {
134142
throw new HttpError({
135143
message: RenderVideoErrorMessage.RENDER_ERROR,
136-
status: HttpCode.BAD_REQUEST,
144+
status: HTTPCode.BAD_REQUEST,
137145
});
138146
}
139147
}
140148

141-
private checkAvatarStatus(
142-
id: string,
143-
): Promise<{ id: string; url: string }> {
149+
private checkAvatarStatus(id: string): Promise<GeneratedAvatarData> {
144150
return new Promise((resolve, reject) => {
145151
const interval = setInterval(() => {
146152
this.azureAIService
@@ -151,7 +157,12 @@ class AvatarVideoService {
151157
GenerateAvatarResponseStatus.SUCCEEDED
152158
) {
153159
clearInterval(interval);
154-
resolve({ id, url: response.outputs.result });
160+
resolve({
161+
id,
162+
url: response.outputs.result,
163+
durationInMilliseconds:
164+
response.properties.durationInMilliseconds,
165+
});
155166
} else if (
156167
response.status ===
157168
GenerateAvatarResponseStatus.FAILED
@@ -160,7 +171,7 @@ class AvatarVideoService {
160171
new HttpError({
161172
message:
162173
RenderVideoErrorMessage.RENDER_ERROR,
163-
status: HttpCode.BAD_REQUEST,
174+
status: HTTPCode.BAD_REQUEST,
164175
}),
165176
);
166177
clearInterval(interval);
@@ -170,7 +181,7 @@ class AvatarVideoService {
170181
reject(
171182
new HttpError({
172183
message: RenderVideoErrorMessage.RENDER_ERROR,
173-
status: HttpCode.BAD_REQUEST,
184+
status: HTTPCode.BAD_REQUEST,
174185
}),
175186
);
176187
clearInterval(interval);
@@ -181,36 +192,81 @@ class AvatarVideoService {
181192

182193
private async handleSuccessfulAvatarsGeneration({
183194
videoRecordId,
184-
avatars,
185-
}: HandleRenderVideoArguments): Promise<void> {
186-
// TODO: REPLACE THIS LOGIC WITH RENDER VIDEO
187-
// TODO: NOTIFY USER
188-
const firstAvatarId = avatars[0]?.id;
189-
const url = avatars[0]?.url;
195+
generatedAvatars,
196+
}: {
197+
videoRecordId: string;
198+
generatedAvatars: GeneratedAvatarData[];
199+
}): Promise<void> {
200+
const scenes = generatedAvatarToRemotionScene(generatedAvatars);
201+
const scenesWithSavedAvatars = await this.saveGeneratedAvatar(scenes);
202+
203+
const renderId = await this.remotionService.renderVideo({
204+
scenes: scenesWithSavedAvatars,
205+
totalDurationInFrames: getTotalDuration(scenesWithSavedAvatars),
206+
});
207+
208+
const url =
209+
await this.remotionService.getRemotionRenderProgress(renderId);
190210

191-
if (!firstAvatarId || !url) {
211+
await this.removeGeneratedAvatars(generatedAvatars);
212+
await this.removeAvatarsFromBucket(generatedAvatars);
213+
214+
if (!url) {
192215
return;
193216
}
217+
// TODO: NOTIFY USER
218+
await this.updateVideoRecord(videoRecordId, url);
219+
}
194220

195-
const savedUrl = await this.saveAvatarVideo(url, firstAvatarId);
196-
221+
private async updateVideoRecord(
222+
videoRecordId: string,
223+
videoUrl: string,
224+
): Promise<void> {
197225
const videoData = await this.videoService.update(videoRecordId, {
198-
url: savedUrl,
226+
url: videoUrl,
199227
});
200228

201229
if (!videoData) {
202230
throw new HttpError({
203231
message: RenderVideoErrorMessage.NOT_SAVED,
204-
status: HttpCode.BAD_REQUEST,
232+
status: HTTPCode.BAD_REQUEST,
205233
});
206234
}
235+
}
207236

208-
await Promise.all(
209-
avatars.map((avatar) => {
237+
private async removeGeneratedAvatars(
238+
generatedAvatars: GeneratedAvatarData[],
239+
): Promise<unknown> {
240+
return Promise.all(
241+
generatedAvatars.map((avatar) => {
210242
return this.azureAIService.removeAvatarVideo(avatar.id);
211243
}),
212244
);
213245
}
246+
247+
private async saveGeneratedAvatar(
248+
generatedAvatars: RemotionAvatarScene[],
249+
): Promise<RemotionAvatarScene[]> {
250+
return Promise.all(
251+
generatedAvatars.map(async (avatar) => {
252+
return {
253+
durationInFrames: avatar.durationInFrames,
254+
id: avatar.id,
255+
url: await this.saveAvatarVideo(avatar.url, avatar.id),
256+
};
257+
}),
258+
);
259+
}
260+
261+
private async removeAvatarsFromBucket(
262+
generatedAvatars: GeneratedAvatarData[],
263+
): Promise<unknown> {
264+
return Promise.all(
265+
generatedAvatars.map((avatar) => {
266+
return this.fileService.deleteFile(getFileName(avatar.id));
267+
}),
268+
);
269+
}
214270
}
215271

216272
export { AvatarVideoService };

0 commit comments

Comments
 (0)