ffmpegwasm / ffmpegwasm/ffmpeg.wasm

4.80MB SIZE WASM!!!

Open
#866 2 comments 12 reactions 0 assignees View on GitHub
Dominant language
C
Stars
17.8k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

This is not an issue this is just a information , people who are looking for smaller build wasm file here is the solution.

features -
Stich png images into video - tested with html canvas
Encode , Decode mp4 files
Audio support

here is docker file instructions at the bottm, please forgive me for my english and typos i am high on coffee.
### DOCKER FILE
```Dockerfile
# syntax=docker/dockerfile-upstream:master-labs

# Base emsdk image with environment variables.
FROM emscripten/emsdk:3.1.40 AS emsdk-base
ARG EXTRA_CFLAGS
ARG EXTRA_LDFLAGS
ARG FFMPEG_ST
ARG FFMPEG_MT
ENV INSTALL_DIR=/opt
# We cannot upgrade to n6.0 as ffmpeg bin only supports multithread at the moment.
ENV FFMPEG_VERSION=n5.1.4
ENV CFLAGS="-I$INSTALL_DIR/include $CFLAGS $EXTRA_CFLAGS"
ENV CXXFLAGS="$CFLAGS"
ENV LDFLAGS="-L$INSTALL_DIR/lib $LDFLAGS $CFLAGS $EXTRA_LDFLAGS"
ENV EM_PKG_CONFIG_PATH=$EM_PKG_CONFIG_PATH:$INSTALL_DIR/lib/pkgconfig:/emsdk/upstream/emscripten/system/lib/pkgconfig
ENV EM_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
ENV PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$EM_PKG_CONFIG_PATH
ENV FFMPEG_ST=$FFMPEG_ST
ENV FFMPEG_MT=$FFMPEG_MT
RUN apt-get update && \
apt-get install -y pkg-config autoconf automake libtool ragel

# Build x264
FROM emsdk-base AS x264-builder
ENV X264_BRANCH=4-cores
ADD https://github.com/ffmpegwasm/x264.git#$X264_BRANCH /src
COPY build/x264.sh /src/build.sh
RUN bash -x /src/build.sh

# Build x265
FROM emsdk-base AS x265-builder
ENV X265_BRANCH=3.4
ADD https://github.com/ffmpegwasm/x265.git#$X265_BRANCH /src
COPY build/x265.sh /src/build.sh
RUN bash -x /src/build.sh

# Build libvpx
FROM emsdk-base AS libvpx-builder
ENV LIBVPX_BRANCH=v1.13.1
ADD https://github.com/ffmpegwasm/libvpx.git#$LIBVPX_BRANCH /src
COPY build/libvpx.sh /src/build.sh
RUN bash -x /src/build.sh

# Build lame
FROM emsdk-base AS lame-builder
ENV LAME_BRANCH=master
ADD https://github.com/ffmpegwasm/lame.git#$LAME_BRANCH /src
COPY build/lame.sh /src/build.sh
RUN bash -x /src/build.sh

# Build ogg
FROM emsdk-base AS ogg-builder
ENV OGG_BRANCH=v1.3.4
ADD https://github.com/ffmpegwasm/Ogg.git#$OGG_BRANCH /src
COPY build/ogg.sh /src/build.sh
RUN bash -x /src/build.sh

# Build theora
FROM emsdk-base AS theora-builder
COPY --from=ogg-builder $INSTALL_DIR $INSTALL_DIR
ENV THEORA_BRANCH=v1.1.1
ADD https://github.com/ffmpegwasm/theora.git#$THEORA_BRANCH /src
COPY build/theora.sh /src/build.sh
RUN bash -x /src/build.sh

# Build opus
FROM emsdk-base AS opus-builder
ENV OPUS_BRANCH=v1.3.1
ADD https://github.com/ffmpegwasm/opus.git#$OPUS_BRANCH /src
COPY build/opus.sh /src/build.sh
RUN bash -x /src/build.sh

# Build vorbis
FROM emsdk-base AS vorbis-builder
COPY --from=ogg-builder $INSTALL_DIR $INSTALL_DIR
ENV VORBIS_BRANCH=v1.3.3
ADD https://github.com/ffmpegwasm/vorbis.git#$VORBIS_BRANCH /src
COPY build/vorbis.sh /src/build.sh
RUN bash -x /src/build.sh

# Build zlib
FROM emsdk-base AS zlib-builder
ENV ZLIB_BRANCH=v1.2.11
ADD https://github.com/ffmpegwasm/zlib.git#$ZLIB_BRANCH /src
COPY build/zlib.sh /src/build.sh
RUN bash -x /src/build.sh

# Build libwebp
FROM emsdk-base AS libwebp-builder
COPY --from=zlib-builder $INSTALL_DIR $INSTALL_DIR
ENV LIBWEBP_BRANCH=v1.3.2
ADD https://github.com/ffmpegwasm/libwebp.git#$LIBWEBP_BRANCH /src
COPY build/libwebp.sh /src/build.sh
RUN bash -x /src/build.sh

# Build freetype2
FROM emsdk-base AS freetype2-builder
ENV FREETYPE2_BRANCH=VER-2-10-4
ADD https://github.com/ffmpegwasm/freetype2.git#$FREETYPE2_BRANCH /src
COPY build/freetype2.sh /src/build.sh
RUN bash -x /src/build.sh

# Build fribidi
FROM emsdk-base AS fribidi-builder
ENV FRIBIDI_BRANCH=v1.0.9
ADD https://github.com/fribidi/fribidi.git#$FRIBIDI_BRANCH /src
COPY build/fribidi.sh /src/build.sh
RUN bash -x /src/build.sh

# Build harfbuzz
FROM emsdk-base AS harfbuzz-builder
ENV HARFBUZZ_BRANCH=5.2.0
ADD https://github.com/harfbuzz/harfbuzz.git#$HARFBUZZ_BRANCH /src
COPY build/harfbuzz.sh /src/build.sh
RUN bash -x /src/build.sh

# Build libass
FROM emsdk-base AS libass-builder
COPY --from=freetype2-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=fribidi-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=harfbuzz-builder $INSTALL_DIR $INSTALL_DIR
ENV LIBASS_BRANCH=0.15.0
ADD https://github.com/libass/libass.git#$LIBASS_BRANCH /src
COPY build/libass.sh /src/build.sh
RUN bash -x /src/build.sh

# Build zimg
FROM emsdk-base AS zimg-builder
ENV ZIMG_BRANCH=release-3.0.5
RUN apt-get update && apt-get install -y git
RUN git clone --recursive -b $ZIMG_BRANCH https://github.com/sekrit-twc/zimg.git /src
COPY build/zimg.sh /src/build.sh
RUN bash -x /src/build.sh

# Base ffmpeg image with dependencies and source code populated.
FROM emsdk-base AS ffmpeg-base
RUN embuilder build sdl2 sdl2-mt
ADD https://github.com/FFmpeg/FFmpeg.git#$FFMPEG_VERSION /src
COPY --from=x264-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=x265-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=libvpx-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=lame-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=opus-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=theora-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=vorbis-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=libwebp-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=libass-builder $INSTALL_DIR $INSTALL_DIR
COPY --from=zimg-builder $INSTALL_DIR $INSTALL_DIR

# Build ffmpeg
FROM ffmpeg-base AS ffmpeg-builder
COPY build/ffmpeg.sh /src/build.sh
RUN bash -x /src/build.sh \
--enable-gpl \
--disable-pthreads \
--disable-w32threads \
--disable-os2threads \
--disable-everything \
--enable-libx264 \
--enable-zlib \
--enable-encoder=libx264 \
--enable-encoder=aac \
--enable-decoder=png \
--enable-decoder=h264 \
--enable-decoder=aac \
--enable-demuxer=image2 \
--enable-demuxer=mov \
--enable-muxer=mp4 \
--enable-parser=aac \
--enable-parser=h264 \
--enable-protocol=file \
--enable-filter=null \
--enable-filter=format \
--enable-filter=scale
# Build ffmpeg.wasm
FROM ffmpeg-builder AS ffmpeg-wasm-builder
COPY src/bind /src/src/bind
COPY src/fftools /src/src/fftools
COPY build/ffmpeg-wasm.sh build.sh
# libraries to link
ENV FFMPEG_LIBS \
-lx264 \
-lz
RUN mkdir -p /src/dist/umd && bash -x /src/build.sh \
${FFMPEG_LIBS} \
-o dist/umd/ffmpeg-core.js \
-s INITIAL_MEMORY=33554432 # 32MB initial memory
RUN mkdir -p /src/dist/esm && bash -x /src/build.sh \
${FFMPEG_LIBS} \
-sEXPORT_ES6 \
-o dist/esm/ffmpeg-core.js \
-s INITIAL_MEMORY=33554432 # 32MB initial memory

# Export ffmpeg-core.wasm to dist/, use `docker buildx build -o . .` to get assets
FROM scratch AS exportor
COPY --from=ffmpeg-wasm-builder /src/dist /dist

```

