Repository: ffmpegwasm/ffmpeg.wasm Branch: main Commit: f876f907c7e9 Files: 265 Total size: 1.1 MB Directory structure: gitextract_a_xgwnzo/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── SECURITY.md │ └── workflows/ │ └── CI.yml ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── apps/ │ ├── angular-app/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ ├── launch.json │ │ │ └── tasks.json │ │ ├── README.md │ │ ├── angular.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.component.css │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.config.ts │ │ │ │ └── app.routes.ts │ │ │ ├── assets/ │ │ │ │ └── .gitkeep │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ └── styles.css │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ └── tsconfig.spec.json │ ├── nextjs-app/ │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app/ │ │ │ ├── Home.tsx │ │ │ ├── NoSSRWrapper.tsx │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── next.config.js │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── tailwind.config.ts │ │ └── tsconfig.json │ ├── react-vite-app/ │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ ├── main.tsx │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ ├── solidstart-app/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app.config.ts │ │ ├── package.json │ │ ├── postcss.config.cjs │ │ ├── src/ │ │ │ ├── app.css │ │ │ ├── app.tsx │ │ │ ├── entry-client.tsx │ │ │ ├── entry-server.tsx │ │ │ ├── global.d.ts │ │ │ └── routes/ │ │ │ └── index.tsx │ │ ├── tailwind.config.cjs │ │ └── tsconfig.json │ ├── sveltekit-app/ │ │ ├── .eslintignore │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── .npmrc │ │ ├── .prettierignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── playwright.config.ts │ │ ├── src/ │ │ │ ├── app.d.ts │ │ │ ├── app.html │ │ │ ├── index.test.ts │ │ │ ├── lib/ │ │ │ │ ├── FFmpegDemo.svelte │ │ │ │ └── index.ts │ │ │ └── routes/ │ │ │ └── +page.svelte │ │ ├── svelte.config.js │ │ ├── tests/ │ │ │ └── test.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── vanilla-app/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── concatDemuxer.html │ │ │ ├── style.css │ │ │ ├── transcode-mt.esm.html │ │ │ ├── transcode-mt.html │ │ │ ├── transcode.esm.html │ │ │ ├── transcode.html │ │ │ └── trim.html │ │ └── server.js │ ├── vue-vite-app/ │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── .prettierrc.json │ │ ├── .vscode/ │ │ │ └── extensions.json │ │ ├── README.md │ │ ├── env.d.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.vue │ │ │ ├── assets/ │ │ │ │ ├── base.css │ │ │ │ └── main.css │ │ │ ├── components/ │ │ │ │ └── FFmpegDemo.vue │ │ │ └── main.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ └── website/ │ ├── .gitignore │ ├── README.md │ ├── assets/ │ │ ├── angular.xcf │ │ ├── background.xcf │ │ ├── ffmpegwasm-arch.drawio │ │ ├── nextjs.xcf │ │ ├── react-vite.xcf │ │ ├── solidstart-vite.xcf │ │ ├── sveltekit-vite.xcf │ │ ├── vanilla.xcf │ │ └── vue-vite.xcf │ ├── babel.config.js │ ├── blog/ │ │ ├── 2023-07-26-release-ffmpeg.wasm-0.12.0.mdx │ │ └── authors.yml │ ├── deploy.sh │ ├── docs/ │ │ ├── contribution/ │ │ │ ├── core.md │ │ │ ├── ffmpeg.md │ │ │ └── util.md │ │ ├── faq.md │ │ ├── getting-started/ │ │ │ ├── examples.md │ │ │ ├── installation.md │ │ │ └── usage.md │ │ ├── migration.md │ │ ├── overview.md │ │ ├── performance.md │ │ └── privacy-policy.md │ ├── docusaurus.config.js │ ├── netlify.toml │ ├── package.json │ ├── sidebars.js │ ├── src/ │ │ ├── components/ │ │ │ ├── ExternalLibraries/ │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── HomepageFeatures/ │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Playground/ │ │ │ │ ├── CoreDownloader.tsx │ │ │ │ ├── CoreSwitcher.tsx │ │ │ │ ├── Workspace/ │ │ │ │ │ ├── Editor.tsx │ │ │ │ │ ├── FileSystemManager.tsx │ │ │ │ │ ├── MoreButton.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── types.tsx │ │ │ │ ├── const.ts │ │ │ │ └── index.tsx │ │ │ └── common/ │ │ │ ├── ExampleCard.tsx │ │ │ ├── LinearProgressWithLabel.tsx │ │ │ ├── MuiThemeProvider/ │ │ │ │ └── index.tsx │ │ │ ├── ThemedButton/ │ │ │ │ └── index.tsx │ │ │ └── ThemedIconButton/ │ │ │ └── index.tsx │ │ ├── css/ │ │ │ └── custom.css │ │ ├── pages/ │ │ │ ├── index.module.css │ │ │ ├── index.tsx │ │ │ └── playground.md │ │ ├── theme/ │ │ │ └── ReactLiveScope/ │ │ │ └── index.js │ │ └── util.ts │ ├── static/ │ │ ├── .nojekyll │ │ └── ads.txt │ └── tsconfig.json ├── build/ │ ├── aom.sh │ ├── ffmpeg-wasm.sh │ ├── ffmpeg.sh │ ├── freetype2.sh │ ├── fribidi.sh │ ├── harfbuzz.sh │ ├── lame.sh │ ├── libass.sh │ ├── libvpx.sh │ ├── libwebp.sh │ ├── ogg.sh │ ├── opus.sh │ ├── theora.sh │ ├── vorbis.sh │ ├── wavpack.sh │ ├── x264.sh │ ├── x265.sh │ ├── zimg.sh │ └── zlib.sh ├── package.json ├── packages/ │ ├── core/ │ │ ├── .gitignore │ │ └── package.json │ ├── core-mt/ │ │ ├── .gitignore │ │ └── package.json │ ├── ffmpeg/ │ │ ├── .eslintignore │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── package.json │ │ ├── src/ │ │ │ ├── classes.ts │ │ │ ├── const.ts │ │ │ ├── empty.mts │ │ │ ├── errors.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils.ts │ │ │ └── worker.ts │ │ ├── tsconfig.esm.json │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── types/ │ │ ├── package.json │ │ └── types/ │ │ └── index.d.ts │ └── util/ │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── package.json │ ├── src/ │ │ ├── const.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ └── types.ts │ ├── tests/ │ │ ├── .eslintrc │ │ ├── constants.js │ │ ├── ffmpeg.test.html │ │ └── ffmpeg.test.js │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ └── webpack.config.cjs ├── scripts/ │ └── download-assets.js ├── src/ │ ├── bind/ │ │ ├── .eslintrc.cjs │ │ └── ffmpeg/ │ │ ├── bind.js │ │ ├── export-runtime.js │ │ └── export.js │ └── fftools/ │ ├── Makefile │ ├── cmdutils.c │ ├── cmdutils.h │ ├── ffmpeg.c │ ├── ffmpeg.h │ ├── ffmpeg_filter.c │ ├── ffmpeg_hw.c │ ├── ffmpeg_mux.c │ ├── ffmpeg_opt.c │ ├── ffplay.c │ ├── ffprobe.c │ ├── fopen_utf8.h │ ├── opt_common.c │ └── opt_common.h ├── tests/ │ ├── .eslintrc.json │ ├── ffmpeg-core-mt.test.html │ ├── ffmpeg-core-st.test.html │ ├── ffmpeg-core.test.js │ ├── ffmpeg-mt.test.html │ ├── ffmpeg-st.test.html │ ├── ffmpeg.test.js │ ├── index.html │ ├── test-helper-browser.js │ ├── test-helper-mt.js │ ├── test-helper-st.js │ └── util.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ . !text !filter !merge !diff ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: ffmpegwasm ko_fi: # Replace with a single Ko-fi username tidelift: # Remove community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Please provide a GitHub link or code snippet. **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/SECURITY.md ================================================ ## Security contact information To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ================================================ FILE: .github/workflows/CI.yml ================================================ name: CI on: push: branches: - main pull_request: branches: - main jobs: build-core: runs-on: ubuntu-latest steps: - name: Checkout Source Code uses: actions/checkout@v2 - name: Update pkg-config database run: sudo ldconfig - name: Setup Docker Buildx id: buildx uses: docker/setup-buildx-action@v2 - name: Cache build id: cache-build uses: actions/cache@v4 with: path: build-cache-st key: build-cache-st-v1-${{ hashFiles('Dockerfile', 'Makefile', 'build/*') }} restore-keys: | build-cache-st-v1- - name: Build ffmpeg-core run: make prd EXTRA_ARGS="--cache-from=type=local,src=build-cache-st --cache-to=type=local,dest=build-cache-st,mode=max" - name: Upload core uses: actions/upload-artifact@v4 with: name: ffmpeg-core path: packages/core/dist/* build-core-mt: runs-on: ubuntu-latest steps: - name: Checkout Source Code uses: actions/checkout@v2 - name: Setup Docker Buildx id: buildx uses: docker/setup-buildx-action@v2 - name: Cache build id: cache-build uses: actions/cache@v4 with: path: build-cache-mt key: build-cache-mt-v1-${{ hashFiles('Dockerfile', 'Makefile', 'build/*') }} restore-keys: | build-cache-v1- - name: Build ffmpet-core-mt run: make prd-mt EXTRA_ARGS="--cache-from=type=local,src=build-cache-mt --cache-to=type=local,dest=build-cache-mt,mode=max" - name: Upload core-mt uses: actions/upload-artifact@v4 with: name: ffmpeg-core-mt path: packages/core-mt/dist/* tests: runs-on: ubuntu-latest needs: - build-core - build-core-mt steps: - name: Checkout Source Code uses: actions/checkout@v2 - name: Download ffmpeg-core uses: actions/download-artifact@v4 with: name: ffmpeg-core path: packages/core/dist - name: Download ffmpeg-core-mt uses: actions/download-artifact@v4 with: name: ffmpeg-core-mt path: packages/core-mt/dist - name: Use Node.js 18 uses: actions/setup-node@v2 with: node-version: 18.x - name: Cache dependencies id: cache-dependencies uses: actions/cache@v4 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }} restore-keys: | node-modules- - name: Install dependencies run: npm install - name: Install Chrome uses: browser-actions/setup-chrome@latest with: chrome-version: stable - name: Run tests env: CHROME_HEADLESS: 1 CHROME_PATH: chrome CHROME_FLAGS: "--headless --disable-gpu --no-sandbox --enable-features=SharedArrayBuffer,CrossOriginIsolation" HEADERS: '{"Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp"}' run: | # Start test server with proper headers for all tests npm run serve -- --headers "$HEADERS" & # Increase wait time to ensure server is ready sleep 15 # Verify headers and isolation status echo "Checking security headers and isolation status..." curl -v http://localhost:3000/tests/ffmpeg-core-st.test.html 2>&1 | grep -i "cross-origin" # Run verification script first echo "Verifying browser environment..." cat << EOF > verify-browser.html EOF # Run single-threaded tests first echo "Running single-threaded tests..." npx mocha-headless-chrome \ --args="$CHROME_FLAGS" \ -a no-sandbox \ -f http://localhost:3000/tests/ffmpeg-core-st.test.html 2>&1 | tee st-core-test.log npx mocha-headless-chrome \ --args="$CHROME_FLAGS" \ -a no-sandbox \ -f http://localhost:3000/tests/ffmpeg-st.test.html 2>&1 | tee st-test.log # Run multi-threaded tests echo "Running multi-threaded tests..." # Create a test script to verify browser environment cat << EOF > verify-browser.html Browser Environment Test EOF # Run the verification in Chrome echo "Verifying browser environment..." npx mocha-headless-chrome \ --args="$CHROME_FLAGS --enable-features=SharedArrayBuffer,CrossOriginIsolation" \ -a no-sandbox \ -f http://localhost:3000/verify-browser.html # Run MT tests with verified configuration npx mocha-headless-chrome \ --args="$CHROME_FLAGS --enable-features=SharedArrayBuffer,CrossOriginIsolation" \ -a no-sandbox \ -f http://localhost:3000/tests/ffmpeg-core-mt.test.html 2>&1 | tee mt-core-test.log npx mocha-headless-chrome \ --args="$CHROME_FLAGS --enable-features=SharedArrayBuffer,CrossOriginIsolation" \ -a no-sandbox \ -f http://localhost:3000/tests/ffmpeg-mt.test.html 2>&1 | tee mt-test.log # Display all logs for debugging echo "=== Test Logs ===" for log in *-test.log; do echo "Contents of $log:" cat $log done ================================================ FILE: .gitignore ================================================ node_modules dist /.nyc_output .DS_Store # ide .idea/ # Local Netlify folder .netlify ================================================ FILE: .gitmodules ================================================ [submodule "testdata"] path = testdata url = https://github.com/ffmpegwasm/testdata ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jeromewus@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ Check https://ffmpegwasm.netlify.app/docs/contribution/core ================================================ 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 \ --enable-libx264 \ --enable-libx265 \ --enable-libvpx \ --enable-libmp3lame \ --enable-libtheora \ --enable-libvorbis \ --enable-libopus \ --enable-zlib \ --enable-libwebp \ --enable-libfreetype \ --enable-libfribidi \ --enable-libass \ --enable-libzimg # 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 \ -lx265 \ -lvpx \ -lmp3lame \ -logg \ -ltheora \ -lvorbis \ -lvorbisenc \ -lvorbisfile \ -lopus \ -lz \ -lwebpmux \ -lwebp \ -lsharpyuv \ -lfreetype \ -lfribidi \ -lharfbuzz \ -lass \ -lzimg RUN mkdir -p /src/dist/umd && bash -x /src/build.sh \ ${FFMPEG_LIBS} \ -o dist/umd/ffmpeg-core.js RUN mkdir -p /src/dist/esm && bash -x /src/build.sh \ ${FFMPEG_LIBS} \ -sEXPORT_ES6 \ -o dist/esm/ffmpeg-core.js # 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 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Jerome Wu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ all: dev MT_FLAGS := -sUSE_PTHREADS -pthread DEV_ARGS := --progress=plain DEV_CFLAGS := --profiling DEV_MT_CFLAGS := $(DEV_CFLAGS) $(MT_FLAGS) PROD_CFLAGS := -O3 -msimd128 PROD_MT_CFLAGS := $(PROD_CFLAGS) $(MT_FLAGS) clean: rm -rf ./packages/core$(PKG_SUFFIX)/dist .PHONY: build build: make clean PKG_SUFFIX="$(PKG_SUFFIX)" EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ EXTRA_LDFLAGS="$(EXTRA_LDFLAGS)" \ FFMPEG_ST="$(FFMPEG_ST)" \ FFMPEG_MT="$(FFMPEG_MT)" \ docker buildx build \ --build-arg EXTRA_CFLAGS \ --build-arg EXTRA_LDFLAGS \ --build-arg FFMPEG_MT \ --build-arg FFMPEG_ST \ -o ./packages/core$(PKG_SUFFIX) \ $(EXTRA_ARGS) \ . build-st: make build \ FFMPEG_ST=yes build-mt: make build \ PKG_SUFFIX=-mt \ FFMPEG_MT=yes dev: make build-st EXTRA_CFLAGS="$(DEV_CFLAGS)" EXTRA_ARGS="$(DEV_ARGS)" dev-mt: make build-mt EXTRA_CFLAGS="$(DEV_MT_CFLAGS)" EXTRA_ARGS="$(DEV_ARGS)" prd: make build-st EXTRA_CFLAGS="$(PROD_CFLAGS)" prd-mt: make build-mt EXTRA_CFLAGS="$(PROD_MT_CFLAGS)" ================================================ FILE: README.md ================================================ ---

