Interesting text data?

In my search for interesting text data to work on, I had the idea to look at TED talks over recent years that have included the topic of AI (artificial intelligence). In further posts I will be analysing this data, but this post is about the code I used to collect the data in the first place.

# Some packages

library(tidyverse)
library(data.table)
library(rvest)

# Some handy dandy functions

patientWaiter<- function(minutes_wait){
  # Ask R to wait a number of minutes before making another request to the server
  for (minute in 1:minutes_wait){
    cat("\n\n",minute, " :")
    for (second in 1:60) {
      Sys.sleep(1)
      cat("\t",second)
    }
  }
} 

get_talk_links<- function(pg){
  # Extract TED Talk links from the TED query result page
  alllinks<-  pg %>% html_nodes("a") %>% html_attr("href")
  goodlinks<- alllinks[alllinks %like% "/talks/"] %>% unique()
  return(goodlinks)
}

extract_meta_table<- function(pg){
  # Extract some information contained in the webpage under the meta tags
  all_meta_attrs <- unique(unlist(lapply(lapply(pg %>% html_nodes("meta"), html_attrs), names)))
  dat <- data.frame(lapply(all_meta_attrs, function(x) {
    pg %>% html_nodes("meta") %>% html_attr(x)
  }),stringsAsFactors = FALSE)
  colnames(dat) <- all_meta_attrs
  N<- nrow(dat)
  dat$RowNum<- 1:N
  return(dat)
}

newmatrix<- function(nr,nc,thing=NA) {
  # Create an empty data frame
  newmat<- matrix(rep(thing,nr*nc),nrow=nr,ncol=nc)
  newmat<- data.frame(newmat)
  return(newmat)
  
}

Let’s begin

Initially I did a manual search for AI-related talks from within the TED talks website, and made a note of the urls that I could see…


# Initial search query for AI-related TED Talks

STEM<-      "https://www.ted.com"
searchurl<- "https://www.ted.com/talks?sort=relevance&topics%5B%5D=Technology&language=en&q=AI"

By downloading the first page of search results (search_page1), I can see that this page contains links to the search results (individual talks) as well as links to further pages of search results (otherpages).


search_page1<- read_html(searchurl)

alllinks<-     search_page1 %>% html_nodes("a") %>% html_attr("href")
goodlinks<-    alllinks[alllinks %like% "/talks/"] %>% unique()

otherpages<-   alllinks[alllinks %like% "/talks\\?"] %>% print()
[1] "/talks?language=en&page=2&q=AI&sort=relevance&topics%5B%5D=Technology"
[2] "/talks?language=en&page=3&q=AI&sort=relevance&topics%5B%5D=Technology"
[3] "/talks?language=en&page=2&q=AI&sort=relevance&topics%5B%5D=Technology"
# [1] "/talks?language=en&page=2&q=AI&sort=relevance&topics%5B%5D=Technology"
# [2] "/talks?language=en&page=3&q=AI&sort=relevance&topics%5B%5D=Technology"
# [3] "/talks?language=en&page=2&q=AI&sort=relevance&topics%5B%5D=Technology"

Compile a list of sites to visit

From here I would like to put together a list of links for talks that I might want to download. I have used the get_talk_links function that I defined above, and then collected these into a single list of links. I could have done this part with the map function.

search_page2<- read_html(paste0(STEM, otherpages[1]))
search_page3<- read_html(paste0(STEM, otherpages[2]))
search_page4<- read_html(paste0(STEM, otherpages[3]))

link_list<- list()

link_list[[1]]<- get_talk_links(search_page1)
link_list[[2]]<- get_talk_links(search_page2)
link_list[[3]]<- get_talk_links(search_page3)
link_list[[4]]<- get_talk_links(search_page4)


talk_links<- unlist(link_list)
saveRDS(talk_links, "talk_links.rds")

Get ready to Scrape!

I can see that the ordinary links we have collected take us to the main TED video page. In order to visit the webpage that contains the text transcript, we have to tweak the links slightly…

