1 Using whisper in R by Jalal Al-Tamimi

During this session, we will use whisper to automatically transcribe an audio file. We will use the R package reticulate to run Python code from within Rstudio. At the end of the session, we will export the transcription to a TextGrid file that can be opened in Praat for further acoustic analysis.

2 Loading and installation

2.1 Loading packages

First, make sure to have already installed and loaded the package reticulate.

### Use the code below to check if you have all required packages installed. If some are not installed already, the code below will install these. If you have all packages installed, then you could load them with the second code.
requiredPackages = c('reticulate', 'tidyverse')
for(p in requiredPackages){
  if(!require(p,character.only = TRUE)) install.packages(p, dependencies = TRUE)
  library(p,character.only = TRUE)
}
options(ggrepel.max.overlaps = Inf)

2.2 Installation of whisper

Make sure to uncomment the line codes below if you haven’t installed openai-whisper. To do so, we need to install it using the function py_install from reticulate. This needs to be done once, otherwise, the package will be installed again (and this may take some time). We could use the new R package audio.whisperthat is built within R. However, the first time you use this package, you will notice that to transcribe the audio file below, it will take you more than 20 minutes. All subsequent running if the code will be relatively faster. In our case, we use the original implementation of whisper in Python that we run via Rstudio and the reticulate package, which is much faster.

# install whisper
## py_install("openai-whisper", pip = TRUE) # remember to uncomment and do this first

2.3 installation of textgrid

reticulate::repl_python()
## !pip install textgrid

2.4 Loading and using whisper

2.4.1 Audio files

We obtain various audio files from the audio.whisper package’s github website, here We will use the audio file jfk.wav which is a recording of John F. Kennedy’s inaugural address in English.

quit
# load whisper module 
whisper <- import("whisper")
audio <- "jfk.wav" #File in English

2.4.2 Download and load model

We download the whisper model. In our case, we use the base model as it provides better predictions than the tiny model

whisper_model <- "base" # Size of the transcription model
# (Down)load the model
print("Loading the model")
[1] "Loading the model"
model <- whisper$load_model(whisper_model)

2.4.3 Transcription

print("Transcription started")
[1] "Transcription started"
 
transcription <- model$transcribe(audio, language = "en", verbose = FALSE, word_timestamps = TRUE)
 
print("Transcription completed")
[1] "Transcription completed"
transcription %>% 
  head(6)
$text
[1] " And so my fellow Americans ask not what your country can do for you, ask what you can do for your country."

$segments
$segments[[1]]
$segments[[1]]$id
[1] 0

$segments[[1]]$seek
[1] 0

$segments[[1]]$start
[1] 0

$segments[[1]]$end
[1] 7.42

$segments[[1]]$text
[1] " And so my fellow Americans ask not what your country can do for you,"

$segments[[1]]$tokens
 [1] 50364   400   370   452  7177  6280  1029   406   437
[10]   428  1941   393   360   337   291    11 50744

$segments[[1]]$temperature
[1] 0

$segments[[1]]$avg_logprob
[1] -0.3966229

$segments[[1]]$compression_ratio
[1] 1.341772

$segments[[1]]$no_speech_prob
[1] 0.09206953

$segments[[1]]$words
$segments[[1]]$words[[1]]
$segments[[1]]$words[[1]]$word
[1] " And"

$segments[[1]]$words[[1]]$start
[1] 0

$segments[[1]]$words[[1]]$end
[1] 0.52

$segments[[1]]$words[[1]]$probability
[1] 0.6432374


$segments[[1]]$words[[2]]
$segments[[1]]$words[[2]]$word
[1] " so"

$segments[[1]]$words[[2]]$start
[1] 0.52

$segments[[1]]$words[[2]]$end
[1] 0.84

$segments[[1]]$words[[2]]$probability
[1] 0.9871083


$segments[[1]]$words[[3]]
$segments[[1]]$words[[3]]$word
[1] " my"

$segments[[1]]$words[[3]]$start
[1] 0.84

$segments[[1]]$words[[3]]$end
[1] 1.18

$segments[[1]]$words[[3]]$probability
[1] 0.7963706


$segments[[1]]$words[[4]]
$segments[[1]]$words[[4]]$word
[1] " fellow"

$segments[[1]]$words[[4]]$start
[1] 1.18

$segments[[1]]$words[[4]]$end
[1] 1.56

$segments[[1]]$words[[4]]$probability
[1] 0.9954759


$segments[[1]]$words[[5]]
$segments[[1]]$words[[5]]$word
[1] " Americans"

$segments[[1]]$words[[5]]$start
[1] 1.56

$segments[[1]]$words[[5]]$end
[1] 2.1

$segments[[1]]$words[[5]]$probability
[1] 0.9258457


$segments[[1]]$words[[6]]
$segments[[1]]$words[[6]]$word
[1] " ask"

$segments[[1]]$words[[6]]$start
[1] 2.1

$segments[[1]]$words[[6]]$end
[1] 3.72

$segments[[1]]$words[[6]]$probability
[1] 0.2835915


