mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-23 05:35:23 +00:00
fix: correct paths for python scripts and implement classpath extraction (#3984)
# Description of Changes - **What was changed** - Relocated `png_to_webp.py` and `split_photos.py` from `scripts/` to `app/core/src/main/resources/static/python/`. - Updated `.github/labeler-config-srvaroa.yml` and `.pre-commit-config.yaml` to include the new script directory in their file-matching patterns. - Added `GeneralUtils.extractScript(String scriptName)` to load Python scripts from the classpath (`static/python/`), extract them into a temporary directory at runtime, and return the filesystem path. - **Why the change was made** - To fix the Internal Server Error caused by missing script files at their old locations. - Ensure the Python helper scripts are packaged inside the JAR/WAR and reliably accessible when the application runs. - Only local installations were affected --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
7b61bbaced
commit
04ba3cebab
5
.github/config/.files.yaml
vendored
5
.github/config/.files.yaml
vendored
@ -11,11 +11,12 @@ openapi: &openapi
|
||||
- app/(common|core|proprietary)/src/main/java/**
|
||||
|
||||
project: &project
|
||||
- app/**
|
||||
- app/(common|core|proprietary)/src/(main|test)/java/**
|
||||
- app/(common|core|proprietary)/build.gradle
|
||||
- 'app/(common|core|proprietary)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*'
|
||||
- exampleYmlFiles/**
|
||||
- gradle/**
|
||||
- libs/**
|
||||
- scripts/**
|
||||
- testing/**
|
||||
- build.gradle
|
||||
- Dockerfile
|
||||
|
8
.github/labeler-config-srvaroa.yml
vendored
8
.github/labeler-config-srvaroa.yml
vendored
@ -76,8 +76,8 @@ labels:
|
||||
- 'app/core/src/main/resources/settings.yml.template'
|
||||
- 'app/core/src/main/resources/application.properties'
|
||||
- 'app/core/src/main/resources/banner.txt'
|
||||
- 'scripts/png_to_webp.py'
|
||||
- 'split_photos.py'
|
||||
- 'app/core/src/main/resources/static/python/png_to_webp.py'
|
||||
- 'app/core/src/main/resources/static/python/split_photos.py'
|
||||
- 'application.properties'
|
||||
|
||||
- label: 'Security'
|
||||
@ -95,8 +95,8 @@ labels:
|
||||
- 'app/core/src/main/java/stirling/software/SPDF/model/api/.*'
|
||||
- 'app/core/src/main/java/stirling/software/SPDF/service/ApiDocService.java'
|
||||
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/.*'
|
||||
- 'scripts/png_to_webp.py'
|
||||
- 'split_photos.py'
|
||||
- 'app/core/src/main/resources/static/python/png_to_webp.py'
|
||||
- 'app/core/src/main/resources/static/python/split_photos.py'
|
||||
- '.github/workflows/swagger.yml'
|
||||
|
||||
- label: 'Documentation'
|
||||
|
@ -6,10 +6,10 @@ repos:
|
||||
args:
|
||||
- --fix
|
||||
- --line-length=127
|
||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
|
||||
exclude: (split_photos.py)
|
||||
- id: ruff-format
|
||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
|
||||
exclude: (split_photos.py)
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.4.1
|
||||
|
@ -14,6 +14,7 @@ public class InstallationPathConfig {
|
||||
private static final String CONFIG_PATH;
|
||||
private static final String CUSTOM_FILES_PATH;
|
||||
private static final String CLIENT_WEBUI_PATH;
|
||||
private static final String SCRIPTS_PATH;
|
||||
|
||||
// Config paths
|
||||
private static final String SETTINGS_PATH;
|
||||
@ -36,6 +37,7 @@ public class InstallationPathConfig {
|
||||
// Initialize config paths
|
||||
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
|
||||
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
|
||||
SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator;
|
||||
|
||||
// Initialize custom file paths
|
||||
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
||||
@ -89,6 +91,10 @@ public class InstallationPathConfig {
|
||||
return CLIENT_WEBUI_PATH;
|
||||
}
|
||||
|
||||
public static String getScriptsPath() {
|
||||
return SCRIPTS_PATH;
|
||||
}
|
||||
|
||||
public static String getSettingsPath() {
|
||||
return SETTINGS_PATH;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||
@ -33,6 +34,9 @@ import stirling.software.common.configuration.InstallationPathConfig;
|
||||
@Slf4j
|
||||
public class GeneralUtils {
|
||||
|
||||
private static final List<String> DEFAULT_VALID_SCRIPTS =
|
||||
List.of("png_to_webp.py", "split_photos.py");
|
||||
|
||||
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
|
||||
String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY");
|
||||
if (customTempDir == null || customTempDir.isEmpty()) {
|
||||
@ -442,6 +446,40 @@ public class GeneralUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a file from classpath:/static/python to a temporary directory and returns the path.
|
||||
*/
|
||||
public static Path extractScript(String scriptName) throws IOException {
|
||||
// Validate input
|
||||
if (scriptName == null || scriptName.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("scriptName must not be null or empty");
|
||||
}
|
||||
if (scriptName.contains("..") || scriptName.contains("/")) {
|
||||
throw new IllegalArgumentException(
|
||||
"scriptName must not contain path traversal characters");
|
||||
}
|
||||
|
||||
if (!DEFAULT_VALID_SCRIPTS.contains(scriptName)) {
|
||||
throw new IllegalArgumentException(
|
||||
"scriptName must be either 'png_to_webp.py' or 'split_photos.py'");
|
||||
}
|
||||
|
||||
Path scriptsDir = Paths.get(InstallationPathConfig.getScriptsPath(), "python");
|
||||
Files.createDirectories(scriptsDir);
|
||||
|
||||
Path scriptFile = scriptsDir.resolve(scriptName);
|
||||
if (!Files.exists(scriptFile)) {
|
||||
ClassPathResource resource = new ClassPathResource("static/python/" + scriptName);
|
||||
try (InputStream in = resource.getInputStream()) {
|
||||
Files.copy(in, scriptFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to extract Python script", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return scriptFile;
|
||||
}
|
||||
|
||||
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
|
||||
if (currentVersion == null || compareVersion == null) {
|
||||
return false;
|
||||
|
2
app/core/.gitignore
vendored
2
app/core/.gitignore
vendored
@ -194,3 +194,5 @@ id_ed25519.pub
|
||||
|
||||
# node_modules
|
||||
node_modules/
|
||||
|
||||
scripts/**/*
|
||||
|
@ -56,8 +56,8 @@ public class ConvertImgPDFController {
|
||||
summary = "Convert PDF to image(s)",
|
||||
description =
|
||||
"This endpoint converts a PDF file to image(s) with the specified image format,"
|
||||
+ " color type, and DPI. Users can choose to get a single image or multiple"
|
||||
+ " images. Input:PDF Output:Image Type:SI-Conditional")
|
||||
+ " color type, and DPI. Users can choose to get a single image or multiple"
|
||||
+ " images. Input:PDF Output:Image Type:SI-Conditional")
|
||||
public ResponseEntity<byte[]> convertToImage(@ModelAttribute ConvertToImageRequest request)
|
||||
throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
@ -117,10 +117,14 @@ public class ConvertImgPDFController {
|
||||
}
|
||||
|
||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||
Path pngToWebpScript = GeneralUtils.extractScript("png_to_webp.py");
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(pythonVersion);
|
||||
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
||||
command.add(
|
||||
pngToWebpScript
|
||||
.toAbsolutePath()
|
||||
.toString()); // Python script to handle the conversion
|
||||
|
||||
// Create a temporary directory for the output WebP files
|
||||
tempOutputDir = Files.createTempDirectory("webp_output");
|
||||
@ -232,7 +236,8 @@ public class ConvertImgPDFController {
|
||||
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
bytes,
|
||||
new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
||||
new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "")
|
||||
+ "_converted.pdf");
|
||||
}
|
||||
|
||||
private String getMediaType(String imageFormat) {
|
||||
|
@ -34,6 +34,7 @@ import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.CheckProgramInstall;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
@ -54,9 +55,9 @@ public class ExtractImageScansController {
|
||||
summary = "Extract image scans from an input file",
|
||||
description =
|
||||
"This endpoint extracts image scans from a given file based on certain"
|
||||
+ " parameters. Users can specify angle threshold, tolerance, minimum area,"
|
||||
+ " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP"
|
||||
+ " Type:SIMO")
|
||||
+ " parameters. Users can specify angle threshold, tolerance, minimum area,"
|
||||
+ " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP"
|
||||
+ " Type:SIMO")
|
||||
public ResponseEntity<byte[]> extractImageScans(
|
||||
@ModelAttribute ExtractImageScansRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
@ -78,6 +79,7 @@ public class ExtractImageScansController {
|
||||
}
|
||||
|
||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||
Path splitPhotosScript = GeneralUtils.extractScript("split_photos.py");
|
||||
try {
|
||||
// Check if input file is a PDF
|
||||
if ("pdf".equalsIgnoreCase(extension)) {
|
||||
@ -120,7 +122,7 @@ public class ExtractImageScansController {
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
pythonVersion,
|
||||
"./scripts/split_photos.py",
|
||||
splitPhotosScript.toAbsolutePath().toString(),
|
||||
images.get(i),
|
||||
tempDir.toString(),
|
||||
"--angle_threshold",
|
||||
|
Loading…
x
Reference in New Issue
Block a user