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
복사
-참고블로그
https://github.com/ffmpegwasm/ffmpeg.wasm/blob/main/apps/vue-vite-app/vite.config.ts
[vite.config.js ffmpegwasm]
https://techkblog.com/fixed-sharedarraybuffer-is-not-defined/
[에러 원인]
https://ko.vitejs.dev/config/dep-optimization-options.html
[관련 vite docs]
https://github.com/ffmpegwasm/ffmpeg.wasm/issues/263
3.
영상이 중간에 멈추는 이유
이런식으로 하이라이트 자르는 시간이 전에 잘랏던 시간보다 전이거나, 시간이 겹치게 된다면 영상이 멈추는 문제가 발생했다.
이것은 하이라이트를 등록할 때 하이라이트배열의 순서를 바꿔줘야 한다고 생각이들었다.
4.
413 (Payload Too Large) / 413 Request Entity Too Large
코스에러 + 파일이 너무 크대
nginx 에서 최대 크기를 늘려주자!!!