FileDetector-基于java开发的照片整理工具
1. 项目背景
开发这个功能的主要原因如下:
1. 大学期间拍摄了约50G的照片,照片很多
2. 存放不规范,导致同一张照片出现在不同的文件夹内,可读性差,无法形成记忆线。
3. 重复存放过多,很多照片都有冗余备份,导致磁盘空间越来越不够用。
2. 解决思路
- 根据照片拍摄时间对照片文件重命名,并移动到统一文件夹内。
- 重复文件只移动一份,结果是除了目标文件夹内的照片以外,其他照片都是冗余照片。
注意:并非所有照片都有拍摄时间,只有数码相机与手机拍摄的才有。部分网上下载的图片也有原始拍摄时间。没有拍摄时间的照片不作处理。
3. 项目概述
3.1 项目依赖
这里的依赖都比较普通,只有一个比较特殊:metadata-extractor是用来提取照片中的拍摄时间的。joda-time用来规范日期格式。
3.2 项目结构
功能实现比较简单,根据业务分了biz/service/util/ui包。其中ui开发的比较粗糙,因为java开发基本上已经转入了后端,swing已经很少用到了,能跑起来就行。
3.3 项目流程图
1.重复文件删除
2.按拍摄时间重命名照片
3.移动文件到目标文件夹
3.4 项目下载
关键代码
1.重复文件检测
package cuishining.bizz; import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.google.common.collect.HashMultimap;
import com.google.common.hash.Hashing;
import com.google.common.io.Files; import cuishining.util.FileUtil; /**
* Created by shining.cui on 2016/7/20.
*/
public class DuplicateFileDetector {
private static final Logger logger = LoggerFactory.getLogger(DuplicateFileDetector.class); public HashMultimap<Long, String> detect(String path, String nameSuffix) {
List<File> fileList = FileUtil.getAllFilesUnderPath(path, nameSuffix);
HashMultimap<Long, String> md5AndFilePathMultiMap = analyzeMd5OfAllFiles(fileList);
return analyzeDuplicateFiles(md5AndFilePathMultiMap);
} private HashMultimap<Long, String> analyzeMd5OfAllFiles(List<File> fileList) {
HashMultimap<Long, String> md5FileNameMultiMap = HashMultimap.create();
for (File file : fileList) {
logger.info("文件{},正在分析中……",file);
try {
long md5 = Files.hash(file, Hashing.md5()).asLong();
String path = file.getCanonicalPath();
md5FileNameMultiMap.put(md5, path);
} catch (IOException e) {
logger.error("文件hash出错,请检查文件是否可读。",e);
} }
return md5FileNameMultiMap;
} private HashMultimap<Long, String> analyzeDuplicateFiles(HashMultimap<Long, String> multimap) {
Set<Long> md5s = multimap.keySet();
HashMultimap<Long, String> duplicateFilesMap = HashMultimap.create();
for (Long md5 : md5s) {
Set<String> fileNames = multimap.get(md5);
// 如果对应md5的value多于1个,证明是重复的文件,放入新的map中返回
if (fileNames.size() > 1) {
for (String name : fileNames) {
duplicateFilesMap.put(md5, name);
}
}
}
return duplicateFilesMap;
}
}
2.重命名策略
package cuishining.service.impl; import java.io.File;
import java.util.List; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import cuishining.service.RenamePolicy;
import cuishining.util.JpgFileUtil; /**
* Created by shining.cui on 2016/7/23.
*/
public class RenameByTimePolicy implements RenamePolicy {
private static final Logger logger = LoggerFactory.getLogger(RenameByTimePolicy.class); @Override
public boolean rename(List<File> fileList) {
logger.info("接受参数fileList为:{}", fileList);
for (File file : fileList) {
String photoTimeStr = JpgFileUtil.getPhotoTimeStr(file);
if (StringUtils.isEmpty(photoTimeStr)) {
logger.error("文件{}不存在拍摄日期,无法重命名",file);
}
String path = file.getParentFile().getAbsolutePath();
if (StringUtils.isNotEmpty(photoTimeStr)) {
renameFile(file, photoTimeStr, path);
}
}
return true;
} private void renameFile(File file, String photoTimeStr, String path) {
logger.info("文件{}正在重命名中……",file);
File renamedFile = new File(path + File.separator + photoTimeStr + ".jpg");
if (renamedFile.exists()) {
logger.error("{}文件已经存在,无法重命名。", renamedFile);
} else {
boolean renameSuccess = file.renameTo(renamedFile);
if (renameSuccess) {
logger.info("{}文件命名为{}", file.getName(), renamedFile.getName());
}
}
}
}
3.文件处理工具
package cuishining.util; import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set; import com.google.common.collect.HashMultimap;
import com.google.common.io.Files;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; /**
* 文件处理工具
* Created by shining.cui on 2016/7/12.
*/
public class FileUtil {
public static Logger logger = LoggerFactory.getLogger(FileUtil.class); /**
* 读取指定路径下的所有文件,使用队列实现
*
* @param filePath 指定的文件夹目录
* @param nameSuffix 指定后缀,若为null或者" "则匹配所有
* @return 文件夹及其子文件夹内所有文件
*/
public static List<File> getAllFilesUnderPath(String filePath, String nameSuffix) {
logger.info("接受的文件夹路径为:{},文件名匹配后缀为:{}", filePath, nameSuffix);
File basicfile = new File(filePath);
List<File> fileLis = Lists.newArrayList();
LinkedList<File> fileQueue = Lists.newLinkedList(Lists.newArrayList(basicfile));
while (!fileQueue.isEmpty()) {
File file = fileQueue.poll();
if (file.isDirectory() && file.listFiles() != null) {
fileQueue.addAll(Lists.newArrayList(file.listFiles()));
} else {
fileQueue = matchTheSuffix(file, nameSuffix, fileQueue, fileLis);
}
}
logger.info("得到的文件列表的长度为:{}", fileLis.size());
return fileLis;
} private static LinkedList<File> matchTheSuffix(File file, String nameSuffix, LinkedList<File> fileQueue,
List<File> fileList) {
String fileName = file.getName();
if (StringUtils.isNotEmpty(nameSuffix)
&& StringUtils.endsWith(fileName.toLowerCase(), nameSuffix.toLowerCase())) {
// 当有后缀名时,匹配的放入队列
fileList.add(file);
} else if (StringUtils.isEmpty(nameSuffix)) {
// 没有匹配名时,所有的都放入队列
fileList.add(file);
}
return fileQueue;
} public static String deleteFilesFromMultiMap(HashMultimap<Long, String> duplicateFileMultimap) {
Set<Long> md5s = duplicateFileMultimap.keySet();
StringBuilder sb = new StringBuilder();
int count = 0;
for (long md5 : md5s) {
ArrayList<String> filenames = Lists.newArrayList(duplicateFileMultimap.get(md5));
sb.append("以下重复文件:\n");
for (String filename : filenames) {
sb.append(filename).append("\n");
}
String firstDupFile = filenames.get(0);
File file = new File(firstDupFile);
boolean delete = file.delete();
if (delete) {
logger.info("文件{}已被删除", firstDupFile);
sb.append("文件").append(firstDupFile).append("已被删除");
count++;
} else {
logger.error("文件{}删除失败", firstDupFile);
}
}
sb.append("共删除").append(count).append("个文件");
logger.info("共删除{}个文件",count);
return sb.toString();
}
}
4.照片拍摄时间提取工具
package cuishining.util; import java.io.File;
import java.io.IOException;
import java.util.Date; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifDirectoryBase; /**
* Created by shining.cui on 2016/7/23.
*/
public class JpgFileUtil {
private static final Logger logger = LoggerFactory.getLogger(JpgFileUtil.class); public static String getPhotoTimeStr(File file) {
Date date = null;
try {
Metadata metadata = ImageMetadataReader.readMetadata(file);
for (Directory dr : metadata.getDirectories()) {
if (dr.containsTag(ExifDirectoryBase.TAG_DATETIME_ORIGINAL)) {
date = dr.getDate(ExifDirectoryBase.TAG_DATETIME_ORIGINAL);
}
if (date != null) {
return TimeUtil.parseDateFromJpgFileDate(date);
}
}
} catch (ImageProcessingException e) {
logger.error("jpg文件读取错误", e);
} catch (IOException e) {
logger.error("发生io错误", e);
}
return null;
}
}
5.时间工具 package cuishining.util; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import java.util.Date; /**
* Created by shining.cui on 2016/7/25.
*/
public class TimeUtil {
private static final String timeFormatStr = "yyyy-MM-dd HH-mm-ss";
private static final String timeFormatStr1 = "yyyy-MM-dd HH:mm:ss"; public static String parseDateFromSystemDate(Date date) {
return new DateTime(date).toString(timeFormatStr1);
} public static String parseDateFromJpgFileDate(Date date) {
return new DateTime(date, DateTimeZone.UTC).toString(timeFormatStr);
}
}
总结
项目总体思想是根据md5删除重复照片,然后根据拍摄时间重命名之后移动到统一文件夹内。可以在同一个文件夹内按照拍摄时间浏览照片,比较有历史感,容易唤起回忆。
FileDetector-基于java开发的照片整理工具的更多相关文章
- 纯 Java 开发 WebService 调用测试工具(wsCaller.jar)
注:本文来自hacpai.com:Tanken的<纯 Java 开发 WebService 调用测试工具(wsCaller.jar)>的文章 基于 Java 开发的 WebService ...
- 基于java开发的在线题库系统tamguo
简介 探果网(简称tamguo)是基于java开发的在线题库系统,包括 在线访问 后台运营 会员中心 书籍中心 管理员账号:system 密码:123456 因为线上数据和测试数据没有做到隔离,作者已 ...
- 免费开源数字货币交易所——基于Java开发的比特币交易所 | BTC交易所 | ETH交易所 | 数字货币交易所
本项目是基于Java开发的比特币交易所 | BTC交易所 | ETH交易所 | 数字货币交易所 | 交易平台 | 撮合交易引擎.本项目基于SpringCloud微服务开发,可用来搭建和二次开发数字货币 ...
- Java 开发环境配置--eclipse工具进行java开发
Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境. Windows 上安装开发环境 Linux 上安装开发环境 安装 Eclipse 运行 Java Cloud Studio ...
- 基于Java的简易表达式解析工具(一)
最近需要用到相关表达式解析的工具,然后去网上搜索,找到了一个用C#写的表达式解析工具,仔细看了功能后发现,这正是我需要的,如果我能将它改造成基于Java语言的方式,岂不是更好吗,所以花了一段时间,把网 ...
- 来认识一下venus-init——一个让你仅需一个命令开始Java开发的命令行工具
源代码地址: Github仓库地址 个人网站:个人网站地址 前言 不知道你是否有过这样的经历.不管你是什么岗位,前端也好,后端也罢,想去了解一下Java开发到底是什么样的,它是不是真的跟传说中的一样. ...
- Java开发常用的在线工具
原文出处: hollischuang(@Hollis_Chuang) 作为一个Java开发人员,经常要和各种各样的工具打交道,除了我们常用的IDE工具以外,其实还有很多工具是我们在日常开发及学习过程中 ...
- [开发工具]Java开发常用的在线工具
注明: 本文转自http://www.hollischuang.com/archives/1459.作为一个Java开发人员,经常要和各种各样的工具打交道,除了我们常用的IDE工具以外,其实还有很多工 ...
- 基于Java的简易表达式解析工具(二)
之前简单的介绍了这个基于Java表达式解析工具,现在把代码分享给大家,希望帮助到有需要的人们,这个分享代码中依赖了一些其他的类,这些类大家可以根据自己的情况进行导入,无非就是写字符串处理工具类,日期处 ...
随机推荐
- raft协议安全性保证
分布式系统中主要的问题就是如何保持节点状态的一致性,不论发生任何failure,只要集群中大部分的节点可以正常工作,则这些节点具有相同的状态,保持一致,在client看来相当于一台机器. 一致性问题本 ...
- UnityShader之遮挡透明
好久没写博客了,最近在学shader,不得不说,shader真的非常美妙,我沉迷其中无法自拔= = 之前做过一个遮挡透明的功能,当物体遮挡住主角时,该物体会变成半透明显示出主角.这次同样是遮挡透明的功 ...
- Scrapy常用命令行工具
查看所有命令 scrapy -h 查看帮助信息 scapy --help 查看版本信息 (venv)ql@ql:~$ scrapy version Scrapy 1.1.2 (venv)ql@ql:~ ...
- [补档]从OI学麻将
背景 作为一名川娃子,怎么能不懂麻将呢= = T1 さきなに~~ [咲 -Saki-] 天才麻将少女什么编 题目 二十一世纪,世界上的麻将竞技人数超过一亿,日本每年也有大规模的全国大赛来对麻将选手进行 ...
- Pandas 操作
一.Series的创建: pd.Series([ 数据 ]) In [17]: import pandas as pd In [18]: import numpy as np In [19]: s = ...
- 移动Web学习笔记(第1天)-bootstrap框架的使用
移动web前言 移动web : 移动端手机浏览器或者微信里面浏览的网页 移动APP : 手机上需要下载安装的应用程序 1. 移动web介绍 1.1 3 天 响应式开发 一套代码运行多个终端 优点:开发 ...
- Jmeter关联,正则表达式提取器使用
一.Jmeter关联的方式: Jmeter中关联可以在需要获取数据的请求上 右键-->后置处理器 选择需要的关联方式,如下图有很多种方法可以提取动态变化数据: 二.正则表达式提取器: 1.比如 ...
- 微软认知服务 Luis
学习认知服务 Luis(Language understand intellgence service) 简述: 开发者可以通过Luis开发可以理解人类语言的只能应用,学习人类语言分析语义. 利用这个 ...
- 专治不会看源码的毛病--spring源码解析AOP篇
昨天有个大牛说我啰嗦,眼光比较细碎,看不到重点.太他爷爷的有道理了!要说看人品,还是女孩子强一些.原来记得看到一个男孩子的抱怨,说怎么两人刚刚开始在一起,女孩子在心里就已经和他过完了一辈子.哥哥们,不 ...
- C#基础课堂笔记
第三章:运算符和表达式 1.认识运算符:运算符又叫操作符,是一个运用于运算的符号,它作用于一个或多个操作数 运算符的分类: (1)按操作数量 一元(目)运算符 作用于一个操作数 二 ...