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" )
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)
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.
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.
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:
ffmpeg_version is the FFmpeg version that did the work, and
ffprobe_version is the FFprobe version that was available. tidymedia reads
both from the programs. A version it cannot read is NA.timestamp is the time of the run, with its offset from UTC.output_size is the size of the output file in bytes, so an empty or cut-off
file shows in the record.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)
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")
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)
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:
tidymedia_timeout for the error.tidymedia_probe_timeout for a warning from a version check or a track
check.tidymedia_batch_timeout for the warning from a batch.The warning from a metadata reader has no condition class of its own.
?tidymedia lists which function gives which condition.
vignette("workflow") shows a full research example.vignette("batch") shows how to run a task function over many files.vignette("metadata") shows how to read metadata into tibbles.?tidymedia lists the package options in one place.Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.