$segments[[1]]$words[[7]]
$segments[[1]]$words[[7]]$word
[1] " not"

$segments[[1]]$words[[7]]$start
[1] 3.72

$segments[[1]]$words[[7]]$end
[1] 4.24

$segments[[1]]$words[[7]]$probability
[1] 0.6561064


$segments[[1]]$words[[8]]
$segments[[1]]$words[[8]]$word
[1] " what"

$segments[[1]]$words[[8]]$start
[1] 4.24

$segments[[1]]$words[[8]]$end
[1] 5.52

$segments[[1]]$words[[8]]$probability
[1] 0.7547233


$segments[[1]]$words[[9]]
$segments[[1]]$words[[9]]$word
[1] " your"

$segments[[1]]$words[[9]]$start
[1] 5.52

$segments[[1]]$words[[9]]$end
[1] 5.76

$segments[[1]]$words[[9]]$probability
[1] 0.9823318


$segments[[1]]$words[[10]]
$segments[[1]]$words[[10]]$word
[1] " country"

$segments[[1]]$words[[10]]$start
[1] 5.76

$segments[[1]]$words[[10]]$end
[1] 6.24

$segments[[1]]$words[[10]]$probability
[1] 0.9989085


$segments[[1]]$words[[11]]
$segments[[1]]$words[[11]]$word
[1] " can"

$segments[[1]]$words[[11]]$start
[1] 6.24

$segments[[1]]$words[[11]]$end
[1] 6.62

$segments[[1]]$words[[11]]$probability
[1] 0.9966052


$segments[[1]]$words[[12]]
$segments[[1]]$words[[12]]$word
[1] " do"

$segments[[1]]$words[[12]]$start
[1] 6.62

$segments[[1]]$words[[12]]$end
[1] 6.82

$segments[[1]]$words[[12]]$probability
[1] 0.9948519


$segments[[1]]$words[[13]]
$segments[[1]]$words[[13]]$word
[1] " for"

$segments[[1]]$words[[13]]$start
[1] 6.82

$segments[[1]]$words[[13]]$end
[1] 7.08

$segments[[1]]$words[[13]]$probability
[1] 0.9948372


$segments[[1]]$words[[14]]
$segments[[1]]$words[[14]]$word
[1] " you,"

$segments[[1]]$words[[14]]$start
[1] 7.08

$segments[[1]]$words[[14]]$end
[1] 7.42

$segments[[1]]$words[[14]]$probability
[1] 0.9964211




$segments[[2]]
$segments[[2]]$id
[1] 1

$segments[[2]]$seek
[1] 0

$segments[[2]]$start
[1] 7.42

$segments[[2]]$end
[1] 10.36

$segments[[2]]$text
[1] " ask what you can do for your country."

$segments[[2]]$tokens
 [1] 50744  1029   437   291   393   360   337   428  1941
[10]    13 50894

$segments[[2]]$temperature
[1] 0

$segments[[2]]$avg_logprob
[1] -0.3966229

$segments[[2]]$compression_ratio
[1] 1.341772

$segments[[2]]$no_speech_prob
[1] 0.09206953

$segments[[2]]$words
$segments[[2]]$words[[1]]
$segments[[2]]$words[[1]]$word
[1] " ask"

$segments[[2]]$words[[1]]$start
[1] 7.42

$segments[[2]]$words[[1]]$end
[1] 8.46

$segments[[2]]$words[[1]]$probability
[1] 0.9024832


$segments[[2]]$words[[2]]
$segments[[2]]$words[[2]]$word
[1] " what"

$segments[[2]]$words[[2]]$start
[1] 8.46

$segments[[2]]$words[[2]]$end
[1] 8.76

$segments[[2]]$words[[2]]$probability
[1] 0.9623243


$segments[[2]]$words[[3]]
$segments[[2]]$words[[3]]$word
[1] " you"

$segments[[2]]$words[[3]]$start
[1] 8.76

$segments[[2]]$words[[3]]$end
[1] 9.04

$segments[[2]]$words[[3]]$probability
[1] 0.9864706


$segments[[2]]$words[[4]]
$segments[[2]]$words[[4]]$word
[1] " can"

$segments[[2]]$words[[4]]$start
[1] 9.04

$segments[[2]]$words[[4]]$end
[1] 9.32

$segments[[2]]$words[[4]]$probability
[1] 0.9966297


$segments[[2]]$words[[5]]
$segments[[2]]$words[[5]]$word
[1] " do"

$segments[[2]]$words[[5]]$start
[1] 9.32

$segments[[2]]$words[[5]]$end
[1] 9.56

$segments[[2]]$words[[5]]$probability
[1] 0.9923227


$segments[[2]]$words[[6]]
$segments[[2]]$words[[6]]$word
[1] " for"

$segments[[2]]$words[[6]]$start
[1] 9.56

$segments[[2]]$words[[6]]$end
[1] 9.78

$segments[[2]]$words[[6]]$probability
[1] 0.9964545


$segments[[2]]$words[[7]]
$segments[[2]]$words[[7]]$word
[1] " your"

