Unable to use ffmpeg.wasm
philhk opened this issue · 3 comments
Hello. I'm trying to use ffmpeg.wasm in my userscript but I'm getting the following error
yarn run v1.22.22
$ tsc && vite build -w
vite v5.2.12 building for production...
watching for file changes...
build started...
✓ 6 modules transformed.
x Build failed in 10ms
[monkey:finalBundle] [vite:worker-import-meta-url] Could not resolve entry module "public/worker-3nePXDJu.js".
file: ./main-DV1a4Cq8.js
file: ./main-DV1a4Cq8.js
to reproduce the issue create a project with yarn create monkey
, add these packages (@ffmpeg/ffmpeg @ffmpeg/util
) and use this code
import { FFmpeg } from '@ffmpeg/ffmpeg';
const ffmpeg = new FFmpeg();
ffmpeg.load();
I know that ffmpeg.wasm uses a worker script but I don't know how to handle it.
// main.tsx
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';
import { useRef, useState } from 'react';
import ReactDOM from 'react-dom/client';
const ffg = new FFmpeg();
const App = () => {
const [loaded, setLoaded] = useState(false);
const [loading, setLoading] = useState(false);
const ffmpegRef = useRef(ffg);
const videoRef = useRef<any>();
const messageRef = useRef<any>();
const load = async () => {
setLoading(true);
const baseURL =
'https://fastly.jsdelivr.net/npm/@ffmpeg/core@0.12.6/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);
setLoading(false);
};
const transcode = async () => {
const ffmpeg = ffmpegRef.current;
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');
if (typeof data === 'object') {
videoRef.current!.src = URL.createObjectURL(
new Blob([data.buffer], { type: 'video/mp4' })
);
}
};
return loaded ? (
<>
<video ref={videoRef} controls></video>
<br />
<button onClick={transcode}>Transcode webm to mp4</button>
<p ref={messageRef}></p>
<p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</>
) : loading ? (
'Loading ffmpeg-core...'
) : (
<button onClick={load}>Load ffmpeg-core (~31 MB)</button>
);
};
ReactDOM.createRoot(
(() => {
const app = document.createElement('div');
document.body.append(app);
return app;
})()
).render(<App />);
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import monkey, { cdn } from 'vite-plugin-monkey';
export default defineConfig({
plugins: [
react(),
{
name: 'replace-url',
apply: 'build',
transform(code, id) {
if (id.includes('node_modules/@ffmpeg/ffmpeg/dist/esm/classes.js')) {
// this will prevent vite create chunk for worker.js
const header = `import MyWorker from './worker.js?worker&inline';\n`;
return (
header +
code.replace(
`new Worker(new URL('./worker.js',`,
`new MyWorker();((`
)
);
}
},
},
monkey({
entry: 'src/main.tsx',
userscript: {
icon: 'https://vitejs.dev/logo.svg',
namespace: 'npm/vite-plugin-monkey',
match: ['https://www.google.com/', 'https://songe.li/'],
},
build: {
externalGlobals: {
react: cdn.jsdelivr('React', 'umd/react.production.min.js'),
'react-dom': cdn.jsdelivr(
'ReactDOM',
'umd/react-dom.production.min.js'
),
},
},
}),
],
});
In Vite's server mode, Vite does not bundle inline worker code, which leads to cross-origin issues. Therefore, the code only works in build mode.
Is there any other question?
Hey! Thanks a bunch for the detailed explanation. I figured out my initial issue was caused because import.meta.url
couldn't be resolved. But you fixed that issue already it seems. But I've decided to focus on building a Chrome extension instead. Thanks for your help!