Leather
Have you ever wished for the ability to retrieve image or video file attributes
such as width, height, size, and mime type in Node without having
to resort to external libraries such as ffprobe?
Yeah, me too! This is why leather was created.
At the moment, the only package that does something similar is
image-size
and while it does work well, it does not handle video formats.
How
leather uses streams to read image and video files in byte-sized chunks.
As soon attributes have been extracted, the stream will be closed. Some file
formats have a well-defined fixed position in which these attributes
can be found, in those cases, leather skips any bytes before that
position and reads only the bytes needed to extract attributes directly.
However, sometimes the byte offset of these attributes may vary, in these
scenarios leather makes a best-effort attempt to read as few bytes as
possible to get to the good stuff!
Why
Before leather, if you wanted to do something like this in Node
you would need to install image-size to handle images and either
use ffprobe directly or using some kind of wrapper package to
handle video files.
While I love ffprobe for its capabilities, setting up a cross-platform
package requires some configuration. This package aims to solve that
by steering clear of any command-line tools which makes it more portable.
Node support
Stable Node versions from 14.18 and up are tested, Node 12.20 and up is supported.
Installation
Install the package locally using npm:
npm install leather --saveOr using yarn
yarn add leatherUsage
After installing the package, it can be imported using commonjs:
const {readMediaAttributes} = require('leather');Or using ES modules:
import {readMediaAttributes} from 'leather';Then, it can be called on supported image and video formats:
console.log(readMediaAttributes('cat.jpg'));
// => {width: 200, height: 200, size: 40000, mime: 'image/jpeg'}Buffer support
Starting from version 2.1.0, all readMediaAttributes methods also accept Buffer
instances in addition to file paths:
const buffer = fs.readFileSync('cat.png');
console.log(readMediaAttributes(buffer));
// => {width: 200, height: 200, size: 40000, mime: 'image/jpeg'}The width and height are pixel based. The size is the same as
fs.stat.
If the width or height could not be extracted, they will default to 0.
The mime type is also returned if found, otherwise undefined.
Using specific extractors
If you are only using one or a few of the extractors, you can opt to require only the extractors you need, e.g. for jpg/jpeg using commonjs:
const {readFileSync} = require('fs');
const {readMediaAttributes} = require('leather/extractors/jpg');
console.log(readMediaAttributes('cat.jpg'));
console.log(readMediaAttributes(readFileSync('cat.jpg')));
// => {width: 200, height: 200, size: 40000, mime: 'image/jpeg'}Or using ES modules:
import {readFileSync} from 'fs';
import {readMediaAttributes} from 'leather/extractors/jpg';
console.log(readMediaAttributes('cat.jpg'));
console.log(readMediaAttributes(readFileSync('cat.jpg')));
// => {width: 200, height: 200, size: 40000, mime: 'image/jpeg'}Supported formats
All supported image and video formats can be found in the table below:
| format | extractor | mime type |
|---|---|---|
| bmp | bmp | image/bmp |
| dds | dds | image/vnd.ms-dds |
| gif | gif | image/gif |
| icns | icns | image/x-icns |
| ico | ico | image/vnd.microsoft.icon |
| cur | ico | image/vnd.microsoft.icon |
| j2c | j2c | image/jp2 |
| jp2 | j2c | image/jp2 |
| jpg | jpg | image/jpeg |
| ktx | ktx | image/ktx |
| png | png | image/png |
| apng | png | image/apng |
| pfm | pnm | application/x-font-type1 |
| pam | pnm | image/x-portable-arbitrarymap |
| pbm | pnm | image/x-portable-bitmap |
| pgm | pnm | image/x-portable-graymap |
| ppm | pnm | image/x-portable-pixmap |
| psd | psd | image/vnd.adobe.photoshop |
| svg | svg | image/svg+xml |
| tiff | tiff | image/tiff |
| webp | webp | image/webp |
| xpm | xpm | image/x-xpixmap |
| xbm | xbm | image/x-xbitmap |
| fit | fit | image/fits |
| cel | cel | application/octet-stream |
| hdr | hdr | image/vnd.radiance |
| avi | avi | video/x-msvideo |
| fli | fli | video/x-fli |
| flc | fli | video/x-fli |
| flv | flv | video/x-flv |
| mng | png | video/x-mng |
| mp4 | mp4 | video/mp4 |
| m4v | mp4 | video/x-m4v |
| mov | mp4 | video/quicktime |
| ogv | ogv | video/ogg |
| mkv | webm | video/x-matroska |
| webm | webm | video/webm |
| wmv | wmv | video/x-ms-wmv |
Changelog
Credits and other resources that saved my soul
- https://www.npmjs.com/package/image-size
- http://www.fastgraph.com/help/avi_header_format.html
- http://www.fastgraph.com/help/flic_header_format.html
- https://docs.microsoft.com/en-us/previous-versions/ms779632(v=vs.85)
- https://en.wikipedia.org/wiki/List_of_file_signatures
- https://www.fileformat.info/format/tiff/egff.htm#TIFF.FO
- http://netpbm.sourceforge.net/doc/#formats
- https://www.garykessler.net/library/file_sigs.html
- https://github.com/sannies/mp4parser/blob/c869d076e9cd42aba5a3e35d88827610dec6ca15/examples/src/main/java/com/google/code/mp4parser/example/GetHeight.java
- https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html