Delete code from invalid license (#5947)

This commit is contained in:
Anthony Stirling
2026-03-16 11:01:31 +00:00
committed by GitHub
parent 971321fb19
commit cddc8e6df0
31 changed files with 64 additions and 5520 deletions
@@ -1,80 +0,0 @@
package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
/**
* Controller class for handling PDF image removal requests. Provides an endpoint to remove images
* from a PDF file to reduce its size.
*/
@GeneralApi
@RequiredArgsConstructor
public class PdfImageRemovalController {
// Service for removing images from PDFs
private final PdfImageRemovalService pdfImageRemovalService;
private final CustomPDFDocumentFactory pdfDocumentFactory;
/**
* Endpoint to remove images from a PDF file.
*
* <p>This method processes the uploaded PDF file, removes all images, and returns the modified
* PDF file with a new name indicating that images were removed.
*
* @param file The PDF file with images to be removed.
* @return ResponseEntity containing the modified PDF file as byte array with appropriate
* content type and filename.
* @throws IOException If an error occurs while processing the PDF file.
*/
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/remove-image-pdf")
@StandardPdfResponse
@Operation(
summary = "Remove images from file to reduce the file size.",
description =
"This endpoint remove images from file to reduce the file size.Input:PDF"
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
// Load the PDF document with proper resource management
try (PDDocument document = pdfDocumentFactory.load(file)) {
// Remove images from the PDF document using the service
try (PDDocument modifiedDocument =
pdfImageRemovalService.removeImagesFromPdf(document)) {
// Create a ByteArrayOutputStream to hold the modified PDF data
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Save the modified PDF document to the output stream
modifiedDocument.save(outputStream);
// Generate a new filename for the modified PDF
String mergedFileName =
GeneralUtils.generateFilename(
file.getFileInput().getOriginalFilename(), "_images_removed.pdf");
// Convert the byte array to a web response and return it
return WebResponseUtils.bytesToWebResponse(
outputStream.toByteArray(), mergedFileName);
}
}
}
}
@@ -150,28 +150,6 @@ public class RearrangePagesPDFController {
return newPageOrder;
}
/**
* Rearrange pages in a PDF file by merging odd and even pages. The first half of the pages will
* be the odd pages, and the second half will be the even pages as input. <br>
* This method is visible for testing purposes only.
*
* @param totalPages Total number of pages in the PDF file.
* @return List of page numbers in the new order. The first page is 0.
*/
List<Integer> oddEvenMerge(int totalPages) {
List<Integer> newPageOrderZeroBased = new ArrayList<>();
int numberOfOddPages = (totalPages + 1) / 2;
for (int oneBasedIndex = 1; oneBasedIndex < (numberOfOddPages + 1); oneBasedIndex++) {
newPageOrderZeroBased.add((oneBasedIndex - 1));
if (numberOfOddPages + oneBasedIndex <= totalPages) {
newPageOrderZeroBased.add((numberOfOddPages + oneBasedIndex - 1));
}
}
return newPageOrderZeroBased;
}
private List<Integer> duplicate(int totalPages, String pageOrder) {
List<Integer> newPageOrder = new ArrayList<>();
int duplicateCount;
@@ -220,7 +198,6 @@ public class RearrangePagesPDFController {
case BOOKLET_SORT -> bookletSort(totalPages);
case SIDE_STITCH_BOOKLET_SORT -> sideStitchBooklet(totalPages);
case ODD_EVEN_SPLIT -> oddEvenSplit(totalPages);
case ODD_EVEN_MERGE -> oddEvenMerge(totalPages);
case REMOVE_FIRST -> removeFirst(totalPages);
case REMOVE_LAST -> removeLast(totalPages);
case REMOVE_FIRST_AND_LAST -> removeFirstAndLast(totalPages);
@@ -1,20 +1,14 @@
package stirling.software.SPDF.controller.api.misc;
import java.awt.*;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -43,7 +37,6 @@ import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ImageProcessingUtils;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;
@@ -65,203 +58,97 @@ public class ExtractImagesController {
+ " file. Users can specify the output image format. Input:PDF"
+ " Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity<StreamingResponseBody> extractImages(
@ModelAttribute PDFExtractImagesRequest request)
throws IOException, InterruptedException, ExecutionException {
@ModelAttribute PDFExtractImagesRequest request) throws IOException {
MultipartFile file = request.getFileInput();
String format = request.getFormat();
boolean allowDuplicates = Boolean.TRUE.equals(request.getAllowDuplicates());
String imageFormat = request.getFormat();
String filename = GeneralUtils.removeExtension(file.getOriginalFilename());
Set<byte[]> processedImages = new HashSet<>();
String baseFilename = GeneralUtils.removeExtension(file.getOriginalFilename());
Set<Integer> processedImageHashes = new HashSet<>();
TempFile zipTempFile = new TempFile(tempFileManager, ".zip");
try (ZipOutputStream zos =
new ZipOutputStream(Files.newOutputStream(zipTempFile.getPath()));
PDDocument document = pdfDocumentFactory.load(file)) {
TempFile zipFile = new TempFile(tempFileManager, ".zip");
try (ZipOutputStream zipStream = new ZipOutputStream(Files.newOutputStream(zipFile.getPath()));
PDDocument pdfDoc = pdfDocumentFactory.load(file)) {
// Set compression level
zos.setLevel(Deflater.BEST_COMPRESSION);
zipStream.setLevel(Deflater.BEST_COMPRESSION);
// Determine if multithreading should be used based on PDF size or number of pages
boolean useMultithreading = shouldUseMultithreading(file, document);
if (useMultithreading) {
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Set<Future<Void>> futures = new HashSet<>();
try {
int pageCount = document.getPages().getCount();
log.debug("Document reports {} pages", pageCount);
int consecutiveFailures = 0;
for (int pgNum = 0; pgNum < pageCount; pgNum++) {
try {
PDPage page = document.getPage(pgNum);
consecutiveFailures = 0; // Reset on success
final int currentPageNum =
pgNum + 1; // Convert to 1-based page numbering
Future<Void> future =
executor.submit(
() -> {
try {
// Call the image extraction method for each
// page
extractImagesFromPage(
page,
format,
filename,
currentPageNum,
processedImages,
zos,
allowDuplicates);
} catch (Exception e) {
// Log the error and continue processing other
// pages
ExceptionUtils.logException(
"image extraction from page "
+ currentPageNum,
e);
}
return null; // Callable requires a return type
});
// Add the Future object to the list to track completion
futures.add(future);
} catch (Exception e) {
consecutiveFailures++;
ExceptionUtils.logException("page access for page " + (pgNum + 1), e);
if (consecutiveFailures >= 3) {
log.warn("Stopping page iteration after 3 consecutive failures");
break;
}
}
}
} catch (Exception e) {
ExceptionUtils.logException("page count determination", e);
throw e;
}
// Wait for all tasks to complete
for (Future<Void> future : futures) {
future.get();
}
// Close executor service
executor.shutdown();
} else {
// Single-threaded extraction
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
PDPage page = document.getPage(pgNum);
extractImagesFromPage(
page,
format,
filename,
pgNum + 1,
processedImages,
zos,
allowDuplicates);
}
int totalPages = pdfDoc.getNumberOfPages();
for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) {
PDPage currentPage = pdfDoc.getPage(pageIndex);
extractAndAddImagesToZip(
currentPage, imageFormat, baseFilename, pageIndex + 1,
processedImageHashes, zipStream);
}
// document and zos closed by try-with-resources
} catch (Exception e) {
zipTempFile.close();
zipFile.close();
throw e;
}
return WebResponseUtils.zipFileToWebResponse(
zipTempFile, filename + "_extracted-images.zip");
zipFile, baseFilename + "_extracted-images.zip");
}
private boolean shouldUseMultithreading(MultipartFile file, PDDocument document) {
// Criteria: Use multithreading if file size > 10MB or number of pages > 20
long fileSizeInMB = file.getSize() / (1024 * 1024);
int numberOfPages = document.getPages().getCount();
return fileSizeInMB > 10 || numberOfPages > 20;
}
private void extractImagesFromPage(
private void extractAndAddImagesToZip(
PDPage page,
String format,
String filename,
int pageNum,
Set<byte[]> processedImages,
ZipOutputStream zos,
boolean allowDuplicates)
String imageFormat,
String baseFilename,
int pageNumber,
Set<Integer> seenImageHashes,
ZipOutputStream zipOutput)
throws IOException {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
log.error("MD5 algorithm not available for extractImages hash.", e);
return;
}
if (page.getResources() == null || page.getResources().getXObjectNames() == null) {
return;
}
int count = 1;
for (COSName name : page.getResources().getXObjectNames()) {
int imageCount = 1;
for (COSName resourceName : page.getResources().getXObjectNames()) {
if (!page.getResources().isImageXObject(resourceName)) {
continue;
}
try {
if (page.getResources().isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
if (!allowDuplicates) {
byte[] data = ImageProcessingUtils.getImageData(image.getImage());
byte[] imageHash = md.digest(data);
synchronized (processedImages) {
if (processedImages.stream()
.anyMatch(hash -> Arrays.equals(hash, imageHash))) {
continue; // Skip already processed images
}
processedImages.add(imageHash);
}
}
PDImageXObject imageObject =
(PDImageXObject) page.getResources().getXObject(resourceName);
int imageHashCode = imageObject.hashCode();
RenderedImage renderedImage = image.getImage();
// Convert to standard RGB colorspace if needed
BufferedImage bufferedImage = convertToRGB(renderedImage, format);
// Encode image outside the lock to allow parallel encoding across threads
String imageName = filename + "_page_" + pageNum + "_" + count++ + "." + format;
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format, imageBaos);
byte[] imageData = imageBaos.toByteArray();
// Write encoded bytes to zip under lock (ZipOutputStream requires
// serialization)
synchronized (zos) {
zos.putNextEntry(new ZipEntry(imageName));
zos.write(imageData);
zos.closeEntry();
}
if (seenImageHashes.contains(imageHashCode)) {
continue;
}
seenImageHashes.add(imageHashCode);
RenderedImage sourceImage = imageObject.getImage();
BufferedImage convertedImage = convertImageToFormat(sourceImage, imageFormat);
String imagePath =
baseFilename + "_page_" + pageNumber + "_" + imageCount++ + "."
+ imageFormat;
ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream();
ImageIO.write(convertedImage, imageFormat, imageBuffer);
zipOutput.putNextEntry(new ZipEntry(imagePath));
zipOutput.write(imageBuffer.toByteArray());
zipOutput.closeEntry();
} catch (IOException e) {
ExceptionUtils.logException("image extraction", e);
ExceptionUtils.logException("image extraction failed", e);
throw ExceptionUtils.handlePdfException(e, "during image extraction");
}
}
}
private BufferedImage convertToRGB(RenderedImage renderedImage, String format) {
int width = renderedImage.getWidth();
int height = renderedImage.getHeight();
BufferedImage rgbImage;
private BufferedImage convertImageToFormat(RenderedImage source, String format) {
int width = source.getWidth();
int height = source.getHeight();
int imageType = BufferedImage.TYPE_INT_RGB;
if ("png".equalsIgnoreCase(format)) {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
} else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
} else if ("gif".equalsIgnoreCase(format)) {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED);
} else {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
imageType = BufferedImage.TYPE_INT_ARGB;
}
Graphics2D g = rgbImage.createGraphics();
g.drawImage((Image) renderedImage, 0, 0, null);
g.dispose();
return rgbImage;
BufferedImage result = new BufferedImage(width, height, imageType);
Graphics2D graphics = result.createGraphics();
graphics.drawImage((Image) source, 0, 0, null);
graphics.dispose();
return result;
}
}
@@ -36,7 +36,6 @@ import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.service.ApiDocService;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.service.PostHogService;
import stirling.software.common.util.FileMonitor;
import tools.jackson.databind.ObjectMapper;
@@ -50,7 +49,6 @@ public class PipelineDirectoryProcessor {
private final ObjectMapper objectMapper;
private final ApiDocService apiDocService;
private final PipelineProcessor processor;
private final FileMonitor fileMonitor;
private final PostHogService postHogService;
private final List<String> watchedFoldersDirs;
private final String finishedFoldersDir;
@@ -63,13 +61,11 @@ public class PipelineDirectoryProcessor {
ObjectMapper objectMapper,
ApiDocService apiDocService,
PipelineProcessor processor,
FileMonitor fileMonitor,
PostHogService postHogService,
RuntimePathConfig runtimePathConfig) {
this.objectMapper = objectMapper;
this.apiDocService = apiDocService;
this.processor = processor;
this.fileMonitor = fileMonitor;
this.postHogService = postHogService;
this.watchedFoldersDirs = runtimePathConfig.getPipelineWatchedFoldersPaths();
this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath();
@@ -274,18 +270,7 @@ public class PipelineDirectoryProcessor {
return isAllowed;
})
.map(Path::toAbsolutePath)
.filter(
path -> {
boolean isReady =
fileMonitor.isFileReadyForProcessing(path);
if (!isReady) {
log.info(
"File not ready for processing (locked/created"
+ " last 5s): {}",
path);
}
return isReady;
})
.filter(path -> true)
.map(Path::toFile)
.toArray(File[]::new);
log.info(
@@ -7,7 +7,6 @@ public enum SortTypes {
BOOKLET_SORT,
SIDE_STITCH_BOOKLET_SORT,
ODD_EVEN_SPLIT,
ODD_EVEN_MERGE,
REMOVE_FIRST,
REMOVE_LAST,
REMOVE_FIRST_AND_LAST,
@@ -22,7 +22,6 @@ public class RearrangePagesRequest extends PDFWithPageNums {
+ "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). "
+ "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n"
+ "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n"
+ "ODD_EVEN_MERGE: Merges pages and organises them alternately into odd and even pages.\n"
+ "REMOVE_FIRST: Removes the first page.\n"
+ "REMOVE_LAST: Removes the last page.\n"
+ "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")
@@ -1,51 +0,0 @@
package stirling.software.SPDF.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.springframework.stereotype.Service;
/** Service class responsible for removing image objects from a PDF document. */
@Service
public class PdfImageRemovalService {
/**
* Removes all image objects from the provided PDF document.
*
* <p>This method iterates over each page in the document and removes any image XObjects found
* in the page's resources.
*
* @param document The PDF document from which images will be removed.
* @return The modified PDF document with images removed.
* @throws IOException If an error occurs while processing the PDF document.
*/
public PDDocument removeImagesFromPdf(PDDocument document) throws IOException {
// Iterate over each page in the PDF document
for (PDPage page : document.getPages()) {
PDResources resources = page.getResources();
// Collect the XObject names to remove
List<COSName> namesToRemove = new ArrayList<>();
// Iterate over all XObject names in the page's resources
for (COSName name : resources.getXObjectNames()) {
// Check if the XObject is an image
if (resources.isImageXObject(name)) {
// Collect the name for removal
namesToRemove.add(name);
}
}
// Now, modify the resources by removing the collected names
for (COSName name : namesToRemove) {
resources.put(name, (PDXObject) null);
}
}
return document;
}
}