Quick React Example :-
```tsx
import React, { useRef, useState } from "react";
import { FFmpeg } from "@ffmpeg/ffmpeg";
import { fetchFile } from "@ffmpeg/util";

export default function App() {
const [loaded, setLoaded] = useState(false);
const [isError, setIsError] = useState(false);
const ffmpegRef = useRef(new FFmpeg());
const videoRef = useRef(null);
const messageRef = useRef(null);
const [logs, setLogs] = useState([]);

const load = async () => {
const enableLarge = false;
const baseURL = `http://localhost:5173/ffmpeg${enableLarge ? '-large' : ''}/esm`;
const ffmpeg = ffmpegRef.current;

ffmpeg.on("log", ({ message }) => {
setLogs((prevLogs) => [...prevLogs, message]);
if (messageRef.current) {
messageRef.current.scrollTop = messageRef.current.scrollHeight;
}
});

try {
await ffmpeg.load({
coreURL: `${baseURL}/ffmpeg-core.js`,
wasmURL: `${baseURL}/ffmpeg-core.wasm`,
});
setLoaded(true);
} catch (e) {
setIsError(true);
alert("Failed to load FFmpeg. Please check the console for more details.");
}
};

const transcode = async () => {
const ffmpeg = ffmpegRef.current;

try {
console.log('Transcoding...');
await ffmpeg.writeFile("input.mp4", await fetchFile("/videos/movie.mp4"));
await ffmpeg.exec([
"-i", "input.mp4",
"-r", "30", // Change frame rate to 30 fps
"-c:v", "libx264", // Encode video with libx264
"-c:a", "copy", // Copy audio stream without changing it
"output.mp4"
]);

const data = await ffmpeg.readFile("output.mp4");
if (videoRef.current) {
videoRef.current.src = URL.createObjectURL(
new Blob([data], { type: "video/mp4" })
);
}
} catch (error) {
setIsError(true);
alert("Transcoding failed! Please check the logs.");
}
};

