priyaprabakaran01/IdScanner
0
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>ID Card Scanner</title>7 8<!-- Tesseract.js: runs OCR entirely in the browser, no server needed -->9<script src="https://cdnjs.cloudflare.com/ajax/libs/tesseract.js/5.0.4/tesseract.min.js"></script>10<!-- SheetJS: builds the .xlsx file client-side -->11<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>12 13<style>14 body { font-family: system-ui, sans-serif; max-width: 720px; margin: 24px auto; padding: 0 16px; color: #1a1a1a; }15 h1 { font-size: 1.4rem; }16 .card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; margin-bottom: 16px; }17 input[type="file"] { margin: 8px 0; }18 img#preview { max-width: 100%; border-radius: 8px; margin-top: 8px; display: none; }19 label { display: block; font-size: 0.85rem; color: #555; margin-top: 10px; }20 input[type="text"] { width: 100%; padding: 8px; font-size: 1rem; box-sizing: border-box; border: 1px solid #ccc; border-radius: 6px; }21 button { padding: 10px 16px; margin-top: 12px; margin-right: 8px; border: none; border-radius: 6px; font-size: 0.95rem; cursor: pointer; }22 .primary { background: #2563eb; color: white; }23 .secondary { background: #eee; color: #222; }24 #status { font-size: 0.9rem; color: #555; margin-top: 8px; min-height: 1.2em; }25 table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 0.85rem; }26 th, td { border: 1px solid #ddd; padding: 6px 8px; text-align: left; }27 th { background: #f5f5f5; }28</style>29</head>30<body>31 32<h1>Student ID Card Scanner</h1>33<p style="color:#555; font-size:0.9rem;">34 OCR runs in your browser — the image itself never leaves your device.35 Only the extracted text fields (name, department, reg no, year) are sent to the36 shared backend when you tap "Add to Sheet", so everyone scanning cards37 contributes to the same spreadsheet.38</p>39 40<div class="card">41 <input type="file" id="fileInput" accept="image/*" capture="environment" />42 <br/>43 <img id="preview" />44 <br/>45 <button class="primary" id="scanBtn">Scan Card</button>46 <div id="status"></div>47 48 <label>Name</label>49 <input type="text" id="nameBox" />50 <label>Department</label>51 <input type="text" id="deptBox" />52 <label>Registration No.</label>53 <input type="text" id="regBox" />54 <label>Year (derived from Reg No.)</label>55 <input type="text" id="yearBox" />56 57 <button class="secondary" id="addBtn">Add to Sheet</button>58</div>59 60<div class="card">61 <strong>Records so far: <span id="count">0</span></strong>62 <button class="primary" id="downloadBtn">Download Excel</button>63 <button class="secondary" id="clearBtn">Clear All</button>64 <table id="recordsTable">65 <thead><tr><th>Name</th><th>Department</th><th>Reg No</th><th>Year</th></tr></thead>66 <tbody></tbody>67 </table>68</div>69 70<script>71// ---- adjust these two if your reg-no format differs ----72// e.g. "227171076" -> digits[1:3] = "27" -> year 202773const YEAR_DIGIT_START = 1;74const YEAR_DIGIT_END = 3;75// ----------------------------------------------------------76 77// Set this to your backend Space's URL, e.g.78// "https://YOUR-USERNAME-idcard-backend.hf.space"79const BACKEND_URL = "https://YOUR-USERNAME-idcard-backend.hf.space";80 81const DEPT_KEYWORDS = ["B.Sc", "B.E", "B.Tech", "M.Sc", "M.Tech", "M.E", "MBA", "BBA", "B.A", "M.A", "Ph.D", "Computer Science", "Engineering"];82const NOISE_WORDS = ["SASTRA", "DEEMED", "UNIVERSITY", "UGC", "ACT", "THINK", "MERIT", "TRANSPARENCY", "OF", "THE", "U/S"];83 84const fileInput = document.getElementById('fileInput');85const preview = document.getElementById('preview');86const scanBtn = document.getElementById('scanBtn');87const statusEl = document.getElementById('status');88const nameBox = document.getElementById('nameBox');89const deptBox = document.getElementById('deptBox');90const regBox = document.getElementById('regBox');91const yearBox = document.getElementById('yearBox');92const addBtn = document.getElementById('addBtn');93const downloadBtn = document.getElementById('downloadBtn');94const clearBtn = document.getElementById('clearBtn');95const countEl = document.getElementById('count');96const tbody = document.querySelector('#recordsTable tbody');97 98let records = [];99loadRecords();100 101async function loadRecords() {102 try {103 const res = await fetch(`${BACKEND_URL}/records`);104 records = await res.json();105 } catch (err) {106 console.error(err);107 statusEl.textContent = 'Could not reach backend — is BACKEND_URL set correctly?';108 }109 renderTable();110}111 112fileInput.addEventListener('change', () => {113 const file = fileInput.files[0];114 if (!file) return;115 const url = URL.createObjectURL(file);116 preview.src = url;117 preview.style.display = 'block';118});119 120scanBtn.addEventListener('click', async () => {121 if (!fileInput.files[0]) {122 statusEl.textContent = 'Please choose or capture an image first.';123 return;124 }125 statusEl.textContent = 'Reading card… (first run downloads the OCR model, may take a moment)';126 scanBtn.disabled = true;127 try {128 const { data: { text } } = await Tesseract.recognize(fileInput.files[0], 'eng');129 const lines = text.split('\n').map(l => l.trim()).filter(Boolean);130 const fields = extractFields(lines);131 nameBox.value = fields.name;132 deptBox.value = fields.dept;133 regBox.value = fields.regNo;134 yearBox.value = fields.year;135 statusEl.textContent = fields.regNo136 ? 'Fields extracted — please review before adding.'137 : 'Could not confidently read the card — please fill in fields manually.';138 } catch (err) {139 console.error(err);140 statusEl.textContent = 'OCR failed: ' + err.message;141 } finally {142 scanBtn.disabled = false;143 }144});145 146function extractFields(lines) {147 // Registration number: longest pure-digit run, 6-12 digits148 let regNo = '';149 for (const l of lines) {150 const digits = l.replace(/\D/g, '');151 if (digits.length >= 6 && digits.length <= 12 && digits.length > regNo.length) {152 regNo = digits;153 }154 }155 156 // Department: line containing a known keyword157 let dept = '';158 let deptIndex = -1;159 for (let i = 0; i < lines.length; i++) {160 if (DEPT_KEYWORDS.some(k => lines[i].toLowerCase().includes(k.toLowerCase()))) {161 dept = lines[i];162 deptIndex = i;163 break;164 }165 }166 167 // Name: an all-caps line, 2+ words, no digits, not noise168 const nameCandidates = [];169 lines.forEach((l, i) => {170 const clean = l.trim();171 const isUpper = clean === clean.toUpperCase() && /[A-Z]/.test(clean);172 const hasDigits = /\d/.test(clean);173 const wordCount = clean.split(/\s+/).length;174 const isNoise = NOISE_WORDS.some(w => clean.toUpperCase().includes(w));175 if (isUpper && !hasDigits && wordCount >= 2 && !isNoise) {176 nameCandidates.push({ index: i, text: clean });177 }178 });179 180 let name = '';181 if (nameCandidates.length) {182 if (deptIndex !== -1) {183 const above = nameCandidates.filter(c => c.index < deptIndex);184 name = above.length ? above[above.length - 1].text : nameCandidates[0].text;185 } else {186 name = nameCandidates[0].text;187 }188 }189 190 // Year from reg no191 let year = '';192 if (regNo.length >= YEAR_DIGIT_END) {193 const yy = regNo.slice(YEAR_DIGIT_START, YEAR_DIGIT_END);194 if (/^\d+$/.test(yy)) year = '20' + yy;195 }196 197 return { name, dept, regNo, year };198}199 200addBtn.addEventListener('click', async () => {201 const regNo = regBox.value.trim();202 if (!regNo) {203 statusEl.textContent = 'Registration number is required to add a record.';204 return;205 }206 addBtn.disabled = true;207 try {208 const res = await fetch(`${BACKEND_URL}/records`, {209 method: 'POST',210 headers: { 'Content-Type': 'application/json' },211 body: JSON.stringify({212 name: nameBox.value.trim(),213 dept: deptBox.value.trim(),214 regNo,215 year: yearBox.value.trim(),216 }),217 });218 if (res.status === 409) {219 statusEl.textContent = `Reg No ${regNo} is already in the sheet.`;220 } else if (!res.ok) {221 const err = await res.json().catch(() => ({}));222 statusEl.textContent = 'Could not add record: ' + (err.detail || res.statusText);223 } else {224 statusEl.textContent = 'Added to shared sheet.';225 await loadRecords();226 }227 } catch (err) {228 console.error(err);229 statusEl.textContent = 'Could not reach backend — is BACKEND_URL set correctly?';230 } finally {231 addBtn.disabled = false;232 }233});234 235function renderTable() {236 tbody.innerHTML = '';237 records.forEach(r => {238 const tr = document.createElement('tr');239 tr.innerHTML = `<td>${r.name}</td><td>${r.dept}</td><td>${r.regNo}</td><td>${r.year}</td>`;240 tbody.appendChild(tr);241 });242 countEl.textContent = records.length;243}244 245downloadBtn.addEventListener('click', () => {246 window.open(`${BACKEND_URL}/download`, '_blank');247});248 249clearBtn.addEventListener('click', async () => {250 if (!confirm('Clear all saved records? This removes them from the shared sheet for everyone.')) return;251 statusEl.textContent = 'Clearing…';252 try {253 for (const r of records) {254 await fetch(`${BACKEND_URL}/records/${encodeURIComponent(r.regNo)}`, { method: 'DELETE' });255 }256 await loadRecords();257 statusEl.textContent = 'Cleared.';258 } catch (err) {259 console.error(err);260 statusEl.textContent = 'Could not clear records: ' + err.message;261 }262});263</script>264</body>265</html>266 