โšกToolZip
๋ธ”๋กœ๊ทธ๐Ÿ› ๏ธ ๋„๊ตฌ ์‚ฌ์šฉํ•˜๊ธฐ
โ† ๋ธ”๋กœ๊ทธ ๋ชฉ๋ก
๐ŸŽฌ video2026๋…„ 9์›” 24์ผ

๋™์˜์ƒ ์••์ถ•์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ตฌํ˜„ํ•œ ์ด์•ผ๊ธฐ โ€” FFmpeg.wasm ์ ์šฉ๊ธฐ

#FFmpeg๋ธŒ๋ผ์šฐ์ €#FFmpeg.wasm๊ฐœ๋ฐœ๊ธฐ#๋™์˜์ƒ์••์ถ•๋ธŒ๋ผ์šฐ์ €#WebAssembly๊ฐœ๋ฐœ#๋™์˜์ƒ์••์ถ•๋„๊ตฌ๊ฐœ๋ฐœ#FFmpeg.wasmํŠœํ† ๋ฆฌ์–ผ

๋™์˜์ƒ ์••์ถ•์€ ์„œ๋ฒ„์—์„œ ํ•˜๋Š” ๊ฒƒ์ด๋ผ๋Š” ๊ณ ์ •๊ด€๋…์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ๋™์˜์ƒ์„ ์••์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋์Šต๋‹ˆ๋‹ค.


FFmpeg.wasm์„ ์•Œ๊ฒŒ ๋œ ๊ณ„๊ธฐ

์ด๋ฏธ์ง€ ์••์ถ• ๋„๊ตฌ๋ฅผ ๋จผ์ € ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. Canvas API๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋น„๊ต์  ๊ฐ„๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

๋™์˜์ƒ์€ ๋‹ฌ๋ž์Šต๋‹ˆ๋‹ค. ๋™์˜์ƒ์€ ์ˆ˜์‹ญ~์ˆ˜๋ฐฑ ๊ฐœ์˜ ์ด๋ฏธ์ง€ ํ”„๋ ˆ์ž„์— ์˜ค๋””์˜ค๊นŒ์ง€ ํ•ฉ์ณ์ง„ ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. Canvas API๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ์—๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์กฐ์‚ฌํ•˜๋‹ค๊ฐ€ FFmpeg.wasm์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. FFmpeg๋ฅผ WebAssembly๋กœ ์ปดํŒŒ์ผํ•ด์„œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.


์„ค์น˜์™€ ์ฒซ ๋ฒˆ์งธ ๋ฒฝ

์„ค์น˜๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

npm install @ffmpeg/ffmpeg @ffmpeg/util

๊ทธ๋Ÿฐ๋ฐ ์‹คํ–‰ํ•˜๋ฉด ๋ฐ”๋กœ ์—๋Ÿฌ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค.

SharedArrayBuffer is not defined

FFmpeg.wasm์€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์„ ์œ„ํ•ด SharedArrayBuffer๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด API๋Š” ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ํŠน์ • HTTP ํ—ค๋”๊ฐ€ ์žˆ์–ด์•ผ๋งŒ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Next.js ์„ค์ • ํŒŒ์ผ์— ์ด ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

