Skip to content

[#170] 배포#332

Merged
wnsgh7368 merged 3 commits intomainfrom
dev
Feb 12, 2026
Merged

[#170] 배포#332
wnsgh7368 merged 3 commits intomainfrom
dev

Conversation

@wnsgh7368
Copy link
Copy Markdown
Collaborator

@wnsgh7368 wnsgh7368 self-assigned this Feb 12, 2026
@wnsgh7368 wnsgh7368 merged commit 221778a into main Feb 12, 2026
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @wnsgh7368, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 비디오 처리 파이프라인의 견고성과 안정성을 대폭 향상시키는 데 중점을 둡니다. 특히, 다양한 형식의 비디오 청크를 병합하고 HLS로 변환하는 과정에서 발생할 수 있는 문제를 해결하기 위해 새로운 중간 변환 단계를 도입하고 인코딩 파라미터를 최적화했습니다. 또한, 비디오 청크 업로드 시 MIME 타입 감지 로직을 개선하고, 공유 링크가 올바른 프론트엔드 URL을 사용하도록 환경 변수를 조정했습니다. 이러한 변경사항들은 전반적인 비디오 처리 워크플로우의 신뢰성을 높이고 사용자 경험을 개선하는 데 기여합니다.

Highlights

  • 비디오 청크 병합 로직 개선: webm과 mp4 컨테이너에 대한 병합 전략을 분리하고, mp4의 경우 concat demuxer 실패 시 raw append로 폴백하는 로직을 추가하여 안정성을 높였습니다.
  • 중간 MP4 변환 단계 도입: HLS 변환 전, 모든 비디오 청크를 libx264+aac 코덱의 '중간 MP4'로 통일하는 단계를 추가하여 타임스탬프 정규화, 코덱 호환성 향상, 오디오 싱크 보정을 구현했습니다.
  • HLS 인코딩 파라미터 최적화: HLS 변환 시 720p 고정 스케일링, GOP(키프레임 간격) 안정화, independent_segments 플래그 추가를 통해 재생 안정성과 호환성을 강화했습니다.
  • 비디오 청크 MIME 타입 감지 개선: 업로드된 비디오 청크의 MIME 타입을 file.mimetype 외에 파일 확장자(mp4, webm)를 통해 추론하는 로직을 추가하여 유연성을 높였습니다.
  • 공유 링크 기본 URL 환경 변수 변경: 공유 링크 생성 시 사용되는 기본 URL을 SERVER_URL에서 FRONTEND_URL로 변경하여 올바른 프론트엔드 경로를 참조하도록 수정했습니다.
Changelog
  • src/services/conversion/videoTranscode.service.js
    • mergedExt 변수 로직이 mp4 우선으로 변경되었습니다.
    • buildOrderedChunkPaths, transcodeToIntermediateMp4, mergeByConcatDemuxer, mergeByRawAppend 함수가 새로 추가되었습니다.
    • mergeChunks 함수가 webm과 mp4 컨테이너에 따라 다른 병합 전략을 사용하도록 재구성되었으며, mp4의 경우 concat demuxer 실패 시 raw append로 폴백하는 로직이 추가되었습니다.
    • encodeToHLS 함수에 720p 고정 스케일링, GOP 안정화, independent_segments 관련 FFmpeg 옵션이 추가되었습니다.
  • src/services/shareLink.service.js
    • baseUrl을 설정하는 환경 변수가 process.env.SERVER_URL에서 process.env.FRONTEND_URL로 변경되었습니다.
  • src/services/video.service.js
    • uploadVideoChunk 함수 내에 resolveVideoMime 함수가 추가되어 MIME 타입 감지 로직이 개선되었습니다.
    • ext 및 contentType 변수가 resolvedMime을 사용하도록 업데이트되었습니다.
Activity
  • 이 Pull Request는 현재까지 특별한 리뷰 활동이나 코멘트가 기록되지 않았습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 풀 리퀘스트는 비디오 처리 파이프라인을 크게 개선하여 트랜스코딩 프로세스를 더욱 견고하고 유연하게 만듭니다. 중간 MP4 변환 단계를 도입하고 다양한 비디오 컨테이너 유형(webm/mp4)을 보다 지능적으로 처리합니다. 또한 비디오 청크 업로드 중 MIME 유형 감지 신뢰성을 향상시키고 공유 링크의 기본 URL을 수정합니다. HLS 변환을 위한 추가 FFmpeg 매개변수는 보다 안정적이고 호환 가능한 스트리밍을 제공할 것입니다.

Comment on lines +189 to +266
const buildOrderedChunkPaths = (chunksDir, orderedChunks, ext) =>
orderedChunks.map((chunk) =>
path.join(chunksDir, `chunk_${String(chunk.chunkIndex).padStart(5, "0")}.${ext}`)
);

/**
* 청크를 바이트 단위로 순서대로 재조립한 뒤 ffmpeg로 재인코딩
* - 프론트에서 완성 Blob을 raw split해 업로드한 경우를 처리
* 청크로 만든 입력(webm/mp4, concat list 포함)을 "중간 mp4"로 통일합니다.
* 왜 필요하나:
* - 브라우저/청크마다 타임스탬프가 흔들릴 수 있어서(+genpts, make_zero) 먼저 정규화해야
* 뒤 HLS 변환에서 길이/싱크가 안정적입니다.
* - 코덱을 libx264+aac으로 고정해 플레이어 호환성을 높입니다.
* - aresample=async로 오디오 타임라인을 보정해 소리 싱크 깨짐을 줄입니다.
*/
const transcodeToIntermediateMp4 = async (inputPath, outputPath, chunksDir, mode = "raw") => {
const inputArgs =
mode === "concat"
? ["-f", "concat", "-safe", "0", "-i", inputPath]
: ["-i", inputPath];

await runCmd(
FFMPEG,
[
"-fflags",
"+genpts",
"-avoid_negative_ts",
"make_zero",
...inputArgs,
"-c:v",
"libx264",
"-preset",
"veryfast",
"-crf",
"21",
"-pix_fmt",
"yuv420p",
"-c:a",
"aac",
"-b:a",
"128k",
"-ar",
"48000",
"-ac",
"2",
"-af",
"aresample=async=1:first_pts=0",
"-movflags",
"+faststart",
"-max_muxing_queue_size",
"1024",
"-y",
outputPath,
],
{ cwd: chunksDir }
);
};

const mergeByConcatDemuxer = async (chunkPaths, outputPath, chunksDir) => {
const concatListPath = path.join(chunksDir, "concat.txt");
const concatContent = chunkPaths
.map((chunkPath) => `file '${path.basename(chunkPath).replaceAll("'", "'\\''")}'`)
.join("\n");
await fs.writeFile(concatListPath, `${concatContent}\n`, "utf-8");
await transcodeToIntermediateMp4(concatListPath, outputPath, chunksDir, "concat");
};

const mergeByRawAppend = async (orderedChunks, chunksDir, ext) => {
const assembledInputPath = path.join(chunksDir, `assembled.${ext}`);
const chunkPaths = buildOrderedChunkPaths(chunksDir, orderedChunks, ext);

for (let i = 0; i < chunkPaths.length; i += 1) {
await pipeline(
createReadStream(chunkPaths[i]),
createWriteStream(assembledInputPath, { flags: i === 0 ? "w" : "a" })
);
}

return assembledInputPath;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

새로운 buildOrderedChunkPaths, transcodeToIntermediateMp4, mergeByConcatDemuxer, mergeByRawAppend 함수가 도입되어 비디오 청크 병합 및 중간 트랜스코딩 프로세스를 모듈화하고 개선합니다. 특히 transcodeToIntermediateMp4는 안정적인 HLS 변환을 위해 타임스탬프 정규화, 코덱 고정 및 오디오 타임라인 보정을 위한 FFmpeg 인수를 잘 정의하고 있습니다. 이는 파이프라인의 견고성을 크게 향상시킵니다.

Comment on lines +293 to +313
if (ext === "webm") {
const assembledInputPath = await mergeByRawAppend(orderedChunks, chunksDir, ext);
await transcodeToIntermediateMp4(assembledInputPath, outputPath, chunksDir);
console.log("[VideoTranscode] webm chunk merge succeeded with raw append");
return;
}

await pipeline(
createReadStream(chunkPath),
createWriteStream(assembledInputPath, { flags: i === 0 ? "w" : "a" })
);
const chunkPaths = buildOrderedChunkPaths(chunksDir, orderedChunks, ext);
try {
await mergeByConcatDemuxer(chunkPaths, outputPath, chunksDir);
console.log("[VideoTranscode] mp4 chunk merge succeeded with concat demuxer");
return;
} catch (concatError) {
const concatMessage =
concatError?.message?.split("\n").slice(0, 6).join("\n") || String(concatError);
console.warn(`[VideoTranscode] mp4 concat merge failed, fallback to raw append\n${concatMessage}`);
}

await runCmd(
FFMPEG,
[
"-fflags",
"+genpts",
"-i",
assembledInputPath,
"-c:v",
"libx264",
"-preset",
"ultrafast",
"-pix_fmt",
"yuv420p",
"-c:a",
"aac", // 오디오 재인코딩
"-b:a",
"128k",
"-movflags",
"faststart",
"-y",
outputPath,
],
{ cwd: chunksDir }
);
const assembledInputPath = await mergeByRawAppend(orderedChunks, chunksDir, ext);
await transcodeToIntermediateMp4(assembledInputPath, outputPath, chunksDir);
console.log("[VideoTranscode] mp4 chunk merge succeeded with raw append fallback");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

mergeChunks 함수가 webmmp4 컨테이너 유형에 따라 다른 병합 전략을 사용하도록 리팩토링되었습니다. mp4의 경우 concat demuxer를 먼저 시도하고 실패 시 raw append로 폴백하는 것은 매우 견고한 접근 방식입니다. 오류 발생 시 console.warn을 사용하여 폴백을 기록하는 것도 디버깅에 유용합니다.

// GCS 원본 청크 확장자
const mergedExt = video.container === "webm" ? "webm" : "mp4";
// 업로드 컨테이너별 병합 경로 선택 (webm/mp4)
const mergedExt = video.container === "mp4" ? "mp4" : "webm";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

이 변경은 mergedExt 변수의 로직을 video.containermp4인 경우 mp4로, 그렇지 않으면 webm으로 설정하도록 수정합니다. 이는 ALLOWED_VIDEO_MIME에 정의된 두 가지 허용된 컨테이너 유형을 고려할 때 올바른 논리적 반전입니다. 이전 로직은 webm을 우선시했지만, 이제 mp4를 우선시하여 컨테이너 유형에 따라 올바른 확장자를 선택합니다.

Comment on lines +327 to 343
"-vf",
"scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2",
"-c:v",
"libx264",
"-preset",
"veryfast",
"-crf",
"23",
"-s",
"1280x720",
"-pix_fmt",
"yuv420p",
"-g",
"60",
"-keyint_min",
"60",
"-sc_threshold",
"0",
"-c:a",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

HLS 변환에 -vf 필터(스케일 및 패딩), -pix_fmt yuv420p, 그리고 GOP(키프레임 간격) 제어를 위한 -g, -keyint_min, -sc_threshold 옵션이 추가되었습니다. 이 매개변수들은 HLS 출력의 일관된 해상도, 색상 형식 및 안정적인 세그먼트 경계를 보장하여 스트리밍 품질과 호환성을 향상시킵니다.

Comment on lines +357 to +358
"-hls_flags",
"independent_segments",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

HLS 변환에 -hls_flags independent_segments 플래그를 추가한 것은 매우 중요합니다. 이 플래그는 각 HLS 세그먼트가 독립적으로 디코딩될 수 있도록 보장하여, 특히 스트리밍 중 탐색 및 오류 복구 기능을 크게 향상시킵니다.

const existingLink = await findExistingLink(projectId, scope, videoId);

const baseUrl = process.env.SERVER_URL || process.env.LOCAL_URL;
const baseUrl = process.env.FRONTEND_URL || process.env.LOCAL_URL;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

baseUrlprocess.env.SERVER_URL에서 process.env.FRONTEND_URL로 변경한 것은 공유 링크가 일반적으로 프론트엔드에서 접근된다는 점을 고려할 때 올바른 수정입니다. 이는 생성된 공유 URL이 올바른 대상(프론트엔드 애플리케이션)을 가리키도록 보장합니다.

Comment on lines +209 to +218
const resolveVideoMime = (incomingFile) => {
if (ALLOWED_VIDEO_MIME.has(incomingFile.mimetype)) {
return incomingFile.mimetype;
}

const originalName = String(incomingFile.originalname || "").toLowerCase();
if (originalName.endsWith(".mp4")) return "video/mp4";
if (originalName.endsWith(".webm")) return "video/webm";
return null;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

resolveVideoMime 함수를 도입하여 file.mimetype이 신뢰할 수 없거나 누락된 경우 원본 파일 이름의 확장자(.mp4 또는 .webm)를 통해 MIME 유형을 추론하도록 한 것은 견고성을 크게 향상시킵니다. 이는 클라이언트에서 제공하는 MIME 유형이 항상 정확하지 않을 수 있는 시나리오에 대한 좋은 폴백 메커니즘입니다.

Comment on lines +221 to +225
if (!resolvedMime) {
throw new InvalidVideoChunkError({
contentType: file.mimetype,
fileName: file.originalname || null,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

MIME 유형을 확인할 수 없을 때 InvalidVideoChunkErrorfileName을 포함하도록 개선한 것은 디버깅에 매우 유용합니다. 어떤 파일이 문제의 원인인지 빠르게 식별하는 데 도움이 됩니다.

}

const ext = file.mimetype === "video/mp4" ? "mp4" : "webm";
const ext = resolvedMime === "video/mp4" ? "mp4" : "webm";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ext 변수가 이제 file.mimetype 대신 resolvedMime을 사용하도록 변경되었습니다. 이는 resolveVideoMime 함수를 통해 MIME 유형을 보다 안정적으로 결정하는 새로운 로직과 일치합니다.

objectKey,
buffer: file.buffer,
contentType: file.mimetype,
contentType: resolvedMime,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

uploadBufferToGCS 호출에서 contentTypefile.mimetype 대신 resolvedMime을 사용하도록 변경되었습니다. 이는 MIME 유형 감지 로직의 개선 사항을 활용하여 GCS에 업로드되는 파일의 Content-Type 메타데이터가 더 정확하도록 보장합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants