Communitygithub.com

starx1n9/video-watermark-remover-skill

Remove fixed logos, corner bugs, watermarks, and burned-in subtitles from videos locally with ProPainter video inpainting. Auto-locates elements and when they appear, shows a mask preview for user confirmation, inpaints, runs QC, and delivers the clean video plus before/after images and a coordinates document for placing a new logo or subtitles.

What is video-watermark-remover-skill?

This skill provides a comprehensive local video watermark and subtitle removal solution using the ProPainter video inpainting engine. It is designed for developers and users who need to remove fixed-position logos, corner bugs, station IDs, watermarks, or burned-in subtitles (hardsubs) from video content without relying on cloud services. The skill works best on NVIDIA GPUs with at least 4GB VRAM, but can also function on CPU at reduced speed. It supports platforms like Claude Code, Cursor, and similar AI agent environments where a directory-based SKILL.md can be invoked. The workflow consists of eight ordered steps: probing the video to understand element positions, locating static elements, generating a mask preview for user confirmation, segmenting the video at scene cuts, inpainting with ProPainter (using ROI cropping to preserve the original background), compositing the result, running quality control (QC) checks with both numerical metrics and visual inspection, and finally delivering the clean video along with before/after images and a coordinates document for re-inserting logos or subtitles. The skill enforces hard-learned rules such as keeping ROI dimensions ≥160px, splitting scene transitions, avoiding full-block covers on thin elements, symmetric subtitle cropping, and mandatory user confirmation before inpainting. It uses scripts under a `scripts/` directory and requires environment setup via `setup_env.py` and a self-test via `selftest.py` before first use.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/starx1n9/video-watermark-remover-skill/tree/HEAD/skills/video-watermark-remover

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Video Logo / Subtitle Removal (video-watermark-remover)

Uses ProPainter to inpaint fixed-position logos, corner bugs, and subtitles (burned into the video). It crops only the ROI around the element for inpainting, then feathers it back into the original video, leaving pixels outside the ROI untouched.

⚠ ProPainter's license is S-Lab License 1.0: non-commercial use only. Contact the original authors for commercial use. Only use on content you have permission to process.

0. Environment (First Use)

Scripts are in scripts/. The working directory must be this skill's directory (where SKILL.md resides).

python scripts/setup_env.py      # Check ffmpeg / PyTorch (CUDA), clone a fixed version of ProPainter, generate config.json
python scripts/selftest.py       # Synthesize a test video, run the full pipeline, compare with ground truth, output PASS/FAIL
  • If config.json does not exist, setup_env.py will prompt what is missing and how to install it. PyTorch requires the user to install the CUDA version specific to their GPU.
  • Do not proceed if self-test fails; fix the environment based on prompts.
  • The python below refers to the interpreter recorded in config by setup_env.py (i.e., the environment used when running setup).

What the User Needs to Provide

ItemRequiredDescription
Video file✅One or more
What to remove✅Natural language: "top-left corner bug", "center large logo", "bottom subtitle"
Position hintOptionalOnly needed when there are multiple instances of the same element
Time rangeOptionalDefault is automatic detection; only needed if you want to keep part of it
Subsequent plansRecommend askingWhether a new logo will be overlaid, subtitles re-inserted, or new subtitles have a background bar. If there is a bar, just blur it – no need to inpaint

If mandatory items are missing, ask the user; do not ask for optional ones.

Capability Boundaries (Check Before Starting; Inform User If Exceeded)

  • ✅ Fixed-position logos, corner bugs, station IDs, including semi-transparent, fade-in/out, or those stretched in scene transitions (e.g., intro/outro).
  • ✅ Fixed-position, horizontally centered, single-line subtitles (burned in), white or dark, with or without shadows.
  • ⚠️ Moving or scaling logos, scrolling subtitles, multi-line subtitles, non-centered subtitles: the script cannot auto-track them; the user must specify time ranges and manually handle with --block or --grow-from, with reduced effectiveness.
  • ⚠️ Preexisting text in the frame (signs, on-screen text) that overlaps with the element to be removed will be blurred too. Flag this in the identification list.
  • Requires an NVIDIA GPU with at least 4GB free VRAM. Can run on CPU only but is very slow.

Workflow (8 Steps, In Order)

Intermediate files go to a temporary working directory $W. The final output is placed next to the original video; QC images go to <video directory>/qc/.

1. Probe

python scripts/probe.py IN1.mp4 [IN2.mp4] --out-dir $W

Examine the stitched frames to determine:

  • Where the element is;
  • Whether it's a corner bug or a centered large logo;
  • Whether there are fades (record the stable frame range of the logo), black frames, scaling or blur transitions;
  • Whether there are subtitles.

2. Locate (Static Logo)

python scripts/locate_static.py --video IN1.mp4@stable_start_frame:stable_end_frame [--video IN2.mp4@a:b] --out-dir $W
  • Avoid fade-in, black frames, and transitions for start/end frames.
  • When the same logo appears in multiple clips, pass them all together. The script only takes pixels that are stationary across all clips, excluding accidentally stationary background blocks.
  • View static_rel.png (darker = more stationary). Define the rough logo bounding box --box and threshold: solid logos use 0.3–0.5, semi-transparent logos use 0.6–0.75.

3. Create Mask and Must Get User Confirmation

Static logo:

python scripts/mask_logo.py --rel $W/static_rel.npy --box x0,y0,x1,y1 --thresh 0.5 \
    [--block rectangle] [--clear region --block region] --preview IN1.mp4@frame [--preview IN2.mp4@frame] --out-dir $W
  • Thin elements (text, rays, lines) are traced by strokes, leaving the real background between strokes as reference for the model.
  • Only use --block rectangle in two cases: solid large blocks, or thin text so small it can't be traced completely (e.g., a line of tiny English text).
  • ❌ Do not fully cover thin elements with a block. As the camera slowly pushes in, the blocked area has almost no real background exposed, forcing the model to generate from scratch, resulting in "scaly texture."
  • Check the preview image for missing edges and tails of the element. Missed parts will carry over as colored trails during inpainting.

Subtitles:

python scripts/mask_subtitle.py IN.mp4 --out-dir $W [--rows y0,y1] [--dark] [--shadow 0,0]
  • Without --rows, automatically finds subtitle rows in the lower 40% of the frame; if wrong, specify manually.
  • Watch for warnings about "close to frame edge" in the output.

User confirmation: Show the user mask_preview.png or subtitle_mask_preview.png, along with an identification list. Use coordinates printed by mask_logo.py, for example:

① Top-left station logo: repair box (21,15)–(124,50), appears throughout ② Bottom subtitle: y485–524, total 47 lines ③ At 1:34, a sign in the frame falls inside the repair area and will be blurred together

Proceed to step 5 only after user confirmation.

4. Segment

python scripts/shots.py IN.mp4 --out $W/bounds.json [--extra 178]

Hard cuts and fade transitions are split. If not segmented, static shots will produce "pill-shaped color patches" of adjacent shot colors.

5+6. Inpaint + Composite

For long videos, run in background, periodically check logs and proactively report progress.

python scripts/inpaint.py --video IN.mp4 (--mask $W/mask.npy | --masks $W/masks.npz) --bounds $W/bounds.json \
    --workdir $W/run --out OUT.mp4 [--mask-dilation 2] [--grow-from 178:14] [--workers 2]
  • --mask-dilation: default 2; use 4 for semi-transparent or glowing logos.
  • --grow-from frame:pixels: expand the mask outward by a number of pixels starting from a given frame, to cover logos stretched in scaling or blur transitions.
  • For the same video needing both logo and subtitle removal, run twice: the second --video uses the first output; do not merge the two ROIs into one large box.
  • If interrupted, rerun the same command – completed segments will be skipped.
  • Name output as original-nologo.mp4 or original-nologo-nosub.mp4. The script refuses to overwrite the original.
  • Time reference (RTX 3060, 2 workers): 960×540, 126-second video: ~17 min for top-left logo, ~21 min for subtitles; 5-second centered large logo: ~1 min.

7. QC

python scripts/qc.py --orig IN.mp4 --out OUT.mp4 (--mask .. | --masks ..) --bounds $W/bounds.json --workdir $W/run \
    [--residue blue] [--times 3,12,20] --out-dir <video_dir>/qc/xxx

Numerical metrics:

  • Residual: variance of output should approximately equal variance of outer region.
  • Flicker: median ratio around 1 or smaller, very few spike frames.
  • Seam: ratio approximately 1.
  • Out-of-region error: less than 2.
  • Residual color: near 0.
  • All-black frames: 0.

⚠ Numerical metrics may pass while local issues exist – visually inspect every `sheet_XX.png (color patches, scaly texture, residual text, colored lines). If problems are found, return to the corresponding step:

  • Color patches: add cut points via shots.py --extra.
  • Scaly texture: switch to stroke-based masking.
  • Residual lines: add --block.

After fixing, rerun only the affected segments, then QC again.

8. Delivery

  • Provide the absolute paths of the output video and comparison images, and show before_after.png to the user.
  • If multiple clips need concatenation, use python scripts/concat.py A.mp4 B.mp4 C.mp4 --out FULL.mp4; the script will verify total frames equal sum of parts.
  • Write a coordinates document using the template at references/positions-template.md. For each element, document three nested boxes: the original element range, the actual repair range, and the outermost feathering range (i.e., the recommended coverage for a new logo). For subtitles, use union_box from subtitle_info.json.
  • List known defects with timestamps clearly; do not hide them.

Hard Rules (Learned from Practice)

  1. ROI width and height must both be ≥160. At height 96, ProPainter outputs all black in the mask region, even with fp32. inpaint.py ensures this automatically when selecting the ROI.
  2. Transitions must be split. Splitting only at hard cuts misses fades, causing ProPainter to borrow pixels across shots, producing color patches. This cannot be fixed by enlarging the mask or using fp32.
  3. Thin elements: trace by strokes, do not fully cover with a block.
  4. Subtitles: crop symmetrically and use the same mask for the same subtitle line. Without symmetric cropping, background bright streaks are cropped in; changing masks for the same line causes flicker, and fades also leak.
  5. Numerical metrics can miss problems – visually inspect every contact sheet page.
  6. Get user confirmation for the mask before inpainting. Do not overwrite the original; do not delete user files.
  7. Background subprocesses must not pop up windows (already built into scripts). Run long tasks in the background and proactively report progress.

Related Skills