How File Format Handlers Are Implemented in convert: A Deep Dive into the p2r3/convert Architecture

File format handlers in the p2r3/convert repository implement the FormatHandler interface defined in src/FormatHandler.ts, exposing standardized init() and doConvert() methods that allow the application to register, initialize, and execute conversions across WASM-based tools like FFmpeg and pure-JavaScript implementations.

The p2r3/convert project treats every supported conversion as a modular file format handler that follows a strict architectural contract. This design decouples the user interface from underlying conversion tools, enabling support for external WASM binaries alongside native JavaScript implementations.

The FormatHandler Interface: The Contract for All File Format Handlers

Every handler must satisfy the FormatHandler interface located in src/FormatHandler.ts. This contract ensures that the runtime can initialize, query, and execute any converter without knowing implementation details.

export interface FormatHandler {
  /** Human‑readable name (e.g. “FFmpeg”) */
  name: string;
  /** List of formats the handler can read/write */
  supportedFormats?: FileFormat[];
  /** If true the handler can accept *any* input when no direct conversion exists */
  supportAnyInput?: boolean;
  /** Set to true after successful initialization */
  ready: boolean;
  /** Initialise external tools, populate `supportedFormats` */
  init: () => Promise<void>;
  /** Core conversion routine */
  doConvert(
    inputFiles: FileData[],
    inputFormat: FileFormat,
    outputFormat: FileFormat,
    args?: string[]
  ): Promise<FileData[]>;
}

The doConvert method receives input files, format descriptors, and optional CLI-style arguments, returning a promise that resolves to the converted file data.

Defining Reusable Format Definitions with CommonFormats

To avoid duplicating MIME types and extensions across handlers, src/CommonFormats.ts centralizes format metadata. The CommonFormats object provides reusable FormatDefinition instances that handlers can specialize.

export const Category = { IMAGE: "image", TEXT: "text", … };

export const CommonFormats = {
  PNG: new FormatDefinition(
    "Portable Network Graphics",
    "png", "png", "image/png", Category.IMAGE
  ),
  // … many more definitions
};

Handlers consume these definitions through the supported() method or the fluent builder() API to construct concrete FileFormat objects that include directionality flags (from, to).

Implementing File Format Handlers: Two Approaches

The architecture supports both WASM-based external tools and pure-JavaScript implementations.

WASM-Based Handlers: The FFmpeg Example

src/handlers/FFmpeg.ts wraps the FFmpeg WASM binary. The handler initializes the external tool, queries its capabilities, and performs conversions using an in-memory filesystem.

class FFmpegHandler implements FormatHandler {
  name = "FFmpeg";
  supportedFormats: FileFormat[] = [];
  ready = false;
  #ffmpeg?: FFmpeg;          // WASM wrapper

  async init() {
    this.#ffmpeg = new FFmpeg();
    await this.#ffmpeg.load({ coreURL: "/convert/wasm/ffmpeg-core.js" });
    // Query FFmpeg for supported codecs, build `supportedFormats`
    …
    this.ready = true;
  }

  async doConvert(inputFiles, inputFormat, outputFormat, args) {
    // Write each input file to the in‑memory FS,
    // Build an FFmpeg command line, run it,
    // Read the output bytes, clean up temporary files.
    …
  }
}

The init() method populates supportedFormats by parsing ffmpeg -formats and ffmpeg -h muxer=…, while doConvert() uses the WASM API methods writeFile, exec, and readFile to shuttle data between the browser and the FFmpeg process.

Pure-JavaScript Handlers: The TextEncoding Example

src/handlers/textEncoding.ts requires no external binaries. It implements encoding detection and transcoding using native browser APIs.

export default class TextEncodingHandler implements FormatHandler {
  name = "TextEncoding";
  supportedFormats = [
    CommonFormats.TEXT.supported("txt", true, true, true),
    { name: "Plain Text (UTF‑8 with BOM)", … internal: "utf8WB", … },
    // UTF‑16/32 variants…
  ];
  ready = true;
  init = async () => { this.ready = true };

  async doConvert(inputFiles, inputFormat, outputFormat) {
    // Detect input encoding → decode → re‑encode to the selected output encoding.
    …
  }
}

Because this handler relies solely on JavaScript, it sets ready = true immediately and implements init() as a no-op.

Registering and Initializing File Format Handlers

The runtime discovers handlers through a barrel file at src/handlers/index.ts. Each instantiation is wrapped in a try … catch block to ensure that missing optional dependencies do not crash the application.

const handlers: FormatHandler[] = [];
try { handlers.push(new FFmpegHandler()) } catch (_) {}
try { handlers.push(new ImageMagickHandler()) } catch (_) {}
try { handlers.push(new TextEncodingHandler()) } catch (_) {}
// … dozens more handlers
export default handlers;

The entry point at src/main.ts imports this list, initializes every handler, and constructs the conversion graph used by the UI.

import handlers from "./handlers";

(async () => {
  for (const h of handlers) await h.init();
  window.supportedFormatCache = new Map(
    handlers.flatMap(h => h.supportedFormats?.map(f => [f.format, f]))
  );
  window.traversionGraph.init(window.supportedFormatCache, handlers);
})();

How to Add a New File Format Handler to convert

Extending the system requires implementing the FormatHandler interface and registering the class:

  1. Create a class implementing FormatHandler in src/handlers/YourHandler.ts.
  2. Expose a name and populate supportedFormats (use CommonFormats when possible).
  3. Implement init() to populate supportedFormats and set ready.
  4. Implement doConvert() to perform the actual conversion logic.
  5. Add the class to src/handlers/index.ts inside a try … catch block.
  6. Run the test suite (npm test) to verify integration.

Summary

Frequently Asked Questions

What is the FormatHandler interface in convert?

The FormatHandler interface is the core contract defined in src/FormatHandler.ts that every file format handler must implement. It requires a name property, a ready boolean, an init() method for setup, and a doConvert() method that performs the actual file transformation. This standardization allows the application to treat WASM-based tools and pure-JavaScript implementations identically.

How does convert handle different file formats without external tools?

For formats that do not require external binaries, convert uses pure-JavaScript handlers like TextEncodingHandler in src/handlers/textEncoding.ts. These handlers implement the same FormatHandler interface but perform conversions using native browser APIs or JavaScript libraries, setting ready = true immediately and skipping WASM initialization. This approach works for text encodings, basic image manipulations, and other browser-native capabilities.

Where are file format handlers registered in the convert codebase?

All handlers are registered in the barrel file src/handlers/index.ts, where each handler class is instantiated inside a try … catch block to prevent missing dependencies from crashing the application. The resulting array is exported and consumed by src/main.ts, which calls init() on each handler and constructs the conversion graph used by the user interface.

Can I add custom file format handlers to convert?

Yes, you can extend convert by creating a new class that implements the FormatHandler interface, implementing the init() and doConvert() methods, and adding the class to src/handlers/index.ts. The architecture is designed for extensibility, allowing you to wrap new WASM tools or write pure-JavaScript converters while leveraging the existing CommonFormats definitions for consistent metadata.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →