Checking results and bounding runs

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
# The chunks below check and produce real files, which means starting FFmpeg or
# FFprobe. Each is evaluated only when the program it needs is installed, so
# this vignette builds on machines and CI images that have neither.
has_ffmpeg <- nzchar(Sys.which("ffmpeg"))
has_ffprobe <- nzchar(Sys.which("ffprobe"))
has_both <- has_ffmpeg && has_ffprobe
# Having FFmpeg is not the same as having an encoder: builds differ in what they
# were compiled with, and some Linux distributions ship one without libx264 or
# libmp3lame. The chunks that re-encode name their encoder and are evaluated
# only where this build has it. Asking that question starts FFmpeg, so it is
# asked in its own guarded chunk below rather than here: this chunk carries no
# `eval` guard, because it is where the guards are defined, and an unguarded
# chunk must start no program (`tools/vignette_chunk_guards.R`).
has_mp3 <- FALSE
# Recorded on stderr rather than into the page: this line is what a build log
# shows for the three programs, so a build that was meant to run without them
# can be checked rather than assumed. `message()` would be captured by knitr and
# then dropped with the rest of this chunk's output.
writeLines(
  paste0("tidymedia vignette build, Sys.which(): ", paste0(
    c("ffmpeg", "ffprobe", "mediainfo"), "=[",
    Sys.which(c("ffmpeg", "ffprobe", "mediainfo")), "]", collapse = " ")),
  con = stderr()
)
# Work in a temporary directory: nothing is written beside the vignette sources,
# and the paths in the reports below stay short enough to read.
knitr::opts_knit$set(root.dir = tempdir())
# Guarded on the two programs, so the encoder question starts FFmpeg only where
# FFmpeg is there to start.
has_mp3 <- tidymedia::has_encoder("libmp3lame")
library(tidymedia)

This page answers three questions about processed files. Does an output have the properties you asked for? Can you later say how a file was made? And how do you stop a program that hangs?

The examples use a copy of the sample clip that comes with the package:

file.copy(
  system.file("extdata", "sample.mp4", package = "tidymedia"),
  "session01.mp4"
)

Check the output against what you asked for

A task function returns the FFmpeg command it ran. That command says what you asked for. verify_media() reads the output file and says what you got:

extract_audio("session01.mp4", "session01.m4a")

verify_media("session01.m4a", audio_codec = "aac", duration = 1)

The result is a tibble with one row for each check. Its columns are file, check, expected, actual and pass. When a check fails, actual shows what the file holds instead:

verify_media("session01.mp4", duration = 5, width = 1920, audio_codec = "aac")

You can check any field that FFprobe reports, by its name:

verify_media("session01.mp4", pix_fmt = "yuv420p", nb_streams = 2)

What the checks cover

The checks read the file's metadata, such as its duration, size and codec names. They do not judge whether the picture or the sound is good.

A number passes when it is within tolerance of the expected value. The default tolerance is 0.1. So a width or a sample rate must match exactly, but a duration can differ a little. A codec name must match exactly.

A property that the file does not have fails. For example, the audio file above has no video stream, so a width check fails with an actual of NA:

verify_media("session01.m4a", width = 320, audio_codec = "aac")

Another field name is looked for in the container information first. Then it is looked for in the video stream, then in the audio stream. The first match is used.

Checking every job in a batch

ffm_batch() and the *_batch() task functions take a verify argument. It adds a verified column to the result. The column is TRUE or FALSE for each job that ran and could be checked. It is NA for a job that failed or could not be checked. A named list applies the same checks to every job:

jobs <- tibble::tibble(
  input  = "session01.mp4",
  output = "session01.mp3"
)

ffm_batch(
  jobs,
  verify = list(audio_codec = "mp3"),
  .f = function(input, output, ...) {
    ffm_files(input, output) |>
      ffm_drop("video") |>
      ffm_codec(audio = "libmp3lame")
  }
)

A failed check does not stop the batch. The other jobs still run, and the verified column shows which outputs to look at. To use different checks for each job, pass a function of the job columns in place of the list.

Record how the files were made

The command for each job records what you asked FFmpeg to do. It does not record which FFmpeg version ran, and two versions can make different files from one command.

ffm_batch(manifest = TRUE) records those facts. The manifest is stored with the batch result, and ffm_manifest() reads it:

res <- ffm_batch(
  jobs,
  manifest = TRUE,
  .f = function(input, output, ...) {
    ffm_files(input, output) |>
      ffm_drop("video") |>
      ffm_codec(audio = "libmp3lame")
  }
)

res

ffm_manifest(res)

The manifest has one row for each job. The command, input and output columns repeat the job. The other columns record the run:

A job with several inputs lists them in one input cell, separated by ;.

With checksums = TRUE, each row also has input_md5 and output_md5. A checksum lets you show later that a file on disk is the file in the record:

res <- ffm_batch(
  jobs,
  manifest = TRUE,
  checksums = TRUE,
  .f = function(input, output, ...) {
    ffm_files(input, output) |>
      ffm_drop("video") |>
      ffm_codec(audio = "libmp3lame")
  }
)

ffm_manifest(res)[, c("input_md5", "output_md5")]

Checksums are off by default, because they read every input and output file in full. That takes time on a large study.

The *_batch() task functions take manifest and checksums too. So extract_audio_batch() and the others record the same facts.

If you give ffm_manifest() a path, it also writes the manifest to a CSV file. You can then keep that file next to the processed data:

ffm_manifest(res, path = "session01_manifest.csv")

file.exists("session01_manifest.csv")

A batch run with run = FALSE runs nothing, so it has no manifest. Reading the manifest of such a batch is an error, not an empty tibble:

jobs <- tibble::tibble(input = "session01.mp4", output = "session01.mp3")
compiled <- ffm_batch(jobs, run = FALSE, manifest = TRUE, .f = function(input, output, ...) {
  ffm_files(input, output) |> ffm_drop("video")
})

ffm_manifest(compiled)

Bound a run that hangs

Each tidymedia call waits for the program it started. If that program hangs, the R session waits with it. For example, a network drive can stall, or FFmpeg can get stuck on a damaged file. A time limit stops the call instead.

The limit is an option, in whole seconds:

options(tidymedia.timeout = 600)

The default is 0, which means no limit.

To limit one call, wrap it in with_timeout(). When the call ends, the option goes back to its earlier value:

with_timeout(
  extract_audio("session01.mp4", "bounded.m4a"),
  seconds = 60
)

getOption("tidymedia.timeout")

To limit the rest of a function, call local_timeout() inside it. Each program started after that line has the limit. When the function returns, the option goes back to its earlier value:

convert_all <- function(files) {
  local_timeout(60)
  vapply(files, function(f) extract_audio(f, sub("[.]mp4$", ".m4a", f)),
         character(1))
}

convert_all("session01.mp4")

getOption("tidymedia.timeout")

Limits of the time limit

The limit sets how long R waits, not how long the program runs. A program that does not stop when asked can make R wait up to 40 seconds longer than the limit. So choose a limit with room to spare.

The limit applies to each program that tidymedia starts. In a batch of 100 jobs, each job gets the full limit.

The limit must be a whole number of seconds. A value such as 0.5 is an error:

with_timeout(extract_audio("session01.mp4", "x.m4a"), seconds = 0.5)

What happens when the limit is reached

A reached limit always gives an error or a warning. The task functions, ffm_run(), the direct commands and verify_media() stop with an error.

Functions that work on many files give a warning, so the other files are not lost. The metadata readers return a row of NA values. ffm_batch() and the *_batch() task functions mark the job with success = FALSE.

To handle these in code, use the condition classes:

The warning from a metadata reader has no condition class of its own.

?tidymedia lists which function gives which condition.

Where to next



Try the tidymedia package in your browser

Any scripts or data that you put into this service are public.

tidymedia documentation built on Oct. 11, 2026, 5:08 p.m.