diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efec373 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/bin/ +.classpath +.project +.settings \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..70ae042 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +## 简介 + +一款简单的照片整理的工具,主要用于查看照片的基本信息以及将多个不同目录的照片合并在一起。 + +## 使用事例 + +### 查看帮助 + + ➜ PhotoManager git:(master) ✗ java -jar ./managePhoto.jar + usage: + + java -jar ./managePhoto.jar -- [filePath] + java -jar ./managePhoto.jar --version + java -jar ./managePhoto.jar --help + + such as: + + java -jar ./getImgInfo.jar --version + + java -jar ./managePhoto.jar --show ~/temp/IMG_3555.jpg + +### 查看版本 + + + ➜ PhotoManager git:(master) ✗ java -jar ./managePhoto.jar --version + class com.bihe0832.photo.getPhotoInfo version 1.0.0 (1) + + homepage : https://github.com/bihe0832/PhotoManager + blog : http://blog.bihe0832.com + github : https://github.com/bihe0832 + + +### 查看照片信息 + + ➜ PhotoManager git:(master) ✗ java -jar ./managePhoto.jar --show ~/temp/IMG_3555.jpg + 照片信息如下: + ****************************************************** + 照片的当前名称: IMG_3555.jpg + 照片的当前路径: /Volumes/Document/Document/temp/IMG_3555.jpg + 照片的空间大小: 2020 KB + 照片的像素大小: 3264 pixels * 2448 pixels + 拍摄时当地时间: 2017:05:22 18:19:28 + 拍摄时标准时间: 2017:05:22 10:19:28.00 UTC + 拍摄时地点经纬: 100° 16' 51.73" E ,28° 53' 55.01" N + 拍摄时地点海拔: Sea level:4519 metres + 拍摄时使用设备: Apple (iPhone 6) + ****************************************************** + +### 整理照片 + + ➜ PhotoManager git:(master) ✗ java -jar ./managePhoto.jar --manage /Volumes/Document/Document/temp/1/ /Volumes/Document/Document/temp/5/ + + + ****************************************************** + 照片整理已经开始…… + 如果照片较多,持续时间较长,请耐心等待。 + 整理过程中您可以在目标目录实时查看整理效果 + + + 正在努力整理中,已完成: /Volumes/Document/Document/temp/1/目录下 5 张照片 + + + + ****************************************************** + 照片整理结果如下:共计从: /Volumes/Document/Document/temp/1/整理了 8 张照片,其中: + 整理成功: 8 张 + : 0 张为已经存在的照片 + : 8 张已经按时间重新命名 + : 0 张保留了原名称 + 整理失败: 0 张 + : 0 张照片没有找到 + : 0 张照片解析时间错误,需要手动整理 + ****************************************************** \ No newline at end of file diff --git a/managePhoto.jar b/managePhoto.jar new file mode 100644 index 0000000..e5b0ff4 Binary files /dev/null and b/managePhoto.jar differ diff --git a/managePhoto/.gitignore b/managePhoto/.gitignore new file mode 100644 index 0000000..4e801a7 --- /dev/null +++ b/managePhoto/.gitignore @@ -0,0 +1,3 @@ +/bin/ +.classpath +.project \ No newline at end of file diff --git a/managePhoto/README.md b/managePhoto/README.md new file mode 100644 index 0000000..ea2f986 --- /dev/null +++ b/managePhoto/README.md @@ -0,0 +1,113 @@ +### 查看帮助 + + ➜ getPNGInfo git:(master) ✗ java -jar ./getImgInfo.jar + usage: + + java -jar ./getImgInfo.jar [filePath] + java -jar ./getImgInfo.jar --version + java -jar ./getImgInfo.jar --help + + such as: + + + java -jar ./getImgInfo.jar ./test.png + java -jar ./getImgInfo.jar --version + java -jar ./getImgInfo.jar --help + + after check,the result will be a string json such as: + + {"ret":0,"msg":"图片是否有渐变: true ,图片尺寸为(宽*高): 432 * 168 , 图片类型: 6","hasAlpha":true,"type":6,"width":432,"height":168} + + +### 查看版本 + + ➜ getPNGInfo git:(master) ✗ java -jar ./getImgInfo.jar --version + class com.bihe0832.png.getImgInfo version 1.0.1 (2) + + homepage : https://github.com/bihe0832/getImageInfo + blog : http://blog.bihe0832.com + github : https://github.com/bihe0832 + +### 查看图片信息 + + ➜ getPNGInfo git:(master) ✗ java -jar ./getImgInfo.jar head.jpg + {"ret":0,"msg":"图片是否有渐变: false ,图片尺寸为(宽*高): 344 * 344 , 图片大小: 7 KB,图片类型: 5","hasAlpha":false,"type":5,"width":344,"height":344,"size":7} + + ➜ getPNGInfo git:(master) ✗ java -jar ./getImgInfo.jar head.png + {"ret":0,"msg":"图片是否有渐变: true ,图片尺寸为(宽*高): 344 * 344 , 图片大小: 33 KB,图片类型: 6","hasAlpha":true,"type":6,"width":344,"height":344,"size":33} + + +## 返回参数说明 + +### hasAlpha + +图片是否有透明度,是否有渐变效果 + +### width & height + +图片的宽和高的像素数值 + +### size + +图片的大小 + +### type + +图片的编码方案,具体值参考官网介绍:[https://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html](https://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html),为了方便查看,个人用google翻译做了简单整理。 + +- `TYPE_CUSTOM` = 0 + + 图像类型无法识别,因此它必须是自定义图像。 + +- `TYPE_INT_RGB` = 1 + + 表示具有8位RGB颜色分量的整数像素的图像,他没有alpha值。 + +- `TYPE_INT_ARGB` = 2 + + 表示具有8位RGBA颜色成分的整数像素的图像,她有alpha值。 + +- `TYPE_INT_ARGB_PRE` = 3 + + 表示具有8位RGBA颜色成分的整数像素的图像。 + +- `TYPE_INT_BGR` = 4 + + 表示具有8位RGB颜色分量的图像,对应于Windows或Solaris样式的BGR颜色模型,蓝色,绿色和红色颜色打包成整数像素。 + +- `TYPE_3BYTE_BGR` = 5 + + 表示具有8位RGB颜色分量的图像,对应于Windows风格的BGR颜色模型,蓝色,绿色和红色以3个字节存储。 + +- `TYPE_4BYTE_ABGR` = 6 + + 表示具有蓝色,绿色和红色的8位RGBA颜色分量的图像,存储在3个字节和1个字节的alpha中。 + +- `TYPE_4BYTE_ABGR_PRE` = 7 + + 表示具有蓝色,绿色和红色的8位RGBA颜色分量的图像,存储在3个字节和1个字节的alpha中。 + +- `TYPE_USHORT_565_RGB` = 8 + + 表示具有5-6-5 RGB颜色分量(5位红色,6位绿色,5位蓝色),无alpha的图像。 + +- `TYPE_USHORT_555_RGB` = 9 + + 表示5-5-5 RGB颜色分量(5位红色,5位绿色,5位蓝色)的图像,无alpha。 + +- `TYPE_BYTE_GRAY` = 10 + + 表示无符号字节灰度图像,无索引。 + +- `TYPE_USHORT_GRAY` = 11 + + 表示无符号短灰度图像,非索引)。 + +- `TYPE_BYTE_BINARY` = 12 + + 表示不透明的字节打包的1,2或4位图像。 + +- `TYPE_BYTE_INDEXED` = 13 + + 表示索引的字节图像。 + diff --git a/managePhoto/libs/metadata-extractor-2.8.1.jar b/managePhoto/libs/metadata-extractor-2.8.1.jar new file mode 100644 index 0000000..4a3faa9 Binary files /dev/null and b/managePhoto/libs/metadata-extractor-2.8.1.jar differ diff --git a/managePhoto/libs/xmpcore-5.1.2.jar b/managePhoto/libs/xmpcore-5.1.2.jar new file mode 100644 index 0000000..ecd5db1 Binary files /dev/null and b/managePhoto/libs/xmpcore-5.1.2.jar differ diff --git a/managePhoto/src/com/bihe0832/photo/PhotoInfo.java b/managePhoto/src/com/bihe0832/photo/PhotoInfo.java new file mode 100644 index 0000000..1524f6d --- /dev/null +++ b/managePhoto/src/com/bihe0832/photo/PhotoInfo.java @@ -0,0 +1,89 @@ +package com.bihe0832.photo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +public class PhotoInfo { + + private static final String MAKE = "Make"; + private static final String MODEL = "Model"; + private static final String DATE_TIME_ORIGINAL = "Date/Time Original"; + private static final String DATE_STAMP = "GPS Date Stamp"; + private static final String TIME_STAMP = "GPS Time-Stamp"; + private static final String WIDTH = "Image Width"; + private static final String HEIGHT = "Image Height"; + private static final String LATITUDE_REF = "GPS Latitude Ref"; + private static final String LATITUDE_VALUE = "GPS Latitude"; + private static final String LONGTITUDE_REF = "GPS Longitude Ref"; + private static final String LONGTITUDE_VALUE = "GPS Longitude"; + private static final String ALTITUDE_REF = "GPS Altitude Ref"; + private static final String ALTITUDE_VALUE = "GPS Altitude"; + + public HashMap valueList = new HashMap(); + + public static final ArrayList keyList = new ArrayList( + Arrays.asList(MAKE, MODEL, DATE_TIME_ORIGINAL, DATE_STAMP, TIME_STAMP, WIDTH, HEIGHT, + LATITUDE_REF, LONGTITUDE_REF, LATITUDE_VALUE, LONGTITUDE_VALUE,ALTITUDE_REF,ALTITUDE_VALUE)); + + private String getValueByKey(String key) { + if(valueList.containsKey(key)){ + String value = valueList.get(key); + return value.trim(); + }else{ + return ""; + } + } + + public String getMake() { + return getValueByKey(MAKE); + } + public String getModel() { + return getValueByKey(MODEL); + } + public String getDateTimeOriginal() { + return getValueByKey( DATE_TIME_ORIGINAL); + } + public String getDateTimeStamp() { + return getValueByKey(DATE_STAMP) + " " + getValueByKey(TIME_STAMP); + } + public String getWidth() { + return getValueByKey( WIDTH); + } + public String getHeight() { + return getValueByKey( HEIGHT); + } + public String getLatitudeRef() { + return getValueByKey(LATITUDE_REF); + } + + public String getLatitudeValue() { + return getValueByKey( LATITUDE_VALUE); + } + public String getLatitude() { + return getLatitudeValue() + " " + getLatitudeRef(); + } + + public String getLongitudeRef() { + return getValueByKey( LONGTITUDE_REF); + } + + public String getLongitudeValue() { + return getValueByKey( LONGTITUDE_VALUE); + } + + public String getLongitude() { + return getLongitudeValue() + " " + getLongitudeRef(); + } + public String getAltitudeRef() { + return getValueByKey( ALTITUDE_REF); + } + + public String getAltitudeValue() { + return getValueByKey( ALTITUDE_VALUE); + } + + public String getAltitude() { + return getAltitudeRef() + ":" + getAltitudeValue() ; + } +} diff --git a/managePhoto/src/com/bihe0832/photo/getPhotoInfo.java b/managePhoto/src/com/bihe0832/photo/getPhotoInfo.java new file mode 100644 index 0000000..97d8188 --- /dev/null +++ b/managePhoto/src/com/bihe0832/photo/getPhotoInfo.java @@ -0,0 +1,317 @@ +package com.bihe0832.photo; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Directory; +import com.drew.metadata.Metadata; +import com.drew.metadata.Tag; + +public class getPhotoInfo { + private static final int VERSION_CODE = 1; + private static final String VERSION_NAME = "1.0.0"; + private static final String HELP_PAGE_GENERAL = "help.txt"; + private static final String VERSION_PAGE_GENERAL = "help_version.txt"; + + private static final int RESULT_FAIL_NOT_FOUND = -1; + private static final int RESULT_FAIL_COPY_EXCEPTION = -2; + private static final int RESULT_SUCC_RENAME = 1; + private static final int RESULT_SUCC_NOINFO = 2; + private static final int RESULT_SUCC_SAME = 3; + + + private static final ArrayList IMG_PREFIX_LIST = new ArrayList(Arrays.asList("jpg","jpeg","png")); + + public static void main(String[] params) throws Exception { + if ((params.length == 0)) { + printUsage(HELP_PAGE_GENERAL); + return; + } + if (params[0].toLowerCase().startsWith("--help")) { + printUsage(HELP_PAGE_GENERAL); + return; + } else if (params[0].toLowerCase().startsWith("--version")) { + System.out.println(getPhotoInfo.class.toString() + " version " + VERSION_NAME + " (" + VERSION_CODE + ")\n"); + printUsage(VERSION_PAGE_GENERAL); + return; + } else if (params[0].toLowerCase().startsWith("--show")) { + if(params.length > 1){ + showPhotoInfo(params[1]); + }else{ + printUsage(HELP_PAGE_GENERAL); + } + return; + }else if (params[0].toLowerCase().startsWith("--manage")) { + if(params.length > 2){ + copyPhoto(params[1],params[2]); + }else{ + printUsage(HELP_PAGE_GENERAL); + } + return; + } else{ + printUsage(HELP_PAGE_GENERAL); + return; + } + } + + private static void showPhotoInfo(String filepath){ + File tempImg = new File(filepath); + if(tempImg.exists()){ + try { + PhotoInfo photoInfo = getPhotoInfoByPath(filepath); + System.out.println( + "照片信息如下:\n"+ + "******************************************************\n"+ + "照片的当前名称: "+ tempImg.getName() + "\n"+ + "照片的当前路径: "+ tempImg.getAbsolutePath() + "\n"+ + "照片的空间大小: "+ tempImg.length() / 1024 + " KB" + "\n"+ + "照片的像素大小: "+ photoInfo.getWidth() + " * " + photoInfo.getHeight() + "\n"+ + "拍摄时当地时间: "+ photoInfo.getDateTimeOriginal() + "\n"+ + "拍摄时标准时间: "+ photoInfo.getDateTimeStamp() + "\n"+ + "拍摄时地点经纬: "+ photoInfo.getLongitude() + " ," + photoInfo.getLatitude() + "\n"+ + "拍摄时地点海拔: "+ photoInfo.getAltitude() + "\n" + + "拍摄时使用设备: "+ photoInfo.getMake() + " (" +photoInfo.getModel() + ")\n"+ + "******************************************************\n" + ); + } catch (Exception e) { + e.printStackTrace(); + } + }else{ + System.out.println("照片:" + filepath + " 不存在"); + } + } + + private static void copyPhoto(String sourceFolder,String targetFolder){ + int succRename = 0; + int succNoInfo = 0; + int succSame = 0; + int failNotFound = 0; + int failException = 0; + System.out.println( + "\n\n******************************************************\n"+ + "照片整理已经开始……\n"+ + "\t如果照片较多,持续时间较长,请耐心等待。\n"+ + "\t整理过程中您可以在目标目录实时查看整理效果\n\n"); + File[] listFiles = new File(sourceFolder).listFiles(); + for(File f: listFiles){ + if(f.isFile() ){ + if(IMG_PREFIX_LIST.contains(f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf(".") + 1).toLowerCase())){ + switch(copyPhotoOnce(f.getAbsolutePath(),targetFolder)){ + case RESULT_SUCC_NOINFO: + succNoInfo++; + f.delete(); + break; + case RESULT_SUCC_RENAME: + succRename++; + f.delete(); + break; + case RESULT_SUCC_SAME: + succSame++; + f.delete(); + break; + case RESULT_FAIL_NOT_FOUND: + failNotFound++; + break; + case RESULT_FAIL_COPY_EXCEPTION: + failException++; + break; + } + if((succRename + succNoInfo + succSame + failNotFound + failException)%5 == 0){ + System.out.println("正在努力整理中,已完成: "+ sourceFolder + "目录下 "+ (succRename + succNoInfo + succSame + failNotFound + failException) +" 张照片\n"); + } + } + }else if(f.isDirectory()){ + copyPhoto(f.getAbsolutePath(), targetFolder); + } + } + + System.out.println( + "\n\n******************************************************\n"+ + "照片整理结果如下:共计从: "+ sourceFolder + "整理了 "+ (succRename + succNoInfo + succSame + failNotFound + failException) +" 张照片,其中:\n"+ + "整理成功: "+ (succRename + succNoInfo + succSame) + " 张\n"+ + "\t: "+ succSame + " 张为已经存在的照片\n"+ + "\t: "+ succRename + " 张已经按时间重新命名\n"+ + "\t: "+ succNoInfo + " 张保留了原名称\n"+ + "整理失败: "+ (failNotFound + failException) + " 张\n"+ + "\t: "+ failNotFound + " 张照片没有找到\n"+ + "\t: "+ failException + " 张照片解析时间错误,需要手动整理\n"+ + "******************************************************\n" + ); + } + + private static int copyPhotoOnce(String sourcePath,String targetFolder){ + int result = 0; + File sourceImg = new File(sourcePath); + File targetImg = null; + String prefix= sourcePath.substring(sourcePath.lastIndexOf(".") + 1); + int imgNum = 0; + if(sourceImg.exists()){ + try { + PhotoInfo photoInfo = getPhotoInfoByPath(sourcePath); + String targetBasePath = ""; + if(photoInfo.getDateTimeOriginal().length() > 0){ + targetBasePath = targetFolder + photoInfo.getDateTimeOriginal(); + result = RESULT_SUCC_RENAME; + }else{ + targetBasePath = targetFolder + sourceImg.getName().substring(0,sourceImg.getName().lastIndexOf(".")); + result = RESULT_SUCC_NOINFO; + } + targetBasePath = targetBasePath.replace(":","-").replace(" ","_"); + String targetPath = targetBasePath; + do{ + targetPath = targetPath + "."+ prefix; + targetImg = new File(targetPath); + if(targetImg.exists()){ + String targetImgMd5= getFileMD5(targetImg); + String sourceImgMd5= getFileMD5(sourceImg); + if(targetImgMd5.equalsIgnoreCase(sourceImgMd5)){ + targetImg.delete(); + result = RESULT_SUCC_SAME; + } + } + imgNum++; + targetPath = targetBasePath +"-[" + imgNum+ "]"; + }while(targetImg.exists()); + Files.copy(sourceImg.toPath(),targetImg.toPath()); + } catch (Exception e) { + System.out.println("照片:" + sourcePath + " 整理失败" ); + e.printStackTrace(); + result = RESULT_FAIL_COPY_EXCEPTION; + } + }else{ + System.out.println("照片:" + sourcePath + " 不存在"); + result = RESULT_FAIL_NOT_FOUND; + } + return result; + } + + private static void printUsage(String page) { + try (BufferedReader in = + new BufferedReader( + new InputStreamReader( + getPhotoInfo.class.getResourceAsStream(page), + StandardCharsets.UTF_8))) { + String line; + while ((line = in.readLine()) != null) { + System.out.println(line); + } + } catch (IOException e) { + throw new RuntimeException("Failed to read " + page + " resource"); + } + } + + private static PhotoInfo getPhotoInfoByPath(String imgFile) { + PhotoInfo photoInfo = new PhotoInfo(); + InputStream is = null; + try { + is = new FileInputStream(imgFile); + } catch (FileNotFoundException e1) { + e1.printStackTrace(); + return photoInfo; + } + + try { + Metadata metadata = ImageMetadataReader.readMetadata(is); + Iterable iterable = metadata.getDirectories(); + for (Iterator iter = iterable.iterator();iter.hasNext();) { + Directory dr = iter.next(); + Collection tags = dr.getTags(); + for (Tag tag : tags) { + if(PhotoInfo.keyList.contains(tag.getTagName())){ + photoInfo.valueList.put(tag.getTagName(), tag.getDescription()); + } +// System.out.println(tag.getTagName() +":" + tag.getDescription()); + } + } + } catch (ImageProcessingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return photoInfo; + } + + private static String getFileMD5(File sourceFile) { + String ret = ""; + if (sourceFile.exists() && sourceFile.length() > 0) { + BufferedInputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(sourceFile)); + ret = getInputStreamMd5(is); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (OutOfMemoryError e) { + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + return ret; + } + + private static String getInputStreamMd5(InputStream is) { + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + + byte[] buffer = new byte[4196]; + int len; + int readSize = 0; + while ((len = is.read(buffer, 0, buffer.length)) != -1) { + if (len > 0) { + md5.update(buffer, 0, len); + readSize += len; + } + } + if (readSize == 0) + return ""; + byte[] md5Bytes = md5.digest(); + return bytesToHexString(md5Bytes); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return ""; + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + private static String bytesToHexString(byte md5Bytes[]) { + if (md5Bytes != null && md5Bytes.length == 16) { + StringBuffer hexString = new StringBuffer(); + String tempStr = ""; + for (int i=0; i [filePath] + java -jar ./managePhoto.jar --version + java -jar ./managePhoto.jar --help + +such as: + + java -jar ./getImgInfo.jar --version + + java -jar ./managePhoto.jar --show ~/temp/IMG_3555.jpg + + the result will be such as: + + 照片信息如下: + ****************************************************** + 照片的当前名称: IMG_3555.jpg + 照片的当前路径: /Volumes/Document/Document/temp/IMG_3555.jpg + 照片的空间大小: 2020 KB + 照片的像素大小: 3264 pixels * 2448 pixels + 拍摄时当地时间: 2017:05:22 18:19:28 + 拍摄时标准时间: 2017:05:22 10:19:28.00 UTC + 拍摄时地点经纬: 100° 16' 51.73" E ,28° 53' 55.01" N + 拍摄时地点海拔: Sea level:4519 metres + 拍摄时使用设备: Apple (iPhone 6) + ****************************************************** + + java -jar ./managePhoto.jar --manage /Volumes/Document/Document/temp/1/ /Volumes/Document/Document/temp/5/ + + the result will be such as: + + ****************************************************** + 照片整理已经开始…… + 如果照片较多,持续时间较长,请耐心等待。 + 整理过程中您可以在目标目录实时查看整理效果 + + + 正在努力整理中,已完成: /Volumes/Document/Document/temp/1/目录下 5 张照片 + + + + ****************************************************** + 照片整理结果如下:共计从: /Volumes/Document/Document/temp/1/整理了 8 张照片,其中: + 整理成功: 8 张 + : 0 张为已经存在的照片 + : 8 张已经按时间重新命名 + : 0 张保留了原名称 + 整理失败: 0 张 + : 0 张照片没有找到 + : 0 张照片解析时间错误,需要手动整理 + ****************************************************** \ No newline at end of file diff --git a/managePhoto/src/com/bihe0832/photo/help_version.txt b/managePhoto/src/com/bihe0832/photo/help_version.txt new file mode 100644 index 0000000..42e9922 --- /dev/null +++ b/managePhoto/src/com/bihe0832/photo/help_version.txt @@ -0,0 +1,3 @@ +homepage : https://github.com/bihe0832/PhotoManager +blog : http://blog.bihe0832.com +github : https://github.com/bihe0832 \ No newline at end of file