// next.config.js
headers: [
  {
    source: "/(.*)",
    headers: [
      { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
      { key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
    ],
  },
]

๋กœ์ปฌ์—์„œ๋Š” ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ Vercel์— ๋ฐฐํฌํ•˜๋ฉด ๋˜ ์•ˆ ๋ฉ๋‹ˆ๋‹ค. Vercel์˜ ๊ฒฝ์šฐ vercel.json์—๋„ ๋™์ผํ•œ ํ—ค๋” ์„ค์ •์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.


์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„ ๋ฌธ์ œ

FFmpeg.wasm์˜ ์ฝ”์–ด ํŒŒ์ผ์€ ์•ฝ 30MB์ž…๋‹ˆ๋‹ค. ์ฒ˜์Œ ์‚ฌ์šฉํ•  ๋•Œ ์ด ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ๋™์˜์ƒ ์••์ถ• ๋ฒ„ํŠผ์„ ์ฒ˜์Œ ๋ˆ„๋ฅด๋ฉด 30์ดˆ ๊ฐ€๊นŒ์ด ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑด UX ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ…์€ ๋‘ ๊ฐ€์ง€์˜€์Šต๋‹ˆ๋‹ค.

๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ: ์ง„ํ–‰ ์ƒํ™ฉ์„ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ”์™€ "FFmpeg ๋กœ๋”ฉ ์ค‘ (28MB)..." ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.

์บ์‹ฑ ํ™œ์šฉ: ํ•œ ๋ฒˆ ๋‹ค์šด๋กœ๋“œํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ฐฉ๋ฌธ๋ถ€ํ„ฐ๋Š” ์ฆ‰์‹œ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.


๊ฐ€์ƒ ํŒŒ์ผ์‹œ์Šคํ…œ ์ดํ•ดํ•˜๊ธฐ

FFmpeg๋Š” ํŒŒ์ผ์„ ์ฝ๊ณ  ์”๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—๋Š” ํŒŒ์ผ์‹œ์Šคํ…œ์ด ์—†์Šต๋‹ˆ๋‹ค. FFmpeg.wasm์€ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์— ๊ฐ€์ƒ ํŒŒ์ผ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

// ํŒŒ์ผ์„ ๊ฐ€์ƒ ํŒŒ์ผ์‹œ์Šคํ…œ์— ์“ฐ๊ธฐ
await ffmpeg.writeFile('input.mp4', await fetchFile(file));

// FFmpeg ๋ช…๋ น ์‹คํ–‰
await ffmpeg.exec([
  '-i', 'input.mp4',
  '-crf', '28',      // ์••์ถ• ํ’ˆ์งˆ (0=๋ฌด์†์‹ค, 51=์ตœ๋Œ€์••์ถ•)
  '-preset', 'fast', // ์ธ์ฝ”๋”ฉ ์†๋„
  'output.mp4'
]);

// ๊ฒฐ๊ณผ ํŒŒ์ผ ์ฝ๊ธฐ
const data = await ffmpeg.readFile('output.mp4');

// ๋‹ค์šด๋กœ๋“œ ๋งํฌ ์ƒ์„ฑ
const blob = new Blob([data.buffer], { type: 'video/mp4' });
const url = URL.createObjectURL(blob);

์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ๋ฌธ์ œ

100MB ๋™์˜์ƒ์„ ์••์ถ•ํ•˜๋Š” ๋ฐ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆด๊นŒ์š”?

์„œ๋ฒ„์—์„œ FFmpeg๋ฅผ ๋Œ๋ฆฌ๋ฉด 12๋ถ„ ์ •๋„์ž…๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š”? ๊ธฐ๊ธฐ๋งˆ๋‹ค ๋‹ค๋ฅด์ง€๋งŒ 515๋ถ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„๋Š” ๊ณ ์„ฑ๋Šฅ CPU๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๋Š” ์‚ฌ์šฉ์ž ๊ธฐ๊ธฐ์˜ CPU๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ตฌํ˜• ๋…ธํŠธ๋ถ์ด๋ผ๋ฉด ๋” ์˜ค๋ž˜ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค.

์ด ํ•œ๊ณ„๋ฅผ ์†”์งํ•˜๊ฒŒ UI์— ํ‘œ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. "ํŒŒ์ผ ํฌ๊ธฐ์— ๋”ฐ๋ผ ์ˆ˜ ๋ถ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํƒญ์„ ๋‹ซ์ง€ ๋งˆ์„ธ์š”."


์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œํ•˜๊ธฐ

FFmpeg.wasm์€ ์ฒ˜๋ฆฌ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์•Œ ์ˆ˜ ์žˆ๋Š” ์ฝœ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ffmpeg.on('progress', ({ progress }) => {
  setProgress(Math.round(progress * 100));
});

0์—์„œ 100๊นŒ์ง€ ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ”๊ฐ€ ์ฑ„์›Œ์ง€๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ฃผ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ ํ›จ์”ฌ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.


๊ฒฐ๊ณผ

๋™์˜์ƒ ์••์ถ•, ํฌ๋งท ๋ณ€ํ™˜(MP4ยทMOVยทAVIยทWebM), GIF ๋ณ€ํ™˜, ์˜ค๋””์˜ค ์ถ”์ถœ๊นŒ์ง€ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋‘ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฒ˜๋ฆฌ๋˜๊ณ  ํŒŒ์ผ์ด ์„œ๋ฒ„๋กœ ์ „์†ก๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ฒ˜์Œ FFmpeg.wasm์„ ์ ‘ํ–ˆ์„ ๋•Œ "์ด๊ฒŒ ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ ?" ํ–ˆ๋Š”๋ฐ, ์‹ค์ œ๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒƒ์„ ๋ณด๋ฉด ์•„์ง๋„ ์‹ ๊ธฐํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ› ๏ธ
์ง€๊ธˆ ๋ฐ”๋กœ ๋„๊ตฌ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ
์„ค์น˜ ์—†์ด ยท ๋ฌด๋ฃŒ ยท ์„œ๋ฒ„ ์ „์†ก ์—†์Œ
๋„๊ตฌ ์—ด๊ธฐ โ†’
๋™์˜์ƒ ์••์ถ•์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ตฌํ˜„ํ•œ ์ด์•ผ๊ธฐ โ€” FFmpeg.wasm ์ ์šฉ๊ธฐ โ€” ToolZip | ToolZip