library(readr)
library(dplyr)
library(jiebaR)
library(tidyr)
library(tidytext)
library(igraph)
library(topicmodels)
library(stringr)
library(ggplot2)
library(jsonlite)

資料來源:kaggle上的電影資料集
https://www.kaggle.com/tmdb/tmdb-movie-metadata

電影所有演職人員的關係

# 讀入演職人員名單
credits = read_csv("./tmdb_5000_credits.csv")
Parsed with column specification:
cols(
  movie_id = col_double(),
  title = col_character(),
  cast = col_character(),
  crew = col_character()
)
credits
# cast:演員
# crew:工作人員
# 將工作人員的資料從JSON格式中轉成tidy格式,並找出所有導演的名單。
director = credits %>% 
  filter(nchar(crew)>2) %>% 
  mutate(js=lapply(crew,fromJSON)) %>% 
  unnest(js) %>% 
  mutate(dir_name = name) %>% 
  select(movie_id, title, job, dir_name) %>% 
  filter(job == "Director")
director
# 過濾出沒有導演的電影
credits %>% 
  filter(.$movie_id %in% credits$movie_id[!unique(credits$movie_id) %in% unique(director$movie_id)]) %>% 
  select(movie_id, title, crew)
# 總出現的導演人數
length(unique(director$dir_name))
[1] 2577
# 畫圖呈現導演拍過的電影片數
director %>%
  filter(!(is.na(dir_name)| dir_name==""))%>%
  group_by(dir_name) %>% 
  summarise(count=n()) %>% 
  top_n(20) %>% 
  mutate(dir_name = reorder(dir_name, count)) %>% 
  ggplot(aes(dir_name,count,fill=dir_name)) + 
  geom_bar(stat="identity") + 
  theme(legend.position="None") + 
  labs(x="Director",y="Count") +
  coord_flip()
Selecting by count

# 將演員的資料從JSON格式中轉成tidy格式。
actor = credits %>% 
  filter(nchar(cast)>2) %>% 
  mutate(js=lapply(cast,fromJSON)) %>% 
  unnest(js) %>% 
  mutate(cast_name = name) %>% 
  select(movie_id, title, cast_name, order)
actor
# 總出現的演員人數
length(unique(actor$cast_name))
[1] 54201
# 畫圖呈現演員拍過的電影片數
actor %>%
  filter(!(is.na(cast_name)| cast_name==""))%>%
  group_by(cast_name) %>% 
  summarise(count=n()) %>% 
  top_n(20) %>% 
  mutate(cast_name = reorder(cast_name, count)) %>% 
  ggplot(aes(cast_name,count,fill=cast_name)) + 
  geom_bar(stat="identity") + 
  theme(legend.position="None") + 
  labs(x="Actor",y="Count") +
  coord_flip()
Selecting by count

# 總共有參與的人員(導演&演員)
all_credit <- c(director$dir_name, actor$cast_name)
length(unique(all_credit))
[1] 56175
# 整理所有人員
# 如果有執導過,就標註他爲dir
# 如果沒有,則標註他爲act
all_list <- data.frame(name=unique(all_credit)) %>%
              mutate(type=ifelse(name %in% director$dir_name, "dir", "act"))
all_list
# 整理所有導演有執導過的演員
act <- right_join(actor, director) %>% 
  select(movie_id, title, cast_name, dir_name, order)
Joining, by = c("movie_id", "title")
act
# 挑出cast_name(演員名字)、dir_name(導演名字)、movie_id 三個欄位
# 為了減少資料量,過濾掉 order < 3 的資料,order代表在該電影裡角色的重要性,越小代表越重要
link <- act %>% 
  filter(order < 3) %>% 
  select(cast_name, dir_name, movie_id)
link
# 建立網路關係
act_Network <- graph_from_data_frame(d=link, directed=T)
act_Network
IGRAPH edb71b1 DN-- 7842 15089 -- 
+ attr: name (v/c), movie_id (e/n)
+ edges from edb71b1 (vertex names):
 [1] Sam Worthington  ->James Cameron     Zoe Saldana      ->James Cameron     Sigourney Weaver ->James Cameron    
 [4] Johnny Depp      ->Gore Verbinski    Orlando Bloom    ->Gore Verbinski    Keira Knightley  ->Gore Verbinski   
 [7] Daniel Craig     ->Sam Mendes        Christoph Waltz  ->Sam Mendes        Léa Seydoux      ->Sam Mendes       
[10] Christian Bale   ->Christopher Nolan Michael Caine    ->Christopher Nolan Gary Oldman      ->Christopher Nolan
[13] Taylor Kitsch    ->Andrew Stanton    Lynn Collins     ->Andrew Stanton    Samantha Morton  ->Andrew Stanton   
[16] Tobey Maguire    ->Sam Raimi         Kirsten Dunst    ->Sam Raimi         James Franco     ->Sam Raimi        
[19] Zachary Levi     ->Byron Howard      Mandy Moore      ->Byron Howard      Donna Murphy     ->Byron Howard     
[22] Zachary Levi     ->Nathan Greno      Mandy Moore      ->Nathan Greno      Donna Murphy     ->Nathan Greno     
+ ... omitted several edges
# 畫出網路圖
plot(act_Network)

看不出什麼東西。

# 將點的大小和線的粗細調小,並不顯示所有人員的名字。
plot(act_Network, vertex.size=2, edge.arrow.size=.2, vertex.label=NA)

可以發現比較有趣的一點:發現有不少部電影的導演是自導自演。

電影分類

根據前面的網路圖可以發現資料量太大,4800部電影、54201位演員、2577位導演,很難從圖中找出關係,
因此我們從kaggle電影資料集的另一部份:每部電影的相關資料(包括電影預算、類型、票房、片長等), 以其中的每部電影的類型來進行分類(動作、科幻、冒險等),藉此以不同的電影類型來進行網路圖的繪製。

# 讀入資料集
movies = read_csv("tmdb_5000_movies.csv")
Parsed with column specification:
cols(
  .default = col_character(),
  budget = col_double(),
  id = col_double(),
  popularity = col_double(),
  release_date = col_date(format = ""),
  revenue = col_double(),
  runtime = col_double(),
  vote_average = col_double(),
  vote_count = col_double()
)
See spec(...) for full column specifications.
movies
# 將所屬類型的資料從JSON格式中轉成tidy格式。
# 製作電影利潤變數
m_genres <- movies %>% 
  filter(nchar(genres)>2) %>% 
  mutate(js=lapply(genres,fromJSON)) %>% 
  unnest(js) %>% 
  mutate(movie_id = id, genre = name, ROI=(revenue-budget)/budget) %>% 
  select(movie_id, title, genre,ROI)
m_genres
revenue_data<-movies %>%
  filter(budget >= 1000000 & revenue >= 1000000) %>%
  mutate(ROI = (revenue - budget)/budget)
#預算
revenue_data %>%
  select(title, budget) %>%
  arrange(desc(budget)) %>%
  head(15)%>%
  ggplot(., aes(x = reorder(title, -desc(budget)), y = budget/1000000)) +
  geom_bar(stat = "identity")+ 
  ggtitle("電影預算")+
  xlab("")+
  ylab("預算金額 (百萬)") + 
  theme(text = element_text(family = "Heiti TC Light")) +
  coord_flip()

#收入
revenue_data %>%
  select(title, revenue) %>%
  arrange(desc(revenue)) %>%
  head(15)%>%
  ggplot(., aes(x = (reorder(title, -desc(revenue))), y = revenue/1000000)) +
  geom_bar( stat = "identity")+ 
  theme(axis.text.x=element_text(hjust=1))+
  ggtitle("Movie Revenues")+
  xlab("")+
  ylab("Revenue (in Millions)") + 
  coord_flip() 

## 投資報酬率
revenue_data %>%
  select(title, budget, revenue, ROI) %>%
  arrange(desc(ROI)) %>%
  head(15)%>%
  ggplot(., aes(x = reorder(title, -desc(ROI)), y = ROI)) +
  geom_bar( stat = "identity")+ 
  theme(axis.text.x=element_text(hjust=1))+
  ggtitle("Movie ROI")+
  xlab("")+
  ylab("ROI") + 
  coord_flip()

投資報酬率高的電影年代都比較久遠

## 透過時間計算電影發布的個數
library(lubridate)
revenue_data %>%
 filter(year(release_date) > 1990) %>%  
 group_by(year = year(release_date), month = month(release_date)) %>%
 summarise(count = n())%>% 
 ggplot(aes(year, as.factor(month)))+
 geom_tile(aes(fill=count),colour="white")+
 scale_fill_gradient(low="light blue",high = "dark blue") +
 xlab("電影發布年份")+
 ylab("電影發布月份")+
 ggtitle("Heat Map") +
  theme(text = element_text(family = "Heiti TC Light"))

從1990-2010電影上映的數量逐年增加,尤其以九月份上映的電影數量偏多

# 將資料合併到剛剛的導演和演員名單
act_genre <- right_join(act, m_genres)
Joining, by = c("movie_id", "title")
act_genre
# 資料集中所有的分類
unique(act_genre$genre)
 [1] "Action"          "Adventure"       "Fantasy"         "Science Fiction" "Crime"           "Drama"          
 [7] "Thriller"        "Animation"       "Family"          "Western"         "Comedy"          "Romance"        
[13] "Horror"          "Mystery"         "History"         "War"             "Music"           "Documentary"    
[19] "Foreign"         "TV Movie"       
# 這裡為了降低資料量,做了兩個限制:
# 1.order < 3,order 越小表示在劇中越重要,亦即主演。
# 2.genre == "Action",找出為動作片的電影。
link_g_action <- act_genre %>% 
  filter(order < 3) %>% 
  filter(genre == "Action") %>% 
  select(cast_name, dir_name, movie_id, genre, ROI)
link_g_action
# 建立網路關係
g_action_Network <- graph_from_data_frame(d=link_g_action, directed=T)
g_action_Network
IGRAPH 24b7d70 DN-- 2489 3626 -- 
+ attr: name (v/c), movie_id (e/n), genre (e/c), ROI (e/n)
+ edges from 24b7d70 (vertex names):
 [1] Sam Worthington  ->James Cameron     Zoe Saldana      ->James Cameron     Sigourney Weaver ->James Cameron    
 [4] Johnny Depp      ->Gore Verbinski    Orlando Bloom    ->Gore Verbinski    Keira Knightley  ->Gore Verbinski   
 [7] Daniel Craig     ->Sam Mendes        Christoph Waltz  ->Sam Mendes        Léa Seydoux      ->Sam Mendes       
[10] Christian Bale   ->Christopher Nolan Michael Caine    ->Christopher Nolan Gary Oldman      ->Christopher Nolan
[13] Taylor Kitsch    ->Andrew Stanton    Lynn Collins     ->Andrew Stanton    Samantha Morton  ->Andrew Stanton   
[16] Tobey Maguire    ->Sam Raimi         Kirsten Dunst    ->Sam Raimi         James Franco     ->Sam Raimi        
[19] Robert Downey Jr.->Joss Whedon       Chris Hemsworth  ->Joss Whedon       Mark Ruffalo     ->Joss Whedon      
[22] Ben Affleck      ->Zack Snyder       Henry Cavill     ->Zack Snyder       Gal Gadot        ->Zack Snyder      
+ ... omitted several edges
# 畫出網路圖
plot(g_action_Network)

還是看不出什麼。

# 將點的大小和線的粗細調小,並不顯示所有人員的名字。
# 如果合作的電影有賺錢標記綠色,沒賺錢標記紅色
# E(g_action_Network)$color <- ifelse(E(g_action_Network)$ROI>mean(E(g_action_Network)$ROI) , "lightgreen", "palevioletred")
plot(g_action_Network, vertex.size=2, edge.arrow.size=.2 , vertex.label.cex=0.8, vertex.label= NA)

可以看出明顯比未做限制的結果稀疏許多,不過還是看不出什麼。

出演次數

因為資料量還是太大,這裡我們選擇以出演次數為限制,找出多產的演員與導演之間的關係。

# 找出出演次數大於10的演員
most_cast <- link_g_action %>% 
  distinct(movie_id, cast_name, .keep_all = T) %>% #排除一個電影可能會有兩個directors
  count(cast_name, sort = TRUE) %>% 
  filter(n >= 10)
most_cast
# 找出執導次數大於5的導演
most_dir <- link_g_action %>% 
  distinct(movie_id, dir_name, .keep_all = T) %>% 
  count(dir_name, sort = TRUE) %>% 
  filter(n >= 5)
most_dir
# 找出「出演次數大於10的演員」且「執導次數大於5的導演」的組合
lim_link_g_action <- link_g_action %>% 
  filter(.$cast_name %in% most_cast$cast_name & .$dir_name %in% most_dir$dir_name)
lim_link_g_action
# 篩選出上一步驟中有出現的組合
filtered_name <- all_list %>%
          filter(name %in% lim_link_g_action$cast_name | name %in% lim_link_g_action$dir_name) %>%
          arrange(desc(type))
filtered_name
# 建立網路關係
lim_g_action_Network <- graph_from_data_frame(d=lim_link_g_action, v=filtered_name, directed=T)
lim_g_action_Network
IGRAPH 864ccd1 DN-B 54 114 -- 
+ attr: name (v/c), type (v/c), movie_id (e/n), genre (e/c), ROI (e/n)
+ edges from 864ccd1 (vertex names):
 [1] Will Smith      ->Barry Sonnenfeld Tommy Lee Jones ->Barry Sonnenfeld Mark Wahlberg   ->Michael Bay     
 [4] Harrison Ford   ->Steven Spielberg Jackie Chan     ->Brett Ratner     Will Smith      ->Barry Sonnenfeld
 [7] Jet Li          ->Rob Cohen        Will Smith      ->David Ayer       Vin Diesel      ->Rob Cohen       
[10] Will Smith      ->Peter Berg       Pierce Brosnan  ->Lee Tamahori     Bruce Willis    ->Michael Bay     
[13] Tommy Lee Jones ->Barry Sonnenfeld Will Smith      ->Barry Sonnenfeld Mel Gibson      ->Richard Donner  
[16] Will Smith      ->Michael Bay      Angelina Jolie  ->Phillip Noyce    Liam Neeson     ->Louis Leterrier 
[19] Antonio Banderas->John McTiernan   Tom Cruise      ->John Woo         Tommy Lee Jones ->Paul Greengrass 
[22] Nicolas Cage    ->John Woo         Mel Gibson      ->Roland Emmerich  Tom Cruise      ->Steven Spielberg
+ ... omitted several edges
# 繪圖
set.seed(12345)
plot(lim_g_action_Network, vertex.size=2, edge.arrow.size=.2, vertex.label.cex=0.8)

還是稍微有點太多,改成只標示較有關聯的點(>=5)。

# 將有執導過人的點改為金色
# 單純是演員的點改為天藍色
# 合作較賺錢邊際設為綠色(ROI>全部平均)
# 較沒賺錢標記紅色(ROI<=全部平均)
# 顯示有超過5個關聯的點
set.seed(12345)
labels <- degree(lim_g_action_Network)
V(lim_g_action_Network)$label <- names(labels)
E(lim_g_action_Network)$color <- ifelse(E(lim_g_action_Network)$ROI>mean(E(lim_g_action_Network)$ROI) , "lightgreen", "palevioletred")
V(lim_g_action_Network)$color <- ifelse(V(lim_g_action_Network)$type == "dir", "gold", "lightblue")
plot(lim_g_action_Network, vertex.size=5, edge.arrow.size=.2 , vertex.label.cex=0.8, 
     vertex.label=ifelse(degree(lim_g_action_Network) >= 5, V(lim_g_action_Network)$label, NA))

透過這張圖我們可以看出,在動作片的這個範疇下,演員經常跟哪些導演合作拍戲,以及導演跟演員合作的電影的投資報酬率高低。

