Skip to content

Commit

Permalink
✨ Feat: handle heic files (#882)
Browse files Browse the repository at this point in the history
* ✨ Feat: handle heic files

* ✨ Feat: extract image magick cli path

* ✨ Feat: specify attributes on tmp file

* ✨ Feat: handle interruptedException and stop thread
  • Loading branch information
mattboll authored Oct 18, 2024
1 parent 4480a1a commit 6cfc337
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 9 deletions.
3 changes: 2 additions & 1 deletion Aptfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ttf-mscorefonts-installer
tesseract-ocr
tesseract-ocr
imagemagick
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class ExtensionValidator implements ConstraintValidator<Extension, Docume
private static final List<String> contentTypes = Arrays.asList(
"image/png",
"image/jpeg",
"image/heif",
"application/pdf"
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import fr.dossierfacile.common.enums.FileStatus;
import fr.dossierfacile.common.enums.FileStorageStatus;
import fr.dossierfacile.common.repository.WatermarkDocumentRepository;
import fr.dossierfacile.common.service.interfaces.DocumentHelperService;
import fr.dossierfacile.common.service.interfaces.EncryptionKeyService;
import fr.dossierfacile.common.service.interfaces.FileStorageService;
import jakarta.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -41,6 +42,7 @@ public class DocumentServiceImpl implements DocumentService {
private final FileStorageService fileStorageService;
private final EncryptionKeyService encryptionKeyService;
private final WatermarkDocumentRepository watermarkDocumentRepository;
private final DocumentHelperService documentHelperService;

@Override
@Transactional
Expand Down Expand Up @@ -152,7 +154,20 @@ private WatermarkDocument createDocument(DocumentForm documentForm) {
.build();

try {
return fileStorageService.upload(multipartFile.getInputStream(), file);
if ("image/heif".equals(multipartFile.getContentType())) {
file.setName(multipartFile.getOriginalFilename().replaceAll("(?i)\\.heic$", "") + ".jpg");
file.setContentType("image/jpeg");

InputStream jpgInputStream = documentHelperService.convertHeicToJpg(multipartFile.getInputStream());
if (jpgInputStream != null) {
return fileStorageService.upload(jpgInputStream, file);
} else {
throw new IOException("Image could not be saved");
}
} else {
return fileStorageService.upload(multipartFile.getInputStream(), file);
}

} catch (IOException e) {
log.error("Error on file save", e);
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.dossierfacile.common.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ImageMagickConfig {
@Value("${path.imagemagick.cli:/usr/bin/convert}")
private String imageMagickCli;

public String getImageMagickCli() {
return imageMagickCli;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.dossierfacile.common.service;

import fr.dossierfacile.common.config.ImageMagickConfig;
import fr.dossierfacile.common.entity.Document;
import fr.dossierfacile.common.entity.File;
import fr.dossierfacile.common.entity.StorageFile;
Expand All @@ -11,13 +12,14 @@
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

Expand All @@ -26,9 +28,15 @@
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.List;
import java.util.Set;
import java.util.UUID;

@Service
Expand All @@ -38,19 +46,35 @@ public class DocumentHelperServiceImpl implements DocumentHelperService {
private final FileStorageService fileStorageService;
private final SharedFileRepository fileRepository;
private final EncryptionKeyService encryptionKeyService;
private final ImageMagickConfig imageMagickConfig;

@Transactional
@Override
public File addFile(MultipartFile multipartFile, Document document) throws IOException {

StorageFile storageFile = StorageFile.builder()
.name(multipartFile.getOriginalFilename())
.contentType(multipartFile.getContentType())
.size(multipartFile.getSize())
.encryptionKey(encryptionKeyService.getCurrentKey())
.build();
String originalFilename = multipartFile.getOriginalFilename();
if (originalFilename == null) {
originalFilename = UUID.randomUUID().toString();
}
if ("image/heif".equals(multipartFile.getContentType())) {
storageFile.setName(originalFilename.replaceAll("(?i)\\.heic$", "") + ".jpg");
storageFile.setContentType("image/jpeg");

InputStream jpgInputStream = convertHeicToJpg(multipartFile.getInputStream());
if (jpgInputStream != null) {
storageFile = fileStorageService.upload(jpgInputStream, storageFile);
} else {
throw new IOException("Image could not be saved");
}
} else {
storageFile.setName(originalFilename);
storageFile.setContentType(multipartFile.getContentType());
storageFile = fileStorageService.upload(multipartFile.getInputStream(), storageFile);
}

storageFile = fileStorageService.upload(multipartFile.getInputStream(), storageFile);

File file = File.builder()
.storageFile(storageFile)
Expand All @@ -64,6 +88,43 @@ public File addFile(MultipartFile multipartFile, Document document) throws IOExc
return file;
}

@Override
public InputStream convertHeicToJpg(InputStream heicInputStream) throws IOException {
String tmpImageName = UUID.randomUUID().toString();
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
java.io.File heicFile = java.nio.file.Files.createTempFile(tmpImageName, ".heic", attr).toFile();

try (FileOutputStream fileOutputStream = new FileOutputStream(heicFile)) {
IOUtils.copy(heicInputStream, fileOutputStream);
}

java.io.File jpgFile = java.nio.file.Files.createTempFile(tmpImageName, ".jpg", attr).toFile();

// Use ImageMagick to convert .heic to .jpg
ProcessBuilder processBuilder = new ProcessBuilder(imageMagickConfig.getImageMagickCli(), heicFile.getAbsolutePath(), jpgFile.getAbsolutePath());
Process process = processBuilder.start();
try {
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Erreur lors de la conversion HEIC en JPG avec ImageMagick.");
}
} catch (InterruptedException e) {
log.error("Interrupted exception", e);
Thread.currentThread().interrupt();
throw new IOException(e);
}

InputStream jpgInputStream = new FileInputStream(jpgFile);

boolean delete = heicFile.delete();
if (!delete) {
log.error("Could not delete temporary file");
}
jpgFile.deleteOnExit();

return jpgInputStream;
}

@Override
public void deleteFiles(Document document) {
if (document.getFiles() != null && !document.getFiles().isEmpty()) {
Expand All @@ -84,7 +145,7 @@ public StorageFile generatePreview(InputStream fileInputStream, String originalN
PDFRenderer pdfRenderer = new PDFRenderer(document);
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 200, ImageType.RGB);
preview = resizeImage(bufferedImage);
log.info("resize pdf duration : " + (System.currentTimeMillis() - startTime));
log.info("resize pdf duration : {}", System.currentTimeMillis() - startTime);
}
} else {
preview = resizeImage(ImageIO.read(fileInputStream));
Expand Down Expand Up @@ -129,7 +190,7 @@ BufferedImage resizeImage(BufferedImage image) {
graphics2D.dispose();
long endTime = System.currentTimeMillis();
long duration = (endTime - startTime);
log.info("resize image duration : " + duration);
log.info("resize image duration : {}", duration);
return resizedImage;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface DocumentHelperService {
*/
File addFile(MultipartFile multipartFile, Document document) throws IOException;

InputStream convertHeicToJpg(InputStream heicInputStream) throws IOException;

/**
* Delete files contained in document.
*/
Expand Down

0 comments on commit 6cfc337

Please sign in to comment.