package photato.core.resize; import java.util.logging.Logger; import java.util.logging.Level; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; import photato.core.resize.ffmpeg.VideoPictureExtractor; import photato.helpers.FileHelper; import photato.helpers.ImageHelper; import photato.helpers.JpegEncoder; import photato.helpers.Md5; import photato.helpers.MediaHelper; public abstract class ResizedImageGenerator { private static final Logger LOGGER = Logger.getLogger(ResizedImageGenerator.class.getName() ); private final int resizedPictureQuality; private final boolean forceResize; protected final FileSystem fileSystem; protected final Path resizedPicturesFolder; private final Path extractedPicturesFolder; private final Path rootFolder; private final Set<Path> resizedPicturesSet; private final Object lock = new Object(); public ResizedImageGenerator(FileSystem fileSystem, Path rootFolder, String resizedPicturesFolderName, String extractedPicturesFolderName, int resizedPictureQuality, boolean forceResize) throws IOException { this.fileSystem = fileSystem; this.resizedPicturesFolder = this.fileSystem.getPath(resizedPicturesFolderName); this.extractedPicturesFolder = this.fileSystem.getPath(extractedPicturesFolderName); this.resizedPicturesSet = new HashSet<>(); this.forceResize = forceResize; this.resizedPictureQuality = resizedPictureQuality; if (!Files.exists(this.resizedPicturesFolder)) { Files.createDirectory(this.resizedPicturesFolder); } this.rootFolder = rootFolder; this.initAndCleanOutdatedResizedPictures(); } /** * Generate a resizedPicture return true if the picture has been properly * regenerated, false if it already existed */ protected final void generateResizedPicture(Path originalFilename, long lastModifiedTimestamp, int rotationId) throws IOException { Path resizedPicturePath = this.getResizedPicturePath(originalFilename, lastModifiedTimestamp); synchronized (this.lock) { if (resizedPicturesSet.contains(resizedPicturePath)) { return; } this.resizedPicturesSet.add(resizedPicturePath); } LOGGER.log(Level.INFO, "Generating resizedPicture for: {0} lastModified: {1}", new Object[] {originalFilename, lastModifiedTimestamp}); BufferedImage originalImage; if (MediaHelper.isVideoFile(originalFilename)) { Path tmpPicturePath = VideoPictureExtractor.extractPictureFromVideoWithRandomPath(originalFilename, extractedPicturesFolder); originalImage = ImageHelper.readImage(tmpPicturePath, 1); Files.delete(tmpPicturePath); } else if (MediaHelper.isPictureFile(originalFilename)) { originalImage = ImageHelper.readImage(originalFilename, rotationId); } else { throw new IllegalArgumentException(); } int newWidth = this.getResizedPictureWidth(originalImage.getWidth(), originalImage.getHeight()); int newHeight = this.getResizedPictureHeight(originalImage.getWidth(), originalImage.getHeight()); BufferedImage image; if (this.forceResize || (newWidth <= originalImage.getWidth() && newHeight <= originalImage.getHeight())) { image = ImageHelper.resizeImageSmooth(originalImage, newWidth, newHeight); } else { image = originalImage; } try (OutputStream out = new BufferedOutputStream(new FileOutputStream(resizedPicturePath.toFile()))) { new JpegEncoder(image, this.resizedPictureQuality, out).Compress(); } } protected final void deleteResizedPicture(Path originalFilename, long lastModifiedTimestamp) throws IOException { synchronized (this.lock) { Path p = this.resizedPicturesFolder.resolve(getResizedPictureFilename(originalFilename, lastModifiedTimestamp)); Files.deleteIfExists(p); this.resizedPicturesSet.remove(p); } } protected final Path getResizedPicturePath(Path originalFilename, long lastModifiedTimestamp) { return this.resizedPicturesFolder.resolve(getResizedPictureFilename(originalFilename, lastModifiedTimestamp)); } protected final String getResizedPictureFilename(Path originalFilename, long lastModifiedTimestamp) { String src = originalFilename.toAbsolutePath() + "_" + lastModifiedTimestamp; return Md5.encodeString(src) + ".jpg"; } protected abstract int getResizedPictureWidth(int originalWidth, int originalHeight); protected abstract int getResizedPictureHeight(int originalWidth, int originalHeight); private void initAndCleanOutdatedResizedPictures() { try { this.resizedPicturesSet.addAll(Files.list(this.resizedPicturesFolder).collect(Collectors.toSet())); Set<Path> toDeleteResizedPictures = new HashSet<>(this.resizedPicturesSet); Queue<Path> toExplore = new LinkedList<>(); toExplore.add(this.rootFolder); while (!toExplore.isEmpty()) { File currentFolder = toExplore.remove().toFile(); if (currentFolder.canRead() && !FileHelper.folderContainsIgnoreFile(currentFolder.toPath())) { File[] files = currentFolder.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { toExplore.add(file.toPath()); } else { Path p = this.resizedPicturesFolder.resolve(getResizedPictureFilename(file.toPath(), file.lastModified())); toDeleteResizedPictures.remove(p); } } } } } // ResizedPictures with no real picture anymore for (Path toDeleteResizedPicture : toDeleteResizedPictures) { toDeleteResizedPicture.toFile().delete(); } } catch (Exception ex) { ex.printStackTrace(); } } }