这是我的专栏《JS真好玩》,将教你用JS实现一些有趣的东西。JS可以直接在浏览器运行,也可以在Node中运行,你可以跟我学习用JS做好玩儿的事情。感谢大家关注~文章求赞噢!祝大家早日变成一名「前端极客」!
欢迎阅读:本专栏最受欢迎文章《用JS找到哪个小坏蛋给我连点2次赞》。
背景
最近我在处理图片,遇到个问题:从网上下载的图片,都没有后缀标明文件类型。我希望写代码识别他们的后缀,并自动改名字。
关于图片存储
文件用后缀标明文件类型,是给计算机的「文件管理器」和人类看的。「文件管理器」可以直接读取文件名,知道用什么软件打开文件,「人类」也可以直观的从文件名中知道类型。
但文件名后缀不是决定文件类型的(所以人类可以任意修改后缀,没有限制)。文件中,通常有 Magic Number 标识文件类型。这个 Magic Number 通常位于文件的头部。一个文件其实就是一个长的二进制序列,头部就是这些二进制序列的前几个字节。
以下来自维基百科对 Magic Number 的一些案例:
- GIF image files have theASCII code for "GIF89a" (
47
49
46
38
39
61
) or "GIF87a" (47
49
46
38
37
61
) - JPEG image files begin with
FF
D8
and end withFF
D9
. JPEG/JFIF files contain theASCII code for "JFIF" (4A
46
49
46
) as anull terminated string. JPEG/Exif files contain theASCII code for "Exif" (45
78
69
66
) also as a null terminated string, followed by moremetadata about the file. - PNG image files begin with an 8-byte signature which identifies the file as a PNG file and allows detection of common file transfer problems:
\211
P
N
G
\r
\n
\032
\n
(89
50
4E
47
0D
0A
1A
0A
). That signature contains variousnewline characters to permit detecting unwarranted automated newline conversions, such as transferring the file usingFTP with theASCIItransfer mode instead of thebinarymode.
上面提到的"GIF89a" (47
49
46
38
39
61
),引号里是ASCII,括号里是16进制,你可以去我写的工具里轻松转换:tool.hullqin.cn/byte-parser… ,工具介绍文章在《手写解析uin8数组的工具:解析二进制字节,太快太方便了!》
方案一:imageinfo
安装
npm i imageinfo
在NodeJS中使用
const imageinfo = require('imageinfo');
const img = fs.readFileSync('图片文件路径');
const info = imageinfo(img);
console.log(info);
console.log(info.format);
console.log("Data is type:", info.mimeType);
console.log(" Size:", img.length, "bytes");
console.log(" Dimensions:", info.width, "x", info.height);
得到info
后,使用info.format.toLowerCase()
就能获得文件后缀。
这个库比较简洁,只能识别png jpg gif swf。如果文件未识别到图片类型,info就是false了,要注意特殊处理这种情况。
源码
imageinfo源码非常简单:
module.exports = function imageInfo(buffer, path) {
var pngSig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
var jpgSig = [0xff, 0xd8, 0xff];
var gifSig = [0x47, 0x49, 0x46, 0x38, [0x37, 0x39], 0x61];
var swfSig = [[0x46, 0x43], 0x57, 0x53];
if (checkSig(buffer, 0, pngSig)) return imageInfoPng(buffer);
if (checkSig(buffer, 0, jpgSig)) return imageInfoJpg(buffer);
if (checkSig(buffer, 0, gifSig)) return imageInfoGif(buffer);
if (checkSig(buffer, 0, swfSig)) return imageInfoSwf(buffer);
return false;
};
可以看到它定义了一些头部标识,然后通过checkSig
去匹配。我们再看看checkSig
:
function checkSig(buffer, offset, sig) {
var len = sig.length;
for (var i = 0; i < len; i++) {
var b = buffer[i+offset],
s = sig[i],
m = false;
if ('number' == typeof s) {
m = s === b;
}
else {
for (var k in s) {
var o = s[k];
if (o === b) {
m = true;
}
}
}
if (!m) {
return false;
}
}
return true;
}
也很简单,就是拿文件头部几个字节和之前定义的头部标识进行逐个匹配,全匹配成功,返回true,否则就返回false。
方案二:file-type
这个库不像imagetype
走简洁路线,file-type
功能更强大,能识别的类型更多。同时支持NodeJS环境和浏览器环境。
官方介绍如下:
The file type is detected by checking the magic number of the buffer.
This package is for detecting binary-based file formats, not text-based formats like .txt
, .csv
, .svg
, etc.
意思是,这个库通过检查文件二进制序列的 magic number 来判断文件类型。不支持 txt csv svg这些文本类型文件的判断。
安装
npm i file-type
使用
NodeJS使用:
import {fileTypeFromFile} from 'file-type';
console.log(await fileTypeFromFile('Unicorn.png'));
//=> {ext: 'png', mime: 'image/png'}
浏览器使用:
import {fileTypeFromStream} from 'file-type';
const url = 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg';
const response = await fetch(url);
const fileType = await fileTypeFromStream(response.body);
console.log(fileType);
更多API,可阅读官方文档: file-type
写在最后
我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》、《极致用户体验》。