const createCanvasAnimation = async () => {
console.log('Creating canvass animation')
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) return;

const frames: Blob[] = [];
canvas.width = 250;
canvas.height = 250;

// Create a simple animation (e.g., moving circle)
const totalFrames = 30; // 3 seconds at 30 FPS
for (let i = 0; i < totalFrames; i++) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(160 + Math.sin(i * (Math.PI / 45)) * 50, 120, 50, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();

// Capture frame
await new Promise((resolve) => {
canvas.toBlob((blob) => {
if (blob) frames.push(blob);
console.log('capture...');
resolve();
}, "image/png");
});

// Simulate a frame delay for animation
await new Promise((resolve) => setTimeout(resolve, 33)); // ~30 FPS
}

// Send frames to FFmpeg
const ffmpeg = ffmpegRef.current;
for (let i = 0; i < frames.length; i++) {
const file = await fetchFile(frames[i]);
await ffmpeg.writeFile(`frame${i}.png`, file);
}

// Create MP4 video from frames
await ffmpeg.exec([
"-framerate",
"30", // frame rate
"-i",
"frame%d.png", // input frames
"-c:v",
"libx264", // video codec
"-r",
"30", // output frame rate
"-pix_fmt",
"yuv420p", // pixel format
"output.mp4" // output file
]);
const data = await ffmpeg.readFile("output.mp4");
if (videoRef.current) {
videoRef.current.src = URL.createObjectURL(
new Blob([data], { type: "video/mp4" })
);
}
};

const clearLogs = () => {
setLogs([]);
};

return (


{loaded ? (
<>



Transcode
{
const ffmpeg = ffmpegRef.current;
await ffmpeg.exec(["-version"]);
}}>Version
Canvas
setIsError(false)}>Clear Error
Clear Logs


Logs:


              {logs.map((log, index) => (

{log}

))}

{isError && (


An error occurred! Check logs for details.



)}

) : (
Load ffmpeg-core
)}

);
}

```

steps to run docker file

1 - Docker required
2 - WSL or linux system requried
Node - make sure you install Docker in wsl ubuntu not windows host otherwise you will face a lot of issues
3 - inside wsl first create image using this command - ```docker build -t ffmpegwasm .```
4 - Once image is created run ```docker buildx build -o . .``` this command will output wasm and core file in dist folder ```ffmpeg.wasm/dist``` this is fast command good for testing although things won't work properly so you will have to use dev or prd commands which is time consuming as well.
5 - to make a dev or prd build use ```make dev``` for dev and for production build use ```make prd``` this will take some time for me first build took 30 minutes after that with cache and all it reduced to 2-3 minutes
6 - with ```make dev``` or ```make prd``` you will find your wasm builds under ```ffmpeg.wasm/packages/core/dist/```

You can also build multi thread and other types of ffmpeg follow these documentations official docs
[Documentation to compile ffmpeg.wasm](https://ffmpegwasm.netlify.app/docs/contribution/core/
)

i have 32gb ram, ryzen 5 processor and nvme drive and its a laptop so build time was good for me.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.