SohomToom commited on
Commit
31dacfa
·
verified ·
1 Parent(s): f1615f3

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +135 -0
  2. requirements.txt +9 -0
app.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import cv2
5
+ import numpy as np
6
+ import gradio as gr
7
+ import zipfile
8
+ import patoolib
9
+ import threading
10
+ from paddleocr import PaddleOCR
11
+ from PIL import Image
12
+
13
+ def delayed_cleanup(path, delay=30):
14
+ def cleanup():
15
+ import time
16
+ time.sleep(delay)
17
+ try:
18
+ if os.path.isdir(path):
19
+ shutil.rmtree(path)
20
+ elif os.path.exists(path):
21
+ os.remove(path)
22
+ except Exception as e:
23
+ print(f"Cleanup failed: {e}")
24
+ threading.Thread(target=cleanup).start()
25
+
26
+ ocr = PaddleOCR(use_angle_cls=True, lang='en', det_model_dir='models/det', rec_model_dir='models/rec', cls_model_dir='models/cls')
27
+
28
+ def classify_background_color(avg_color, white_thresh=230, black_thresh=50, yellow_thresh=100):
29
+ r, g, b = avg_color
30
+ if r >= white_thresh and g >= white_thresh and b >= white_thresh:
31
+ return (255, 255, 255)
32
+ if r <= black_thresh and g <= black_thresh and b <= black_thresh:
33
+ return (0, 0, 0)
34
+ if r >= yellow_thresh and g >= yellow_thresh and b < yellow_thresh:
35
+ return (255, 255, 0)
36
+ return None
37
+
38
+ def sample_border_color(image, box, padding=2):
39
+ h, w = image.shape[:2]
40
+ x_min, y_min, x_max, y_max = box
41
+ x_min = max(0, x_min - padding)
42
+ x_max = min(w-1, x_max + padding)
43
+ y_min = max(0, y_min - padding)
44
+ y_max = min(h-1, y_max + padding)
45
+
46
+ top = image[y_min:y_min+padding, x_min:x_max]
47
+ bottom = image[y_max-padding:y_max, x_min:x_max]
48
+ left = image[y_min:y_max, x_min:x_min+padding]
49
+ right = image[y_min:y_max, x_max-padding:x_max]
50
+
51
+ border_pixels = np.vstack((top.reshape(-1, 3), bottom.reshape(-1, 3),
52
+ left.reshape(-1, 3), right.reshape(-1, 3)))
53
+ if border_pixels.size == 0:
54
+ return (255, 255, 255)
55
+ median_color = np.median(border_pixels, axis=0)
56
+ return tuple(map(int, median_color))
57
+
58
+ def detect_text_boxes(image):
59
+ results = ocr.ocr(image, cls=True)
60
+ if not results or not results[0]:
61
+ return []
62
+ boxes = []
63
+ for line in results[0]:
64
+ box, (text, confidence) = line
65
+ if text.strip():
66
+ x_min = int(min(pt[0] for pt in box))
67
+ x_max = int(max(pt[0] for pt in box))
68
+ y_min = int(min(pt[1] for pt in box))
69
+ y_max = int(max(pt[1] for pt in box))
70
+ boxes.append(((x_min, y_min, x_max, y_max), text, confidence))
71
+ return boxes
72
+
73
+ def remove_text_dynamic_fill(img_path, output_path):
74
+ image = cv2.imread(img_path)
75
+ if image is None:
76
+ return
77
+ if len(image.shape) == 2 or image.shape[2] == 1:
78
+ image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
79
+ else:
80
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
81
+
82
+ boxes = detect_text_boxes(image)
83
+
84
+ for (bbox, text, confidence) in boxes:
85
+ if confidence < 0.4 or not text.strip():
86
+ continue
87
+ x_min, y_min, x_max, y_max = bbox
88
+ height = y_max - y_min
89
+ padding = 2 if height <= 30 else 4 if height <= 60 else 6
90
+ x_min_p = max(0, x_min - padding)
91
+ y_min_p = max(0, y_min - padding)
92
+ x_max_p = min(image.shape[1]-1, x_max + padding)
93
+ y_max_p = min(image.shape[0]-1, y_max + padding)
94
+ sample_crop = image[y_min_p:y_max_p, x_min_p:x_max_p]
95
+ avg_color = np.mean(sample_crop.reshape(-1, 3), axis=0)
96
+ fill_color = classify_background_color(avg_color)
97
+ if fill_color is None:
98
+ fill_color = sample_border_color(image, (x_min, y_min, x_max, y_max))
99
+ cv2.rectangle(image, (x_min_p, y_min_p), (x_max_p, y_max_p), fill_color, -1)
100
+
101
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
102
+ cv2.imwrite(output_path, image)
103
+
104
+ def extract_comic_archive(archive_path, extract_to):
105
+ if archive_path.endswith(".cbz"):
106
+ with zipfile.ZipFile(archive_path, 'r') as zip_ref:
107
+ zip_ref.extractall(extract_to)
108
+ elif archive_path.endswith(".cbr"):
109
+ patoolib.extract_archive(archive_path, outdir=extract_to)
110
+
111
+ def process_cbz_cbr(file):
112
+ temp_input = tempfile.mkdtemp()
113
+ temp_output = tempfile.mkdtemp()
114
+ extract_comic_archive(file.name, temp_input)
115
+ for root, _, files in os.walk(temp_input):
116
+ for fname in files:
117
+ if fname.lower().endswith((".jpg", ".jpeg", ".png")):
118
+ src = os.path.join(root, fname)
119
+ dst = os.path.join(temp_output, fname)
120
+ remove_text_dynamic_fill(src, dst)
121
+ zip_path = shutil.make_archive(temp_output, 'zip', temp_output)
122
+ delayed_cleanup(temp_input)
123
+ delayed_cleanup(temp_output)
124
+ delayed_cleanup(zip_path)
125
+ return zip_path
126
+
127
+ demo = gr.Interface(
128
+ fn=process_cbz_cbr,
129
+ inputs=gr.File(file_types=[".cbz", ".cbr"], label="Upload Comic Archive (.cbz or .cbr)"),
130
+ outputs=gr.File(label="Download Cleaned Zip"),
131
+ title="Comic Text Cleaner (.cbz/.cbr)",
132
+ description="Upload a .cbz or .cbr file and get a zip of cleaned comic images (text removed using PaddleOCR)."
133
+ )
134
+
135
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ paddleocr==2.7.1
2
+ paddlepaddle==2.5.2
3
+ opencv-python
4
+ gradio
5
+ Pillow
6
+ numpy
7
+ pyunpack
8
+ patool
9
+