Skip to content

Instantly share code, notes, and snippets.

@jikamens
Created March 28, 2026 23:58
Show Gist options
  • Select an option

  • Save jikamens/19fc31364709889ff947824fd7020f33 to your computer and use it in GitHub Desktop.

Select an option

Save jikamens/19fc31364709889ff947824fd7020f33 to your computer and use it in GitHub Desktop.
Quick-and-dirty script to blur a box in a video using ffmpeg
#!/usr/bin/env perl
# Quick-and-dirty script to blur a box in a video.
#
# Still to deal with later when I need it:
#
# * Blur multiple regions
# * Only blur for part of the duration of the video (can add
# "enable=between(t,#,3)" to filters for this)
# * Handle videos with sound properly (might need "-map 0:a -c:a copy",
# might need to use mplayer -identify to check if audio track exists)
#
# Lessons learned from:
# https://shotstack.io/learn/ffmpeg-blur-video/
# https://www.youtube.com/watch?v=i_7WFG2C5cM
# https://stackoverflow.com/questions/20847674/ffmpeg-libx264-height-not-divisible-by-2
#
# By Jonathan Kamens <jik@kamens.us>
# Released into the public domain.
use strict;
use warnings;
use File::Basename;
use File::Temp 'tempfile';
use Getopt::Long;
my $whoami = basename $0;
my $usage = "Usage: $whoami [--help] [--verbose] [--no-show] [--input-file path]
[--output-file path] [--left #] [--top #] [--right #] [--bottom #]
[--blur-radius #]\n";
my($verbose, $input_file, $output_file, $left, $top, $right, $bottom);
my $radius = 2;
my $show = 1;
die $usage if (! GetOptions("help" => sub {print $usage; exit; },
"verbose" => \$verbose,
"show!" => \$show,
"input-file=s" => \$input_file,
"output-file=s" => \$output_file,
"left=i" => \$left,
"top=i" => \$top,
"right=i" => \$right,
"bottom=i" => \$bottom,
"blur-radius=i" => \$radius));
sub prompt {
my($prompt) = @_;
print(STDERR "$prompt: ");
my $response = <STDIN>;
chomp $response;
exit if (! $response);
return $response;
}
$input_file = &prompt("Input video file") if (! $input_file);
die "$input_file does not exist\n" if (! -f $input_file);
$output_file = &prompt("Output video file") if (! $output_file);
my($fh, $still_file) = tempfile(undef, SUFFIX => ".jpg");
system('ffmpeg', '-y', '-loglevel', 'quiet', '-i', $input_file, '-frames', '1',
$still_file) and die;
if (! (defined $left && defined $top && defined $right && defined $bottom)) {
if (! fork) {
exec('gimp', $still_file);
}
}
$left = &prompt("Left of area to be blurred") if (! defined $left);
$top = &prompt("Top of area to be blurred") if (! defined $top);
$right = &prompt("Right of area to be blurred") if (! defined $right);
$bottom = &prompt("Bottom of area to be blurred") if (! defined $bottom);
my $width = $right - $left;
my $height = $bottom - $top;
my(@cmd) = ("ffmpeg", "-y");
if (! $verbose) {
push(@cmd, "-loglevel", "quiet");
}
push(@cmd,
"-i", $input_file,
"-filter_complex",
"[0:v]crop=$width:$height:$left:$top,boxblur=$radius\[mask];" .
"[0:v][mask]overlay=$left:$top\[combined];" .
# Just in case the video is an odd width or height and the target encoder
# doesn't allow that.
"[combined]pad=ceil(iw/2)*2:ceil(ih/2)*2[padded];",
"-map", "[padded]",
$output_file);
print("@cmd\n");
system(@cmd) and die;
system("open", $output_file) if ($show);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment