NodeJs 獲取照片拍攝日期和視頻拍攝日期,並按日期目錄存檔


需安裝依賴模塊:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM