Skip to content

LostBeard/ffmpeg.wasm

 
 

Repository files navigation

ffmpeg.wasm

Fork of the awesome project ffmpeg.wasm

This fork is used by SpawnDev.BlazorJS.FFmpegWasm to bring ffmpeg.wasm into Blazor WASM apps.

Demo code

transcode.js

"use strict";

const useWorkerFSIfAvailable = true;
const useMultiThreadIfAvailable = true;
// uses custom build of ffmpeg.wasm v0.12.7 with the addition of pull request #562 to allow setting the worker.js path in the load config.
// https://github.com/LostBeard/ffmpeg.wasm/releases/download/v0.12.8-beta/LostBeard-ffmpeg.wasm.umd.v0.12.8-beta.zip
const baseURLFFMPEG = 'ffmpeg-wasm/ffmpeg';
const baseURLCore = 'ffmpeg-wasm/core';
const baseURLCoreMT = 'ffmpeg-wasm/core-mt';
var ffmpeg = null;
var loadBtn = null;
var transcodeBtn = null;
var logDiv = null;
var videoEl = null;
var localFileInput = null;
var remoteFileInput = null;

const toBlobURL = async (url, mimeType) => {
    var resp = await fetch(url);
    var body = await resp.blob();
    var blob = new Blob([body], { type: mimeType });
    return URL.createObjectURL(blob);
};

const fetchFile = async (url) => {
    var resp = typeof url === 'string' ? await fetch(url) : url;
    var buffer = await resp.arrayBuffer();
    return new Uint8Array(buffer);
};

const load = async () => {
    loadBtn.setAttribute('disabled', true);
    const ffmpegBlobURL = await toBlobURL(`${baseURLFFMPEG}/ffmpeg.js`, 'text/javascript');
    await import(ffmpegBlobURL);
    ffmpeg = new FFmpegWASM.FFmpeg();
    ffmpeg.on('log', ({ message }) => {
        logDiv.innerHTML = message;
        console.log(message);
    });
    // check if SharedArrayBuffer is supported via crossOriginIsolated global var
    // https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated
    if (useMultiThreadIfAvailable && window.crossOriginIsolated) {
        transcodeBtn.innerHTML = 'Transcode remote file (multi-threaded)';
        await ffmpeg.load({
            workerLoadURL: await toBlobURL(`${baseURLFFMPEG}/814.ffmpeg.js`, 'text/javascript'),
            coreURL: await toBlobURL(`${baseURLCoreMT}/ffmpeg-core.js`, 'text/javascript'),
            wasmURL: await toBlobURL(`${baseURLCoreMT}/ffmpeg-core.wasm`, 'application/wasm'),
            workerURL: await toBlobURL(`${baseURLCoreMT}/ffmpeg-core.worker.js`, 'application/javascript'),
        });
    } else {
        transcodeBtn.innerHTML = 'Transcode remote file (single-threaded)';
        await ffmpeg.load({
            workerLoadURL: await toBlobURL(`${baseURLFFMPEG}/814.ffmpeg.js`, 'text/javascript'),
            coreURL: await toBlobURL(`${baseURLCore}/ffmpeg-core.js`, 'text/javascript'),
            wasmURL: await toBlobURL(`${baseURLCore}/ffmpeg-core.wasm`, 'application/wasm'),
        });
    }
    console.log('ffmpeg.load success');
    transcodeBtn.removeAttribute('disabled');
    localFileInput.removeAttribute('disabled');
    remoteFileInput.removeAttribute('disabled');
}

const transcodeRemoteFile = async () => {
    transcodeBtn.setAttribute('disabled', true);
    localFileInput.setAttribute('disabled', true);
    await ffmpeg.writeFile('input.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm'));
    await ffmpeg.exec(['-i', 'input.webm', 'output.mp4']);
    const data = await ffmpeg.readFile('output.mp4');
    videoEl.src = URL.createObjectURL(new Blob([data], { type: 'video/mp4' }));
    transcodeBtn.removeAttribute('disabled');
    localFileInput.removeAttribute('disabled');
}

const transcodeLocalFileInput = async () => {
    let files = localFileInput.files;
    let file = files.length ? files[0] : null;
    if (!file) return;
    transcodeBtn.setAttribute('disabled', true);
    localFileInput.setAttribute('disabled', true);
    const data = await transcodeFile(file);
    videoEl.src = URL.createObjectURL(new Blob([data], { type: 'video/mp4' }));
    transcodeBtn.removeAttribute('disabled');
    localFileInput.removeAttribute('disabled');
};

const transcodeFile = async (file) => {
    console.log('file', file);
    const inputDir = '/input';
    const inputFile = `${inputDir}/${file.name}`;
    const outputFile = 'output.mp4';
    console.log('inputFile', inputFile);
    await ffmpeg.createDir(inputDir);
    const workerFSSupported = !!ffmpeg.mount;
    if (workerFSSupported) {
        await ffmpeg.mount('WORKERFS', { files: [file], }, inputDir);
    } else {
        await ffmpeg.writeFile(inputFile, new Uint8Array(await file.arrayBuffer()))
    }
    await ffmpeg.exec(['-i', inputFile, outputFile]);
    const data = await ffmpeg.readFile(outputFile);
    await ffmpeg.deleteFile(outputFile);
    if (workerFSSupported) {
        await ffmpeg.unmount(inputDir);
    } else {
        await ffmpeg.deleteFile(inputFile);
    }
    await ffmpeg.deleteDir(inputDir);
    return data;
};

addEventListener("load", (event) => {
    localFileInput = document.querySelector('#local-file');
    localFileInput.addEventListener('change', async () => await transcodeLocalFileInput());
    remoteFileInput = document.querySelector('#remote-file');
    loadBtn = document.querySelector('#load-button');
    loadBtn.addEventListener('click', async () => await load());
    loadBtn.removeAttribute('disabled');
    transcodeBtn = document.querySelector('#transcode-button');
    transcodeBtn.addEventListener('click', async () => await transcodeRemoteFile());
    logDiv = document.querySelector('#log-div');
    videoEl = document.querySelector('#video-result');
    logDiv.innerHTML = window.crossOriginIsolated ? 'Multithreading supported' : 'Multithreading not supported';
    console.log('window loaded');
});

index.html

<html>
<head>
    <script src="transcode.js"></script>
</head>
<body>
    <p>Test using <a href="https://github.com/LostBeard/ffmpeg.wasm">ffmpeg.wasm custom v0.12.7</a>. A fork of the
        awesome project at <a href="https://github.com/ffmpegwasm/ffmpeg.wasm">ffmpeg.wasm</a></p>
    <div style="padding: 5px;">
        <button disabled id="load-button">Load ffmpeg-core (~31 MB)</button><br />
        <video autoplay muted id="video-result" controls></video><br />
    </div>
    <div style="padding: 5px;">
        Local file input:<br />
        <input disabled type="file" id="local-file" accept=".mp4,.m4v,.webm,.avi,.mkv,.mov,.wmv" /><br />
    </div>
    <div style="padding: 5px;">
        Remote file input:<br />
        <input style="width: 100%;" disabled id="remote-file" type="text"
            value="https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm"><br />
        <button disabled id="transcode-button">Transcode remote file</button><br />
    </div>
    <p id="log-div"></p>
    <p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</body>
</html>

About

FFmpeg for browser, powered by WebAssembly

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C 85.7%
  • TypeScript 6.6%
  • JavaScript 2.5%
  • HTML 1.7%
  • Shell 1.5%
  • Dockerfile 0.7%
  • Other 1.3%