1
1
import { type VideoGetAllItemResponseDto } from 'shared' ;
2
- import { HttpCode , HttpError } from 'shared' ;
2
+ import { HTTPCode , HttpError } from 'shared' ;
3
3
import { v4 as uuidv4 } from 'uuid' ;
4
4
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 > {
@@ -81,7 +91,6 @@ class AvatarVideoService {
81
91
82
92
public async submitAvatarsConfigs (
83
93
configs : AvatarData [ ] ,
84
- userId : string ,
85
94
recordId : string ,
86
95
) : Promise < string [ ] > {
87
96
try {
@@ -98,25 +107,24 @@ class AvatarVideoService {
98
107
return response . id ;
99
108
} ) ;
100
109
101
- this . checkAvatarsProcessing ( ids , userId , recordId ) . catch ( ( ) => {
110
+ this . checkAvatarsProcessing ( ids , recordId ) . catch ( ( ) => {
102
111
throw new HttpError ( {
103
112
message : RenderVideoErrorMessage . RENDER_ERROR ,
104
- status : HttpCode . BAD_REQUEST ,
113
+ status : HTTPCode . BAD_REQUEST ,
105
114
} ) ;
106
115
} ) ;
107
116
108
117
return ids ;
109
118
} catch {
110
119
throw new HttpError ( {
111
120
message : RenderVideoErrorMessage . RENDER_ERROR ,
112
- status : HttpCode . BAD_REQUEST ,
121
+ status : HTTPCode . BAD_REQUEST ,
113
122
} ) ;
114
123
}
115
124
}
116
125
117
126
public async checkAvatarsProcessing (
118
127
ids : string [ ] ,
119
- userId : string ,
120
128
videoRecordId : string ,
121
129
) : Promise < void > {
122
130
try {
@@ -127,20 +135,18 @@ class AvatarVideoService {
127
135
) ;
128
136
129
137
await this . handleSuccessfulAvatarsGeneration ( {
130
- avatars : response ,
138
+ generatedAvatars : response ,
131
139
videoRecordId,
132
140
} ) ;
133
141
} catch {
134
142
throw new HttpError ( {
135
143
message : RenderVideoErrorMessage . RENDER_ERROR ,
136
- status : HttpCode . BAD_REQUEST ,
144
+ status : HTTPCode . BAD_REQUEST ,
137
145
} ) ;
138
146
}
139
147
}
140
148
141
- private checkAvatarStatus (
142
- id : string ,
143
- ) : Promise < { id : string ; url : string } > {
149
+ private checkAvatarStatus ( id : string ) : Promise < GeneratedAvatarData > {
144
150
return new Promise ( ( resolve , reject ) => {
145
151
const interval = setInterval ( ( ) => {
146
152
this . azureAIService
@@ -151,7 +157,12 @@ class AvatarVideoService {
151
157
GenerateAvatarResponseStatus . SUCCEEDED
152
158
) {
153
159
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
+ } ) ;
155
166
} else if (
156
167
response . status ===
157
168
GenerateAvatarResponseStatus . FAILED
@@ -160,7 +171,7 @@ class AvatarVideoService {
160
171
new HttpError ( {
161
172
message :
162
173
RenderVideoErrorMessage . RENDER_ERROR ,
163
- status : HttpCode . BAD_REQUEST ,
174
+ status : HTTPCode . BAD_REQUEST ,
164
175
} ) ,
165
176
) ;
166
177
clearInterval ( interval ) ;
@@ -170,7 +181,7 @@ class AvatarVideoService {
170
181
reject (
171
182
new HttpError ( {
172
183
message : RenderVideoErrorMessage . RENDER_ERROR ,
173
- status : HttpCode . BAD_REQUEST ,
184
+ status : HTTPCode . BAD_REQUEST ,
174
185
} ) ,
175
186
) ;
176
187
clearInterval ( interval ) ;
@@ -181,36 +192,81 @@ class AvatarVideoService {
181
192
182
193
private async handleSuccessfulAvatarsGeneration ( {
183
194
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 ) ;
190
210
191
- if ( ! firstAvatarId || ! url ) {
211
+ await this . removeGeneratedAvatars ( generatedAvatars ) ;
212
+ await this . removeAvatarsFromBucket ( generatedAvatars ) ;
213
+
214
+ if ( ! url ) {
192
215
return ;
193
216
}
217
+ // TODO: NOTIFY USER
218
+ await this . updateVideoRecord ( videoRecordId , url ) ;
219
+ }
194
220
195
- const savedUrl = await this . saveAvatarVideo ( url , firstAvatarId ) ;
196
-
221
+ private async updateVideoRecord (
222
+ videoRecordId : string ,
223
+ videoUrl : string ,
224
+ ) : Promise < void > {
197
225
const videoData = await this . videoService . update ( videoRecordId , {
198
- url : savedUrl ,
226
+ url : videoUrl ,
199
227
} ) ;
200
228
201
229
if ( ! videoData ) {
202
230
throw new HttpError ( {
203
231
message : RenderVideoErrorMessage . NOT_SAVED ,
204
- status : HttpCode . BAD_REQUEST ,
232
+ status : HTTPCode . BAD_REQUEST ,
205
233
} ) ;
206
234
}
235
+ }
207
236
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 ) => {
210
242
return this . azureAIService . removeAvatarVideo ( avatar . id ) ;
211
243
} ) ,
212
244
) ;
213
245
}
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
+ }
214
270
}
215
271
216
272
export { AvatarVideoService } ;
0 commit comments