diff --git a/pom.xml b/pom.xml index 10ed6f8..507a398 100644 --- a/pom.xml +++ b/pom.xml @@ -237,6 +237,17 @@ 3.9.4 + + org.apache.pdfbox + pdfbox + 3.0.0 + + + org.apache.pdfbox + pdfbox-io + 3.0.0 + + org.eclipse.jetty jetty-server diff --git a/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java b/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java index 13397ce..6cd2f1a 100644 --- a/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java +++ b/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java @@ -6,11 +6,7 @@ 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 java.util.*; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; @@ -19,6 +15,11 @@ import javax.imageio.stream.ImageInputStream; import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.ImageProcessingException; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.io.RandomAccessReadBufferedFile; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.forkalsrud.album.db.DirectoryProps; import org.forkalsrud.album.video.MovieMetadataGenerator; import org.slf4j.Logger; @@ -91,6 +92,9 @@ public class DirectoryMetadataGenerator { } else if (name.endsWith(".mov") || name.endsWith(".MOV") || name.endsWith(".mp4") || name.endsWith(".m4v") || name.endsWith(".avi")) { Map p = movieMetadataGenerator.generateVideoProperties(f); addPropsForFile(props, f, p); + } else if (name.endsWith(".pdf")) { + Map p = generatePdfProperties(f); + addPropsForFile(props, f, p); } } } @@ -229,4 +233,34 @@ public class DirectoryMetadataGenerator { iis.close(); return new Dimension(img.getWidth(), img.getHeight()); } + + + + private Map generatePdfProperties(File file) throws IOException { + + HashMap props = new HashMap<>(); + + String name = file.getName(); + props.put("etag", Integer.toHexString(name.hashCode() + Long.valueOf(file.lastModified()).hashCode())); + + try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(file))) { + + PDPage page = document.getPage(0); + PDRectangle rect = page.getMediaBox(); + + + int width = Math.round(rect.getWidth()); + int height = Math.round(rect.getHeight()); + props.put("dimensions", new Dimension(width, height).toString()); + props.put("orientation", "1"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); + Calendar cal = document.getDocumentInformation().getCreationDate(); + Date date = cal != null ? cal.getTime() : new Date(file.lastModified()); + props.put("captureDate", sdf.format(date)); + + } + return props; + } + + } diff --git a/src/main/java/org/forkalsrud/album/web/PictureScaler.java b/src/main/java/org/forkalsrud/album/web/PictureScaler.java index 48f7c7e..ee344eb 100644 --- a/src/main/java/org/forkalsrud/album/web/PictureScaler.java +++ b/src/main/java/org/forkalsrud/album/web/PictureScaler.java @@ -25,6 +25,15 @@ import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.cos.COSDocument; +import org.apache.pdfbox.io.RandomAccessRead; +import org.apache.pdfbox.io.RandomAccessReadBufferedFile; +import org.apache.pdfbox.io.RandomAccessReadMemoryMappedFile; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.rendering.PDFRenderer; import org.forkalsrud.album.exif.Dimension; import org.forkalsrud.album.exif.Thumbnail; @@ -85,7 +94,10 @@ public class PictureScaler { @Override public CachedImage call() throws Exception { - try { + try { + if (file.getName().endsWith(".pdf")) { + return renderPdf(file, thumbnail, size); + } return scalePictureReally(file, thumbnail, size); } catch (Exception e) { log.error("sadness: " + file.getAbsolutePath(), e); @@ -151,11 +163,11 @@ public class PictureScaler { * @param size * @throws IOException */ - CachedImage scalePictureReally(File file, Thumbnail thumbnail, String size) throws IOException { + CachedImage scalePictureReally(File file, Thumbnail thumbnail, String size) throws IOException { Dimension orig = thumbnail.getSize(); Dimension outd = orig.scale(size); - + /* In order to make the quality as good as possible we follow the advice from * http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html * and scale the image by a factor of 2 in intermediate steps, thereby getting a smoother @@ -174,7 +186,7 @@ public class PictureScaler { // on pictures with each orientation. // // 1 2 3 4 5 6 7 8 - // + // // 888888 888888 88 88 8888888888 88 88 8888888888 // 88 88 88 88 88 88 88 88 88 88 88 88 // 8888 8888 8888 8888 88 8888888888 8888888888 88 @@ -185,46 +197,50 @@ public class PictureScaler { // The last four are obtained by rotating 90 degrees and then flipping X and/or Y. // - double[] flipX = new double[] { +1, -1, -1, +1, -1, +1, +1, -1 }; - double[] flipY = new double[] { +1, +1, -1, -1, +1, +1, -1, -1 }; + double[] flipX = new double[]{ +1, -1, -1, +1, -1, +1, +1, -1 }; + double[] flipY = new double[]{ +1, +1, -1, -1, +1, +1, -1, -1 }; int idx = thumbnail.getOrientation() - 1; - // Recalculate scale after sub-sampling was applied - double scale; - AffineTransform xform = new AffineTransform(); + // Recalculate scale after sub-sampling was applied + double scale; + AffineTransform xform = new AffineTransform(); boolean rotate = idx >= 4; xform.translate(intermediate.getWidth() / 2d, intermediate.getHeight() / 2d); if (rotate) { - xform.rotate(Math.PI / 2); - scale = (double)intermediate.getHeight() / img.getWidth(); + xform.rotate(Math.PI / 2); + scale = (double) intermediate.getHeight() / img.getWidth(); } else { - scale = (double)intermediate.getWidth() / img.getWidth(); + scale = (double) intermediate.getWidth() / img.getWidth(); } xform.scale(scale, scale); xform.scale(flipX[idx], flipY[idx]); xform.translate(-img.getWidth() / 2d, -img.getHeight() / 2d); - int imgType = img.getType() > 0 ? img.getType() : BufferedImage.TYPE_INT_RGB; - BufferedImage buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), imgType); - Graphics2D g2 = buf2.createGraphics(); - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - - g2.transform(xform); - g2.drawImage(img, 0, 0, null); - img = null; + int imgType = img.getType() > 0 ? img.getType() : BufferedImage.TYPE_INT_RGB; + BufferedImage buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), imgType); + Graphics2D g2 = buf2.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - while (intermediate.getWidth() > outd.getWidth()) { - - BufferedImage buf3 = buf2; - intermediate = intermediate.halved(); - buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), imgType); - g2 = buf2.createGraphics(); - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2.drawImage(buf3, 0, 0, intermediate.getWidth(), intermediate.getHeight(), null); - } + g2.transform(xform); + g2.drawImage(img, 0, 0, null); + img = null; - Iterator writers = ImageIO.getImageWritersByFormatName("jpg"); + while (intermediate.getWidth() > outd.getWidth()) { + + BufferedImage buf3 = buf2; + intermediate = intermediate.halved(); + buf2 = new BufferedImage(intermediate.getWidth(), intermediate.getHeight(), imgType); + g2 = buf2.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.drawImage(buf3, 0, 0, intermediate.getWidth(), intermediate.getHeight(), null); + } + return encoded(buf2, file.lastModified()); + } + + + private CachedImage encoded(BufferedImage buf2, long lastModifiedTs) throws IOException { + Iterator writers = ImageIO.getImageWritersByFormatName("jpg"); ImageWriter writer = writers.next(); ImageWriteParam writeParam = writer.getDefaultWriteParam(); writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); @@ -241,10 +257,37 @@ public class PictureScaler { ios.flush(); CachedImage cimg = new CachedImage(); - cimg.lastModified = file.lastModified(); + cimg.lastModified = lastModifiedTs; cimg.mimeType = "image/jpeg"; cimg.bits = bits.toByteArray(); return cimg; } + + CachedImage renderPdf(File file, Thumbnail thumbnail, String size) throws IOException { + + + Dimension orig = thumbnail.getSize(); + Dimension dim = orig.scale(size); + + + + try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(file))) { + + PDPage page = document.getPage(0); + PDRectangle rect = page.getMediaBox(); + + + float width = rect.getWidth() / 72; // inches + float targetWidth = dim.getWidth(); // pixels + + PDFRenderer renderer = new PDFRenderer(document); + BufferedImage image = renderer.renderImageWithDPI(0, targetWidth / width); + + + return encoded(image, file.lastModified()); + } + } + + }