Add video split example

This commit is contained in:
Jerome Wu 2023-08-21 21:53:17 +08:00
parent a19ce28c01
commit 1b2f5f14f5

View File

@ -6,11 +6,12 @@ Learn the basics of using ffmpeg.wasm.
It is recommended to read [Overview](/docs/overview) first. It is recommended to read [Overview](/docs/overview) first.
::: :::
## Transcoding video ## Transcode avi to mp4 video
:::tip :::caution
`Load ffmpeg-core` might take a few minutes to complete as it downloads If you are a [vite](https://vitejs.dev/) user, use `esm` in **baseURL** instead of `umd`:
a ~31 MB ffmpeg-core.wasm.
~~https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd~~ => https://unpkg.com/@ffmpeg/core@0.12.2/dist/esm
::: :::
```jsx live ```jsx live
@ -27,28 +28,20 @@ function() {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
ffmpeg.on("log", ({ message }) => { ffmpeg.on("log", ({ message }) => {
messageRef.current.innerHTML = message; messageRef.current.innerHTML = message;
console.log(message);
}); });
// toBlobURL is used to bypass CORS issue, urls with the same // toBlobURL is used to bypass CORS issue, urls with the same
// domain can be used directly. // domain can be used directly.
await ffmpeg.load({ await ffmpeg.load({
coreURL: await toBlobURL( coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
`${baseURL}/ffmpeg-core.js`, wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
"text/javascript",
),
wasmURL: await toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
"application/wasm",
),
}); });
setLoaded(true); setLoaded(true);
} }
const transcode = async () => { const transcode = async () => {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile( await ffmpeg.writeFile("input.avi", await fetchFile('/video/video-15s.avi'));
"input.avi",
await fetchFile('/video/video-15s.avi')
);
await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']); await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4'); const data = await ffmpeg.readFile('output.mp4');
videoRef.current.src = videoRef.current.src =
@ -61,20 +54,21 @@ function() {
<video ref={videoRef} controls></video><br/> <video ref={videoRef} controls></video><br/>
<button onClick={transcode}>Transcode avi to mp4</button> <button onClick={transcode}>Transcode avi to mp4</button>
<p ref={messageRef}></p> <p ref={messageRef}></p>
<p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</> </>
) )
: ( : (
<button onClick={load}>Load ffmpeg-core</button> <button onClick={load}>Load ffmpeg-core (~31 MB)</button>
) )
); );
} }
``` ```
## Transcoding video (multi-thread) ## Transcode avi to mp4 video (multi-thread)
:::tip :::caution
`Load ffmpeg-core` might take a few minutes to complete as it downloads As SharedArrayBuffer is required for multithread version, make sure
a ~31 MB ffmpeg-core.wasm. you have have fulfilled [Security Requirements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements).
::: :::
```jsx live ```jsx live
@ -91,32 +85,21 @@ function() {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
ffmpeg.on("log", ({ message }) => { ffmpeg.on("log", ({ message }) => {
messageRef.current.innerHTML = message; messageRef.current.innerHTML = message;
console.log(message);
}); });
// toBlobURL is used to bypass CORS issue, urls with the same // toBlobURL is used to bypass CORS issue, urls with the same
// domain can be used directly. // domain can be used directly.
await ffmpeg.load({ await ffmpeg.load({
coreURL: await toBlobURL( coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
`${baseURL}/ffmpeg-core.js`, wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
"text/javascript", workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, "text/javascript"),
),
wasmURL: await toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
"application/wasm",
),
workerURL: await toBlobURL(
`${baseURL}/ffmpeg-core.worker.js`,
"text/javascript",
),
}); });
setLoaded(true); setLoaded(true);
} }
const transcode = async () => { const transcode = async () => {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile( await ffmpeg.writeFile("input.avi", await fetchFile('/video/video-15s.avi'));
"input.avi",
await fetchFile('/video/video-15s.avi')
);
await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']); await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4'); const data = await ffmpeg.readFile('output.mp4');
videoRef.current.src = videoRef.current.src =
@ -129,20 +112,17 @@ function() {
<video ref={videoRef} controls></video><br/> <video ref={videoRef} controls></video><br/>
<button onClick={transcode}>Transcode avi to mp4</button> <button onClick={transcode}>Transcode avi to mp4</button>
<p ref={messageRef}></p> <p ref={messageRef}></p>
<p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</> </>
) )
: ( : (
<button onClick={load}>Load ffmpeg-core</button> <button onClick={load}>Load ffmpeg-core (~31 MB)</button>
) )
); );
} }
``` ```
:::caution
As SharedArrayBuffer is required for multithread version, make sure
you have have fulfilled [Security Requirements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements).
:::
## Transcoding video with timeout ## Transcode video with timeout
```jsx live ```jsx live
// import { FFmpeg } from '@ffmpeg/ffmpeg'; // import { FFmpeg } from '@ffmpeg/ffmpeg';
@ -158,28 +138,20 @@ function() {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
ffmpeg.on("log", ({ message }) => { ffmpeg.on("log", ({ message }) => {
messageRef.current.innerHTML = message; messageRef.current.innerHTML = message;
console.log(message);
}); });
// toBlobURL is used to bypass CORS issue, urls with the same // toBlobURL is used to bypass CORS issue, urls with the same
// domain can be used directly. // domain can be used directly.
await ffmpeg.load({ await ffmpeg.load({
coreURL: await toBlobURL( coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
`${baseURL}/ffmpeg-core.js`, wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
"text/javascript",
),
wasmURL: await toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
"application/wasm",
),
}); });
setLoaded(true); setLoaded(true);
} }
const transcode = async () => { const transcode = async () => {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile( await ffmpeg.writeFile("input.avi", await fetchFile('/video/video-15s.avi'));
"input.avi",
await fetchFile('/video/video-15s.avi')
);
// The exec should stop after 1 second. // The exec should stop after 1 second.
await ffmpeg.exec(['-i', 'input.avi', 'output.mp4'], 1000); await ffmpeg.exec(['-i', 'input.avi', 'output.mp4'], 1000);
const data = await ffmpeg.readFile('output.mp4'); const data = await ffmpeg.readFile('output.mp4');
@ -193,16 +165,22 @@ function() {
<video ref={videoRef} controls></video><br/> <video ref={videoRef} controls></video><br/>
<button onClick={transcode}>Transcode avi to mp4</button> <button onClick={transcode}>Transcode avi to mp4</button>
<p ref={messageRef}></p> <p ref={messageRef}></p>
<p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</> </>
) )
: ( : (
<button onClick={load}>Load ffmpeg-core</button> <button onClick={load}>Load ffmpeg-core (~31 MB)</button>
) )
); );
} }
``` ```
## Transcoding video with progress ## Transcode video with progress (experimental)
:::danger
`progress` is an experimental feature and might not work for many cases
(ex. concat video files, convert image files, ...). Please use with caution.
:::
```jsx live ```jsx live
// import { FFmpeg } from '@ffmpeg/ffmpeg'; // import { FFmpeg } from '@ffmpeg/ffmpeg';
@ -217,30 +195,21 @@ function() {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd' const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd'
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
// Listen to progress event instead of log. // Listen to progress event instead of log.
ffmpeg.on("progress", ({ progress }) => { ffmpeg.on("progress", ({ progress, time }) => {
messageRef.current.innerHTML = `${progress * 100} %`; messageRef.current.innerHTML = `${progress * 100} % (transcoded time: ${time / 1000000} s)`;
}); });
// toBlobURL is used to bypass CORS issue, urls with the same // toBlobURL is used to bypass CORS issue, urls with the same
// domain can be used directly. // domain can be used directly.
await ffmpeg.load({ await ffmpeg.load({
coreURL: await toBlobURL( coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
`${baseURL}/ffmpeg-core.js`, wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
"text/javascript",
),
wasmURL: await toBlobURL(
`${baseURL}/ffmpeg-core.wasm`,
"application/wasm",
),
}); });
setLoaded(true); setLoaded(true);
} }
const transcode = async () => { const transcode = async () => {
const ffmpeg = ffmpegRef.current; const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile( await ffmpeg.writeFile("input.avi", await fetchFile('/video/video-15s.avi'));
"input.avi",
await fetchFile('/video/video-15s.avi')
);
await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']); await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4'); const data = await ffmpeg.readFile('output.mp4');
videoRef.current.src = videoRef.current.src =
@ -256,13 +225,73 @@ function() {
</> </>
) )
: ( : (
<button onClick={load}>Load ffmpeg-core</button> <button onClick={load}>Load ffmpeg-core (~31 MB)</button>
) )
); );
} }
``` ```
:::caution ## Split video into segments of equal duration
`progress` is an experimental feature and might not work for many cases
(ex. concat video files, convert image files, ...). Please use with caution. ```jsx live
::: // import { FFmpeg } from '@ffmpeg/ffmpeg';
// import { fetchFile } from '@ffmpeg/util';
function() {
const [loaded, setLoaded] = useState(false);
const ffmpegRef = useRef(new FFmpeg());
const videoRef = useRef(null);
const messageRef = useRef(null);
const load = async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.2/dist/umd'
const ffmpeg = ffmpegRef.current;
ffmpeg.on("log", ({ message }) => {
messageRef.current.innerHTML = message;
console.log(message);
});
// toBlobURL is used to bypass CORS issue, urls with the same
// domain can be used directly.
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
});
setLoaded(true);
}
const transcode = async () => {
const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile("input.avi", await fetchFile('/video/video-15s.avi'));
await ffmpeg.exec([
'-i',
'input.avi',
'-f',
'segment',
'-segment_time',
'3',
'-reset_timestamps',
'1',
'-map',
'0:0',
'-an',
'output_%d.mp4'
]);
const data = await ffmpeg.readFile('output_2.mp4');
videoRef.current.src =
URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'}));
}
return (loaded
? (
<>
<video ref={videoRef} controls></video><br/>
<button onClick={transcode}>Split video to segments of 3 sec. and plays 3rd segment</button>
<p ref={messageRef}></p>
<p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</>
)
: (
<button onClick={load}>Load ffmpeg-core (~31 MB)</button>
)
);
}
```