Turn recordings into a corpus of annotated transcripts
prepare_audio.RdTranscribes a recording and writes its pauses and speakers back into the
text, so measure(text = ) reads a transcript that carries them. A plain
transcript records what was said and discards how. Pauses, hesitation and who
was speaking leave with it, and for a construct that lives in delivery rather
than in wording that is the absence of the thing being measured.
Usage
prepare_audio(
x,
...,
diarize_names = FALSE,
annotate_timing = FALSE,
tool = c("whisperx", "whispermlx"),
asr_model = "large-v3",
min_speakers = NULL,
max_speakers = NULL,
language = NULL,
hf_token = NULL,
extra = NULL,
min_pause = 0.2,
speakers = NULL,
id = NULL,
cache_dir = NULL,
force = FALSE,
verbosity = c("normal", "quiet", "verbose")
)Arguments
- x
A character vector of paths to audio files, one row each. A path that does not resolve comes back with
exists = FALSErather than being dropped.- ...
Not used, and refused if anything reaches it. Every argument after it is name-only, so a misspelling lands here rather than being matched by position.
- diarize_names
A logical value indicating whether to label who is speaking. This is a transcription setting rather than a rendering one. It runs the diarizer, needs
hf_token, and is in the cache key, so switching it re-transcribes. It also decides whether the text has turns in it at all, since a turn is a run of words by one speaker. Off, every silence is a[pause]and the transcript is one run of text. Default isFALSE.- annotate_timing
A logical value indicating whether silences are written into the text, as
[pause 0.4s]inside a turn and[gap 0.8s]between two. Default isFALSE.- tool
A string naming the recognizer to run,
"whisperx"(the default) or"whispermlx". Both emit the same JSON.- asr_model
A string naming the Whisper checkpoint, which decides the words. Both tools take the same short names. The command-line default is
small, which is not the default here. Default is"large-v3".- min_speakers, max_speakers
Integers passed to the diarizer when known, or
NULL(the default). An interview has two speakers and the researcher knows it beforehand, which is the cheapest reduction in diarization error available.- language
A string giving the spoken language, detected per file when
NULL(the default).- hf_token
A string giving a HuggingFace read token, required when
diarize_namesis on. Default isNULL.- extra
A character vector of further flags for the recognizer, passed through verbatim and recorded on the result. Each element is its own argument, so
c("--beam_size", "10")rather than one string. Default isNULL.- min_pause
A number giving the seconds below which a silence is not marked or counted. Jefferson leaves anything under 0.2 as an untimed micropause and gives a figure above it, which is where the default comes from.
- speakers
A named character vector renaming the speakers, such as
c(SPEAKER_00 = "Interviewer"). A name not in the timings is refused rather than ignored. Default isNULL.- id
A character vector of identifiers, one per file. Defaults to the file names without their extensions, and must be unique.
- cache_dir
A string giving the directory where transcripts are written and looked for. Defaults to beside each recording.
- force
A logical value indicating whether to transcribe again even where a usable transcript is cached. Default is
FALSE.- verbosity
A string setting how much to report.
"quiet"reports nothing."normal"is the default. One line per recording as it is transcribed, or in an interactive session a bar redrawn in place when there is more than one. The recognizer's own output is held rather than printed, and appears only if the call fails."verbose"lets the recognizer write to the console as it runs. These tools report their dependencies at length on every invocation, so this is for one long file rather than for a corpus.
Value
A tibble of one row per file.
id,audioandexistsidentify the recording.textis the transcript, with turns wherediarize_nameswas on and silences whereannotate_timingwas.duration_sandn_wordshold what the recording holds.n_turnsandn_speakersareNAwhen the timings carry no speaker.n_pausescounts the silences inside a turn at or abovemin_pause.n_gapscounts the silences at a turn boundary,NAwhen not diarized.median_gap_sis the middle silence between two words.speech_rateandarticulation_rateare words per second over the whole recording, and over voiced time only. The first counts the silences and the second does not, so the ratio between them is how much of the elapsed time was pausing rather than talking.median_turn_sis the middle turn,NAwithout diarization.median_scoreis the middle alignment confidence,NAwhere the transcript carries none. Every silence figure rests on the word boundaries this describes, so it is what says how far to trust them.diarizedsays whether the transcript carried speakers, which is also whether the text has turns in it.annotate_timingsays whether the silences were written in.transcribedsays whether this call produced the transcript.tool,asr_modelandextrahold what made the words, orNAwhere a transcript was found with no record beside it.transcribedand these together say whether a row was made now, read from a verified transcript, or read from one of unknown origin.languageandmin_pauseare read off the transcript or applied by this call, so both hold however the transcript was obtained.
Caching
A transcript already on disk is read instead of transcribing again, so an
interrupted run resumes and a hand-corrected transcript survives. force = TRUE returns to the recognizer.
What counts as already done is the question measure() asks of its
checkpoints, which is whether the work was done under the settings now being
requested rather than merely done. tool, asr_model, diarize_names,
language and extra are recorded in a .fathom.json beside each
transcript, and a run whose settings differ transcribes again. Without that,
a call with diarize_names off in a directory that already holds a diarized
transcript would read the diarized one and quietly ignore the argument.
annotate_timing, min_pause and speakers are not in that record, since
none of them reaches the recognizer. Changing a rendering costs nothing and
re-reads the same transcript.
A transcript with no record beside it is used rather than discarded, because
that is what a hand-corrected one looks like. Its tool, asr_model and
extra come back NA, since naming settings that did not produce those
words would be worse than saying nothing.
Transcript format
The row a model is asked about is the whole recording, and the structure
inside it is written into text rather than spread across rows.
Inside a row, a line is a turn, meaning a maximal run of words by one
speaker. Nothing splits a turn further. A silence inside one person's talk is
written where it happened, as [pause 0.4s], which says how long it was
rather than only that it passed a threshold.
Silence at a turn boundary is written [gap 0.8s] and belongs to neither
speaker, so it goes on a line of its own. Whether a silence is a hesitation
or a turn transition is a fact about the recording, so the two markers are
told apart by whether the timings carry a speaker, not by whether labels were
asked for.
Lines are newline separated with the speaker in brackets ahead of them.
Speakers
Speakers reach the output only from the recognizer. Absent, n_speakers and
n_turns are NA and diarized is FALSE, so a transcript with no speaker
in it is never read as a transcript with one speaker in it.
speakers renames what the recognizer emitted, since SPEAKER_00 carries
nothing a model can use. It is also the fastest way to put the answer in the
prompt. A speaker renamed Angry Protester has been coded before the model
saw the text, and nothing in the results would show it.
Pause and gap counts
n_pauses and n_gaps count what min_pause found in the timings, so they
hold with annotate_timing off, where the text shows nothing. Only text
and annotate_timing change with the rendering.
None of them reaches the model, since measure() reads id and text and
nothing else.
n_pauses also moves with diarization on the same words, because without
turns a turn transition is just another silence.
References
Jefferson, G. (1989). Preliminary notes on a possible metric which provides for a standard maximum silence of approximately one second in conversation. In D. Roger and P. Bull (eds), Conversation. An Interdisciplinary Perspective, 166–196. Multilingual Matters.
Stivers, T., Enfield, N. J., Brown, P., Englert, C., Hayashi, M., Heinemann, T., Hoymann, G., Rossano, F., de Ruiter, J. P., Yoon, K.-E., and Levinson, S. C. (2009). Universals and cultural variation in turn-taking in conversation. Proceedings of the National Academy of Sciences 106(26), 10587–10592. doi:10.1073/pnas.0903616106
See also
prepare_video() for recordings with pictures, and measure(),
which reads the text column. vignette("quick_start", package = "fathom") for installing the recognizer and the diarization token.
Examples
if (FALSE) { # \dontrun{
# Needs ffmpeg and a recognizer, so it is not run here.
# Everything off, which is a plain transcript and needs no token and no
# optional package.
plain <- prepare_audio("hearing_04.wav")
# Two speakers known in advance, labeled turns with the silences marked.
docs <- prepare_audio(
c("hearing_04.wav", "hearing_11.wav"),
diarize_names = TRUE,
annotate_timing = TRUE,
min_speakers = 2,
max_speakers = 2,
hf_token = Sys.getenv("HF_TOKEN")
)
# Anything else the recognizer takes.
tuned <- prepare_audio(
"hearing_04.wav",
extra = c("--no_speech_threshold", "0.4", "--beam_size", "10")
)
} # }