diff --git a/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java b/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java index 6a4b500..c6f7871 100644 --- a/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java +++ b/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java @@ -3,12 +3,9 @@ */ package org.forkalsrud.album.exif; -import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.text.DecimalFormat; -import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -18,31 +15,15 @@ import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; import org.forkalsrud.album.db.DirectoryDatabase; import org.forkalsrud.album.db.DirectoryProps; -import com.drew.imaging.jpeg.JpegMetadataReader; -import com.drew.metadata.Directory; -import com.drew.metadata.Metadata; -import com.drew.metadata.MetadataException; -import com.drew.metadata.exif.ExifDirectory; -import com.drew.metadata.jpeg.JpegDirectory; - /** * @author knut */ public class DirectoryEntry extends Entry { - private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DirectoryEntry.class); - final static String CACHE_FILE = "cache.properties"; final static String OVERRIDE_FILE = "album.properties"; @@ -59,7 +40,7 @@ public class DirectoryEntry extends Entry { throw new RuntimeException("Use DirectoryEntry only for directories: " + file); } this.db = db; - cache = db.get(file.getAbsolutePath()); + cache = db.load(file.getAbsolutePath()); if (cache == null) { cache = new DirectoryProps(); } @@ -114,12 +95,12 @@ public class DirectoryEntry extends Entry { return; } try { - Properties cachedProps = isCacheCurrent() ? cache : generateCache(); - Properties combined = new Properties(); + DirectoryProps cachedProps = isCacheCurrent() ? cache : generateCache(); + DirectoryProps combined = new DirectoryProps(); combined.putAll(cachedProps); File override = new File(file, OVERRIDE_FILE); if (override.exists() && override.isFile() && override.canRead()) { - Properties overrideProps = loadFromFile(override); + DirectoryProps overrideProps = loadFromFile(override); combined.putAll(overrideProps); } populate(combined); @@ -135,162 +116,24 @@ public class DirectoryEntry extends Entry { return file.lastModified() <= cache.getTimestamp(); } - Properties loadFromFile(File propFile) throws IOException { + DirectoryProps loadFromFile(File propFile) throws IOException { - Properties props = new Properties(); + DirectoryProps props = new DirectoryProps(); FileInputStream fis = new FileInputStream(propFile); props.load(fis); fis.close(); + props.setTimestamp(propFile.lastModified()); return props; } - Properties generateCache() throws IOException { + DirectoryProps generateCache() throws IOException { - long start = System.currentTimeMillis(); - DirectoryProps props = new DirectoryProps(); - generateFileEntries(props); - long end = System.currentTimeMillis(); - props.setTimestamp(end); + DirectoryProps props = new DirectoryMetadataGenerator(file).generate(); db.store(file.getAbsolutePath(), props); - log.info("Time to generate properties for " + file.getPath() + ": " + (end - start)/1000d + " s"); return props; } - void generateFileEntries(Properties props) throws IOException { - - File[] files = file.listFiles(); - for (File f : files) { - - String name = f.getName(); - if (f.isDirectory()) { - if ("CVS".equals(name)) { - continue; - } - if (f.isHidden()) { - continue; - } - generateDirectoryProperties(props, f); - continue; - } - if (file.isHidden()) { - continue; - } - if (CACHE_FILE.equals(name) || OVERRIDE_FILE.equals(name)) { - // cache.properties and album.properties - continue; - } - if (name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".JPG")) { - String base = "file." + f.getName() + "."; - Map p = generateThumbnailProperties(f); - for (Map.Entry e : p.entrySet()) { - props.setProperty(base + e.getKey(), e.getValue()); - } - } - } - } - - - private void generateDirectoryProperties(Properties props, File f) { - - String name = f.getName(); - props.setProperty("dir." + name, "present"); - } - - private Map generateThumbnailProperties(File f) - throws IOException { - HashMap props = new HashMap(); - String name = f.getName(); - boolean hasDate = false; - boolean hasOrientation = false; - boolean hasDim = false; - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); - NumberFormat nf = new DecimalFormat("0"); - - Metadata metadata; - try { - metadata = JpegMetadataReader.readMetadata(f); - Directory exifDirectory = metadata.getDirectory(ExifDirectory.class); - if (exifDirectory.containsTag(ExifDirectory.TAG_ORIENTATION)) { - int orientation = exifDirectory.getInt(ExifDirectory.TAG_ORIENTATION); - props.put("orientation", nf.format(orientation)); - hasOrientation = true; - } - if (exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_WIDTH) && - exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT)) { - int width = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH); - int height = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT); - props.put("dimensions", new Dimension(width, height).toString()); - hasDim = true; - } - if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) { - Date captureDate = getExifDate(exifDirectory, ExifDirectory.TAG_DATETIME_ORIGINAL); - if (captureDate != null) { - props.put("captureDate", sdf.format(captureDate)); - hasDate = true; - } - } - if (exifDirectory.containsTag(ExifDirectory.TAG_USER_COMMENT)) { - String comment = exifDirectory.getString(ExifDirectory.TAG_USER_COMMENT); - props.put("comment", comment); - } - Directory jpegDirectory = metadata.getDirectory(JpegDirectory.class); - if (jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_WIDTH) && - jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT)) { - int width = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH); - int height = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT); - props.put("dimensions", new Dimension(width, height).toString()); - hasDim = true; - } - } catch (Exception e) { - throw new RuntimeException("problem reading file " + f.getPath(), e); - } - props.put("etag", Integer.toHexString(name.hashCode() + Long.valueOf(f.lastModified()).hashCode())); - if (!hasDate) { - props.put("captureDate", sdf.format(new Date(f.lastModified()))); - } - if (!hasOrientation) { - props.put("orientation", "1"); - } - if (!hasDim) { - Dimension dim = decodeImageForDimensions(f); - if (dim != null) { - props.put("dimensions", dim.toString()); - hasDim = true; - } - } - return props; - } - - private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException { - - String dateStr = (String)exifDirectory.getObject(tagName); - if (" : : : : ".equals(dateStr)) { - return null; - } - return exifDirectory.getDate(tagName); - } - - Dimension decodeImageForDimensions(File file) throws IOException { - - String suffix = null; - String name = file.getName(); - if (name.indexOf('.') > 0) { - suffix = name.substring(name.lastIndexOf('.') + 1); - } - Iterator readers = ImageIO.getImageReadersBySuffix(suffix); - if (!readers.hasNext()) { - return null; - } - ImageReader reader = readers.next(); - ImageInputStream iis = ImageIO.createImageInputStream(file); - reader.setInput(iis, true); - ImageReadParam param = reader.getDefaultReadParam(); - BufferedImage img = reader.read(0, param); - iis.close(); - return new Dimension(img.getWidth(), img.getHeight()); - } - private void sort() { Collections.sort(children, sort); @@ -312,7 +155,7 @@ public class DirectoryEntry extends Entry { } - private void populate(Properties props) throws ParseException { + private void populate(DirectoryProps props) throws ParseException { String coverFileName = props.getProperty("cover"); String caption = props.getProperty("caption"); diff --git a/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java b/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java new file mode 100644 index 0000000..fa77b8d --- /dev/null +++ b/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java @@ -0,0 +1,190 @@ +package org.forkalsrud.album.exif; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import org.forkalsrud.album.db.DirectoryProps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.drew.imaging.jpeg.JpegMetadataReader; +import com.drew.metadata.Directory; +import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.ExifDirectory; +import com.drew.metadata.jpeg.JpegDirectory; + +public class DirectoryMetadataGenerator { + + private static Logger log = LoggerFactory.getLogger(DirectoryMetadataGenerator.class); + final static String CACHE_FILE = "cache.properties"; + final static String OVERRIDE_FILE = "album.properties"; + + File dir; + + + public DirectoryMetadataGenerator(File f) { + this.dir = f; + } + + + public DirectoryProps generate() throws IOException { + + long start = System.currentTimeMillis(); + DirectoryProps props = new DirectoryProps(); + generateFileEntries(props); + long end = System.currentTimeMillis(); + props.setTimestamp(end); + log.info("Time to generate properties for " + dir.getPath() + ": " + (end - start)/1000d + " s"); + return props; + } + + + private void generateFileEntries(Properties props) throws IOException { + + File[] files = dir.listFiles(); + for (File f : files) { + + String name = f.getName(); + if (f.isDirectory()) { + if ("CVS".equals(name)) { + continue; + } + if (f.isHidden()) { + continue; + } + generateDirectoryProperties(props, f); + continue; + } + if (f.isHidden()) { + continue; + } + if (CACHE_FILE.equals(name) || OVERRIDE_FILE.equals(name)) { + // cache.properties and album.properties + continue; + } + if (name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".JPG")) { + String base = "file." + f.getName() + "."; + Map p = generateThumbnailProperties(f); + for (Map.Entry e : p.entrySet()) { + props.setProperty(base + e.getKey(), e.getValue()); + } + } + } + } + + + private void generateDirectoryProperties(Properties props, File f) { + + String name = f.getName(); + props.setProperty("dir." + name, "present"); + } + + private Map generateThumbnailProperties(File f) throws IOException { + + HashMap props = new HashMap(); + String name = f.getName(); + boolean hasDate = false; + boolean hasOrientation = false; + boolean hasDim = false; + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); + NumberFormat nf = new DecimalFormat("0"); + + Metadata metadata; + try { + metadata = JpegMetadataReader.readMetadata(f); + Directory exifDirectory = metadata.getDirectory(ExifDirectory.class); + if (exifDirectory.containsTag(ExifDirectory.TAG_ORIENTATION)) { + int orientation = exifDirectory.getInt(ExifDirectory.TAG_ORIENTATION); + props.put("orientation", nf.format(orientation)); + hasOrientation = true; + } + if (exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_WIDTH) && + exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT)) { + int width = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH); + int height = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT); + props.put("dimensions", new Dimension(width, height).toString()); + hasDim = true; + } + if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) { + Date captureDate = getExifDate(exifDirectory, ExifDirectory.TAG_DATETIME_ORIGINAL); + if (captureDate != null) { + props.put("captureDate", sdf.format(captureDate)); + hasDate = true; + } + } + if (exifDirectory.containsTag(ExifDirectory.TAG_USER_COMMENT)) { + String comment = exifDirectory.getString(ExifDirectory.TAG_USER_COMMENT); + props.put("comment", comment); + } + Directory jpegDirectory = metadata.getDirectory(JpegDirectory.class); + if (jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_WIDTH) && + jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT)) { + int width = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH); + int height = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT); + props.put("dimensions", new Dimension(width, height).toString()); + hasDim = true; + } + } catch (Exception e) { + throw new RuntimeException("problem reading file " + f.getPath(), e); + } + props.put("etag", Integer.toHexString(name.hashCode() + Long.valueOf(f.lastModified()).hashCode())); + if (!hasDate) { + props.put("captureDate", sdf.format(new Date(f.lastModified()))); + } + if (!hasOrientation) { + props.put("orientation", "1"); + } + if (!hasDim) { + Dimension dim = decodeImageForDimensions(f); + if (dim != null) { + props.put("dimensions", dim.toString()); + hasDim = true; + } + } + return props; + } + + private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException { + + String dateStr = (String)exifDirectory.getObject(tagName); + if (" : : : : ".equals(dateStr)) { + return null; + } + return exifDirectory.getDate(tagName); + } + + private Dimension decodeImageForDimensions(File file) throws IOException { + + String suffix = null; + String name = file.getName(); + if (name.indexOf('.') > 0) { + suffix = name.substring(name.lastIndexOf('.') + 1); + } + Iterator readers = ImageIO.getImageReadersBySuffix(suffix); + if (!readers.hasNext()) { + return null; + } + ImageReader reader = readers.next(); + ImageInputStream iis = ImageIO.createImageInputStream(file); + reader.setInput(iis, true); + ImageReadParam param = reader.getDefaultReadParam(); + BufferedImage img = reader.read(0, param); + iis.close(); + return new Dimension(img.getWidth(), img.getHeight()); + } +}