Refactoring the file/directory parsing code away from the properties model of the metadata.
This commit is contained in:
parent
72c827c3f2
commit
cad37df18b
2 changed files with 200 additions and 167 deletions
|
|
@ -3,12 +3,9 @@
|
||||||
*/
|
*/
|
||||||
package org.forkalsrud.album.exif;
|
package org.forkalsrud.album.exif;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -18,31 +15,15 @@ import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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.DirectoryDatabase;
|
||||||
import org.forkalsrud.album.db.DirectoryProps;
|
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
|
* @author knut
|
||||||
*/
|
*/
|
||||||
public class DirectoryEntry extends Entry {
|
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 CACHE_FILE = "cache.properties";
|
||||||
final static String OVERRIDE_FILE = "album.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);
|
throw new RuntimeException("Use DirectoryEntry only for directories: " + file);
|
||||||
}
|
}
|
||||||
this.db = db;
|
this.db = db;
|
||||||
cache = db.get(file.getAbsolutePath());
|
cache = db.load(file.getAbsolutePath());
|
||||||
if (cache == null) {
|
if (cache == null) {
|
||||||
cache = new DirectoryProps();
|
cache = new DirectoryProps();
|
||||||
}
|
}
|
||||||
|
|
@ -114,12 +95,12 @@ public class DirectoryEntry extends Entry {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Properties cachedProps = isCacheCurrent() ? cache : generateCache();
|
DirectoryProps cachedProps = isCacheCurrent() ? cache : generateCache();
|
||||||
Properties combined = new Properties();
|
DirectoryProps combined = new DirectoryProps();
|
||||||
combined.putAll(cachedProps);
|
combined.putAll(cachedProps);
|
||||||
File override = new File(file, OVERRIDE_FILE);
|
File override = new File(file, OVERRIDE_FILE);
|
||||||
if (override.exists() && override.isFile() && override.canRead()) {
|
if (override.exists() && override.isFile() && override.canRead()) {
|
||||||
Properties overrideProps = loadFromFile(override);
|
DirectoryProps overrideProps = loadFromFile(override);
|
||||||
combined.putAll(overrideProps);
|
combined.putAll(overrideProps);
|
||||||
}
|
}
|
||||||
populate(combined);
|
populate(combined);
|
||||||
|
|
@ -135,162 +116,24 @@ public class DirectoryEntry extends Entry {
|
||||||
return file.lastModified() <= cache.getTimestamp();
|
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);
|
FileInputStream fis = new FileInputStream(propFile);
|
||||||
props.load(fis);
|
props.load(fis);
|
||||||
fis.close();
|
fis.close();
|
||||||
|
props.setTimestamp(propFile.lastModified());
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
Properties generateCache() throws IOException {
|
DirectoryProps generateCache() throws IOException {
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
DirectoryProps props = new DirectoryMetadataGenerator(file).generate();
|
||||||
DirectoryProps props = new DirectoryProps();
|
|
||||||
generateFileEntries(props);
|
|
||||||
long end = System.currentTimeMillis();
|
|
||||||
props.setTimestamp(end);
|
|
||||||
db.store(file.getAbsolutePath(), props);
|
db.store(file.getAbsolutePath(), props);
|
||||||
log.info("Time to generate properties for " + file.getPath() + ": " + (end - start)/1000d + " s");
|
|
||||||
return props;
|
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() {
|
private void sort() {
|
||||||
|
|
||||||
Collections.sort(children, 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 coverFileName = props.getProperty("cover");
|
||||||
String caption = props.getProperty("caption");
|
String caption = props.getProperty("caption");
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue