SohomToom commited on
Commit
cb7a3b8
·
verified ·
1 Parent(s): 5210bae

Update app.py

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