podsni commited on
Commit
0c63b0c
·
verified ·
1 Parent(s): 2bea478

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +974 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Realtime Ocr
3
- emoji: 🚀
4
- colorFrom: blue
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: realtime-ocr
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,974 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Real-Time OCR Scanner</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/[email protected]/dist/tesseract.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <style>
11
+ .video-container {
12
+ position: relative;
13
+ width: 100%;
14
+ max-width: 640px;
15
+ margin: 0 auto;
16
+ background: #111827;
17
+ border-radius: 12px;
18
+ overflow: hidden;
19
+ }
20
+ #video {
21
+ width: 100%;
22
+ height: auto;
23
+ transform: rotateY(180deg);
24
+ -webkit-transform: rotateY(180deg);
25
+ }
26
+ #canvas {
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ width: 100%;
31
+ height: 100%;
32
+ display: none;
33
+ }
34
+ .scan-box {
35
+ position: absolute;
36
+ top: 50%;
37
+ left: 50%;
38
+ transform: translate(-50%, -50%);
39
+ width: 80%;
40
+ height: 30%;
41
+ border: 3px dashed rgba(59, 130, 246, 0.7);
42
+ border-radius: 8px;
43
+ pointer-events: none;
44
+ box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.5);
45
+ }
46
+ .pulse {
47
+ animation: pulse 2s infinite;
48
+ }
49
+ @keyframes pulse {
50
+ 0% {
51
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
52
+ }
53
+ 70% {
54
+ box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
55
+ }
56
+ 100% {
57
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
58
+ }
59
+ }
60
+ .result-text {
61
+ white-space: pre-wrap;
62
+ word-break: break-word;
63
+ }
64
+ .language-selector {
65
+ background-color: rgba(0, 0, 0, 0.7);
66
+ backdrop-filter: blur(5px);
67
+ }
68
+ .preview-image {
69
+ max-height: 300px;
70
+ object-fit: contain;
71
+ border-radius: 8px;
72
+ margin: 0 auto;
73
+ display: block;
74
+ }
75
+ .tab-button {
76
+ transition: all 0.3s ease;
77
+ }
78
+ .tab-button.active {
79
+ background-color: #3b82f6;
80
+ color: white;
81
+ }
82
+ .progress-bar {
83
+ height: 4px;
84
+ background-color: #3b82f6;
85
+ transition: width 0.3s ease;
86
+ }
87
+ .dropzone {
88
+ border: 2px dashed #4b5563;
89
+ border-radius: 8px;
90
+ padding: 2rem;
91
+ text-align: center;
92
+ cursor: pointer;
93
+ transition: all 0.3s ease;
94
+ }
95
+ .dropzone.active {
96
+ border-color: #3b82f6;
97
+ background-color: rgba(59, 130, 246, 0.1);
98
+ }
99
+ .enhance-options {
100
+ transition: all 0.3s ease;
101
+ max-height: 0;
102
+ overflow: hidden;
103
+ }
104
+ .enhance-options.open {
105
+ max-height: 300px;
106
+ padding: 1rem 0;
107
+ }
108
+ </style>
109
+ </head>
110
+ <body class="bg-gray-900 text-white min-h-screen">
111
+ <div class="container mx-auto px-4 py-8">
112
+ <header class="text-center mb-8">
113
+ <h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent">
114
+ <i class="fas fa-camera-retro mr-2"></i> Advanced OCR Scanner
115
+ </h1>
116
+ <p class="text-gray-400 max-w-2xl mx-auto">
117
+ Extract text from camera or uploaded images with enhanced precision and editing tools
118
+ </p>
119
+ </header>
120
+
121
+ <!-- Tab Navigation -->
122
+ <div class="flex justify-center mb-8">
123
+ <div class="inline-flex rounded-full bg-gray-800 p-1">
124
+ <button id="cameraTab" class="tab-button px-6 py-2 rounded-full font-medium flex items-center active">
125
+ <i class="fas fa-camera mr-2"></i> Camera
126
+ </button>
127
+ <button id="uploadTab" class="tab-button px-6 py-2 rounded-full font-medium flex items-center">
128
+ <i class="fas fa-upload mr-2"></i> Upload
129
+ </button>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="flex flex-col lg:flex-row gap-8">
134
+ <!-- Input Section -->
135
+ <div class="flex-1">
136
+ <div class="bg-gray-800 rounded-xl p-4 shadow-xl">
137
+ <!-- Camera Section -->
138
+ <div id="cameraSection">
139
+ <div class="flex justify-between items-center mb-4">
140
+ <h2 class="text-xl font-semibold">
141
+ <i class="fas fa-video mr-2 text-blue-400"></i> Camera Feed
142
+ </h2>
143
+ <div class="flex items-center gap-2">
144
+ <div class="language-selector px-3 py-1 rounded-full">
145
+ <select id="language" class="bg-transparent text-white focus:outline-none">
146
+ <option value="eng">English</option>
147
+ <option value="spa">Spanish</option>
148
+ <option value="fra">French</option>
149
+ <option value="deu">German</option>
150
+ <option value="chi_sim">Chinese</option>
151
+ <option value="jpn">Japanese</option>
152
+ <option value="kor">Korean</option>
153
+ <option value="ara">Arabic</option>
154
+ </select>
155
+ </div>
156
+ <button id="enhanceBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded-full text-sm">
157
+ <i class="fas fa-sliders-h mr-1"></i> Enhance
158
+ </button>
159
+ </div>
160
+ </div>
161
+
162
+ <div class="enhance-options mb-4">
163
+ <div class="grid grid-cols-2 gap-4">
164
+ <div>
165
+ <label class="block text-sm text-gray-400 mb-1">Brightness</label>
166
+ <input type="range" id="brightness" min="-100" max="100" value="0" class="w-full">
167
+ </div>
168
+ <div>
169
+ <label class="block text-sm text-gray-400 mb-1">Contrast</label>
170
+ <input type="range" id="contrast" min="-100" max="100" value="0" class="w-full">
171
+ </div>
172
+ <div>
173
+ <label class="block text-sm text-gray-400 mb-1">Threshold</label>
174
+ <input type="range" id="threshold" min="0" max="255" value="0" class="w-full">
175
+ </div>
176
+ <div>
177
+ <label class="block text-sm text-gray-400 mb-1">Sharpness</label>
178
+ <input type="range" id="sharpness" min="0" max="200" value="100" class="w-full">
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
+ <div class="video-container">
184
+ <video id="video" autoplay playsinline></video>
185
+ <canvas id="canvas"></canvas>
186
+ <div class="scan-box"></div>
187
+ </div>
188
+
189
+ <div class="flex justify-center mt-4 gap-4">
190
+ <button id="startBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-full font-medium flex items-center pulse">
191
+ <i class="fas fa-play mr-2"></i> Start Camera
192
+ </button>
193
+ <button id="captureBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-full font-medium flex items-center opacity-50 cursor-not-allowed" disabled>
194
+ <i class="fas fa-camera mr-2"></i> Capture Text
195
+ </button>
196
+ </div>
197
+ </div>
198
+
199
+ <!-- Upload Section -->
200
+ <div id="uploadSection" class="hidden">
201
+ <div class="flex justify-between items-center mb-4">
202
+ <h2 class="text-xl font-semibold">
203
+ <i class="fas fa-upload mr-2 text-blue-400"></i> Upload Image
204
+ </h2>
205
+ <div class="flex items-center gap-2">
206
+ <div class="language-selector px-3 py-1 rounded-full">
207
+ <select id="uploadLanguage" class="bg-transparent text-white focus:outline-none">
208
+ <option value="eng">English</option>
209
+ <option value="spa">Spanish</option>
210
+ <option value="fra">French</option>
211
+ <option value="deu">German</option>
212
+ <option value="chi_sim">Chinese</option>
213
+ <option value="jpn">Japanese</option>
214
+ <option value="kor">Korean</option>
215
+ <option value="ara">Arabic</option>
216
+ </select>
217
+ </div>
218
+ <button id="uploadEnhanceBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded-full text-sm">
219
+ <i class="fas fa-sliders-h mr-1"></i> Enhance
220
+ </button>
221
+ </div>
222
+ </div>
223
+
224
+ <div class="enhance-options mb-4">
225
+ <div class="grid grid-cols-2 gap-4">
226
+ <div>
227
+ <label class="block text-sm text-gray-400 mb-1">Brightness</label>
228
+ <input type="range" id="uploadBrightness" min="-100" max="100" value="0" class="w-full">
229
+ </div>
230
+ <div>
231
+ <label class="block text-sm text-gray-400 mb-1">Contrast</label>
232
+ <input type="range" id="uploadContrast" min="-100" max="100" value="0" class="w-full">
233
+ </div>
234
+ <div>
235
+ <label class="block text-sm text-gray-400 mb-1">Threshold</label>
236
+ <input type="range" id="uploadThreshold" min="0" max="255" value="0" class="w-full">
237
+ </div>
238
+ <div>
239
+ <label class="block text-sm text-gray-400 mb-1">Sharpness</label>
240
+ <input type="range" id="uploadSharpness" min="0" max="200" value="100" class="w-full">
241
+ </div>
242
+ </div>
243
+ <button id="applyEnhanceBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-full text-sm mt-2">
244
+ <i class="fas fa-magic mr-1"></i> Apply Enhancements
245
+ </button>
246
+ </div>
247
+
248
+ <div class="dropzone" id="dropzone">
249
+ <i class="fas fa-cloud-upload-alt text-4xl text-blue-400 mb-2"></i>
250
+ <p class="font-medium">Drag & drop your image here</p>
251
+ <p class="text-sm text-gray-400 mt-1">or</p>
252
+ <label for="fileInput" class="inline-block bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-full mt-2 cursor-pointer">
253
+ <i class="fas fa-folder-open mr-1"></i> Browse Files
254
+ </label>
255
+ <input type="file" id="fileInput" accept="image/*" class="hidden">
256
+ </div>
257
+
258
+ <div id="imagePreviewContainer" class="hidden mt-4">
259
+ <div class="relative">
260
+ <img id="imagePreview" class="preview-image" src="" alt="Preview">
261
+ <canvas id="uploadCanvas" class="hidden"></canvas>
262
+ <div class="absolute top-2 right-2">
263
+ <button id="removeImageBtn" class="bg-red-600 hover:bg-red-700 text-white p-2 rounded-full">
264
+ <i class="fas fa-times"></i>
265
+ </button>
266
+ </div>
267
+ </div>
268
+ <button id="processImageBtn" class="w-full bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-full font-medium flex items-center justify-center mt-4">
269
+ <i class="fas fa-cogs mr-2"></i> Process Image
270
+ </button>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </div>
275
+
276
+ <!-- Results Section -->
277
+ <div class="flex-1">
278
+ <div class="bg-gray-800 rounded-xl p-4 shadow-xl h-full">
279
+ <div class="flex justify-between items-center mb-4">
280
+ <h2 class="text-xl font-semibold">
281
+ <i class="fas fa-file-alt mr-2 text-green-400"></i> Extracted Text
282
+ </h2>
283
+ <div class="text-sm text-gray-400">
284
+ <span id="status">Status: Ready</span>
285
+ </div>
286
+ </div>
287
+
288
+ <div class="progress-bar-container bg-gray-700 rounded-full h-1 mb-4">
289
+ <div id="progressBar" class="progress-bar rounded-full" style="width: 0%"></div>
290
+ </div>
291
+
292
+ <div class="bg-gray-900 rounded-lg p-4 h-96 overflow-y-auto mb-4">
293
+ <div id="results" class="result-text text-gray-300">
294
+ <div class="text-center text-gray-500 py-16">
295
+ <i class="fas fa-align-left text-4xl mb-2"></i>
296
+ <p>Extracted text will appear here</p>
297
+ </div>
298
+ </div>
299
+ </div>
300
+
301
+ <div class="flex justify-between items-center">
302
+ <div class="text-sm text-gray-400">
303
+ <span id="confidence">Confidence: --</span>
304
+ </div>
305
+ <div class="flex gap-2">
306
+ <button id="copyBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
307
+ <i class="fas fa-copy mr-1"></i> Copy
308
+ </button>
309
+ <button id="clearBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
310
+ <i class="fas fa-trash-alt mr-1"></i> Clear
311
+ </button>
312
+ <button id="downloadBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
313
+ <i class="fas fa-download mr-1"></i> Save
314
+ </button>
315
+ <button id="editBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
316
+ <i class="fas fa-edit mr-1"></i> Edit
317
+ </button>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </div>
322
+ </div>
323
+
324
+ <!-- Features Section -->
325
+ <div class="mt-12">
326
+ <h2 class="text-2xl font-bold text-center mb-6 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent">
327
+ <i class="fas fa-star mr-2"></i> Key Features
328
+ </h2>
329
+ <div class="grid md:grid-cols-3 gap-6">
330
+ <div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
331
+ <div class="text-blue-400 text-3xl mb-4">
332
+ <i class="fas fa-camera"></i>
333
+ </div>
334
+ <h3 class="text-xl font-semibold mb-2">Real-Time Scanning</h3>
335
+ <p class="text-gray-400">Capture text instantly using your device's camera with auto-focus and enhancement tools.</p>
336
+ </div>
337
+ <div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
338
+ <div class="text-purple-400 text-3xl mb-4">
339
+ <i class="fas fa-image"></i>
340
+ </div>
341
+ <h3 class="text-xl font-semibold mb-2">Image Upload</h3>
342
+ <p class="text-gray-400">Extract text from existing images in your gallery with drag & drop support.</p>
343
+ </div>
344
+ <div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
345
+ <div class="text-green-400 text-3xl mb-4">
346
+ <i class="fas fa-magic"></i>
347
+ </div>
348
+ <h3 class="text-xl font-semibold mb-2">Image Enhancement</h3>
349
+ <p class="text-gray-400">Adjust brightness, contrast, threshold and sharpness for better OCR results.</p>
350
+ </div>
351
+ <div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
352
+ <div class="text-yellow-400 text-3xl mb-4">
353
+ <i class="fas fa-language"></i>
354
+ </div>
355
+ <h3 class="text-xl font-semibold mb-2">Multi-Language</h3>
356
+ <p class="text-gray-400">Supports 8 languages including English, Spanish, Chinese, Arabic and more.</p>
357
+ </div>
358
+ <div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
359
+ <div class="text-red-400 text-3xl mb-4">
360
+ <i class="fas fa-chart-line"></i>
361
+ </div>
362
+ <h3 class="text-xl font-semibold mb-2">Confidence Score</h3>
363
+ <p class="text-gray-400">See how confident the OCR engine is about each text extraction.</p>
364
+ </div>
365
+ <div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
366
+ <div class="text-indigo-400 text-3xl mb-4">
367
+ <i class="fas fa-file-export"></i>
368
+ </div>
369
+ <h3 class="text-xl font-semibold mb-2">Export Options</h3>
370
+ <p class="text-gray-400">Copy, edit or download your extracted text for further use.</p>
371
+ </div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+
376
+ <script>
377
+ document.addEventListener('DOMContentLoaded', function() {
378
+ // DOM Elements
379
+ const video = document.getElementById('video');
380
+ const canvas = document.getElementById('canvas');
381
+ const startBtn = document.getElementById('startBtn');
382
+ const captureBtn = document.getElementById('captureBtn');
383
+ const resultsDiv = document.getElementById('results');
384
+ const statusSpan = document.getElementById('status');
385
+ const confidenceSpan = document.getElementById('confidence');
386
+ const copyBtn = document.getElementById('copyBtn');
387
+ const clearBtn = document.getElementById('clearBtn');
388
+ const downloadBtn = document.getElementById('downloadBtn');
389
+ const editBtn = document.getElementById('editBtn');
390
+ const progressBar = document.getElementById('progressBar');
391
+ const languageSelect = document.getElementById('language');
392
+ const cameraTab = document.getElementById('cameraTab');
393
+ const uploadTab = document.getElementById('uploadTab');
394
+ const cameraSection = document.getElementById('cameraSection');
395
+ const uploadSection = document.getElementById('uploadSection');
396
+ const dropzone = document.getElementById('dropzone');
397
+ const fileInput = document.getElementById('fileInput');
398
+ const imagePreviewContainer = document.getElementById('imagePreviewContainer');
399
+ const imagePreview = document.getElementById('imagePreview');
400
+ const uploadCanvas = document.getElementById('uploadCanvas');
401
+ const removeImageBtn = document.getElementById('removeImageBtn');
402
+ const processImageBtn = document.getElementById('processImageBtn');
403
+ const uploadLanguage = document.getElementById('uploadLanguage');
404
+ const enhanceBtn = document.getElementById('enhanceBtn');
405
+ const uploadEnhanceBtn = document.getElementById('uploadEnhanceBtn');
406
+ const enhanceOptions = document.querySelector('#cameraSection .enhance-options');
407
+ const uploadEnhanceOptions = document.querySelector('#uploadSection .enhance-options');
408
+ const applyEnhanceBtn = document.getElementById('applyEnhanceBtn');
409
+
410
+ // State variables
411
+ let stream = null;
412
+ let isScanning = false;
413
+ let scanInterval = null;
414
+ let currentImageData = null;
415
+
416
+ // Tab switching
417
+ cameraTab.addEventListener('click', function() {
418
+ cameraTab.classList.add('active');
419
+ uploadTab.classList.remove('active');
420
+ cameraSection.classList.remove('hidden');
421
+ uploadSection.classList.add('hidden');
422
+ stopCamera(); // Stop camera when switching to upload tab
423
+ });
424
+
425
+ uploadTab.addEventListener('click', function() {
426
+ uploadTab.classList.add('active');
427
+ cameraTab.classList.remove('active');
428
+ cameraSection.classList.add('hidden');
429
+ uploadSection.classList.remove('hidden');
430
+ stopCamera(); // Stop camera when switching to upload tab
431
+ });
432
+
433
+ // Enhance options toggle
434
+ enhanceBtn.addEventListener('click', function() {
435
+ enhanceOptions.classList.toggle('open');
436
+ });
437
+
438
+ uploadEnhanceBtn.addEventListener('click', function() {
439
+ uploadEnhanceOptions.classList.toggle('open');
440
+ });
441
+
442
+ // Start camera
443
+ startBtn.addEventListener('click', async function() {
444
+ try {
445
+ if (stream) {
446
+ stopCamera();
447
+ return;
448
+ }
449
+
450
+ statusSpan.textContent = 'Status: Accessing camera...';
451
+ stream = await navigator.mediaDevices.getUserMedia({
452
+ video: {
453
+ facingMode: 'environment',
454
+ width: { ideal: 1280 },
455
+ height: { ideal: 720 }
456
+ },
457
+ audio: false
458
+ });
459
+
460
+ video.srcObject = stream;
461
+ startBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Camera';
462
+ startBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
463
+ startBtn.classList.add('bg-red-600', 'hover:bg-red-700');
464
+ captureBtn.disabled = false;
465
+ captureBtn.classList.remove('opacity-50', 'cursor-not-allowed');
466
+ statusSpan.textContent = 'Status: Camera ready';
467
+
468
+ // Auto-focus every 2 seconds (simulated)
469
+ scanInterval = setInterval(() => {
470
+ if (isScanning) return;
471
+ autoScan();
472
+ }, 2000);
473
+
474
+ } catch (err) {
475
+ console.error('Error accessing camera:', err);
476
+ statusSpan.textContent = 'Status: Error accessing camera';
477
+ showError(`Could not access camera: ${err.message}`);
478
+ }
479
+ });
480
+
481
+ function stopCamera() {
482
+ if (stream) {
483
+ stream.getTracks().forEach(track => track.stop());
484
+ stream = null;
485
+ video.srcObject = null;
486
+ startBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Start Camera';
487
+ startBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
488
+ startBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
489
+ captureBtn.disabled = true;
490
+ captureBtn.classList.add('opacity-50', 'cursor-not-allowed');
491
+ statusSpan.textContent = 'Status: Camera stopped';
492
+ clearInterval(scanInterval);
493
+ isScanning = false;
494
+ }
495
+ }
496
+
497
+ // Capture button
498
+ captureBtn.addEventListener('click', function() {
499
+ if (!stream) return;
500
+ captureFromCamera();
501
+ });
502
+
503
+ function captureFromCamera() {
504
+ isScanning = true;
505
+ statusSpan.textContent = 'Status: Processing image...';
506
+ captureBtn.disabled = true;
507
+ captureBtn.classList.add('opacity-50', 'cursor-not-allowed');
508
+ progressBar.style.width = '0%';
509
+
510
+ // Get enhancement values
511
+ const brightness = parseInt(document.getElementById('brightness').value);
512
+ const contrast = parseInt(document.getElementById('contrast').value);
513
+ const threshold = parseInt(document.getElementById('threshold').value);
514
+ const sharpness = parseInt(document.getElementById('sharpness').value) / 100;
515
+
516
+ // Get the dimensions of the scan box
517
+ const videoWidth = video.videoWidth;
518
+ const videoHeight = video.videoHeight;
519
+ const boxWidth = video.offsetWidth * 0.8;
520
+ const boxHeight = video.offsetHeight * 0.3;
521
+ const boxLeft = (video.offsetWidth - boxWidth) / 2;
522
+ const boxTop = (video.offsetHeight - boxHeight) / 2;
523
+
524
+ // Calculate the actual capture area in video coordinates
525
+ const scaleX = videoWidth / video.offsetWidth;
526
+ const scaleY = videoHeight / video.offsetHeight;
527
+
528
+ const captureWidth = boxWidth * scaleX;
529
+ const captureHeight = boxHeight * scaleY;
530
+ const captureLeft = boxLeft * scaleX;
531
+ const captureTop = boxTop * scaleY;
532
+
533
+ // Set canvas dimensions
534
+ canvas.width = captureWidth;
535
+ canvas.height = captureHeight;
536
+
537
+ // Draw video frame to canvas (only the scan box area)
538
+ const ctx = canvas.getContext('2d');
539
+ ctx.drawImage(
540
+ video,
541
+ captureLeft, captureTop, captureWidth, captureHeight,
542
+ 0, 0, captureWidth, captureHeight
543
+ );
544
+
545
+ // Apply image enhancements
546
+ if (brightness !== 0 || contrast !== 0 || threshold > 0 || sharpness !== 1) {
547
+ applyImageEnhancements(ctx, canvas, brightness, contrast, threshold, sharpness);
548
+ }
549
+
550
+ // Get image data from canvas
551
+ const imageData = canvas.toDataURL('image/jpeg', 0.8);
552
+ currentImageData = imageData;
553
+
554
+ // Process with Tesseract
555
+ processImage(imageData, languageSelect.value);
556
+ }
557
+
558
+ // Auto-scan function
559
+ function autoScan() {
560
+ if (!stream || isScanning) return;
561
+
562
+ isScanning = true;
563
+ statusSpan.textContent = 'Status: Auto-scanning...';
564
+
565
+ // Get the dimensions of the scan box (same as manual capture)
566
+ const videoWidth = video.videoWidth;
567
+ const videoHeight = video.videoHeight;
568
+ const boxWidth = video.offsetWidth * 0.8;
569
+ const boxHeight = video.offsetHeight * 0.3;
570
+ const boxLeft = (video.offsetWidth - boxWidth) / 2;
571
+ const boxTop = (video.offsetHeight - boxHeight) / 2;
572
+
573
+ const scaleX = videoWidth / video.offsetWidth;
574
+ const scaleY = videoHeight / video.offsetHeight;
575
+
576
+ const captureWidth = boxWidth * scaleX;
577
+ const captureHeight = boxHeight * scaleY;
578
+ const captureLeft = boxLeft * scaleX;
579
+ const captureTop = boxTop * scaleY;
580
+
581
+ canvas.width = captureWidth;
582
+ canvas.height = captureHeight;
583
+
584
+ const ctx = canvas.getContext('2d');
585
+ ctx.drawImage(
586
+ video,
587
+ captureLeft, captureTop, captureWidth, captureHeight,
588
+ 0, 0, captureWidth, captureHeight
589
+ );
590
+
591
+ const imageData = canvas.toDataURL('image/jpeg', 0.8);
592
+ currentImageData = imageData;
593
+
594
+ Tesseract.recognize(
595
+ imageData,
596
+ languageSelect.value,
597
+ { logger: m => {} }
598
+ ).then(({ data: { text, confidence } }) => {
599
+ if (text.trim()) {
600
+ resultsDiv.innerHTML = `
601
+ <div class="bg-blue-900/20 border border-blue-700 rounded p-3 mb-3 text-blue-100">
602
+ <i class="fas fa-robot mr-2"></i>
603
+ Auto-detected text! (Confidence: ${confidence.toFixed(1)}%)
604
+ </div>
605
+ <div class="result-text bg-gray-800 p-3 rounded">${text}</div>
606
+ `;
607
+
608
+ enableActionButtons();
609
+ confidenceSpan.textContent = `Confidence: ${confidence.toFixed(1)}%`;
610
+ }
611
+
612
+ statusSpan.textContent = 'Status: Ready (auto-scan)';
613
+ isScanning = false;
614
+ }).catch(err => {
615
+ console.error('Auto-scan error:', err);
616
+ isScanning = false;
617
+ statusSpan.textContent = 'Status: Ready (auto-scan failed)';
618
+ });
619
+ }
620
+
621
+ // File upload handling
622
+ fileInput.addEventListener('change', handleFileSelect);
623
+
624
+ // Drag and drop handling
625
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
626
+ dropzone.addEventListener(eventName, preventDefaults, false);
627
+ });
628
+
629
+ function preventDefaults(e) {
630
+ e.preventDefault();
631
+ e.stopPropagation();
632
+ }
633
+
634
+ ['dragenter', 'dragover'].forEach(eventName => {
635
+ dropzone.addEventListener(eventName, highlight, false);
636
+ });
637
+
638
+ ['dragleave', 'drop'].forEach(eventName => {
639
+ dropzone.addEventListener(eventName, unhighlight, false);
640
+ });
641
+
642
+ function highlight() {
643
+ dropzone.classList.add('active');
644
+ }
645
+
646
+ function unhighlight() {
647
+ dropzone.classList.remove('active');
648
+ }
649
+
650
+ dropzone.addEventListener('drop', handleDrop, false);
651
+
652
+ function handleDrop(e) {
653
+ const dt = e.dataTransfer;
654
+ const files = dt.files;
655
+ if (files.length) {
656
+ handleFileSelect({ target: { files } });
657
+ }
658
+ }
659
+
660
+ function handleFileSelect(event) {
661
+ const file = event.target.files[0];
662
+ if (!file) return;
663
+
664
+ if (!file.type.match('image.*')) {
665
+ showError('Please select an image file (JPEG, PNG, etc.)');
666
+ return;
667
+ }
668
+
669
+ const reader = new FileReader();
670
+ reader.onload = function(e) {
671
+ imagePreview.src = e.target.result;
672
+ imagePreviewContainer.classList.remove('hidden');
673
+ dropzone.classList.add('hidden');
674
+
675
+ // Set up canvas for processing
676
+ const img = new Image();
677
+ img.onload = function() {
678
+ uploadCanvas.width = img.width;
679
+ uploadCanvas.height = img.height;
680
+ const ctx = uploadCanvas.getContext('2d');
681
+ ctx.drawImage(img, 0, 0);
682
+ currentImageData = uploadCanvas.toDataURL('image/jpeg', 0.8);
683
+ };
684
+ img.src = e.target.result;
685
+ };
686
+ reader.readAsDataURL(file);
687
+ }
688
+
689
+ removeImageBtn.addEventListener('click', function() {
690
+ imagePreviewContainer.classList.add('hidden');
691
+ dropzone.classList.remove('hidden');
692
+ fileInput.value = '';
693
+ currentImageData = null;
694
+ });
695
+
696
+ processImageBtn.addEventListener('click', function() {
697
+ if (!currentImageData) return;
698
+
699
+ // Get enhancement values
700
+ const brightness = parseInt(document.getElementById('uploadBrightness').value);
701
+ const contrast = parseInt(document.getElementById('uploadContrast').value);
702
+ const threshold = parseInt(document.getElementById('uploadThreshold').value);
703
+ const sharpness = parseInt(document.getElementById('uploadSharpness').value) / 100;
704
+
705
+ // Apply enhancements if needed
706
+ if (brightness !== 0 || contrast !== 0 || threshold > 0 || sharpness !== 1) {
707
+ const ctx = uploadCanvas.getContext('2d');
708
+ const tempCanvas = document.createElement('canvas');
709
+ tempCanvas.width = uploadCanvas.width;
710
+ tempCanvas.height = uploadCanvas.height;
711
+ const tempCtx = tempCanvas.getContext('2d');
712
+ tempCtx.drawImage(uploadCanvas, 0, 0);
713
+
714
+ applyImageEnhancements(tempCtx, tempCanvas, brightness, contrast, threshold, sharpness);
715
+ currentImageData = tempCanvas.toDataURL('image/jpeg', 0.8);
716
+ }
717
+
718
+ processImage(currentImageData, uploadLanguage.value);
719
+ });
720
+
721
+ applyEnhanceBtn.addEventListener('click', function() {
722
+ if (!currentImageData) return;
723
+
724
+ // Get enhancement values
725
+ const brightness = parseInt(document.getElementById('uploadBrightness').value);
726
+ const contrast = parseInt(document.getElementById('uploadContrast').value);
727
+ const threshold = parseInt(document.getElementById('uploadThreshold').value);
728
+ const sharpness = parseInt(document.getElementById('uploadSharpness').value) / 100;
729
+
730
+ // Apply to preview
731
+ const img = new Image();
732
+ img.onload = function() {
733
+ const tempCanvas = document.createElement('canvas');
734
+ tempCanvas.width = img.width;
735
+ tempCanvas.height = img.height;
736
+ const ctx = tempCanvas.getContext('2d');
737
+ ctx.drawImage(img, 0, 0);
738
+
739
+ applyImageEnhancements(ctx, tempCanvas, brightness, contrast, threshold, sharpness);
740
+ imagePreview.src = tempCanvas.toDataURL('image/jpeg', 0.8);
741
+ };
742
+ img.src = currentImageData;
743
+ });
744
+
745
+ function processImage(imageData, language) {
746
+ isScanning = true;
747
+ statusSpan.textContent = 'Status: Processing image...';
748
+ progressBar.style.width = '0%';
749
+
750
+ if (processImageBtn) {
751
+ processImageBtn.disabled = true;
752
+ processImageBtn.classList.add('opacity-50', 'cursor-not-allowed');
753
+ }
754
+
755
+ if (captureBtn) {
756
+ captureBtn.disabled = true;
757
+ captureBtn.classList.add('opacity-50', 'cursor-not-allowed');
758
+ }
759
+
760
+ Tesseract.recognize(
761
+ imageData,
762
+ language,
763
+ {
764
+ logger: m => {
765
+ if (m.status === 'recognizing text') {
766
+ statusSpan.textContent = `Status: ${m.status} (${Math.round(m.progress * 100)}%)`;
767
+ progressBar.style.width = `${m.progress * 100}%`;
768
+ } else {
769
+ statusSpan.textContent = `Status: ${m.status}`;
770
+ }
771
+ }
772
+ }
773
+ ).then(({ data: { text, confidence, hocr } }) => {
774
+ if (text.trim()) {
775
+ resultsDiv.innerHTML = `
776
+ <div class="bg-green-900/20 border border-green-700 rounded p-3 mb-3 text-green-100">
777
+ <i class="fas fa-check-circle mr-2"></i>
778
+ Successfully extracted text! (Confidence: ${confidence.toFixed(1)}%)
779
+ </div>
780
+ <div class="result-text bg-gray-800 p-3 rounded">${text}</div>
781
+ `;
782
+
783
+ enableActionButtons();
784
+ confidenceSpan.textContent = `Confidence: ${confidence.toFixed(1)}%`;
785
+ } else {
786
+ resultsDiv.innerHTML = `
787
+ <div class="bg-yellow-900/20 border border-yellow-700 rounded p-3 text-yellow-100">
788
+ <i class="fas fa-exclamation-circle mr-2"></i>
789
+ No text was detected. Try adjusting the position or lighting.
790
+ </div>
791
+ `;
792
+ }
793
+
794
+ statusSpan.textContent = 'Status: Ready';
795
+ isScanning = false;
796
+
797
+ if (processImageBtn) {
798
+ processImageBtn.disabled = false;
799
+ processImageBtn.classList.remove('opacity-50', 'cursor-not-allowed');
800
+ }
801
+
802
+ if (captureBtn) {
803
+ captureBtn.disabled = false;
804
+ captureBtn.classList.remove('opacity-50', 'cursor-not-allowed');
805
+ }
806
+ }).catch(err => {
807
+ console.error('OCR Error:', err);
808
+ showError(`Error processing image: ${err.message}`);
809
+ statusSpan.textContent = 'Status: Error processing image';
810
+ isScanning = false;
811
+
812
+ if (processImageBtn) {
813
+ processImageBtn.disabled = false;
814
+ processImageBtn.classList.remove('opacity-50', 'cursor-not-allowed');
815
+ }
816
+
817
+ if (captureBtn) {
818
+ captureBtn.disabled = false;
819
+ captureBtn.classList.remove('opacity-50', 'cursor-not-allowed');
820
+ }
821
+ });
822
+ }
823
+
824
+ function applyImageEnhancements(ctx, canvas, brightness, contrast, threshold, sharpness) {
825
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
826
+ const data = imageData.data;
827
+
828
+ // Apply brightness and contrast
829
+ if (brightness !== 0 || contrast !== 0) {
830
+ const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
831
+
832
+ for (let i = 0; i < data.length; i += 4) {
833
+ // Apply contrast
834
+ data[i] = factor * (data[i] - 128) + 128 + brightness;
835
+ data[i+1] = factor * (data[i+1] - 128) + 128 + brightness;
836
+ data[i+2] = factor * (data[i+2] - 128) + 128 + brightness;
837
+
838
+ // Clamp values between 0-255
839
+ data[i] = Math.max(0, Math.min(255, data[i]));
840
+ data[i+1] = Math.max(0, Math.min(255, data[i+1]));
841
+ data[i+2] = Math.max(0, Math.min(255, data[i+2]));
842
+ }
843
+ }
844
+
845
+ // Apply threshold (convert to black and white)
846
+ if (threshold > 0) {
847
+ for (let i = 0; i < data.length; i += 4) {
848
+ const avg = (data[i] + data[i+1] + data[i+2]) / 3;
849
+ const value = avg > threshold ? 255 : 0;
850
+ data[i] = data[i+1] = data[i+2] = value;
851
+ }
852
+ }
853
+
854
+ ctx.putImageData(imageData, 0, 0);
855
+
856
+ // Apply sharpness (using a simple convolution filter)
857
+ if (sharpness !== 1) {
858
+ const tempCanvas = document.createElement('canvas');
859
+ tempCanvas.width = canvas.width;
860
+ tempCanvas.height = canvas.height;
861
+ const tempCtx = tempCanvas.getContext('2d');
862
+ tempCtx.drawImage(canvas, 0, 0);
863
+
864
+ // Apply sharpening filter
865
+ const weights = [0, -1 * sharpness, 0, -1 * sharpness, 1 + 4 * sharpness, -1 * sharpness, 0, -1 * sharpness, 0];
866
+ const divisor = 1;
867
+ const bias = 0;
868
+
869
+ ctx.filter = `contrast(${100 + contrast}%) brightness(${100 + brightness}%)`;
870
+ ctx.drawImage(tempCanvas, 0, 0);
871
+ }
872
+ }
873
+
874
+ function enableActionButtons() {
875
+ copyBtn.disabled = false;
876
+ clearBtn.disabled = false;
877
+ downloadBtn.disabled = false;
878
+ editBtn.disabled = false;
879
+ }
880
+
881
+ function showError(message) {
882
+ resultsDiv.innerHTML = `
883
+ <div class="bg-red-900/50 border border-red-700 rounded p-3 text-red-100">
884
+ <i class="fas fa-exclamation-triangle mr-2"></i>
885
+ ${message}
886
+ </div>
887
+ `;
888
+ }
889
+
890
+ // Copy text
891
+ copyBtn.addEventListener('click', function() {
892
+ const textToCopy = resultsDiv.querySelector('.result-text')?.textContent;
893
+ if (textToCopy) {
894
+ navigator.clipboard.writeText(textToCopy).then(() => {
895
+ const originalText = copyBtn.innerHTML;
896
+ copyBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
897
+ setTimeout(() => {
898
+ copyBtn.innerHTML = originalText;
899
+ }, 2000);
900
+ });
901
+ }
902
+ });
903
+
904
+ // Clear results
905
+ clearBtn.addEventListener('click', function() {
906
+ resultsDiv.innerHTML = `
907
+ <div class="text-center text-gray-500 py-16">
908
+ <i class="fas fa-align-left text-4xl mb-2"></i>
909
+ <p>Extracted text will appear here</p>
910
+ </div>
911
+ `;
912
+ copyBtn.disabled = true;
913
+ clearBtn.disabled = true;
914
+ downloadBtn.disabled = true;
915
+ editBtn.disabled = true;
916
+ confidenceSpan.textContent = 'Confidence: --';
917
+ });
918
+
919
+ // Download text
920
+ downloadBtn.addEventListener('click', function() {
921
+ const textToDownload = resultsDiv.querySelector('.result-text')?.textContent;
922
+ if (textToDownload) {
923
+ const blob = new Blob([textToDownload], { type: 'text/plain' });
924
+ const url = URL.createObjectURL(blob);
925
+ const a = document.createElement('a');
926
+ a.href = url;
927
+ a.download = `ocr-extracted-text-${new Date().toISOString().slice(0,10)}.txt`;
928
+ document.body.appendChild(a);
929
+ a.click();
930
+ document.body.removeChild(a);
931
+ URL.revokeObjectURL(url);
932
+ }
933
+ });
934
+
935
+ // Edit text
936
+ editBtn.addEventListener('click', function() {
937
+ const resultText = resultsDiv.querySelector('.result-text');
938
+ if (!resultText) return;
939
+
940
+ const currentText = resultText.textContent;
941
+ resultText.innerHTML = `
942
+ <textarea id="textEditor" class="w-full h-64 bg-gray-700 text-white p-3 rounded">${currentText}</textarea>
943
+ <div class="flex justify-end mt-2 gap-2">
944
+ <button id="cancelEditBtn" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-1 rounded text-sm">
945
+ Cancel
946
+ </button>
947
+ <button id="saveEditBtn" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-1 rounded text-sm">
948
+ Save Changes
949
+ </button>
950
+ </div>
951
+ `;
952
+
953
+ document.getElementById('cancelEditBtn').addEventListener('click', function() {
954
+ resultText.textContent = currentText;
955
+ });
956
+
957
+ document.getElementById('saveEditBtn').addEventListener('click', function() {
958
+ const editedText = document.getElementById('textEditor').value;
959
+ resultText.textContent = editedText;
960
+ });
961
+ });
962
+
963
+ // Language change
964
+ languageSelect.addEventListener('change', function() {
965
+ statusSpan.textContent = `Status: Language set to ${this.options[this.selectedIndex].text}`;
966
+ });
967
+
968
+ uploadLanguage.addEventListener('change', function() {
969
+ statusSpan.textContent = `Status: Language set to ${this.options[this.selectedIndex].text}`;
970
+ });
971
+ });
972
+ </script>
973
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=podsni/realtime-ocr" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
974
+ </html>