dumbtv
dumbtv takes the ads, screen tracking and update nags out of your Fire TV over ADB. No root, none of your apps touched, every step can be undone. Free and open source.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證在瀏覽器中去除照片的中繼資料並完全模糊人臉。選擇人臉偵測程式碼與模糊方法;圖像永不離開您的電腦。
AI 翻譯
Strip photo metadata and blur faces entirely in the browser. Pick a face-detection model and a blur method; the image never leaves your machine.
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Remove metadata + face anonymization.
Upload a photo, pick a face-detection model and a blur method, and get back an
anonymized image with all metadata stripped.
Everything runs in the browser. There is no backend, no upload, and no API
call — the image never leaves the machine it was opened on.
Also reachable at the deploy URLs it is served from:
• Vercel — https://privacy-processor-urbanistai.vercel.app
• GitHub Pages — https://kartikdagreat.github.io/privacy-processor-urbanistai/檢視完整 README ↗Remove metadata + face anonymization.
Upload a photo, pick a face-detection model and a blur method, and get back an anonymized image with all metadata stripped.
Everything runs in the browser. There is no backend, no upload, and no API call — the image never leaves the machine it was opened on.
Live: https://anonymize.kartikgounder.com
Also reachable at the deploy URLs it is served from:
npm install
npm run fetch-models # one-time: downloads ~33 MB of models into public/
npm run dev # http://localhost:5173
npm run fetch-models is required before the first run. The model weights and
wasm runtimes are gitignored rather than committed, so a fresh clone needs this
step once. It is safe to re-run; existing files are skipped.
npm run build # production build into dist/
npm run preview # serve the build
npm run typecheck # tsc, no emit
dist/ is a fully static directory. Any static host will do — there is nothing
to deploy server-side.
The original design for this app called for a FastAPI service doing detection with OpenCV and MediaPipe. This build does the same work client-side:
| Stage | Where it runs |
|---|---|
| Decode + EXIF orientation | createImageBitmap |
| Face detection | MediaPipe Tasks (wasm) and OpenCV.js (wasm) |
| Blurring | Hand-written kernels over ImageData |
| Metadata stripping | Canvas re-encode to PNG |
The interface is built around verification rather than a single "done": the result is one large frame you wipe between original and anonymized, with the blurred regions marked, and a contact sheet that runs the same photo through every detector so you can see which one caught the most.
Metadata stripping is a property of the pipeline, not a separate step. A
<canvas> holds pixels and nothing else, so anything re-encoded from one carries
no EXIF, GPS, maker notes, or colour profile. Both the Original preview and the
downloaded result are rendered from re-encoded pixels, so even the "before" image
on screen is already clean.
| UI label | id | Runtime |
|---|---|---|
| Run All (cumulative) | run_all | orchestration only |
| MediaPipe | mediapipe | BlazeFace short-range, MediaPipe Tasks |
| MediaPipe Full Range | mediapipe_full | BlazeFace full-range, MediaPipe Tasks |
| OpenCV DNN SSD | opencv_dnn | res10 SSD Caffe model, OpenCV.js |
| Haar Cascade | haar | haarcascade_frontalface_default, OpenCV.js |
| YuNet | yunet | face_detection_yunet_2023mar.onnx, OpenCV.js |
Run All chains the five models cumulatively: each one detects on the output of the previous stage, so a face already blurred by MediaPipe is no longer a face by the time YuNet looks. Coverage adds up instead of competing, which is the point — it maximizes the chance that every face is caught by at least one model.
box, gaussian, median, pixelate. Kernel size follows the original
pipeline: k = max(23, (side / 5) | 1), so a given face gets roughly the same
blur strength it would have had server-side.
Box and Gaussian use moving-sum passes, so their cost does not grow with kernel size. Median uses a sliding 256-bin histogram (Huang's method), which is O(kernel) per pixel rather than O(kernel²).
"Take the tour" in the empty state runs a four-step walkthrough that does the real work rather than pointing at the UI: it loads the hard sample photo, runs Run All to chain every detector, then runs all five separately and narrates the result using the numbers that actually came back.
The closing narration is generated from those numbers, never hardcoded. It adapts its headline depending on whether the chain beat the best single detector, and explains each empty-handed model for the right reason — only the Caffe SSD rescales to 300x300; the MediaPipe models come up short because they expect faces closer to the camera.
Three freely-licensed samples ship in public/samples/ so the tool can be tried
without hunting for an image. They were chosen by running the app's own
detectors over a pool of Wikimedia Commons candidates and keeping one photo
where every model agrees (5 faces), one where they disagree sharply (51 faces —
Haar finds 43, YuNet 51, the DNN 0 because it downsamples to 300x300), and one
of candid profiles. Attribution is in
public/samples/CREDITS.md and shown in the app.
Swapping one means dropping a file in that directory and updating the matching
entry in src/lib/samples.ts.
Model files are fetched lazily and cached for the session — nothing downloads until a detector that needs it is actually run. Picking an OpenCV-backed model pulls in the 11 MB OpenCV.js runtime on first use; MediaPipe models are much smaller. Subsequent runs reuse the loaded detectors.
src/
lib/
assets.ts Paths to everything under public/
blur.ts Box / Gaussian / median / pixelate kernels
image.ts Decode, re-encode, metadata stripping
pipeline.ts Detect -> blur -> encode, shared by both UI surfaces
apiError.ts Turns failures into messages that name the model
types.ts Model and blur-method registries
detect/
index.ts Orchestration, including the run_all chain
mediapipe.ts MediaPipe Tasks detectors
opencv.ts Haar, DNN SSD, YuNet via OpenCV.js
components/ Header, ImageUpload, CompareView, selectors, ComparisonGrid
pages/Index.tsx The single page
scripts/
fetch-models.mjs Downloads models and runtimes into public/
The build is fully static, so both targets are just file hosting. Both deploy
automatically on push to main.
| Target | Trigger | Base path |
|---|---|---|
Vercel (canonical, anonymize.kartikgounder.com) | GitHub integration | / |
| GitHub Pages | .github/workflows/deploy.yml | /privacy-processor-urbanistai/ |
The two differ only in where they are rooted. vite.config.ts reads VITE_BASE
(defaulting to /), and the Pages workflow sets it to the repo path. Because
src/lib/assets.ts resolves model paths against import.meta.env.BASE_URL,
the same source builds correctly for both.
Neither target commits the model weights — both run npm run fetch-models
during the build.
anonymize.kartikgounder.com points at the Vercel deployment. DNS is on
Cloudflare:
| Type | Name | Target | Proxy |
|---|---|---|---|
| CNAME | anonymize | cname.vercel-dns.com | DNS only (grey cloud) |
The record is deliberately not proxied, matching how the apex
kartikgounder.com is configured. Turning Cloudflare's orange proxy on is the
usual cause of a stuck certificate or a redirect loop, since Vercel terminates
TLS itself.
Vercel's CLI warns that the nameservers are Cloudflare's rather than
ns1/ns2.vercel-dns.com. That warning is safe to ignore — a CNAME on
third-party DNS is a supported setup and nothing needs to move.
To point a subdomain at GitHub Pages instead, CNAME it to
kartikdagreat.github.io and drop VITE_BASE from the workflow — a custom
domain serves from the root, so the base path has to go back to /.
Link to the final presentation recording: https://columbia.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=ea280966-1dc6-4e27-9e76-b44400fdf038