NodeJs获取照片信息并分类、缩略图生成
读取照片exif信息,按日期分类,生成缩略图预览,结合云服务,实现高性能相册功能~
# 前言
一开始想着女朋友生日快到了,想准备个有意义的礼物啥的,看了手机和文件夹里的这么多照片(在一块拍了很多照片),做个相册不是很nice嘛,也是一份很棒的回忆和纪念呀
初步想法是:
- 首先可以通过脚本自动的把那么多照片简单分类,可以按照日期
- 获取照片的日期、位置等基本信息,以时间轴的形式书写故事
- 需要结合云服务器对象存储,并配合数据库生成图片信息表(名称、日期、位置、图片原图地址、图片缩略图地址等)
- 可以通过接口的形式获取
json
数据,在前端渲染UI,并提供预览和原图下载功能
第三方云服务的对象存储的必要性,云端管理,随用随取,结合CDN快速访问,隐私性考虑
这里还是以我比较熟悉的 Nodejs为例
# nodejs的exif库
这里首先排除文件创建日期,我们的照片文件是时刻在传输的,创建日期没有参考价值,必须读取图片的头信息才行
它具有以下能力:
从图像(JPEG)中提取Exif元数据,相机在图像文件中保存关于图像的额外信息:相机型号、分辨率、图像的拍摄地点(GPS)或拍摄时间。
这里我简单列举部分属性:
{
image: {
ImageWidth: 3648,
ImageHeight: 2736,
Make: 'HUAWEI',
ModifyDate: '2021:05:04 12:55:57',
// ...
},
exif: {
ISO: 200,
DateTimeOriginal: '2021:05:04 12:55:57',
CreateDate: '2021:05:04 12:55:57',
LightSource: 1,
Flash: 0,
FocalLength: 5.58,
// ...
},
gps: {
// ...
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CreateDate
可以帮助我们按日期,以月或者年的维度去做一些归类处理,以时间轴...
imgge
、GPS
信息可以在开发相册网站功能时提供能力
解决了数据获取的问题后,接下来就是比较常规一些的操作了
# 根据日期简单分类
比如我这里打算以时间轴的形式去描述和分类,以2021年为例,按月份做细粒度划分,结合具体事件做出更有意义的分类
// 先定义下分类
5: "521&南京游",
6: "端午",
7: "杭州游",
8: "七夕",
9: "大学城&中秋节",
// ...
2
3
4
5
6
7
8
那这里的话,可以结合自身需求:
- 把照片按照月份归类到单独的文件夹去,以标签的形式标记,供网站使用
- 把基本信息呈现在照片名称中,提供快速可读性,供其他用途
# 代码
这里我因为其他用途,先以名称方式分类处理
开始之前先安装下常用依赖:fs-extra
、pify
提示:现在nodejs版本内部自带提供了 fs/promise
方法,结合 ES6 module
使用还是很不错的
# 读取目录下的所有照片
这里按最简单处理,先不考虑嵌套递归
const fs = require("fs-extra");
const pify = require("pify");
// 读取目录
const readDir = async () =>{
const dir = await pify(fs.readdir(dirPath));
}
2
3
4
5
6
7
# 读取图片信息
- 格式过滤
const path = require("path");
// 文件格式
const ext = path.extname(currentPath);
// .jpg
2
3
4
5
- 读取信息
const Exif = require("exif");
const readImg = async (currentPath) => {
const imgInfo = await pify(Exif.ExifImage)({
image: currentPath,
});
// 文件格式
const ext = path.extname(currentPath);
// 获取日期 2021:05:04 12:55:57
const createDate = imgInfo.exif.CreateDate
const date = new Date(.replace(":", "-").replace(":", "-"));
return {
currentPath,
date,
ext
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这里为了方便处理,可以用 Promise.all
包装一下
const res = await Promise.all(dir.map(readImg))
- 按日期排序
const sortData = res.sort((a, b) => a.date - b.date);
# 重命名处理
拿到 sortData
数据,同样按照 dir的方式处理
const result = await Promise.all(renameFile)
目标效果:5月21日(521&南京游)-1.jpg
// 来自于入口函数
// toDir = ''
// sortArr={}
const renameFile = async () => sortData.map(async ({ currentPath, date, ext }) => {
// 基本信息: 月 日 描述
const month = date.getMonth() + 1;
const day = date.getDate();
const desc = MONTHS[month] || "未分类";
// 以月份内部排序
sortArr[month] = sortArr[month] || [];
const len = sortArr[month].length + 1;
sortArr[month].push(len);
const fileName = `${month}月${day}日(${desc})-${sortArr[month].length}`;
const newPath = toDir + fileName + ext; // 5月21日(521&南京)-1.jpg
// 判断是否过滤已存在的目标文件
if (fs.existsSync(newPath)) {
console.log(currentPath, "已存在", newPath);
}
// 文件重命名
if (!isDebug) {
fs.renameSync(currentPath, newPath);
}
console.log(currentPath, "移动到", newPath);
return { fileName, date };
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
主函数
/**
* @param {String} dirPath 来源目录
* @param {String} toDir 目标目录
* @param {Boolean} isDebug 是否调试模式。调试模式只会在控制台输出信息,不会真正操作文件
*/
const main = async (dirPath, toDir, isDebug = true) => {
//
}
2
3
4
5
6
7
8
9
# nodejs的image-thumbnail库
按照插件默认配置,最终得到的结果,效果十分明显,不过,这里因为压缩比列比较高,清晰度损失很大,可以根据自身使用场景自定义压缩比列
用法也是比较简单的:
- 先读取到照片的缩略图数据
- 再写入到一个新图片文件中即可
const thumbnail = await imageThumbnail("./origin_photos/IMG_20210403_224457.jpg");
const res = await pify(writeFile("./target_photos/IMG_20210403_224457.jpg", thumbnail));
2
缩略图效果:
/**
* 缩略图
* origin_photos/IMG_20210403_224457.jpg => 5.5M
* target_photos/IMG_20210403_224457.jpg => 16k
*/
2
3
4
5
# 云服务商
现在CDN都是标配了
又拍云的图片存储服务自带免费缩略图能力
七牛云的存储优惠幅度大一些
这些云服务商都有10-20G不等的免费存储服务和每月固定的几十G的免费流量限制,如果只是个人网站使用,我觉得还是值得尝试一下的。