@@ -5,40 +5,50 @@ import { v4 as uuidv4 } from 'uuid';
5
5
import { type AvatarData } from '~/common/services/azure-ai/avatar-video/types/avatar-data.js' ;
6
6
import { type AzureAIService } from '~/common/services/azure-ai/azure-ai.service.js' ;
7
7
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' ;
8
10
9
11
import { type VideoService } from '../videos/video.service.js' ;
10
12
import { REQUEST_DELAY } from './constants/constnats.js' ;
11
13
import {
12
14
GenerateAvatarResponseStatus ,
13
15
RenderVideoErrorMessage ,
14
16
} 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' ;
16
23
import {
17
24
type Composition ,
25
+ type GeneratedAvatarData ,
18
26
type RenderAvatarVideoRequestDto ,
19
27
} from './types/types.js' ;
20
28
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 ;
27
34
} ;
28
35
29
36
class AvatarVideoService {
30
37
private azureAIService : AzureAIService ;
31
38
private fileService : FileService ;
32
39
private videoService : VideoService ;
40
+ private remotionService : RemotionService ;
33
41
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 ) {
39
48
this . azureAIService = azureAIService ;
40
49
this . fileService = fileService ;
41
50
this . videoService = videoService ;
51
+ this . remotionService = remotionService ;
42
52
}
43
53
44
54
private async saveAvatarVideo ( url : string , id : string ) : Promise < string > {
@@ -70,7 +80,6 @@ class AvatarVideoService {
70
80
71
81
public async submitAvatarsConfigs (
72
82
configs : AvatarData [ ] ,
73
- userId : string ,
74
83
recordId : string ,
75
84
) : Promise < string [ ] > {
76
85
try {
@@ -87,7 +96,7 @@ class AvatarVideoService {
87
96
return response . id ;
88
97
} ) ;
89
98
90
- this . checkAvatarsProcessing ( ids , userId , recordId ) . catch ( ( ) => {
99
+ this . checkAvatarsProcessing ( ids , recordId ) . catch ( ( ) => {
91
100
throw new HttpError ( {
92
101
message : RenderVideoErrorMessage . RENDER_ERROR ,
93
102
status : HTTPCode . BAD_REQUEST ,
@@ -105,7 +114,6 @@ class AvatarVideoService {
105
114
106
115
public async checkAvatarsProcessing (
107
116
ids : string [ ] ,
108
- userId : string ,
109
117
videoRecordId : string ,
110
118
) : Promise < void > {
111
119
try {
@@ -116,7 +124,7 @@ class AvatarVideoService {
116
124
) ;
117
125
118
126
await this . handleSuccessfulAvatarsGeneration ( {
119
- avatars : response ,
127
+ generatedAvatars : response ,
120
128
videoRecordId,
121
129
} ) ;
122
130
} catch {
@@ -127,9 +135,7 @@ class AvatarVideoService {
127
135
}
128
136
}
129
137
130
- private checkAvatarStatus (
131
- id : string ,
132
- ) : Promise < { id : string ; url : string } > {
138
+ private checkAvatarStatus ( id : string ) : Promise < GeneratedAvatarData > {
133
139
return new Promise ( ( resolve , reject ) => {
134
140
const interval = setInterval ( ( ) => {
135
141
this . azureAIService
@@ -140,7 +146,12 @@ class AvatarVideoService {
140
146
GenerateAvatarResponseStatus . SUCCEEDED
141
147
) {
142
148
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
+ } ) ;
144
155
} else if (
145
156
response . status ===
146
157
GenerateAvatarResponseStatus . FAILED
@@ -170,21 +181,38 @@ class AvatarVideoService {
170
181
171
182
private async handleSuccessfulAvatarsGeneration ( {
172
183
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 ) ;
179
199
180
- if ( ! firstAvatarId || ! url ) {
200
+ await this . removeGeneratedAvatars ( generatedAvatars ) ;
201
+ await this . removeAvatarsFromBucket ( generatedAvatars ) ;
202
+
203
+ if ( ! url ) {
181
204
return ;
182
205
}
206
+ // TODO: NOTIFY USER
207
+ await this . updateVideoRecord ( videoRecordId , url ) ;
208
+ }
183
209
184
- const savedUrl = await this . saveAvatarVideo ( url , firstAvatarId ) ;
185
-
210
+ private async updateVideoRecord (
211
+ videoRecordId : string ,
212
+ videoUrl : string ,
213
+ ) : Promise < void > {
186
214
const videoData = await this . videoService . update ( videoRecordId , {
187
- url : savedUrl ,
215
+ url : videoUrl ,
188
216
} ) ;
189
217
190
218
if ( ! videoData ) {
@@ -193,13 +221,41 @@ class AvatarVideoService {
193
221
status : HTTPCode . BAD_REQUEST ,
194
222
} ) ;
195
223
}
224
+ }
196
225
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 ) => {
199
231
return this . azureAIService . removeAvatarVideo ( avatar . id ) ;
200
232
} ) ,
201
233
) ;
202
234
}
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
+ }
203
259
}
204
260
205
261
export { AvatarVideoService } ;
0 commit comments