add ocr
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
FROM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/server ./cmd/server
|
||||||
|
|
||||||
|
FROM alpine:3.21
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates curl
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /bin/server .
|
||||||
|
COPY internal/db/migrations ./internal/db/migrations
|
||||||
|
|
||||||
|
RUN mkdir -p /app/uploads /data
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["./server"]
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/vaultdrop/backend/internal/db"
|
"github.com/vaultdrop/backend/internal/db"
|
||||||
|
"github.com/vaultdrop/backend/internal/ocr"
|
||||||
"github.com/vaultdrop/backend/internal/service"
|
"github.com/vaultdrop/backend/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -191,6 +192,8 @@ func (h *Handlers) UploadFiles(c *gin.Context) {
|
|||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := ocr.NewClient("http://localhost:9090")
|
||||||
|
|
||||||
filesStats := []FileStats{}
|
filesStats := []FileStats{}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@@ -260,6 +263,15 @@ func (h *Handlers) UploadFiles(c *gin.Context) {
|
|||||||
Id: dbFile.ID,
|
Id: dbFile.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
text, err := client.Recognize(fileByte)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(text)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package ocr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
endpoint string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(endpoint string) *Client {
|
||||||
|
return &Client{
|
||||||
|
endpoint: endpoint,
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Timeout: 120 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Recognize(imageData []byte) ([]TextBlock, error) {
|
||||||
|
b64 := base64.StdEncoding.EncodeToString(imageData)
|
||||||
|
|
||||||
|
reqBody, err := json.Marshal(OCRRequest{Image: b64})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Post(
|
||||||
|
c.endpoint+"/ocr",
|
||||||
|
"application/json",
|
||||||
|
bytes.NewReader(reqBody),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("call paddleocr: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("paddleocr returned %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ocrResp OCRResponse
|
||||||
|
if err := json.Unmarshal(body, &ocrResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ocrResp.ErrorCode != 0 {
|
||||||
|
return nil, fmt.Errorf("paddleocr error %d: %s", ocrResp.ErrorCode, ocrResp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flattenResults(ocrResp.Result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) HealthCheck() error {
|
||||||
|
resp, err := c.httpClient.Get(c.endpoint + "/health")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("health check failed: status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenResults(result OCRResult) []TextBlock {
|
||||||
|
var blocks []TextBlock
|
||||||
|
for _, page := range result.OCRResults {
|
||||||
|
for i, text := range page.RecTexts {
|
||||||
|
score := 0.0
|
||||||
|
if i < len(page.RecScores) {
|
||||||
|
score = page.RecScores[i]
|
||||||
|
}
|
||||||
|
blocks = append(blocks, TextBlock{Text: text, Score: score})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocks
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package ocr
|
||||||
|
|
||||||
|
type OCRRequest struct {
|
||||||
|
Image string `json:"image"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCRResponse struct {
|
||||||
|
ErrorCode int `json:"errorCode"`
|
||||||
|
Result OCRResult `json:"result"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCRResult struct {
|
||||||
|
OCRResults []OCRPageResult `json:"ocrResults"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OCRPageResult struct {
|
||||||
|
RecTexts []string `json:"rec_texts"`
|
||||||
|
RecScores []float64 `json:"rec_scores"`
|
||||||
|
RecBoxes [][]int `json:"rec_boxes"`
|
||||||
|
RecPolys [][][]int `json:"rec_polys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextBlock struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
Score float64 `json:"score"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
services:
|
||||||
|
paddleocr:
|
||||||
|
image: paddlecloud/paddleocr:2.6-cpu-latest
|
||||||
|
ports:
|
||||||
|
- "9090:8080"
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
|
||||||
|
# backend:
|
||||||
|
# build:
|
||||||
|
# context: ./backend
|
||||||
|
# dockerfile: Dockerfile
|
||||||
|
# ports:
|
||||||
|
# - "8080:8080"
|
||||||
|
# environment:
|
||||||
|
# - PORT=8080
|
||||||
|
# - DB_PATH=/data/vaultdrop.db
|
||||||
|
# - OCR_ENDPOINT=http://paddleocr:8080
|
||||||
|
# volumes:
|
||||||
|
# - backend-data:/data
|
||||||
|
# - ./backend/uploads:/app/uploads
|
||||||
|
# depends_on:
|
||||||
|
# paddleocr:
|
||||||
|
# condition: service_healthy
|
||||||
|
# restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
backend-data:
|
||||||
Reference in New Issue
Block a user