Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 78 additions & 0 deletions app/lib/r3x/client/ocr.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require "base64"

module R3x
module Client
class Ocr
ENDPOINT = "parse/image"
BASE_URL = "https://api.ocr.space"

MIME_TYPES = {
".png" => "image/png",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".gif" => "image/gif",
".tif" => "image/tiff",
".tiff" => "image/tiff",
".bmp" => "image/bmp",
".pdf" => "application/pdf"
}.freeze

def initialize(api_key_env: "OCRSPACE_API_KEY")
@api_key = R3x::Env.secure_fetch(api_key_env, prefix: "OCRSPACE_API_KEY")
end

def parse(io_or_path, language: nil, engine: nil, filetype: nil, overlay: false)
mime_type = filetype || detect_mime(io_or_path)
params = build_params(io_or_path, mime_type, language: language, engine: engine, overlay: overlay)
response = connection.post(ENDPOINT, params)
raise "OCR request failed: #{response.status}" unless response.success?

body = response.body
raise "OCR API error: #{body["ErrorMessage"]}" if body["IsErroredOnProcessing"]

Result.new(body)
end

private

attr_reader :api_key

def connection
@connection ||= Faraday.new(url: BASE_URL) do |f|
f.request :url_encoded
f.response :json
f.options.timeout = 30
f.options.open_timeout = 5
f.headers["apikey"] = api_key
end
end

def detect_mime(io_or_path)
if io_or_path.respond_to?(:read)
raise ArgumentError, "filetype required for IO objects (e.g. filetype: 'image/jpeg')"
end

ext = File.extname(io_or_path.to_s).downcase
MIME_TYPES.fetch(ext) do
raise ArgumentError, "Unsupported file extension: '#{ext}'. Pass filetype: explicitly."
end
end

def build_params(io_or_path, mime_type, language:, engine:, overlay:)
params = {
isOverlayRequired: overlay.to_s
}
params[:language] = language if language
params[:OCREngine] = engine.to_s if engine

raw = io_or_path.respond_to?(:read) ? io_or_path.read : File.binread(io_or_path.to_s)
encoded = Base64.strict_encode64(raw)
params[:base64Image] = "data:#{mime_type};base64,#{encoded}"

params
end
end
end
end
51 changes: 51 additions & 0 deletions app/lib/r3x/client/ocr/result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module R3x
module Client
class Ocr
class Result
include Enumerable

def initialize(body)
@body = body
@pages = body.fetch("ParsedResults", []).map { |r| Page.new(r) }
end

def text
pages.map(&:text).join("\n")
end

def success?
exit_code == 1
end

def partial?
exit_code == 2
end

def exit_code
body["OCRExitCode"].to_i
end

def processing_time_ms
body["ProcessingTimeInMilliseconds"]&.to_i
end

def each(&block)
pages.each(&block)
end

private

attr_reader :body, :pages

Page = Struct.new(:data) do
def text; data["ParsedText"]; end
def success?; data["FileParseExitCode"].to_i == 1; end
def error_message; data["ErrorMessage"]; end
def error_details; data["ErrorDetails"]; end
end
end
end
end
end
Loading