library(geniusr)                         # This package gets lyrics
library(tidyverse)
library(tidytext)
library(wordcloud2)

Using Genius to get lyrics

Genius.com is a site that has music lyrics. It is used by music services like Apple Music and Spotify. The package geniusr is an R interface to the Genius site, and brings lyrics into R. Let’s work through the geniusr API, starting with signing up for access:

  1. Go here and create a login and app https://genius.com/api-clients/new
  2. Go here and generate a client acces token https://genius.com/api-clients
  3. Call genius_token() and then enter the client access token in the console when prompted

genius_token()
[1] "a4tjUC6-o2Q2woojDmmId0UtiQNH6kLYA-S6yNW2SHNIcyxLZWkxvMzXp5-3yJxY"

Once you get that working, let’s work through how to get information from Genius.

  1. get album id. search for song with unique name on the album. find the song_id that matches the correct correct song
search_song("like a rolling stone")
  1. Put the song_id number into the parentheses below to get more info about it.
get_song_meta(54784)
  1. One of the columns above is album_id. Enter that number into the parentheses below to get all of the other tracks on the album. this one we have to save as a file.
highway_tracks <- scrape_tracklist(13573)
argument is not an atomic vector; coercing
highway_tracks
  1. get the lyrics. map_df() repeatedly applies, or maps, one function (scrape_lyrics_url) to each line of highway_tracks$song_lyrics_url, and puts the results into highway_lyrics.
highway_lyrics <- map_df(highway_tracks$song_lyrics_url, scrape_lyrics_url)
highway_lyrics
NA

Preparing lyrics for analysis

Now we need to use unnest_tokens() to separate the words one to a row, make them lowercase, and remove the punctuation. Put that into a new data frame called highway_words. Note that the column with the lyrics is called ‘line.’

highway_words <- highway_lyrics %>%
  unnest_tokens(word, line) %>% 
  select(song_name, word)

highway_words

Word counts and word cloud

To get a sense of the common words on the album, pipe highway_words into count(word, sort = T)

highway_words %>% 
  count( word, sort = T)

But that has a lot of simple and unimportant words at the top of the list. Let’s get rid of stop words first. This uses anti_join() to remove the stopwords from our words:

highway_words %>% 
  anti_join(get_stopwords()) %>% 
  count(word, sort = T)
Joining, by = "word"

Create a word cloud by copy-pasting the code above and adding the following:

  1. Add the line top_n(200) to limit the number of words in the cloud to 200
  2. add the line wordcloud2(size = .5)
highway_words %>% 
  anti_join(get_stopwords()) %>% 
  count(word, sort = T) %>% 
  top_n(200) %>% 
  wordcloud2(size = .5)
Joining, by = "word"
Selecting by n

Sentiment analysis

Sentiment is another word for opinion or emotion. Sentiment analysis is the analysis of text for the opinions or emotions it contains. It is often used in marketing research to see customers’ opinions in the reviews they leave on sites like Amazon.

Sentiment analysis uses dictionaries that contain words related to sentiments and then compares the dictionary to the text being analyzed. Let’s look at one of the sentiment dictionaries, called bing after one of the researchers who developed it.

bing <- get_sentiments("bing")
bing

We need to join the sentiment dictionary with our lyrics. inner_join() will retain all of the words that are present in both.

highway_words %>% 
  inner_join(bing) %>% 
  count(word, sentiment, sort = TRUE)
Joining, by = "word"

Take the above code and pipe it into the following to create a graph of the table above.

highway_words %>% 
  inner_join(bing) %>% 
  count(word, sentiment, sort = TRUE) %>% 
  group_by(sentiment) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(vars(sentiment), scales = "free") +
  labs(y = "Bob Dylan's Highway 61 album: Words that contribute the most to each sentiment",
       x = NULL) +
  scale_fill_viridis_d() +
  coord_flip() +
  theme_minimal()
Joining, by = "word"
Selecting by n

word clouds with each sentiment:

highway_words %>% 
  inner_join(bing) %>% 
  count(word, sentiment, sort = TRUE) %>% 
  filter(sentiment == "positive") %>%
  select(word, n) %>% 
  wordcloud2()
Joining, by = "word"

highway_words %>% 
  inner_join(bing) %>% 
  count(word, sentiment, sort = TRUE) %>% 
  filter(sentiment == "negative") %>%
  select(word, n) %>% 
  wordcloud2()
Joining, by = "word"

NA

Other sentiment dictionaries

Another sentiment dictionary is nrc, which stands for National Research Council, a government agency in Canada similar to the National Science Foundation in the US.

nrc <- get_sentiments("nrc")
nrc

Notice that this dictionary has several different sentiments, not just positive and negative. You can view all the different sentiments by piping nrc into distinct(sentiment). Do that below:

nrc %>% 
  distinct(sentiment)

Conduct the same analysis as above - the tables and graphs of words that contribute to each sentiment, but do it for this new lexicon. Hint: When you create the graph, try a smaller value in top_n(), like 3 or 5.

highway_words %>% 
  inner_join(nrc) %>% 
  count(word, sentiment, sort = TRUE) %>% 
  group_by(sentiment) %>%
  top_n(3) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(vars(sentiment), scales = "free") +
  labs(y = "Bob Dylan's Highway 61 album: Words that contribute the most to each sentiment",
       x = NULL) +
  scale_fill_viridis_d() +
  coord_flip() +
  theme_minimal()
