CoolFace
Apppublic

priyaprabakaran01/IdScanner

sourceHugging Faceupdated 15d agoView on Hugging Face
0likes
index.html266 linesDownload Raw Back to root
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