ffmpeg.wasm

# ffmpeg.wasm ffmpeg.wasm is a pure Webassembly / Javascript port of FFmpeg. It enables video & audio record, convert and stream right inside browsers. [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://github.com/emersion/stability-badges#experimental) [![Node Version](https://img.shields.io/node/v/@ffmpeg/ffmpeg.svg)](https://img.shields.io/node/v/@ffmpeg/ffmpeg.svg) [![Actions Status](https://github.com/ffmpegwasm/ffmpeg.wasm/workflows/CI/badge.svg)](https://github.com/ffmpegwasm/ffmpeg.wasm/actions) ![npm (tag)](https://img.shields.io/npm/v/@ffmpeg/ffmpeg/latest) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/ffmpegwasm/ffmpeg.wasm/graphs/commit-activity) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Downloads Total](https://img.shields.io/npm/dt/@ffmpeg/ffmpeg.svg)](https://www.npmjs.com/package/@ffmpeg/ffmpeg) [![Downloads Month](https://img.shields.io/npm/dm/@ffmpeg/ffmpeg.svg)](https://www.npmjs.com/package/@ffmpeg/ffmpeg) [![Netlify Status](https://api.netlify.com/api/v1/badges/1943b6d3-45ad-4b46-bfba-cb8d5716604c/deploy-status)](https://app.netlify.com/sites/ffmpegwasm/deploys) Join us on Discord! [![Discord](https://dcbadge.vercel.app/api/server/NjGMaqqfm5)](https://discord.gg/NjGMaqqfm5) ## Documentation - [Introduction](https://ffmpegwasm.netlify.app/docs/overview) - [Getting Started](https://ffmpegwasm.netlify.app/docs/getting-started/installation) - [API](https://ffmpegwasm.netlify.app/docs/api/ffmpeg/) - [FAQ](https://ffmpegwasm.netlify.app/docs/faq) - [Contribution](https://ffmpegwasm.netlify.app/docs/contribution/core) Please sponsor ffmpeg.wasm to make it sustainable. :heart: ================================================ FILE: apps/angular-app/.editorconfig ================================================ # Editor configuration, see https://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.ts] quote_type = single [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: apps/angular-app/.gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # Compiled output /dist /tmp /out-tsc /bazel-out # Node /node_modules npm-debug.log yarn-error.log # IDEs and editors .idea/ .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # Visual Studio Code .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .history/* # Miscellaneous /.angular/cache .sass-cache/ /connect.lock /coverage /libpeerconnection.log testem.log /typings # System files .DS_Store Thumbs.db ================================================ FILE: apps/angular-app/.vscode/extensions.json ================================================ { // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 "recommendations": ["angular.ng-template"] } ================================================ FILE: apps/angular-app/.vscode/launch.json ================================================ { // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "ng serve", "type": "chrome", "request": "launch", "preLaunchTask": "npm: start", "url": "http://localhost:4200/" }, { "name": "ng test", "type": "chrome", "request": "launch", "preLaunchTask": "npm: test", "url": "http://localhost:9876/debug.html" } ] } ================================================ FILE: apps/angular-app/.vscode/tasks.json ================================================ { // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 "version": "2.0.0", "tasks": [ { "type": "npm", "script": "start", "isBackground": true, "problemMatcher": { "owner": "typescript", "pattern": "$tsc", "background": { "activeOnStart": true, "beginsPattern": { "regexp": "(.*?)" }, "endsPattern": { "regexp": "bundle generation complete" } } } }, { "type": "npm", "script": "test", "isBackground": true, "problemMatcher": { "owner": "typescript", "pattern": "$tsc", "background": { "activeOnStart": true, "beginsPattern": { "regexp": "(.*?)" }, "endsPattern": { "regexp": "bundle generation complete" } } } } ] } ================================================ FILE: apps/angular-app/README.md ================================================ # AngularApp This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.7. ## Development server Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. ## Code scaffolding Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. ## Build Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. ## Running unit tests Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). ## Running end-to-end tests Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. ## Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. ================================================ FILE: apps/angular-app/angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular-app": { "projectType": "application", "schematics": {}, "root": "", "sourceRoot": "src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/angular-app", "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ "zone.js" ], "tsConfig": "tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.css" ], "scripts": [] }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "outputHashing": "all" }, "development": { "optimization": false, "extractLicenses": false, "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "buildTarget": "angular-app:build:production" }, "development": { "buildTarget": "angular-app:build:development" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "buildTarget": "angular-app:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": [ "zone.js", "zone.js/testing" ], "tsConfig": "tsconfig.spec.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.css" ], "scripts": [] } } } } } } ================================================ FILE: apps/angular-app/package.json ================================================ { "name": "angular-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" }, "private": true, "dependencies": { "@angular/animations": "^17.0.0", "@angular/common": "^17.0.0", "@angular/compiler": "^17.0.0", "@angular/core": "^17.0.0", "@angular/forms": "^17.0.0", "@angular/platform-browser": "^17.0.0", "@angular/platform-browser-dynamic": "^17.0.0", "@angular/router": "^17.0.0", "@ffmpeg/ffmpeg": "*", "@ffmpeg/util": "*", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.2" }, "devDependencies": { "@angular-devkit/build-angular": "^17.0.7", "@angular/cli": "^17.0.7", "@angular/compiler-cli": "^17.0.0", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.2.2" } } ================================================ FILE: apps/angular-app/src/app/app.component.css ================================================ ================================================ FILE: apps/angular-app/src/app/app.component.html ================================================

{{ message }}

