feat(cbz-to-pdf,pdf-to-cbz): Converter for CBZ format to and from PDF (#4472)

This commit is contained in:
Balázs Szücs
2025-09-28 21:41:50 +01:00
committed by GitHub
parent 413cd0c697
commit 51aa03b256
12 changed files with 737 additions and 2 deletions
@@ -8,6 +8,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -16,6 +17,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.rendering.ImageType;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -30,11 +32,22 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.ConvertCbzToPdfRequest;
import stirling.software.SPDF.model.api.converters.ConvertPdfToCbzRequest;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.*;
import stirling.software.common.util.CbzUtils;
import stirling.software.common.util.CheckProgramInstall;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.PdfToCbzUtils;
import stirling.software.common.util.PdfUtils;
import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.RegexPatternUtils;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@@ -44,6 +57,10 @@ import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
public class ConvertImgPDFController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final TempFileManager tempFileManager;
private static final Pattern EXTENSION_PATTERN =
RegexPatternUtils.getInstance().getPattern(RegexPatternUtils.getExtensionRegex());
private static final String DEFAULT_COMIC_NAME = "comic";
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/pdf/img")
@Operation(
@@ -234,6 +251,74 @@ public class ConvertImgPDFController {
GeneralUtils.generateFilename(file[0].getOriginalFilename(), "_converted.pdf"));
}
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/cbz/pdf")
@Operation(
summary = "Convert CBZ comic book archive to PDF",
description =
"This endpoint converts a CBZ (ZIP) comic book archive to a PDF file. "
+ "Input:CBZ Output:PDF Type:SISO")
public ResponseEntity<byte[]> convertCbzToPdf(@ModelAttribute ConvertCbzToPdfRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
byte[] pdfBytes;
try {
pdfBytes = CbzUtils.convertCbzToPdf(file, pdfDocumentFactory, tempFileManager);
} catch (IllegalArgumentException ex) {
String message = ex.getMessage() == null ? "Invalid CBZ file" : ex.getMessage();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.TEXT_PLAIN)
.body(message.getBytes(java.nio.charset.StandardCharsets.UTF_8));
}
String filename = createConvertedFilename(file.getOriginalFilename(), "_converted.pdf");
return WebResponseUtils.bytesToWebResponse(pdfBytes, filename);
}
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/pdf/cbz")
@Operation(
summary = "Convert PDF to CBZ comic book archive",
description =
"This endpoint converts a PDF file to a CBZ (ZIP) comic book archive. "
+ "Input:PDF Output:CBZ Type:SISO")
public ResponseEntity<byte[]> convertPdfToCbz(@ModelAttribute ConvertPdfToCbzRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
Integer dpi = request.getDpi();
if (dpi == null || dpi <= 0) {
dpi = 300;
}
byte[] cbzBytes;
try {
cbzBytes = PdfToCbzUtils.convertPdfToCbz(file, dpi, pdfDocumentFactory);
} catch (IllegalArgumentException ex) {
String message = ex.getMessage() == null ? "Invalid PDF file" : ex.getMessage();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.TEXT_PLAIN)
.body(message.getBytes(java.nio.charset.StandardCharsets.UTF_8));
}
String filename = createConvertedFilename(file.getOriginalFilename(), "_converted.cbz");
return WebResponseUtils.bytesToWebResponse(
cbzBytes, filename, MediaType.APPLICATION_OCTET_STREAM);
}
private String createConvertedFilename(String originalFilename, String suffix) {
if (originalFilename == null) {
return GeneralUtils.generateFilename(DEFAULT_COMIC_NAME, suffix);
}
String baseName = EXTENSION_PATTERN.matcher(originalFilename).replaceFirst("");
if (baseName.isBlank()) {
baseName = DEFAULT_COMIC_NAME;
}
return GeneralUtils.generateFilename(baseName, suffix);
}
private String getMediaType(String imageFormat) {
String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat);
return "null".equals(mimeType) ? MediaType.APPLICATION_OCTET_STREAM_VALUE : mimeType;
@@ -23,6 +23,20 @@ public class ConverterWebController {
return "convert/img-to-pdf";
}
@GetMapping("/cbz-to-pdf")
@Hidden
public String convertCbzToPdfForm(Model model) {
model.addAttribute("currentPage", "cbz-to-pdf");
return "convert/cbz-to-pdf";
}
@GetMapping("/pdf-to-cbz")
@Hidden
public String convertPdfToCbzForm(Model model) {
model.addAttribute("currentPage", "pdf-to-cbz");
return "convert/pdf-to-cbz";
}
@GetMapping("/html-to-pdf")
@Hidden
public String convertHTMLToPdfForm(Model model) {
@@ -0,0 +1,18 @@
package stirling.software.SPDF.model.api.converters;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode
public class ConvertCbzToPdfRequest {
@Schema(
description = "The input CBZ file to be converted to a PDF file",
requiredMode = Schema.RequiredMode.REQUIRED)
private MultipartFile fileInput;
}
@@ -0,0 +1,24 @@
package stirling.software.SPDF.model.api.converters;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode
public class ConvertPdfToCbzRequest {
@Schema(
description = "The input PDF file to be converted to a CBZ file",
requiredMode = Schema.RequiredMode.REQUIRED)
private MultipartFile fileInput;
@Schema(
description = "The DPI (Dots Per Inch) for rendering PDF pages as images",
example = "300",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private Integer dpi = 300;
}