Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d1bf4c4
Initial implementation for OCRmyPDF
ZiadAbdElFatah May 11, 2026
af44508
Addressed the review comments
ZiadAbdElFatah May 12, 2026
1170da2
Used pre-existing StreamGobbler for BufferedReader
ZiadAbdElFatah May 12, 2026
f8546b5
Reomved unused variables
ZiadAbdElFatah May 12, 2026
349cee5
Reformated the code to meet JabRef's code guidelines
ZiadAbdElFatah May 12, 2026
4173f5d
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 12, 2026
2cdb734
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 13, 2026
e6ba274
Added GUI to perform OCR
ZiadAbdElFatah May 13, 2026
45a9fbc
Added localized messages
ZiadAbdElFatah May 13, 2026
c08ab99
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 16, 2026
b536820
Merge branch 'JabRef:main' into feature-OCR
ZiadAbdElFatah May 16, 2026
0ea7950
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 18, 2026
9c06e02
Addressed the review comments
ZiadAbdElFatah May 18, 2026
8a92bfe
Reverted unintended change
ZiadAbdElFatah May 18, 2026
8a6e9be
Addressed the review comments
ZiadAbdElFatah May 18, 2026
e0311c6
Solved checkstyle failing check
ZiadAbdElFatah May 18, 2026
d8610e6
Added linking the new OCRed file to the used entry
ZiadAbdElFatah May 18, 2026
b3a58f6
Addressed the review commits
ZiadAbdElFatah May 18, 2026
dd79d9e
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 18, 2026
e0d19e8
Added localized string to JabRef_en.preperties
ZiadAbdElFatah May 18, 2026
796b5d4
Added check for OCRMYPDF availability
ZiadAbdElFatah May 18, 2026
1a07f5d
Added a comment for readability
ZiadAbdElFatah May 18, 2026
b3ea8d6
Reduced the time of checking the avaialabilty of OCRmyPDF
ZiadAbdElFatah May 18, 2026
9c318d0
Added --skip-text to handle partial searchable pdfs
ZiadAbdElFatah May 18, 2026
9c46aac
Update jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAc…
ZiadAbdElFatah May 18, 2026
f3490ea
Apply suggestion from @InAnYan
ZiadAbdElFatah May 18, 2026
a9dfa18
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 18, 2026
f5ec242
Extracted the wait time in a single variable
ZiadAbdElFatah May 18, 2026
239542d
Apply suggestion from @InAnYan
InAnYan May 18, 2026
1793269
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 18, 2026
8acb6c5
Added the PDF file type for the new OCRed file
ZiadAbdElFatah May 18, 2026
afc4b1d
Added some missing strings to JabRef_en.propeties
ZiadAbdElFatah May 18, 2026
9d4bd44
Fix comment formatting for LinkedFile constructor
ZiadAbdElFatah May 18, 2026
72d33fa
Merge branch 'JabRef:main' into feature-OCR
ZiadAbdElFatah May 18, 2026
e843ab8
Addressed some openrewrite comments
ZiadAbdElFatah May 18, 2026
e6b51fe
Added a changelog entry
ZiadAbdElFatah May 18, 2026
11de63b
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 19, 2026
ce515cd
Link OCRed file to entries
ZiadAbdElFatah May 19, 2026
b548fb7
Improve OCRed file linking
ZiadAbdElFatah May 19, 2026
49c9b15
Remove unintended file
ZiadAbdElFatah May 19, 2026
4434905
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 22, 2026
a984da7
Address review comments
ZiadAbdElFatah May 22, 2026
7fd2621
Fix comment formatting in LinkedFile constructor
ZiadAbdElFatah May 22, 2026
c584be5
Fix comment formatting in LinkedFile constructor
ZiadAbdElFatah May 22, 2026
3cca2fb
Merge branch 'main' into feature-OCR
ZiadAbdElFatah May 22, 2026
199a8b4
Address review comment
ZiadAbdElFatah May 22, 2026
84a7f97
fix(ocr): fix failure message
InAnYan May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions jablib/src/main/java/org/jabref/logic/ocr/OcrEngine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.jabref.logic.ocr;

import java.nio.file.Path;

