zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    Spring-AOP切面编程(3)
    【SpringBoot】SpingBoot整合AOP
    反射--Reflection
    泛型--Generic
    C#系统库的源代码
    C#中的?
    C#语法糖
    C#初识LINQ
    C#委托和事件的区别
    C#中的lambda表达式
  • 原文地址:https://www.cnblogs.com/sochishun/p/14289483.html
Copyright © 2011-2022 走看看