$segments[[2]]$words[[7]]$start
[1] 9.78

$segments[[2]]$words[[7]]$end
[1] 9.94

$segments[[2]]$words[[7]]$probability
[1] 0.9963707


$segments[[2]]$words[[8]]
$segments[[2]]$words[[8]]$word
[1] " country."

$segments[[2]]$words[[8]]$start
[1] 9.94

$segments[[2]]$words[[8]]$end
[1] 10.36

$segments[[2]]$words[[8]]$probability
[1] 0.9990544





$language
[1] "en"

2.4.4 export

2.4.4.1 First sentence

textDF1 <- data.frame(t(transcription$segments))[1,1]
textDF1 %>% 
  head(6)
[[1]]
[[1]]$id
[1] 0

[[1]]$seek
[1] 0

[[1]]$start
[1] 0

[[1]]$end
[1] 7.42

[[1]]$text
[1] " And so my fellow Americans ask not what your country can do for you,"

[[1]]$tokens
 [1] 50364   400   370   452  7177  6280  1029   406   437
[10]   428  1941   393   360   337   291    11 50744

[[1]]$temperature
[1] 0

[[1]]$avg_logprob
[1] -0.3966229

[[1]]$compression_ratio
[1] 1.341772

[[1]]$no_speech_prob
[1] 0.09206953

[[1]]$words
[[1]]$words[[1]]
[[1]]$words[[1]]$word
[1] " And"

[[1]]$words[[1]]$start
[1] 0

[[1]]$words[[1]]$end
[1] 0.52

[[1]]$words[[1]]$probability
[1] 0.6432374


[[1]]$words[[2]]
[[1]]$words[[2]]$word
[1] " so"

[[1]]$words[[2]]$start
[1] 0.52

[[1]]$words[[2]]$end
[1] 0.84

[[1]]$words[[2]]$probability
[1] 0.9871083


[[1]]$words[[3]]
[[1]]$words[[3]]$word
[1] " my"

[[1]]$words[[3]]$start
[1] 0.84

[[1]]$words[[3]]$end
[1] 1.18

[[1]]$words[[3]]$probability
[1] 0.7963706


[[1]]$words[[4]]
[[1]]$words[[4]]$word
[1] " fellow"

[[1]]$words[[4]]$start
[1] 1.18

[[1]]$words[[4]]$end
[1] 1.56

[[1]]$words[[4]]$probability
[1] 0.9954759


[[1]]$words[[5]]
[[1]]$words[[5]]$word
[1] " Americans"

[[1]]$words[[5]]$start
[1] 1.56

[[1]]$words[[5]]$end
[1] 2.1

[[1]]$words[[5]]$probability
[1] 0.9258457


[[1]]$words[[6]]
[[1]]$words[[6]]$word
[1] " ask"

[[1]]$words[[6]]$start
[1] 2.1

[[1]]$words[[6]]$end
[1] 3.72

[[1]]$words[[6]]$probability
[1] 0.2835915


[[1]]$words[[7]]
[[1]]$words[[7]]$word
[1] " not"

[[1]]$words[[7]]$start
[1] 3.72

[[1]]$words[[7]]$end
[1] 4.24

[[1]]$words[[7]]$probability
[1] 0.6561064


[[1]]$words[[8]]
[[1]]$words[[8]]$word
[1] " what"

[[1]]$words[[8]]$start
[1] 4.24

[[1]]$words[[8]]$end
[1] 5.52

[[1]]$words[[8]]$probability
[1] 0.7547233


[[1]]$words[[9]]
[[1]]$words[[9]]$word
[1] " your"

[[1]]$words[[9]]$start
[1] 5.52

[[1]]$words[[9]]$end
[1] 5.76

[[1]]$words[[9]]$probability
[1] 0.9823318


[[1]]$words[[10]]
[[1]]$words[[10]]$word
[1] " country"

[[1]]$words[[10]]$start
[1] 5.76

[[1]]$words[[10]]$end
[1] 6.24

[[1]]$words[[10]]$probability
[1] 0.9989085


[[1]]$words[[11]]
[[1]]$words[[11]]$word
[1] " can"

[[1]]$words[[11]]$start
[1] 6.24

[[1]]$words[[11]]$end
[1] 6.62

[[1]]$words[[11]]$probability
[1] 0.9966052


[[1]]$words[[12]]
[[1]]$words[[12]]$word
[1] " do"

[[1]]$words[[12]]$start
[1] 6.62

[[1]]$words[[12]]$end
[1] 6.82

[[1]]$words[[12]]$probability
[1] 0.9948519


[[1]]$words[[13]]
[[1]]$words[[13]]$word
[1] " for"

[[1]]$words[[13]]$start
[1] 6.82

[[1]]$words[[13]]$end
[1] 7.08

[[1]]$words[[13]]$probability
[1] 0.9948372


[[1]]$words[[14]]
[[1]]$words[[14]]$word
[1] " you,"

[[1]]$words[[14]]$start
[1] 7.08

[[1]]$words[[14]]$end
[1] 7.42

