107 lines
4.5 KiB
Markdown
107 lines
4.5 KiB
Markdown
|
|
# image-meta
|
||
|
|
|
||
|
|
[![npm version][npm-version-src]][npm-version-href]
|
||
|
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
||
|
|
[![Codecov][codecov-src]][codecov-href]
|
||
|
|
[![bundle][bundle-src]][bundle-href]
|
||
|
|
|
||
|
|
Detect image type and size using pure JavaScript. It has no dependencies and works in Node.js, browsers and other runtimes.
|
||
|
|
|
||
|
|
## Usage
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import { imageMeta } from "image-meta";
|
||
|
|
|
||
|
|
const res = await fetch(url);
|
||
|
|
const data = new Uint8Array(await res.arrayBuffer());
|
||
|
|
|
||
|
|
const meta = imageMeta(data);
|
||
|
|
// => { type: "png", width: 123, height: 456 }
|
||
|
|
```
|
||
|
|
|
||
|
|
In Node.js, a `Buffer` works as input because it is a `Uint8Array`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import { readFile } from "node:fs/promises";
|
||
|
|
import { imageMeta } from "image-meta";
|
||
|
|
|
||
|
|
const meta = imageMeta(await readFile("./image.jpg"));
|
||
|
|
```
|
||
|
|
|
||
|
|
The type is detected from the file contents, not the file extension.
|
||
|
|
|
||
|
|
### Result
|
||
|
|
|
||
|
|
```ts
|
||
|
|
type ImageMeta = {
|
||
|
|
type?: string; // detected format, e.g. "png"
|
||
|
|
width: number; // of the largest image when there are several
|
||
|
|
height: number;
|
||
|
|
orientation?: number; // EXIF orientation (jpg)
|
||
|
|
images?: Omit<ImageMeta, "images">[]; // all embedded images (ico, cur, icns)
|
||
|
|
animated?: boolean; // animation or image sequence (gif, png, webp, avif, heic, jxl)
|
||
|
|
pages?: number; // number of frames or pages (gif, png, webp, tiff)
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### Animation and pages
|
||
|
|
|
||
|
|
`animated` and `pages` are read from the headers without decoding the image, and are left `undefined` when they can't be found cheaply (for example in a truncated file). A single frame is not reported as animated. For tiff, every directory counts as a page, including thumbnails and reduced-resolution images (e.g. in camera raw files).
|
||
|
|
|
||
|
|
| Format | `animated` | `pages` |
|
||
|
|
| ----------- | --------------------------------------------------- | ----------------------- |
|
||
|
|
| gif | more than one frame | number of frames |
|
||
|
|
| png (APNG) | `acTL` chunk before `IDAT` | `acTL` frame count |
|
||
|
|
| webp | `VP8X` animation flag | number of `ANMF` chunks |
|
||
|
|
| avif / heic | sequence brand (`avis`, `msf1`, …) and a `moov` box | - |
|
||
|
|
| jxl | `have_animation` in the image metadata | - |
|
||
|
|
| tiff | - | length of the IFD chain |
|
||
|
|
|
||
|
|
> [!WARNING]
|
||
|
|
> These values are **best-effort hints**, useful for UX and processing decisions (keeping an animation when converting, skipping a step for single-frame images, showing a frame count). They are **not** reliable for security decisions such as size or resource limits: image-meta parses headers on its own, so for malformed or crafted files its answer can differ from what a decoder will do. Ask the decoder that will process the file when you need a guarantee.
|
||
|
|
|
||
|
|
### Error handling
|
||
|
|
|
||
|
|
`imageMeta` throws an error if the input is not a `Uint8Array`, the format is unsupported, or the data is invalid. Wrap calls that take untrusted input in `try/catch`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
try {
|
||
|
|
const meta = imageMeta(data);
|
||
|
|
} catch {
|
||
|
|
// Not a supported image
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Supported formats
|
||
|
|
|
||
|
|
`avif`, `bmp`, `cur`, `dds`, `gif`, `heic`, `icns`, `ico`, `j2c`, `jp2`, `jpg`, `jxl`, `ktx`, `png`, `pnm`, `psd`, `svg`, `tga`, `tiff`, `webp`
|
||
|
|
|
||
|
|
## Development
|
||
|
|
|
||
|
|
- Clone this repository
|
||
|
|
- Install the latest LTS version of [Node.js](https://nodejs.org/en/)
|
||
|
|
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
|
||
|
|
- Install dependencies using `pnpm install`
|
||
|
|
- Run interactive tests using `pnpm dev`
|
||
|
|
|
||
|
|
See [AGENTS.md](./AGENTS.md) for the project layout and how to add a new format.
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
Made with 💛
|
||
|
|
|
||
|
|
🔀 Based on [image-size](https://github.com/image-size/image-size) by [Aditya Yadav](https://github.com/netroy) and [contributors](https://github.com/image-size/image-size/graphs/contributors).
|
||
|
|
|
||
|
|
Published under [MIT License](./LICENSE).
|
||
|
|
|
||
|
|
<!-- Badges -->
|
||
|
|
|
||
|
|
[npm-version-src]: https://img.shields.io/npm/v/image-meta?style=flat&colorA=18181B&colorB=F0DB4F
|
||
|
|
[npm-version-href]: https://npmjs.com/package/image-meta
|
||
|
|
[npm-downloads-src]: https://img.shields.io/npm/dm/image-meta?style=flat&colorA=18181B&colorB=F0DB4F
|
||
|
|
[npm-downloads-href]: https://npmjs.com/package/image-meta
|
||
|
|
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/image-meta/main?style=flat&colorA=18181B&colorB=F0DB4F
|
||
|
|
[codecov-href]: https://codecov.io/gh/unjs/image-meta
|
||
|
|
[bundle-src]: https://img.shields.io/bundlephobia/minzip/image-meta?style=flat&colorA=18181B&colorB=F0DB4F
|
||
|
|
[bundle-href]: https://bundlephobia.com/result?p=image-meta
|