use std::io::Result as IoResult;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::process;
use std::{error::Error, fmt, path::PathBuf};
use serde::{
Deserialize, Serialize, };
#[derive(Debug, Clone)]
pub struct OsNotSupportedError;
impl fmt::Display for OsNotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "OS not supported")
}
}
impl Error for OsNotSupportedError {}
type Point = [usize; 2];
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum OcrRec {
Content { code: u32, data: Vec<ContentData> },
Message { code: u32, data: String },
}
#[derive(Deserialize, Debug, Clone)]
pub struct ContentData {
#[serde(rename(deserialize = "box"))]
pub rect: Rectangle,
pub score: f64,
pub text: String,
}
pub type Rectangle = [Point; 4];
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ImageData {
ImagePathDict { image_path: String },
ImageBase64Dict { image_base64: String },
}
impl ImageData {
pub fn from_path<S>(path: S) -> ImageData
where
S: AsRef<str> + std::fmt::Display,
{
ImageData::ImagePathDict {
image_path: path.to_string(),
}
}
pub fn from_base64(base64: String) -> ImageData {
ImageData::ImageBase64Dict {
image_base64: base64,
}
}
#[cfg(feature = "bytes")]
pub fn from_bytes<T>(bytes: T) -> ImageData
where
T: AsRef<[u8]>,
{
use base64::Engine;
let engine = base64::engine::general_purpose::STANDARD;
ImageData::ImageBase64Dict {
image_base64: engine.encode(bytes),
}
}
}
impl From<&Path> for ImageData {
fn from(path: &Path) -> Self {
ImageData::from_path(path.to_string_lossy())
}
}
impl From<PathBuf> for ImageData {
fn from(path: PathBuf) -> Self {
ImageData::from_path(path.to_string_lossy())
}
}
pub struct Ppocr {
#[allow(dead_code)]
exe_path: PathBuf,
process: process::Child,
}
impl Ppocr {
pub fn new(exe_path: PathBuf, config_path: Option<PathBuf>) -> Result<Ppocr, Box<dyn Error>> {
std::env::set_var("RUST_BACKTRACE", "full");
if !cfg!(target_os = "windows") {
return Err(Box::new(OsNotSupportedError {}));
}
if !exe_path.exists() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Executable not found",
)));
}
let wd = exe_path
.canonicalize()?
.parent()
.ok_or_else(|| "No parent directory found")?
.to_path_buf();
let mut command = process::Command::new(&exe_path);
command.current_dir(wd);
if let Some(config_path) = config_path {
command.args(&["--config_path", &config_path.to_string_lossy()]);
}
let process = command
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.stdin(process::Stdio::piped())
.spawn()?;
let mut p = Ppocr { exe_path, process };
for _i in 1..10 {
match p.read_line() {
Ok(line) => {
if line.contains("OCR init completed.")
|| line.contains("Image path dose not exist")
{
break; }
}
Err(e) => {
return Err(Box::new(e));
}
}
}
Ok(p)
}
fn read_line(&mut self) -> IoResult<String> {
let mut buff = String::new();
let mut stdout = BufReader::new(self.process.stdout.as_mut().unwrap());
match stdout.read_line(&mut buff) {
Ok(_siz) => Ok(buff),
Err(e) => Err(e),
}
}
#[inline]
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> IoResult<()> {
let inner = self.process.stdin.as_mut().ok_or(std::io::Error::new(
std::io::ErrorKind::Other,
"stdin not piped",
))?;
inner.write_fmt(fmt)
}
pub fn ocr(&mut self, image: ImageData) -> IoResult<String> {
let s = serde_json::to_string(&image).unwrap().replace("\n", "");
self.write_fmt(format_args!("{}\n", s))?;
self.read_line()
}
#[inline]
pub fn ocr_clipboard(&mut self) -> IoResult<String> {
self.ocr(ImageData::from_path("clipboard"))
}
pub fn ocr_and_parse(&mut self, image: ImageData) -> Result<Vec<ContentData>, String> {
let ocr_result = self.ocr(image);
let Ok(ocr_string) = ocr_result.as_ref() else {
return Err("OCR failed".to_string());
};
match serde_json::from_str::<OcrRec>(&ocr_string) {
Ok(OcrRec::Content { data, .. }) => Ok(data),
Ok(OcrRec::Message { code, data }) => Err(format!("Error Message {}: {}", code, data)),
Err(e) => Err(format!("Response JSON parse failed: {}", e)),
}
}
}
impl Drop for Ppocr {
fn drop(&mut self) {
self.process.kill().err();
}
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use crate::{ImageData, Ppocr};
#[test]
fn recognize() {
let mut p = Ppocr::new(
PathBuf::from(
"E:/code/paddleocr/v1.4.0/PaddleOCR-json.exe", ),
Default::default(),
)
.unwrap();
let now = std::time::Instant::now(); {
println!(
"{}",
p.ocr(Path::new("C:/Users/Neko/Pictures/test1.png").into())
.unwrap()
);
println!(
"{}",
p.ocr(Path::new("C:/Users/Neko/Pictures/test2.png").into())
.unwrap()
);
println!(
"{}",
p.ocr(Path::new("C:/Users/Neko/Pictures/test3.png").into())
.unwrap()
);
println!(
"{}",
p.ocr(Path::new("C:/Users/Neko/Pictures/test4.png").into())
.unwrap()
);
println!(
"{}",
p.ocr(Path::new("C:/Users/Neko/Pictures/test5.png").into())
.unwrap()
);
println!("{}", p.ocr_clipboard().unwrap());
}
println!("Elapsed: {:.2?}", now.elapsed());
}
#[test]
fn parse() {
let mut p = Ppocr::new(
PathBuf::from("E:/code/paddleocr/v1.4.0/PaddleOCR-json.exe"), Default::default(), )
.unwrap();
p.ocr_and_parse(Path::new("C:/Users/Neko/Pictures/test2.png").into())
.unwrap();
p.ocr_and_parse(ImageData::from_bytes(include_bytes!(
"C:/Users/Neko/Pictures/test3.png"
)))
.unwrap();
}
}