buffer-type
Version:
Detect content-type from Buffer data.
411 lines (372 loc) • 15.3 kB
JavaScript
;
/**
* @see http://www.onicos.com/staff/iz/formats/gif.html
*
GIF format
Byte Order: Little-endian
GIF Header
Offset Length Contents
0 3 bytes "GIF" 0x47 0x49 0x46
3 3 bytes "87a" or "89a" 0x38 0x39|0x37 0x61
6 2 bytes <Logical Screen Width>
8 2 bytes <Logical Screen Height>
10 1 byte bit 0: Global Color Table Flag (GCTF)
bit 1..3: Color Resolution
bit 4: Sort Flag to Global Color Table
bit 5..7: Size of Global Color Table: 2^(1+n)
11 1 byte <Background Color Index>
12 1 byte <Pixel Aspect Ratio>
13 ? bytes <Global Color Table(0..255 x 3 bytes) if GCTF is one>
? bytes <Blocks>
1 bytes <Trailer> (0x3b)
*
*/
function gif(buf) {
if (buf.length < 13 ||
// "GIF" 0x47 0x49 0x46
buf[0] !== 0x47 || buf[1] !== 0x49 || buf[2] !== 0x46 ||
// "87a" or "89a"
buf[3] !== 0x38 || (buf[4] !== 0x39 && buf[4] !== 0x37) || buf[5] !== 0x61) {
return;
}
const width = buf.readUInt16LE(6);
const height = buf.readUInt16LE(8);
return {
type: 'image/gif',
extension: '.gif',
width,
height,
};
}
/**
* @see http://en.wikipedia.org/wiki/Portable_Network_Graphics
*
A PNG file starts with an 8-byte signature. The hexadecimal byte values are 89 50 4E 47 0D 0A 1A 0A; the decimal values are 137 80 78 71 13 10 26 10. Each of the header bytes is there for a specific reason:[7]
Bytes Purpose
89 Has the high bit set to detect transmission systems that do not support 8 bit data and to reduce the chance that a text file is mistakenly interpreted as a PNG, or vice versa.
50 4E 47 In ASCII, the letters PNG, allowing a person to identify the format easily if it is viewed in a text editor.
0D 0A A DOS-style line ending (CRLF) to detect DOS-Unix line ending conversion of the data.
1A A byte that stops display of the file under DOS when the command type has been used—the end-of-file character
0A A Unix-style line ending (LF) to detect Unix-DOS line ending conversion.
*
*/
function png(buf) {
if (buf.length < 16 ||
buf[0] !== 0x89 ||
// PNG
buf[1] !== 0x50 || buf[2] !== 0x4E || buf[3] !== 0x47 ||
// \r\n
buf[4] !== 0x0D || buf[5] !== 0x0A ||
buf[6] !== 0x1A || buf[7] !== 0x0A) {
return;
}
// Length Chunk type Chunk data CRC
// 4 bytes 4 bytes Length bytes 4 bytes
const length = buf.readUInt32BE(8);
// var chunkType = buf.slice(12, 16).toString(); // should be 'IHDR'
// console.log(length, chunkType, buf.slice(12, 16))
const chunkData = buf.slice(16, 16 + length);
// Width: 4 bytes 0
// Height: 4 bytes 4
// Bit depth: 1 byte 8
// Color type: 1 byte 9
// Compression method: 1 byte 10
// Filter method: 1 byte 11
// Interlace method: 1 byte 12
const width = chunkData.readUInt32BE(0, true);
const height = chunkData.readUInt32BE(4, true);
return {
type: 'image/png',
extension: '.png',
width,
height,
bit: chunkData.readUInt8(8, true),
color: chunkData.readUInt8(9, true),
compression: chunkData.readUInt8(10, true),
filter: chunkData.readUInt8(11, true),
interlace: chunkData.readUInt8(12, true),
};
}
/**
* @see http://www.onicos.com/staff/iz/formats/jpeg.html
* @see http://blog.csdn.net/lpt19832003/article/details/1713718
*
JPEG format
Byte Order: Big-endian
Offset Length Contents
0 1 byte 0xff
1 1 byte 0xd8 (SOI)
2 1 byte 0xff
3 1 byte 0xe0 (APP0)
4 2 bytes length of APP0 block
6 5 bytes "JFIF\0" 4a 46 49 46 00
11 1 byte <Major version>
12 1 byte Minor version
13 1 byte <Units for the X and Y densities>
units = 0: no units, X and Y specify the pixel aspect ratio
units = 1: X and Y are dots per inch
units = 2: X and Y are dots per cm
14 2 bytes <Xdensity: Horizontal pixel density>
16 2 bytes <Ydensity: Vertical pixel density>
18 1 byte <Xthumbnail: Thumbnail horizontal pixel count>
19 1 byte <Ythumbnail: Thumbnail vertical pixel count>
...
1 byte 0xff
1 byte 0xd9 (EOI) end-of-file
*
*/
function jpeg(buf) {
// JFIF - JPEG FILE Interchange Format
if (buf.length < 20 ||
// 0xff 0xd8(SOI)
buf[0] !== 0xff || buf[1] !== 0xd8 ||
// 0xff 0xe0(APP0)
buf[2] !== 0xff || buf[3] !== 0xe0 ||
// length of APP0 block should be 16
buf.readUInt16BE(4, true) !== 16 ||
// 'JFIF\0' 4a 46 49 46 00
buf[6] !== 0x4a || buf[7] !== 0x46 || buf[8] !== 0x49 || buf[9] !== 0x46 || buf[10] !== 0 ||
// DQT offset 20, 0xffdb
buf[20] !== 0xff || buf[21] !== 0xdb) {
return;
}
// APP0
// const majorVersion = buf.readUInt8(11, true);
// const minorVersion = buf.readUInt8(12, true);
// var units = buf.readUInt8(13, true);
// var width = buf.readUInt16BE(14, true);
// var height = buf.readUInt16BE(16, true);
// DQT,Define Quantization Table,定义量化表
// * 标记代码 2字节 固定值0xFFDB
// * 包含2个具体字段:
// ① 数据长度 2字节 字段①和多个字段②的总长度
// 即不包括标记代码,但包括本字段
// ② 量化表 数据长度-2字节
// a) 精度及量化表ID 1字节 高4位:精度,只有两个可选值
// 0:8位;1:16位
// 低4位:量化表ID,取值范围为0~3
// b) 表项 (64×(精度+1))字节 例如8位精度的量化表
// 其表项长度为64×(0+1)=64字节
// 本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。
// SOF0,Start of Frame,帧图像开始
// u 标记代码 2字节 固定值0xFFC0
// u 包含9个具体字段:
// ① 数据长度 2字节 ①~⑥六个字段的总长度
// 即不包括标记代码,但包括本字段
// ② 精度 1字节 每个数据样本的位数
// 通常是8位,一般软件都不支持 12位和16位
// ③ 图像高度 2字节 图像高度(单位:像素),如果不支持 DNL 就必须 >0
// ④ 图像宽度 2字节 图像宽度(单位:像素),如果不支持 DNL 就必须 >0
// ⑤ 颜色分量数 1字节 只有3个数值可选
// 1:灰度图;3:YCrCb或YIQ;4:CMYK
// 而JFIF中使用YCrCb,故这里颜色分量数恒为3
// ⑥颜色分量信息 颜色分量数×3字节(通常为9字节)
// a) 颜色分量ID 1字节
// b) 水平/垂直采样因子 1字节 高4位:水平采样因子
// 低4位:垂直采样因子
// (曾经看到某资料把这两者调转了)
// c) 量化表 1字节 当前分量使用的量化表的ID
// 本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。
// find SOF0
let offset = 20;
let sof0 = null;
while (offset < buf.length) {
const flag = buf.slice(offset, offset + 2);
const size = buf.readUInt16BE(offset + 2);
if (flag[0] === 0xff && flag[1] === 0xc0) {
sof0 = offset;
break;
}
offset += 2 + size;
}
const r = {
type: 'image/jpeg',
extension: '.jpg',
};
if (sof0) {
// If found out SOF0, we can detect width and height
offset = sof0 + 2 + 2 + 1;
r.height = buf.readUInt16BE(offset, true);
r.width = buf.readUInt16BE(offset + 2, true);
}
return r;
}
// Exif - Exchange Image File Format
// 这个标准是 Camera 产业联合会发布的,主要目的就是设计一种文件格式,方便交换照片文件的 metadata
// https://blog.csdn.net/kickxxx/article/details/8173332
// Exif文件的layout
//
// SOI 0xFF, 0xD8 Start of frame
// APP1 0xFF, 0xE1 Exif Attribute Information
// APP2 0xFF, 0xE2 Flashpix Externsion data
// ...
// APPz 0xFF, 0xEn
// DQT 0xFF, 0xDB
// DHT 0xFF, 0xC4
// DRI 0xFF, 0xDD
// SOF 0xFF, 0xC0(0xFF, 0xC2)
// SOS 0xFF, 0xDA
// Compressed Data
// EOI 0xFF, 0xD9
function jpegExif(buf) {
if (buf.length < 4 ||
// 0xff 0xd8(SOI)
buf[0] !== 0xff || buf[1] !== 0xd8 ||
// 0xff 0xe0(APP1)
buf[2] !== 0xff || buf[3] !== 0xe1) {
return;
}
// APPn 应用程序保留标记
// 标记代码 marker 2 bytes 固定值0xFFE1 ~ 0xFFEF, n=1~15
// 数据长度 length 2 bytes APPn的总长度,不包括marker的2bytes
// 详细信息 (length - 2) bytes 内容是应用特定的,比如Exif使用APP1来存放图片的metadata,Adobe Photoshop用APP1和APP13两个标记段分别存储了一副图像的副本。
// find SOF0
let offset = 2;
let sof0 = null;
while (offset < buf.length) {
const flag = buf.slice(offset, offset + 2);
const size = buf.readUInt16BE(offset + 2);
if (flag[0] === 0xff && flag[1] === 0xc0) {
sof0 = offset;
break;
}
offset += 2 + size;
}
// SOF0,Start of Frame,帧图像开始
// u 标记代码 2字节 固定值0xFFC0
// u 包含9个具体字段:
// ① 数据长度 2字节 ①~⑥六个字段的总长度
// 即不包括标记代码,但包括本字段
// ② 精度 1字节 每个数据样本的位数
// 通常是8位,一般软件都不支持 12位和16位
// ③ 图像高度 2字节 图像高度(单位:像素),如果不支持 DNL 就必须 >0
// ④ 图像宽度 2字节 图像宽度(单位:像素),如果不支持 DNL 就必须 >0
// ⑤ 颜色分量数 1字节 只有3个数值可选
// 1:灰度图;3:YCrCb或YIQ;4:CMYK
// 而JFIF中使用YCrCb,故这里颜色分量数恒为3
// ⑥颜色分量信息 颜色分量数×3字节(通常为9字节)
// a) 颜色分量ID 1字节
// b) 水平/垂直采样因子 1字节 高4位:水平采样因子
// 低4位:垂直采样因子
// (曾经看到某资料把这两者调转了)
// c) 量化表 1字节 当前分量使用的量化表的ID
// 本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。
const r = {
type: 'image/jpeg',
extension: '.jpg',
};
if (sof0) {
// If found out SOF0, we can detect width and height
offset = sof0 + 2 + 2 + 1;
r.height = buf.readUInt16BE(offset, true);
r.width = buf.readUInt16BE(offset + 2, true);
}
return r;
}
/**
* @see http://www.onicos.com/staff/iz/formats/bmp.html
*
BMP - Microsoft Windows bitmap image file
Byte Order: Little-endian
Offset Length Contents
0 2 bytes "BM" 0x42 0x4d
2 4 bytes Total size included "BM" magic (s)
6 2 bytes Reserved1 00
8 2 bytes Reserved2 00
10 4 bytes Offset bits
14 4 bytes Header size (n)
18 n-4 bytes Header (See bellow)
14+n .. s-1 Image data
Header: n==12 (Old BMP image file format, Used OS/2)
Offset Length Contents
18 2 bytes Width
20 2 bytes Height
22 2 bytes Planes
24 2 bytes Bits per Pixel
Header: n>12 (Microsoft Windows BMP image file)
Offset Length Contents
18 4 bytes Width
22 4 bytes Height
26 2 bytes Planes
28 2 bytes Bits per Pixel
30 4 bytes Compression
34 4 bytes Image size
38 4 bytes X Pixels per meter
42 4 bytes Y Pixels per meter
46 4 bytes Number of Colors
50 4 bytes Colors Important
54 (n-40) bytes OS/2 new extentional fields??
*
*/
function bmp(buf) {
if (buf.length < 36 ||
buf[0] !== 0x42 || buf[1] !== 0x4d) {
return;
}
const headerSize = buf.readUInt32LE(14, true);
const header = buf.slice(18, 18 + headerSize - 4);
let width,
height;
if (headerSize === 12) {
width = header.readUInt16LE(0, true);
height = header.readUInt16LE(2, true);
} else {
width = header.readUInt32LE(0, true);
height = header.readUInt32LE(4, true);
}
return {
type: 'image/bmp',
extension: '.bmp',
width,
height,
};
}
// webp format: https://developers.google.com/speed/webp/docs/riff_container
// WebP File Header
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 'R' | 'I' | 'F' | 'F' |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | File Size |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 'W' | 'E' | 'B' | 'P' |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// 'RIFF': 32 bits
// The ASCII characters 'R' 'I' 'F' 'F'.
// File Size: 32 bits (uint32)
// The size of the file in bytes starting at offset 8.
// The maximum value of this field is 2^32 minus 10 bytes and
// thus the size of the whole file is at most 4GiB minus 2 bytes.
// 'WEBP': 32 bits
// The ASCII characters 'W' 'E' 'B' 'P'.
function webp(buf) {
if (buf.length < 12) {
return;
}
if (buf.slice(0, 4).toString() !== 'RIFF' || buf.slice(8, 12).toString() !== 'WEBP') {
return;
}
// uint32: A 32-bit, little-endian, unsigned integer.
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the 'WEBP' FourCC.
const size = buf.readUInt32LE(4);
return {
type: 'image/webp',
extension: '.webp',
size: size + 8,
};
}
const types = [ gif, png, jpeg, jpegExif, bmp, webp ];
function detect(buf) {
if (!buf || !buf.length) {
return;
}
for (let i = 0; i < types.length; i++) {
const r = types[i](buf);
if (r) {
return r;
}
}
}
module.exports = detect;