# Convert ordinary links into links that will lead to the talk transcript
transcript_links<- map_chr(talk_links,~paste0(STEM,str_replace(.x, "\\?language=en","/transcript\\?language=en")))

Set up some empty objects into which to collect the data. I know preference varies quite widely on this point, but this time the data is quite well-behaved so a dataframe should be OK. I also like to save the raw html pages in a list in case I want to go back and extract any additional information later.

# Set up an empty data frame to hold the results
outdf<- newmatrix(length(transcript_links),9)
names(outdf)<- c("ID","date","author","long_title","keywords","description","title","author2","full_text")

# Set up a one-row version of the dataframe that can be easily collapsed
res<- newmatrix(1,9)
names(res)<- names(outdf)

# Set up an empty list to hold the raw html pages in case our parsing goes awry
page_list<- list()

Visit and Scrape

Now we’re ready to visit each of the talk transcript web pages in turn and collect the information we’re interested in.

for  (i in 1:length(transcript_links)){

    random_minutes<-  sample(2:6,1)  # Choose a random waiting time
    link<-            transcript_links[i]
    pg<-              read_html(link)
    page_list[[i]]<-  pg
    pnodes<-          pg %>% html_nodes("p") %>% html_text() %>% stripWhitespace() %>% trimws()
    res$full_text<-   str_c(pnodes, collapse=" ")

    meta<- extract_meta_table(pg)
    meta$itemprop[is.na(meta$itemprop)]<- ""
    meta$name[is.na(meta$name)]<- ""

    res$ID<-          i
    res$date<-        meta$content[meta$itemprop== "uploadDate"]
    res$author<-      meta$content[meta$name== "author"]
    res$long_title<-  meta$content[meta$name== "title"]
    res$keywords<-    meta$content[meta$name== "keywords"]
    res$description<- meta$content[meta$itemprop== "description"]
    res$title<-       meta$content[meta$itemprop== "name"][1]
    res$author2<-     meta$content[meta$itemprop== "name"][2]

    outdf[i,]<- res

    # I like to "save as I go" in case the scrape is interrupted part-way through
    saveRDS(outdf,"outdf.rds")
    outdf$full_text<- str_sub(outdf$full_text,1,32000) # Truncate for the Excel version
    writexl::write_xlsx(outdf,"TED_output.xlsx")
    saveRDS(page_list,"page_list.rds")

    # Some console output
    cat("\n\nWorking on talk number",i,": ",res$title, "-----------------------------------\n")
    patientWaiter(random_minutes)

}

Tidying up the output

We have now completed the main scraping task, and the data now exists in a local data frame and several saved copies. We finish up by doing some basic tidying…

# Remove duplicate talks
df<- df %>% filter(duplicated(title))  # 88 talks
 
# All transcripts carry the following sentence. This can be removed.

endTag<- "TED.com translations are made possible by volunteer translators. Learn more about the Open Translation Project. © TED Conferences, LLC. All rights reserved."

df$full_text<- str_remove_all(df$full_text, endTag)

# Convert to a date object
df$date<- lubridate::ymd(str_sub(df$date,1,10))

saveRDS(df,"TED_data.rds")

What have we collected?

Further analyses will be conducted in future posts, but just as a summary, let’s see how the talks we have collected are spread across recent years. We can see that while TED Talks about AI have been appearing for more than 13 years, there has been a dramatic increase in AI as a topic if interest in the past 3 to 4 years. We can see fewer talks from 2020 as we are only half-way through the year, and there may well be fewer TED talks this year due to the COVID-19 pandemic.

df %>% mutate(Year= year(date)) %>% count(Year) %>%
  ggplot(aes(x= Year, y=n)) +
  geom_col(fill= "lightblue", color= "grey50", size= 0.2) +
  geom_text(aes(label=n), vjust=-0.5, hjust= 0.4) +
  scale_y_continuous(limits = c(0,21), expand = c(0, 0)) +
  scale_x_continuous(breaks= 2007:2020) +
  theme_bw() + 
  labs(title= "TED Talks about AI: Count by Year", x= "\nYear", y=NULL) +
  theme(axis.text.x = element_text(angle= 90, hjust= 1,size=9),
        panel.grid = element_blank(),
        axis.text.y= element_blank(),
        axis.line.y = element_blank(),
        axis.ticks = element_blank(),
        axis.line.x = element_line(color= "grey50", size= 0.4),
        panel.border = element_blank())