================================================ FILE: apps/angular-app/src/app/app.component.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AppComponent], }).compileComponents(); }); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have the 'angular-app' title`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app.title).toEqual('angular-app'); }); it('should render title', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.nativeElement as HTMLElement; expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-app'); }); }); ================================================ FILE: apps/angular-app/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterOutlet } from '@angular/router'; import { FFmpeg } from '@ffmpeg/ffmpeg'; import { fetchFile, toBlobURL } from '@ffmpeg/util'; const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.10/dist/esm'; @Component({ selector: 'app-root', standalone: true, imports: [CommonModule, RouterOutlet], templateUrl: './app.component.html', styleUrl: './app.component.css', }) export class AppComponent { loaded = false; ffmpeg = new FFmpeg(); videoURL = ''; message = ''; async load() { this.ffmpeg.on('log', ({ message }) => { this.message = message; }); await this.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', ), }); this.loaded = true; } async transcode() { const videoURL = 'https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi'; await this.ffmpeg.writeFile('input.avi', await fetchFile(videoURL)); await this.ffmpeg.exec(['-i', 'input.avi', 'output.mp4']); const fileData = await this.ffmpeg.readFile('output.mp4'); const data = new Uint8Array(fileData as ArrayBuffer); this.videoURL = URL.createObjectURL( new Blob([data.buffer], { type: 'video/mp4' }), ); } } ================================================ FILE: apps/angular-app/src/app/app.config.ts ================================================ import { ApplicationConfig } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [provideRouter(routes)] }; ================================================ FILE: apps/angular-app/src/app/app.routes.ts ================================================ import { Routes } from '@angular/router'; export const routes: Routes = []; ================================================ FILE: apps/angular-app/src/assets/.gitkeep ================================================ ================================================ FILE: apps/angular-app/src/index.html ================================================ AngularApp ================================================ FILE: apps/angular-app/src/main.ts ================================================ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent, appConfig) .catch((err) => console.error(err)); ================================================ FILE: apps/angular-app/src/styles.css ================================================ /* You can add global styles to this file, and also import other style files */ ================================================ FILE: apps/angular-app/tsconfig.app.json ================================================ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", "types": [] }, "files": [ "src/main.ts" ], "include": [ "src/**/*.d.ts" ] } ================================================ FILE: apps/angular-app/tsconfig.json ================================================ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "esModuleInterop": true, "sourceMap": true, "declaration": false, "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, "target": "ES2022", "module": "ES2022", "useDefineForClassFields": false, "lib": [ "ES2022", "dom" ] }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true } } ================================================ FILE: apps/angular-app/tsconfig.spec.json ================================================ /* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", "types": [ "jasmine" ] }, "include": [ "src/**/*.spec.ts", "src/**/*.d.ts" ] } ================================================ FILE: apps/nextjs-app/.eslintrc.json ================================================ { "extends": "next/core-web-vitals" } ================================================ FILE: apps/nextjs-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: apps/nextjs-app/README.md ================================================ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). ## Getting Started First, run the development server: ```bash npm run dev # or yarn dev # or pnpm dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. ## Learn More To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. ================================================ FILE: apps/nextjs-app/app/Home.tsx ================================================ "use client"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { fetchFile, toBlobURL } from "@ffmpeg/util"; import { useRef, useState } from "react"; export default function Home() { const [loaded, setLoaded] = useState(false); const [isLoading, setIsLoading] = useState(false); const ffmpegRef = useRef(new FFmpeg()); const videoRef = useRef(null); const messageRef = useRef(null); const load = async () => { setIsLoading(true); const baseURL = "https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/umd"; const ffmpeg = ffmpegRef.current; ffmpeg.on("log", ({ message }) => { if (messageRef.current) messageRef.current.innerHTML = 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); setIsLoading(false); }; const transcode = async () => { const ffmpeg = ffmpegRef.current; // u can use 'https://ffmpegwasm.netlify.app/video/video-15s.avi' to download the video to public folder for testing await ffmpeg.writeFile( "input.avi", await fetchFile( "https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi" ) ); await ffmpeg.exec(["-i", "input.avi", "output.mp4"]); const data = (await ffmpeg.readFile("output.mp4")) as any; if (videoRef.current) videoRef.current.src = URL.createObjectURL( new Blob([data.buffer], { type: "video/mp4" }) ); }; return loaded ? (

) : ( ); } ================================================ FILE: apps/nextjs-app/app/NoSSRWrapper.tsx ================================================ import dynamic from 'next/dynamic' import React from 'react' const NoSSRWrapper = props => ( {props.children} ) export default dynamic(() => Promise.resolve(NoSSRWrapper), { ssr: false }) ================================================ FILE: apps/nextjs-app/app/globals.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; ================================================ FILE: apps/nextjs-app/app/layout.tsx ================================================ import './globals.css' import type { Metadata } from 'next' import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ================================================ FILE: apps/nextjs-app/app/page.tsx ================================================ 'use client' import NoSSRWrapper from "./NoSSRWrapper"; import Home from "./Home"; export default function Page() { return } ================================================ FILE: apps/nextjs-app/next.config.js ================================================ /** @type {import('next').NextConfig} */ const nextConfig = {} module.exports = nextConfig ================================================ FILE: apps/nextjs-app/package.json ================================================ { "name": "nextjs-ffmpeg-starter", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@ffmpeg/ffmpeg": "*", "@ffmpeg/util": "*", "@types/node": "20.4.9", "@types/react": "18.2.20", "@types/react-dom": "18.2.7", "autoprefixer": "10.4.14", "eslint": "8.46.0", "eslint-config-next": "13.4.13", "next": "^14.0.4", "postcss": "8.4.31", "react": "18.2.0", "react-dom": "18.2.0", "tailwindcss": "3.3.3", "typescript": "5.1.6" } } ================================================ FILE: apps/nextjs-app/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: apps/nextjs-app/tailwind.config.ts ================================================ import type { Config } from 'tailwindcss' const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], } export default config ================================================ FILE: apps/nextjs-app/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: apps/react-vite-app/.eslintrc.cjs ================================================ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], }, } ================================================ FILE: apps/react-vite-app/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: apps/react-vite-app/README.md ================================================ # React + TypeScript + Vite This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. Currently, two official plugins are available: - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh ## Expanding the ESLint configuration If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: - Configure the top-level `parserOptions` property like this: ```js parserOptions: { ecmaVersion: 'latest', sourceType: 'module', project: ['./tsconfig.json', './tsconfig.node.json'], tsconfigRootDir: __dirname, }, ``` - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list ================================================ FILE: apps/react-vite-app/index.html ================================================ Vite + React + TS
================================================ FILE: apps/react-vite-app/package.json ================================================ { "name": "react-vite-app", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { "@ffmpeg/ffmpeg": "*", "@ffmpeg/util": "*", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react": "^4.0.3", "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", "typescript": "^5.0.2", "vite": "^4.5.2" } } ================================================ FILE: apps/react-vite-app/src/App.tsx ================================================ import { useState, useRef } from "react"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { toBlobURL, fetchFile } from "@ffmpeg/util"; function App() { const [loaded, setLoaded] = useState(false); const ffmpegRef = useRef(new FFmpeg()); const videoRef = useRef(null); const messageRef = useRef(null); const load = async () => { const baseURL = "https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.10/dist/esm"; const ffmpeg = ffmpegRef.current; ffmpeg.on("log", ({ message }) => { if (messageRef.current) messageRef.current.innerHTML = 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" ), workerURL: await toBlobURL( `${baseURL}/ffmpeg-core.worker.js`, "text/javascript" ), }); setLoaded(true); }; const transcode = async () => { const videoURL = "https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi"; const ffmpeg = ffmpegRef.current; await ffmpeg.writeFile("input.avi", await fetchFile(videoURL)); await ffmpeg.exec(["-i", "input.avi", "output.mp4"]); const fileData = await ffmpeg.readFile("output.mp4"); const data = new Uint8Array(fileData as ArrayBuffer); if (videoRef.current) { videoRef.current.src = URL.createObjectURL( new Blob([data.buffer], { type: "video/mp4" }) ); } }; return loaded ? ( <>

) : ( ); } export default App; ================================================ FILE: apps/react-vite-app/src/index.css ================================================ :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } a { font-weight: 500; color: #646cff; text-decoration: inherit; } a:hover { color: #535bf2; } body { margin: 0; display: flex; place-items: center; min-width: 320px; min-height: 100vh; justify-content: center; } h1 { font-size: 3.2em; line-height: 1.1; } button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; } button:hover { border-color: #646cff; } button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } a:hover { color: #747bff; } button { background-color: #f9f9f9; } } ================================================ FILE: apps/react-vite-app/src/main.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: apps/react-vite-app/src/vite-env.d.ts ================================================ /// ================================================ FILE: apps/react-vite-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] } ================================================ FILE: apps/react-vite-app/tsconfig.node.json ================================================ { "compilerOptions": { "composite": true, "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts"] } ================================================ FILE: apps/react-vite-app/vite.config.ts ================================================ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], optimizeDeps: { exclude: ["@ffmpeg/ffmpeg", "@ffmpeg/util"], }, server: { headers: { "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp", }, }, }); ================================================ FILE: apps/solidstart-app/.gitignore ================================================ dist .solid .output .vercel .netlify .vinxi # Environment .env .env*.local # dependencies /node_modules # IDEs and editors /.idea .project .classpath *.launch .settings/ # Temp gitignore # System Files .DS_Store Thumbs.db ================================================ FILE: apps/solidstart-app/README.md ================================================ # SolidStart Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); ## Creating a project ```bash # create a new project in the current directory npm init solid@latest # create a new project in my-app npm init solid@latest my-app ``` ## Developing Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: ```bash npm run dev # or start the server and open the app in a new browser tab npm run dev -- --open ``` ## Building Solid apps are built with _presets_, which optimise your project for deployment to different environments. By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`. ## This project was created with the [Solid CLI](https://solid-cli.netlify.app) ================================================ FILE: apps/solidstart-app/app.config.ts ================================================ import { defineConfig } from "@solidjs/start/config"; export default defineConfig({ ssr: false, server: { static: true, }, vite: { optimizeDeps: { exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'] }, } }); ================================================ FILE: apps/solidstart-app/package.json ================================================ { "name": "solidstart-ffmpeg", "type": "module", "scripts": { "dev": "vinxi dev", "build": "vinxi build", "start": "vinxi start" }, "dependencies": { "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", "@solidjs/router": "^0.14.1", "@solidjs/start": "^1.0.4", "autoprefixer": "^10.4.19", "postcss": "^8.4.38", "solid-js": "^1.8.18", "tailwindcss": "^3.4.3", "vinxi": "^0.5.7" }, "engines": { "node": ">=18" } } ================================================ FILE: apps/solidstart-app/postcss.config.cjs ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: apps/solidstart-app/src/app.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; :root { --background-rgb: 214, 219, 220; --foreground-rgb: 0, 0, 0; } @media (prefers-color-scheme: dark) { :root { --background-rgb: 0, 0, 0; --foreground-rgb: 255, 255, 255; } } body { background: rgb(var(--background-rgb)); color: rgb(var(--foreground-rgb)); } ================================================ FILE: apps/solidstart-app/src/app.tsx ================================================ import { Router } from '@solidjs/router'; import { FileRoutes } from '@solidjs/start/router'; import { Suspense } from 'solid-js'; import './app.css'; export default function App() { return ( ( <> {props.children} )}> ); } ================================================ FILE: apps/solidstart-app/src/entry-client.tsx ================================================ // @refresh reload import { mount, StartClient } from "@solidjs/start/client"; mount(() => , document.getElementById("app")!); ================================================ FILE: apps/solidstart-app/src/entry-server.tsx ================================================ // @refresh reload import { createHandler, StartServer } from '@solidjs/start/server'; import { HttpHeader, HttpStatusCode } from '@solidjs/start'; export default createHandler(() => ( ( {/*necessary because ffmpeg library uses sharedArrayBuffer */} {assets}
{children}
{scripts} )} /> )); ================================================ FILE: apps/solidstart-app/src/global.d.ts ================================================ /// ================================================ FILE: apps/solidstart-app/src/routes/index.tsx ================================================ import { FFmpeg } from '@ffmpeg/ffmpeg'; import { fetchFile, toBlobURL } from '@ffmpeg/util'; import { createSignal, Show } from 'solid-js'; const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.10/dist/esm'; const videoURL = 'https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi'; export default function Home() { const [loaded, setLoaded] = createSignal(false); const [isLoading, setIsLoading] = createSignal(false); let ffmpegRef = new FFmpeg(); let videoRef: any = null; let messageRef: any = null; const load = async () => { setIsLoading(true); const ffmpeg = ffmpegRef; ffmpeg.on('log', ({ message }) => { if (messageRef) messageRef.innerHTML = 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' ), workerURL: await toBlobURL( `${baseURL}/ffmpeg-core.worker.js`, 'text/javascript' ), }); setLoaded(true); setIsLoading(false); }; const transcode = async () => { const ffmpeg = ffmpegRef; // u can use 'https://ffmpegwasm.netlify.app/video/video-15s.avi' to download the video to public folder for testing await ffmpeg.writeFile('input.avi', await fetchFile(videoURL)); await ffmpeg.exec(['-i', 'input.avi', 'output.mp4']); const data = (await ffmpeg.readFile('output.mp4')) as any; if (videoRef) videoRef.src = URL.createObjectURL( new Blob([data.buffer], { type: 'video/mp4' }) ); }; return ( Load ffmpeg-core }>

); } ================================================ FILE: apps/solidstart-app/tailwind.config.cjs ================================================ /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{html,js,jsx,ts,tsx}"], theme: { extend: {} }, plugins: [] }; ================================================ FILE: apps/solidstart-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "jsx": "preserve", "jsxImportSource": "solid-js", "allowJs": true, "noEmit": true, "strict": true, "types": ["vinxi/types/client"], "isolatedModules": true, "paths": { "~/*": ["./src/*"] } } } ================================================ FILE: apps/sveltekit-app/.eslintignore ================================================ .DS_Store node_modules /build /.svelte-kit /package .env .env.* !.env.example # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml package-lock.json yarn.lock ================================================ FILE: apps/sveltekit-app/.eslintrc.cjs ================================================ /** @type { import("eslint").Linter.Config } */ module.exports = { root: true, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:svelte/recommended', 'prettier' ], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], parserOptions: { sourceType: 'module', ecmaVersion: 2020, extraFileExtensions: ['.svelte'] }, env: { browser: true, es2017: true, node: true }, overrides: [ { files: ['*.svelte'], parser: 'svelte-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser' } } ] }; ================================================ FILE: apps/sveltekit-app/.gitignore ================================================ .DS_Store node_modules /build /.svelte-kit /package .env .env.* !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* ================================================ FILE: apps/sveltekit-app/.npmrc ================================================ engine-strict=true ================================================ FILE: apps/sveltekit-app/.prettierignore ================================================ # Ignore files for PNPM, NPM and YARN pnpm-lock.yaml package-lock.json yarn.lock ================================================ FILE: apps/sveltekit-app/.prettierrc ================================================ { "useTabs": true, "singleQuote": true, "trailingComma": "none", "printWidth": 100, "plugins": ["prettier-plugin-svelte"], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] } ================================================ FILE: apps/sveltekit-app/README.md ================================================ # create-svelte Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). ## Creating a project If you're seeing this, you've probably already done this step. Congrats! ```bash # create a new project in the current directory npm create svelte@latest # create a new project in my-app npm create svelte@latest my-app ``` ## Developing Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: ```bash npm run dev # or start the server and open the app in a new browser tab npm run dev -- --open ``` ## Building To create a production version of your app: ```bash npm run build ``` You can preview the production build with `npm run preview`. > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. ================================================ FILE: apps/sveltekit-app/package.json ================================================ { "name": "sveltekit-app", "version": "0.0.1", "private": true, "scripts": { "dev": "vite dev", "build": "vite build", "preview": "vite preview", "test": "npm run test:integration && npm run test:unit", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --check . && eslint .", "format": "prettier --write .", "test:integration": "playwright test", "test:unit": "vitest" }, "devDependencies": { "@ffmpeg/ffmpeg": "*", "@ffmpeg/util": "*", "@playwright/test": "^1.41.2", "@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/kit": "^2.5.0", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@tsconfig/node18": "^18.2.2", "@types/eslint": "8.56.2", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.35.1", "prettier": "^3.2.5", "prettier-plugin-svelte": "^3.1.2", "svelte": "^4.2.10", "svelte-check": "^3.6.4", "tslib": "^2.6.2", "typescript": "^5.3.3", "vite": "^5.1.1", "vitest": "^1.2.2" }, "type": "module" } ================================================ FILE: apps/sveltekit-app/playwright.config.ts ================================================ import type { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { webServer: { command: 'npm run build && npm run preview', port: 4173 }, testDir: 'tests', testMatch: /(.+\.)?(test|spec)\.[jt]s/ }; export default config; ================================================ FILE: apps/sveltekit-app/src/app.d.ts ================================================ // See https://kit.svelte.dev/docs/types#app // for information about these interfaces declare global { namespace App { // interface Error {} // interface Locals {} // interface PageData {} // interface PageState {} // interface Platform {} } } export {}; ================================================ FILE: apps/sveltekit-app/src/app.html ================================================ %sveltekit.head%
%sveltekit.body%
================================================ FILE: apps/sveltekit-app/src/index.test.ts ================================================ import { describe, it, expect } from 'vitest'; describe('sum test', () => { it('adds 1 + 2 to equal 3', () => { expect(1 + 2).toBe(3); }); }); ================================================ FILE: apps/sveltekit-app/src/lib/FFmpegDemo.svelte ================================================
================================================ FILE: apps/sveltekit-app/src/lib/index.ts ================================================ // place files you want to import through the `$lib` alias in this folder. ================================================ FILE: apps/sveltekit-app/src/routes/+page.svelte ================================================

Welcome to SvelteKit

Visit kit.svelte.dev to read the documentation

================================================ FILE: apps/sveltekit-app/svelte.config.js ================================================ import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { // Consult https://kit.svelte.dev/docs/integrations#preprocessors // for more information about preprocessors preprocess: vitePreprocess(), kit: { // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. adapter: adapter() } }; export default config; ================================================ FILE: apps/sveltekit-app/tests/test.ts ================================================ import { expect, test } from '@playwright/test'; test('index page has expected h1', async ({ page }) => { await page.goto('/'); await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible(); }); ================================================ FILE: apps/sveltekit-app/tsconfig.json ================================================ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { "allowJs": true, "checkJs": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, "strict": true, "moduleResolution": "bundler" } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in } ================================================ FILE: apps/sveltekit-app/vite.config.ts ================================================ import { sveltekit } from '@sveltejs/kit/vite'; import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vitest/config'; /** @type {import('vite').Plugin} */ const viteServerConfig = { name: 'log-request-middleware', configureServer(server) { server.middlewares.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET"); res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); next(); }); } }; export default defineConfig({ plugins: [sveltekit(), viteServerConfig], test: { include: ['src/**/*.{test,spec}.{js,ts}'] }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, optimizeDeps: { exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'] }, server: { headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }, fs: { allow: ['../..'] } } }); ================================================ FILE: apps/vanilla-app/.gitignore ================================================ *.tgz public/assets ================================================ FILE: apps/vanilla-app/README.md ================================================ # Vanilla / Browser Examples ## Setup You need to download assets from npm before running the examples: ```bash $ npm run download ``` ## Run To run this example, execute: ```bash $ npm start ``` Visit http://localhost:8080 to check available examples. ## Examples | Example | Description | | ------- | ----------- | | transcode.html | Transcoding example | | transcode-mt.html | Transcoding example using multi-thread | | transcode.esm.html | Transcoding example using module | | trim.html | Video trimming example | | concatDemuxer.html | Video concat example | ================================================ FILE: apps/vanilla-app/package.json ================================================ { "name": "browser", "version": "0.12.0-alpha.0", "description": "browser example", "private": true, "scripts": { "download": "node ../../scripts/download-assets.js", "prestart": "npm run download", "start": "node server.js" }, "author": "Jerome Wu ", "license": "MIT", "dependencies": { "express": "^4.19.2", "serve-index": "^1.9.1", "tar": "^6.2.1" } } ================================================ FILE: apps/vanilla-app/public/concatDemuxer.html ================================================

Select multiple video files to Concatenate


================================================ FILE: apps/vanilla-app/public/style.css ================================================ html, body { margin: 0; width: 100%; height: 100% } body { display: flex; flex-direction: column; align-items: center; } ================================================ FILE: apps/vanilla-app/public/transcode-mt.esm.html ================================================

Upload a video to transcode to mp4 (x264) and play!


================================================ FILE: apps/vanilla-app/public/transcode-mt.html ================================================

Upload a video to transcode to mp4 (x264) and play!


================================================ FILE: apps/vanilla-app/public/transcode.esm.html ================================================

Upload a video to transcode to mp4 (x264) and play!


================================================ FILE: apps/vanilla-app/public/transcode.html ================================================

Upload a video to transcode to mp4 (x264) and play!


================================================ FILE: apps/vanilla-app/public/trim.html ================================================

Upload a mp4 (x264) video and trim its first 1 seconds and play!


================================================ FILE: apps/vanilla-app/server.js ================================================ const path = require("path"); const express = require("express"); const serveIndex = require("serve-index"); const app = express(); const PORT = 8080; const ROOT = path.join(__dirname, "public"); app.use((_, res, next) => { res.set({ "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp", "Cross-Origin-Resource-Policy": "cross-origin", "Origin-Agent-Cluster": "?1", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Range", }); next(); }); app.use(express.static(ROOT)); app.use("/", serveIndex(ROOT)); app.listen(PORT, () => { console.log(`Listening on port ${PORT}`); }); ================================================ FILE: apps/vue-vite-app/.eslintrc.cjs ================================================ /* eslint-env node */ require('@rushstack/eslint-patch/modern-module-resolution') module.exports = { root: true, 'extends': [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier/skip-formatting' ], parserOptions: { ecmaVersion: 'latest' } } ================================================ FILE: apps/vue-vite-app/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules .DS_Store dist dist-ssr coverage *.local /cypress/videos/ /cypress/screenshots/ # Editor directories and files .vscode/* !.vscode/extensions.json .idea *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: apps/vue-vite-app/.prettierrc.json ================================================ { "$schema": "https://json.schemastore.org/prettierrc", "semi": false, "tabWidth": 2, "singleQuote": true, "printWidth": 100, "trailingComma": "none" } ================================================ FILE: apps/vue-vite-app/.vscode/extensions.json ================================================ { "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] } ================================================ FILE: apps/vue-vite-app/README.md ================================================ # vue-vite-app This template should help get you started developing with Vue 3 in Vite. ## Recommended IDE Setup [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). ## Type Support for `.vue` Imports in TS TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 1. Disable the built-in TypeScript Extension 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. ## Customize configuration See [Vite Configuration Reference](https://vitejs.dev/config/). ## Project Setup ```sh npm install ``` ### Compile and Hot-Reload for Development ```sh npm run dev ``` ### Type-Check, Compile and Minify for Production ```sh npm run build ``` ### Lint with [ESLint](https://eslint.org/) ```sh npm run lint ``` ================================================ FILE: apps/vue-vite-app/env.d.ts ================================================ /// ================================================ FILE: apps/vue-vite-app/index.html ================================================ Vite App
================================================ FILE: apps/vue-vite-app/package.json ================================================ { "name": "vue-vite-app", "version": "0.0.0", "private": true, "scripts": { "dev": "vite", "build": "run-p type-check build-only", "preview": "vite preview", "build-only": "vite build", "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "format": "prettier --write src/" }, "dependencies": { "@ffmpeg/ffmpeg": "*", "@ffmpeg/util": "*", "vue": "^3.4.18" }, "devDependencies": { "@rushstack/eslint-patch": "^1.7.2", "@tsconfig/node18": "^18.2.2", "@types/node": "^20.11.17", "@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue-jsx": "^3.1.0", "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^12.0.0", "@vue/tsconfig": "^0.5.1", "eslint": "^8.56.0", "eslint-plugin-vue": "^9.21.1", "npm-run-all": "^4.1.5", "prettier": "^3.2.5", "typescript": "~5.3.3", "vite": "^4.5.2", "vue-tsc": "^1.8.27" } } ================================================ FILE: apps/vue-vite-app/src/App.vue ================================================ ================================================ FILE: apps/vue-vite-app/src/assets/base.css ================================================ /* color palette from */ :root { --vt-c-white: #ffffff; --vt-c-white-soft: #f8f8f8; --vt-c-white-mute: #f2f2f2; --vt-c-black: #181818; --vt-c-black-soft: #222222; --vt-c-black-mute: #282828; --vt-c-indigo: #2c3e50; --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); --vt-c-text-light-1: var(--vt-c-indigo); --vt-c-text-light-2: rgba(60, 60, 60, 0.66); --vt-c-text-dark-1: var(--vt-c-white); --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); } /* semantic color variables for this project */ :root { --color-background: var(--vt-c-white); --color-background-soft: var(--vt-c-white-soft); --color-background-mute: var(--vt-c-white-mute); --color-border: var(--vt-c-divider-light-2); --color-border-hover: var(--vt-c-divider-light-1); --color-heading: var(--vt-c-text-light-1); --color-text: var(--vt-c-text-light-1); --section-gap: 160px; } @media (prefers-color-scheme: dark) { :root { --color-background: var(--vt-c-black); --color-background-soft: var(--vt-c-black-soft); --color-background-mute: var(--vt-c-black-mute); --color-border: var(--vt-c-divider-dark-2); --color-border-hover: var(--vt-c-divider-dark-1); --color-heading: var(--vt-c-text-dark-1); --color-text: var(--vt-c-text-dark-2); } } *, *::before, *::after { box-sizing: border-box; margin: 0; font-weight: normal; } body { min-height: 100vh; color: var(--color-text); background: var(--color-background); transition: color 0.5s, background-color 0.5s; line-height: 1.6; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 15px; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } ================================================ FILE: apps/vue-vite-app/src/assets/main.css ================================================ @import './base.css'; #app { max-width: 1280px; margin: 0 auto; padding: 2rem; font-weight: normal; } a, .green { text-decoration: none; color: hsla(160, 100%, 37%, 1); transition: 0.4s; } @media (hover: hover) { a:hover { background-color: hsla(160, 100%, 37%, 0.2); } } @media (min-width: 1024px) { body { display: flex; place-items: center; } #app { display: grid; grid-template-columns: 1fr 1fr; padding: 0 2rem; } } ================================================ FILE: apps/vue-vite-app/src/components/FFmpegDemo.vue ================================================ ================================================ FILE: apps/vue-vite-app/src/main.ts ================================================ import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app') ================================================ FILE: apps/vue-vite-app/tsconfig.app.json ================================================ { "extends": "@vue/tsconfig/tsconfig.dom.json", "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "exclude": ["src/**/__tests__/*"], "compilerOptions": { "composite": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, "target": "esnext", "moduleResolution": "node" } } ================================================ FILE: apps/vue-vite-app/tsconfig.json ================================================ { "files": [], "references": [ { "path": "./tsconfig.node.json" }, { "path": "./tsconfig.app.json" } ] } ================================================ FILE: apps/vue-vite-app/tsconfig.node.json ================================================ { "extends": "@tsconfig/node18/tsconfig.json", "include": [ "vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*" ], "compilerOptions": { "composite": true, "module": "ESNext", "moduleResolution": "Bundler", "types": ["node"] } } ================================================ FILE: apps/vue-vite-app/vite.config.ts ================================================ import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue(), vueJsx()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, optimizeDeps: { exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'] }, server: { headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' } } }) ================================================ FILE: apps/website/.gitignore ================================================ # Dependencies /node_modules # Production /build /docs/api # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: apps/website/README.md ================================================ # Website This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. ### Installation When cloning the repository, make sure you have git-lfs installed. If you install git-lfs after cloning, simple run `git lfs pull` to download large files. ``` $ npm install ``` ### Local Development ``` $ npm start ``` This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. ### Build ``` $ npm run build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. ### Deployment Using SSH: ``` $ USE_SSH=true npm run deploy ``` Not using SSH: ``` $ GIT_USER= npm run deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. ================================================ FILE: apps/website/assets/ffmpegwasm-arch.drawio ================================================ ================================================ FILE: apps/website/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: apps/website/blog/2023-07-26-release-ffmpeg.wasm-0.12.0.mdx ================================================ --- slug: release-ffmpeg.wasm-0.12.0 title: Release ffmpeg.wasm 0.12.0 authors: [jeromewu] tags: [ffmpeg.wasm] --- Finally, a new start. :tada: I started to work on ffmpeg.wasm around Oct. 2019, it was the time WebAssembly is still in very early stage and transpiling FFmpeg to ffmpeg.wasm was very challenging. (but possible as there was a pioneer project [ffmpeg.js](https://github.com/Kagami/ffmpeg.js/)) Over these years, I have been trying to make ffmpeg.wasm to be production-grade, but failed as FFmpeg is such a powerful framework and WebAssembly is still evolving. It wasn't easy to support even one requirements (ex. RTSP) from the community and I found it really hard to continue sometimes. But with almost **11k stars**, **600 forks**, **4.1k used-by projects** and **2.3M downloads** in total, I still believe ffmpeg.wasm is a meaningful project to continue. That's why I decided to pause a little while and rethink what should be done in the next stage. As the result, I started to work on ffmpeg.wasm 0.12.0, it is a major version and not backward compatible with 0.11.0. Massive issues are fixed in this version, including: - Upgrade Emscripten to 3.1.40 - Upgrade FFmpeg to n5.1.3 - Docker build with cache (reduce time to build a new version) - Support timeout(), terminate() and other fundamental operations - Split libraries to multiple small libraries - Rewrite the whole library with TypeScript - Merge multiple repositories into one monorepo - Offical Web Worker support - Offical single thread and multi thread version support - Enhanced documentation - Official domain name Lots of features are still in the backlog and it requires lots effort to even just compelete one of them, such as: - Speed up ffmpeg.wasm using WebAssembly intrinsic - Support RTSP protocol - Support more popular libraries I believe there is still a long way to go for ffmpeg.wasm to really become a production-grade library, but it is defintely a gamechanger and a chance to see the potential of WebAssembly and web technologies in general. That's why I am working on it and welcome to join us. Hope you enjoy this release :smile: \- Jerome ================================================ FILE: apps/website/blog/authors.yml ================================================ jeromewu: name: Jerome Wu title: Maintainer of ffmpeg.wasm url: https://github.com/jeromewu image_url: /img/jeromewu.png ================================================ FILE: apps/website/deploy.sh ================================================ #!/bin/bash npm install npm run build ================================================ FILE: apps/website/docs/contribution/core.md ================================================ # @ffmpeg/core To build @ffmpeg/core, make sure your docker is version 23.0+ as [buildx](https://docs.docker.com/build/architecture/) is adopted. Also You will need to install `make` to run build scripts. ## Build Dev Build (single thread): ```bash $ make dev ``` Dev Build (multithread): ```bash $ make dev-mt ``` Prodution Build (single thread): ```bash $ make prd ``` Prodution Build (multithread): ```bash $ make prd-mt ``` > Each build might take around 1 hour depends on the spec of your machine, > subsequent builds are faster as most layers are cached. The output file locates at **/packages/core** or **/packages/core-mt**. ## Custom Build / Reduce Build Size You can customize your build to include only the libraries you need, which can significantly reduce the final build size. This is done by modifying the `Dockerfile`. --- ### Step-by-Step Example: Removing WebP Support Here's how to remove `libwebp` (WebP image support) from the build. The same principle applies to other libraries. You need to make changes in **4 places** in the `Dockerfile`: #### 1. Remove the library builder stage Find the stage that builds the library you want to remove and delete the entire block: ```dockerfile # Remove this entire block 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 ``` #### 2. Remove the COPY instruction In the `ffmpeg-base` stage, remove the line that copies the built library: ```dockerfile # Remove this line COPY --from=libwebp-builder $INSTALL_DIR $INSTALL_DIR ``` #### 3. Remove the configure flag In the `ffmpeg-builder` stage, remove the corresponding `--enable-lib...` flag: ```dockerfile # Remove this line from the ffmpeg-builder stage --enable-libwebp \ ``` #### 4. Remove the linker flags In the `ffmpeg-wasm-builder` stage, remove the library from `FFMPEG_LIBS`: ```dockerfile # Remove these lines from FFMPEG_LIBS -lwebpmux \ -lwebp \ -lsharpyuv \ ``` > **💡 Pro Tip:** Start by removing just the main library flag (e.g., `-lwebp`). If the build fails with "undefined reference" errors, those errors will tell you exactly which additional libraries to remove. #### 5. Build and test ```bash # Run the build command make prd # Output will be in packages/core/dist/ ``` --- **Additional Build Size Optimization:** You can sometimes play around with `build/ffmpeg-wasm.sh` and `build/ffmpeg.sh` to disable things you are not using to make the size smaller. ### More Advance Customization Example: Creating a Minimal Build For more advanced customization, you might want to create a minimal build that only includes the features you need. A good example is creating a build that can create a video from a sequence of images (e.g., from an HTML canvas), handle MP4 encoding/decoding, and support audio. A community member, @Kaizodo, shared an approach that resulted in a build size of only 4.80MB. You can find the full details and a discussion in [GitHub Issue #866](https://github.com/ffmpegwasm/ffmpeg.wasm/issues/866). The general strategy is to: 1. **Start with a minimal configuration:** Instead of removing libraries one by one, a more effective approach is to start with a minimal ffmpeg configuration. This can be achieved by using flags like `--disable-everything` in the ffmpeg configuration step within the `Dockerfile`. 2. **Enable specific components:** After disabling everything, you can selectively enable only the encoders, decoders, muxers, demuxers, and protocols you need for your specific use case. For example: `--enable-encoder=libx264`, `--enable-decoder=png`, `--enable-muxer=mp4`, etc. 3. **Include only necessary libraries:** Make sure your `Dockerfile` only builds and links the external libraries that correspond to the features you enabled (e.g., `libx264`). You can remove the build stages for any other libraries. This approach gives you control over the build content and its final size. We would like to thank @Kaizodo, @harkdawg and other community members for sharing their knowledge! ## Publish Simply run `npm publish` under **packages/core** or **/packages/core-mt**. ================================================ FILE: apps/website/docs/contribution/ffmpeg.md ================================================ # @ffmpeg/ffmpeg The source code of @ffmpeg/ffmpeg locates at **/packages/ffmpeg**. ## Development ```bash $ npm run dev ``` ## Build Transpile Typescript to JavaScript. ```bash $ npm run build ``` ## Lint ```bash $ npm run lint ``` ## Publish Simply run `npm publish` under **packages/ffmpeg**. ================================================ FILE: apps/website/docs/contribution/util.md ================================================ # @ffmpeg/util The source code of @ffmpeg/util locates at **/packages/util**. ## Development ```bash $ npm run dev ``` ## Build Transpile Typescript to JavaScript. ```bash $ npm run build ``` ## Lint ```bash $ npm run lint ``` ## Publish Simply run `npm publish` under **packages/util**. ================================================ FILE: apps/website/docs/faq.md ================================================ # FAQ ### Why ffmpeg.wasm doesn't support nodejs? ffmpeg.wasm did support nodejs before 0.12.0, but decided to discontinue nodejs support due to: - It takes extra effort to maintain nodejs support - If you are not in browser, there are a lot of better choices than using WebAssembly for a better performance, ex: - nodejs: https://www.npmjs.com/package/fluent-ffmpeg - react-native: https://github.com/arthenica/ffmpeg-kit Of course, it is still highly welcome to contribute a nodejs version of ffmpeg.wasm. ### Why ffmpeg.wasm is so slow comparing to ffmpeg? As of now, WebAssembly is still a lot slower than native, it is possible to further speed up using WebAssembly intrinsic, which is basically writing assembly code. It is something we are investigating and hope to introduce in the future. If you are OK with more unstable version of ffmpeg.wasm, using ffmpeg.wasm multithread (mt) version can have around 2x speed comparing to single thread (but consume a lot more memory and cpu) ### Is RTSP supported by ffmpeg.wasm? We are trying to support, but so far WebAssembly itself lack of features like sockets which makes it hard to implement RTSP protocol. Possible workarounds are still under investigation. ### What is the license of ffmpeg.wasm? There are two components inside ffmpeg.wasm: - @ffmpeg/ffmpeg (https://github.com/ffmpegwasm/ffmpeg.wasm/packages/ffmpeg) - @ffmpeg/core (https://github.com/ffmpegwasm/ffmpeg.wasm/packages/core) @ffmpeg/core contains WebAssembly code which is transpiled from original FFmpeg C code with minor modifications, but overall it still following the same licenses as FFmpeg and its external libraries (as each external libraries might have its own license). @ffmpeg/ffmpeg contains kind of a wrapper to handle the complexity of loading core and calling low-level APIs. It is a small code base and under MIT license. ### What is the maximum size of input file? 2 GB, which is a hard limit in WebAssembly. Might become 4 GB in the future. ### How can I build my own ffmpeg.wasm? In fact, it is `@ffmpeg/core` most people would like to build. To build on your own, you can check [Contribution Guide](/docs/contribution/core) Also you can check this series of posts to learn more fundamental concepts (OUTDATED, but still good to learn foundations): - https://jeromewu.github.io/build-ffmpeg-webassembly-version-part-1-preparation/ - https://jeromewu.github.io/build-ffmpeg-webassembly-version-part-2-compile-with-emscripten/ - https://jeromewu.github.io/build-ffmpeg-webassembly-version-part-3-v0.1/ - https://jeromewu.github.io/build-ffmpeg-webassembly-version-part-4-v0.2/ ================================================ FILE: apps/website/docs/getting-started/examples.md ================================================ import Grid from '@mui/material/Unstable_Grid2'; import MuiThemeProvider from "@site/src/components/common/MuiThemeProvider"; import ExampleCard from "@site/src/components/common/ExampleCard"; # Examples You can find how to use ffmpeg.wasm with frameworks here. :smile: :::caution Do remember to run `npm run build` in the root of the repository before trying any of the examples. ::: ================================================ FILE: apps/website/docs/getting-started/installation.md ================================================ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Installation :::note ffmpeg.wasm only supports running in browser, see [FAQ](/docs/faq) for more details ::: ## Package Managers Install ffmpeg.wasm using package managers like npm and yarn: ```bash npm install @ffmpeg/ffmpeg @ffmpeg/util ``` ```bash yarn add @ffmpeg/ffmpeg @ffmpeg/util ``` :::info As `@ffmpeg/ffmpeg` spawns a web worker, you cannot import `@ffmpeg/ffmpeg` from CDN like jsdelivr. It is recommended to download it and host it on your server most of the time. ::: ================================================ FILE: apps/website/docs/getting-started/usage.md ================================================ # Usage Learn the basics of using ffmpeg.wasm. :::note It is recommended to read [Overview](/docs/overview) first. ::: ## Transcode webm to mp4 video :::caution If you are a [vite](https://vitejs.dev/) user, use `esm` in **baseURL** instead of `umd`: ~~https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/umd~~ => https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/esm ::: ```jsx live // import { FFmpeg } from '@ffmpeg/ffmpeg'; // import { fetchFile, toBlobURL } 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://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/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.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'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

Open Developer Tools (Ctrl+Shift+I) to View Logs

) : ( ) ); } ``` ## Transcode webm to mp4 video (multi-thread) :::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). ::: ```jsx live // import { FFmpeg } from '@ffmpeg/ffmpeg'; // import { fetchFile, toBlobURL } 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://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.10/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'), workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript'), }); setLoaded(true); } 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'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

Open Developer Tools (Ctrl+Shift+I) to View Logs

) : ( ) ); } ``` ## Transcode video with timeout ```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://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/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.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm')); // The exec should stop after 1 second. await ffmpeg.exec(['-i', 'input.webm', 'output.mp4'], 1000); const data = await ffmpeg.readFile('output.mp4'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

Open Developer Tools (Ctrl+Shift+I) to View Logs

) : ( ) ); } ``` ## 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 // 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://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/umd' const ffmpeg = ffmpegRef.current; // Listen to progress event instead of log. ffmpeg.on('progress', ({ progress, time }) => { messageRef.current.innerHTML = `${progress * 100} % (transcoded time: ${time / 1000000} s)`; }); // 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.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'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

) : ( ) ); } ``` ## Split video into segments of equal duration ```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://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/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.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm')); await ffmpeg.exec([ '-i', 'input.webm', '-f', 'segment', '-segment_time', '3', '-g', '9', '-sc_threshold', '0', '-force_key_frames', 'expr:gte(t,n_forced*9)', '-reset_timestamps', '1', '-map', '0', 'output_%d.mp4' ]); const data = await ffmpeg.readFile('output_1.mp4'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

Open Developer Tools (Ctrl+Shift+I) to View Logs

) : ( ) ); } ``` ## Display Text on the video ```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://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/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.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm')); await ffmpeg.writeFile('arial.ttf', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/arial.ttf')); await ffmpeg.exec([ '-i', 'input.webm', '-vf', 'drawtext=fontfile=/arial.ttf:text=\'ffmpeg.wasm\':x=10:y=10:fontsize=24:fontcolor=white', 'output.mp4', ]); const data = await ffmpeg.readFile('output.mp4'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

Open Developer Tools (Ctrl+Shift+I) to View Logs

) : ( ) ); } ``` ## Interlace 2 Videos ```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://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/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.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm')); await ffmpeg.writeFile('reversed.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s_reversed.webm')); await ffmpeg.exec([ '-i', 'input.webm', '-i', 'reversed.webm', '-filter_complex', '[0:v][1:v]blend=all_expr=\'A*(if(eq(0,N/2),1,T))+B*(if(eq(0,N/2),T,1))\'', 'output.mp4', ]); const data = await ffmpeg.readFile('output.mp4'); videoRef.current.src = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'})); } return (loaded ? ( <>

Open Developer Tools (Ctrl+Shift+I) to View Logs

) : ( ) ); } ``` ## Use WORKERFS :::note Required: - @ffmpeg/ffmpeg@0.12.10+ - @ffmpeg/core@0.12.4+ ::: Please Check this PR: [Add WORKERFS support](https://github.com/ffmpegwasm/ffmpeg.wasm/pull/581) ## Abort exec() with signal :::note Required: - @ffmpeg/ffmpeg@0.12.10+ - @ffmpeg/core@0.12.4+ ::: Please check this PR: [abort signal](https://github.com/ffmpegwasm/ffmpeg.wasm/pull/573) ================================================ FILE: apps/website/docs/migration.md ================================================ import MuiThemeProvider from "@site/src/components/common/MuiThemeProvider"; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; # Migrating from 0.11.x to 0.12+ As 0.12+ is not backward compatible with 0.11.x, below is a quick mapping table to transform 0.11.x to 0.12+ 0.11.x 0.12+ Note {[ {"0.11.x": "import { createFFmpeg } from '@ffmpeg/ffmpeg'", "0.12+": "import { FFmpeg } from '@ffmpeg/ffmpeg'", note: ""}, {"0.11.x": "createFFmpeg()", "0.12+": "new FFmpeg()", note: "argumens of createFFmpeg() is moved to ffmpeg.load()"}, {"0.11.x": "await ffmpeg.load()", "0.12+": "await ffmpeg.load()", note: ""}, {"0.11.x": "await ffmpeg.run(...args)", "0.12+": "await ffmpeg.exec([...args])", note: ""}, {"0.11.x": "ffmpeg.FS.writeFile()", "0.12+": "await ffmpeg.writeFile()", note: ""}, {"0.11.x": "ffmpeg.FS.readFile()", "0.12+": "await ffmpeg.readFile()", note: ""}, {"0.11.x": "ffmpeg.exit()", "0.12+": "ffmpeg.terminate()", note: ""}, {"0.11.x": "ffmpeg.setLogger()", "0.12+": "ffmpeg.on(\"log\", () => {})", note: ""}, {"0.11.x": "ffmpeg.setProgress()", "0.12+": "ffmpeg.on(\"progress\", () => {})", note: ""}, {"0.11.x": "import { fetchFile } from '@ffmpeg/ffmpeg'", "0.12+": "import { fetchFile } from '@ffmpeg/util'", note: ""}, ].map((row) => ( {row['0.11.x']} {row['0.12+']} {row.note} ))}
================================================ FILE: apps/website/docs/overview.md ================================================ import MuiThemeProvider from "@site/src/components/common/MuiThemeProvider"; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; # Overview :::info For 0.11.x, visit [https://ffmpegwasm-0-11-x.netlify.app](https://ffmpegwasm-0-11-x.netlify.app) and [0.11.x](https://github.com/ffmpegwasm/ffmpeg.wasm/tree/0.11.x) branch. ::: ## Introduction ffmpeg.wasm is a pure WebAssembly / JavaScript port of [FFmpeg](https://www.ffmpeg.org/) enabling video & audio record, convert and stream right inside browsers. We leverage [Emscripten](https://emscripten.org/) to transpile FFmpeg source code and many libraries to WebAssembly and develop a minimal but essential library to free developers from common requirements like running ffmpeg inside web worker and more. ## Advantages - **Security**: your users' data only lives inside their browser, no need to worry about any data leakage or network latency. - **Client-side computing**: instead of hosting a cluster of server-end servers, you can now offload multimedia processing to client-side. - **Flexible**: ffmpeg.wasm comes with single-thread and multi-thread cores, you can use whichever fits your use case. ## Architecture ![architecture](/img/ffmpegwasm-arch.png) Multimedia transcoding is a resource-intensive task that you don't want to execute in main thread, thus in ffmpeg.wasm we offload those task to web worker (`ffmpeg.worker`) by default. This makes almost all function calls in ffmpeg.wasm are asynchronous and it is recommended to use **async** / **await** syntax. `ffmpeg.worker` downloads WebAssembly code (`ffmpeg-core`) from CDN and initialized it in WorkerGlobalScope. For any input video file you would like to process, you need to first populated them inside ffmpeg-core File System and also read result from `ffmpeg-core` File System once it is done. If you are using a multi-thread version of `ffmpeg-core`, more web workers will be spawned by `ffmpeg-core` inside `ffmpeg.worker` :::info The concept of `core` in ffmpeg.wasm is like the engine of a car, it is not only the most important part of ffmpeg.wasm but also a swappable component. Currently we maintain single-thread (`@ffmpeg/core`) and multi-thread version (`@ffmpeg/core-mt`) cores, you can build your own core (ex. a core with x264 lib only to minimize ffmpeg-core.wasm file size) using build scripts in the repository. ::: ## Packages All ffmpeg.wasm packages are under [@ffmpeg](https://www.npmjs.com/search?q=%40ffmpeg) name space: | Name | Usage | | ---- | ----- | | @ffmpeg/ffmpeg | ffmpeg.wasm main package | | @ffmpeg/util | common utility functions | | @ffmpeg/types | TypeScript types | | @ffmpeg/core | single-thread ffmpeg.wasm core | | @ffmpeg/core-mt | multi-thread ffmpeg.wasm core | ## Libraries ffmpeg.wasm is built with toolchains / libraries: Name Version Note {[ {name: "Emscripten", version: "3.1.40", note: "Emscripten is a toolchain for compiling C and C++ code into WebAssembly and JavaScript, making it possible to run applications written in these languages in web browsers."}, {name: "FFmpeg", version: "n5.1.4", note: "FFmpeg is a powerful multimedia framework that can decode, encode, transcode, and stream audio and video files. It's widely used for media manipulation and streaming."}, {name: "x264", version: "0.164.x", note: "x264 is a popular video encoding library that provides high-quality H.264 video compression. It's commonly used for video encoding and transcoding."}, {name: "x265", version: "3.4", note: "x265 is a video encoding library that specializes in encoding videos using the H.265/HEVC codec, offering high compression efficiency for video content."}, {name: "libvpx", version: "v1.13.1", note: "libvpx is an open-source video codec library used for encoding and decoding VP8 and VP9 video formats, commonly used for web-based video streaming."}, {name: "lame", version: "3.100", note: "LAME is an audio encoder that converts audio files to the MP3 format, making it widely used for creating MP3 audio files."}, {name: "ogg", version: "v1.3.4", note: "Ogg is a multimedia container format, and this library provides support for encoding and decoding audio and video in the Ogg format."}, {name: "theora", version: "v1.1.1", note: "Theora is an open video codec designed for efficient video compression within the Ogg multimedia framework."}, {name: "opus", version: "v1.3.1", note: "Opus is a versatile audio codec capable of handling both voice and music with low latency and high-quality compression."}, {name: "vorbis", version: "v1.3.3", note: "Vorbis is an open-source audio codec known for its high audio quality and efficient compression. It's often used for audio streaming."}, {name: "zlib", version: "v1.2.11", note: "zlib is a compression library that provides data compression and decompression functionality, commonly used in file compression formats like gzip."}, {name: "libwebp", version: "v1.3.2", note: "libwebp is a library for working with the WebP image format, offering efficient image compression for web use"}, {name: "freetype2", version: "v2.10.4", note: "FreeType 2 is a library for rendering fonts. It is commonly used for text rendering in applications and systems."}, {name: "fribidi", version: "v1.0.9", note: "FriBidi is a library for handling bidirectional text (text containing both left-to-right and right-to-left scripts) and is often used in text layout and rendering."}, {name: "harfbuzz", version: "5.2.0", note: "HarfBuzz is a text shaping engine that allows complex script text to be rendered correctly. It's used in conjunction with font rendering libraries."}, {name: "libass", version: "0.15.0", note: "libass is a library for rendering and formatting subtitles in multimedia applications, making it essential for displaying subtitles alongside video content."}, {name: "zimg", version: "3.0.5", note: "zimg implements the commonly required image processing basics of scaling, colorspace conversion, and depth conversion."} ].map((row) => ( {row.name} {row.version} {row.note} ))}
================================================ FILE: apps/website/docs/performance.md ================================================ # Performance ffmpeg.wasm uses transpiled FFmpeg C source code to WebAssembly code, it is for certain that ffmpeg.wasm won't perform as good as FFmpeg as it is not fully optimized at the moment. (Even in ffmpeg.wasm multithread version). In this section we provide a short comparison, so that you can make decision based on your needs: ## Environment - CPU: 8 × 11th Gen Intel® Core™ i5-1135G7 @ 2.40GHz - Memory: 15.6 GiB of RAM - OS: Manjaro Linux 6.1.44-1-MANJARO (64-bit) - Browser: Google Chrome Version 116.0.5845.96 (Official Build) (64-bit) - FFmpeg: n5.1.2 ## Comparison Setup: - Each command is executed 5 times. - Only `ffmpeg.exec()` time is measured. - Candidates - FFmpeg: [native FFmpeg](https://hub.docker.com/r/linuxserver/ffmpeg), considered as baseline. - core: ffmpeg.wasm single thread version. - core-mt: ffmpeg.wasm multi thread version. ### $ ffmpeg -i [input.webm](https://test-videos.co.uk/vids/bigbuckbunny/webm/vp8/720/Big_Buck_Bunny_720_10s_1MB.webm) output.mp4 | # | FFmpeg | core v0.12.3 | core-mt v0.12.3 | | --- | ------ | ------------ | --------------- | | Avg | 5.2 sec | 128.8 sec (0.04x) | 60.4 sec (0.08x) | | Max | 5.3 sec | 130.7 sec | 63.9 sec | | Min | 5.1 sec | 126.6 sec | 59 sec | ================================================ FILE: apps/website/docs/privacy-policy.md ================================================ # Privacy Policy

Last updated: August 05, 2023

This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.

We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the help of the Free Privacy Policy Generator.

Interpretation and Definitions

Interpretation

The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.

Definitions

For the purposes of this Privacy Policy:

  • Account means a unique account created for You to access our Service or parts of our Service.

  • Affiliate means an entity that controls, is controlled by or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.

  • Company (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to ffmpeg.wasm.

  • Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.

  • Country refers to: Singapore

  • Device means any device that can access the Service such as a computer, a cellphone or a digital tablet.

  • Personal Data is any information that relates to an identified or identifiable individual.

  • Service refers to the Website.

  • Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.

  • Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).

  • Website refers to ffmpeg.wasm, accessible from https://ffmpegwasm.netlify.app/

  • You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.

Collecting and Using Your Personal Data

Types of Data Collected

Personal Data

While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:

  • Usage Data

Usage Data

Usage Data is collected automatically when using the Service.

Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.

We may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device.

Tracking Technologies and Cookies

We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies used are beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:

  • Cookies or Browser Cookies. A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service. Unless you have adjusted Your browser setting so that it will refuse Cookies, our Service may use Cookies.
  • Web Beacons. Certain sections of our Service and our emails may contain small electronic files known as web beacons (also referred to as clear gifs, pixel tags, and single-pixel gifs) that permit the Company, for example, to count users who have visited those pages or opened an email and for other related website statistics (for example, recording the popularity of a certain section and verifying system and server integrity).

Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser. Learn more about cookies on the Free Privacy Policy website article.

We use both Session and Persistent Cookies for the purposes set out below:

  • Necessary / Essential Cookies

    Type: Session Cookies

    Administered by: Us

    Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.

  • Cookies Policy / Notice Acceptance Cookies

    Type: Persistent Cookies

    Administered by: Us

    Purpose: These Cookies identify if users have accepted the use of cookies on the Website.

  • Functionality Cookies

    Type: Persistent Cookies

    Administered by: Us

    Purpose: These Cookies allow us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.

For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of our Privacy Policy.

Use of Your Personal Data

The Company may use Personal Data for the following purposes:

  • To provide and maintain our Service, including to monitor the usage of our Service.

  • To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.

  • For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.

  • To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.

  • To provide You with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless You have opted not to receive such information.

  • To manage Your requests: To attend and manage Your requests to Us.

  • For business transfers: We may use Your information to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.

  • For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.

We may share Your personal information in the following situations:

  • With Service Providers: We may share Your personal information with Service Providers to monitor and analyze the use of our Service, to contact You.
  • For business transfers: We may share or transfer Your personal information in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
  • With Affiliates: We may share Your information with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
  • With business partners: We may share Your information with Our business partners to offer You certain products, services or promotions.
  • With other users: when You share personal information or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
  • With Your consent: We may disclose Your personal information for any other purpose with Your consent.

Retention of Your Personal Data

The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.

The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods.

Transfer of Your Personal Data

Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.

Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer.

The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.

Delete Your Personal Data

You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.

Our Service may give You the ability to delete certain information about You from within the Service.

You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us.

Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.

Disclosure of Your Personal Data

Business Transactions

If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.

Law enforcement

Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

Other legal requirements

The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:

  • Comply with a legal obligation
  • Protect and defend the rights or property of the Company
  • Prevent or investigate possible wrongdoing in connection with the Service
  • Protect the personal safety of Users of the Service or the public
  • Protect against legal liability

Security of Your Personal Data

The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security.

Children's Privacy

Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers.

If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.

Links to Other Websites

Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.

We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

Changes to this Privacy Policy

We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.

We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.

You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

Contact Us

If you have any questions about this Privacy Policy, You can contact us:

  • By email: jeromewus@gmail.com
================================================ FILE: apps/website/docusaurus.config.js ================================================ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion const lightCodeTheme = require("prism-react-renderer/themes/github"); const darkCodeTheme = require("prism-react-renderer/themes/dracula"); /** @type {import('@docusaurus/types').Config} */ const config = { title: "ffmpeg.wasm", tagline: "ffmpeg.wasm is a pure WebAssembly / JavaScript port of FFmpeg enabling video & audio record, convert and stream right inside browsers!", url: "https://ffmpegwasm.netlify.app", baseUrl: "/", onBrokenLinks: "throw", onBrokenMarkdownLinks: "warn", favicon: "img/favicon.ico", // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. organizationName: "ffmpegwasm", // Usually your GitHub org/user name. projectName: "ffmpeg.wasm", // Usually your repo name. // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { defaultLocale: "en", locales: ["en"], }, presets: [ [ "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { sidebarPath: require.resolve("./sidebars.js"), // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: "https://github.com/ffmpegwasm/ffmpeg.wasm/tree/main/apps/website", }, blog: { showReadingTime: true, // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: "https://github.com/ffmpegwasm/ffmpeg.wasm/tree/main/apps/website", }, theme: { customCss: [ require.resolve("./src/css/custom.css"), require.resolve("@fontsource/roboto/300.css"), require.resolve("@fontsource/roboto/400.css"), require.resolve("@fontsource/roboto/500.css"), require.resolve("@fontsource/roboto/700.css"), ], }, gtag: { trackingID: "G-8NBTQ7N6RB", anonymizeIP: true, }, }), ], ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ navbar: { title: "ffmpeg.wasm", logo: { alt: "ffmpeg.wasm Logo", src: "img/logo192.png", }, items: [ { type: "doc", docId: "overview", position: "left", label: "Docs", }, { to: "/playground", label: "Playground", position: "left" }, { to: "/blog", label: "Blog", position: "left" }, { href: "https://github.com/ffmpegwasm/ffmpeg.wasm", label: "GitHub", position: "right", }, ], }, footer: { style: "dark", links: [ { title: "Docs", items: [ { label: "Tutorial", to: "/docs/overview", }, ], }, { title: "Community", items: [ { label: "Stack Overflow", href: "https://stackoverflow.com/questions/tagged/ffmpeg.wasm", }, { label: "Discord", href: "https://discord.gg/NjGMaqqfm5", }, ], }, { title: "More", items: [ { label: "Blog", to: "/blog", }, { label: "GitHub", href: "https://github.com/ffmpegwasm/ffmpeg.wasm", }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} ffmpeg.wasm, Inc. Built with Docusaurus.`, }, prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, additionalLanguages: ['docker'], }, }), plugins: [ [require.resolve('@easyops-cn/docusaurus-search-local'), { indexDocs: true, indexBlog: true, hashed: true, }], [ "docusaurus-plugin-typedoc", { id: "ffmpeg", entryPoints: ["../../packages/ffmpeg/src/index.ts"], tsconfig: "../../packages/ffmpeg/tsconfig.json", readme: "none", out: "api/ffmpeg", sidebar: { indexLabel: "@ffmpeg/ffmpeg", fullNames: true, }, }, ], [ "docusaurus-plugin-typedoc", { id: "util", entryPoints: ["../../packages/util/src/index.ts"], tsconfig: "../../packages/util/tsconfig.json", readme: "none", out: "api/util", sidebar: { indexLabel: "@ffmpeg/util", fullNames: true, }, }, ], ], themes: ["@docusaurus/theme-live-codeblock"], scripts: [ { src: "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8688083214014126", async: true, crossorigin: "anonymous", }, ], }; module.exports = config; ================================================ FILE: apps/website/netlify.toml ================================================ [[headers]] for = "/*" [headers.values] Cross-Origin-Opener-Policy = "same-origin" Cross-Origin-Embedder-Policy = "require-corp" ================================================ FILE: apps/website/package.json ================================================ { "name": "website", "version": "0.12.0-alpha.0", "private": true, "scripts": { "docusaurus": "docusaurus", "build:packages": "cd ../.. && npm run build", "clean": "rm -rf docs/api", "prestart": "npm run clean && npm run build:packages", "start": "docusaurus start", "prebuild": "npm run clean && npm run build:packages", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "typecheck": "tsc" }, "dependencies": { "@docusaurus/core": "^2.4.1", "@docusaurus/preset-classic": "^2.4.1", "@docusaurus/theme-live-codeblock": "^2.4.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", "@ffmpeg/ffmpeg": "*", "@ffmpeg/util": "*", "@fontsource/roboto": "^4.5.8", "@mdx-js/react": "^1.6.22", "@mui/icons-material": "^5.10.6", "@mui/material": "^5.10.8", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", "react-ace": "^10.1.0", "react-dom": "^17.0.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "^2.2.0", "@easyops-cn/docusaurus-search-local": "^0.34.0", "@tsconfig/docusaurus": "^1.0.5", "@types/ace": "^0.0.48", "docusaurus-plugin-typedoc": "^0.17.5", "typedoc": "^0.23.15", "typedoc-plugin-markdown": "^3.13.6", "typescript": "^4.7.4" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "engines": { "node": ">=18.17" } } ================================================ FILE: apps/website/sidebars.js ================================================ /** * Creating a sidebar enables you to: - create an ordered group of docs - render a sidebar for each doc of that group - provide next/previous navigation The sidebars can be generated from the filesystem, or explicitly defined here. Create as many sidebars as you want. */ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure // tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], // But you can create a sidebar manually tutorialSidebar: [ "overview", { type: "category", label: "Getting Started", items: [ "getting-started/installation", "getting-started/usage", "getting-started/examples", ], }, "performance", "migration", "faq", { type: "category", label: "API", items: ["api/ffmpeg/index", "api/util/index"], }, { type: "category", label: "Contribution", items: ["contribution/core", "contribution/ffmpeg", "contribution/util"], }, { type: "doc", label: "Privacy Policy", id: "privacy-policy", }, ], }; module.exports = sidebars; ================================================ FILE: apps/website/src/components/ExternalLibraries/index.tsx ================================================ import React from "react"; import clsx from "clsx"; import styles from "./styles.module.css"; interface LibraryItem { title: string; desc: string; img: string; isBlackBackground?: boolean; } const libs: LibraryItem[] = [ { title: "x264", desc: "H.264 Codec", img: require("@site/static/img/libs/x264.png").default, isBlackBackground: true, }, { title: "x265", desc: "H.265 codec", img: require("@site/static/img/libs/x265.webp").default, }, { title: "libvpx", desc: "VP8/VP9 codec", img: require("@site/static/img/libs/libvpx.png").default, }, { title: "theora", desc: "OGV codec", img: require("@site/static/img/libs/theora.png").default, }, { title: "lame", desc: "MP3 codec", img: require("@site/static/img/libs/lame.gif").default, }, { title: "vorbis", desc: "OGG codec", img: require("@site/static/img/libs/vorbis.png").default, }, { title: "opus", desc: "OPUS codec", img: require("@site/static/img/libs/opus.png").default, }, { title: "freetype2", desc: "Font file renderer", img: require("@site/static/img/libs/freetype.png").default, }, { title: "libass", desc: "subtitle renderer", img: require("@site/static/img/libs/freetype.png").default, }, { title: "libwebp", desc: "WEBP codec", img: require("@site/static/img/libs/webp.png").default, }, ]; function Library({ title, desc, img, isBlackBackground = false }: LibraryItem) { return (

{title}

{desc}

); } export default function ExternalLibraries(): JSX.Element { return (

External Libraries

{" "} ffmpeg.wasm is built with common external libraries, and more of libraries to be added!

{libs.map((props, idx) => ( ))}
); } ================================================ FILE: apps/website/src/components/ExternalLibraries/styles.module.css ================================================ .libraries { display: flex; align-items: center; padding: 2rem 0; width: 100%; background-color: rgba(229,231,235, 1); color: #1b1b1d; } .libraryImg { height: 96px; padding: 16px; } .blackBackground { background-color: #1b1b1d; } ================================================ FILE: apps/website/src/components/HomepageFeatures/index.tsx ================================================ import React from "react"; import clsx from "clsx"; import styles from "./styles.module.css"; type FeatureItem = { title: string; Svg: React.ComponentType>; description: JSX.Element; }; const FeatureList: FeatureItem[] = [ { title: "Data Security", Svg: require("@site/static/img/safety-icon.svg").default, description: ( <> ffmpeg.wasm runs only inside your browser, data security is guaranteed as no data is sent to remote server. ), }, { title: "Powered by WebAssembly", Svg: require("@site/static/img/wasm-logo.svg").default, description: ( <> ffmpeg.wasm transpiles ffmpeg source code to WebAssembly code using Emscripten to achieve optimal performance. ), }, { title: "Made with TypeScript", Svg: require("@site/static/img/ts-logo-round-512.svg").default, description: ( <> ffmpeg.wasm is written in TypeScript to provide great developer experience (DX). ), }, ]; function Feature({ title, Svg, description }: FeatureItem) { return (

{title}

{description}

); } export default function HomepageFeatures(): JSX.Element { return (
{FeatureList.map((props, idx) => ( ))}
); } ================================================ FILE: apps/website/src/components/HomepageFeatures/styles.module.css ================================================ .features { display: flex; align-items: center; padding: 2rem 0; width: 100%; } .featureSvg { height: 200px; width: 200px; } ================================================ FILE: apps/website/src/components/Playground/CoreDownloader.tsx ================================================ import * as React from "react"; import Typography from "@mui/material/Typography"; import Container from "@mui/material/Container"; import LinearProgressWithLabel from "@site/src/components/common/LinearProgressWithLabel"; import { CORE_SIZE } from "./const"; export default function CoreDownloader({ url, received }) { const total = CORE_SIZE[url]; return ( {`Downloading ${url}`} {`(${received} / ${total} bytes)`} ); } ================================================ FILE: apps/website/src/components/Playground/CoreSwitcher.tsx ================================================ import React from "react"; import Stack from "@mui/material/Stack"; import FormGroup from "@mui/material/FormGroup"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; import IconButton from "@mui/material/IconButton"; import HelpIcon from "@mui/icons-material/HelpOutline"; import Tooltip from "@mui/material/Tooltip"; interface CoreSwitcherProps { checked: boolean; onChange: (event: React.ChangeEvent) => void; } export default function CoreSwitcher({ checked, onChange }: CoreSwitcherProps) { return ( <> } label="Use Multithreading" disabled={typeof SharedArrayBuffer !== "function"} /> ); } ================================================ FILE: apps/website/src/components/Playground/Workspace/Editor.tsx ================================================ /// import React, { useEffect, useState } from "react"; import AceEditor from "react-ace"; import Paper from "@mui/material/Paper"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import LinearProgressWithLabel from "@site/src/components/common/LinearProgressWithLabel"; import { useColorMode } from "@docusaurus/theme-common"; import "ace-builds/src-noconflict/mode-json"; import "ace-builds/src-noconflict/mode-javascript"; import "ace-builds/src-noconflict/mode-text"; import "ace-builds/src-noconflict/theme-dracula"; import "ace-builds/src-noconflict/theme-github"; const genFFmpegText = (args: string) => { let data: any = []; try { data = JSON.parse(args); } catch (e) {} return `// equivalent ffmpeg.wasm API call ffmpeg.exec(${JSON.stringify(data)}); // equivalent ffmpeg command line ffmpeg ${data.join(" ")}`; }; interface EditorProps { args: string; logs: string[]; progress: number; time: number; onArgsUpdate: (args: string) => void; onExec: () => Promise; } export default function Editor({ args = "", logs = [], progress = 0, time = 0, onArgsUpdate, onExec, }: EditorProps) { const { colorMode } = useColorMode(); const [output, setOutput] = useState(); useEffect(() => { // scroll logs to the end. output && output.renderer.scrollToLine(Number.POSITIVE_INFINITY); }, [logs]); const theme = colorMode === "dark" ? "github" : "dracula"; return ( Editor: Edit arguments below to update command: Console Output: setOutput(editor)} /> Transcoding Progress: {time === 0 ? "" : `Time Elapsed: ${(time / 1000).toFixed(2)} s`} ); } ================================================ FILE: apps/website/src/components/Playground/Workspace/FileSystemManager.tsx ================================================ import React, { useState, ChangeEvent } from "react"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import IconButton from "@mui/material/IconButton"; import List from "@mui/material/List"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import Modal from "@mui/material/Modal"; import Paper from "@mui/material/Paper"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; import FolderIcon from "@mui/icons-material/Folder"; import RefreshIcon from "@mui/icons-material/Refresh"; import UploadFileIcon from "@mui/icons-material/UploadFile"; import UploadIcon from "@mui/icons-material/Upload"; import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder"; import MoreButton from "./MoreButton"; import { Node } from "./types"; interface FileSystemManagerProps { path: string; nodes: Node[]; oldName: string; newName: string; renameOpen: boolean; onNewNameChange: () => (event: ChangeEvent) => Promise; onCloseRenameModal: () => () => Promise; onFileUpload: ( isText: boolean ) => (event: ChangeEvent) => Promise; onDirClick: (name: string) => () => Promise; onFileClick: (name: string) => (option: string) => Promise; onDirCreate: (name: string) => () => Promise; onRename: (old_name: string, new_name: string) => () => Promise; onRefresh: () => Promise; onLoadSamples: () => Promise; } const modalStyle = { position: "absolute" as "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", width: 400, bgcolor: "background.paper", p: 4, }; export const options = [ { text: "Rename", key: "rename" }, { text: "Download", key: "download" }, { text: "Download as Text File", key: "download-text" }, { text: "Delete", key: "delete" }, ]; export default function FileSystemManager({ path = "/", nodes = [], oldName = "", newName = "", renameOpen = false, onNewNameChange = () => () => Promise.resolve(), onCloseRenameModal = () => () => Promise.resolve(), onFileUpload = () => () => Promise.resolve(), onFileClick = () => () => Promise.resolve(), onDirClick = () => () => Promise.resolve(), onDirCreate = () => () => Promise.resolve(), onRename = () => () => Promise.resolve(), onRefresh = () => Promise.resolve(), onLoadSamples = () => Promise.resolve(), }: FileSystemManagerProps) { const [newFolderOpen, setNewFolderOpen] = useState(false); const [dirName, setDirName] = useState(""); const handleNewFolderModalOpen = () => setNewFolderOpen(true); const handleNewFolderModalClose = () => setNewFolderOpen(false); return ( <> <> File System: {}} aria-label="upload-text" component="label" size="small" > { setDirName(""); handleNewFolderModalOpen(); }} aria-label="create-a-new-folder" size="small" > {`Path: ${path}`} {nodes.map(({ name, isDir }, index) => isDir ? ( ) : ( } > ) )} Folder Name: setDirName(event.target.value)} /> New Name: ); } ================================================ FILE: apps/website/src/components/Playground/Workspace/MoreButton.tsx ================================================ import * as React from "react"; import IconButton from "@mui/material/IconButton"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import MoreVertIcon from "@mui/icons-material/MoreVert"; const ITEM_HEIGHT = 48; interface Option { key: string; text: string; } interface MoreButtonProps { options: Option[]; onItemClick: (option: string) => any; } export default function MoreButton({ options, onItemClick }: MoreButtonProps) { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const handleItemClick = (option: string) => () => { onItemClick(option); setAnchorEl(null); }; return (
{options.map(({ text, key }) => ( {text} ))}
); } ================================================ FILE: apps/website/src/components/Playground/Workspace/index.tsx ================================================ import React, { ChangeEvent, useState, useEffect, MutableRefObject, } from "react"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { fetchFile } from "@ffmpeg/util"; import { downloadFile } from "@site/src/util"; import { Node } from "./types"; import FileSystemManager from "./FileSystemManager"; import { SAMPLE_FILES } from "../const"; import Editor from "./Editor"; const defaultArgs = JSON.stringify(["-i", "video.webm", "video.mp4"], null, 2); interface WorkspaceProps { ffmpeg: MutableRefObject; } export default function Workspace({ ffmpeg: _ffmpeg }: WorkspaceProps) { const [path, setPath] = useState("/"); const [nodes, setNodes] = useState([]); const [oldName, setOldName] = useState(""); const [newName, setNewName] = useState(""); const [renameOpen, setRenameOpen] = useState(false); const [args, setArgs] = useState(defaultArgs); const [progress, setProgress] = useState(0); const [time, setTime] = useState(0); const [logs, setLogs] = useState([]); const ffmpeg = _ffmpeg.current; const refreshDir = async (curPath: string) => { if (ffmpeg.loaded) { setNodes( (await ffmpeg.listDir(curPath)).filter(({ name }) => name !== ".") ); } }; const onNewNameChange = () => async (event: ChangeEvent) => { setNewName(event.target.value); }; const onCloseRenameModal = () => async () => { setRenameOpen(false); }; const onFileUpload = (isText: boolean) => async ({ target: { files } }: ChangeEvent) => { for (let i = 0; i < files.length; i++) { const file = files[i]; let data: Uint8Array | string = await fetchFile(file); if (isText) data = new TextDecoder().decode(data); await ffmpeg.writeFile(`${path}/${file.name}`, data); } refreshDir(path); }; const onFileClick = (name: string) => async (option: string) => { const fullPath = `${path}/${name}`; switch (option) { case "rename": setOldName(name); setNewName(""); setRenameOpen(true); break; case "download": downloadFile( name, ((await ffmpeg.readFile(fullPath, "binary")) as Uint8Array).buffer ); break; case "download-text": downloadFile(name, await ffmpeg.readFile(fullPath, "utf8")); break; case "delete": await ffmpeg.deleteFile(fullPath); refreshDir(path); break; default: break; } }; const onDirClick = (name: string) => async () => { let nextPath = path; if (path === "/") { if (name !== "..") nextPath = `/${name}`; } else if (name === "..") { const cols = path.split("/"); cols.pop(); nextPath = cols.length === 1 ? "/" : cols.join("/"); } else { nextPath = `${path}/${name}`; } setPath(nextPath); refreshDir(nextPath); }; const onDirCreate = (name: string) => async () => { if (name !== "") { await ffmpeg.createDir(`${path}/${name}`); } refreshDir(path); }; const onRename = (old_name: string, new_name: string) => async () => { if (old_name !== "" && new_name !== "") { await ffmpeg.rename(`${path}/${old_name}`, `${path}/${new_name}`); } setRenameOpen(false); refreshDir(path); }; const onLoadSamples = async () => { for (const name of Object.keys(SAMPLE_FILES)) { await ffmpeg.writeFile(name, await fetchFile(SAMPLE_FILES[name])); } refreshDir(path); }; const onExec = async () => { setProgress(0); setTime(0); const logListener = ({ message }) => { setLogs((_logs) => [..._logs, message]); }; const progListener = ({ progress: prog }) => { setProgress(prog * 100); }; ffmpeg.on("log", logListener); ffmpeg.on("progress", progListener); const start = performance.now(); await ffmpeg.exec(JSON.parse(args)); setTime(performance.now() - start); ffmpeg.off("log", logListener); ffmpeg.off("progress", progListener); refreshDir(path); }; useEffect(() => { refreshDir(path); }, []); return ( refreshDir(path)} /> setArgs(_args)} onExec={onExec} /> ); } ================================================ FILE: apps/website/src/components/Playground/Workspace/types.tsx ================================================ export interface Node { name: string; isDir: boolean; } ================================================ FILE: apps/website/src/components/Playground/const.ts ================================================ export const CORE_VERSION = "0.12.10"; export const CORE_URL = `https://cdn.jsdelivr.net/npm/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`; export const CORE_MT_URL = `https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.js`; export const CORE_SIZE = { [`https://cdn.jsdelivr.net/npm/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`]: 114673, [`https://cdn.jsdelivr.net/npm/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`]: 32129114, [`https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.js`]: 132680, [`https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`]: 32609891, [`https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.worker.js`]: 2915, }; export const SAMPLE_FILES = { "video.webm": "https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm", }; ================================================ FILE: apps/website/src/components/Playground/index.tsx ================================================ import React, { useState, useEffect, useRef } from "react"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { toBlobURL } from "@ffmpeg/util"; import Stack from "@mui/material/Stack"; import MuiThemeProvider from "@site/src/components/common/MuiThemeProvider"; import CoreDownloader from "./CoreDownloader"; import Workspace from "./Workspace"; import { CORE_URL, CORE_MT_URL } from "./const"; import CoreSwitcher from "./CoreSwitcher"; enum State { NOT_LOADED, LOADING, LOADED, } export default function Playground() { const [state, setState] = useState(State.LOADED); const [isCoreMT, setIsCoreMT] = useState(false); const [url, setURL] = useState(""); const [received, setReceived] = useState(0); const ffmpeg = useRef(new FFmpeg()); const load = async (mt: boolean = false) => { setState(State.LOADING); const setProgress = ({ url: _url, received: _received }) => { setURL(_url as string); setReceived(_received); }; const coreURL = await toBlobURL( mt ? CORE_MT_URL : CORE_URL, "text/javascript", true, setProgress ); const wasmURL = await toBlobURL( mt ? CORE_MT_URL.replace(/.js$/g, ".wasm") : CORE_URL.replace(/.js$/g, ".wasm"), "application/wasm", true, setProgress ); const workerURL = mt ? await toBlobURL( CORE_MT_URL.replace(/.js$/g, ".worker.js"), "text/javascript", true, setProgress ) : ""; ffmpeg.current.terminate(); await ffmpeg.current.load({ coreURL, wasmURL, workerURL, }); setState(State.LOADED); }; useEffect(() => { load(isCoreMT); }, []); return ( { setIsCoreMT(evt.target.checked); load(evt.target.checked); }} /> {(() => { switch (state) { case State.LOADING: return ; case State.LOADED: return ; default: return <>; } })()} ); } ================================================ FILE: apps/website/src/components/common/ExampleCard.tsx ================================================ import * as React from "react"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; import CardMedia from "@mui/material/CardMedia"; import CardActions from "@mui/material/CardActions"; import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; export default function ActionAreaCard({ img, title, desc, url }) { return ( {title} {desc} ); } ================================================ FILE: apps/website/src/components/common/LinearProgressWithLabel.tsx ================================================ import * as React from "react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import LinearProgress, { LinearProgressProps, } from "@mui/material/LinearProgress"; export default function LinearProgressWithLabel( props: LinearProgressProps & { value: number } ) { return ( {`${Math.round( props.value )}%`} ); } ================================================ FILE: apps/website/src/components/common/MuiThemeProvider/index.tsx ================================================ import React from "react"; import { useColorMode } from "@docusaurus/theme-common"; import { ThemeProvider, createTheme } from "@mui/material/styles"; const lightTheme = createTheme({}); const darkTheme = createTheme({ palette: { mode: "dark", }, }); export default function MuiThemeProvider(props: any) { const { colorMode } = useColorMode(); return ( ); } ================================================ FILE: apps/website/src/components/common/ThemedButton/index.tsx ================================================ import React from "react"; import MuiThemeProvider from "../MuiThemeProvider"; import Button, { ButtonProps } from "@mui/material/Button"; export default function ThemedButton(props: ButtonProps) { return (