From e0a89de0e3ec1af47a3c1b35f241feaee88d924b Mon Sep 17 00:00:00 2001 From: shio <85730998+dino3616@users.noreply.github.com> Date: Sat, 16 Mar 2024 13:28:18 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20(api)=20transcribeYoutubeVi?= =?UTF-8?q?deo=20=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/.gitignore | 4 + apps/api/package.json | 4 + .../api/src/common/service/env/env.service.ts | 1 - .../infra/langchain/langchain.controller.ts | 27 +++++++ .../src/infra/langchain/langchain.module.ts | 5 ++ .../src/infra/langchain/langchain.service.ts | 4 +- apps/api/src/infra/openai/openai.module.ts | 8 ++ apps/api/src/infra/openai/openai.service.ts | 25 ++++++ apps/api/src/infra/youtube/youtube.module.ts | 8 ++ apps/api/src/infra/youtube/youtube.service.ts | 74 ++++++++++++++++++ apps/api/tmp/.gitkeep | 0 bun.lockb | Bin 235690 -> 242722 bytes docker/api/Dockerfile.production | 8 +- 13 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 apps/api/src/infra/langchain/langchain.controller.ts create mode 100644 apps/api/src/infra/openai/openai.module.ts create mode 100644 apps/api/src/infra/openai/openai.service.ts create mode 100644 apps/api/src/infra/youtube/youtube.module.ts create mode 100644 apps/api/src/infra/youtube/youtube.service.ts create mode 100644 apps/api/tmp/.gitkeep diff --git a/apps/api/.gitignore b/apps/api/.gitignore index 4a545a7..f013de8 100644 --- a/apps/api/.gitignore +++ b/apps/api/.gitignore @@ -4,5 +4,9 @@ schema.gql # auto generated folder generated +# tmp files +tmp/* +!tmp/.gitkeep + # certificates *.crt diff --git a/apps/api/package.json b/apps/api/package.json index f31b622..3746608 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@as-integrations/fastify": "2.1.1", + "@distube/ytdl-core": "4.13.3", "@fastify/compress": "7.0.0", "@fastify/cors": "9.0.1", "@fastify/helmet": "11.1.1", @@ -33,7 +34,9 @@ "@nestjs/core": "10.3.3", "@nestjs/platform-fastify": "10.3.3", "@nestjs/testing": "10.3.3", + "fluent-ffmpeg": "2.1.2", "langchain": "0.1.27", + "openai": "4.29.1", "ts-pattern": "5.0.8", "uuid": "9.0.1", "zod": "3.22.4" @@ -42,6 +45,7 @@ "@hanjaemeo-api/tsconfig": "workspace:*", "@hanjaemeo-api/type": "workspace:*", "@nestjs/schematics": "10.1.1", + "@types/fluent-ffmpeg": "2.1.24", "@types/uuid": "9.0.8" } } diff --git a/apps/api/src/common/service/env/env.service.ts b/apps/api/src/common/service/env/env.service.ts index 117580d..8323ea3 100644 --- a/apps/api/src/common/service/env/env.service.ts +++ b/apps/api/src/common/service/env/env.service.ts @@ -6,7 +6,6 @@ export class EnvService { private readonly logger = new Logger(EnvService.name); constructor(@Inject(ConfigService) private readonly configService: ConfigService) { - this.logger.debug(`${EnvService.name} constructed`); this.logger.log(`NODE_ENV: ${this.NodeEnv}`); } diff --git a/apps/api/src/infra/langchain/langchain.controller.ts b/apps/api/src/infra/langchain/langchain.controller.ts new file mode 100644 index 0000000..4065e4c --- /dev/null +++ b/apps/api/src/infra/langchain/langchain.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Get, Inject, Logger, Param } from '@nestjs/common'; +import { OpenaiService } from '#api/infra/openai/openai.service'; +import { YoutubeService } from '../youtube/youtube.service'; + +@Controller() +export class LangchainController { + private readonly logger = new Logger(LangchainController.name); + + constructor( + @Inject(OpenaiService) + private readonly openaiService: OpenaiService, + @Inject(YoutubeService) + private readonly youtubeService: YoutubeService, + ) {} + + @Get('/predict/:id') + async transcribeYoutubeVideo(@Param('id') id: string): Promise { + this.logger.log(`${this.transcribeYoutubeVideo.name} called`); + this.logger.debug(`Transcribing video: https://www.youtube.com/watch?v=${id}`); + + const outputFilePath = await this.youtubeService.saveVideoStreamToFile(`https://www.youtube.com/watch?v=${id}`); + + const transcription = await this.openaiService.transcribeFromFile(outputFilePath); + + return transcription; + } +} diff --git a/apps/api/src/infra/langchain/langchain.module.ts b/apps/api/src/infra/langchain/langchain.module.ts index d29c764..991a65d 100644 --- a/apps/api/src/infra/langchain/langchain.module.ts +++ b/apps/api/src/infra/langchain/langchain.module.ts @@ -1,8 +1,13 @@ import { Global, Module } from '@nestjs/common'; +import { OpenaiModule } from '#api/infra/openai/openai.module'; +import { YoutubeModule } from '#api/infra/youtube/youtube.module'; +import { LangchainController } from './langchain.controller'; import { LangchainService } from './langchain.service'; @Global() @Module({ + imports: [OpenaiModule, YoutubeModule], + controllers: [LangchainController], providers: [LangchainService], exports: [LangchainService], }) diff --git a/apps/api/src/infra/langchain/langchain.service.ts b/apps/api/src/infra/langchain/langchain.service.ts index c883242..a2bec9b 100644 --- a/apps/api/src/infra/langchain/langchain.service.ts +++ b/apps/api/src/infra/langchain/langchain.service.ts @@ -30,9 +30,7 @@ export class LangchainService { private readonly logger = new Logger(LangchainService.name); - constructor(@Inject(EnvService) private readonly envService: EnvService) { - this.logger.debug(`${LangchainService.name} constructed`); - } + constructor(@Inject(EnvService) private readonly envService: EnvService) {} async embedding(text: string) { if (!this.embeddingModel) { diff --git a/apps/api/src/infra/openai/openai.module.ts b/apps/api/src/infra/openai/openai.module.ts new file mode 100644 index 0000000..a18b9dc --- /dev/null +++ b/apps/api/src/infra/openai/openai.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { OpenaiService } from './openai.service'; + +@Module({ + providers: [OpenaiService], + exports: [OpenaiService], +}) +export class OpenaiModule {} diff --git a/apps/api/src/infra/openai/openai.service.ts b/apps/api/src/infra/openai/openai.service.ts new file mode 100644 index 0000000..28f1c36 --- /dev/null +++ b/apps/api/src/infra/openai/openai.service.ts @@ -0,0 +1,25 @@ +import fs from 'node:fs'; +import { Inject, Injectable } from '@nestjs/common'; +import { OpenAI } from 'openai'; +import { EnvService } from '#api/common/service/env/env.service'; + +@Injectable() +export class OpenaiService { + private readonly openai: OpenAI; + + constructor(@Inject(EnvService) private readonly envService: EnvService) { + this.openai = new OpenAI({ apiKey: this.envService.OpenaiApiKey }); + } + + async transcribeFromFile(path: string): Promise { + const res = await this.openai.audio.transcriptions.create({ + model: 'whisper-1', + language: 'ko', + timestamp_granularities: ['segment'], + response_format: 'verbose_json', + file: fs.createReadStream(path), + }); + + return res.text; + } +} diff --git a/apps/api/src/infra/youtube/youtube.module.ts b/apps/api/src/infra/youtube/youtube.module.ts new file mode 100644 index 0000000..34099c2 --- /dev/null +++ b/apps/api/src/infra/youtube/youtube.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { YoutubeService } from './youtube.service'; + +@Module({ + providers: [YoutubeService], + exports: [YoutubeService], +}) +export class YoutubeModule {} diff --git a/apps/api/src/infra/youtube/youtube.service.ts b/apps/api/src/infra/youtube/youtube.service.ts new file mode 100644 index 0000000..5eed303 --- /dev/null +++ b/apps/api/src/infra/youtube/youtube.service.ts @@ -0,0 +1,74 @@ +import fs from 'node:fs'; +import ytdl from '@distube/ytdl-core'; +import { Injectable, Logger } from '@nestjs/common'; +import ffmpeg from 'fluent-ffmpeg'; + +@Injectable() +export class YoutubeService { + private readonly logger = new Logger(YoutubeService.name); + + async saveVideoStreamToFile(url: string): Promise { + const videoInfo = await this.getVideoInfo(url); + + const downloadedPath = this.isAlreadyDownloaded(videoInfo.videoDetails.videoId) + ? `./tmp/${videoInfo.videoDetails.videoId}.mp4` + : await this.getSoundOnlyVideoStream(videoInfo); + + const audioFilePath = this.isAlreadyConverted(videoInfo.videoDetails.videoId) + ? `./tmp/${videoInfo.videoDetails.videoId}.mp3` + : await this.convertToMp3(downloadedPath, videoInfo.videoDetails.videoId); + + return audioFilePath; + } + + private async getVideoInfo(url: string) { + const videoInfo = await ytdl.getInfo(url); + + return videoInfo; + } + + private isAlreadyDownloaded(id: string): boolean { + const downloaded = fs.existsSync(`./tmp/${id}.mp4`); + + return downloaded; + } + + private async getSoundOnlyVideoStream(videoInfo: ytdl.videoInfo): Promise { + const stream = await ytdl + .downloadFromInfo(videoInfo, { + filter: 'audioonly', + requestOptions: { + reset: true, + }, + }) + .pipe(fs.createWriteStream(`./tmp/${videoInfo.videoDetails.videoId}.mp4`)); + + this.logger.debug(`Downloaded: ${videoInfo.videoDetails.videoId}`); + + return stream.path as string; + } + + private isAlreadyConverted(id: string): boolean { + const downloaded = fs.existsSync(`./tmp/${id}.mp3`); + + return downloaded; + } + + private async convertToMp3(inputPath: string, id: string): Promise { + await new Promise((resolve, reject) => { + ffmpeg(inputPath) + .outputFormat('mp3') + .on('end', () => { + resolve(void undefined); + }) + .on('error', error => { + reject(error); + }) + .save(`./tmp/${id}.mp3`); + }); + + this.logger.debug(`Converted to mp3: ${id}`); + + return `./tmp/${id}.mp3`; + } +} diff --git a/apps/api/tmp/.gitkeep b/apps/api/tmp/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/bun.lockb b/bun.lockb index 5dc6595504053eae1445639dad16d80f5d3c9d97..4e93839f5634ab54f191b7868baa7fedb2aca466 100755 GIT binary patch delta 41012 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⁣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 delta 36671 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%0Dtr(TFhdk`pW{mYHuZJfUXc`dMd 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 Gwf_tGYFu9c diff --git a/docker/api/Dockerfile.production b/docker/api/Dockerfile.production index eeca218..c2f4db9 100644 --- a/docker/api/Dockerfile.production +++ b/docker/api/Dockerfile.production @@ -12,9 +12,15 @@ RUN bun install --frozen-lockfile --ignore-scripts # hadolint ignore=DL3059 RUN bun turbo --filter='@hanjaemeo-api/api' build -FROM oven/bun:1.0.30-alpine AS runner +FROM oven/bun:1.0.30-slim AS runner ENV NODE_ENV=production +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get --no-install-recommends -y install ffmpeg \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists + WORKDIR /hanjaemeo-api/ COPY --from=builder /hanjaemeo-api/node_modules/ ./node_modules/