From 2e4ce6c4582ff0d4cb55cc2d08f2bce9591ab49e Mon Sep 17 00:00:00 2001
From: Junior
Date: Mon, 3 Aug 2026 21:16:57 -0400
Subject: [PATCH] Add feature to use OCR on card images and store with the data
so it can be searched on in the gallery
---
.gitignore | 2 ++
ajax/savecard.php | 2 ++
ajax/uploadimages.php | 1 +
class_image.php | 34 ++++++++++++++++++++--
composer.json | 5 ++++
composer.lock | 68 ++++++++++++++++++++++++++++++++++++++++++++
header.php | 1 +
install/initial_db_mysql.sql | 4 ++-
install/refreshocr.php | 19 +++++++++++++
install/testocr.php | 32 +++++++++++++++++++++
js/manage.js | 14 +++++----
manage.php | 4 +++
12 files changed, 176 insertions(+), 10 deletions(-)
create mode 100644 composer.json
create mode 100644 composer.lock
create mode 100644 install/refreshocr.php
create mode 100644 install/testocr.php
diff --git a/.gitignore b/.gitignore
index 1f2db4f..92fd865 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,5 @@ images/
?.php
sess.php
+
+vendor/
diff --git a/ajax/savecard.php b/ajax/savecard.php
index 136e806..38877c8 100644
--- a/ajax/savecard.php
+++ b/ajax/savecard.php
@@ -43,6 +43,8 @@ if ( $filename != $image->getFileName() ) {
}
}
+$image->setOCR($_REQUEST["cardtext"]);
+
$image->setEnabled(($_REQUEST["enabled"] == "1") ? true : false);
$image->save();
$data["image"] = $image;
diff --git a/ajax/uploadimages.php b/ajax/uploadimages.php
index 557f1f8..a3c558f 100644
--- a/ajax/uploadimages.php
+++ b/ajax/uploadimages.php
@@ -40,6 +40,7 @@ foreach ( $_FILES['images']['name'] as $key => $name ) {
$image->setFileName($filename);
$image->setWidth($width);
$image->setHeight($height);
+ $image->updateOCR();
$image->save();
}
}
diff --git a/class_image.php b/class_image.php
index 7e891a7..c1fe56d 100644
--- a/class_image.php
+++ b/class_image.php
@@ -1,5 +1,7 @@
ocr;
+ }
+
public function getWidth() {
return $this->width;
}
@@ -117,6 +124,12 @@ class MTGImage implements JsonSerializable {
return true;
}
+ public function setOCR($value = null) {
+ if ( is_null($value) || !is_string($value) ) return false;
+ $this->ocr = $value;
+ return true;
+ }
+
public function setWidth($value = null) {
if ( is_null($value) ) return false;
if ( !is_int($value) || ($value < 0) ) return false;
@@ -131,6 +144,18 @@ class MTGImage implements JsonSerializable {
return true;
}
+ public function updateOCR() {
+ try {
+ $text = (new TesseractOCR(IMAGEPATH . $this->getFileName()))
+ ->lang('eng')
+ ->run();
+ $text = str_replace("\n", " ", $text);
+ return $this->setOCR($text);
+ } catch ( Exception $e ) {
+ return $e->getMessage();
+ }
+ }
+
public function renameImage($value = "") {
$oldname = $this->getFileName();
$conflictImage = MTGImage::getImageByFileName($value);
@@ -207,7 +232,7 @@ class MTGImage implements JsonSerializable {
$query = "SELECT id FROM images ";
if ( !is_null($filter) ) {
- $query .= "WHERE filename LIKE :filter ";
+ $query .= "WHERE filename LIKE :filter OR ocr LIKE :filter ";
}
$query .= "ORDER BY LOWER(filename)";
$sth = $globaldbh->prepare($query);
@@ -236,6 +261,7 @@ class MTGImage implements JsonSerializable {
'title' => pathinfo($this->getFileName(), PATHINFO_FILENAME),
'enabled' => $this->getEnabled(),
'played' => $this->getPlayed(),
+ 'cardtext' => $this->getOCR(),
'width' => $this->getWidth(),
'height' => $this->getHeight(),
'dimensions' => $this->getDimensions(),
@@ -263,12 +289,13 @@ class MTGImage implements JsonSerializable {
public function save() {
global $globaldbh;
- $query = "INSERT INTO images (id, filename, width, height) VALUES(:id, :filename, :width, :height) ON DUPLICATE KEY UPDATE filename=:filename, enabled=:enabled, played=:played, width=:width, height=:height";
+ $query = "INSERT INTO images (id, filename, ocr, width, height) VALUES(:id, :filename, :ocr, :width, :height) ON DUPLICATE KEY UPDATE filename=:filename, enabled=:enabled, played=:played, ocr=:ocr, width=:width, height=:height";
$sth = $globaldbh->prepare($query);
$sth->bindValue(":id", (int) $this->getId(), PDO::PARAM_INT);
$sth->bindValue(":filename", $this->getFileName(), PDO::PARAM_STR);
$sth->bindValue(":enabled", (int) $this->getEnabled(MTGImage::MTGI_BOOLEANDB), PDO::PARAM_INT);
$sth->bindValue(":played", (int) $this->getPlayed(MTGImage::MTGI_BOOLEANDB), PDO::PARAM_INT);
+ $sth->bindValue(":ocr", $this->getOCR(), PDO::PARAM_STR);
$sth->bindValue(":width", (int) $this->getWidth(), PDO::PARAM_INT);
$sth->bindValue(":height", (int) $this->getHeight(), PDO::PARAM_INT);
$sth->execute();
@@ -280,7 +307,7 @@ class MTGImage implements JsonSerializable {
global $globaldbh;
if ( !is_int($imgid) || $imgid == 0 ) return;
- $query = "SELECT id, filename, enabled, played, width, height, created FROM images where id=:id";
+ $query = "SELECT id, filename, enabled, played, ocr, width, height, created FROM images where id=:id";
$sth = $globaldbh->prepare($query);
$sth->bindValue(":id", (int) $imgid, PDO::PARAM_INT);
$sth->execute();
@@ -289,6 +316,7 @@ class MTGImage implements JsonSerializable {
$this->setFileName($row["filename"]);
$this->setEnabled($row["enabled"]);
$this->setPlayed($row["played"]);
+ $this->setOCR($row["ocr"]);
$this->setWidth($row["width"]);
$this->setHeight($row["height"]);
$this->created = $row["created"];
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..acee0dc
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "thiagoalessio/tesseract_ocr": "2.*"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..d18a1fe
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,68 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "2fcc4ad110dab2a1eb87ee31f83940b4",
+ "packages": [
+ {
+ "name": "thiagoalessio/tesseract_ocr",
+ "version": "2.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thiagoalessio/tesseract-ocr-for-php.git",
+ "reference": "232a8cb9d571992f9bd1e263f2f6909cf6c173a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thiagoalessio/tesseract-ocr-for-php/zipball/232a8cb9d571992f9bd1e263f2f6909cf6c173a1",
+ "reference": "232a8cb9d571992f9bd1e263f2f6909cf6c173a1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/php-code-coverage": "^2.2.4 || ^9.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "thiagoalessio\\TesseractOCR\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "thiagoalessio",
+ "email": "thiagoalessio@me.com"
+ }
+ ],
+ "description": "A wrapper to work with Tesseract OCR inside PHP.",
+ "keywords": [
+ "OCR",
+ "Tesseract",
+ "text recognition"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.net/tesseract-ocr-for-php",
+ "issues": "https://github.com/thiagoalessio/tesseract-ocr-for-php/issues",
+ "source": "https://github.com/thiagoalessio/tesseract-ocr-for-php"
+ },
+ "time": "2023-10-05T21:14:48+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.6.0"
+}
diff --git a/header.php b/header.php
index 8682399..8707deb 100644
--- a/header.php
+++ b/header.php
@@ -2,6 +2,7 @@
require_once "config.php";
require_once "functions.php";
+require_once 'vendor/autoload.php';
require_once "class_image.php";
diff --git a/install/initial_db_mysql.sql b/install/initial_db_mysql.sql
index b685495..f7fcf7f 100644
--- a/install/initial_db_mysql.sql
+++ b/install/initial_db_mysql.sql
@@ -28,10 +28,12 @@ CREATE TABLE `images` (
`filename` varchar(255) NOT NULL,
`enabled` tinyint(1) DEFAULT 1,
`played` tinyint(1) DEFAULT 0,
+ `ocr` text DEFAULT '',
`width` mediumint(8) unsigned NOT NULL DEFAULT 0,
`height` mediumint(8) unsigned NOT NULL DEFAULT 0,
`created` datetime NOT NULL DEFAULT current_timestamp(),
- PRIMARY KEY (`id`)
+ PRIMARY KEY (`id`),
+ FULLTEXT KEY `idx_ocr` (`ocr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
diff --git a/install/refreshocr.php b/install/refreshocr.php
new file mode 100644
index 0000000..37ae419
--- /dev/null
+++ b/install/refreshocr.php
@@ -0,0 +1,19 @@
+updateOCR();
+ if ( $updated === true ) {
+ $image->save();
+ echo " Success on: {$image->getFileName()}\n";
+ } else {
+ echo "Error: " . $updated;
+ }
+}
+
+// vim: ts=4 sw=4:
diff --git a/install/testocr.php b/install/testocr.php
new file mode 100644
index 0000000..0322c55
--- /dev/null
+++ b/install/testocr.php
@@ -0,0 +1,32 @@
+lang('eng') // Set languages
+ ->run();
+
+ echo $text;
+} catch (Exception $e) {
+ echo 'Error: ' . $e->getMessage(), "\n";
+}
+
+// vim: ts=4 sw=4:
diff --git a/js/manage.js b/js/manage.js
index 197d3b0..2437f7b 100644
--- a/js/manage.js
+++ b/js/manage.js
@@ -26,6 +26,7 @@ $(document).ready(function() {
$(this).dialog("close");
$("#edit-props-id").val("0");
$("#edit-props-filename").val("");
+ $("#edit-props-cardtext").val("");
$("#edit-props-enabled").prop("checked", false);
},
"Save": saveCardProperties
@@ -33,16 +34,13 @@ $(document).ready(function() {
});
$("#searchfilter").on("keyup", function(event) {
clearTimeout(typingTimer);
- typingTimer = setTimeout(performSearch, 750);
+ typingTimer = setTimeout(showGallery, 750);
});
showGallery();
});
-function performSearch() {
- showGallery($("#searchfilter").val());
-}
-
-function showGallery(filter = "") {
+function showGallery() {
+ filter = $("#searchfilter").val();
$.ajax({
url: 'ajax/getimages.php',
data: {filter: filter},
@@ -183,6 +181,7 @@ function openEditDialog(imageId) {
$("#edit-image").attr("src", "thumb.php?id="+imageId);
$("#edit-props-id").val(imageId);
$("#edit-props-filename").val(data.image.title);
+ $("#edit-props-cardtext").val(data.image.cardtext);
$("#edit-props-enabled").prop("checked", data.image.enabled);
$("#edit-props-dimensions").html(data.image.dimensions);
$("#edit-props-size").html(data.image.size);
@@ -196,6 +195,7 @@ function clearEditDialog() {
$("#edit-image").attr("src", "img/error.png");
$("#edit-props-id").val("0");
$("#edit-props-filename").val("");
+ $("#edit-props-cardtext").val("");
$("#edit-props-enabled").prop("checked", false);
$("#edit-props-dimensions").html("");
$("#edit-props-size").html("");
@@ -230,8 +230,10 @@ function saveCardProperties() {
data: {
id: $("#edit-props-id").val(),
filename: $("#edit-props-filename").val(),
+ cardtext: $("#edit-props-cardtext").val(),
enabled: ($("#edit-props-enabled").prop("checked")) ? 1 : 0
},
+ type: 'POST',
success: function(data, stat, jqo) {
if ( !data.error ) {
clearEditDialog();
diff --git a/manage.php b/manage.php
index f3a1fc7..2e0dc08 100644
--- a/manage.php
+++ b/manage.php
@@ -27,6 +27,10 @@ $files = MTGImage::getList();
File Name:
+
+ Card Text:
+
+
Enabled: