Skip to contents

Anonymize a video by covering one or more fixed rectangular regions with opaque filled boxes. For example, you can redact a face, a name badge, or a screen that stays in one place for the whole clip. The regions are fixed (there is no face or object tracking), so this suits footage where the areas to cover do not move. The glossary in vignette("tidymedia") explains media terms such as codec, pixel format and stream copy.

Usage

anonymize_video(
  infile,
  outfile,
  regions,
  color = "black",
  video_codec = "libx264",
  audio_codec = "copy",
  pixel_format = "yuv420p",
  hardware = c("none", "nvenc", "videotoolbox"),
  fallback = FALSE,
  quality = NULL,
  audio_stream = NULL,
  run = TRUE
)

Arguments

infile

A string containing the path to a video file.

outfile

A string containing the path of the video file to write.

regions

A data frame with one row per box and columns x, y, width, height (and optionally color); see Details.

color

A string naming the default fill color in FFmpeg color syntax, used for any row without its own color (default "black").

video_codec

A string naming the output video codec (default "libx264"). NULL emits no -codec:v and lets the output container's default encoder decide. NULL is how you opt out of the H.264 default for a container that does not hold it. For a .webm output, pass video_codec = NULL and audio_codec = NULL, because the default audio_codec = "copy" would otherwise carry a codec WebM cannot hold.

audio_codec

A string naming the output audio codec. The default "copy" stream-copies the source audio unchanged. Name a real encoder, such as "aac", when the source audio codec cannot be copied into the output container. NULL emits no -codec:a and lets the container's default encoder decide.

pixel_format

A string naming the output pixel format (default "yuv420p").

hardware

The encoder backend. "none" (default) uses the software video_codec. "nvenc" uses NVIDIA GPU encoding (H.264, HEVC and AV1), and "videotoolbox" uses Apple GPU encoding (H.264 and HEVC). A backend uses its own encoder for the family of video_codec. For example, "libx264" becomes "h264_nvenc" or "h264_videotoolbox". See has_hardware_encoder for availability and its caveats. This applies to video only. audio_codec is never hardware-accelerated. Resolving a hardware backend asks this FFmpeg build which encoders it has. So the first such call that re-encodes the video runs FFmpeg while the command is built, even under run = FALSE. The answer is remembered for the rest of the R session. See refresh_ffmpeg_capabilities to discard it.

fallback

A logical. When a hardware other than "none" is requested but its encoder is unavailable, TRUE re-encodes with the software video_codec and a message. FALSE (default) aborts instead. This keeps output reproducible by never changing the codec silently. A video_codec in a family that the backend has no encoder for is a wrong argument, not an absent encoder. So it aborts whatever fallback says.

quality

A number, or NULL (default) to leave the encoder's own default in place. It is the encoder's own rate-control value, passed through unchanged. libx264 and libx265 read it as -crf (0 to 51). The nvenc encoders read it as -cq (0 to 51), and the videotoolbox encoders as -q:v (1 to 100). Each scale is its own: the same number means something different on each encoder. A value outside the encoder's range is refused. An encoder outside those seven, such as libvpx-vp9, is refused with quality set. So is a video_codec of "copy", or of NULL under hardware = "none". When fallback = TRUE falls back to software, the value is dropped and the message says so, because it belonged to the hardware encoder's scale.

audio_stream

The audio track to carry into the output, as a number that counts from 0 among the audio tracks of the input. 0 is the first audio track and 1 is the second. Other streams in the file, such as video, do not count. NULL (default) carries every audio track. The every-track family reads NULL this way: separate_audio_video, standardize_video, anonymize_video, crop_video, segment_video and format_for_web, and their _batch forms. The first-track family reads it as the first audio track only: extract_audio, convert_audio and normalize_audio, and their _batch forms. The function does not carry subtitle or data streams in either case. A track the input does not have gives an FFmpeg error, not an R one. See audio_stream for how this differs from audio_input, the input index on compare_videos and picture_in_picture. (default = NULL)

run

A logical: run the command through FFmpeg (TRUE, default) or return the compiled command without running it (FALSE).

Value

The compiled FFmpeg command (invisibly when run = TRUE).

Details

regions is a data frame with one row per box and the columns x, y, width, and height. Each value is a pixel number or an FFmpeg expression such as "in_w/2". x and y give the top-left corner, and width and height give the size. An optional color column overrides the color argument for that row. Every box is a solid fill (FFmpeg's drawbox with t=fill). The function intentionally does not offer hollow outlines.

Because the function applies a filter, it re-encodes the video. The video_codec and pixel_format arguments set the encoding, and default to H.264 and yuv420p. The function floors odd source dimensions to even, so the output always encodes. yuv420p and libx264 require even dimensions, and the step changes nothing for input that is already even. The function stream-copies the audio unchanged (-c:a copy) unless audio_codec names an encoder. The same input and regions therefore always compile to a byte-identical command.

References

https://ffmpeg.org/ffmpeg-filters.html#drawbox

Examples

video <- system.file("extdata", "sample.mp4", package = "tidymedia")
# Cover two fixed regions with black boxes
regions <- data.frame(
  x = c(10, 200), y = c(10, 150),
  width = c(120, 80), height = c(90, 60)
)
anonymize_video(video, "anon.mp4", regions, run = FALSE)
#> [1] "-y -i \"/home/runner/work/_temp/Library/tidymedia/extdata/sample.mp4\" -vf \"crop=w=floor(in_w/2)*2:h=floor(in_h/2)*2:x=(in_w-out_w)/2:y=(in_h-out_h)/2,drawbox=x=10:y=10:w=120:h=90:c=black:t=fill,drawbox=x=200:y=150:w=80:h=60:c=black:t=fill\" -codec:v libx264 -codec:a copy -pix_fmt yuv420p -map \"0:v?\" -map \"0:a?\" \"anon.mp4\""
# Carry only the second audio track instead of all of them
anonymize_video(video, "anon.mp4", regions, audio_stream = 1, run = FALSE)
#> [1] "-y -i \"/home/runner/work/_temp/Library/tidymedia/extdata/sample.mp4\" -vf \"crop=w=floor(in_w/2)*2:h=floor(in_h/2)*2:x=(in_w-out_w)/2:y=(in_h-out_h)/2,drawbox=x=10:y=10:w=120:h=90:c=black:t=fill,drawbox=x=200:y=150:w=80:h=60:c=black:t=fill\" -codec:v libx264 -codec:a copy -pix_fmt yuv420p -map \"0:v?\" -map \"0:a:1\" \"anon.mp4\""