001package net.filebot.media; 002 003import static net.filebot.Logging.*; 004 005import java.io.File; 006import java.io.FileFilter; 007import java.io.IOException; 008import java.time.Instant; 009import java.util.EnumMap; 010import java.util.LinkedHashMap; 011import java.util.Map; 012import java.util.Optional; 013import java.util.function.Function; 014import java.util.function.Predicate; 015import java.util.stream.Stream; 016 017import com.drew.imaging.ImageMetadataReader; 018import com.drew.imaging.ImageProcessingException; 019import com.drew.lang.GeoLocation; 020import com.drew.metadata.Directory; 021import com.drew.metadata.Metadata; 022import com.drew.metadata.Tag; 023import com.drew.metadata.exif.ExifIFD0Directory; 024import com.drew.metadata.exif.ExifSubIFDDirectory; 025import com.drew.metadata.exif.GpsDirectory; 026import com.drew.metadata.file.FileSystemDirectory; 027 028import net.filebot.util.ExtensionFileFilter; 029import net.filebot.web.Geocode; 030import net.filebot.web.Geocode.AddressComponent; 031 032public class ImageMetadata { 033 034 private final Metadata metadata; 035 036 public ImageMetadata(File file) throws ImageProcessingException, IOException { 037 if (!SUPPORTED_FILE_TYPES.accept(file)) { 038 throw new IllegalArgumentException("Image type not supported: " + file); 039 } 040 041 this.metadata = ImageMetadataReader.readMetadata(file); 042 } 043 044 public Map<String, String> snapshot() { 045 return snapshot(Tag::getTagName); 046 } 047 048 public Map<String, String> snapshot(Function<Tag, String> key) { 049 return snapshot(key, d -> Stream.of("JPEG", "JFIF", "Interoperability", "Huffman", "File").noneMatch(d.getName()::equals)); 050 } 051 052 public Map<String, String> snapshot(Function<Tag, String> key, Predicate<Directory> accept) { 053 Map<String, String> values = new LinkedHashMap<String, String>(); 054 055 for (Directory directory : metadata.getDirectories()) { 056 if (accept.test(directory)) { 057 for (Tag tag : directory.getTags()) { 058 String v = tag.getDescription(); 059 if (v != null && v.length() > 0) { 060 values.put(key.apply(tag), v); 061 } 062 } 063 } 064 } 065 066 return values; 067 } 068 069 public Optional<String> getName() { 070 return extract(m -> m.getFirstDirectoryOfType(FileSystemDirectory.class)).map(d -> d.getString(FileSystemDirectory.TAG_FILE_NAME)); 071 } 072 073 public Optional<Instant> getDateTaken() { 074 return extract(m -> m.getFirstDirectoryOfType(ExifIFD0Directory.class)).map(d -> d.getDate(ExifSubIFDDirectory.TAG_DATETIME)).map(d -> d.toInstant()); 075 } 076 077 public Optional<Map<CameraProperty, String>> getCameraModel() { 078 return extract(m -> m.getFirstDirectoryOfType(ExifIFD0Directory.class)).map(d -> { 079 String maker = d.getDescription(ExifIFD0Directory.TAG_MAKE); 080 String model = d.getDescription(ExifIFD0Directory.TAG_MODEL); 081 082 Map<CameraProperty, String> camera = new EnumMap<CameraProperty, String>(CameraProperty.class); 083 if (maker != null) { 084 camera.put(CameraProperty.maker, maker); 085 } 086 if (model != null) { 087 camera.put(CameraProperty.model, model); 088 } 089 090 return camera; 091 }).filter(m -> !m.isEmpty()); 092 } 093 094 public enum CameraProperty { 095 maker, model; 096 } 097 098 public Optional<GeoLocation> getLocationTaken() { 099 return extract(m -> m.getFirstDirectoryOfType(GpsDirectory.class)).map(GpsDirectory::getGeoLocation); 100 } 101 102 public Optional<Map<AddressComponent, String>> getLocationTaken(Geocode geocode) { 103 return getLocationTaken().map(position -> { 104 try { 105 return geocode.locate(position.getLatitude(), position.getLongitude()); 106 } catch (Exception e) { 107 trace(e); 108 } 109 return null; 110 }); 111 } 112 113 public <T> Optional<T> extract(Function<Metadata, T> extract) { 114 try { 115 return Optional.ofNullable(extract.apply(metadata)); 116 } catch (Exception e) { 117 debug.finest(cause("Failed to extract image metadata", e)); 118 } 119 return Optional.empty(); 120 } 121 122 public static final FileFilter SUPPORTED_FILE_TYPES = new ExtensionFileFilter("jpg", "jpeg", "heic", "png", "webp", "gif", "ico", "bmp", "tif", "tiff", "psd", "pcx", "raw", "crw", "cr2", "nef", "orf", "raf", "rw2", "rwl", "srw", "arw", "dng", "x3f"); 123 124}