├── test ├── fixtures │ ├── 1.webp │ ├── logo.png │ ├── 1_22x22.gif │ ├── 1_607x78.jpg │ ├── fixture.webp │ ├── 1_240x164.bmp │ ├── 2_124x124.bmp │ ├── 2_240x293.gif │ ├── buffer-type.jpg │ ├── btninet_56x47.gif │ └── marbles_1419x1001.bmp └── buffer-type.test.js ├── .eslintrc ├── renovate.json ├── .gitignore ├── .travis.yml ├── appveyor.yml ├── History.md ├── package.json ├── LICENSE.txt ├── README.md └── lib └── buffer-type.js /test/fixtures/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/1.webp -------------------------------------------------------------------------------- /test/fixtures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/logo.png -------------------------------------------------------------------------------- /test/fixtures/1_22x22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/1_22x22.gif -------------------------------------------------------------------------------- /test/fixtures/1_607x78.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/1_607x78.jpg -------------------------------------------------------------------------------- /test/fixtures/fixture.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/fixture.webp -------------------------------------------------------------------------------- /test/fixtures/1_240x164.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/1_240x164.bmp -------------------------------------------------------------------------------- /test/fixtures/2_124x124.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/2_124x124.bmp -------------------------------------------------------------------------------- /test/fixtures/2_240x293.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/2_240x293.gif -------------------------------------------------------------------------------- /test/fixtures/buffer-type.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/buffer-type.jpg -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg", 3 | "parserOptions": { 4 | "ecmaVersion": 2017 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/btninet_56x47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/btninet_56x47.gif -------------------------------------------------------------------------------- /test/fixtures/marbles_1419x1001.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-modules/buffer-type/HEAD/test/fixtures/marbles_1419x1001.bmp -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.DS_Store 10 | 11 | pids 12 | logs 13 | results 14 | 15 | node_modules 16 | npm-debug.log 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '10' 6 | install: 7 | - npm i npminstall && npminstall 8 | script: 9 | - npm run ci 10 | after_script: 11 | - npminstall codecov && codecov 12 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | - nodejs_version: '10' 5 | 6 | install: 7 | - ps: Install-Product node $env:nodejs_version 8 | - npm i npminstall && node_modules\.bin\npminstall 9 | 10 | test_script: 11 | - node --version 12 | - npm --version 13 | - npm run test 14 | 15 | build: off 16 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.0.0 / 2018-06-28 3 | ================== 4 | 5 | **others** 6 | * [[`e07ae1b`](http://github.com/node-modules/buffer-type/commit/e07ae1bf830afd369e892e8abeb722c75ccbaf80)] - * feat: support Exif - Exchange Image File Format image (fengmk2 <>) 7 | 8 | 0.0.2 / 2014-05-04 9 | ================== 10 | 11 | * add webp detect 12 | 13 | 0.0.1 / 2013-08-13 14 | ================== 15 | 16 | * add bmp detect 17 | * add jpeg detect 18 | * add png image detect 19 | * add gif image detect 20 | * first commit 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buffer-type", 3 | "version": "1.0.0", 4 | "description": "Detect content-type from Buffer data.", 5 | "main": "lib/buffer-type.js", 6 | "files": [ 7 | "lib" 8 | ], 9 | "scripts": { 10 | "lint": "eslint lib test", 11 | "test-local": "egg-bin test", 12 | "test": "npm run lint && npm run test-local", 13 | "ci": "npm run lint && egg-bin cov" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "egg-bin": "^4.7.0", 18 | "egg-ci": "^1.8.0", 19 | "eslint": "^5.0.1", 20 | "eslint-config-egg": "^7.0.0", 21 | "mime": "1" 22 | }, 23 | "homepage": "https://github.com/node-modules/buffer-type", 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/node-modules/buffer-type.git" 27 | }, 28 | "keywords": [ 29 | "buffer-type", 30 | "image-type", 31 | "content-type", 32 | "buffer", 33 | "type", 34 | "mime", 35 | "filetype", 36 | "file extension", 37 | "buffer type" 38 | ], 39 | "engines": { 40 | "node": ">= 8.0.0" 41 | }, 42 | "ci": { 43 | "version": "8, 10" 44 | }, 45 | "author": "fengmk2 ", 46 | "license": "MIT" 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright(c) 2013 fengmk2 4 | Copyright(c) 2013 - present node-modules and other contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/buffer-type.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const mime = require('mime'); 7 | const bt = require('..'); 8 | 9 | const fixtures = path.join(__dirname, 'fixtures'); 10 | const names = fs.readdirSync(fixtures); 11 | 12 | describe('buffer-type.test.js', () => { 13 | names.forEach(name => { 14 | 15 | it('should check ' + name + ' work', () => { 16 | const filepath = path.join(fixtures, name); 17 | const extname = path.extname(filepath); 18 | const contentType = mime.lookup(filepath); 19 | const expect = { 20 | type: contentType, 21 | extension: extname, 22 | }; 23 | if (contentType.indexOf('image/') === 0) { 24 | const m = /\_(\d+)x(\d+)/.exec(name); 25 | if (m) { 26 | expect.width = Number(m[1]); 27 | expect.height = Number(m[2]); 28 | } 29 | } 30 | const r = bt(fs.readFileSync(filepath)); 31 | assert(r); 32 | for (const k in expect) { 33 | assert(r[k] === expect[k]); 34 | } 35 | }); 36 | 37 | }); 38 | 39 | it('should detect jpeg', () => { 40 | assert.deepEqual(bt(fs.readFileSync(path.join(fixtures, 'buffer-type.jpg'))), { 41 | type: 'image/jpeg', 42 | extension: '.jpg', 43 | width: 1600, 44 | height: 459, 45 | }); 46 | 47 | assert.deepEqual(bt(fs.readFileSync(path.join(fixtures, 'buffer-type.jpg')).slice(0, 10)), { 48 | type: 'image/jpeg', 49 | extension: '.jpg', 50 | }); 51 | }); 52 | 53 | it('should detect logo.png to .png', () => { 54 | assert.deepEqual(bt(fs.readFileSync(path.join(fixtures, 'logo.png'))), { 55 | type: 'image/png', 56 | extension: '.png', 57 | width: 618, 58 | height: 96, 59 | bit: 8, 60 | color: 6, 61 | compression: 0, 62 | filter: 0, 63 | interlace: 0, 64 | }); 65 | }); 66 | 67 | it('should detect jpeg missing width and height', () => { 68 | assert.deepEqual(bt(fs.readFileSync(path.join(__dirname, 'fixtures', '1_607x78.jpg')).slice(0, 60)), { 69 | type: 'image/jpeg', 70 | extension: '.jpg', 71 | }); 72 | }); 73 | 74 | it('should not detect any type', () => { 75 | assert(!bt()); 76 | assert(!bt(new Buffer(0))); 77 | assert(!bt(new Buffer(200))); 78 | assert(!bt(new Buffer([ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x16, 0x00, 0x16, 0x00 ]))); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | buffer-type 2 | ======= 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![NPM quality][quality-image]][quality-url] 6 | [![build status][travis-image]][travis-url] 7 | [![Test coverage][codecov-image]][codecov-url] 8 | [![David deps][david-image]][david-url] 9 | [![Known Vulnerabilities][snyk-image]][snyk-url] 10 | [![NPM download][download-image]][download-url] 11 | 12 | [npm-image]: https://img.shields.io/npm/v/buffer-type.svg?style=flat-square 13 | [npm-url]: https://npmjs.org/package/buffer-type 14 | [quality-image]: http://npm.packagequality.com/shield/buffer-type.svg?style=flat-square 15 | [quality-url]: http://packagequality.com/#?package=buffer-type 16 | [travis-image]: https://img.shields.io/travis/node-modules/buffer-type.svg?style=flat-square 17 | [travis-url]: https://travis-ci.org/node-modules/buffer-type 18 | [codecov-image]: https://img.shields.io/codecov/c/github/node-modules/buffer-type.svg?style=flat-square 19 | [codecov-url]: https://codecov.io/gh/node-modules/buffer-type 20 | [david-image]: https://img.shields.io/david/node-modules/buffer-type.svg?style=flat-square 21 | [david-url]: https://david-dm.org/node-modules/buffer-type 22 | [snyk-image]: https://snyk.io/test/npm/buffer-type/badge.svg?style=flat-square 23 | [snyk-url]: https://snyk.io/test/npm/buffer-type 24 | [download-image]: https://img.shields.io/npm/dm/buffer-type.svg?style=flat-square 25 | [download-url]: https://npmjs.org/package/buffer-type 26 | 27 | Detect content-type from Buffer data. 28 | 29 | ## Install 30 | 31 | ```bash 32 | $ npm install buffer-type 33 | ``` 34 | 35 | ## Usage 36 | 37 | ```js 38 | const bt = require('buffer-type'); 39 | const fs = require('fs'); 40 | 41 | const info = bt(fs.readFileSync(__dirname + '/logo.png')); 42 | console.log(info); 43 | // { 44 | // type: 'image/png', 45 | // extension: '.png', 46 | // width: 618, 47 | // height: 96, 48 | // bit: 8, // bit depth 49 | // color: 6, 50 | // compression: 0, 51 | // filter: 0, 52 | // interlace: 0 53 | // } 54 | ``` 55 | 56 | ## References 57 | 58 | * http://www.onicos.com/staff/iz/formats/ 59 | * http://www.fastgraph.com/help/image_file_header_formats.html 60 | * http://en.wikipedia.org/wiki/Portable_Network_Graphics 61 | * http://en.wikipedia.org/wiki/Image_file_format 62 | 63 | ## TODO 64 | 65 | * Image 66 | * [√] .png 67 | * [√] .jpg 68 | * [√] .bmp 69 | * [√] .gif 70 | * [√] .webp 71 | * [ ] .svg 72 | * [ ] .tif 73 | * [ ] .psd 74 | * Tar 75 | * [ ] .tar 76 | * [ ] .gzip 77 | * [ ] .zip 78 | * [ ] .rar 79 | * PE file 80 | * [ ] .exe 81 | * [ ] .msi 82 | * [ ] .apk 83 | * [ ] .ipa 84 | * Text 85 | * [ ] .xml 86 | * [ ] .html 87 | * [ ] .json 88 | * Media 89 | * [ ] .mp3 90 | * [ ] .mp4 91 | * [ ] .avi 92 | 93 | ## License 94 | 95 | [MIT](LICENSE.txt) 96 | -------------------------------------------------------------------------------- /lib/buffer-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @see http://www.onicos.com/staff/iz/formats/gif.html 5 | * 6 | GIF format 7 | 8 | Byte Order: Little-endian 9 | GIF Header 10 | 11 | Offset Length Contents 12 | 0 3 bytes "GIF" 0x47 0x49 0x46 13 | 3 3 bytes "87a" or "89a" 0x38 0x39|0x37 0x61 14 | 6 2 bytes 15 | 8 2 bytes 16 | 10 1 byte bit 0: Global Color Table Flag (GCTF) 17 | bit 1..3: Color Resolution 18 | bit 4: Sort Flag to Global Color Table 19 | bit 5..7: Size of Global Color Table: 2^(1+n) 20 | 11 1 byte 21 | 12 1 byte 22 | 13 ? bytes 23 | ? bytes 24 | 1 bytes (0x3b) 25 | * 26 | */ 27 | 28 | function gif(buf) { 29 | if (buf.length < 13 || 30 | // "GIF" 0x47 0x49 0x46 31 | buf[0] !== 0x47 || buf[1] !== 0x49 || buf[2] !== 0x46 || 32 | // "87a" or "89a" 33 | buf[3] !== 0x38 || (buf[4] !== 0x39 && buf[4] !== 0x37) || buf[5] !== 0x61) { 34 | return; 35 | } 36 | const width = buf.readUInt16LE(6); 37 | const height = buf.readUInt16LE(8); 38 | return { 39 | type: 'image/gif', 40 | extension: '.gif', 41 | width, 42 | height, 43 | }; 44 | } 45 | 46 | /** 47 | * @see http://en.wikipedia.org/wiki/Portable_Network_Graphics 48 | * 49 | 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] 50 | Bytes Purpose 51 | 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. 52 | 50 4E 47 In ASCII, the letters PNG, allowing a person to identify the format easily if it is viewed in a text editor. 53 | 0D 0A A DOS-style line ending (CRLF) to detect DOS-Unix line ending conversion of the data. 54 | 1A A byte that stops display of the file under DOS when the command type has been used—the end-of-file character 55 | 0A A Unix-style line ending (LF) to detect Unix-DOS line ending conversion. 56 | * 57 | */ 58 | 59 | function png(buf) { 60 | if (buf.length < 16 || 61 | buf[0] !== 0x89 || 62 | // PNG 63 | buf[1] !== 0x50 || buf[2] !== 0x4E || buf[3] !== 0x47 || 64 | // \r\n 65 | buf[4] !== 0x0D || buf[5] !== 0x0A || 66 | buf[6] !== 0x1A || buf[7] !== 0x0A) { 67 | return; 68 | } 69 | // Length Chunk type Chunk data CRC 70 | // 4 bytes 4 bytes Length bytes 4 bytes 71 | const length = buf.readUInt32BE(8); 72 | // var chunkType = buf.slice(12, 16).toString(); // should be 'IHDR' 73 | // console.log(length, chunkType, buf.slice(12, 16)) 74 | const chunkData = buf.slice(16, 16 + length); 75 | // Width: 4 bytes 0 76 | // Height: 4 bytes 4 77 | // Bit depth: 1 byte 8 78 | // Color type: 1 byte 9 79 | // Compression method: 1 byte 10 80 | // Filter method: 1 byte 11 81 | // Interlace method: 1 byte 12 82 | const width = chunkData.readUInt32BE(0, true); 83 | const height = chunkData.readUInt32BE(4, true); 84 | return { 85 | type: 'image/png', 86 | extension: '.png', 87 | width, 88 | height, 89 | bit: chunkData.readUInt8(8, true), 90 | color: chunkData.readUInt8(9, true), 91 | compression: chunkData.readUInt8(10, true), 92 | filter: chunkData.readUInt8(11, true), 93 | interlace: chunkData.readUInt8(12, true), 94 | }; 95 | } 96 | 97 | /** 98 | * @see http://www.onicos.com/staff/iz/formats/jpeg.html 99 | * @see http://blog.csdn.net/lpt19832003/article/details/1713718 100 | * 101 | JPEG format 102 | 103 | Byte Order: Big-endian 104 | Offset Length Contents 105 | 0 1 byte 0xff 106 | 1 1 byte 0xd8 (SOI) 107 | 2 1 byte 0xff 108 | 3 1 byte 0xe0 (APP0) 109 | 4 2 bytes length of APP0 block 110 | 6 5 bytes "JFIF\0" 4a 46 49 46 00 111 | 11 1 byte 112 | 12 1 byte Minor version 113 | 13 1 byte 114 | units = 0: no units, X and Y specify the pixel aspect ratio 115 | units = 1: X and Y are dots per inch 116 | units = 2: X and Y are dots per cm 117 | 14 2 bytes 118 | 16 2 bytes 119 | 18 1 byte 120 | 19 1 byte 121 | 122 | ... 123 | 124 | 1 byte 0xff 125 | 1 byte 0xd9 (EOI) end-of-file 126 | * 127 | */ 128 | 129 | function jpeg(buf) { 130 | // JFIF - JPEG FILE Interchange Format 131 | if (buf.length < 20 || 132 | // 0xff 0xd8(SOI) 133 | buf[0] !== 0xff || buf[1] !== 0xd8 || 134 | // 0xff 0xe0(APP0) 135 | buf[2] !== 0xff || buf[3] !== 0xe0 || 136 | // length of APP0 block should be 16 137 | buf.readUInt16BE(4, true) !== 16 || 138 | // 'JFIF\0' 4a 46 49 46 00 139 | buf[6] !== 0x4a || buf[7] !== 0x46 || buf[8] !== 0x49 || buf[9] !== 0x46 || buf[10] !== 0 || 140 | // DQT offset 20, 0xffdb 141 | buf[20] !== 0xff || buf[21] !== 0xdb) { 142 | 143 | return; 144 | } 145 | 146 | // APP0 147 | // const majorVersion = buf.readUInt8(11, true); 148 | // const minorVersion = buf.readUInt8(12, true); 149 | // var units = buf.readUInt8(13, true); 150 | // var width = buf.readUInt16BE(14, true); 151 | // var height = buf.readUInt16BE(16, true); 152 | 153 | // DQT,Define Quantization Table,定义量化表 154 | // * 标记代码 2字节 固定值0xFFDB 155 | // * 包含2个具体字段: 156 | // ① 数据长度 2字节 字段①和多个字段②的总长度 157 | // 即不包括标记代码,但包括本字段 158 | // ② 量化表 数据长度-2字节 159 | // a) 精度及量化表ID 1字节 高4位:精度,只有两个可选值 160 | // 0:8位;1:16位 161 | // 低4位:量化表ID,取值范围为0~3 162 | // b) 表项 (64×(精度+1))字节 例如8位精度的量化表 163 | // 其表项长度为64×(0+1)=64字节 164 | // 本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。 165 | 166 | // SOF0,Start of Frame,帧图像开始 167 | // u 标记代码 2字节 固定值0xFFC0 168 | // u 包含9个具体字段: 169 | // ① 数据长度 2字节 ①~⑥六个字段的总长度 170 | // 即不包括标记代码,但包括本字段 171 | // ② 精度 1字节 每个数据样本的位数 172 | // 通常是8位,一般软件都不支持 12位和16位 173 | // ③ 图像高度 2字节 图像高度(单位:像素),如果不支持 DNL 就必须 >0 174 | // ④ 图像宽度 2字节 图像宽度(单位:像素),如果不支持 DNL 就必须 >0 175 | // ⑤ 颜色分量数 1字节 只有3个数值可选 176 | // 1:灰度图;3:YCrCb或YIQ;4:CMYK 177 | // 而JFIF中使用YCrCb,故这里颜色分量数恒为3 178 | // ⑥颜色分量信息 颜色分量数×3字节(通常为9字节) 179 | // a) 颜色分量ID 1字节 180 | // b) 水平/垂直采样因子 1字节 高4位:水平采样因子 181 | // 低4位:垂直采样因子 182 | // (曾经看到某资料把这两者调转了) 183 | // c) 量化表 1字节 当前分量使用的量化表的ID 184 | 185 | // 本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。 186 | 187 | // find SOF0 188 | let offset = 20; 189 | let sof0 = null; 190 | while (offset < buf.length) { 191 | const flag = buf.slice(offset, offset + 2); 192 | const size = buf.readUInt16BE(offset + 2); 193 | if (flag[0] === 0xff && flag[1] === 0xc0) { 194 | sof0 = offset; 195 | break; 196 | } 197 | offset += 2 + size; 198 | } 199 | 200 | const r = { 201 | type: 'image/jpeg', 202 | extension: '.jpg', 203 | }; 204 | 205 | if (sof0) { 206 | // If found out SOF0, we can detect width and height 207 | offset = sof0 + 2 + 2 + 1; 208 | r.height = buf.readUInt16BE(offset, true); 209 | r.width = buf.readUInt16BE(offset + 2, true); 210 | } 211 | 212 | return r; 213 | } 214 | 215 | // Exif - Exchange Image File Format 216 | // 这个标准是 Camera 产业联合会发布的,主要目的就是设计一种文件格式,方便交换照片文件的 metadata 217 | // https://blog.csdn.net/kickxxx/article/details/8173332 218 | // Exif文件的layout 219 | // 220 | // SOI 0xFF, 0xD8 Start of frame 221 | // APP1 0xFF, 0xE1 Exif Attribute Information 222 | // APP2 0xFF, 0xE2 Flashpix Externsion data 223 | // ... 224 | // APPz 0xFF, 0xEn 225 | // DQT 0xFF, 0xDB 226 | // DHT 0xFF, 0xC4 227 | // DRI 0xFF, 0xDD 228 | // SOF 0xFF, 0xC0(0xFF, 0xC2) 229 | // SOS 0xFF, 0xDA 230 | // Compressed Data 231 | // EOI 0xFF, 0xD9 232 | function jpegExif(buf) { 233 | if (buf.length < 4 || 234 | // 0xff 0xd8(SOI) 235 | buf[0] !== 0xff || buf[1] !== 0xd8 || 236 | // 0xff 0xe0(APP1) 237 | buf[2] !== 0xff || buf[3] !== 0xe1) { 238 | return; 239 | } 240 | 241 | // APPn 应用程序保留标记 242 | // 标记代码 marker 2 bytes 固定值0xFFE1 ~ 0xFFEF, n=1~15 243 | // 数据长度 length 2 bytes APPn的总长度,不包括marker的2bytes 244 | // 详细信息 (length - 2) bytes 内容是应用特定的,比如Exif使用APP1来存放图片的metadata,Adobe Photoshop用APP1和APP13两个标记段分别存储了一副图像的副本。 245 | // find SOF0 246 | let offset = 2; 247 | let sof0 = null; 248 | while (offset < buf.length) { 249 | const flag = buf.slice(offset, offset + 2); 250 | const size = buf.readUInt16BE(offset + 2); 251 | if (flag[0] === 0xff && flag[1] === 0xc0) { 252 | sof0 = offset; 253 | break; 254 | } 255 | offset += 2 + size; 256 | } 257 | 258 | // SOF0,Start of Frame,帧图像开始 259 | // u 标记代码 2字节 固定值0xFFC0 260 | // u 包含9个具体字段: 261 | // ① 数据长度 2字节 ①~⑥六个字段的总长度 262 | // 即不包括标记代码,但包括本字段 263 | // ② 精度 1字节 每个数据样本的位数 264 | // 通常是8位,一般软件都不支持 12位和16位 265 | // ③ 图像高度 2字节 图像高度(单位:像素),如果不支持 DNL 就必须 >0 266 | // ④ 图像宽度 2字节 图像宽度(单位:像素),如果不支持 DNL 就必须 >0 267 | // ⑤ 颜色分量数 1字节 只有3个数值可选 268 | // 1:灰度图;3:YCrCb或YIQ;4:CMYK 269 | // 而JFIF中使用YCrCb,故这里颜色分量数恒为3 270 | // ⑥颜色分量信息 颜色分量数×3字节(通常为9字节) 271 | // a) 颜色分量ID 1字节 272 | // b) 水平/垂直采样因子 1字节 高4位:水平采样因子 273 | // 低4位:垂直采样因子 274 | // (曾经看到某资料把这两者调转了) 275 | // c) 量化表 1字节 当前分量使用的量化表的ID 276 | 277 | // 本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。 278 | const r = { 279 | type: 'image/jpeg', 280 | extension: '.jpg', 281 | }; 282 | 283 | if (sof0) { 284 | // If found out SOF0, we can detect width and height 285 | offset = sof0 + 2 + 2 + 1; 286 | r.height = buf.readUInt16BE(offset, true); 287 | r.width = buf.readUInt16BE(offset + 2, true); 288 | } 289 | 290 | return r; 291 | } 292 | 293 | /** 294 | * @see http://www.onicos.com/staff/iz/formats/bmp.html 295 | * 296 | BMP - Microsoft Windows bitmap image file 297 | 298 | Byte Order: Little-endian 299 | Offset Length Contents 300 | 0 2 bytes "BM" 0x42 0x4d 301 | 2 4 bytes Total size included "BM" magic (s) 302 | 6 2 bytes Reserved1 00 303 | 8 2 bytes Reserved2 00 304 | 10 4 bytes Offset bits 305 | 14 4 bytes Header size (n) 306 | 18 n-4 bytes Header (See bellow) 307 | 14+n .. s-1 Image data 308 | 309 | Header: n==12 (Old BMP image file format, Used OS/2) 310 | Offset Length Contents 311 | 18 2 bytes Width 312 | 20 2 bytes Height 313 | 22 2 bytes Planes 314 | 24 2 bytes Bits per Pixel 315 | 316 | Header: n>12 (Microsoft Windows BMP image file) 317 | Offset Length Contents 318 | 18 4 bytes Width 319 | 22 4 bytes Height 320 | 26 2 bytes Planes 321 | 28 2 bytes Bits per Pixel 322 | 30 4 bytes Compression 323 | 34 4 bytes Image size 324 | 38 4 bytes X Pixels per meter 325 | 42 4 bytes Y Pixels per meter 326 | 46 4 bytes Number of Colors 327 | 50 4 bytes Colors Important 328 | 54 (n-40) bytes OS/2 new extentional fields?? 329 | * 330 | */ 331 | 332 | function bmp(buf) { 333 | if (buf.length < 36 || 334 | buf[0] !== 0x42 || buf[1] !== 0x4d) { 335 | return; 336 | } 337 | const headerSize = buf.readUInt32LE(14, true); 338 | const header = buf.slice(18, 18 + headerSize - 4); 339 | let width, 340 | height; 341 | if (headerSize === 12) { 342 | width = header.readUInt16LE(0, true); 343 | height = header.readUInt16LE(2, true); 344 | } else { 345 | width = header.readUInt32LE(0, true); 346 | height = header.readUInt32LE(4, true); 347 | } 348 | return { 349 | type: 'image/bmp', 350 | extension: '.bmp', 351 | width, 352 | height, 353 | }; 354 | } 355 | 356 | // webp format: https://developers.google.com/speed/webp/docs/riff_container 357 | // WebP File Header 358 | // 0 1 2 3 359 | // 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 360 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 361 | // | 'R' | 'I' | 'F' | 'F' | 362 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 363 | // | File Size | 364 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 365 | // | 'W' | 'E' | 'B' | 'P' | 366 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 367 | // 368 | // 'RIFF': 32 bits 369 | // The ASCII characters 'R' 'I' 'F' 'F'. 370 | // File Size: 32 bits (uint32) 371 | // The size of the file in bytes starting at offset 8. 372 | // The maximum value of this field is 2^32 minus 10 bytes and 373 | // thus the size of the whole file is at most 4GiB minus 2 bytes. 374 | // 'WEBP': 32 bits 375 | // The ASCII characters 'W' 'E' 'B' 'P'. 376 | function webp(buf) { 377 | if (buf.length < 12) { 378 | return; 379 | } 380 | if (buf.slice(0, 4).toString() !== 'RIFF' || buf.slice(8, 12).toString() !== 'WEBP') { 381 | return; 382 | } 383 | 384 | // uint32: A 32-bit, little-endian, unsigned integer. 385 | // The file size in the header is the total size of the chunks that follow plus 4 bytes for the 'WEBP' FourCC. 386 | const size = buf.readUInt32LE(4); 387 | 388 | return { 389 | type: 'image/webp', 390 | extension: '.webp', 391 | size: size + 8, 392 | }; 393 | } 394 | 395 | const types = [ gif, png, jpeg, jpegExif, bmp, webp ]; 396 | 397 | function detect(buf) { 398 | if (!buf || !buf.length) { 399 | return; 400 | } 401 | 402 | for (let i = 0; i < types.length; i++) { 403 | const r = types[i](buf); 404 | if (r) { 405 | return r; 406 | } 407 | } 408 | } 409 | 410 | module.exports = detect; 411 | --------------------------------------------------------------------------------