React

by wuct

HTML

<div id="app"></div>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

li {
  margin: 8px 0;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.done {
  color: rgba(0, 0, 0, 0.3);
  text-decoration: line-through;
}

input {
  margin-right: 5px;
}

React

//import { Muxer, ArrayBufferTarget } from "mp4-muxer";
//import { useState, useRef, useEffect, useCallback } from "react";

export default function App() {
  const [videoSrc, setVideoSrc] = useState(null);
  const videoRef = useRef();
  useEffect(() => {
    videoRef.current?.load();
  }, [videoSrc]);

  const handleChange = useCallback((e) => {
    console.log("start convertGifToMp4");
    const file = e.target.files[0];
    const filereader = new FileReader();

    filereader.onload = (res) => {
      // TODO: check why it's loaded twice sometimes
      console.log("file one load", res);

      convertGifToMp4(res.target.result).then((bytes) => {
        console.log("mp4 result", bytes);
        const file = new Blob([bytes], { type: "video/mp4" });
        const url = URL.createObjectURL(file);
        setVideoSrc(url);
      });
    };
    filereader.readAsArrayBuffer(file);
  }, []);

  async function convertGifToMp4(data) {
    const imageDecoder = new ImageDecoder({ data, type: "image/gif" });
    await imageDecoder.completed;
    await imageDecoder.tracks.ready;
    const frameCount = imageDecoder.tracks.selectedTrack.frameCount;

    console.log("imageDecoder", imageDecoder);

    // decode first frame to obtain gif resolution
    const firstFrame = await imageDecoder.decode({ frameIndex: 0 });
    const { displayWidth, displayHeight } = firstFrame.image;
    // H264 only supports even sized frames
    const width = displayWidth % 2 === 0 ? displayWidth : displayWidth - 1;
    const height = displayHeight % 2 === 0 ? displayHeight : displayHeight - 1;

    // https://github.com/Vanilagy/mp4-muxer?tab=readme-ov-file#initialization
    const muxer = new Muxer({
      target: new ArrayBufferTarget(),
      video: {
        codec: "vp9",
        width,
        height,
      },
      fastStart: "in-memory",
    });

    const videoEncoder = new VideoEncoder({
      output: (chunk, meta) => {
        console.log("videoEncoder ouput", meta, chunk);
       ...