/**
* Interface for OCR engines.
* Any engine in the future can implement this interface.
*/
public interface OcrEngine {

/**
* Performs OCR on the given input file and returns the result.
*
* @param pdfPath the file to perform OCR on.
* @return the result of the OCR operation with the extracted text or an error message.
Comment thread
ZiadAbdElFatah marked this conversation as resolved.
Outdated
*/
OcrResult performOcr(Path pdfPath);
Comment thread
ZiadAbdElFatah marked this conversation as resolved.
Outdated

/**
* Checks if the OCR engine is available for use.
*
* @return true if the engine is available, false otherwise.
*/
boolean isAvailable();

/**
* Gets the name of the OCR engine.
*
* @return the name of the OCR engine (e.g., "OCRmyPDF", "Tesseract").
*/
String getName();
}
101 changes: 101 additions & 0 deletions jablib/src/main/java/org/jabref/logic/ocr/OcrMyPdfEngine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.jabref.logic.ocr;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.jabref.logic.util.io.FileUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Implementation of the OcrEngine interface using OCRmyPDF.
*/
public class OcrMyPdfEngine implements OcrEngine {

private static final Logger LOGGER = LoggerFactory.getLogger(OcrMyPdfEngine.class);
private static final int TIMEOUT_MINS = 10;

@Override
public String getName() {
return "OCRmyPDF";
}

@Override
public boolean isAvailable() {
return true;
Comment thread
InAnYan marked this conversation as resolved.
Outdated
}

/**
* OCRmyPDF writes the searchable PDF to a new file alongside the original file.
* <p>
* Example: document.pdf -> document_ocr.pdf.
*
* @param pdfPath the file to perform OCR on.
* @return {@link OcrResult.Success} containing the path to the searchable PDF,
* or {@link OcrResult.Failure} with an error message if OCR failed.
*/
@Override
public OcrResult performOcr(Path pdfPath) {
if (!isAvailable()) {
return OcrResult.failure("OCRmyPDF is not installed. Please install it using: pip install OCRmyPDF");
}
List<String> command = new ArrayList<>();
Comment thread
InAnYan marked this conversation as resolved.
Outdated
command.add("ocrmypdf");
command.add(pdfPath.toString());
Path outputPath = makeOutputFilePath(pdfPath);
String outputFile = outputPath.toString();
command.add(outputFile);
try {
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();

StringBuilder processOutput = new StringBuilder();

// Get the output and the errors of the process
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
Comment thread
InAnYan marked this conversation as resolved.
Outdated
String line;
while ((line = reader.readLine()) != null) {
processOutput.append(line).append("\n");
LOGGER.debug("OCRmyPDF: {}", line);
}
}

boolean finished = process.waitFor(TIMEOUT_MINS, TimeUnit.MINUTES);
if (!finished) {
process.destroyForcibly();
return OcrResult.failure("OCRmyPDF process timed out after " + TIMEOUT_MINS + " minutes.");
Comment thread
ZiadAbdElFatah marked this conversation as resolved.
Outdated
}

if (process.exitValue() == 0) {
String result = "A searchable PDF has been created with OCRmyPDF at: " + outputFile;
return OcrResult.success(result, outputPath);
Comment thread
ZiadAbdElFatah marked this conversation as resolved.
Outdated
} else {
return OcrResult.failure("OCRmyPDF failed with exit code " + process.exitValue() + ": " + processOutput.toString());
}
} catch (IOException | InterruptedException e) {
LOGGER.error("Error while running OCRmyPDF.", e);
return OcrResult.failure("Error while running OCRmyPDF: " + e.getMessage());
}
}

/**
* Generates the output path for the searchable PDF.
* Example: Documents/my files/document.pdf -> Documents/my files/document_ocr.pdf.
*
* @param inputPath the path of the PDF that needs to be OCRed.
* @return the output path of the searchable OCRed PDF.
*/
private Path makeOutputFilePath(Path inputPath) {
String baseName = FileUtil.getBaseName(inputPath.toString());
Path outputPath = inputPath.resolveSibling(baseName + "_ocr.pdf");
Comment thread
InAnYan marked this conversation as resolved.
Outdated
return outputPath;
}
}
66 changes: 66 additions & 0 deletions jablib/src/main/java/org/jabref/logic/ocr/OcrResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.jabref.logic.ocr;

import java.nio.file.Path;

import org.apache.commons.lang3.StringUtils;

/**
* Represents the result of an OCR operation.
* Uses sealed classes to ensure type safety and avoid null parameters.
*/
public sealed interface OcrResult {
/**
* Represents a successful OCR result, containing the recognized text and optional output file.
*/
record Success(String text, Path outputFile) implements OcrResult {
public Success(String text) {
this(text, null);
}
}

/**
* Represents a failed OCR result with an error message.
*/
record Failure(String errorMessage) implements OcrResult {
public Failure {
if (StringUtils.isBlank(errorMessage)) {
Comment thread
ZiadAbdElFatah marked this conversation as resolved.
Outdated
errorMessage = "Unknown error during OCR process";
}
}
}

/**
* Checks if this result is success.
*/
default boolean isSuccess() {
return this instanceof Success;
}

/**
* Checks if this result is failure.
*/
default boolean isFailure() {
return this instanceof Failure;
}

/**
* Factory method to create a success result with text only.
*/
static OcrResult success(String text) {
return new Success(text);
}

/**
* Factory method to create a success result with text and output file.
*/
static OcrResult success(String text, Path outputFile) {
return new Success(text, outputFile);
}

/**
* Factory method to create a failure result with an error message.
*/
static OcrResult failure(String errorMessage) {
return new Failure(errorMessage);
}
}
Loading