Skip to main content

mimick/api_client/
ocr.rs

1//! Text Immich recognized in photos (OCR).
2
3use super::ImmichApiClient;
4
5/// One recognized piece of text; corners run top-left, top-right, bottom-right, bottom-left,
6/// normalized 0–1 to the edited, orientation-corrected image.
7#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
8pub struct OcrBox {
9    pub text: String,
10    pub x1: f64,
11    pub y1: f64,
12    pub x2: f64,
13    pub y2: f64,
14    pub x3: f64,
15    pub y3: f64,
16    pub x4: f64,
17    pub y4: f64,
18}
19
20impl OcrBox {
21    pub fn corners(&self) -> [(f64, f64); 4] {
22        [
23            (self.x1, self.y1),
24            (self.x2, self.y2),
25            (self.x3, self.y3),
26            (self.x4, self.y4),
27        ]
28    }
29}
30
31impl ImmichApiClient {
32    /// OCR boxes for an asset (`GET /api/assets/{id}/ocr`).
33    pub async fn fetch_asset_ocr(&self, asset_id: &str) -> Result<Vec<OcrBox>, String> {
34        let path = format!("/api/assets/{asset_id}/ocr");
35        let resp = self.send_json(reqwest::Method::GET, &path, None).await?;
36        resp.json().await.map_err(|err| err.to_string())
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn ocr_box_parses_immich_response() {
46        let json = serde_json::json!([{
47            "id": "o1",
48            "assetId": "a1",
49            "text": "EXIT",
50            "textScore": 0.98,
51            "boxScore": 0.91,
52            "x1": 0.125,
53            "y1": 0.25,
54            "x2": 0.5,
55            "y2": 0.25,
56            "x3": 0.5,
57            "y3": 0.5,
58            "x4": 0.125,
59            "y4": 0.5
60        }]);
61        let boxes: Vec<OcrBox> = serde_json::from_value(json).unwrap();
62        assert_eq!(boxes[0].text, "EXIT");
63        assert_eq!(
64            boxes[0].corners(),
65            [(0.125, 0.25), (0.5, 0.25), (0.5, 0.5), (0.125, 0.5)]
66        );
67    }
68}