需安裝依賴模塊:
npm install exif
npm install ffprobe
npm install ffprobe-static
npm install pify
功能代碼獻上(photo-archives.js)
// 文件路徑:photo-archives.js
/**
* NodeJs 獲取照片拍攝日期和視頻拍攝日期,並按日期目錄存檔
* 功能:NodeJs 獲取照片拍攝日期和視頻拍攝日期,並按日期目錄存檔
* 使用:node photo-archives.js
* 擴展包:
* npm install exif
* npm install ffprobe
* npm install ffprobe-static
* npm install pify
*/
// 引用 fs 文件系統模塊
const NmFs = require('fs')
// 引用 path 路徑處理模塊
const NmPath = require('path');
// 安裝並引用 exif 圖像元數據處理模塊
const NmExif = require('exif')
// 安裝並引用 ffprobe 視頻元數據處理模塊
const NmFfprobe = require('ffprobe')
const NmFfprobeStatic = require('ffprobe-static');
// 安裝並引用 Promise 自動轉換回調方法模塊
const NmPify = require('pify');
// 配置信息
const config = {
image_exts: ['jpg', 'png', 'gif', 'jpeg', 'webp', 'tiff'],
video_exts: ['mp4', 'mov'],
}
/**
* 移動照片和視頻到日期目錄
* @param {String} fromDir 來源目錄
* @param {String} toDir 目標目錄
* @param {Boolean} isDebug 是否調試模式。調試模式只會在控制台輸出信息,不會真正操作文件
* @param {Boolean} isSkipExists 是否跳過已存在的目標文件
*/
async function movePhotos(fromDir, toDir, isDebug = true, isSkipExists = true) {
if (!NmFs.existsSync(fromDir)) {
console.log('path not exists: ', fromDir);
return;
}
// 自動補齊路徑符
const SEP = NmPath.sep;
if (!fromDir.endsWith(SEP)) {
fromDir += SEP;
}
if (!toDir.endsWith(SEP)) {
toDir += SEP;
}
// 打開目錄
const dir = await NmFs.promises.opendir(fromDir);
// 聲明變量,優化內存
let ext = '',
prefix = '',
newDir = '',
newPath = '',
currentPath = '',
stat = null,
datestr = '',
time = 0,
date = null,
exifData = null;
for await (const dirent of dir) {
// 當前路徑
currentPath = fromDir + dirent.name;
// 處理目錄
if (dirent.isDirectory()) {
// 如果當前路徑是目錄,則進入遞歸模式
movePhotos(currentPath + SEP, toDir, isDebug, isSkipExists);
continue;
}
// 處理文件
ext = NmPath.extname(dirent.name); // .jpg
if (!ext) {
continue;
}
date = null;
ext = ext.substring(1).toLowerCase();
if (config.image_exts.includes(ext)) {
prefix = 'IMG-';
} else if (config.video_exts.includes(ext)) {
prefix = 'MOV-';
} else {
// 過濾非圖片和視頻格式的文件
continue;
}
if (ext == 'jpg' || ext == 'jpeg') {
exifData = await NmPify(NmExif.ExifImage)({
image: currentPath
});
// 2020:04:04 08:34:33
datestr = exifData.exif.CreateDate.replace(':', '-').replace(':', '-').replace(' ', 'T') + 'Z';
date = new Date(datestr); // 注意,時間后面要帶字符Z,否則會差8小時,詳見:https://segmentfault.com/q/1010000011751536
// console.log('jpeg===', currentPath, datestr);
}
if (ext == 'mov' || ext == 'mp4') {
exifData = await NmFfprobe(currentPath, {
path: NmFfprobeStatic.path
})
// creation_time: '2020-04-04T00:34:32.000000Z'
datestr = exifData.streams[0].tags.creation_time.substring(0, 19) + 'Z';
date = new Date(datestr);
// console.log('video===', currentPath, datestr);
}
if (!date) {
stat = NmFs.statSync(currentPath);
time = stat.birthtime > stat.ctime ? stat.ctime : stat.birthtime;
if (time > stat.mtime) {
time = stat.mtime;
}
// 時間后面要加字符'Z',否則會少8小時
date = new Date(time + 'Z');
// console.log('other===', currentPath, date.toISOString());
}
// 自動創建目標目錄
newDir = toDir + date.toISOString().substring(0, 10);
if (!isDebug && !NmFs.existsSync(newDir)) {
NmFs.mkdirSync(newDir, {
recursive: true
});
}
newPath = date.toISOString().replace(/-/g, '').replace(/:/g, '').substring(0, 15);
newPath = newDir + '/' + prefix + newPath + '.' + ext; // IMG-20210117113520.jpg
// 判斷是否過濾已存在的目標文件
if (isSkipExists && NmFs.existsSync(newPath)) {
console.log(currentPath, '已存在', newPath);
continue;
}
// 文件重命名
if (!isDebug) {
NmFs.renameSync(currentPath, newPath);
}
console.log(currentPath, '移動到', newPath);
}
}
// 執行批量照片和視頻歸檔功能
movePhotos('./a/', './b/', true).catch(err => console.log(err))
// 文件時間調試
function debugFileTime(stat) {
let options = {
birthtime: '',
ctime: '',
mtime: '',
atime: ''
}
for (let key in options) {
if (stat[key]) {
options[key] = new Date(stat[key] + 'Z').toISOString();
}
}
console.log('debugFileTime', options);
}
運行:
node photo-archives.js