NA
NA
LS0tCnRpdGxlOiAiVEVEIFRhbGtzIGluIEFJOiBTY3JhcGluZyB0aGUgRGF0YSIKb3V0cHV0OiBodG1sX25vdGVib29rCmRhdGU6ICIyMDIwLTA3LTAxIgphdXRob3I6ICJDZWwgTWNDcmFja2VuIgotLS0KCiMjIyBJbnRlcmVzdGluZyB0ZXh0IGRhdGE/CgpJbiBteSBzZWFyY2ggZm9yIGludGVyZXN0aW5nIHRleHQgZGF0YSB0byB3b3JrIG9uLCBJIGhhZCB0aGUgaWRlYSB0byBsb29rIGF0IFRFRCB0YWxrcyBvdmVyIHJlY2VudCB5ZWFycyB0aGF0IGhhdmUgaW5jbHVkZWQgdGhlIHRvcGljIG9mIEFJIChhcnRpZmljaWFsIGludGVsbGlnZW5jZSkuIEluIGZ1cnRoZXIgcG9zdHMgSSB3aWxsIGJlIGFuYWx5c2luZyB0aGlzIGRhdGEsIGJ1dCB0aGlzIHBvc3QgaXMgYWJvdXQgdGhlIGNvZGUgSSB1c2VkIHRvIGNvbGxlY3QgdGhlIGRhdGEgaW4gdGhlIGZpcnN0IHBsYWNlLgoKIVtmcm9tIGh0dHBzOi8vd3d3LnRlZC5jb20vdGFsa3MvZ3JhZHlfYm9vY2hfZG9uX3RfZmVhcl9zdXBlcmludGVsbGlnZW50X2FpL3RyYW5zY3JpcHQ/bGFuZ3VhZ2U9ZW5dKC9Vc2Vycy9DZWxlc3RlL0Rlc2t0b3AvVEVEX3NjcmVlbnNob3QucG5nKQoKYGBge3IgfQojIFNvbWUgcGFja2FnZXMKCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkocnZlc3QpCgojIFNvbWUgaGFuZHkgZGFuZHkgZnVuY3Rpb25zCgpwYXRpZW50V2FpdGVyPC0gZnVuY3Rpb24obWludXRlc193YWl0KXsKICAjIEFzayBSIHRvIHdhaXQgYSBudW1iZXIgb2YgbWludXRlcyBiZWZvcmUgbWFraW5nIGFub3RoZXIgcmVxdWVzdCB0byB0aGUgc2VydmVyCiAgZm9yIChtaW51dGUgaW4gMTptaW51dGVzX3dhaXQpewogICAgY2F0KCJcblxuIixtaW51dGUsICIgOiIpCiAgICBmb3IgKHNlY29uZCBpbiAxOjYwKSB7CiAgICAgIFN5cy5zbGVlcCgxKQogICAgICBjYXQoIlx0IixzZWNvbmQpCiAgICB9CiAgfQp9IAoKZ2V0X3RhbGtfbGlua3M8LSBmdW5jdGlvbihwZyl7CiAgIyBFeHRyYWN0IFRFRCBUYWxrIGxpbmtzIGZyb20gdGhlIFRFRCBxdWVyeSByZXN1bHQgcGFnZQogIGFsbGxpbmtzPC0gIHBnICU+JSBodG1sX25vZGVzKCJhIikgJT4lIGh0bWxfYXR0cigiaHJlZiIpCiAgZ29vZGxpbmtzPC0gYWxsbGlua3NbYWxsbGlua3MgJWxpa2UlICIvdGFsa3MvIl0gJT4lIHVuaXF1ZSgpCiAgcmV0dXJuKGdvb2RsaW5rcykKfQoKZXh0cmFjdF9tZXRhX3RhYmxlPC0gZnVuY3Rpb24ocGcpewogICMgRXh0cmFjdCBzb21lIGluZm9ybWF0aW9uIGNvbnRhaW5lZCBpbiB0aGUgd2VicGFnZSB1bmRlciB0aGUgbWV0YSB0YWdzCiAgYWxsX21ldGFfYXR0cnMgPC0gdW5pcXVlKHVubGlzdChsYXBwbHkobGFwcGx5KHBnICU+JSBodG1sX25vZGVzKCJtZXRhIiksIGh0bWxfYXR0cnMpLCBuYW1lcykpKQogIGRhdCA8LSBkYXRhLmZyYW1lKGxhcHBseShhbGxfbWV0YV9hdHRycywgZnVuY3Rpb24oeCkgewogICAgcGcgJT4lIGh0bWxfbm9kZXMoIm1ldGEiKSAlPiUgaHRtbF9hdHRyKHgpCiAgfSksc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogIGNvbG5hbWVzKGRhdCkgPC0gYWxsX21ldGFfYXR0cnMKICBOPC0gbnJvdyhkYXQpCiAgZGF0JFJvd051bTwtIDE6TgogIHJldHVybihkYXQpCn0KCm5ld21hdHJpeDwtIGZ1bmN0aW9uKG5yLG5jLHRoaW5nPU5BKSB7CiAgIyBDcmVhdGUgYW4gZW1wdHkgZGF0YSBmcmFtZQogIG5ld21hdDwtIG1hdHJpeChyZXAodGhpbmcsbnIqbmMpLG5yb3c9bnIsbmNvbD1uYykKICBuZXdtYXQ8LSBkYXRhLmZyYW1lKG5ld21hdCkKICByZXR1cm4obmV3bWF0KQogIAp9CgpgYGAKCgoKIyMjIExldCdzIGJlZ2luCgpJbml0aWFsbHkgSSBkaWQgYSBtYW51YWwgc2VhcmNoIGZvciBBSS1yZWxhdGVkIHRhbGtzIGZyb20gd2l0aGluIHRoZSBURUQgdGFsa3Mgd2Vic2l0ZSwgYW5kIG1hZGUgYSBub3RlIG9mIHRoZSB1cmxzIHRoYXQgSSBjb3VsZCBzZWUuLi4KCgpgYGB7cn0KIyBJbml0aWFsIHNlYXJjaCBxdWVyeSBmb3IgQUktcmVsYXRlZCBURUQgVGFsa3MKClNURU08LSAgICAgICJodHRwczovL3d3dy50ZWQuY29tIgpzZWFyY2h1cmw8LSAiaHR0cHM6Ly93d3cudGVkLmNvbS90YWxrcz9zb3J0PXJlbGV2YW5jZSZ0b3BpY3MlNUIlNUQ9VGVjaG5vbG9neSZsYW5ndWFnZT1lbiZxPUFJIgoKYGBgCgpCeSBkb3dubG9hZGluZyB0aGUgZmlyc3QgcGFnZSBvZiBzZWFyY2ggcmVzdWx0cyAoYHNlYXJjaF9wYWdlMWApLCBJIGNhbiBzZWUgdGhhdCB0aGlzIHBhZ2UgY29udGFpbnMgbGlua3MgdG8gdGhlIHNlYXJjaCByZXN1bHRzIChpbmRpdmlkdWFsIHRhbGtzKSBhcyB3ZWxsIGFzIGxpbmtzIHRvIGZ1cnRoZXIgcGFnZXMgb2Ygc2VhcmNoIHJlc3VsdHMgKGBvdGhlcnBhZ2VzYCkuCgoKYGBge3J9CnNlYXJjaF9wYWdlMTwtIHJlYWRfaHRtbChzZWFyY2h1cmwpCgphbGxsaW5rczwtICAgICBzZWFyY2hfcGFnZTEgJT4lIGh0bWxfbm9kZXMoImEiKSAlPiUgaHRtbF9hdHRyKCJocmVmIikKZ29vZGxpbmtzPC0gICAgYWxsbGlua3NbYWxsbGlua3MgJWxpa2UlICIvdGFsa3MvIl0gJT4lIHVuaXF1ZSgpCgpvdGhlcnBhZ2VzPC0gICBhbGxsaW5rc1thbGxsaW5rcyAlbGlrZSUgIi90YWxrc1xcPyJdICU+JSBwcmludCgpCmBgYAoKIyMjIENvbXBpbGUgYSBsaXN0IG9mIHNpdGVzIHRvIHZpc2l0CgpGcm9tIGhlcmUgSSB3b3VsZCBsaWtlIHRvIHB1dCB0b2dldGhlciBhIGxpc3Qgb2YgbGlua3MgZm9yIHRhbGtzIHRoYXQgSSBtaWdodCB3YW50IHRvIGRvd25sb2FkLiAgSSBoYXZlIHVzZWQgdGhlIGBnZXRfdGFsa19saW5rc2AgZnVuY3Rpb24gdGhhdCBJIGRlZmluZWQgYWJvdmUsIGFuZCB0aGVuIGNvbGxlY3RlZCB0aGVzZSBpbnRvIGEgc2luZ2xlIGxpc3Qgb2YgbGlua3MuICBJIGNvdWxkIGhhdmUgZG9uZSB0aGlzIHBhcnQgd2l0aCB0aGUgYG1hcGAgZnVuY3Rpb24uCgpgYGB7cn0Kc2VhcmNoX3BhZ2UyPC0gcmVhZF9odG1sKHBhc3RlMChTVEVNLCBvdGhlcnBhZ2VzWzFdKSkKc2VhcmNoX3BhZ2UzPC0gcmVhZF9odG1sKHBhc3RlMChTVEVNLCBvdGhlcnBhZ2VzWzJdKSkKc2VhcmNoX3BhZ2U0PC0gcmVhZF9odG1sKHBhc3RlMChTVEVNLCBvdGhlcnBhZ2VzWzNdKSkKCmxpbmtfbGlzdDwtIGxpc3QoKQoKbGlua19saXN0W1sxXV08LSBnZXRfdGFsa19saW5rcyhzZWFyY2hfcGFnZTEpCmxpbmtfbGlzdFtbMl1dPC0gZ2V0X3RhbGtfbGlua3Moc2VhcmNoX3BhZ2UyKQpsaW5rX2xpc3RbWzNdXTwtIGdldF90YWxrX2xpbmtzKHNlYXJjaF9wYWdlMykKbGlua19saXN0W1s0XV08LSBnZXRfdGFsa19saW5rcyhzZWFyY2hfcGFnZTQpCgoKdGFsa19saW5rczwtIHVubGlzdChsaW5rX2xpc3QpCnNhdmVSRFModGFsa19saW5rcywgInRhbGtfbGlua3MucmRzIikKYGBgCgoKIyMjIEdldCByZWFkeSB0byBTY3JhcGUhCgpJIGNhbiBzZWUgdGhhdCB0aGUgb3JkaW5hcnkgbGlua3Mgd2UgaGF2ZSBjb2xsZWN0ZWQgdGFrZSB1cyB0byB0aGUgbWFpbiBURUQgdmlkZW8gcGFnZS4gSW4gb3JkZXIgdG8gdmlzaXQgdGhlIHdlYnBhZ2UgdGhhdCBjb250YWlucyB0aGUgdGV4dCB0cmFuc2NyaXB0LCB3ZSBoYXZlIHRvIHR3ZWFrIHRoZSBsaW5rcyBzbGlnaHRseS4uLgoKYGBge3J9CiMgQ29udmVydCBvcmRpbmFyeSBsaW5rcyBpbnRvIGxpbmtzIHRoYXQgd2lsbCBsZWFkIHRvIHRoZSB0YWxrIHRyYW5zY3JpcHQKdHJhbnNjcmlwdF9saW5rczwtIG1hcF9jaHIodGFsa19saW5rcyx+cGFzdGUwKFNURU0sc3RyX3JlcGxhY2UoLngsICJcXD9sYW5ndWFnZT1lbiIsIi90cmFuc2NyaXB0XFw/bGFuZ3VhZ2U9ZW4iKSkpCgpgYGAKClNldCB1cCBzb21lIGVtcHR5IG9iamVjdHMgaW50byB3aGljaCB0byBjb2xsZWN0IHRoZSBkYXRhLiBJIGtub3cgcHJlZmVyZW5jZSB2YXJpZXMgcXVpdGUgd2lkZWx5IG9uIHRoaXMgcG9pbnQsIGJ1dCB0aGlzIHRpbWUgdGhlIGRhdGEgaXMgcXVpdGUgd2VsbC1iZWhhdmVkIHNvIGEgZGF0YWZyYW1lIHNob3VsZCBiZSBPSy4gIEkgYWxzbyBsaWtlIHRvIHNhdmUgdGhlIHJhdyBodG1sIHBhZ2VzIGluIGEgbGlzdCBpbiBjYXNlIEkgd2FudCB0byBnbyBiYWNrIGFuZCBleHRyYWN0IGFueSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGxhdGVyLgoKYGBge3J9CiMgU2V0IHVwIGFuIGVtcHR5IGRhdGEgZnJhbWUgdG8gaG9sZCB0aGUgcmVzdWx0cwpvdXRkZjwtIG5ld21hdHJpeChsZW5ndGgodHJhbnNjcmlwdF9saW5rcyksOSkKbmFtZXMob3V0ZGYpPC0gYygiSUQiLCJkYXRlIiwiYXV0aG9yIiwibG9uZ190aXRsZSIsImtleXdvcmRzIiwiZGVzY3JpcHRpb24iLCJ0aXRsZSIsImF1dGhvcjIiLCJmdWxsX3RleHQiKQoKIyBTZXQgdXAgYSBvbmUtcm93IHZlcnNpb24gb2YgdGhlIGRhdGFmcmFtZSB0aGF0IGNhbiBiZSBlYXNpbHkgY29sbGFwc2VkCnJlczwtIG5ld21hdHJpeCgxLDkpCm5hbWVzKHJlcyk8LSBuYW1lcyhvdXRkZikKCiMgU2V0IHVwIGFuIGVtcHR5IGxpc3QgdG8gaG9sZCB0aGUgcmF3IGh0bWwgcGFnZXMgaW4gY2FzZSBvdXIgcGFyc2luZyBnb2VzIGF3cnkKcGFnZV9saXN0PC0gbGlzdCgpCmBgYAoKIyMjIFZpc2l0IGFuZCBTY3JhcGUKTm93IHdlJ3JlIHJlYWR5IHRvIHZpc2l0IGVhY2ggb2YgdGhlIHRhbGsgdHJhbnNjcmlwdCB3ZWIgcGFnZXMgaW4gdHVybiBhbmQgY29sbGVjdCB0aGUgaW5mb3JtYXRpb24gd2UncmUgaW50ZXJlc3RlZCBpbi4KCmBgYHtyfQpmb3IgIChpIGluIDE6bGVuZ3RoKHRyYW5zY3JpcHRfbGlua3MpKXsKCiAgICByYW5kb21fbWludXRlczwtICBzYW1wbGUoMjo2LDEpICAjIENob29zZSBhIHJhbmRvbSB3YWl0aW5nIHRpbWUKICAgIGxpbms8LSAgICAgICAgICAgIHRyYW5zY3JpcHRfbGlua3NbaV0KICAgIHBnPC0gICAgICAgICAgICAgIHJlYWRfaHRtbChsaW5rKQogICAgcGFnZV9saXN0W1tpXV08LSAgcGcKICAgIHBub2RlczwtICAgICAgICAgIHBnICU+JSBodG1sX25vZGVzKCJwIikgJT4lIGh0bWxfdGV4dCgpICU+JSBzdHJpcFdoaXRlc3BhY2UoKSAlPiUgdHJpbXdzKCkKICAgIHJlcyRmdWxsX3RleHQ8LSAgIHN0cl9jKHBub2RlcywgY29sbGFwc2U9IiAiKQoKICAgIG1ldGE8LSBleHRyYWN0X21ldGFfdGFibGUocGcpCiAgICBtZXRhJGl0ZW1wcm9wW2lzLm5hKG1ldGEkaXRlbXByb3ApXTwtICIiCiAgICBtZXRhJG5hbWVbaXMubmEobWV0YSRuYW1lKV08LSAiIgoKICAgIHJlcyRJRDwtICAgICAgICAgIGkKICAgIHJlcyRkYXRlPC0gICAgICAgIG1ldGEkY29udGVudFttZXRhJGl0ZW1wcm9wPT0gInVwbG9hZERhdGUiXQogICAgcmVzJGF1dGhvcjwtICAgICAgbWV0YSRjb250ZW50W21ldGEkbmFtZT09ICJhdXRob3IiXQogICAgcmVzJGxvbmdfdGl0bGU8LSAgbWV0YSRjb250ZW50W21ldGEkbmFtZT09ICJ0aXRsZSJdCiAgICByZXMka2V5d29yZHM8LSAgICBtZXRhJGNvbnRlbnRbbWV0YSRuYW1lPT0gImtleXdvcmRzIl0KICAgIHJlcyRkZXNjcmlwdGlvbjwtIG1ldGEkY29udGVudFttZXRhJGl0ZW1wcm9wPT0gImRlc2NyaXB0aW9uIl0KICAgIHJlcyR0aXRsZTwtICAgICAgIG1ldGEkY29udGVudFttZXRhJGl0ZW1wcm9wPT0gIm5hbWUiXVsxXQogICAgcmVzJGF1dGhvcjI8LSAgICAgbWV0YSRjb250ZW50W21ldGEkaXRlbXByb3A9PSAibmFtZSJdWzJdCgogICAgb3V0ZGZbaSxdPC0gcmVzCgogICAgIyBJIGxpa2UgdG8gInNhdmUgYXMgSSBnbyIgaW4gY2FzZSB0aGUgc2NyYXBlIGlzIGludGVycnVwdGVkIHBhcnQtd2F5IHRocm91Z2gKICAgIHNhdmVSRFMob3V0ZGYsIm91dGRmLnJkcyIpCiAgICBvdXRkZiRmdWxsX3RleHQ8LSBzdHJfc3ViKG91dGRmJGZ1bGxfdGV4dCwxLDMyMDAwKSAjIFRydW5jYXRlIGZvciB0aGUgRXhjZWwgdmVyc2lvbgogICAgd3JpdGV4bDo6d3JpdGVfeGxzeChvdXRkZiwiVEVEX291dHB1dC54bHN4IikKICAgIHNhdmVSRFMocGFnZV9saXN0LCJwYWdlX2xpc3QucmRzIikKCiAgICAjIFNvbWUgY29uc29sZSBvdXRwdXQKICAgIGNhdCgiXG5cbldvcmtpbmcgb24gdGFsayBudW1iZXIiLGksIjogIixyZXMkdGl0bGUsICItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuIikKICAgIHBhdGllbnRXYWl0ZXIocmFuZG9tX21pbnV0ZXMpCgp9CgpgYGAKCiMjIyBUaWR5aW5nIHVwIHRoZSBvdXRwdXQKV2UgaGF2ZSBub3cgY29tcGxldGVkIHRoZSBtYWluIHNjcmFwaW5nIHRhc2ssIGFuZCB0aGUgZGF0YSBub3cgZXhpc3RzIGluIGEgbG9jYWwgZGF0YSBmcmFtZSBhbmQgc2V2ZXJhbCBzYXZlZCBjb3BpZXMuICBXZSBmaW5pc2ggdXAgYnkgZG9pbmcgc29tZSBiYXNpYyB0aWR5aW5nLi4uCgpgYGB7cn0KIyBSZW1vdmUgZHVwbGljYXRlIHRhbGtzCmRmPC0gZGYgJT4lIGZpbHRlcihkdXBsaWNhdGVkKHRpdGxlKSkgICMgODggdGFsa3MKIAojIEFsbCB0cmFuc2NyaXB0cyBjYXJyeSB0aGUgZm9sbG93aW5nIHNlbnRlbmNlLiBUaGlzIGNhbiBiZSByZW1vdmVkLgoKZW5kVGFnPC0gIlRFRC5jb20gdHJhbnNsYXRpb25zIGFyZSBtYWRlIHBvc3NpYmxlIGJ5IHZvbHVudGVlciB0cmFuc2xhdG9ycy4gTGVhcm4gbW9yZSBhYm91dCB0aGUgT3BlbiBUcmFuc2xhdGlvbiBQcm9qZWN0LiDCqSBURUQgQ29uZmVyZW5jZXMsIExMQy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4iCgpkZiRmdWxsX3RleHQ8LSBzdHJfcmVtb3ZlX2FsbChkZiRmdWxsX3RleHQsIGVuZFRhZykKCiMgQ29udmVydCB0byBhIGRhdGUgb2JqZWN0CmRmJGRhdGU8LSBsdWJyaWRhdGU6OnltZChzdHJfc3ViKGRmJGRhdGUsMSwxMCkpCgpzYXZlUkRTKGRmLCJURURfZGF0YS5yZHMiKQpgYGAKCiMjIyBXaGF0IGhhdmUgd2UgY29sbGVjdGVkPwoKRnVydGhlciBhbmFseXNlcyB3aWxsIGJlIGNvbmR1Y3RlZCBpbiBmdXR1cmUgcG9zdHMsIGJ1dCBqdXN0IGFzIGEgc3VtbWFyeSwgbGV0J3Mgc2VlIGhvdyB0aGUgdGFsa3Mgd2UgaGF2ZSBjb2xsZWN0ZWQgYXJlIHNwcmVhZCBhY3Jvc3MgcmVjZW50IHllYXJzLiAgV2UgY2FuIHNlZSB0aGF0IHdoaWxlIFRFRCBUYWxrcyBhYm91dCBBSSBoYXZlIGJlZW4gYXBwZWFyaW5nIGZvciBtb3JlIHRoYW4gMTMgeWVhcnMsIHRoZXJlIGhhcyBiZWVuIGEgZHJhbWF0aWMgaW5jcmVhc2UgaW4gQUkgYXMgYSB0b3BpYyBpZiBpbnRlcmVzdCBpbiB0aGUgcGFzdCAzIHRvIDQgeWVhcnMuICBXZSBjYW4gc2VlIGZld2VyIHRhbGtzIGZyb20gMjAyMCBhcyB3ZSBhcmUgb25seSBoYWxmLXdheSB0aHJvdWdoIHRoZSB5ZWFyLCBhbmQgdGhlcmUgbWF5IHdlbGwgYmUgZmV3ZXIgVEVEIHRhbGtzIHRoaXMgeWVhciBkdWUgdG8gdGhlIENPVklELTE5IHBhbmRlbWljLgoKYGBge3J9CiMgQmFycGxvdCBvZiBjb3VudHMgYnkgeWVhcgoKZGYgJT4lIG11dGF0ZShZZWFyPSB5ZWFyKGRhdGUpKSAlPiUgY291bnQoWWVhcikgJT4lCiAgZ2dwbG90KGFlcyh4PSBZZWFyLCB5PW4pKSArCiAgZ2VvbV9jb2woZmlsbD0gImxpZ2h0Ymx1ZSIsIGNvbG9yPSAiZ3JleTUwIiwgc2l6ZT0gMC4yKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uKSwgdmp1c3Q9IC0wLjUsIGhqdXN0PSAwLjQpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAyMSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPSAyMDA3OjIwMjApICsKICB0aGVtZV9idygpICsgCiAgbGFicyh0aXRsZT0gIlRFRCBUYWxrcyBhYm91dCBBSTogQ291bnQgYnkgWWVhciIsIHg9ICJcblllYXIiLCB5PU5VTEwpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0gOTAsIGhqdXN0PSAxLCBzaXplPTkpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMubGluZS54ID0gZWxlbWVudF9saW5lKGNvbG9yPSAiZ3JleTUwIiwgc2l6ZT0gMC40KSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpCiAgCiAgCmBgYA==