Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

인코딩 응답 수정 #232

Merged
merged 4 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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