奇怪的需求(1)——Exif的批量编辑

今天是本人破壳32年纪念日。为了纪念这个伟大的日子,今日起决定开一个新坑,记录下自己平时使用数码产品时一些奇怪的需求。

问题来自于老婆大人的30岁生日。作为口头“奔三”终于实现,迈向口头“奔四”的重要时刻,我觉得平常的生日礼物都显得不够特别。思来想去,决定送一套写真,记录一下老婆大人的“少女时代”。而这个需求也就由此源起。

为了让时光都有充分的影像记录,我的所有照片都通过NAS进行管理,并通过NAS同步至我的Surface Pro和Mac Mini中。而几乎所有的主流照片管理应用都具备时间线管理功能,我认为这也是最适合浏览的照片管理方式,可以快速帮助浏览者回到当时的记忆中。当然,这样的操作方式严重依赖于照片的Exif信息,若Exif本身就是错的,时间线的组织就会相当混乱。对于某些没有Exif的照片而言,每次编辑操作都会将照片的时间置为编辑当日,对于一个有着整理强迫症的人而言,这几乎是完全不能忍受的行为。因此,在拍写真时我专门嘱咐了摄影师,要求其打开相机的Exif记录功能,并将日期和时间设置正确。摄影师答应的很爽快,我也便并未亲自检查,问题便由此产生。

拍摄完毕已经将近晚上18点,无论是夫人还是陪同拍摄的我都很疲惫,因此没有现场选片便打车回家休息了。晚些时候,摄影师将所有的底片通过网盘发给了我。应该说摄影师的拍摄水平还是过硬的,很多照片甚至不需要精修都很出色。然而在我检查Exif信息的时候头大的事情出现了——Exif信息确实是打开的,然而时间都被设置为了2020年12月23日。

好在摄影师拍摄的时候,我用手机也蹭着拍了不少。通过照片姿势动作等倒是不难还原出真实的拍摄时间。而虽然Exif日期设置错误,但照片间的拍摄间隔是不会有错的。因此,理论上只需要确定第一张照片的真实拍摄时间,再用每张照片的Exif记录时间减去第一张照片的Exif记录时间得到间隔,用真实拍摄时间加上这个间隔即能搞定每张照片的真实拍摄时间。问题被简化为了三步:

  1. 确定每套写真首图真实拍摄时间
  2. 计算该套写真照片与首图Exif记录时间间隔
  3. 首图真实拍摄时间+间隔=每张照片的真实拍摄时间

到这一步,唯一的问题就是照片太多了,500多张手动改的话会累死。因此一个想法自然而言的冒了出来,干脆写个程序搞定算了。

日期的计算其实很简单,现代程序语言都提供了极为丰富的日期/时间类。问题在于照片的Exif信息怎么读取和写入完全没有头绪。传统的方法应该是Google搜索“Java操作Exif”类似的关键词,得到的词条可能是某个库的文档或者一段例程。而都4202年了,为了这么一个小工具用这种方式效率太低也不Fashion,怎么也得有AI加持才行。于是乎,ChatGPT走起。输入“使用Java获取Exif拍摄日期”,便可得到如下答案:

后文中也给出了例程。到这一步其实程序便很简单了。输入两个日期,分别为首图的Exif日期和推测的真实拍摄时间,计算后通过Apache Commons Imaging修改Exif参数即可。其实这才是当前阶段AI辅助编程的重要方式,计算机科学是一门飞速发展的科学,不可能要求程序员对每个库每个API都做到了如指掌。那么在庞大繁杂的文档中快速提取有效信息,甚至于用简单描述快速创建原型Demo,便是AI可以大有可为的场景。而这种工作对于程序员的帮助是巨大的。相关代码如下:

public static void modifyExifTime(File imageFile, LocalDateTime baseTime, LocalDateTime targetTime, String operation) {
    try {
        ImageMetadata metadata = Imaging.getMetadata(imageFile);
        if (metadata instanceof JpegImageMetadata) {
            JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
            TiffImageMetadata exifMetadata = jpegMetadata.getExif();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss");
            Duration duration = null;

            if (exifMetadata != null) {
                String dateTaken = exifMetadata.findField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL).getValueDescription().substring(1,20);
                //获取原始EXIF时间
                LocalDateTime originalTime = LocalDateTime.parse(dateTaken, formatter);

                duration = Duration.between(baseTime, originalTime);
                LocalDateTime resultTime = targetTime.plus(duration);

                if (operation.equalsIgnoreCase("modify")) {

                    TiffOutputSet outputSet = exifMetadata != null ? exifMetadata.getOutputSet() : new TiffOutputSet();

                    //移除原有EXIF时间
                    TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
                    exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
                    exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_DIGITIZED);

                    //写入新EXIF时间
                    String resultTimeString = resultTime.format(formatter);
                    exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL, resultTimeString);
                    File tempFile = File.createTempFile("tempImage", ".jpg");
                    try (FileOutputStream fos = new FileOutputStream(tempFile)) {
                        new ExifRewriter().updateExifMetadataLossless(imageFile, fos, outputSet);
                    }

                    // 替换原始文件
                    if (imageFile.delete() && tempFile.renameTo(imageFile)) {
                        System.out.println("拍摄日期已修改成功:" + resultTimeString);
                    } else {
                        System.out.println("无法替换原始文件。");
                    }
                }else {
                    formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                    String resultTimeString = resultTime.format(formatter);
                    System.out.println("文件名:" + imageFile.getName() + "拟替换的结果时间:" + resultTimeString);
                }
            }
        }
    } catch (Exception e) {
        //很多图片可能读取exif出现异常为正常现象 通常无需处理
        e.printStackTrace();
    }
}

执行的结果如下图所示。为了方便,我设置了两种操作方式。输入任意字符可输出推测的拍摄时间,可与手机拍摄的照片姿势做比对,用于微调推测的首图拍摄时间。输入“modify”则会真实写入图片的Exif信息,包括拍摄时间和数字化时间。执行完毕后在时间线中浏览图片,可以看到手机拍的图和发来的底片顺序完美融合,虽不敢说推测的拍摄时间一秒不差,但误差也在几秒之间,看起来不会有任何问题。

相关代码已开源在Github,有需要者可自取(自恋,这种破玩意谁需要):j-10cc/ExifEditor: 已知某一照片的拍摄时间,批量校正其他同批Exif拍摄时间 (github.com)

最后,我老婆真好看!(题图为AI版^_^)

《奇怪的需求(1)——Exif的批量编辑》上有2条评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注