Skip to content

Commit

Permalink
Merge branch 'next' into task/OV-368-create-consent-component
Browse files Browse the repository at this point in the history
  • Loading branch information
stefano-lacorazza authored Sep 24, 2024
2 parents 25dbec9 + c4390f9 commit 1fb3913
Show file tree
Hide file tree
Showing 96 changed files with 6,957 additions and 3,248 deletions.
3 changes: 0 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,5 @@
"shared": "*",
"swagger-jsdoc": "6.2.8",
"tiktoken": "1.0.16"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.9.5"
}
}
21 changes: 13 additions & 8 deletions backend/src/bundles/avatar-videos/avatar-videos.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,22 @@ class AvatarVideoController extends BaseController {
): Promise<ApiHandlerResponse> {
const userId = (options.user as UserGetCurrentResponseDto).id;

const videoRecord = await this.avatarVideoService.createVideo({
...options.body,
userId,
});
const { composition, name, videoId } = options.body;

const avatarsConfigs = this.avatarVideoService.getAvatarsConfigs(
options.body.composition,
);
const videoPayload = {
name,
composition,
};

const videoRecord = await (videoId
? this.avatarVideoService.updateVideo({ ...videoPayload, videoId })
: this.avatarVideoService.createVideo({ ...videoPayload, userId }));

const scenesConfigs =
this.avatarVideoService.getScenesConfigs(composition);

await this.avatarVideoService.submitAvatarsConfigs(
avatarsConfigs,
scenesConfigs,
videoRecord.id,
);

Expand Down
134 changes: 75 additions & 59 deletions backend/src/bundles/avatar-videos/avatar-videos.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { type VideoGetAllItemResponseDto } from 'shared';
import { HTTPCode, HttpError } from 'shared';
import { v4 as uuidv4 } from 'uuid';

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

import { type VideoService } from '../videos/video.service.js';
import { REQUEST_DELAY } from './constants/constnats.js';
Expand All @@ -22,8 +19,9 @@ import {
} from './helpers/helpers.js';
import {
type Composition,
type GeneratedAvatarData,
type RenderAvatarVideoRequestDto,
type SceneForRenderAvatar,
type SceneWithGeneratedAvatar,
} from './types/types.js';

type Constructor = {
Expand Down Expand Up @@ -74,36 +72,43 @@ class AvatarVideoService {
});
}