Joining, by = "word"
Selecting by n

Bigrams

So far we have been examining individual words, but it is often useful to examine words that appear together. To prepare the data we use unnest_tokens() again, but now we add token = “ngrams”, n = 2 to look at word pairs or bigrams. Here is how to do that:

highway_lyrics %>%
  unnest_tokens(bigram, line, token = "ngrams", n = 2) %>% 
  select(bigram)
NA

Notice that the 2nd word of the first bigram is the 1st word of the second bigram. In this way, every possible two-word combination is retained.

Once you are satisfied that it is working, make a dataframe of this new set of words called highway_bigrams. Here’s a new way to do that. We have used this as an assignment operator: <- but you can also use it the other direction -> .

Copy-paste the above code chunk, but then put the following line at the end of the last line: -> highway_bigrams

Then view the new data frame by typing in its name.

highway_lyrics %>%
  unnest_tokens(bigram, line, token = "ngrams", n = 2) %>% 
  select(bigram) -> highway_bigrams

To count the most common bigrams, pipe highway_bigrams into count(bigram, sort = T) below:

highway_bigrams %>% 
  count(bigram, sort = T)

This is the same problem as before, that many of the bigrams contain common and uninteresting words. It’s a little more complicated to remove stop words from bigrams, but here’s how to do it. The following separates the bigrams, checks each word against the list of stop words, filters them out if they are, and then puts them back together with unite().

highway_bigrams %>% 
  separate(bigram, c("word1", "word2"), sep = " ") %>% 
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word) %>% 
  unite(bigram, word1, word2, sep = " ")
NA

Now pipe the above code into count(bigram, sort = T) again to see the difference in the top bigrams when stopwords are removed:

highway_bigrams %>% 
  separate(bigram, c("word1", "word2"), sep = " ") %>% 
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word) %>% 
  unite(bigram, word1, word2, sep = " ") %>% 
  count(bigram, sort = T)
NA

Now take the above chunk, including the count() line, and pipe it into the following to create a word cloud of all the bigrams that occur more than once in the album:

highway_bigrams %>% 
  separate(bigram, c("word1", "word2"), sep = " ") %>% 
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word) %>% 
  unite(bigram, word1, word2, sep = " ") %>% 
  count(bigram, sort = T) %>% 
  filter(n > 1) %>% 
  wordcloud2(size = .5)

Identifying specific word pairs

Using bigrams, we can also see what words typically follow a given word. For example, which words follow I and you? This can tell us something about the psychology of how someone sees themselves vs. other people. Heres how to do that:

first_word <- c("i", "you")                                  # these need to be lowercase

highway_bigrams %>% 
  count(bigram, sort = T) %>% 
  separate(bigram, c("word1", "word2"), sep = " ") %>%       # separate the two words
  filter(word1 %in% first_word) %>%                          # find first words from our list
  count(word1, word2, wt = n, sort = TRUE) %>% 
  rename(total = nn)
NA

Take all of the above chunk that creates the I/you counts, put it on top of the following chunk (don’t forget the pipe to connect them), and run it to create a graph.

first_word <- c("i", "you")                                  # these need to be lowercase

highway_bigrams %>% 
  count(bigram, sort = T) %>% 
  separate(bigram, c("word1", "word2"), sep = " ") %>%       # separate the two words
  filter(word1 %in% first_word) %>%                          # find first words from our list
  count(word1, word2, wt = n, sort = TRUE) %>% 
  rename(total = nn) %>% 


  mutate(word2 = factor(word2, levels = rev(unique(word2)))) %>%     # put the words in order
  group_by(word1) %>% 
  top_n(5) %>% 
  ggplot(aes(word2, total, fill = word1)) +                          #
  scale_fill_viridis_d() +                                           # set the color palette
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = NULL, title = "Word following:") +
  facet_wrap(~word1, scales = "free") +
  coord_flip() +
  theme_minimal()
Selecting by total

Copy and paste the chunk above, but replace (“i”, “you”) with (“he”, “she”) to see what Bob has men and women doing in his songs.

first_word <- c("he", "she")                                  # these need to be lowercase

highway_bigrams %>% 
  count(bigram, sort = T) %>% 
  separate(bigram, c("word1", "word2"), sep = " ") %>%       # separate the two words
  filter(word1 %in% first_word) %>%                          # find first words from our list
  count(word1, word2, wt = n, sort = TRUE) %>% 
  rename(total = nn) %>% 


  mutate(word2 = factor(word2, levels = rev(unique(word2)))) %>%     # put the words in order
  group_by(word1) %>% 
  top_n(3) %>% 
  ggplot(aes(word2, total, fill = word1)) +                          #
  scale_fill_viridis_d() +                                           # set the color palette
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = NULL, title = "Word following:") +
  facet_wrap(~word1, scales = "free") +
  coord_flip() +
  theme_minimal()
Selecting by total

One final example: You can set a higher ngram. Let’s look at 4-word patterns:

highway_lyrics %>%
  unnest_tokens(words, line, token = "ngrams", n = 4) %>%        # get ngrams of 4
  count(words, sort = T)
NA

How often does the line “like a rolling stone” appear on the album? Take the above chunk and copy-paste it below, and add the following line: filter(words == “like a rolling stone”)

highway_lyrics %>%
  unnest_tokens(words, line, token = "ngrams", n = 4) %>%        # get ngrams of 4
  count(words, sort = T) %>% 
  filter(words =="like a rolling stone")
NA
LS0tCnRpdGxlOiAibHlyaWNzIGFuYWx5c2lzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeShnZW5pdXNyKSAgICAgICAgICAgICAgICAgICAgICAgICAjIFRoaXMgcGFja2FnZSBnZXRzIGx5cmljcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeSh3b3JkY2xvdWQyKQpgYGAKCiMjIyBVc2luZyBHZW5pdXMgdG8gZ2V0IGx5cmljcwoKR2VuaXVzLmNvbSBpcyBhIHNpdGUgdGhhdCBoYXMgbXVzaWMgbHlyaWNzLiBJdCBpcyB1c2VkIGJ5IG11c2ljIHNlcnZpY2VzIGxpa2UgQXBwbGUgTXVzaWMgYW5kIFNwb3RpZnkuIFRoZSBwYWNrYWdlIGdlbml1c3IgaXMgYW4gUiBpbnRlcmZhY2UgdG8gdGhlIEdlbml1cyBzaXRlLCBhbmQgYnJpbmdzIGx5cmljcyBpbnRvIFIuIExldCdzIHdvcmsgdGhyb3VnaCB0aGUgZ2VuaXVzciBBUEksIHN0YXJ0aW5nIHdpdGggc2lnbmluZyB1cCBmb3IgYWNjZXNzOgoKMS4gR28gaGVyZSBhbmQgY3JlYXRlIGEgbG9naW4gYW5kIGFwcCBodHRwczovL2dlbml1cy5jb20vYXBpLWNsaWVudHMvbmV3CjIuIEdvIGhlcmUgYW5kIGdlbmVyYXRlIGEgY2xpZW50IGFjY2VzIHRva2VuIGh0dHBzOi8vZ2VuaXVzLmNvbS9hcGktY2xpZW50cwozLiBDYWxsIGdlbml1c190b2tlbigpIGFuZCB0aGVuIGVudGVyIHRoZSBjbGllbnQgYWNjZXNzIHRva2VuIGluIHRoZSBjb25zb2xlIHdoZW4gcHJvbXB0ZWQKYGBge3J9CgpnZW5pdXNfdG9rZW4oKQpgYGAKCgoKT25jZSB5b3UgZ2V0IHRoYXQgd29ya2luZywgbGV0J3Mgd29yayB0aHJvdWdoIGhvdyB0byBnZXQgaW5mb3JtYXRpb24gZnJvbSBHZW5pdXMuCgoxLiBnZXQgYWxidW0gaWQuIHNlYXJjaCBmb3Igc29uZyB3aXRoIHVuaXF1ZSBuYW1lIG9uIHRoZSBhbGJ1bS4gZmluZCB0aGUgc29uZ19pZCB0aGF0IG1hdGNoZXMgdGhlIGNvcnJlY3QgY29ycmVjdCBzb25nCgpgYGB7cn0Kc2VhcmNoX3NvbmcoImxpa2UgYSByb2xsaW5nIHN0b25lIikKYGBgCgoyLiBQdXQgdGhlIHNvbmdfaWQgbnVtYmVyIGludG8gdGhlIHBhcmVudGhlc2VzIGJlbG93IHRvIGdldCBtb3JlIGluZm8gYWJvdXQgaXQuCgpgYGB7cn0KZ2V0X3NvbmdfbWV0YSg1NDc4NCkKYGBgCgozLiBPbmUgb2YgdGhlIGNvbHVtbnMgYWJvdmUgaXMgYWxidW1faWQuIEVudGVyIHRoYXQgbnVtYmVyIGludG8gdGhlIHBhcmVudGhlc2VzIGJlbG93IHRvIGdldCBhbGwgb2YgdGhlIG90aGVyIHRyYWNrcyBvbiB0aGUgYWxidW0uIHRoaXMgb25lIHdlIGhhdmUgdG8gc2F2ZSBhcyBhIGZpbGUuCgpgYGB7cn0KaGlnaHdheV90cmFja3MgPC0gc2NyYXBlX3RyYWNrbGlzdCgxMzU3MykKaGlnaHdheV90cmFja3MKYGBgCgo0LiBnZXQgdGhlIGx5cmljcy4gbWFwX2RmKCkgcmVwZWF0ZWRseSBhcHBsaWVzLCBvciBtYXBzLCBvbmUgZnVuY3Rpb24gKHNjcmFwZV9seXJpY3NfdXJsKSB0byBlYWNoIGxpbmUgb2YgaGlnaHdheV90cmFja3Mkc29uZ19seXJpY3NfdXJsLCBhbmQgcHV0cyB0aGUgcmVzdWx0cyBpbnRvIGhpZ2h3YXlfbHlyaWNzLgoKYGBge3J9CmhpZ2h3YXlfbHlyaWNzIDwtIG1hcF9kZihoaWdod2F5X3RyYWNrcyRzb25nX2x5cmljc191cmwsIHNjcmFwZV9seXJpY3NfdXJsKQpoaWdod2F5X2x5cmljcwoKYGBgCgoKIyMjIFByZXBhcmluZyBseXJpY3MgZm9yIGFuYWx5c2lzCgpOb3cgd2UgbmVlZCB0byB1c2UgdW5uZXN0X3Rva2VucygpIHRvIHNlcGFyYXRlIHRoZSB3b3JkcyBvbmUgdG8gYSByb3csIG1ha2UgdGhlbSBsb3dlcmNhc2UsIGFuZCByZW1vdmUgdGhlIHB1bmN0dWF0aW9uLiBQdXQgdGhhdCBpbnRvIGEgbmV3IGRhdGEgZnJhbWUgY2FsbGVkIGhpZ2h3YXlfd29yZHMuIE5vdGUgdGhhdCB0aGUgY29sdW1uIHdpdGggdGhlIGx5cmljcyBpcyBjYWxsZWQgJ2xpbmUuJwoKYGBge3J9CmhpZ2h3YXlfd29yZHMgPC0gaGlnaHdheV9seXJpY3MgJT4lCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCBsaW5lKSAlPiUgCiAgc2VsZWN0KHNvbmdfbmFtZSwgd29yZCkKCmhpZ2h3YXlfd29yZHMKYGBgCgoKCgojIyMgV29yZCBjb3VudHMgYW5kIHdvcmQgY2xvdWQKClRvIGdldCBhIHNlbnNlIG9mIHRoZSBjb21tb24gd29yZHMgb24gdGhlIGFsYnVtLCBwaXBlIGhpZ2h3YXlfd29yZHMgaW50byBjb3VudCh3b3JkLCBzb3J0ID0gVCkKCmBgYHtyfQpoaWdod2F5X3dvcmRzICU+JSAKICBjb3VudCggd29yZCwgc29ydCA9IFQpCmBgYAoKCgoKQnV0IHRoYXQgaGFzIGEgbG90IG9mIHNpbXBsZSBhbmQgdW5pbXBvcnRhbnQgd29yZHMgYXQgdGhlIHRvcCBvZiB0aGUgbGlzdC4gTGV0J3MgZ2V0IHJpZCBvZiBzdG9wIHdvcmRzIGZpcnN0LiBUaGlzIHVzZXMgYW50aV9qb2luKCkgdG8gcmVtb3ZlIHRoZSBzdG9wd29yZHMgZnJvbSBvdXIgd29yZHM6CgpgYGB7cn0KaGlnaHdheV93b3JkcyAlPiUgCiAgYW50aV9qb2luKGdldF9zdG9wd29yZHMoKSkgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUKQpgYGAKCgoKQ3JlYXRlIGEgd29yZCBjbG91ZCBieSBjb3B5LXBhc3RpbmcgdGhlIGNvZGUgYWJvdmUgYW5kIGFkZGluZyB0aGUgZm9sbG93aW5nOgoKMS4gQWRkIHRoZSBsaW5lIHRvcF9uKDIwMCkgdG8gbGltaXQgdGhlIG51bWJlciBvZiB3b3JkcyBpbiB0aGUgY2xvdWQgdG8gMjAwCjIuIGFkZCB0aGUgbGluZSB3b3JkY2xvdWQyKHNpemUgPSAuNSkKCmBgYHtyfQpoaWdod2F5X3dvcmRzICU+JSAKICBhbnRpX2pvaW4oZ2V0X3N0b3B3b3JkcygpKSAlPiUgCiAgY291bnQod29yZCwgc29ydCA9IFQpICU+JSAKICB0b3BfbigyMDApICU+JSAKICB3b3JkY2xvdWQyKHNpemUgPSAuNSkKYGBgCgoKCgoKCgojIyMgU2VudGltZW50IGFuYWx5c2lzCgpTZW50aW1lbnQgaXMgYW5vdGhlciB3b3JkIGZvciBvcGluaW9uIG9yIGVtb3Rpb24uIFNlbnRpbWVudCBhbmFseXNpcyBpcyB0aGUgYW5hbHlzaXMgb2YgdGV4dCBmb3IgdGhlIG9waW5pb25zIG9yIGVtb3Rpb25zIGl0IGNvbnRhaW5zLiBJdCBpcyBvZnRlbiB1c2VkIGluIG1hcmtldGluZyByZXNlYXJjaCB0byBzZWUgY3VzdG9tZXJzJyBvcGluaW9ucyBpbiB0aGUgcmV2aWV3cyB0aGV5IGxlYXZlIG9uIHNpdGVzIGxpa2UgQW1hem9uLgoKU2VudGltZW50IGFuYWx5c2lzIHVzZXMgZGljdGlvbmFyaWVzIHRoYXQgY29udGFpbiB3b3JkcyByZWxhdGVkIHRvIHNlbnRpbWVudHMgYW5kIHRoZW4gY29tcGFyZXMgdGhlIGRpY3Rpb25hcnkgdG8gdGhlIHRleHQgYmVpbmcgYW5hbHl6ZWQuIExldCdzIGxvb2sgYXQgb25lIG9mIHRoZSBzZW50aW1lbnQgZGljdGlvbmFyaWVzLCBjYWxsZWQgKmJpbmcqIGFmdGVyIG9uZSBvZiB0aGUgcmVzZWFyY2hlcnMgd2hvIGRldmVsb3BlZCBpdC4KCmBgYHtyfQpiaW5nIDwtIGdldF9zZW50aW1lbnRzKCJiaW5nIikKYmluZwpgYGAKCgoKV2UgbmVlZCB0byBqb2luIHRoZSBzZW50aW1lbnQgZGljdGlvbmFyeSB3aXRoIG91ciBseXJpY3MuIGlubmVyX2pvaW4oKSB3aWxsIHJldGFpbiBhbGwgb2YgdGhlIHdvcmRzIHRoYXQgYXJlIHByZXNlbnQgaW4gYm90aC4KCmBgYHtyfQpoaWdod2F5X3dvcmRzICU+JSAKICBpbm5lcl9qb2luKGJpbmcpICU+JSAKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKQoKYGBgCgoKClRha2UgdGhlIGFib3ZlIGNvZGUgYW5kIHBpcGUgaXQgaW50byB0aGUgZm9sbG93aW5nIHRvIGNyZWF0ZSBhIGdyYXBoIG9mIHRoZSB0YWJsZSBhYm92ZS4KCmBgYHtyfQpoaWdod2F5X3dvcmRzICU+JSAKICBpbm5lcl9qb2luKGJpbmcpICU+JSAKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUgCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUKICB0b3BfbigxMCkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gc2VudGltZW50KSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKHZhcnMoc2VudGltZW50KSwgc2NhbGVzID0gImZyZWUiKSArCiAgbGFicyh5ID0gIkJvYiBEeWxhbidzIEhpZ2h3YXkgNjEgYWxidW06IFdvcmRzIHRoYXQgY29udHJpYnV0ZSB0aGUgbW9zdCB0byBlYWNoIHNlbnRpbWVudCIsCiAgICAgICB4ID0gTlVMTCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKCgoKCgoKCndvcmQgY2xvdWRzIHdpdGggZWFjaCBzZW50aW1lbnQ6CgpgYGB7cn0KaGlnaHdheV93b3JkcyAlPiUgCiAgaW5uZXJfam9pbihiaW5nKSAlPiUgCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lIAogIGZpbHRlcihzZW50aW1lbnQgPT0gInBvc2l0aXZlIikgJT4lCiAgc2VsZWN0KHdvcmQsIG4pICU+JSAKICB3b3JkY2xvdWQyKCkKCgpoaWdod2F5X3dvcmRzICU+JSAKICBpbm5lcl9qb2luKGJpbmcpICU+JSAKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAibmVnYXRpdmUiKSAlPiUKICBzZWxlY3Qod29yZCwgbikgJT4lIAogIHdvcmRjbG91ZDIoKQogIApgYGAKCgojIyMgT3RoZXIgc2VudGltZW50IGRpY3Rpb25hcmllcwoKCkFub3RoZXIgc2VudGltZW50IGRpY3Rpb25hcnkgaXMgbnJjLCB3aGljaCBzdGFuZHMgZm9yIE5hdGlvbmFsIFJlc2VhcmNoIENvdW5jaWwsIGEgZ292ZXJubWVudCBhZ2VuY3kgaW4gQ2FuYWRhIHNpbWlsYXIgdG8gdGhlIE5hdGlvbmFsIFNjaWVuY2UgRm91bmRhdGlvbiBpbiB0aGUgVVMuCgoKYGBge3J9Cm5yYyA8LSBnZXRfc2VudGltZW50cygibnJjIikKbnJjCmBgYAoKCk5vdGljZSB0aGF0IHRoaXMgZGljdGlvbmFyeSBoYXMgc2V2ZXJhbCBkaWZmZXJlbnQgc2VudGltZW50cywgbm90IGp1c3QgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlLiBZb3UgY2FuIHZpZXcgYWxsIHRoZSBkaWZmZXJlbnQgc2VudGltZW50cyBieSBwaXBpbmcgbnJjIGludG8gZGlzdGluY3Qoc2VudGltZW50KS4gRG8gdGhhdCBiZWxvdzoKCmBgYHtyfQpucmMgJT4lIAogIGRpc3RpbmN0KHNlbnRpbWVudCkKYGBgCgoKCgpDb25kdWN0IHRoZSBzYW1lIGFuYWx5c2lzIGFzIGFib3ZlIC0gdGhlIHRhYmxlcyBhbmQgZ3JhcGhzIG9mIHdvcmRzIHRoYXQgY29udHJpYnV0ZSB0byBlYWNoIHNlbnRpbWVudCwgYnV0IGRvIGl0IGZvciB0aGlzIG5ldyBsZXhpY29uLgpIaW50OiBXaGVuIHlvdSBjcmVhdGUgdGhlIGdyYXBoLCB0cnkgYSBzbWFsbGVyIHZhbHVlIGluIHRvcF9uKCksIGxpa2UgMyBvciA1LgoKYGBge3J9CmhpZ2h3YXlfd29yZHMgJT4lIAogIGlubmVyX2pvaW4obnJjKSAlPiUgCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lIAogIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lCiAgdG9wX24oMykgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gc2VudGltZW50KSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKHZhcnMoc2VudGltZW50KSwgc2NhbGVzID0gImZyZWUiKSArCiAgbGFicyh5ID0gIkJvYiBEeWxhbidzIEhpZ2h3YXkgNjEgYWxidW06IFdvcmRzIHRoYXQgY29udHJpYnV0ZSB0aGUgbW9zdCB0byBlYWNoIHNlbnRpbWVudCIsCiAgICAgICB4ID0gTlVMTCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKCgoKCgoKCiMjIyBCaWdyYW1zCgpTbyBmYXIgd2UgaGF2ZSBiZWVuIGV4YW1pbmluZyBpbmRpdmlkdWFsIHdvcmRzLCBidXQgaXQgaXMgb2Z0ZW4gdXNlZnVsIHRvIGV4YW1pbmUgd29yZHMgdGhhdCBhcHBlYXIgdG9nZXRoZXIuIFRvIHByZXBhcmUgdGhlIGRhdGEgd2UgdXNlIHVubmVzdF90b2tlbnMoKSBhZ2FpbiwgYnV0IG5vdyB3ZSBhZGQgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIgdG8gbG9vayBhdCB3b3JkIHBhaXJzIG9yIGJpZ3JhbXMuIEhlcmUgaXMgaG93IHRvIGRvIHRoYXQ6CgpgYGB7cn0KaGlnaHdheV9seXJpY3MgJT4lCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIGxpbmUsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUgCiAgc2VsZWN0KGJpZ3JhbSkKCmBgYAoKCk5vdGljZSB0aGF0IHRoZSAybmQgd29yZCBvZiB0aGUgZmlyc3QgYmlncmFtIGlzIHRoZSAxc3Qgd29yZCBvZiB0aGUgc2Vjb25kIGJpZ3JhbS4gSW4gdGhpcyB3YXksIGV2ZXJ5IHBvc3NpYmxlIHR3by13b3JkIGNvbWJpbmF0aW9uIGlzIHJldGFpbmVkLgoKT25jZSB5b3UgYXJlIHNhdGlzZmllZCB0aGF0IGl0IGlzIHdvcmtpbmcsIG1ha2UgYSBkYXRhZnJhbWUgb2YgdGhpcyBuZXcgc2V0IG9mIHdvcmRzIGNhbGxlZCBoaWdod2F5X2JpZ3JhbXMuIEhlcmUncyBhIG5ldyB3YXkgdG8gZG8gdGhhdC4gV2UgaGF2ZSB1c2VkIHRoaXMgYXMgYW4gYXNzaWdubWVudCBvcGVyYXRvcjogPC0gYnV0IHlvdSBjYW4gYWxzbyB1c2UgaXQgdGhlIG90aGVyIGRpcmVjdGlvbiAtPiAuCgpDb3B5LXBhc3RlIHRoZSBhYm92ZSBjb2RlIGNodW5rLCBidXQgdGhlbiBwdXQgdGhlIGZvbGxvd2luZyBsaW5lIGF0IHRoZSBlbmQgb2YgdGhlIGxhc3QgbGluZToKLT4gaGlnaHdheV9iaWdyYW1zCgpUaGVuIHZpZXcgdGhlIG5ldyBkYXRhIGZyYW1lIGJ5IHR5cGluZyBpbiBpdHMgbmFtZS4KCmBgYHtyfQpoaWdod2F5X2x5cmljcyAlPiUKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgbGluZSwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpICU+JSAKICBzZWxlY3QoYmlncmFtKSAtPiBoaWdod2F5X2JpZ3JhbXMKCmBgYAoKCgoKClRvIGNvdW50IHRoZSBtb3N0IGNvbW1vbiBiaWdyYW1zLCBwaXBlIGhpZ2h3YXlfYmlncmFtcyBpbnRvIGNvdW50KGJpZ3JhbSwgc29ydCA9IFQpIGJlbG93OgoKYGBge3J9CmhpZ2h3YXlfYmlncmFtcyAlPiUgCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVCkKYGBgCgoKCgoKVGhpcyBpcyB0aGUgc2FtZSBwcm9ibGVtIGFzIGJlZm9yZSwgdGhhdCBtYW55IG9mIHRoZSBiaWdyYW1zIGNvbnRhaW4gY29tbW9uIGFuZCB1bmludGVyZXN0aW5nIHdvcmRzLiBJdCdzIGEgbGl0dGxlIG1vcmUgY29tcGxpY2F0ZWQgdG8gcmVtb3ZlIHN0b3Agd29yZHMgZnJvbSBiaWdyYW1zLCBidXQgaGVyZSdzIGhvdyB0byBkbyBpdC4gVGhlIGZvbGxvd2luZyBzZXBhcmF0ZXMgdGhlIGJpZ3JhbXMsIGNoZWNrcyBlYWNoIHdvcmQgYWdhaW5zdCB0aGUgbGlzdCBvZiBzdG9wIHdvcmRzLCBmaWx0ZXJzIHRoZW0gb3V0IGlmIHRoZXkgYXJlLCBhbmQgdGhlbiBwdXRzIHRoZW0gYmFjayB0b2dldGhlciB3aXRoIHVuaXRlKCkuCgpgYGB7cn0KaGlnaHdheV9iaWdyYW1zICU+JSAKICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIikgJT4lIAogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQogIGZpbHRlcighd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JSAKICB1bml0ZShiaWdyYW0sIHdvcmQxLCB3b3JkMiwgc2VwID0gIiAiKQoKYGBgCgoKCgoKCgoKCk5vdyBwaXBlIHRoZSBhYm92ZSBjb2RlIGludG8gY291bnQoYmlncmFtLCBzb3J0ID0gVCkgYWdhaW4gdG8gc2VlIHRoZSBkaWZmZXJlbmNlIGluIHRoZSB0b3AgYmlncmFtcyB3aGVuIHN0b3B3b3JkcyBhcmUgcmVtb3ZlZDoKCmBgYHtyfQpoaWdod2F5X2JpZ3JhbXMgJT4lIAogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUgCiAgZmlsdGVyKCF3b3JkMSAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lCiAgZmlsdGVyKCF3b3JkMiAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lIAogIHVuaXRlKGJpZ3JhbSwgd29yZDEsIHdvcmQyLCBzZXAgPSAiICIpICU+JSAKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUKQoKYGBgCgoKCgoKCgoKTm93IHRha2UgdGhlIGFib3ZlIGNodW5rLCBpbmNsdWRpbmcgdGhlIGNvdW50KCkgbGluZSwgYW5kIHBpcGUgaXQgaW50byB0aGUgZm9sbG93aW5nIHRvIGNyZWF0ZSBhIHdvcmQgY2xvdWQgb2YgYWxsIHRoZSBiaWdyYW1zIHRoYXQgb2NjdXIgbW9yZSB0aGFuIG9uY2UgaW4gdGhlIGFsYnVtOgoKYGBge3J9CmhpZ2h3YXlfYmlncmFtcyAlPiUgCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpICU+JSAKICBmaWx0ZXIoIXdvcmQxICVpbiUgc3RvcF93b3JkcyR3b3JkKSAlPiUKICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKSAlPiUgCiAgdW5pdGUoYmlncmFtLCB3b3JkMSwgd29yZDIsIHNlcCA9ICIgIikgJT4lIAogIGNvdW50KGJpZ3JhbSwgc29ydCA9IFQpICU+JSAKICBmaWx0ZXIobiA+IDEpICU+JSAKICB3b3JkY2xvdWQyKHNpemUgPSAuNSkKYGBgCgoKCgojIyBJZGVudGlmeWluZyBzcGVjaWZpYyB3b3JkIHBhaXJzCgpVc2luZyBiaWdyYW1zLCB3ZSBjYW4gYWxzbyBzZWUgd2hhdCB3b3JkcyB0eXBpY2FsbHkgZm9sbG93IGEgZ2l2ZW4gd29yZC4gRm9yIGV4YW1wbGUsIHdoaWNoIHdvcmRzIGZvbGxvdyBJIGFuZCB5b3U/IFRoaXMgY2FuIHRlbGwgdXMgc29tZXRoaW5nIGFib3V0IHRoZSBwc3ljaG9sb2d5IG9mIGhvdyBzb21lb25lIHNlZXMgdGhlbXNlbHZlcyB2cy4gb3RoZXIgcGVvcGxlLiBIZXJlcyBob3cgdG8gZG8gdGhhdDoKCgpgYGB7cn0KZmlyc3Rfd29yZCA8LSBjKCJpIiwgInlvdSIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlc2UgbmVlZCB0byBiZSBsb3dlcmNhc2UKCmhpZ2h3YXlfYmlncmFtcyAlPiUgCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVCkgJT4lIAogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUgICAgICAgIyBzZXBhcmF0ZSB0aGUgdHdvIHdvcmRzCiAgZmlsdGVyKHdvcmQxICVpbiUgZmlyc3Rfd29yZCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAjIGZpbmQgZmlyc3Qgd29yZHMgZnJvbSBvdXIgbGlzdAogIGNvdW50KHdvcmQxLCB3b3JkMiwgd3QgPSBuLCBzb3J0ID0gVFJVRSkgJT4lIAogIHJlbmFtZSh0b3RhbCA9IG5uKQoKYGBgCgoKVGFrZSBhbGwgb2YgdGhlIGFib3ZlIGNodW5rIHRoYXQgY3JlYXRlcyB0aGUgSS95b3UgY291bnRzLCBwdXQgaXQgb24gdG9wIG9mIHRoZSBmb2xsb3dpbmcgY2h1bmsgKGRvbid0IGZvcmdldCB0aGUgcGlwZSB0byBjb25uZWN0IHRoZW0pLCBhbmQgcnVuIGl0IHRvIGNyZWF0ZSBhIGdyYXBoLgoKCmBgYHtyfQpmaXJzdF93b3JkIDwtIGMoImkiLCAieW91IikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGVzZSBuZWVkIHRvIGJlIGxvd2VyY2FzZQoKaGlnaHdheV9iaWdyYW1zICU+JSAKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUKSAlPiUgCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpICU+JSAgICAgICAjIHNlcGFyYXRlIHRoZSB0d28gd29yZHMKICBmaWx0ZXIod29yZDEgJWluJSBmaXJzdF93b3JkKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICMgZmluZCBmaXJzdCB3b3JkcyBmcm9tIG91ciBsaXN0CiAgY291bnQod29yZDEsIHdvcmQyLCB3dCA9IG4sIHNvcnQgPSBUUlVFKSAlPiUgCiAgcmVuYW1lKHRvdGFsID0gbm4pICU+JSAKCgogIG11dGF0ZSh3b3JkMiA9IGZhY3Rvcih3b3JkMiwgbGV2ZWxzID0gcmV2KHVuaXF1ZSh3b3JkMikpKSkgJT4lICAgICAjIHB1dCB0aGUgd29yZHMgaW4gb3JkZXIKICBncm91cF9ieSh3b3JkMSkgJT4lIAogIHRvcF9uKDUpICU+JSAKICBnZ3Bsb3QoYWVzKHdvcmQyLCB0b3RhbCwgZmlsbCA9IHdvcmQxKSkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgIwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHNldCB0aGUgY29sb3IgcGFsZXR0ZQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgdGl0bGUgPSAiV29yZCBmb2xsb3dpbmc6IikgKwogIGZhY2V0X3dyYXAofndvcmQxLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgoKQ29weSBhbmQgcGFzdGUgdGhlIGNodW5rIGFib3ZlLCBidXQgcmVwbGFjZSAoImkiLCAieW91Iikgd2l0aCAoImhlIiwgInNoZSIpIHRvIHNlZSB3aGF0IEJvYiBoYXMgbWVuIGFuZCB3b21lbiBkb2luZyBpbiBoaXMgc29uZ3MuCgpgYGB7cn0KZmlyc3Rfd29yZCA8LSBjKCJoZSIsICJzaGUiKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRoZXNlIG5lZWQgdG8gYmUgbG93ZXJjYXNlCgpoaWdod2F5X2JpZ3JhbXMgJT4lIAogIGNvdW50KGJpZ3JhbSwgc29ydCA9IFQpICU+JSAKICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIikgJT4lICAgICAgICMgc2VwYXJhdGUgdGhlIHR3byB3b3JkcwogIGZpbHRlcih3b3JkMSAlaW4lIGZpcnN0X3dvcmQpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgIyBmaW5kIGZpcnN0IHdvcmRzIGZyb20gb3VyIGxpc3QKICBjb3VudCh3b3JkMSwgd29yZDIsIHd0ID0gbiwgc29ydCA9IFRSVUUpICU+JSAKICByZW5hbWUodG90YWwgPSBubikgJT4lIAoKCiAgbXV0YXRlKHdvcmQyID0gZmFjdG9yKHdvcmQyLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQyKSkpKSAlPiUgICAgICMgcHV0IHRoZSB3b3JkcyBpbiBvcmRlcgogIGdyb3VwX2J5KHdvcmQxKSAlPiUgCiAgdG9wX24oMykgJT4lIAogIGdncGxvdChhZXMod29yZDIsIHRvdGFsLCBmaWxsID0gd29yZDEpKSArICAgICAgICAgICAgICAgICAgICAgICAgICAjCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc2V0IHRoZSBjb2xvciBwYWxldHRlCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMLCB0aXRsZSA9ICJXb3JkIGZvbGxvd2luZzoiKSArCiAgZmFjZXRfd3JhcCh+d29yZDEsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbWluaW1hbCgpCgpgYGAKCgoKT25lIGZpbmFsIGV4YW1wbGU6IFlvdSBjYW4gc2V0IGEgaGlnaGVyIG5ncmFtLiBMZXQncyBsb29rIGF0IDQtd29yZCBwYXR0ZXJuczoKICAKYGBge3J9CmhpZ2h3YXlfbHlyaWNzICU+JQogIHVubmVzdF90b2tlbnMod29yZHMsIGxpbmUsIHRva2VuID0gIm5ncmFtcyIsIG4gPSA0KSAlPiUgICAgICAgICMgZ2V0IG5ncmFtcyBvZiA0CiAgY291bnQod29yZHMsIHNvcnQgPSBUKQoKYGBgCiAgCgoKCkhvdyBvZnRlbiBkb2VzIHRoZSBsaW5lICJsaWtlIGEgcm9sbGluZyBzdG9uZSIgYXBwZWFyIG9uIHRoZSBhbGJ1bT8gVGFrZSB0aGUgYWJvdmUgY2h1bmsgYW5kIGNvcHktcGFzdGUgaXQgYmVsb3csIGFuZCBhZGQgdGhlIGZvbGxvd2luZyBsaW5lOiBmaWx0ZXIod29yZHMgPT0gImxpa2UgYSByb2xsaW5nIHN0b25lIikKCmBgYHtyfQpoaWdod2F5X2x5cmljcyAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmRzLCBsaW5lLCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gNCkgJT4lICAgICAgICAjIGdldCBuZ3JhbXMgb2YgNAogIGNvdW50KHdvcmRzLCBzb3J0ID0gVCkgJT4lIAogIGZpbHRlcih3b3JkcyA9PSJsaWtlIGEgcm9sbGluZyBzdG9uZSIpCgpgYGA=