小帅の技术博客 小帅の技术博客
首页
  • 基础
  • 框架
  • 进阶
  • 工程化
  • NodeJS
  • 脚本
  • 拓展
  • 技术
  • 服务器
程序猿
关于
友链
  • 分类
  • 标签
  • 归档
GitHub

前端小帅

学而不思则罔,思而不学则殆
首页
  • 基础
  • 框架
  • 进阶
  • 工程化
  • NodeJS
  • 脚本
  • 拓展
  • 技术
  • 服务器
程序猿
关于
友链
  • 分类
  • 标签
  • 归档
GitHub
  • NodeJs

    • puppeteer无头浏览器
    • 新一代ORM-Prisma
    • NodeJs获取照片信息并分类
      • 前言
      • nodejs的exif库
        • 根据日期简单分类
        • 代码
      • nodejs的image-thumbnail库
      • 云服务商
    • 微信每天给女朋友发早安
    • Koa之洋葱模型分析
  • 脚本

  • 拓展

  • 技术

  • 服务器

  • 全栈
  • NodeJs
sunss
2021-11-14

NodeJs获取照片信息并分类、缩略图生成

读取照片exif信息,按日期分类,生成缩略图预览,结合云服务,实现高性能相册功能~

核心库:node-exif 、image-thumbnail

# 前言

一开始想着女朋友生日快到了,想准备个有意义的礼物啥的,看了手机和文件夹里的这么多照片(在一块拍了很多照片),做个相册不是很nice嘛,也是一份很棒的回忆和纪念呀

初步想法是:

  1. 首先可以通过脚本自动的把那么多照片简单分类,可以按照日期
  2. 获取照片的日期、位置等基本信息,以时间轴的形式书写故事
  3. 需要结合云服务器对象存储,并配合数据库生成图片信息表(名称、日期、位置、图片原图地址、图片缩略图地址等)
  4. 可以通过接口的形式获取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: {
    // ...
  },
}
1
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: "大学城&中秋节",
// ...
1
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));
}
1
2
3
4
5
6
7

# 读取图片信息

  • 格式过滤
const path = require("path");

// 文件格式
const ext = path.extname(currentPath);
// .jpg
1
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
  }
}
1
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))
1
  • 按日期排序
const sortData = res.sort((a, b) => a.date - b.date);
1

# 重命名处理

拿到 sortData数据,同样按照 dir的方式处理

const result = await Promise.all(renameFile)
1

目标效果: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 };
})
1
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) => {
  // 
}
1
2
3
4
5
6
7
8
9

# nodejs的image-thumbnail库

按照插件默认配置,最终得到的结果,效果十分明显,不过,这里因为压缩比列比较高,清晰度损失很大,可以根据自身使用场景自定义压缩比列

用法也是比较简单的:

  1. 先读取到照片的缩略图数据
  2. 再写入到一个新图片文件中即可
const thumbnail = await imageThumbnail("./origin_photos/IMG_20210403_224457.jpg");
const res = await pify(writeFile("./target_photos/IMG_20210403_224457.jpg", thumbnail));
1
2

缩略图效果:

/**
 * 缩略图
 * origin_photos/IMG_20210403_224457.jpg => 5.5M
 * target_photos/IMG_20210403_224457.jpg => 16k
 */
1
2
3
4
5

# 云服务商

现在CDN都是标配了

又拍云的图片存储服务自带免费缩略图能力

七牛云的存储优惠幅度大一些

这些云服务商都有10-20G不等的免费存储服务和每月固定的几十G的免费流量限制,如果只是个人网站使用,我觉得还是值得尝试一下的。

编辑
#工具
上次更新: 2024/04/15, 14:35:14
新一代ORM-Prisma
微信每天给女朋友发早安

← 新一代ORM-Prisma 微信每天给女朋友发早安→

最近更新
01
说说call、apply、bind是如何改变this的
03-29
02
从输入 URL 到页面加载完成发生了什么?
03-29
03
JavaScript进阶—— new 的执行过程
03-26
更多文章>
sunss | © 2020.08-2024.04 浙ICP备2022002957号-1
载入天数... 载入时分秒...  |  总访问量 次
提供CDN加速/云存储服务
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式