public getAvatarsConfigs(composition: Composition): AvatarData[] {
public async updateVideo({
videoId,
composition,
name,
}: RenderAvatarVideoRequestDto): Promise<VideoGetAllItemResponseDto> {
return await this.videoService.update(videoId as string, {
composition,
name: name,
});
}

public getScenesConfigs(composition: Composition): SceneForRenderAvatar[] {
return distributeScriptsToScenes(composition);
}

public async submitAvatarsConfigs(
configs: AvatarData[],
scenesForRenderAvatar: SceneForRenderAvatar[],
recordId: string,
): Promise<string[]> {
): Promise<void> {
try {
const responses = await Promise.all(
configs.map((config) => {
await Promise.all(
scenesForRenderAvatar.map((scene) => {
return this.azureAIService.renderAvatarVideo({
id: uuidv4(),
payload: config,
id: scene.id,
payload: scene.avatar,
});
}),
);

const ids = responses.map((response) => {
return response.id;
});

this.checkAvatarsProcessing(ids, recordId).catch(() => {
throw new HttpError({
message: RenderVideoErrorMessage.RENDER_ERROR,
status: HTTPCode.BAD_REQUEST,
});
});

return ids;
this.checkAvatarsProcessing(scenesForRenderAvatar, recordId).catch(
() => {
throw new HttpError({
message: RenderVideoErrorMessage.RENDER_ERROR,
status: HTTPCode.BAD_REQUEST,
});
},
);
} catch {
throw new HttpError({
message: RenderVideoErrorMessage.RENDER_ERROR,
Expand All @@ -113,18 +118,18 @@ class AvatarVideoService {
}

public async checkAvatarsProcessing(
ids: string[],
scenesForRenderAvatar: SceneForRenderAvatar[],
videoRecordId: string,
): Promise<void> {
try {
const response = await Promise.all(
ids.map((id) => {
return this.checkAvatarStatus(id);
scenesForRenderAvatar.map((scene) => {
return this.checkAvatarStatus(scene);
}),
);

await this.handleSuccessfulAvatarsGeneration({
generatedAvatars: response,
scenesWithGeneratedAvatars: response,
videoRecordId,
});
} catch {
Expand All @@ -135,20 +140,25 @@ class AvatarVideoService {
}
}

private checkAvatarStatus(id: string): Promise<GeneratedAvatarData> {
private checkAvatarStatus(
scene: SceneForRenderAvatar,
): Promise<SceneWithGeneratedAvatar> {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
this.azureAIService
.getAvatarVideo(id)
.getAvatarVideo(scene.id)
.then((response) => {
if (
response.status ===
GenerateAvatarResponseStatus.SUCCEEDED
) {
clearInterval(interval);

resolve({
id,
url: response.outputs.result,
...scene,
avatar: {
url: response.outputs.result,
},
durationInMilliseconds:
response.properties.durationInMilliseconds,
});
Expand Down Expand Up @@ -181,30 +191,33 @@ class AvatarVideoService {

private async handleSuccessfulAvatarsGeneration({
videoRecordId,
generatedAvatars,
scenesWithGeneratedAvatars,
}: {
videoRecordId: string;
generatedAvatars: GeneratedAvatarData[];
scenesWithGeneratedAvatars: SceneWithGeneratedAvatar[];
}): Promise<void> {
const scenes = generatedAvatarToRemotionScene(generatedAvatars);
const scenesWithSavedAvatars = await this.saveGeneratedAvatar(scenes);
const scenesWithSavedAvatars = await this.saveGeneratedAvatar(
scenesWithGeneratedAvatars,
);
const scenesForRendering = generatedAvatarToRemotionScene(
scenesWithSavedAvatars,
);

const renderId = await this.remotionService.renderVideo({
scenes: scenesWithSavedAvatars,
totalDurationInFrames: getTotalDuration(scenesWithSavedAvatars),
scenes: scenesForRendering,
totalDurationInFrames: getTotalDuration(scenesForRendering),
});

const url =
await this.remotionService.getRemotionRenderProgress(renderId);

await this.removeGeneratedAvatars(generatedAvatars);
await this.removeAvatarsFromBucket(generatedAvatars);

if (!url) {
return;
if (url) {
// TODO: NOTIFY USER
await this.updateVideoRecord(videoRecordId, url);
}
// TODO: NOTIFY USER
await this.updateVideoRecord(videoRecordId, url);

await this.removeGeneratedAvatars(scenesWithSavedAvatars);
await this.removeAvatarsFromBucket(scenesWithSavedAvatars);
}

private async updateVideoRecord(
Expand All @@ -224,35 +237,38 @@ class AvatarVideoService {
}

private async removeGeneratedAvatars(
generatedAvatars: GeneratedAvatarData[],
scenesWithGeneratedAvatars: SceneWithGeneratedAvatar[],
): Promise<unknown> {
return Promise.all(
generatedAvatars.map((avatar) => {
return this.azureAIService.removeAvatarVideo(avatar.id);
scenesWithGeneratedAvatars.map((scene) => {
return this.azureAIService.removeAvatarVideo(scene.id);
}),
);
}

private async saveGeneratedAvatar(
generatedAvatars: RemotionAvatarScene[],
): Promise<RemotionAvatarScene[]> {
return Promise.all(
generatedAvatars.map(async (avatar) => {
return {
durationInFrames: avatar.durationInFrames,
id: avatar.id,
url: await this.saveAvatarVideo(avatar.url, avatar.id),
};
scenesWithGeneratedAvatars: SceneWithGeneratedAvatar[],
): Promise<SceneWithGeneratedAvatar[]> {
const urls = await Promise.all(
scenesWithGeneratedAvatars.map(async (scene) => {
return this.saveAvatarVideo(scene.avatar.url, scene.id);
}),
);

return scenesWithGeneratedAvatars.map((scene, index) => ({
...scene,
avatar: {
url: urls[index] as string,
},
}));
}

private async removeAvatarsFromBucket(
generatedAvatars: GeneratedAvatarData[],
scenesWithGeneratedAvatars: SceneWithGeneratedAvatar[],
): Promise<unknown> {
return Promise.all(
generatedAvatars.map((avatar) => {
return this.fileService.deleteFile(getFileName(avatar.id));
scenesWithGeneratedAvatars.map((scene) => {
return this.fileService.deleteFile(getFileName(scene.id));
}),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const REQUEST_DELAY = 4000;
const REQUEST_DELAY = 5000;

export { REQUEST_DELAY };
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { type AvatarData } from '~/common/services/azure-ai/avatar-video/types/types.js';

import { ScriptProcessor } from '../services/script-processor.service.js';
import { type Scene, type Script } from '../types/types.js';
import {
type Scene,
type SceneForRenderAvatar,
type Script,
} from '../types/types.js';

function distributeScriptsToScenes({
scenes,
scripts,
}: {
scenes: Scene[];
scripts: Script[];
}): AvatarData[] {
}): SceneForRenderAvatar[] {
const processor = new ScriptProcessor(scenes, scripts);
return processor.distributeScriptsToScenes();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { type RemotionAvatarScene } from '~/common/services/remotion/type/types.js';

import { FPS } from '../constants/fps.js';
import { type GeneratedAvatarData } from '../types/types.js';
import { type SceneWithGeneratedAvatar } from '../types/types.js';

const generatedAvatarToRemotionScene = (
generatedAvatars: GeneratedAvatarData[],
scenesWithGeneratedAvatars: SceneWithGeneratedAvatar[],
): RemotionAvatarScene[] => {
return generatedAvatars.map((avatar) => {
return scenesWithGeneratedAvatars.map((scene) => {
return {
id: avatar.id,
url: avatar.url,
...scene,
durationInFrames: Math.round(
(avatar.durationInMilliseconds / 1000) * FPS,
(scene.durationInMilliseconds / 1000) * FPS,
),
};
});
Expand Down
Loading

0 comments on commit 1fb3913

Please sign in to comment.