dcraw-wasm compiles dcraw.c to WebAssembly and exposes a JavaScript API that can:
- extract RAW metadata
- extract embedded JPEG thumbnails
The package now exposes a typed TypeScript-first public API with convenience layers for browser and Node batch workflows.
It currently exposes three API styles: an advanced RawDecoder facade for full control, browser convenience helpers for file/drop workflows, and Node.js batch helpers for mass processing. See API, Convenience Layers, and Node.js Batch Processing for details.
This package is intended for browser-based workflows where RAW files are provided as Uint8Array.
npm i dcraw-wasmimport { analyzeBrowserFiles } from 'dcraw-wasm/browser';
const results = await analyzeBrowserFiles(fileInput.files, {
includeMetadata: true,
includeThumbnail: true,
});
for (const result of results) {
if (result.error) {
console.error(result.fileName, result.error);
continue;
}
console.log(result.metadata?.propertyMap['camera.model']?.value);
console.log(result.thumbnail?.length);
}import { RawDecoder } from 'dcraw-wasm';
const decoder = new RawDecoder();
// Each method creates its own isolated runtime — safe to call repeatedly.
const metadata = await decoder.readMetadata(rawBuffer);
const thumbnail = await decoder.extractThumbnail(rawBuffer);
console.log(metadata.propertyMap['camera.model']?.value);If your bundler or hosting setup serves static assets from a custom path, use locateFile to point to dcraw.wasm:
const decoder = new RawDecoder();
await decoder.init({ locateFile: (path) => `/static/wasm/${path}` });
const metadata = await decoder.readMetadata(rawBuffer);Creates the advanced decoder facade with full control. No initialization step is required before calling decode methods.
Optional warmup step. Stores module options (e.g. locateFile) that are forwarded to all isolated runtime instances created by subsequent decode calls. Call this before any decode method when you need to customize the WASM module (e.g. custom WASM asset path):
const decoder = new RawDecoder();
await decoder.init({
locateFile: (path) => `/static/wasm/${path}`,
});If the default WASM resolution works for your environment, init() can be omitted entirely.
Extracts metadata and returns both raw text and typed metadata properties:
type ParsedMetadata = {
rawText: string;
lines: string[];
properties: MetadataProperty[];
propertyMap: Record<string, MetadataProperty>;
};Each call to readMetadata on the same instance is isolated and safe to repeat.
Extracts embedded thumbnail and returns Uint8Array. Each call is isolated.
Runs metadata and thumbnail extraction in one call and returns:
{
metadata: ParsedMetadata | null;
thumbnail: Uint8Array | null;
}Async safe wrappers that return { ok, data, error }.
Escape hatch for direct low-level option usage. Requires a prior init() call. The underlying runtime is one-shot — calling runInternal more than once on the same RawDecoder instance may produce incorrect results. Prefer the standard async methods for safe repeated use.
MetadataProperty shape:
type MetadataProperty = {
id: string; // stable key, e.g. "camera.model"
label: string; // English display label
sourceLabel: string; // original dcraw label
value: string | number | boolean | Date | FractionValue;
valueType: 'string' | 'int' | 'float' | 'fraction' | 'datetime' | 'boolean' | 'unknown';
unit: string | null;
confidence: 'exact' | 'inferred' | 'parsed';
rawValue: string;
};Unknown labels are preserved as unknown.<normalized_label> ids.
Import from dcraw-wasm/browser:
analyzeBrowserFiles(fileListOrArray, options?)analyzeDroppedFiles(dataTransfer, options?)
Each returns an array of per-file results with metadata, thumbnail, and error isolation.
Import from dcraw-wasm/node:
import { analyzeNodeFiles } from 'dcraw-wasm/node';
const summary = await analyzeNodeFiles(['/in/a.ARW', '/in/b.ARW'], {
concurrency: 2,
includeMetadata: true,
includeThumbnail: true,
thumbnailOutputDir: './thumbs',
});
console.log(summary.succeeded, summary.failed);The original low-level class is still available as an internal surface:
import { DcrawWasm } from 'dcraw-wasm/internal';Most consumers should use RawDecoder and convenience APIs; this path is for low-level control. The underlying DcrawWasm instance should be treated as single-use per initialized instance — repeated decode calls on the same low-level instance may fail with RuntimeError: memory access out of bounds. RawDecoder and the convenience helpers avoid this entirely by isolating each operation in a fresh runtime.
Common low-level flags mapped by the internal runtime include:
identify: true(-i)verbose: true(-v)extractThumbnail: true(-e)setBrightnessLevel: 1.0(-b)setInterpolationQuality: 3(-q)
The typed facade prefers descriptive options (brightness, interpolationQuality, etc.) and maps them internally.
Instructions are explained for macOS; other platforms may vary. Some additional background on building WebAssembly can be found here.
Install emscripten:
brew install emscriptennpm run build # dev profile (default)
npm run build:prod # optimized profileBuild output is generated in ./bin.
npm run startThis copies runtime files to ./demo and starts emrun with demo/index.html.
Demo index includes:
- browser convenience API demo (
demo/browser/convenience.html) - browser
RawDecoderdemo (demo/browser/raw-decoder.html) - node batch API example (
demo/node/batch-example.js)
Run the node demo with:
npm run demo:nodenpm testTest workflow:
- uses
test/fixtures-local/SAMPLE.ARWif present - otherwise tries downloading
https://rawcameraimages.com/demo/SAMPLE.ARW - mirrors fixture into
test/fixtures/SAMPLE.ARWfor runtime compatibility - runs smoke tests via
node:test
Tarball integration test (publish-shape validation):
npm run test:packThis packs the module, installs the tarball in a temporary sandbox, and runs one decode.
This project is inspired by dcraw.js which sadly seems not been maintained since 2017 at the point of starting this. Due to open PR's, missing ES6 support, missing WASM support I thought it might be a good idea to give it a chance and also learn something new. So I gave it a try.
License for dcraw.c.
License for remaining source code in this repository (excluding dependencies and libraries) can be found in LICENSE.