FFmpeg.wasm

http://soen.kr/lecture/library/FFmpeg/FFmpeg.htm [FFmpeg 강의 - 상당히 오래전것]
설치
yarn add @ffmpeg/ffmpeg @ffmpeg/util
JavaScript
복사
[옵션 항목에서 대한 주의 사항]
영상을 자를 때 -ss 옵션은 -i 옵션 뒤 쪽에 위치시켜 줘야 한다. 그렇지 않으면 -t 옵션을 인식하지 못했다.
t 옵션에 주어지는 시간은 -ss 옵션에서 시작한 시간이 시작점이 된다. 즉 -ss 는 영상의 시작 지점을 나타내고 -t 는 소요시간을 의미한다.
예를 들어 -ss 00:09:10 -t 00:50:00 이라고 했을 때 -ss 9분10초 이전의 영상은 버리고 여기서 부터 시작하여 -t 에 주어진 50분 간의 영상을 잘라서 사용하겠다는 의미이다.
const args = [ "-i", "test.mp4", "-ss", "00:00:05", "-to", "00:00:10", "-c:v", "copy", "-c:a", "copy", "output22.mp4", ]; await ffmpeg.exec([...args]) data3 = await ffmpeg.readFile("output22.mp4"); video3.value = URL.createObjectURL(new Blob([data3.buffer], {type: "video/mp4"}));
Plain Text
복사
하면서 생겼던 오류 & 해결방법
1.
자른 영상 초반의 1~3초 정도 멈추는 오류 발생
const args = [ "-ss", `${startTime.value}`, "-i", "test.mp4", "-to", `${endTime.value}`, "-vcodec", "copy", "-acodec", "copy", "output.ts", ];
JavaScript
복사
-ss, -i의 순서를 바꿔줘서 해결 위 처럼 하게되면 -to 를 인식 못하기 때문에 default 10초의 길이로 영상이 잘리게 됨.
mp4 to ts 변환 과정에서 문제가 있었던 것 같음!! mp4 to mp4는 문제가 없음!!
const args = [ "-i", "test.mp4", "-ss", `${startTime.value}`, "-t", `${endTime.value}`, "-c", "copy", "output.mp4", ];
JavaScript
복사
우선 mp4>mp4로 영상 자르기만 진행한다! (ts로 변환했던 이유는 concat을 하기 위함이었다.)
concat
mp4는 concat에서 지원되지 않는 파일이다. 즉 output.mp4 파일을 ts파일로 변환한 뒤 concat하고 output을 mp4로 변환하면 가능!
//위의 코드를 활용하여 자른 영상을 output1.mp4, output2.mp4로 가져온 뒤 //이를 copy하여 ts파일로 변환해준다. const args1 = ["-i", "output2.mp4", "-c", "copy", "output2.ts"]; const args2 = ["-i", "output1.mp4", "-c", "copy", "output1.ts"]; await ffmpeg.exec([...args1]); await ffmpeg.exec([...args2]); //그 후 변환한 파일을 concat한 뒤 //output을 mp4로 하면 //파일 손상이 없이 깔끔한 concat&변환이 가능하다!! const args = [ "-i", "concat:output1.ts|output2.ts", "-c", "copy", "output55.mp4", ]; await ffmpeg.exec([...args]); data4 = await ffmpeg.readFile("output55.mp4"); video4.value = URL.createObjectURL( new Blob([data4.buffer], { type: "video/mp4" }) );
JavaScript
복사
완성된 최종 실험 코드
<script setup> import { ref, onMounted, computed, watch } from "vue"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { fetchFile, toBlobURL } from "@ffmpeg/util"; const path = "/src/assets/file_1704701089680_3840x2160_"; const vi = ref(null); const currentTime = ref(0); onMounted(() => {}); const targetMP4File = "C:/Users/SSAFY/Downloads/sample.mp4"; //영상 파일 const baseURL = "https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm"; const videoURL = "https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi"; const localURL = "http://localhost:5173/src/assets/test.mp4"; //mp4 영상 파일 // const localURL = "http://localhost:5173/src/assets/Big_Buck_Bunny_180_10s.webm"; // webm 영상 파일 const ffmpeg = new FFmpeg(); const message = ref("Click Start to Transcode"); let video = ref(""); let data = ref(); let data2 = ref(); let video2 = ref(""); let data3 = ref(); let video3 = ref(""); let data4 = ref(); let video4 = ref(""); //avi to mp4 async function transcode() { message.value = "Loading ffmpeg-core.js"; await ffmpeg.load({ coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"), wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"), workerURL: await toBlobURL( `${baseURL}/ffmpeg-core.worker.js`, "text/javascript" ), }); console.log("hello"); message.value = "Start transcoding"; await ffmpeg.writeFile("test.avi", await fetchFile(videoURL)); await ffmpeg.exec(["-i", "test.avi", "test.mp4"]); message.value = "Complete transcoding"; data = await ffmpeg.readFile("test.mp4"); video.value = URL.createObjectURL( new Blob([data.buffer], { type: "video/mp4" }) ); } //mp4 혹은 webm 파일을 가져오기 const bringMP4 = async () => { await ffmpeg.load({ coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"), wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"), workerURL: await toBlobURL( `${baseURL}/ffmpeg-core.worker.js`, "text/javascript" ), }); console.log("hi-mp4"); await ffmpeg.writeFile("test.webm", await fetchFile(localURL)); message.value = "Complete transcoding"; data = await ffmpeg.readFile("test.webm"); video.value = URL.createObjectURL( new Blob([data.buffer], { type: "video/webm" }) ); }; //영상 시작, 러닝타임 정해줄때 사용 let startTime = ref("00:00:02"); //영상의 끝단위 let endTime = ref("00:00:08"); //만약 running Time 을 지정하고 싶다면 // "-to"가 아닌 "-t"로 변경하면 가능하다. //비디오 원하는 초에 자르기 const trim = async () => { const args = [ "-i", "test.mp4", "-ss", `${startTime.value}`, "-to", `${endTime.value}`, "-c", "copy", "output1.mp4", ]; await ffmpeg.writeFile("test.mp4", await fetchFile(localURL)); await ffmpeg.exec([...args]); data2 = await ffmpeg.readFile("output1.mp4"); if (video.value) { URL.revokeObjectURL(video.value); } video2.value = URL.createObjectURL( new Blob([data2.buffer], { type: "video/mp4" }) ); }; //비디오 여기서 시간정해서 자르기 const addVideos = async () => { const args = [ "-i", "test.mp4", "-ss", "00:00:05", "-to", "00:00:10", "-c", "copy", "output2.mp4", ]; console.log("hi"); await ffmpeg.exec([...args]); data3 = await ffmpeg.readFile("output2.mp4"); video3.value = URL.createObjectURL( new Blob([data3.buffer], { type: "video/mp4" }) ); }; //비디오 합치기 const combineVideos = async () => { const args1 = ["-i", "output2.mp4", "-c", "copy", "output2.ts"]; const args2 = ["-i", "output1.mp4", "-c", "copy", "output1.ts"]; await ffmpeg.exec([...args1]); await ffmpeg.exec([...args2]); const args = [ "-i", "concat:output1.ts|output2.ts", "-c", "copy", "output55.mp4", ]; await ffmpeg.exec([...args]); data4 = await ffmpeg.readFile("output55.mp4"); video4.value = URL.createObjectURL( new Blob([data4.buffer], { type: "video/mp4" }) ); }; </script> <template> <header><h1>동영상 편집 테스트</h1></header> <input type="file" accept="video/*" /> <br /> <video :src="video" controls style="width: 400px; height: 150px"></video> <br /> <button @click="transcode">Start</button> <button @click="bringMP4">MP4파일가져온다</button> <p>{{ message }}</p> <!-- 비디오 시간입력한 후 자르는 부분--> <video :src="video2" controls style="width: 400px; height: 150px"></video> <div> <input type="text" v-model="startTime" placeholder="시작시간 00:00:01 부터" /> <input type="text" v-model="endTime" placeholder="끝 시간 00:00:00" /> <button @click="trim">시간 입력 후 자르기</button> </div> <!-- 정해진 시간의 비디오 자르기 --> <button @click="addVideos">영상 자르기</button> <video :src="video3" controls style="width: 400px; height: 150px"></video> <button @click="combineVideos">본영상과 자른 영상합취귀</button> <video :src="video4" controls style="width: 400px; height: 150px"></video> <!-- <video controls width="400" ref="vi" @play="(e) => console.log(e.target.currentTime)" @timeupdate="(e) => (currentTime = e.target.currentTime)" > <source src="C:\Users\SSAFY\Downloads\sample.mp4" id="test" /> </video> <br /> <section class="imgs"> <div v-for="index in 13" :key="index" @click="() => (vi.currentTime = index)" :class="currentTime >= index && currentTime < index + 1 ? 'selectImg' : ''" > <div class="thumb" :style="{ 'background-image': `url(${path}${index}.jpg)`, }" ></div> <span>{{ index }}</span> </div> </section> </template>
JavaScript
복사
프로젝트에 적용하려고 하니 에러가 뜸 위의 코드를 가져와서 쓰다 보니 console.log를 찍어도 아무것도 발생하지 않아 try, catch로 error를 찍어보았따.
//ShortpingHighlight.vue //...생략 const ffmpeg = new FFmpeg() const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm' const videoURL = 'http://localhost:5173/src/assets/video/test/test.mp4' let videos = ref('') let datas = ref('') const bringMP4 = async () => { console.log('11') console.log(toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript')) try { await ffmpeg.load({ coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript') }) console.log('hi-mp4') await ffmpeg.writeFile('test.mp4', await fetchFile(videoURL)) console.log('22') datas.value = await ffmpeg.readFile('test.mp4') console.log('33') videos.value = URL.createObjectURL(new Blob([datas.value.buffer], { type: 'video/mp4' })) } catch (error) { console.error('Error in bringMP4:', error) } }
JavaScript
복사
1.
정말 아무런 것도 보이지 않았다.
그래서 구글링 하던 도중 vite 설정에 optimize에 추가를 해야 한단다. vite는 코드가 최적화되도록 해주는데, ffmpeg는 배제해야 한단다
//vite.config.js optimizeDeps: { exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'] },
JavaScript
복사
이를 추가했더니, try catch문에서 error가 확인되었다!!
2.
ReferenceError: SharedArrayBuffer is not defined - cross-origin문제였다. - 해결 방법 vite.config.js에 코드 추가해주자!
//vite.config.js server: { headers: { 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Opener-Policy': 'same-origin' } } //생략
JavaScript
복사
//vite.config.js server: { headers: { 'Cross-Origin-Embedder-Policy': 'credentialless', 'Cross-Origin-Opener-Policy': 'same-origin' } }
JSON
복사
3.
영상이 중간에 멈추는 이유
이런식으로 하이라이트 자르는 시간이 전에 잘랏던 시간보다 전이거나, 시간이 겹치게 된다면 영상이 멈추는 문제가 발생했다.
이것은 하이라이트를 등록할 때 하이라이트배열의 순서를 바꿔줘야 한다고 생각이들었다.
4.
413 (Payload Too Large) / 413 Request Entity Too Large 코스에러 + 파일이 너무 크대
nginx 에서 최대 크기를 늘려주자!!!