From 94752df2b24c14fa75ba3c8395240d09cd6a7608 Mon Sep 17 00:00:00 2001 From: 5tarry Date: Wed, 6 Dec 2023 22:49:04 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20=EB=B9=84=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=EC=9D=B8=EC=BD=94=EB=94=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20act?= =?UTF-8?q?ion=20=EC=8B=A4=ED=96=89=20=EB=B0=8F=20=EC=9D=B8=EC=BD=94?= =?UTF-8?q?=EB=94=A9=20=EC=99=84=EB=A3=8C=20=ED=9B=84=20=EC=9D=91=EB=8B=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/.gitignore | 4 --- .../encoding-action-fail.exception.ts | 9 ++++++ server/src/exceptions/enum/exception.enum.ts | 2 ++ server/src/video/video.controller.ts | 3 +- server/src/video/video.service.ts | 31 +++++++++++++++++-- 5 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 server/src/exceptions/encoding-action-fail.exception.ts diff --git a/server/.gitignore b/server/.gitignore index 43d03da3..a2e6062a 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -35,7 +35,3 @@ lerna-debug.log* !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json - -src/test.py -src/video/common.response.dto.ts -src/video/video.decorator.ts \ No newline at end of file diff --git a/server/src/exceptions/encoding-action-fail.exception.ts b/server/src/exceptions/encoding-action-fail.exception.ts new file mode 100644 index 00000000..dba1bb3e --- /dev/null +++ b/server/src/exceptions/encoding-action-fail.exception.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; +import { ErrorCode } from 'src/exceptions/enum/exception.enum'; +import { BaseException } from './base.exception'; + +export class EncodingActionFailException extends BaseException { + constructor() { + super(ErrorCode.EncodingActionFail, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/server/src/exceptions/enum/exception.enum.ts b/server/src/exceptions/enum/exception.enum.ts index e948621b..c4f8b269 100644 --- a/server/src/exceptions/enum/exception.enum.ts +++ b/server/src/exceptions/enum/exception.enum.ts @@ -19,6 +19,7 @@ enum ErrorCode { BadVideoFormat = 8000, BadThumbnailFormat = 8100, BadRequestFormat = 8200, + EncodingActionFail = 4280, } const ErrorMessage = { @@ -41,6 +42,7 @@ const ErrorMessage = { [ErrorCode.ThumbnailUploadRequired]: '썸네일을 먼저 업로드 해야합니다.', [ErrorCode.VideoUploadRequired]: '비디오를 먼저 업로드 해야합니다.', [ErrorCode.BadRequestFormat]: '요청 형식이 잘못됨', + [ErrorCode.EncodingActionFail]: '인코딩 액션 실패', }; export { ErrorCode, ErrorMessage }; diff --git a/server/src/video/video.controller.ts b/server/src/video/video.controller.ts index e67b7ef0..2ced92ef 100644 --- a/server/src/video/video.controller.ts +++ b/server/src/video/video.controller.ts @@ -12,7 +12,6 @@ import { Query, } from '@nestjs/common'; import { - ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiProduces, @@ -70,7 +69,7 @@ export class VideoController { } /** - * 비디오 업로드 + * 비디오 인코딩 */ @UseInterceptors( FileFieldsInterceptor([ diff --git a/server/src/video/video.service.ts b/server/src/video/video.service.ts index 17a49905..349366cb 100644 --- a/server/src/video/video.service.ts +++ b/server/src/video/video.service.ts @@ -4,7 +4,6 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; -import { requestEncoding } from 'src/ncpAPI/requestEncoding'; import { User } from 'src/user/schemas/user.schema'; import { deleteObject } from 'src/ncpAPI/deleteObject'; import { VideoNotFoundException } from 'src/exceptions/video-not-found.exception'; @@ -18,6 +17,7 @@ import { checkUpload } from 'src/ncpAPI/listObjects'; import { VideoUploadRequiredException } from 'src/exceptions/video-upload-required-exception copy'; import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2'; import { BadRequestFormatException } from 'src/exceptions/bad-request-format.exception'; +import { EncodingActionFailException } from 'src/exceptions/encoding-action-fail.exception'; import { VideoDto } from './dto/video.dto'; import { Video } from './schemas/video.schema'; import { CategoryEnum } from './enum/category.enum'; @@ -170,7 +170,11 @@ export class VideoService { throw new ThumbnailUploadRequiredException(); } - await requestEncoding(process.env.INPUT_BUCKET, [videoName]); + await this.requestEncodingAPI( + process.env.INPUT_BUCKET, + process.env.OUTPUT_BUCKET, + videoName, + ); const uploader = await this.UserModel.findOne({ uuid }, { _id: 1 }); @@ -188,6 +192,29 @@ export class VideoService { return { videoId, ...videoDto }; } + async requestEncodingAPI( + inputBucket: string, + outputBucket: string, + objectName: string, + ) { + const videoUrl = await createPresignedUrl(inputBucket, objectName, 'GET'); + const accessKey = process.env.ACCESS_KEY; + const secretKey = process.env.SECRET_KEY; + const data = { + videoUrl, + object_name: objectName, + outputBucket, + accessKey, + secretKey, + }; + console.log(data); + try { + await axios.post(process.env.ENCODING_API_URL, data); + } catch (error) { + throw new EncodingActionFailException(); + } + } + async deleteEncodedVideo(videoId: string) { const encodingSuffixes = process.env.ENCODING_SUFFIXES.split(','); const fileNamePrefix = `${process.env.VIDEO_OUTPUT_PATH}/${videoId}`; From d513abdae322ad68d7971fa72f7e4927eb0d83f1 Mon Sep 17 00:00:00 2001 From: 5tarry Date: Wed, 6 Dec 2023 22:57:13 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat=20:=20swagger=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/video/video.controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/video/video.controller.ts b/server/src/video/video.controller.ts index 2ced92ef..4945f601 100644 --- a/server/src/video/video.controller.ts +++ b/server/src/video/video.controller.ts @@ -34,6 +34,7 @@ import { VideoConflictException } from 'src/exceptions/video-conflict.exception' import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2'; import { VideoUploadRequiredException } from 'src/exceptions/video-upload-required-exception copy'; import { BadRequestFormatException } from 'src/exceptions/bad-request-format.exception'; +import { EncodingActionFailException } from 'src/exceptions/encoding-action-fail.exception'; import { VideoService } from './video.service'; import { VideoDto } from './dto/video.dto'; import { VideoRatingDTO } from './dto/video-rating.dto'; @@ -85,6 +86,7 @@ export class VideoController { VideoUploadRequiredException, ThumbnailUploadRequiredException, ]) + @ApiFailResponse('인코딩 실패', [EncodingActionFailException]) uploadVideo( @Body() videoDto: VideoDto, @RequestUser() user: User, From a39abe4c809b97e05d7fa3e423c33280f58c9b3f Mon Sep 17 00:00:00 2001 From: 5tarry Date: Thu, 7 Dec 2023 13:40:39 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat=20:=20manifest=20url=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: msjang4 --- server/src/user/user.service.ts | 2 +- server/src/video/video.controller.ts | 35 +--------------------------- server/src/video/video.service.ts | 19 +-------------- 3 files changed, 3 insertions(+), 53 deletions(-) diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index 2d5b80f4..25d61043 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -144,7 +144,7 @@ export class UserService { const rating = raterCount ? (totalRating / raterCount).toFixed(1) : null; - const manifest = `${process.env.MANIFEST_URL_PREFIX}${videoInfo._id}_,${process.env.ENCODING_SUFFIXES}${process.env.ABR_MANIFEST_URL_SUFFIX}`; + const manifest = `${process.env.MANIFEST_URL_PREFIX}/${videoInfo._id}_master.m3u8`; const thumbnailImageUrl = await createPresignedUrl( process.env.THUMBNAIL_BUCKET, `${videoInfo._id}.${thumbnailExtension}`, diff --git a/server/src/video/video.controller.ts b/server/src/video/video.controller.ts index 4945f601..cced770e 100644 --- a/server/src/video/video.controller.ts +++ b/server/src/video/video.controller.ts @@ -6,17 +6,11 @@ import { Post, Delete, Body, - Header, UseInterceptors, UseGuards, Query, } from '@nestjs/common'; -import { - ApiBearerAuth, - ApiOkResponse, - ApiProduces, - ApiTags, -} from '@nestjs/swagger'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { FileFieldsInterceptor } from '@nestjs/platform-express'; import { AuthGuard } from 'src/auth/auth.guard'; import { ApiFailResponse } from 'src/decorators/api-fail-response'; @@ -28,8 +22,6 @@ import { NotYourVideoException } from 'src/exceptions/not-your-video.exception'; import { RequestUser, User } from 'src/decorators/request-user'; import { ActionService } from 'src/action/action.service'; import { NeverViewVideoException } from 'src/exceptions/never-view-video.exception'; -import { IgnoreInterceptor } from 'src/decorators/ignore-interceptor'; -import { SeedQueryDto } from 'src/action/dto/manifest-query.dto'; import { VideoConflictException } from 'src/exceptions/video-conflict.exception'; import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2'; import { VideoUploadRequiredException } from 'src/exceptions/video-upload-required-exception copy'; @@ -104,31 +96,6 @@ export class VideoController { return this.videoService.getTopRatedVideo(query.category); } - /** - * Manifest 파일 반환 - */ - @IgnoreInterceptor() - @ApiTags('LEGACY') - @Get(':id/manifest') - @ApiProduces('application/vnd.apple.mpegurl') - @ApiOkResponse({ - type: String, - description: '비디오 Manifest 파일', - }) - @ApiFailResponse('비디오를 찾을 수 없음', [VideoNotFoundException]) - @Header('content-type', 'application/vnd.apple.mpegurl') - getManifest( - @Param('id') videoId: string, - @RequestUser() user: User, - @Query() manifestQueryDto: SeedQueryDto, - ) { - return this.videoService.getManifest( - videoId, - user.id, - manifestQueryDto.seed, - ); - } - /** * 인기 비디오 반환 */ diff --git a/server/src/video/video.service.ts b/server/src/video/video.service.ts index 349366cb..fa74b386 100644 --- a/server/src/video/video.service.ts +++ b/server/src/video/video.service.ts @@ -103,28 +103,11 @@ export class VideoService { return { videos: videoInfos, seed: viewSeed }; } - async getManifest(videoId: string, userId: string, seed: number) { - this.actionService.viewVideo(videoId, userId, seed); - - const encodingSuffixes = process.env.ENCODING_SUFFIXES.split(','); - const manifestURL = `${process.env.MANIFEST_URL_PREFIX}${videoId}_,${process.env.ENCODING_SUFFIXES}${process.env.ABR_MANIFEST_URL_SUFFIX}`; - const manifest: string = await axios - .get(manifestURL) - .then((res) => res.data); - - let index = -1; - const modifiedManifest = manifest.replace(/.*\.m3u8$/gm, () => { - index += 1; - return `${process.env.MANIFEST_URL_PREFIX}${videoId}_${encodingSuffixes[index]}${process.env.SBR_MANIFEST_URL_SUFFIX}`; - }); - return modifiedManifest; - } - async getVideoInfo(video: any): Promise { const { totalRating, raterCount, uploaderId, ...videoInfo } = video; const rating = raterCount ? (totalRating / raterCount).toFixed(1) : null; - const manifest = `${process.env.MANIFEST_URL_PREFIX}${videoInfo._id}_,${process.env.ENCODING_SUFFIXES}${process.env.ABR_MANIFEST_URL_SUFFIX}`; + const manifest = `${process.env.MANIFEST_URL_PREFIX}/${videoInfo._id}_master.m3u8`; const { profileImageExtension, uuid, ...uploaderInfo } = '_doc' in uploaderId ? uploaderId._doc : uploaderId; // uploaderId가 model인경우 _doc을 붙여줘야함 From 167cacf93c05814bd79a0bfcde256f44c4765511 Mon Sep 17 00:00:00 2001 From: 5tarry Date: Thu, 7 Dec 2023 14:20:37 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat=20:=20=EB=B9=84=EB=94=94=EC=98=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=9D=B8=EC=BD=94=EB=94=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: msjang4 --- server/src/video/video.service.ts | 33 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/server/src/video/video.service.ts b/server/src/video/video.service.ts index fa74b386..57eb3cb8 100644 --- a/server/src/video/video.service.ts +++ b/server/src/video/video.service.ts @@ -13,7 +13,7 @@ import * as _ from 'lodash'; import { ActionService } from 'src/action/action.service'; import { createPresignedUrl } from 'src/ncpAPI/presignedURL'; import { VideoConflictException } from 'src/exceptions/video-conflict.exception'; -import { checkUpload } from 'src/ncpAPI/listObjects'; +import { checkUpload, listObjects } from 'src/ncpAPI/listObjects'; import { VideoUploadRequiredException } from 'src/exceptions/video-upload-required-exception copy'; import { ThumbnailUploadRequiredException } from 'src/exceptions/thumbnail-upload-required-exception copy 2'; import { BadRequestFormatException } from 'src/exceptions/bad-request-format.exception'; @@ -153,13 +153,14 @@ export class VideoService { throw new ThumbnailUploadRequiredException(); } - await this.requestEncodingAPI( - process.env.INPUT_BUCKET, - process.env.OUTPUT_BUCKET, - videoName, - ); - - const uploader = await this.UserModel.findOne({ uuid }, { _id: 1 }); + const [__, uploader] = await Promise.all([ + this.requestEncodingAPI( + process.env.INPUT_BUCKET, + process.env.OUTPUT_BUCKET, + videoName, + ), + this.UserModel.findOne({ uuid }, { _id: 1 }), + ]); const { title, content, category } = videoDto; const newVideo = new this.VideoModel({ @@ -199,16 +200,14 @@ export class VideoService { } async deleteEncodedVideo(videoId: string) { - const encodingSuffixes = process.env.ENCODING_SUFFIXES.split(','); - const fileNamePrefix = `${process.env.VIDEO_OUTPUT_PATH}/${videoId}`; - return Promise.all([ - ...encodingSuffixes.map((suffix) => - deleteObject( - process.env.OUTPUT_BUCKET, - `${fileNamePrefix}_${suffix}.mp4`, - ), + const deleteList = await listObjects(process.env.OUTPUT_BUCKET, { + prefix: videoId, + }); + return Promise.all( + deleteList.map((fileName: string) => + deleteObject(process.env.OUTPUT_BUCKET, fileName), ), - ]); + ); } async deleteVideo(videoId: string, uuid: string) {