Skill: ozzylennon/ffmpeg-video-watermark-remover (version 1.0.0)

--- name: video-watermark-remover description: Remove watermarks from videos using ffmpeg delogo filter. Use this skill whenever the user wants to remove a watermark from a video, asks to remove a logo/text overlay, sends a video with visible watermarks, or says "去除水印" / "remove watermark" / "去水印". Works for both fixed-position and dynamically-moving watermarks (segmented delogo approach). Does NOT remove transparent/AI-generated complex watermarks that require inpainting — those need a different pipeline. --- # Video Watermark Remover Remove watermark/logo/text overlays from videos using ffmpeg's `delogo` filter. ## Core Logic **Step 1: Extract sample frames** Extract frames at 1-second intervals to locate watermark positions: ```bash for t in 1 2 3 4 5; do ffmpeg -y -ss 00:00:0$t -i "$INPUT" -frames:v 1 "/tmp/wm_frame_$t.jpg" 2>/dev/null done ``` **Step 2: Analyze with vision model** Use the `image` tool to analyze the sample frames. Ask for: - Exact watermark positions (which corner/edge) - Pixel bounding box: x, y, width, height for a WxH resolution video - Whether watermarks are in the same position across all frames (fixed) or move (dynamic) **Step 3: Build segmentation strategy** *Fixed watermark* (same position throughout): - One delogo pass on entire video *Dynamic watermark* (position changes at specific times): - Identify segment boundaries (e.g., 0-3.5s bottom-right, 3.5s+ left edge) - Process each segment separately with its own delogo coordinates - Concatenate segments back together **Step 4: Process and verify** ```bash # Single segment ffmpeg -y -i "$INPUT" -vf "delogo=x=$X:y=$Y:w=$W:h=$H" -c:a copy "$OUTPUT" # Multiple segments # Process each segment separately with -ss/-t, then concat ``` Verify each segment's result with the vision model before final delivery. ## Key Parameters | Parameter | Description | |-----------|-------------| | `x, y` | Top-left corner of watermark region | | `w, h` | Width and height of watermark region | | `INPUT` | Input video path | | `OUTPUT` | Output video path | **Finding exact coordinates:** - Vision model gives normalized coordinates on 0-1000 scale — convert to actual pixels - For a 720x1280 video: `actual_x = normalized_x * width / 1000` - Always add 5-10px padding to the detected region to ensure full coverage ## Dynamic Watermark Segmentation When watermark moves between distinct positions: 1. **Identify transition points** — Note which frames have watermarks at which positions 2. **Create segments** — Each position change = new segment boundary 3. **Process each segment** — Apply correct delogo coordinates to each time range 4. **Concatenate** — Use ffmpeg concat with a manifest file: ``` file 'seg1.mp4' file 'seg2.mp4' ``` ```bash ffmpeg -y -f concat -safe 0 -i concat.txt -c copy output.mp4 ``` **Example segmentation:** | Segment | Time Range | Watermark Position | |---------|-----------|-------------------| | 1 | 0-3.5s | Bottom-right (x=540,y=1195,w=165,h=55) | | 2 | 3.5-5s | Left edge (x=30,y=480,w=160,h=210) | ## Handling Multiple Watermarks If multiple watermarks exist simultaneously (e.g., corner logo + username handle): - Identify which the user wants removed vs. preserved - Apply multiple `delogo` filters in the same vf string: ```bash -vf "delogo=x=540:y=1195:w=165:h=55,delogo=x=30:y=480:w=160:h=210" ``` ## Output Delivery - Save to `/root/.openclaw/workspace/downloads/` - Send via `message` tool with `media` parameter - Confirm with user before final delivery ## Limitations - `delogo` fills the region with a blurred/averaged patch — works best on simple/static backgrounds - Semi-transparent watermarks, complex AI-generated watermarks, and moving watermarks over textured backgrounds may still show traces - For AI inpainting (natural content-aware fill): this skill cannot do it — inform the user and suggest inpainting pipeline if delogo result is unsatisfactory

path: scripts/extract_frames.py
content: #!/usr/bin/env python3
"""
Video Watermark Remover - Helper Script
Extracts frames from video at regular intervals for watermark analysis.
"""
import subprocess
import json
import sys
from pathlib import Path

def get_video_info(input_path):
    cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', input_path]
    data = json.loads(subprocess.run(cmd, capture_output=True, text=True).stdout)
    vid = next((s for s in data['streams'] if s['codec_type'] == 'video'), None)
    if not vid:
        raise ValueError(f"No video stream found in {input_path}")
    
    cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', input_path]
    data = json.loads(subprocess.run(cmd, capture_output=True, text=True).stdout)
    duration = float(data['format']['duration'])
    
    return vid['width'], vid['height'], duration

def extract_sample_frames(input_path, output_dir, interval=1.0):
    """Extract frames at regular intervals for watermark detection."""
    _, _, duration = get_video_info(input_path)
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)
    
    frames = []
    num_samples = int(duration / interval)
    for i in range(num_samples + 1):
        t = i * interval
        if t > duration:
            break
        frame_path = output_dir / f"frame_{i:03d}.jpg"
        cmd = [
            'ffmpeg', '-y', '-ss', f'{t:.2f}',
            '-i', input_path,
            '-frames:v', '1', '-q:v', '2',
            str(frame_path)
        ]
        subprocess.run(cmd, capture_output=True)
        if frame_path.exists():
            frames.append((t, frame_path))
            print(f"  t={t:.2f}s: {frame_path}")
    
    return frames

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: extract_frames.py <input_video> <output_dir> [interval_seconds]")
        sys.exit(1)
    
    input_video = sys.argv[1]
    output_dir = sys.argv[2]
    interval = float(sys.argv[3]) if len(sys.argv) > 3 else 1.0
    
    print(f"Extracting frames from: {input_video}")
    w, h, dur = get_video_info(input_video)
    print(f"Video: {w}x{h}, duration: {dur:.2f}s")
    
    frames = extract_sample_frames(input_video, output_dir, interval)
    print(f"\nExtracted {len(frames)} frames to {output_dir}/")


path: scripts/process_video.py
content: #!/usr/bin/env python3
"""
Video Watermark Remover - Main Processor
Handles single-segment and multi-segment watermark removal.
"""
import subprocess
import json
import sys
from pathlib import Path

def process_single_segment(input_video, output_video, x, y, w, h):
    """Process video with a single delogo region."""
    cmd = [
        'ffmpeg', '-y', '-i', input_video,
        '-vf', f'delogo=x={x}:y={y}:w={w}:h={h}',
        '-c:a', 'copy', output_video
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"ERROR: {result.stderr[-500:]}", file=sys.stderr)
        return False
    return True

def process_multi_segment(input_video, output_video, segments, temp_dir):
    """
    Process video with multiple delogo regions across different time segments.
    
    segments: list of (start_time, end_time, x, y, w, h)
    """
    temp_dir = Path(temp_dir)
    temp_dir.mkdir(parents=True, exist_ok=True)
    
    # Process each segment
    seg_files = []
    for i, (s, e, x, y, w, h) in enumerate(segments):
        seg_file = temp_dir / f'seg_{i+1}.mp4'
        t_len = e - s
        
        # For first segment, decode from 0; for others, use -ss with input seeking
        if i == 0:
            cmd = [
                'ffmpeg', '-y', '-i', input_video,
                '-vf', f'delogo=x={x}:y={y}:w={w}:h={h}',
                '-t', str(t_len), '-c:a', 'copy', str(seg_file)
            ]
        else:
            cmd = [
                'ffmpeg', '-y', '-ss', str(s), '-i', input_video,
                '-vf', f'delogo=x={x}:y={y}:w={w}:h={h}',
                '-t', str(t_len), '-c:a', 'aac', str(seg_file)
            ]
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            print(f"Segment {i+1} error: {result.stderr[-300:]}", file=sys.stderr)
            return False
        seg_files.append(seg_file)
        print(f"  Segment {i+1}: {s:.2f}s-{e:.2f}s delogo ({x},{y},{w},{h}) → OK")
    
    # Concatenate segments
    concat_file = temp_dir / 'concat.txt'
    concat_file.write_text('\n'.join(f"file '{f}'" for f in seg_files))
    
    result = subprocess.run([
        'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
        '-i', str(concat_file), '-c', 'copy', output_video
    ], capture_output=True, text=True)
    
    if result.returncode != 0:
        print(f"Concat error: {result.stderr[-300:]}", file=sys.stderr)
        return False
    return True

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: process_video.py <input> <output> <segments_json>")
        print("  segments_json: JSON array of [start, end, x, y, w, h]")
        sys.exit(1)
    
    input_video = sys.argv[1]
    output_video = sys.argv[2]
    segments = json.loads(sys.argv[3])
    
    temp_dir = f"/tmp/watermark_proc_{Path(input_video).stem}"
    
    if len(segments) == 1:
        s = segments[0]
        print(f"Processing single segment: delogo ({s[2]},{s[3]},{s[4]},{s[5]})")
        ok = process_single_segment(input_video, output_video, s[2], s[3], s[4], s[5])
    else:
        print(f"Processing {len(segments)} segments...")
        ok = process_multi_segment(input_video, output_video, segments, temp_dir)
    
    if ok:
        sz = Path(output_video).stat().st_size
        print(f"Done: {output_video} ({sz/1024/1024:.2f}MB)")
    else:
        sys.exit(1)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)