diff --git a/Aptfile b/Aptfile index 58cf173a7..74914b3f9 100644 --- a/Aptfile +++ b/Aptfile @@ -1,2 +1,3 @@ ttf-mscorefonts-installer -tesseract-ocr \ No newline at end of file +tesseract-ocr +imagemagick diff --git a/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java b/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java index a6e3be64c..56dfbc748 100644 --- a/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java +++ b/dossierfacile-api-tenant/src/main/java/fr/dossierfacile/api/front/validator/ExtensionValidator.java @@ -12,6 +12,7 @@ public class ExtensionValidator implements ConstraintValidator contentTypes = Arrays.asList( "image/png", "image/jpeg", + "image/heif", "application/pdf" ); diff --git a/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java b/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java index 3951b2aa0..f0774f898 100644 --- a/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java +++ b/dossierfacile-api-watermark/src/main/java/fr/dossierfacile/api/pdf/service/DocumentServiceImpl.java @@ -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; @@ -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 @@ -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); diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java new file mode 100644 index 000000000..02ff6822a --- /dev/null +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/config/ImageMagickConfig.java @@ -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; + } +} diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java index cae47a96c..68d694878 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/DocumentHelperServiceImpl.java @@ -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; @@ -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; @@ -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 @@ -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) @@ -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> 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()) { @@ -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)); @@ -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; } diff --git a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java index d8aab1ab3..deea61047 100644 --- a/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java +++ b/dossierfacile-common-library/src/main/java/fr/dossierfacile/common/service/interfaces/DocumentHelperService.java @@ -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. */