Remove hardcoded (burned-in) subtitles, watermarks, and logos from video locally.
Preview the detection first, then wipe. CLI, Docker, or a local web UI.
Auto-detect burn-in text · review each track · inpaint the background · keep the original audio.
Files stay on your machine. No cloud account.
中文 · Site · Quick start · FAQ · Docker
VideoWipe is a self-hosted hardcoded subtitle remover and video delogo tool. It finds burned-in / burn-in text, watermarks, logos, and on-screen timestamps, lets you review each track, then inpaints only the pixels you chose to erase.
People usually land here looking for:
- remove hardcoded subtitles from video / remove burned-in subtitles
- remove watermark from video / remove logo from video (delogo)
- local, offline, self-hosted video text removal (no upload)
- a scriptable alternative to online removers and one-click desktop apps
The cleaned MP4 keeps the original audio track.
If you want a Windows one-click desktop app, video-subtitle-remover (VSR) is the established option. Use VideoWipe when you need to see the detection before it erases, run it headless (CLI, Docker, worker), or keep source files off the cloud.
Not soft subtitles: VideoWipe does not strip
.srt/.asstracks. It removes text that is burned into the picture.
Requirements: Python 3.10+, and either ONNX Runtime or PyTorch. Model weights download automatically on first run to ~/.videowipe/weights/.
VideoWipe is not on PyPI yet — install from source:
git clone https://github.com/KKenny0/videowipe.git
cd videowipe
pip install -e ".[onnx]"
# Auto-detect and remove hardcoded text overlays
videowipe clean input.mp4 -o result/Prefer a browser UI (still local):
pip install -e ".[web,onnx]"
videowipe serve
# Open http://127.0.0.1:8000 — upload, preview tracks, download cleaned MP4No Python? Use Docker.
Optional extras: .[torch] (PyTorch), .[ocr] (OCR text recognition), .[propainter] (adapter deps only; model not bundled).
| Hardcoded subtitle removal | Burn-in / burned-in text, including multi-language clips |
| Watermark, logo, timestamp cleanup | Persistent corner marks and on-screen clocks |
| Auto detection | No hand-drawn mask required (you can still supply one) |
| Preview before erase | Review each track: remove or keep, with time ranges |
| Local-first | CLI, web UI, Docker, Python SDK — no cloud account |
| Original audio preserved | Cleaned video keeps the source soundtrack |
| Multilingual detection | Chinese, English, Korean, and more out of the box |
| Pluggable quality | Default STTN; optional ProPainter or any external inpainting command |
| Batch / embeddable | Reuse one engine across many videos in your own worker |
| Before | After |
|---|---|
Built-in detector finds text regions across multilingual content:
| Video | Candidates | Selected | Types |
|---|---|---|---|
| Chinese drama | 4 | 2 | top subtitle, bottom subtitle |
| English clip | 2 | 2 | bottom subtitle |
| Music video (Korean + Burmese) | 7 | 5 | top watermark, bottom multilingual subtitles |
Tested with --detect-mode balanced (50 sampled frames). Green boxes show regions selected for cleanup.
| Upload | Preview targets | Download |
|---|---|---|
![]() |
![]() |
![]() |
VideoWipe is built for people who already know they want the burn-in gone, and do not want a black box to decide where.
- Editors and archivists cleaning private or archive footage before a recut (respect copyright and platform terms)
- Self-hosters who will not upload source video to an online remover
- Developers who need detect → review → inpaint as a local CLI, Docker job, or Python worker
It is a weaker fit if you want a Windows .exe with no install steps. That is VSR's job.
- Remove burned-in bottom subtitles from drama, lecture, or language-study clips
- Clean a corner logo / watermark (delogo) before reuse
- Strip on-screen timestamps or platform chrome
- On multilingual videos, preview first, then remove only the tracks you care about
- Run batch jobs on a worker without a desktop display
Three stages:
- Detection — Sample frames, find text regions, group them into stable tracks over time (multilingual, no manual mask required).
- Planning — Build a reviewable WipePlan: each track has type (subtitle / watermark / logo / timestamp), remove|keep action, time segments, and a precise mask.
- Inpainting — Only remove-track masks are applied per frame; default STTN fills from neighboring frames. Optional external models (e.g. ProPainter) plug in for higher quality.
You can stop after detection (--preview), edit the plan JSON, then run cleanup — so the tool does not erase blindly.
| Online removers | VSR | Hand masks (AE / Resolve) | VideoWipe | |
|---|---|---|---|---|
| Privacy | Upload required | Local | Local | Local |
| Auto-detect burn-in text | Sometimes | Yes | Manual | Yes |
| Review before erase | Rare | Limited | Manual | Yes (WipePlan / web UI) |
| Headless / Docker / worker | No | Desktop GUI | Desktop | CLI, Docker, Python |
Windows one-click .exe |
Sometimes | Yes | N/A | No (source or Docker) |
| Embed in your pipeline | Hard | Hard | Hard | Python SDK + CLI |
- Not a soft subtitle editor (
.srt/.ass) - Not a translation or re-caption product
- Not a promise of perfect pixels on every shot — fast motion and complex textures are harder; preview first
- Not a cloud SaaS — you run it yourself
# Recommended: auto-detect and remove overlays
videowipe clean input.mp4 -o result/
# Only subtitles, only bottom of frame
videowipe clean input.mp4 --target subtitle --region bottom -o result/
# Natural-language intent
videowipe clean input.mp4 --intent "remove bottom Chinese subtitles" -o result/
# Preview detection only (no inpainting)
videowipe clean input.mp4 --preview -o plan/
# Execute a reviewed plan
videowipe clean input.mp4 --plan plan/wipe_plan.json -o result/
# Manual mask when you want full control
videowipe clean input.mp4 -m mask.png -o result/| Flag | Description | Default |
|---|---|---|
--target |
Target type (repeatable): subtitle, timestamp, watermark, logo |
auto-detect all |
--region |
Screen region (repeatable): top, bottom, top-left, top-right, bottom-left, bottom-right, center |
all regions |
--intent |
Natural-language cleanup intent | — |
--preview |
Write detection artifacts only (no inpainting) | off |
--plan |
Execute an existing wipe_plan.json (mutually exclusive with -m, --mask) |
— |
--confirm |
Show detected targets and confirm before processing | off |
--detect-mode |
fast (24 frames); balanced (50) / sensitive (80) densely recheck detector-backed remove segments |
balanced |
--ocr |
OCR: auto, off, rapidocr |
auto |
--agent |
Local LLM CLI for intent-based selection (e.g. claude, codex) |
— |
--external-command |
External inpainting command (bypasses built-in STTN) | — |
-g, --gap |
Frames per inpainting segment. 25 balances performance and quality; larger values add temporal context but grow compute and memory superlinearly |
25 |
-d, --dual |
Side-by-side original in the output | off |
-m, --mask |
Mask image path | auto |
Legacy: detext command
detext auto-detects subtitles only. Prefer clean for new usage.
videowipe detext -v input.mp4 -o result/
videowipe detext -v input.mp4 -m mask.png -o result/| Flag | Description | Default |
|---|---|---|
-v, --video |
Input video path | required |
-m, --mask |
Mask image path | auto |
-o, --output |
Output directory | result/ |
-w, --weight |
Model weight path (PyTorch .pth/.pt, or ONNX prefix) |
auto |
-g, --gap |
Frames per inpainting segment; larger values cost more compute and memory | 25 |
-d, --dual |
Side-by-side original in the output | off |
--external-command |
External inpainting command | — |
CPU:
docker pull ghcr.io/kkenny0/videowipe:latest
docker run --rm -v "$(pwd)":/data ghcr.io/kkenny0/videowipe clean /data/input.mp4 -o /data/result/GPU:
docker pull ghcr.io/kkenny0/videowipe:gpu
docker run --rm --gpus all -v "$(pwd)":/data ghcr.io/kkenny0/videowipe:gpu clean /data/input.mp4 -o /data/result/Or use the wrapper (auto-picks CPU/GPU):
./scripts/docker-videowipe.sh clean input.mp4 -o result/| Image | Size | GPU | Notes |
|---|---|---|---|
ghcr.io/kkenny0/videowipe:latest |
~480 MB | No | CPU only, smallest |
ghcr.io/kkenny0/videowipe:gpu |
~1.4 GB | Yes | Prebuilt GPU image |
videowipe:gpu |
~1.4 GB | Yes | Local build tag |
Floating latest / gpu track the current main build. Pin versioned tags from GHCR after a successful release.
Build from source
docker build --target runtime-cpu -t videowipe:latest .
docker build --target runtime-gpu --build-arg VARIANT=gpu -t videowipe:gpu .GPU image needs NVIDIA runtime for CUDA; otherwise ONNX Runtime falls back to CPU.
docker run --rm -v "$(pwd)":/data videowipe:latest clean /data/input.mp4 -o /data/result/
docker run --rm --gpus all -v "$(pwd)":/data videowipe:gpu clean /data/input.mp4 -o /data/result/Default backend is STTN (works on CPU via ONNX). For tougher shots, plug in an external model.
ProPainter is validated as a higher-quality option:
git clone https://github.com/sczhou/ProPainter.git ../models/ProPainter
videowipe clean input.mp4 --model propainter --propainter-dir ../models/ProPainter
# equivalent:
videowipe clean input.mp4 --external-command "python scripts/propainter_wipe.py"Note: ProPainter needs a GPU with ~16GB VRAM for 480p, and uses NTU S-Lab License 1.0 (non-commercial).
Quality comparison: ProPainter vs STTN
Multilingual music video (Korean + Burmese subtitles, 852×480, 10s). Same mask for both.
| Original | ProPainter (GPU fp16) | STTN (CPU ONNX) |
|---|---|---|
![]() |
![]() |
![]() |
The same engine powers CLI, web, and Docker. Use it when you want batch jobs or a custom worker.
from videowipe import remove_text
# Mask optional — regions auto-detected if omitted
remove_text(video="input.mp4", output="result/")Full clean pipeline with target selection:
from videowipe import WipeEngine
engine = WipeEngine(task="clean", detect_mode="balanced", ocr="auto")
engine.process(
video="input.mp4",
targets=["subtitle", "watermark"],
regions=["bottom"],
intent="remove Chinese subtitles and logo watermark",
output="result/",
)
engine.cleanup()Batch with a long-lived engine (model stays loaded):
from videowipe import CancellationToken, WipeEngine, WipeRequest
with WipeEngine(task="detext") as engine:
result = engine.run(
WipeRequest(video="clip1.mp4", mask="mask.png", output_dir="result/clip1"),
cancellation=CancellationToken(),
)
print(result.output_path, result.backend, result.timings)See examples/batch_worker.py and examples/custom_inpainter.py.
Review and edit the WipePlan
Generate a plan without loading the inpainting model, edit track action / segments in JSON (do not edit the sidecar .npz masks), then execute:
from videowipe import CancellationToken, WipeEngine, WipeRequest
engine = WipeEngine(task="clean")
plan = engine.plan(
WipeRequest(video="input.mp4", output_dir="plan/"),
cancellation=CancellationToken(),
)
engine.cleanup()
with WipeEngine(task="clean") as engine:
result = engine.run(WipeRequest(
video="input.mp4",
output_dir="result/",
plan="plan/wipe_plan.json",
))videowipe clean input.mp4 --preview -o plan/
videowipe clean input.mp4 --plan plan/wipe_plan.json -o result/The plan is ordinary JSON — no LLM or cloud required. A plan is bound to its source video.
Does VideoWipe remove soft subtitles (.srt / .ass)?
No. Soft tracks are separate files or streams. VideoWipe removes hardcoded / burn-in text painted into the video frames.
Do my videos leave my computer?
Default path is fully local (CLI, web UI on 127.0.0.1, Docker with a bind mount). Nothing is uploaded to a VideoWipe cloud.
Is the original audio kept?
Yes. Downloaded / output MP4 keeps the source audio track.
Do I need a GPU?
No. CPU + ONNX Runtime works. GPU images and PyTorch/ProPainter paths are faster or higher quality when available.
Is it on PyPI?
Not yet. Install from this repo or pull the Docker image.
Hardcoded subtitle vs watermark — can I choose?
Yes. Use --target subtitle / --target watermark / --region bottom, natural-language --intent, or the web UI track toggles.
STTN or ProPainter?
STTN is the default (lighter, CPU-friendly). ProPainter often looks better on hard regions but needs more VRAM and a non-commercial license for that model.
Will cleanup always look perfect?
No tool can guarantee that. Fast motion, thin textures, and semi-transparent marks are harder. Use preview / confirm before a long run.
Can I plug this into my own product or worker?
Yes. Treat VideoWipe as an embeddable engine: one WipeEngine, stable request/result types, optional custom inpainters. Product boundary is detect → plan → inpaint — not a full NLE or cloud studio.
How is this different from video-subtitle-remover (VSR)?
VSR is a desktop GUI with a Windows package. VideoWipe is preview-first and built to run from CLI, Docker, a local web UI, or your own worker. Same job (hardcoded subtitle / watermark removal), different way to operate it.
If VideoWipe saves you time on subtitle, watermark, or overlay cleanup:
https://kkenny0.github.io/support/
Support helps maintain model packaging, Docker images, detection tuning, and docs.
| Project | Relationship |
|---|---|
| Video-Auto-Wipe | Ancestor this repo derives from |
| video-subtitle-remover (VSR) | Popular desktop GUI for the same job |
| STTN | Default inpainting model |
| OnnxOCR | Built-in text detection |
| ProPainter | Optional higher-quality inpainter (not bundled) |
| InpaintDelogo | AviSynth+ delogo plugin, different stack |
Built on STTN and the original Video-Auto-Wipe. Built-in text detection from OnnxOCR.
GNU General Public License v3.0 — see LICENSE.
This repository derives from GPL-3.0-licensed Video-Auto-Wipe. If you distribute VideoWipe or a combined work, review the GPL-3.0 obligations for your distribution model.