[[1]]$words[[14]]$probability
[1] 0.9964211

2.4.4.2 Second sentence

textDF2 <- data.frame(t(transcription$segments))[1,2]
textDF2 %>% 
  head(6)
[[1]]
[[1]]$id
[1] 1

[[1]]$seek
[1] 0

[[1]]$start
[1] 7.42

[[1]]$end
[1] 10.36

[[1]]$text
[1] " ask what you can do for your country."

[[1]]$tokens
 [1] 50744  1029   437   291   393   360   337   428  1941
[10]    13 50894

[[1]]$temperature
[1] 0

[[1]]$avg_logprob
[1] -0.3966229

[[1]]$compression_ratio
[1] 1.341772

[[1]]$no_speech_prob
[1] 0.09206953

[[1]]$words
[[1]]$words[[1]]
[[1]]$words[[1]]$word
[1] " ask"

[[1]]$words[[1]]$start
[1] 7.42

[[1]]$words[[1]]$end
[1] 8.46

[[1]]$words[[1]]$probability
[1] 0.9024832


[[1]]$words[[2]]
[[1]]$words[[2]]$word
[1] " what"

[[1]]$words[[2]]$start
[1] 8.46

[[1]]$words[[2]]$end
[1] 8.76

[[1]]$words[[2]]$probability
[1] 0.9623243


[[1]]$words[[3]]
[[1]]$words[[3]]$word
[1] " you"

[[1]]$words[[3]]$start
[1] 8.76

[[1]]$words[[3]]$end
[1] 9.04

[[1]]$words[[3]]$probability
[1] 0.9864706


[[1]]$words[[4]]
[[1]]$words[[4]]$word
[1] " can"

[[1]]$words[[4]]$start
[1] 9.04

[[1]]$words[[4]]$end
[1] 9.32

[[1]]$words[[4]]$probability
[1] 0.9966297


[[1]]$words[[5]]
[[1]]$words[[5]]$word
[1] " do"

[[1]]$words[[5]]$start
[1] 9.32

[[1]]$words[[5]]$end
[1] 9.56

[[1]]$words[[5]]$probability
[1] 0.9923227


[[1]]$words[[6]]
[[1]]$words[[6]]$word
[1] " for"

[[1]]$words[[6]]$start
[1] 9.56

[[1]]$words[[6]]$end
[1] 9.78

[[1]]$words[[6]]$probability
[1] 0.9964545


[[1]]$words[[7]]
[[1]]$words[[7]]$word
[1] " your"

[[1]]$words[[7]]$start
[1] 9.78

[[1]]$words[[7]]$end
[1] 9.94

[[1]]$words[[7]]$probability
[1] 0.9963707


[[1]]$words[[8]]
[[1]]$words[[8]]$word
[1] " country."

[[1]]$words[[8]]$start
[1] 9.94

[[1]]$words[[8]]$end
[1] 10.36

[[1]]$words[[8]]$probability
[1] 0.9990544

2.4.4.3 Dataframes

textDF1 <- data.frame(textDF1)[1,]
textDF2 <- data.frame(textDF2)[1,]
textDF1
textDF2

2.4.4.4 Full sentence

2.4.4.4.1 Sentence 1
textDF1Sentence <- textDF1[,c(5,3,4)]
textDF1Sentence
2.4.4.4.2 Sentence 2
textDF2Sentence <- textDF2[,c(5,3,4)]
textDF2Sentence

2.4.4.5 Words

2.4.4.5.1 Sentence 1
textDF1Words <- textDF1[,c(11:66)]
textDF1Words <- textDF1Words %>% 
  rename_with(~str_remove(., 'words.')) %>% 
  rename(word.0 = word,
         start.0 = start,
         end.0 = end,
         probability.0 = probability)
colnames(textDF1Words) <- str_replace(colnames(textDF1Words), "\\d+", 
      function(x) sprintf("%02d", as.integer(x)))

textDF1Words
textDF1Words <- textDF1Words %>% 
  pivot_longer(cols= starts_with(c("word", "start", "end", "probability")),
               names_to = c(".value", "limit"),
               names_pattern = "(.*)(..)$") %>% 
  rename(word = word.,
         start = start.,
         end = end.,
         probability = probability.)

textDF1Words
2.4.4.5.2 Sentence 2
textDF2Words <- textDF2[,c(11:42)]
textDF2Words <- textDF2Words %>% 
  rename_with(~str_remove(., 'words.')) %>% 
  rename(word.0 = word,
         start.0 = start,
         end.0 = end,
         probability.0 = probability)
colnames(textDF2Words) <- str_replace(colnames(textDF2Words), "\\d+", 
      function(x) sprintf("%02d", as.integer(x)))

textDF2Words
textDF2Words <- textDF2Words %>% 
  pivot_longer(cols= starts_with(c("word", "start", "end", "probability")),
               names_to = c(".value", "limit"),
               names_pattern = "(.*)(..)$") %>% 
  rename(word = word.,
         start = start.,
         end = end.,
         probability = probability.)

