Edit on GitHub

SvelteKit

Full Working Example

Install

Install the package

npm install @transcribe/transcriber

and copy the files from @transcribe/shout to /public

cp -r node_modules/@transcribe/shout/src/shout/* static/

Cross-Origin Headers

The wasm files must be served with the correct Cross-Origin headers. Otherwise browsers will refuse to load the files.

For the development server the headers are added by a "plugin" in vite.config.ts.

// vite.config.ts

// ...

export default defineConfig({
  plugins: [
    {
      name: "configure-response-headers",
      configureServer(server) {
        server.middlewares.use((req, res, next) => {
          res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
          res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
          next();
        });
      },
    },
    sveltekit(),
  ],
  // ...
});

For the preview server you can use the adapter-node in combination with a custom server that adds the headers.

// server.js

// ...

app.use((req, res, next) => {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
});

// ...

Depending on your deployment target you need to make sure that the webserver sets the correct headers for shout.wasm.js and shout.wasm.worker.mjs .

Usage

Rollup is not able to bundle shout.wasm.js. Also using an importmap like in Svelte doesn't work. To work around this we use a dynamic import that loads the module from /static/shout.wasm.js.

First exclude shout.wasm.js from the bundler in vite.config.ts

// vite.config.ts

// ...

export default defineConfig({
  // ...
  build: {
    rollupOptions: {
      external: ["/shout.wasm.js?url"],
    },
  },
});

then use dynamic import("/shout.wasm.js?url") in the component

// +page.svelte
<script lang="ts">
  import { onMount } from "svelte";
  import { FileTranscriber } from "@transcribe/transcriber";

  let createModule: (args?: {}) => Promise<any>;
  let transcriber: FileTranscriber;

  async function transcribe() {
    if (!createModule) {
      console.error("WASM module not loaded yet");
      return;
    }

    // check if transcriber is initialized
    if (!transcriber?.isReady) return;

    // there must be at least one user interaction (e.g click) before you can call this function
    const result = await transcriber.transcribe("/jfk.wav", { lang: "en" });

    // do something with the result json
    this.result = result.transcription.map((t) => t.text).join(" ");
  }

  onMount(async () => {
    // dynamic import wasm module from /static
    // this is a workaround because Rollup can't bundle this file
    createModule = (await import("/shout.wasm.js?url")).default;

    // create new instance
    transcriber = new FileTranscriber({
      createModule,
      model: "/ggml-tiny-q5_1.bin",
      workerPath: "/",
    });

    // and initialize the transcriber
    await transcriber.init();
  });
</script>

Note: Transcribe.js only runs in the browser. Node.js is not supported. Make sure that the code only gets executed in browser context and on/after user interaction.