Refactoring the file/directory parsing code away from the properties model of the metadata.

This commit is contained in:
Knut Forkalsrud 2010-05-23 13:13:32 -07:00
parent 72c827c3f2
commit cad37df18b
2 changed files with 200 additions and 167 deletions

View file

@ -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<String, String> p = generateThumbnailProperties(f);
for (Map.Entry<String, String> 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<String, String> generateThumbnailProperties(File f)
throws IOException {
HashMap<String, String> props = new HashMap<String, String>();
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<ImageReader> 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");

View file

@ -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<String, String> p = generateThumbnailProperties(f);
for (Map.Entry<String, String> 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<String, String> generateThumbnailProperties(File f) throws IOException {
HashMap<String, String> props = new HashMap<String, String>();
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<ImageReader> 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());
}
}