Skip to content

Commit

Permalink
Merge pull request #232 from boostcampwm2023/feat/encode_action
Browse files Browse the repository at this point in the history
인코딩 응답 수정
  • Loading branch information
msjang4 authored Dec 7, 2023
2 parents e978ef3 + 167cacf commit 789ff48
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 73 deletions.
4 changes: 0 additions & 4 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions server/src/exceptions/encoding-action-fail.exception.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 2 additions & 0 deletions server/src/exceptions/enum/exception.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum ErrorCode {
BadVideoFormat = 8000,
BadThumbnailFormat = 8100,
BadRequestFormat = 8200,
EncodingActionFail = 4280,
}

const ErrorMessage = {
Expand All @@ -41,6 +42,7 @@ const ErrorMessage = {
[ErrorCode.ThumbnailUploadRequired]: '썸네일을 먼저 업로드 해야합니다.',
[ErrorCode.VideoUploadRequired]: '비디오를 먼저 업로드 해야합니다.',
[ErrorCode.BadRequestFormat]: '요청 형식이 잘못됨',
[ErrorCode.EncodingActionFail]: '인코딩 액션 실패',
};

export { ErrorCode, ErrorMessage };
2 changes: 1 addition & 1 deletion server/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand Down
40 changes: 4 additions & 36 deletions server/src/video/video.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,11 @@ import {
Post,
Delete,
Body,
Header,
UseInterceptors,
UseGuards,
Query,
} from '@nestjs/common';
import {
ApiBadRequestResponse,
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';
Expand All @@ -29,12 +22,11 @@ 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';
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';
Expand Down Expand Up @@ -70,7 +62,7 @@ export class VideoController {
}

/**
* 비디오 업로드
* 비디오 인코딩
*/
@UseInterceptors(
FileFieldsInterceptor([
Expand All @@ -86,6 +78,7 @@ export class VideoController {
VideoUploadRequiredException,
ThumbnailUploadRequiredException,
])
@ApiFailResponse('인코딩 실패', [EncodingActionFailException])
uploadVideo(
@Body() videoDto: VideoDto,
@RequestUser() user: User,
Expand All @@ -103,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,
);
}

/**
* 인기 비디오 반환
*/
Expand Down
73 changes: 41 additions & 32 deletions server/src/video/video.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -14,10 +13,11 @@ 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';
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';
Expand Down Expand Up @@ -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<VideoInfoDto> {
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을 붙여줘야함
Expand Down Expand Up @@ -170,9 +153,14 @@ export class VideoService {
throw new ThumbnailUploadRequiredException();
}

await requestEncoding(process.env.INPUT_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({
Expand All @@ -188,17 +176,38 @@ 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}`;
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) {
Expand Down

0 comments on commit 789ff48

Please sign in to comment.