textDF2Words

2.4.4.6 Merging

textDFFull <- rbind(textDF1Words, textDF2Words)

textDFFull
write.csv(textDFFull, "words_whisper.csv", row.names = FALSE)

2.4.5 Transform to TextGrid

reticulate::repl_python()
import csv
import textgrid # install with pip intall textgrid
# Load the CSV data
with open("words_whisper.csv",
          "r", encoding="utf-8") as f:
    reader = csv.DictReader(f,
                            delimiter="," 
                            )
    data = [row for row in reader]
    
# Create a TextGrid object
tg = textgrid.TextGrid()

# Create IntervalTier objects
transcript_tier = textgrid.IntervalTier(name="word")

# Populate the interval tiers
for row in data:
    start_time = float(row["start"])
    end_time = float(row["end"])
    transcript_tier.add(start_time, end_time, row["word"])
    
# Add the interval tiers to the TextGrid
tg.append(transcript_tier)

# Write the TextGrid to a file
with open("words_whisper.TextGrid", "w", encoding="utf-8") as f:
    tg.write(f)   
    
LS0tDQp0aXRsZTogIlVzaW5nIHdoaXNwZXIgaW4gUiINCmF1dGhvcjoNCiAgbmFtZTogIkphbGFsIEFsLVRhbWltaSINCiAgYWZmaWxpYXRpb246IFVuaXZlcnNpdMOpIFBhcmlzIENpdMOpDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNg0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogeWVzDQogICAgICBmaWdfY3JvcDogbm8NCmVkaXRvcl9vcHRpb25zOiANCiAgbWFya2Rvd246IA0KICAgIHdyYXA6IHNlbnRlbmNlDQotLS0NCg0KVXNpbmcgd2hpc3BlciBpbiBSIGJ5IEphbGFsIEFsLVRhbWltaQ0KPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCg0KRHVyaW5nIHRoaXMgc2Vzc2lvbiwgd2Ugd2lsbCB1c2UgYHdoaXNwZXJgIHRvIGF1dG9tYXRpY2FsbHkgdHJhbnNjcmliZSBhbiBhdWRpbyBmaWxlLiBXZSB3aWxsIHVzZSB0aGUgUiBwYWNrYWdlIGByZXRpY3VsYXRlYCB0byBydW4gUHl0aG9uIGNvZGUgZnJvbSB3aXRoaW4gUnN0dWRpby4gQXQgdGhlIGVuZCBvZiB0aGUgc2Vzc2lvbiwgd2Ugd2lsbCBleHBvcnQgdGhlIHRyYW5zY3JpcHRpb24gdG8gYSAgYFRleHRHcmlkYCBmaWxlIHRoYXQgY2FuIGJlIG9wZW5lZCBpbiBgUHJhYXRgIGZvciBmdXJ0aGVyIGFjb3VzdGljIGFuYWx5c2lzLg0KDQojIExvYWRpbmcgYW5kIGluc3RhbGxhdGlvbg0KDQojIyBMb2FkaW5nIHBhY2thZ2VzIA0KDQpGaXJzdCwgbWFrZSBzdXJlIHRvIGhhdmUgYWxyZWFkeSBpbnN0YWxsZWQgYW5kIGxvYWRlZCB0aGUgcGFja2FnZSBgcmV0aWN1bGF0ZWAuDQoNCg0KYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQojIyMgVXNlIHRoZSBjb2RlIGJlbG93IHRvIGNoZWNrIGlmIHlvdSBoYXZlIGFsbCByZXF1aXJlZCBwYWNrYWdlcyBpbnN0YWxsZWQuIElmIHNvbWUgYXJlIG5vdCBpbnN0YWxsZWQgYWxyZWFkeSwgdGhlIGNvZGUgYmVsb3cgd2lsbCBpbnN0YWxsIHRoZXNlLiBJZiB5b3UgaGF2ZSBhbGwgcGFja2FnZXMgaW5zdGFsbGVkLCB0aGVuIHlvdSBjb3VsZCBsb2FkIHRoZW0gd2l0aCB0aGUgc2Vjb25kIGNvZGUuDQpyZXF1aXJlZFBhY2thZ2VzID0gYygncmV0aWN1bGF0ZScsICd0aWR5dmVyc2UnKQ0KZm9yKHAgaW4gcmVxdWlyZWRQYWNrYWdlcyl7DQogIGlmKCFyZXF1aXJlKHAsY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcyhwLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICBsaWJyYXJ5KHAsY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KfQ0Kb3B0aW9ucyhnZ3JlcGVsLm1heC5vdmVybGFwcyA9IEluZikNCmBgYA0KDQoNCg0KIyMgSW5zdGFsbGF0aW9uIG9mIGB3aGlzcGVyYA0KDQoNCk1ha2Ugc3VyZSB0byB1bmNvbW1lbnQgdGhlIGxpbmUgY29kZXMgYmVsb3cgaWYgeW91IGhhdmVuJ3QgaW5zdGFsbGVkIGBvcGVuYWktd2hpc3BlcmAuIFRvIGRvIHNvLCB3ZSBuZWVkIHRvIGluc3RhbGwgaXQgdXNpbmcgdGhlIGZ1bmN0aW9uIGBweV9pbnN0YWxsYCBmcm9tIGByZXRpY3VsYXRlYC4gVGhpcyBuZWVkcyB0byBiZSBkb25lIG9uY2UsIG90aGVyd2lzZSwgdGhlIHBhY2thZ2Ugd2lsbCBiZSBpbnN0YWxsZWQgYWdhaW4gKGFuZCB0aGlzIG1heSB0YWtlIHNvbWUgdGltZSkuIA0KV2UgY291bGQgdXNlIHRoZSBuZXcgUiBwYWNrYWdlIGBhdWRpby53aGlzcGVyYHRoYXQgaXMgYnVpbHQgd2l0aGluIFIuIEhvd2V2ZXIsIHRoZSBmaXJzdCB0aW1lIHlvdSB1c2UgdGhpcyBwYWNrYWdlLCB5b3Ugd2lsbCBub3RpY2UgdGhhdCB0byB0cmFuc2NyaWJlIHRoZSBhdWRpbyBmaWxlIGJlbG93LCBpdCB3aWxsIHRha2UgeW91IG1vcmUgdGhhbiAyMCBtaW51dGVzLiBBbGwgc3Vic2VxdWVudCBydW5uaW5nIGlmIHRoZSBjb2RlIHdpbGwgYmUgcmVsYXRpdmVseSBmYXN0ZXIuIEluIG91ciBjYXNlLCB3ZSB1c2UgdGhlIG9yaWdpbmFsIGltcGxlbWVudGF0aW9uIG9mIGB3aGlzcGVyYCBpbiBQeXRob24gdGhhdCB3ZSBydW4gdmlhIFJzdHVkaW8gYW5kIHRoZSByZXRpY3VsYXRlIHBhY2thZ2UsIHdoaWNoIGlzIG11Y2ggZmFzdGVyLg0KDQoNCmBgYHtyfQ0KIyBpbnN0YWxsIHdoaXNwZXINCiMjIHB5X2luc3RhbGwoIm9wZW5haS13aGlzcGVyIiwgcGlwID0gVFJVRSkgIyByZW1lbWJlciB0byB1bmNvbW1lbnQgYW5kIGRvIHRoaXMgZmlyc3QNCmBgYA0KDQojIyBpbnN0YWxsYXRpb24gb2YgYHRleHRncmlkYA0KDQpgYGB7cHl0aG9ufQ0KIyMgIXBpcCBpbnN0YWxsIHRleHRncmlkDQpgYGANCg0KDQojIyBMb2FkaW5nIGFuZCB1c2luZyBgd2hpc3BlcmANCg0KIyMjIEF1ZGlvIGZpbGVzDQoNCldlIG9idGFpbiB2YXJpb3VzIGF1ZGlvIGZpbGVzIGZyb20gdGhlIGF1ZGlvLndoaXNwZXIgcGFja2FnZSdzIGdpdGh1YiB3ZWJzaXRlLCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2Jub3NhYy9hdWRpby53aGlzcGVyL3RyZWUvbWFzdGVyL2luc3Qvc2FtcGxlcykNCldlIHdpbGwgdXNlIHRoZSBhdWRpbyBmaWxlIGBqZmsud2F2YCB3aGljaCBpcyBhIHJlY29yZGluZyBvZiBKb2huIEYuIEtlbm5lZHkncyBpbmF1Z3VyYWwgYWRkcmVzcyBpbiBFbmdsaXNoLiANCg0KDQpgYGB7cn0NCiMgbG9hZCB3aGlzcGVyIG1vZHVsZSANCndoaXNwZXIgPC0gaW1wb3J0KCJ3aGlzcGVyIikNCmF1ZGlvIDwtICJqZmsud2F2IiAjRmlsZSBpbiBFbmdsaXNoDQpgYGANCg0KDQojIyMgRG93bmxvYWQgYW5kIGxvYWQgbW9kZWwNCg0KV2UgZG93bmxvYWQgdGhlIHdoaXNwZXIgbW9kZWwuIEluIG91ciBjYXNlLCB3ZSB1c2UgdGhlIGBiYXNlYCBtb2RlbCBhcyBpdCBwcm92aWRlcyBiZXR0ZXIgcHJlZGljdGlvbnMgdGhhbiB0aGUgYHRpbnlgIG1vZGVsDQoNCmBgYHtyfQ0Kd2hpc3Blcl9tb2RlbCA8LSAiYmFzZSIgIyBTaXplIG9mIHRoZSB0cmFuc2NyaXB0aW9uIG1vZGVsDQojIChEb3duKWxvYWQgdGhlIG1vZGVsDQpwcmludCgiTG9hZGluZyB0aGUgbW9kZWwiKQ0KbW9kZWwgPC0gd2hpc3BlciRsb2FkX21vZGVsKHdoaXNwZXJfbW9kZWwpDQpgYGANCg0KDQojIyMgVHJhbnNjcmlwdGlvbg0KDQpgYGB7cn0NCnByaW50KCJUcmFuc2NyaXB0aW9uIHN0YXJ0ZWQiKQ0KIA0KdHJhbnNjcmlwdGlvbiA8LSBtb2RlbCR0cmFuc2NyaWJlKGF1ZGlvLCBsYW5ndWFnZSA9ICJlbiIsIHZlcmJvc2UgPSBGQUxTRSwgd29yZF90aW1lc3RhbXBzID0gVFJVRSkNCiANCnByaW50KCJUcmFuc2NyaXB0aW9uIGNvbXBsZXRlZCIpDQpgYGANCg0KDQpgYGB7cn0NCnRyYW5zY3JpcHRpb24gJT4lIA0KICBoZWFkKDYpDQpgYGANCg0KIyMjIGV4cG9ydCANCg0KIyMjIyBGaXJzdCBzZW50ZW5jZQ0KDQpgYGB7cn0NCnRleHRERjEgPC0gZGF0YS5mcmFtZSh0KHRyYW5zY3JpcHRpb24kc2VnbWVudHMpKVsxLDFdDQp0ZXh0REYxICU+JSANCiAgaGVhZCg2KQ0KYGBgDQoNCiMjIyMgU2Vjb25kIHNlbnRlbmNlDQoNCmBgYHtyfQ0KdGV4dERGMiA8LSBkYXRhLmZyYW1lKHQodHJhbnNjcmlwdGlvbiRzZWdtZW50cykpWzEsMl0NCnRleHRERjIgJT4lIA0KICBoZWFkKDYpDQpgYGANCg0KDQojIyMjIERhdGFmcmFtZXMNCg0KYGBge3J9DQp0ZXh0REYxIDwtIGRhdGEuZnJhbWUodGV4dERGMSlbMSxdDQp0ZXh0REYyIDwtIGRhdGEuZnJhbWUodGV4dERGMilbMSxdDQp0ZXh0REYxDQp0ZXh0REYyDQpgYGANCg0KDQojIyMjIEZ1bGwgc2VudGVuY2UNCg0KIyMjIyMgU2VudGVuY2UgMQ0KDQpgYGB7cn0NCnRleHRERjFTZW50ZW5jZSA8LSB0ZXh0REYxWyxjKDUsMyw0KV0NCnRleHRERjFTZW50ZW5jZQ0KYGBgDQoNCiMjIyMjIFNlbnRlbmNlIDINCg0KYGBge3J9DQp0ZXh0REYyU2VudGVuY2UgPC0gdGV4dERGMlssYyg1LDMsNCldDQp0ZXh0REYyU2VudGVuY2UNCmBgYA0KDQoNCiMjIyMgV29yZHMgDQoNCiMjIyMjIFNlbnRlbmNlIDENCg0KYGBge3J9DQp0ZXh0REYxV29yZHMgPC0gdGV4dERGMVssYygxMTo2NildDQp0ZXh0REYxV29yZHMgPC0gdGV4dERGMVdvcmRzICU+JSANCiAgcmVuYW1lX3dpdGgofnN0cl9yZW1vdmUoLiwgJ3dvcmRzLicpKSAlPiUgDQogIHJlbmFtZSh3b3JkLjAgPSB3b3JkLA0KICAgICAgICAgc3RhcnQuMCA9IHN0YXJ0LA0KICAgICAgICAgZW5kLjAgPSBlbmQsDQogICAgICAgICBwcm9iYWJpbGl0eS4wID0gcHJvYmFiaWxpdHkpDQpjb2xuYW1lcyh0ZXh0REYxV29yZHMpIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKHRleHRERjFXb3JkcyksICJcXGQrIiwgDQogICAgICBmdW5jdGlvbih4KSBzcHJpbnRmKCIlMDJkIiwgYXMuaW50ZWdlcih4KSkpDQoNCnRleHRERjFXb3Jkcw0KYGBgDQoNCg0KYGBge3J9DQp0ZXh0REYxV29yZHMgPC0gdGV4dERGMVdvcmRzICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbHM9IHN0YXJ0c193aXRoKGMoIndvcmQiLCAic3RhcnQiLCAiZW5kIiwgInByb2JhYmlsaXR5IikpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSBjKCIudmFsdWUiLCAibGltaXQiKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3BhdHRlcm4gPSAiKC4qKSguLikkIikgJT4lIA0KICByZW5hbWUod29yZCA9IHdvcmQuLA0KICAgICAgICAgc3RhcnQgPSBzdGFydC4sDQogICAgICAgICBlbmQgPSBlbmQuLA0KICAgICAgICAgcHJvYmFiaWxpdHkgPSBwcm9iYWJpbGl0eS4pDQoNCnRleHRERjFXb3Jkcw0KYGBgDQoNCg0KDQojIyMjIyBTZW50ZW5jZSAyDQoNCmBgYHtyfQ0KdGV4dERGMldvcmRzIDwtIHRleHRERjJbLGMoMTE6NDIpXQ0KdGV4dERGMldvcmRzIDwtIHRleHRERjJXb3JkcyAlPiUgDQogIHJlbmFtZV93aXRoKH5zdHJfcmVtb3ZlKC4sICd3b3Jkcy4nKSkgJT4lIA0KICByZW5hbWUod29yZC4wID0gd29yZCwNCiAgICAgICAgIHN0YXJ0LjAgPSBzdGFydCwNCiAgICAgICAgIGVuZC4wID0gZW5kLA0KICAgICAgICAgcHJvYmFiaWxpdHkuMCA9IHByb2JhYmlsaXR5KQ0KY29sbmFtZXModGV4dERGMldvcmRzKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyh0ZXh0REYyV29yZHMpLCAiXFxkKyIsIA0KICAgICAgZnVuY3Rpb24oeCkgc3ByaW50ZigiJTAyZCIsIGFzLmludGVnZXIoeCkpKQ0KDQp0ZXh0REYyV29yZHMNCmBgYA0KDQoNCmBgYHtyfQ0KdGV4dERGMldvcmRzIDwtIHRleHRERjJXb3JkcyAlPiUgDQogIHBpdm90X2xvbmdlcihjb2xzPSBzdGFydHNfd2l0aChjKCJ3b3JkIiwgInN0YXJ0IiwgImVuZCIsICJwcm9iYWJpbGl0eSIpKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gYygiLnZhbHVlIiwgImxpbWl0IiksDQogICAgICAgICAgICAgICBuYW1lc19wYXR0ZXJuID0gIiguKikoLi4pJCIpICU+JSANCiAgcmVuYW1lKHdvcmQgPSB3b3JkLiwNCiAgICAgICAgIHN0YXJ0ID0gc3RhcnQuLA0KICAgICAgICAgZW5kID0gZW5kLiwNCiAgICAgICAgIHByb2JhYmlsaXR5ID0gcHJvYmFiaWxpdHkuKQ0KDQp0ZXh0REYyV29yZHMNCmBgYA0KDQoNCiMjIyMgTWVyZ2luZyANCg0KYGBge3J9DQp0ZXh0REZGdWxsIDwtIHJiaW5kKHRleHRERjFXb3JkcywgdGV4dERGMldvcmRzKQ0KDQp0ZXh0REZGdWxsDQp3cml0ZS5jc3YodGV4dERGRnVsbCwgIndvcmRzX3doaXNwZXIuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQoNCmBgYA0KDQoNCg0KDQojIyMgVHJhbnNmb3JtIHRvIFRleHRHcmlkDQoNCmBgYHtweXRob259DQppbXBvcnQgY3N2DQppbXBvcnQgdGV4dGdyaWQgIyBpbnN0YWxsIHdpdGggcGlwIGludGFsbCB0ZXh0Z3JpZA0KIyBMb2FkIHRoZSBDU1YgZGF0YQ0Kd2l0aCBvcGVuKCJ3b3Jkc193aGlzcGVyLmNzdiIsDQogICAgICAgICAgInIiLCBlbmNvZGluZz0idXRmLTgiKSBhcyBmOg0KICAgIHJlYWRlciA9IGNzdi5EaWN0UmVhZGVyKGYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsaW1pdGVyPSIsIiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQogICAgZGF0YSA9IFtyb3cgZm9yIHJvdyBpbiByZWFkZXJdDQoNCiMgQ3JlYXRlIGEgVGV4dEdyaWQgb2JqZWN0DQp0ZyA9IHRleHRncmlkLlRleHRHcmlkKCkNCg0KIyBDcmVhdGUgSW50ZXJ2YWxUaWVyIG9iamVjdHMNCnRyYW5zY3JpcHRfdGllciA9IHRleHRncmlkLkludGVydmFsVGllcihuYW1lPSJ3b3JkIikNCg0KIyBQb3B1bGF0ZSB0aGUgaW50ZXJ2YWwgdGllcnMNCmZvciByb3cgaW4gZGF0YToNCiAgICBzdGFydF90aW1lID0gZmxvYXQocm93WyJzdGFydCJdKQ0KICAgIGVuZF90aW1lID0gZmxvYXQocm93WyJlbmQiXSkNCiAgICB0cmFuc2NyaXB0X3RpZXIuYWRkKHN0YXJ0X3RpbWUsIGVuZF90aW1lLCByb3dbIndvcmQiXSkNCg0KIyBBZGQgdGhlIGludGVydmFsIHRpZXJzIHRvIHRoZSBUZXh0R3JpZA0KdGcuYXBwZW5kKHRyYW5zY3JpcHRfdGllcikNCg0KIyBXcml0ZSB0aGUgVGV4dEdyaWQgdG8gYSBmaWxlDQp3aXRoIG9wZW4oIndvcmRzX3doaXNwZXIuVGV4dEdyaWQiLCAidyIsIGVuY29kaW5nPSJ1dGYtOCIpIGFzIGY6DQogICAgdGcud3JpdGUoZikgICANCiAgICANCmBgYA0KDQoNCg==