antiboredom / ffmpeg-explorer

Create and render complex ffmpeg filtergraphs in the browser.
https://ffmpeg.lav.io
446 stars 17 forks source link

Chrome with MT fixed and COEP/COOP server! #13

Open radames opened 1 year ago

radames commented 1 year ago

Hi @antiboredom, amazing project! It looks like the issue with ffmpeg wasm MT on Chrome was solved with the latest @ffmpeg/ffmpeg@0.12.6 it worth trying!

https://github.com/antiboredom/ffmpeg-explorer/blob/568390ba23b5565dd5ce623eed1d22ea4c7b3b85/src/App.svelte#L21-L22

BTW is you ever need to host static pages with COEP/COOP header HuggingFace Static SDK now support custom headers!! https://huggingface.co/spaces/radames/OpenAI-CLIP-JavaScript

# README.md
custom_headers:
  cross-origin-embedder-policy: require-corp
  cross-origin-opener-policy: same-origin
  cross-origin-resource-policy: cross-origin

docs: https://huggingface.co/docs/hub/spaces-config-reference#:~:text=can%20be%20embedded.-,custom_headers,-%3A%20Dict%5Bstring ps. I work there 🤦🏼

antiboredom commented 1 year ago

Ah - thanks for the tip - I'll look give it a try!

antiboredom commented 1 year ago

@radames - trying this and I'm seeing a bunch of errors with the mt version on chrome. Maybe doing something wrong, but have you by any chance gotten it working?

radames commented 1 year ago

hi @antiboredom, sorry I just ran a test on your repo upgrading the versions and indeed the problem continues 🤔 I'm working on a similar environment on different project, svelte + ffmpeg/wasm, the only difference is I'm running the ffmpeg on a web worker and I'm not getting the issue. Maybe worth trying using an web worker, since it's nice to your main webpage, it won't lock the main thread. I'll happy to help, can't at the right minute but here are some snippets

@ffmpeg/core-mt@0.12.3
@ffmpeg/ffmpeg@0.12.6
@ffmpeg/util@0.12.1
// ffmpeg.worker.js
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';

class FFmpegSingleton {
  static instance = null;
  static async getInstance(logger = false) {
    if (this.instance === null) {
      const ffmpeg = new FFmpeg();
      if (ffmpeg === null) {
        throw new Error('ffmpeg is null');
      }
      ffmpeg.on('log', ({ message }) => {
        if (logger) {
          console.log(message);
        }
      });
      ffmpeg.on('progress', ({ progress, time }) => {
        console.log(progress, time);
      });
      const baseURL = `https://unpkg.com/@ffmpeg/core-mt@0.12.3/dist/esm`;
      console.log('baseURL', baseURL);
      await ffmpeg.load({
        coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
        wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'applicaiton/wasm'),
        workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')
      });

      this.instance = ffmpeg;
    }
    return this.instance;
  }
}

self.addEventListener('message', async (event) => {
  const { inputURL, inputName, outputName, mimeType, command } = event.data;

  const ffmpeg = await FFmpegSingleton.getInstance(true);
  self.postMessage({
    status: 'progress',
    message: 'writing file'
  });
  const inputFile = await fetchFile(inputURL);
  await ffmpeg.writeFile(inputName, inputFile);
  self.postMessage({
    status: 'progress',
    message: 'running command'
  });
  const cmd = ['-nostdin', '-y', '-i', inputName, ...command, outputName];
  console.log('cmd', cmd);
  await ffmpeg.exec(cmd);

  try {
    self.postMessage({
      status: 'progress',
      message: 'reading file'
    });
    const outputData = await ffmpeg.readFile(outputName);
    const blob = new Blob([outputData.buffer], { type: mimeType });
    const url = URL.createObjectURL(blob);

    self.postMessage({
      status: 'complete',
      output: url
    });
  } catch (e) {
    self.postMessage({
      status: 'error',
      message: 'Error processing ffmpeg command'
    });
  }
});

let ffmpegWorker;
onMount(() => {
    const _ffmpegWorker = await import('$lib/ffmpeg.worker.js?worker');
    ffmpegWorker = new _ffmpegWorker.default();
})
export async function runFFmpeg(worker, videoSrc, filterStr, command, statusCallback) {
  if (!videoSrc) return;
  if (!filterStr) return videoSrc.href;

  return new Promise(async (resolve, reject) => {
    // Create the worker if it does not yet exist.

    const onMessageReceived = (e) => {
      switch (e.data.status) {
        case 'complete':
          const videoURL = e.data.output;
          resolve(videoURL);
          break;
        case 'progress':
          statusCallback(e.data);
          break;
        case 'error':
          reject(e.data);
          break;
      }
    };

    // Attach the callback function as an event listener.
    worker.addEventListener('message', onMessageReceived);
    worker.postMessage({
      inputURL: videoSrc.href,
      inputName: videoSrc.name,
      outputName: 'out.mp4',
      mimeType: 'video/*',
      command: command
    });
  });
}
antiboredom commented 1 year ago

@radames thanks - this is super helpful!!!