LS0tCnRpdGxlOiAi6YCP6YGO57ay6Lev5YiG5p6QS2FnZ2xl55qE6Zu75b2x6LOH5paZ6ZuG5Lit5ryU5ZOh6IiH5bCO5ryU55qE6Zec5L+CIgphdXRob3I6ICLnrKzkupTntYQiCmRhdGU6ICIyMDE5LzUvMjAiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGppZWJhUikKbGlicmFyeSh0aWR5cikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkodG9waWNtb2RlbHMpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGpzb25saXRlKQpgYGAKCj4g6LOH5paZ5L6G5rqQ77yaa2FnZ2xl5LiK55qE6Zu75b2x6LOH5paZ6ZuGPGJyPgo+IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vdG1kYi90bWRiLW1vdmllLW1ldGFkYXRhCgojIyDpm7vlvbHmiYDmnInmvJTogbfkurrlk6HnmoTpl5zkv4IKYGBge3J9CiMg6K6A5YWl5ryU6IG35Lq65ZOh5ZCN5ZauCmNyZWRpdHMgPSByZWFkX2NzdigiLi90bWRiXzUwMDBfY3JlZGl0cy5jc3YiKQpjcmVkaXRzCiMgY2FzdDrmvJTlk6EKIyBjcmV3OuW3peS9nOS6uuWToQpgYGAKCmBgYHtyfQojIOWwh+W3peS9nOS6uuWToeeahOizh+aWmeW+nkpTT07moLzlvI/kuK3ovYnmiJB0aWR55qC85byP77yM5Lim5om+5Ye65omA5pyJ5bCO5ryU55qE5ZCN5Zau44CCCmRpcmVjdG9yID0gY3JlZGl0cyAlPiUgCiAgZmlsdGVyKG5jaGFyKGNyZXcpPjIpICU+JSAKICBtdXRhdGUoanM9bGFwcGx5KGNyZXcsZnJvbUpTT04pKSAlPiUgCiAgdW5uZXN0KGpzKSAlPiUgCiAgbXV0YXRlKGRpcl9uYW1lID0gbmFtZSkgJT4lIAogIHNlbGVjdChtb3ZpZV9pZCwgdGl0bGUsIGpvYiwgZGlyX25hbWUpICU+JSAKICBmaWx0ZXIoam9iID09ICJEaXJlY3RvciIpCmRpcmVjdG9yCmBgYAoKYGBge3J9CiMg6YGO5r++5Ye65rKS5pyJ5bCO5ryU55qE6Zu75b2xCmNyZWRpdHMgJT4lIAogIGZpbHRlciguJG1vdmllX2lkICVpbiUgY3JlZGl0cyRtb3ZpZV9pZFshdW5pcXVlKGNyZWRpdHMkbW92aWVfaWQpICVpbiUgdW5pcXVlKGRpcmVjdG9yJG1vdmllX2lkKV0pICU+JSAKICBzZWxlY3QobW92aWVfaWQsIHRpdGxlLCBjcmV3KQpgYGAKCmBgYHtyfQojIOe4veWHuuePvueahOWwjua8lOS6uuaVuApsZW5ndGgodW5pcXVlKGRpcmVjdG9yJGRpcl9uYW1lKSkKYGBgCgpgYGB7cn0KIyDnlavlnJblkYjnj77lsI7mvJTmi43pgY7nmoTpm7vlvbHniYfmlbgKZGlyZWN0b3IgJT4lCiAgZmlsdGVyKCEoaXMubmEoZGlyX25hbWUpfCBkaXJfbmFtZT09IiIpKSU+JQogIGdyb3VwX2J5KGRpcl9uYW1lKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50PW4oKSkgJT4lIAogIHRvcF9uKDIwKSAlPiUgCiAgbXV0YXRlKGRpcl9uYW1lID0gcmVvcmRlcihkaXJfbmFtZSwgY291bnQpKSAlPiUgCiAgZ2dwbG90KGFlcyhkaXJfbmFtZSxjb3VudCxmaWxsPWRpcl9uYW1lKSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJOb25lIikgKyAKICBsYWJzKHg9IkRpcmVjdG9yIix5PSJDb3VudCIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpgYGB7cn0KIyDlsIfmvJTlk6HnmoTos4fmlpnlvp5KU09O5qC85byP5Lit6L2J5oiQdGlkeeagvOW8j+OAggphY3RvciA9IGNyZWRpdHMgJT4lIAogIGZpbHRlcihuY2hhcihjYXN0KT4yKSAlPiUgCiAgbXV0YXRlKGpzPWxhcHBseShjYXN0LGZyb21KU09OKSkgJT4lIAogIHVubmVzdChqcykgJT4lIAogIG11dGF0ZShjYXN0X25hbWUgPSBuYW1lKSAlPiUgCiAgc2VsZWN0KG1vdmllX2lkLCB0aXRsZSwgY2FzdF9uYW1lLCBvcmRlcikKYWN0b3IKYGBgCgpgYGB7cn0KIyDnuL3lh7rnj77nmoTmvJTlk6HkurrmlbgKbGVuZ3RoKHVuaXF1ZShhY3RvciRjYXN0X25hbWUpKQpgYGAKCmBgYHtyfQojIOeVq+WcluWRiOePvua8lOWToeaLjemBjueahOmbu+W9seeJh+aVuAphY3RvciAlPiUKICBmaWx0ZXIoIShpcy5uYShjYXN0X25hbWUpfCBjYXN0X25hbWU9PSIiKSklPiUKICBncm91cF9ieShjYXN0X25hbWUpICU+JSAKICBzdW1tYXJpc2UoY291bnQ9bigpKSAlPiUgCiAgdG9wX24oMjApICU+JSAKICBtdXRhdGUoY2FzdF9uYW1lID0gcmVvcmRlcihjYXN0X25hbWUsIGNvdW50KSkgJT4lIAogIGdncGxvdChhZXMoY2FzdF9uYW1lLGNvdW50LGZpbGw9Y2FzdF9uYW1lKSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJOb25lIikgKyAKICBsYWJzKHg9IkFjdG9yIix5PSJDb3VudCIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpgYGB7cn0KIyDnuL3lhbHmnInlj4PoiIfnmoTkurrlk6Eo5bCO5ryUJua8lOWToSkKYWxsX2NyZWRpdCA8LSBjKGRpcmVjdG9yJGRpcl9uYW1lLCBhY3RvciRjYXN0X25hbWUpCmxlbmd0aCh1bmlxdWUoYWxsX2NyZWRpdCkpCmBgYAoKYGBge3J9CiMg5pW055CG5omA5pyJ5Lq65ZOhCiMg5aaC5p6c5pyJ5Z+35bCO6YGO77yM5bCx5qiZ6Ki75LuW54iyZGlyCiMg5aaC5p6c5rKS5pyJ77yM5YmH5qiZ6Ki75LuW54iyYWN0CmFsbF9saXN0IDwtIGRhdGEuZnJhbWUobmFtZT11bmlxdWUoYWxsX2NyZWRpdCkpICU+JQogICAgICAgICAgICAgIG11dGF0ZSh0eXBlPWlmZWxzZShuYW1lICVpbiUgZGlyZWN0b3IkZGlyX25hbWUsICJkaXIiLCAiYWN0IikpCmFsbF9saXN0CmBgYAoKYGBge3J9CiMg5pW055CG5omA5pyJ5bCO5ryU5pyJ5Z+35bCO6YGO55qE5ryU5ZOhCmFjdCA8LSByaWdodF9qb2luKGFjdG9yLCBkaXJlY3RvcikgJT4lIAogIHNlbGVjdChtb3ZpZV9pZCwgdGl0bGUsIGNhc3RfbmFtZSwgZGlyX25hbWUsIG9yZGVyKQphY3QKYGBgCgpgYGB7cn0KIyDmjJHlh7pjYXN0X25hbWUo5ryU5ZOh5ZCN5a2XKeOAgWRpcl9uYW1lKOWwjua8lOWQjeWtlynjgIFtb3ZpZV9pZCDkuInlgIvmrITkvY0KIyDngrrkuobmuJvlsJHos4fmlpnph4/vvIzpgY7mv77mjokgb3JkZXIgPCAzIOeahOizh+aWme+8jG9yZGVy5Luj6KGo5Zyo6Kmy6Zu75b2x6KOh6KeS6Imy55qE6YeN6KaB5oCn77yM6LaK5bCP5Luj6KGo6LaK6YeN6KaBCmxpbmsgPC0gYWN0ICU+JSAKICBmaWx0ZXIob3JkZXIgPCAzKSAlPiUgCiAgc2VsZWN0KGNhc3RfbmFtZSwgZGlyX25hbWUsIG1vdmllX2lkKQpsaW5rCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCmFjdF9OZXR3b3JrIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkPWxpbmssIGRpcmVjdGVkPVQpCmFjdF9OZXR3b3JrCmBgYAoKYGBge3J9CiMg55Wr5Ye657ay6Lev5ZyWCnBsb3QoYWN0X05ldHdvcmspCmBgYAo+IOeci+S4jeWHuuS7gOm6vOadseilv+OAggoKYGBge3J9CiMg5bCH6bue55qE5aSn5bCP5ZKM57ea55qE57KX57Sw6Kq/5bCP77yM5Lim5LiN6aGv56S65omA5pyJ5Lq65ZOh55qE5ZCN5a2X44CCCnBsb3QoYWN0X05ldHdvcmssIHZlcnRleC5zaXplPTIsIGVkZ2UuYXJyb3cuc2l6ZT0uMiwgdmVydGV4LmxhYmVsPU5BKQpgYGAKPiDlj6/ku6Xnmbznj77mr5TovIPmnInotqPnmoTkuIDpu57vvJrnmbznj77mnInkuI3lsJHpg6jpm7vlvbHnmoTlsI7mvJTmmK/oh6rlsI7oh6rmvJTjgIIKCiMjIOmbu+W9seWIhumhngo+IOagueaTmuWJjemdoueahOe2sui3r+WcluWPr+S7peeZvOePvuizh+aWmemHj+WkquWkp++8jDQ4MDDpg6jpm7vlvbHjgIE1NDIwMeS9jea8lOWToeOAgTI1NzfkvY3lsI7mvJTvvIzlvojpm6Plvp7lnJbkuK3mib7lh7rpl5zkv4LvvIw8YnI+Cj4g5Zug5q2k5oiR5YCR5b6ea2FnZ2xl6Zu75b2x6LOH5paZ6ZuG55qE5Y+m5LiA6YOo5Lu977ya5q+P6YOo6Zu75b2x55qE55u46Zec6LOH5paZKOWMheaLrOmbu+W9semgkOeul+OAgemhnuWei+OAgeelqOaIv+OAgeeJh+mVt+etiSnvvIwKPiDku6XlhbbkuK3nmoTmr4/pg6jpm7vlvbHnmoTpoZ7lnovkvobpgLLooYzliIbpoZ4o5YuV5L2c44CB56eR5bm744CB5YaS6Zqq562JKe+8jOiXieatpOS7peS4jeWQjOeahOmbu+W9semhnuWei+S+humAsuihjOe2sui3r+WclueahOe5quijveOAggoKYGBge3J9CiMg6K6A5YWl6LOH5paZ6ZuGCm1vdmllcyA9IHJlYWRfY3N2KCJ0bWRiXzUwMDBfbW92aWVzLmNzdiIpCm1vdmllcwpgYGAKCmBgYHtyfQojIOWwh+aJgOWxrOmhnuWei+eahOizh+aWmeW+nkpTT07moLzlvI/kuK3ovYnmiJB0aWR55qC85byP44CCCiMg6KO95L2c6Zu75b2x5Yip5r2k6K6K5pW4Cm1fZ2VucmVzIDwtIG1vdmllcyAlPiUgCiAgZmlsdGVyKG5jaGFyKGdlbnJlcyk+MikgJT4lIAogIG11dGF0ZShqcz1sYXBwbHkoZ2VucmVzLGZyb21KU09OKSkgJT4lIAogIHVubmVzdChqcykgJT4lIAogIG11dGF0ZShtb3ZpZV9pZCA9IGlkLCBnZW5yZSA9IG5hbWUsIFJPST0ocmV2ZW51ZS1idWRnZXQpL2J1ZGdldCkgJT4lIAogIHNlbGVjdChtb3ZpZV9pZCwgdGl0bGUsIGdlbnJlLFJPSSkKbV9nZW5yZXMKYGBgCgpgYGB7cn0KcmV2ZW51ZV9kYXRhPC1tb3ZpZXMgJT4lCiAgZmlsdGVyKGJ1ZGdldCA+PSAxMDAwMDAwICYgcmV2ZW51ZSA+PSAxMDAwMDAwKSAlPiUKICBtdXRhdGUoUk9JID0gKHJldmVudWUgLSBidWRnZXQpL2J1ZGdldCkKCiPpoJDnrpcKcmV2ZW51ZV9kYXRhICU+JQogIHNlbGVjdCh0aXRsZSwgYnVkZ2V0KSAlPiUKICBhcnJhbmdlKGRlc2MoYnVkZ2V0KSkgJT4lCiAgaGVhZCgxNSklPiUKICBnZ3Bsb3QoLiwgYWVzKHggPSByZW9yZGVyKHRpdGxlLCAtZGVzYyhidWRnZXQpKSwgeSA9IGJ1ZGdldC8xMDAwMDAwKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSsgCiAgZ2d0aXRsZSgi6Zu75b2x6aCQ566XIikrCiAgeGxhYigiIikrCiAgeWxhYigi6aCQ566X6YeR6aGNICjnmb7okKwpIikgKyAKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKYGBge3J9CiPmlLblhaUKcmV2ZW51ZV9kYXRhICU+JQogIHNlbGVjdCh0aXRsZSwgcmV2ZW51ZSkgJT4lCiAgYXJyYW5nZShkZXNjKHJldmVudWUpKSAlPiUKICBoZWFkKDE1KSU+JQogIGdncGxvdCguLCBhZXMoeCA9IChyZW9yZGVyKHRpdGxlLCAtZGVzYyhyZXZlbnVlKSkpLCB5ID0gcmV2ZW51ZS8xMDAwMDAwKSkgKwogIGdlb21fYmFyKCBzdGF0ID0gImlkZW50aXR5IikrIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChoanVzdD0xKSkrCiAgZ2d0aXRsZSgiTW92aWUgUmV2ZW51ZXMiKSsKICB4bGFiKCIiKSsKICB5bGFiKCJSZXZlbnVlIChpbiBNaWxsaW9ucykiKSArIAogIGNvb3JkX2ZsaXAoKSAKYGBgCgpgYGB7cn0KIyMg5oqV6LOH5aCx6YWs546HCnJldmVudWVfZGF0YSAlPiUKICBzZWxlY3QodGl0bGUsIGJ1ZGdldCwgcmV2ZW51ZSwgUk9JKSAlPiUKICBhcnJhbmdlKGRlc2MoUk9JKSkgJT4lCiAgaGVhZCgxNSklPiUKICBnZ3Bsb3QoLiwgYWVzKHggPSByZW9yZGVyKHRpdGxlLCAtZGVzYyhST0kpKSwgeSA9IFJPSSkpICsKICBnZW9tX2Jhciggc3RhdCA9ICJpZGVudGl0eSIpKyAKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoaGp1c3Q9MSkpKwogIGdndGl0bGUoIk1vdmllIFJPSSIpKwogIHhsYWIoIiIpKwogIHlsYWIoIlJPSSIpICsgCiAgY29vcmRfZmxpcCgpCmBgYAo+IOaKleizh+WgsemFrOeOh+mrmOeahOmbu+W9seW5tOS7o+mDveavlOi8g+S5hemBoAoKYGBge3J9CiMjIOmAj+mBjuaZgumWk+ioiOeul+mbu+W9seeZvOW4g+eahOWAi+aVuApsaWJyYXJ5KGx1YnJpZGF0ZSkKcmV2ZW51ZV9kYXRhICU+JQogZmlsdGVyKHllYXIocmVsZWFzZV9kYXRlKSA+IDE5OTApICU+JSAgCiBncm91cF9ieSh5ZWFyID0geWVhcihyZWxlYXNlX2RhdGUpLCBtb250aCA9IG1vbnRoKHJlbGVhc2VfZGF0ZSkpICU+JQogc3VtbWFyaXNlKGNvdW50ID0gbigpKSU+JSAKIGdncGxvdChhZXMoeWVhciwgYXMuZmFjdG9yKG1vbnRoKSkpKwogZ2VvbV90aWxlKGFlcyhmaWxsPWNvdW50KSxjb2xvdXI9IndoaXRlIikrCiBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0ibGlnaHQgYmx1ZSIsaGlnaCA9ICJkYXJrIGJsdWUiKSArCiB4bGFiKCLpm7vlvbHnmbzluIPlubTku70iKSsKIHlsYWIoIumbu+W9seeZvOW4g+aciOS7vSIpKwogZ2d0aXRsZSgiSGVhdCBNYXAiKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkKYGBgCj4g5b6eMTk5MC0yMDEw6Zu75b2x5LiK5pig55qE5pW46YeP6YCQ5bm05aKe5Yqg77yM5bCk5YW25Lul5Lmd5pyI5Lu95LiK5pig55qE6Zu75b2x5pW46YeP5YGP5aSaCgpgYGB7cn0KIyDlsIfos4fmlpnlkIjkvbXliLDliZvliZvnmoTlsI7mvJTlkozmvJTlk6HlkI3llq4KYWN0X2dlbnJlIDwtIHJpZ2h0X2pvaW4oYWN0LCBtX2dlbnJlcykKYWN0X2dlbnJlCmBgYAoKYGBge3J9CiMg6LOH5paZ6ZuG5Lit5omA5pyJ55qE5YiG6aGeCnVuaXF1ZShhY3RfZ2VucmUkZ2VucmUpCmBgYAoKYGBge3J9CiMg6YCZ6KOh54K65LqG6ZmN5L2O6LOH5paZ6YeP77yM5YGa5LqG5YWp5YCL6ZmQ5Yi277yaCiMgMS5vcmRlciA8IDPvvIxvcmRlciDotorlsI/ooajnpLrlnKjliofkuK3otorph43opoHvvIzkuqbljbPkuLvmvJTjgIIKIyAyLmdlbnJlID09ICJBY3Rpb24i77yM5om+5Ye654K65YuV5L2c54mH55qE6Zu75b2x44CCCmxpbmtfZ19hY3Rpb24gPC0gYWN0X2dlbnJlICU+JSAKICBmaWx0ZXIob3JkZXIgPCAzKSAlPiUgCiAgZmlsdGVyKGdlbnJlID09ICJBY3Rpb24iKSAlPiUgCiAgc2VsZWN0KGNhc3RfbmFtZSwgZGlyX25hbWUsIG1vdmllX2lkLCBnZW5yZSwgUk9JKQpsaW5rX2dfYWN0aW9uCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCmdfYWN0aW9uX05ldHdvcmsgPC0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGQ9bGlua19nX2FjdGlvbiwgZGlyZWN0ZWQ9VCkKZ19hY3Rpb25fTmV0d29yawpgYGAKCmBgYHtyfQojIOeVq+WHuue2sui3r+WclgpwbG90KGdfYWN0aW9uX05ldHdvcmspCmBgYAo+IOmChOaYr+eci+S4jeWHuuS7gOm6vOOAggoKYGBge3J9CiMg5bCH6bue55qE5aSn5bCP5ZKM57ea55qE57KX57Sw6Kq/5bCP77yM5Lim5LiN6aGv56S65omA5pyJ5Lq65ZOh55qE5ZCN5a2X44CCCiMg5aaC5p6c5ZCI5L2c55qE6Zu75b2x5pyJ6LO66Yyi5qiZ6KiY57ag6Imy77yM5rKS6LO66Yyi5qiZ6KiY57SF6ImyCiMgRShnX2FjdGlvbl9OZXR3b3JrKSRjb2xvciA8LSBpZmVsc2UoRShnX2FjdGlvbl9OZXR3b3JrKSRST0k+bWVhbihFKGdfYWN0aW9uX05ldHdvcmspJFJPSSkgLCAibGlnaHRncmVlbiIsICJwYWxldmlvbGV0cmVkIikKcGxvdChnX2FjdGlvbl9OZXR3b3JrLCB2ZXJ0ZXguc2l6ZT0yLCBlZGdlLmFycm93LnNpemU9LjIgLCB2ZXJ0ZXgubGFiZWwuY2V4PTAuOCwgdmVydGV4LmxhYmVsPSBOQSkKYGBgCj4g5Y+v5Lul55yL5Ye65piO6aGv5q+U5pyq5YGa6ZmQ5Yi255qE57WQ5p6c56iA55aP6Kix5aSa77yM5LiN6YGO6YKE5piv55yL5LiN5Ye65LuA6bq844CCCgojIyDlh7rmvJTmrKHmlbgKPiDlm6Dngrros4fmlpnph4/pgoTmmK/lpKrlpKfvvIzpgJnoo6HmiJHlgJHpgbjmk4fku6Xlh7rmvJTmrKHmlbjngrrpmZDliLbvvIzmib7lh7rlpJrnlKLnmoTmvJTlk6HoiIflsI7mvJTkuYvplpPnmoTpl5zkv4LjgIIKCmBgYHtyfQojIOaJvuWHuuWHuua8lOasoeaVuOWkp+aWvDEw55qE5ryU5ZOhCm1vc3RfY2FzdCA8LSBsaW5rX2dfYWN0aW9uICU+JSAKICBkaXN0aW5jdChtb3ZpZV9pZCwgY2FzdF9uYW1lLCAua2VlcF9hbGwgPSBUKSAlPiUgI+aOkumZpOS4gOWAi+mbu+W9seWPr+iDveacg+acieWFqeWAi2RpcmVjdG9ycwogIGNvdW50KGNhc3RfbmFtZSwgc29ydCA9IFRSVUUpICU+JSAKICBmaWx0ZXIobiA+PSAxMCkKbW9zdF9jYXN0CmBgYAoKYGBge3J9CiMg5om+5Ye65Z+35bCO5qyh5pW45aSn5pa8NeeahOWwjua8lAptb3N0X2RpciA8LSBsaW5rX2dfYWN0aW9uICU+JSAKICBkaXN0aW5jdChtb3ZpZV9pZCwgZGlyX25hbWUsIC5rZWVwX2FsbCA9IFQpICU+JSAKICBjb3VudChkaXJfbmFtZSwgc29ydCA9IFRSVUUpICU+JSAKICBmaWx0ZXIobiA+PSA1KQptb3N0X2RpcgpgYGAKCmBgYHtyfQojIOaJvuWHuuOAjOWHuua8lOasoeaVuOWkp+aWvDEw55qE5ryU5ZOh44CN5LiU44CM5Z+35bCO5qyh5pW45aSn5pa8NeeahOWwjua8lOOAjeeahOe1hOWQiApsaW1fbGlua19nX2FjdGlvbiA8LSBsaW5rX2dfYWN0aW9uICU+JSAKICBmaWx0ZXIoLiRjYXN0X25hbWUgJWluJSBtb3N0X2Nhc3QkY2FzdF9uYW1lICYgLiRkaXJfbmFtZSAlaW4lIG1vc3RfZGlyJGRpcl9uYW1lKQpsaW1fbGlua19nX2FjdGlvbgpgYGAKCmBgYHtyfQojIOevqemBuOWHuuS4iuS4gOatpempn+S4reacieWHuuePvueahOe1hOWQiApmaWx0ZXJlZF9uYW1lIDwtIGFsbF9saXN0ICU+JQogICAgICAgICAgZmlsdGVyKG5hbWUgJWluJSBsaW1fbGlua19nX2FjdGlvbiRjYXN0X25hbWUgfCBuYW1lICVpbiUgbGltX2xpbmtfZ19hY3Rpb24kZGlyX25hbWUpICU+JQogICAgICAgICAgYXJyYW5nZShkZXNjKHR5cGUpKQpmaWx0ZXJlZF9uYW1lCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCmxpbV9nX2FjdGlvbl9OZXR3b3JrIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkPWxpbV9saW5rX2dfYWN0aW9uLCB2PWZpbHRlcmVkX25hbWUsIGRpcmVjdGVkPVQpCmxpbV9nX2FjdGlvbl9OZXR3b3JrCmBgYAoKYGBge3J9CiMg57mq5ZyWCnNldC5zZWVkKDEyMzQ1KQpwbG90KGxpbV9nX2FjdGlvbl9OZXR3b3JrLCB2ZXJ0ZXguc2l6ZT0yLCBlZGdlLmFycm93LnNpemU9LjIsIHZlcnRleC5sYWJlbC5jZXg9MC44KQpgYGAKPiDpgoTmmK/nqI3lvq7mnInpu57lpKrlpJrvvIzmlLnmiJDlj6rmqJnnpLrovIPmnInpl5zoga/nmoTpu54oPj01KeOAggoKYGBge3J9CiMg5bCH5pyJ5Z+35bCO6YGO5Lq655qE6bue5pS554K66YeR6ImyCiMg5Zau57SU5piv5ryU5ZOh55qE6bue5pS554K65aSp6JeN6ImyCiMg5ZCI5L2c6LyD6LO66Yyi6YKK6Zqb6Kit54K657ag6ImyKFJPST7lhajpg6jlubPlnYcpCiMg6LyD5rKS6LO66Yyi5qiZ6KiY57SF6ImyKFJPSTw95YWo6YOo5bmz5Z2HKQojIOmhr+ekuuaciei2hemBjjXlgIvpl5zoga/nmoTpu54Kc2V0LnNlZWQoMTIzNDUpCgpsYWJlbHMgPC0gZGVncmVlKGxpbV9nX2FjdGlvbl9OZXR3b3JrKQpWKGxpbV9nX2FjdGlvbl9OZXR3b3JrKSRsYWJlbCA8LSBuYW1lcyhsYWJlbHMpCkUobGltX2dfYWN0aW9uX05ldHdvcmspJGNvbG9yIDwtIGlmZWxzZShFKGxpbV9nX2FjdGlvbl9OZXR3b3JrKSRST0k+bWVhbihFKGxpbV9nX2FjdGlvbl9OZXR3b3JrKSRST0kpICwgImxpZ2h0Z3JlZW4iLCAicGFsZXZpb2xldHJlZCIpClYobGltX2dfYWN0aW9uX05ldHdvcmspJGNvbG9yIDwtIGlmZWxzZShWKGxpbV9nX2FjdGlvbl9OZXR3b3JrKSR0eXBlID09ICJkaXIiLCAiZ29sZCIsICJsaWdodGJsdWUiKQpwbG90KGxpbV9nX2FjdGlvbl9OZXR3b3JrLCB2ZXJ0ZXguc2l6ZT01LCBlZGdlLmFycm93LnNpemU9LjIgLCB2ZXJ0ZXgubGFiZWwuY2V4PTAuOCwgCiAgICAgdmVydGV4LmxhYmVsPWlmZWxzZShkZWdyZWUobGltX2dfYWN0aW9uX05ldHdvcmspID49IDUsIFYobGltX2dfYWN0aW9uX05ldHdvcmspJGxhYmVsLCBOQSkpCmBgYAo+IOmAj+mBjumAmeW8teWcluaIkeWAkeWPr+S7peeci+WHuu+8jOWcqOWLleS9nOeJh+eahOmAmeWAi+evhOeWh+S4i++8jOa8lOWToee2k+W4uOi3n+WTquS6m+Wwjua8lOWQiOS9nOaLjeaIsu+8jOS7peWPiuWwjua8lOi3n+a8lOWToeWQiOS9nOeahOmbu+W9seeahOaKleizh+WgsemFrOeOh+mrmOS9juOAggo=