# This is pre-processing you can ignore
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.1.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(knitr)
library(rsample)
data <- read_csv("https://github.com/diagram-chasing/censor-board-cuts/raw/refs/heads/master/data/data.csv",
col_types = cols(.default = "c")) %>%
mutate(
cert_date = as.Date(cert_date),
total_modified_time_secs = as.numeric(total_modified_time_secs),
deleted_secs = as.numeric(deleted_secs),
replaced_secs = as.numeric(replaced_secs),
inserted_secs = as.numeric(inserted_secs)
) %>%
mutate(
office = str_split(certifier, ",") %>%
map_chr(last) %>%
str_trim()
) %>%
separate_rows(ai_content_types, sep = "\\|") %>%
mutate(ai_content_types = str_trim(ai_content_types))%>%
mutate(
language = case_when(
language == "Oriya" ~ "Odia",
language == "Gujrati" ~ "Gujarati",
language == "Chhatisgarhi" ~ "Chhattisgarhi",
language == "Hariyanvi" ~ "Haryanvi",
language == "Hindi Dub" ~ "Hindi Dubbed",
TRUE ~ language
)
) %>%
filter(!is.na(language))
top_languages <- data %>%
filter(!is.na(language), language != "") %>%
distinct(certificate_id, language) %>%
count(language) %>%
slice_max(order_by = n, n = 15) %>%
pull(language)
Percentage of action
types
data %>%
filter(!is.na(ai_action)) %>%
count(ai_action, name = "count", sort = TRUE) %>%
mutate(`Percentage (%)` = count / sum(count) * 100) %>%
rename(`Action Type` = ai_action, Count = count) %>%
kable(digits = 1)
deletion |
45443 |
43.7 |
audio_modification |
22247 |
21.4 |
insertion |
15071 |
14.5 |
visual_modification |
8326 |
8.0 |
replacement |
6609 |
6.4 |
text_modification |
5740 |
5.5 |
content_overlay |
439 |
0.4 |
Percentage of content
altered by type
data %>%
filter(!is.na(ai_media_element)) %>%
count(ai_media_element, name = "count", sort = TRUE) %>%
mutate(`Percentage (%)` = count / sum(count) * 100) %>%
rename(`Media Type` = ai_media_element, Count = count) %>%
kable(digits = 1)
visual_scene |
47869 |
46.1 |
text_dialogue |
35207 |
33.9 |
metadata |
16034 |
15.4 |
music |
4211 |
4.1 |
other |
554 |
0.5 |
Share of ratings by
language
ratings_by_language <- data %>%
mutate(rating_group = case_when(
rating %in% c("UA", "UA 7+", "UA 13+", "UA 16+") ~ "General Audience (U/UA)",
rating == "A" ~ "Adults Only (A)",
rating == "U" ~ "Unrestricted (U)",
rating == "S" ~ "S",
TRUE ~ "NA / Other"
)) %>%
filter(language %in% top_languages) %>%
distinct(certificate_id, language, rating_group) %>%
count(language, rating_group) %>%
group_by(language) %>%
mutate(percentage = n / sum(n)) %>%
ungroup() %>%
select(-n) %>%
pivot_wider(names_from = rating_group, values_from = percentage, values_fill = 0) %>%
mutate(across(where(is.numeric), ~ .x * 100))
ratings_by_language%>%
kable(digits = 1)
Bengali |
7.2 |
74.7 |
18.1 |
0.0 |
0 |
Bhojpuri |
9.0 |
85.1 |
5.9 |
0.0 |
0 |
Chhattisgarhi |
3.7 |
72.0 |
24.4 |
0.0 |
0 |
English |
16.1 |
72.3 |
11.7 |
0.0 |
0 |
Gujarati |
2.2 |
71.9 |
26.0 |
0.0 |
0 |
Hindi |
9.5 |
78.1 |
12.2 |
0.3 |
0 |
Hindustani |
0.9 |
93.0 |
6.0 |
0.0 |
0 |
Kannada |
11.2 |
71.3 |
17.5 |
0.0 |
0 |
Malayalam |
4.8 |
60.1 |
34.8 |
0.3 |
0 |
Marathi |
6.9 |
73.8 |
19.3 |
0.0 |
0 |
Odia |
1.1 |
56.8 |
42.0 |
0.0 |
0 |
Punjabi |
5.6 |
69.4 |
25.0 |
0.0 |
0 |
Tamil |
6.6 |
67.7 |
25.4 |
0.4 |
0 |
Telugu |
12.4 |
74.7 |
12.9 |
0.0 |
0 |
Urdu |
1.4 |
91.3 |
7.2 |
0.0 |
0 |
Duration modified by
language
Total time modified by language:
In your SQL code, you grouped it by rating which I have not done; I
have just calculated by grouping movies by language. But the values are
comparable.
movie_durations <- data %>%
filter(!is.na(language), language != "") %>%
distinct(certificate_id, language, duration_secs) %>%
mutate(duration_secs = as.numeric(duration_secs)) %>%
group_by(language) %>%
summarise(total_movie_secs = sum(duration_secs, na.rm = TRUE))
modified_durations <- data %>%
mutate(total_modified_time_secs = as.numeric(total_modified_time_secs)) %>%
filter(!is.na(language), language != "") %>%
group_by(language) %>%
summarise(total_modified_secs = sum(total_modified_time_secs, na.rm = TRUE))
language_summary <- movie_durations %>%
full_join(modified_durations, by = "language") %>%
mutate(
`Total Movie Duration (Hours)` = total_movie_secs / 3600,
`Total Modified Duration (Hours)` = total_modified_secs / 3600,
`Percent Modified (%)` = (total_modified_secs / total_movie_secs) * 100
) %>%
select(
Language = language,
`Total Movie Duration (Hours)`,
`Total Modified Duration (Hours)`,
`Percent Modified (%)`
) %>%
filter(`Language` %in% top_languages) %>%
arrange(desc(`Total Movie Duration (Hours)`))
language_summary %>%
kable(digits = 1)
Hindi |
8351.4 |
192.6 |
2.3 |
Tamil |
5534.6 |
63.3 |
1.1 |
Telugu |
5405.3 |
55.8 |
1.0 |
Kannada |
4223.3 |
52.9 |
1.3 |
English |
2876.2 |
63.9 |
2.2 |
Malayalam |
2852.5 |
53.2 |
1.9 |
Bhojpuri |
2358.2 |
84.1 |
3.6 |
Marathi |
1520.9 |
26.3 |
1.7 |
Bengali |
1033.8 |
27.9 |
2.7 |
Gujarati |
775.1 |
4.9 |
0.6 |
Odia |
557.9 |
11.9 |
2.1 |
Punjabi |
548.4 |
5.3 |
1.0 |
Hindustani |
471.8 |
64.5 |
13.7 |
Chhattisgarhi |
191.1 |
0.8 |
0.4 |
Urdu |
143.4 |
17.6 |
12.3 |
Average time modified by language:
movie_modifications <- data %>%
filter(!is.na(language), language != "", total_modified_time_secs > 0) %>%
group_by(certificate_id, language) %>%
summarize(
total_duration_modified = sum(total_modified_time_secs, na.rm = TRUE),
.groups = "drop"
)
top_languages <- movie_modifications %>%
count(language) %>%
slice_max(order_by = n, n = 10) %>%
pull(language)
movie_modifications %>%
filter(language %in% top_languages) %>%
bootstraps(times = 2000) %>%
mutate(movie_data = map(splits, analysis)) %>%
unnest(movie_data) %>%
group_by(language, id) %>%
summarize(avg_duration = mean(total_duration_modified), .groups = "drop") %>%
group_by(language) %>%
summarize(
mean = mean(avg_duration),
conf.low = quantile(avg_duration, 0.025),
conf.high = quantile(avg_duration, 0.975)
) %>%
mutate(across(c(mean, conf.low, conf.high), ~ .x / 60)) %>%
arrange(desc(mean))%>%
kable(digits = 1)
Hindustani |
18.0 |
16.9 |
19.1 |
Bhojpuri |
6.5 |
5.9 |
7.1 |
Bengali |
5.4 |
4.5 |
6.2 |
Malayalam |
4.0 |
3.5 |
4.6 |
Marathi |
3.8 |
3.1 |
4.4 |
Hindi |
3.7 |
3.5 |
3.9 |
Kannada |
3.4 |
1.8 |
6.2 |
English |
2.6 |
2.5 |
2.8 |
Telugu |
2.6 |
2.4 |
2.9 |
Tamil |
2.3 |
2.1 |
2.5 |
Avg modifications per
film (by language)
I generally avoid saying cuts because these could just be visual
disclaimers being added and so on, not necessarily a
deletion/replacement. Also mapped with confidence intervals based on
this example: https://github.com/dgrtwo/data-screencasts/blob/master/bird-collisions.Rmd
avg_cuts_by_lang <- data %>%
filter(!is.na(language), language != "") %>%
group_by(language) %>%
summarise(
total_movies = n_distinct(certificate_id),
total_cuts = n(),
.groups = "drop"
) %>%
filter(total_movies > 50) %>%
mutate(avg_cuts = total_cuts / total_movies) %>%
arrange(desc(avg_cuts))
avg_cuts_by_lang %>%
kable(digits = 1)
English |
1687 |
16446 |
9.7 |
Tamil |
2671 |
16309 |
6.1 |
Bhojpuri |
1031 |
6240 |
6.1 |
Telugu |
2558 |
14745 |
5.8 |
Bengali |
498 |
2714 |
5.4 |
Hindustani |
215 |
1078 |
5.0 |
Hindi |
4178 |
20915 |
5.0 |
Kannada |
1975 |
9886 |
5.0 |
Marathi |
737 |
3584 |
4.9 |
Urdu |
69 |
325 |
4.7 |
Malayalam |
1338 |
5683 |
4.2 |
Punjabi |
268 |
1084 |
4.0 |
Gujarati |
366 |
1413 |
3.9 |
Chhattisgarhi |
82 |
315 |
3.8 |
Odia |
264 |
972 |
3.7 |
Maybe graphing these with confidence intervals? Which ones have the
highest spread?
library(tidyverse)
library(rsample)
set.seed(2025)
movie_cuts <- data %>%
filter(!is.na(language), language != "") %>%
count(certificate_id, language, name = "n_cuts")
top_languages <- movie_cuts %>%
count(language) %>%
slice_max(order_by = n, n = 15) %>%
pull(language)
movie_cuts %>%
filter(language %in% top_languages) %>%
bootstraps(times = 2000) %>%
mutate(movie_data = map(splits, analysis)) %>%
unnest(movie_data) %>%
group_by(language, id) %>%
summarize(avg_cuts = mean(n_cuts), .groups = "drop") %>%
group_by(language) %>%
summarize(
mean = mean(avg_cuts),
conf.low = quantile(avg_cuts, 0.025),
conf.high = quantile(avg_cuts, 0.975)
) %>%
mutate(language = fct_reorder(language, mean)) %>%
ggplot(aes(x = mean, y = language)) +
geom_vline(
xintercept = mean(movie_cuts$n_cuts),
linetype = "dashed",
color = "gray50"
) +
geom_errorbarh(
aes(xmin = conf.low, xmax = conf.high),
height = 0.2,
color = "gray70"
) +
geom_point(
aes(color = mean > mean(movie_cuts$n_cuts)),
size = 3
) +
scale_color_manual(guide = "none", values = c("FALSE" = "#0072B2", "TRUE" = "#D55E00")) +
labs(
title = "Which Languages Receive More Censor Cuts?",
subtitle = "95% confidence intervals for the average number of cuts per film.",
x = "Average Cuts per Movie",
y = "Language"
) +
theme_minimal(base_family = "sans") +
theme(
panel.grid.major.y = element_blank()
)

Languages certified by
each office
Just curious about which offices modify which languages
valid_offices <- c("Mumbai", "Kolkata", "Hyderabad", "Guwahati", "Delhi",
"Cuttack", "Chennai", "Bangalore", "Thiruvananthpuram")
top_n_languages <- 15
data %>%
mutate(
office = str_trim(office),
office = str_to_title(office)
) %>%
distinct(id, language, office) %>%
filter(office %in% valid_offices, !is.na(language), language != "") %>%
count(office, language, name = "count") %>%
mutate(language = fct_lump_n(language, n = top_n_languages, w = count)) %>%
filter(language != "Other") %>%
mutate(
office = fct_reorder(office, count, .fun = sum),
language = fct_reorder(language, count, .fun = sum, .desc = TRUE)
) %>%
ggplot(aes(x = language, y = office, fill = count)) +
geom_tile(color = "gray20", linewidth = 0.4) +
geom_text(
aes(label = count, color = count > 1200),
size = 2.75,
fontface = "bold"
) +
scale_fill_viridis_c(
option = "magma",
name = "Movie Count",
direction = -1
) +
scale_color_manual(guide = "none", values = c("FALSE" = "black", "TRUE" = "white")) +
labs(
title = paste("Frequency of Film Language by Certifying Office"),
x = "",
y = ""
) +
theme_minimal(base_family = "sans") +
theme(
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
plot.subtitle = element_text(hjust = 0.5, size = 12, color = "gray40"),
axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1),
panel.grid = element_blank(),
legend.position = "none"
)

LS0tCnRpdGxlOiAiQ0JGQyBXYXRjaCBTdW1tYXJpZXMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IHJlYWRhYmxlCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGZpZ193aWR0aDogMTAKICAgIGZpZ19oZWlnaHQ6IDYKICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAga2VlcF9tZDogZmFsc2UKZWRpdG9yX29wdGlvbnM6CiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgpgYGB7cn0KCiMgVGhpcyBpcyBwcmUtcHJvY2Vzc2luZyB5b3UgY2FuIGlnbm9yZQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShrbml0cikKbGlicmFyeShyc2FtcGxlKQpkYXRhIDwtIHJlYWRfY3N2KCJodHRwczovL2dpdGh1Yi5jb20vZGlhZ3JhbS1jaGFzaW5nL2NlbnNvci1ib2FyZC1jdXRzL3Jhdy9yZWZzL2hlYWRzL21hc3Rlci9kYXRhL2RhdGEuY3N2IiwgCiAgICAgICAgICAgICAgICAgY29sX3R5cGVzID0gY29scyguZGVmYXVsdCA9ICJjIikpICU+JQogIG11dGF0ZSgKICAgIGNlcnRfZGF0ZSA9IGFzLkRhdGUoY2VydF9kYXRlKSwKICAgIHRvdGFsX21vZGlmaWVkX3RpbWVfc2VjcyA9IGFzLm51bWVyaWModG90YWxfbW9kaWZpZWRfdGltZV9zZWNzKSwKICAgIGRlbGV0ZWRfc2VjcyA9IGFzLm51bWVyaWMoZGVsZXRlZF9zZWNzKSwKICAgIHJlcGxhY2VkX3NlY3MgPSBhcy5udW1lcmljKHJlcGxhY2VkX3NlY3MpLAogICAgaW5zZXJ0ZWRfc2VjcyA9IGFzLm51bWVyaWMoaW5zZXJ0ZWRfc2VjcykKICApICU+JQogIG11dGF0ZSgKICAgIG9mZmljZSA9IHN0cl9zcGxpdChjZXJ0aWZpZXIsICIsIikgJT4lCiAgICAgIG1hcF9jaHIobGFzdCkgJT4lCiAgICAgIHN0cl90cmltKCkKICApICU+JQogIHNlcGFyYXRlX3Jvd3MoYWlfY29udGVudF90eXBlcywgc2VwID0gIlxcfCIpICU+JQogIG11dGF0ZShhaV9jb250ZW50X3R5cGVzID0gc3RyX3RyaW0oYWlfY29udGVudF90eXBlcykpJT4lCiAgbXV0YXRlKAogICAgbGFuZ3VhZ2UgPSBjYXNlX3doZW4oCiAgICAgIGxhbmd1YWdlID09ICJPcml5YSIgfiAiT2RpYSIsCiAgICAgIGxhbmd1YWdlID09ICJHdWpyYXRpIiB+ICJHdWphcmF0aSIsCiAgICAgIGxhbmd1YWdlID09ICJDaGhhdGlzZ2FyaGkiIH4gIkNoaGF0dGlzZ2FyaGkiLAogICAgICBsYW5ndWFnZSA9PSAiSGFyaXlhbnZpIiB+ICJIYXJ5YW52aSIsCiAgICAgIGxhbmd1YWdlID09ICJIaW5kaSBEdWIiIH4gIkhpbmRpIER1YmJlZCIsCiAgICAgIFRSVUUgfiBsYW5ndWFnZQogICAgKQogICkgJT4lCiAgZmlsdGVyKCFpcy5uYShsYW5ndWFnZSkpCgp0b3BfbGFuZ3VhZ2VzIDwtIGRhdGEgJT4lCiAgZmlsdGVyKCFpcy5uYShsYW5ndWFnZSksIGxhbmd1YWdlICE9ICIiKSAlPiUKICBkaXN0aW5jdChjZXJ0aWZpY2F0ZV9pZCwgbGFuZ3VhZ2UpICU+JQogIGNvdW50KGxhbmd1YWdlKSAlPiUKICBzbGljZV9tYXgob3JkZXJfYnkgPSBuLCBuID0gMTUpICU+JQogIHB1bGwobGFuZ3VhZ2UpCmBgYAoKIyMgUGVyY2VudGFnZSBvZiBhY3Rpb24gdHlwZXMKCmBgYHtyfQpkYXRhICU+JQogIGZpbHRlcighaXMubmEoYWlfYWN0aW9uKSkgJT4lCiAgY291bnQoYWlfYWN0aW9uLCBuYW1lID0gImNvdW50Iiwgc29ydCA9IFRSVUUpICU+JQogIG11dGF0ZShgUGVyY2VudGFnZSAoJSlgID0gY291bnQgLyBzdW0oY291bnQpICogMTAwKSAlPiUKICByZW5hbWUoYEFjdGlvbiBUeXBlYCA9IGFpX2FjdGlvbiwgQ291bnQgPSBjb3VudCkgJT4lCiAga2FibGUoZGlnaXRzID0gMSkKYGBgCgojIyBQZXJjZW50YWdlIG9mIGNvbnRlbnQgYWx0ZXJlZCBieSB0eXBlCmBgYHtyfQpkYXRhICU+JQogIGZpbHRlcighaXMubmEoYWlfbWVkaWFfZWxlbWVudCkpICU+JQogIGNvdW50KGFpX21lZGlhX2VsZW1lbnQsIG5hbWUgPSAiY291bnQiLCBzb3J0ID0gVFJVRSkgJT4lCiAgbXV0YXRlKGBQZXJjZW50YWdlICglKWAgPSBjb3VudCAvIHN1bShjb3VudCkgKiAxMDApICU+JQogIHJlbmFtZShgTWVkaWEgVHlwZWAgPSBhaV9tZWRpYV9lbGVtZW50LCBDb3VudCA9IGNvdW50KSAlPiUKICBrYWJsZShkaWdpdHMgPSAxKQpgYGAKCgojIyBTaGFyZSBvZiByYXRpbmdzIGJ5IGxhbmd1YWdlCgpgYGB7cn0KcmF0aW5nc19ieV9sYW5ndWFnZSA8LSBkYXRhICU+JQogIG11dGF0ZShyYXRpbmdfZ3JvdXAgPSBjYXNlX3doZW4oCiAgICByYXRpbmcgJWluJSBjKCJVQSIsICJVQSA3KyIsICJVQSAxMysiLCAiVUEgMTYrIikgfiAiR2VuZXJhbCBBdWRpZW5jZSAoVS9VQSkiLAogICAgcmF0aW5nID09ICJBIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAiQWR1bHRzIE9ubHkgKEEpIiwKICAgIHJhdGluZyA9PSAiVSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gIlVucmVzdHJpY3RlZCAoVSkiLAogICAgcmF0aW5nID09ICJTIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAiUyIsCiAgICBUUlVFICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+ICJOQSAvIE90aGVyIgogICkpICU+JSAKIGZpbHRlcihsYW5ndWFnZSAlaW4lIHRvcF9sYW5ndWFnZXMpICU+JQogIGRpc3RpbmN0KGNlcnRpZmljYXRlX2lkLCBsYW5ndWFnZSwgcmF0aW5nX2dyb3VwKSAlPiUKICBjb3VudChsYW5ndWFnZSwgcmF0aW5nX2dyb3VwKSAlPiUKICBncm91cF9ieShsYW5ndWFnZSkgJT4lCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSBuIC8gc3VtKG4pKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KC1uKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcmF0aW5nX2dyb3VwLCB2YWx1ZXNfZnJvbSA9IHBlcmNlbnRhZ2UsIHZhbHVlc19maWxsID0gMCkgJT4lIAogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIH4gLnggKiAxMDApKQoKcmF0aW5nc19ieV9sYW5ndWFnZSU+JQogIGthYmxlKGRpZ2l0cyA9IDEpCmBgYAoKCiMjIER1cmF0aW9uIG1vZGlmaWVkIGJ5IGxhbmd1YWdlCgpUb3RhbCB0aW1lIG1vZGlmaWVkIGJ5IGxhbmd1YWdlOgoKSW4geW91ciBTUUwgY29kZSwgeW91IGdyb3VwZWQgaXQgYnkgcmF0aW5nIHdoaWNoIEkgaGF2ZSBub3QgZG9uZTsgSSBoYXZlIGp1c3QgY2FsY3VsYXRlZCBieSBncm91cGluZyBtb3ZpZXMgYnkgbGFuZ3VhZ2UuIEJ1dCB0aGUgdmFsdWVzIGFyZSBjb21wYXJhYmxlLiAKCmBgYHtyfQptb3ZpZV9kdXJhdGlvbnMgPC0gZGF0YSAlPiUKICBmaWx0ZXIoIWlzLm5hKGxhbmd1YWdlKSwgbGFuZ3VhZ2UgIT0gIiIpICU+JQogIGRpc3RpbmN0KGNlcnRpZmljYXRlX2lkLCBsYW5ndWFnZSwgZHVyYXRpb25fc2VjcykgJT4lCiAgbXV0YXRlKGR1cmF0aW9uX3NlY3MgPSBhcy5udW1lcmljKGR1cmF0aW9uX3NlY3MpKSAlPiUKICBncm91cF9ieShsYW5ndWFnZSkgJT4lCiAgc3VtbWFyaXNlKHRvdGFsX21vdmllX3NlY3MgPSBzdW0oZHVyYXRpb25fc2VjcywgbmEucm0gPSBUUlVFKSkKCm1vZGlmaWVkX2R1cmF0aW9ucyA8LSBkYXRhICU+JQogIG11dGF0ZSh0b3RhbF9tb2RpZmllZF90aW1lX3NlY3MgPSBhcy5udW1lcmljKHRvdGFsX21vZGlmaWVkX3RpbWVfc2VjcykpICU+JQogIGZpbHRlcighaXMubmEobGFuZ3VhZ2UpLCBsYW5ndWFnZSAhPSAiIikgJT4lCiAgZ3JvdXBfYnkobGFuZ3VhZ2UpICU+JQogIHN1bW1hcmlzZSh0b3RhbF9tb2RpZmllZF9zZWNzID0gc3VtKHRvdGFsX21vZGlmaWVkX3RpbWVfc2VjcywgbmEucm0gPSBUUlVFKSkKCmxhbmd1YWdlX3N1bW1hcnkgPC0gbW92aWVfZHVyYXRpb25zICU+JQogIGZ1bGxfam9pbihtb2RpZmllZF9kdXJhdGlvbnMsIGJ5ID0gImxhbmd1YWdlIikgJT4lCiAgbXV0YXRlKAogICAgYFRvdGFsIE1vdmllIER1cmF0aW9uIChIb3VycylgID0gdG90YWxfbW92aWVfc2VjcyAvIDM2MDAsCiAgICBgVG90YWwgTW9kaWZpZWQgRHVyYXRpb24gKEhvdXJzKWAgPSB0b3RhbF9tb2RpZmllZF9zZWNzIC8gMzYwMCwKICAgIGBQZXJjZW50IE1vZGlmaWVkICglKWAgPSAodG90YWxfbW9kaWZpZWRfc2VjcyAvIHRvdGFsX21vdmllX3NlY3MpICogMTAwCiAgKSAlPiUKICBzZWxlY3QoCiAgICBMYW5ndWFnZSA9IGxhbmd1YWdlLAogICAgYFRvdGFsIE1vdmllIER1cmF0aW9uIChIb3VycylgLAogICAgYFRvdGFsIE1vZGlmaWVkIER1cmF0aW9uIChIb3VycylgLAogICAgYFBlcmNlbnQgTW9kaWZpZWQgKCUpYAogICkgJT4lCiAgZmlsdGVyKGBMYW5ndWFnZWAgJWluJSB0b3BfbGFuZ3VhZ2VzKSAlPiUgCiAgYXJyYW5nZShkZXNjKGBUb3RhbCBNb3ZpZSBEdXJhdGlvbiAoSG91cnMpYCkpCgpsYW5ndWFnZV9zdW1tYXJ5ICU+JQogIGthYmxlKGRpZ2l0cyA9IDEpCmBgYAoKCkF2ZXJhZ2UgdGltZSBtb2RpZmllZCBieSBsYW5ndWFnZToKYGBge3J9Cm1vdmllX21vZGlmaWNhdGlvbnMgPC0gZGF0YSAlPiUKICAgIGZpbHRlcighaXMubmEobGFuZ3VhZ2UpLCBsYW5ndWFnZSAhPSAiIiwgdG90YWxfbW9kaWZpZWRfdGltZV9zZWNzID4gMCkgJT4lCiAgICBncm91cF9ieShjZXJ0aWZpY2F0ZV9pZCwgbGFuZ3VhZ2UpICU+JQogICAgc3VtbWFyaXplKAogICAgICAgIHRvdGFsX2R1cmF0aW9uX21vZGlmaWVkID0gc3VtKHRvdGFsX21vZGlmaWVkX3RpbWVfc2VjcywgbmEucm0gPSBUUlVFKSwKICAgICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICApCnRvcF9sYW5ndWFnZXMgPC0gbW92aWVfbW9kaWZpY2F0aW9ucyAlPiUKICAgIGNvdW50KGxhbmd1YWdlKSAlPiUKICAgIHNsaWNlX21heChvcmRlcl9ieSA9IG4sIG4gPSAxMCkgJT4lCiAgICBwdWxsKGxhbmd1YWdlKQoKCm1vdmllX21vZGlmaWNhdGlvbnMgJT4lCiAgICBmaWx0ZXIobGFuZ3VhZ2UgJWluJSB0b3BfbGFuZ3VhZ2VzKSAlPiUKICAgIGJvb3RzdHJhcHModGltZXMgPSAyMDAwKSAlPiUKICAgIG11dGF0ZShtb3ZpZV9kYXRhID0gbWFwKHNwbGl0cywgYW5hbHlzaXMpKSAlPiUKICAgIHVubmVzdChtb3ZpZV9kYXRhKSAlPiUKICAgIGdyb3VwX2J5KGxhbmd1YWdlLCBpZCkgJT4lCiAgICBzdW1tYXJpemUoYXZnX2R1cmF0aW9uID0gbWVhbih0b3RhbF9kdXJhdGlvbl9tb2RpZmllZCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogICAgZ3JvdXBfYnkobGFuZ3VhZ2UpICU+JQogICAgc3VtbWFyaXplKAogICAgICAgIG1lYW4gPSBtZWFuKGF2Z19kdXJhdGlvbiksCiAgICAgICAgY29uZi5sb3cgPSBxdWFudGlsZShhdmdfZHVyYXRpb24sIDAuMDI1KSwKICAgICAgICBjb25mLmhpZ2ggPSBxdWFudGlsZShhdmdfZHVyYXRpb24sIDAuOTc1KQogICAgKSAlPiUKICAgIG11dGF0ZShhY3Jvc3MoYyhtZWFuLCBjb25mLmxvdywgY29uZi5oaWdoKSwgfiAueCAvIDYwKSkgJT4lCiAgICBhcnJhbmdlKGRlc2MobWVhbikpJT4lCiAga2FibGUoZGlnaXRzID0gMSkKYGBgCgoKIyMgQXZnIG1vZGlmaWNhdGlvbnMgcGVyIGZpbG0gKGJ5IGxhbmd1YWdlKQoKSSBnZW5lcmFsbHkgYXZvaWQgc2F5aW5nIGN1dHMgYmVjYXVzZSB0aGVzZSBjb3VsZCBqdXN0IGJlIHZpc3VhbCBkaXNjbGFpbWVycyBiZWluZyBhZGRlZCBhbmQgc28gb24sIG5vdCBuZWNlc3NhcmlseSBhIGRlbGV0aW9uL3JlcGxhY2VtZW50LiAKQWxzbyBtYXBwZWQgd2l0aCBjb25maWRlbmNlIGludGVydmFscyBiYXNlZCBvbiB0aGlzIGV4YW1wbGU6IGh0dHBzOi8vZ2l0aHViLmNvbS9kZ3J0d28vZGF0YS1zY3JlZW5jYXN0cy9ibG9iL21hc3Rlci9iaXJkLWNvbGxpc2lvbnMuUm1kCgpgYGB7cn0KYXZnX2N1dHNfYnlfbGFuZyA8LSBkYXRhICU+JQogIGZpbHRlcighaXMubmEobGFuZ3VhZ2UpLCBsYW5ndWFnZSAhPSAiIikgJT4lCiAgZ3JvdXBfYnkobGFuZ3VhZ2UpICU+JQogIHN1bW1hcmlzZSgKICAgIHRvdGFsX21vdmllcyA9IG5fZGlzdGluY3QoY2VydGlmaWNhdGVfaWQpLAogICAgdG90YWxfY3V0cyA9IG4oKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIGZpbHRlcih0b3RhbF9tb3ZpZXMgPiA1MCkgJT4lCiAgbXV0YXRlKGF2Z19jdXRzID0gdG90YWxfY3V0cyAvIHRvdGFsX21vdmllcykgJT4lCiAgYXJyYW5nZShkZXNjKGF2Z19jdXRzKSkKCmF2Z19jdXRzX2J5X2xhbmcgJT4lCiAga2FibGUoZGlnaXRzID0gMSkKCgpgYGAKCk1heWJlIGdyYXBoaW5nIHRoZXNlIHdpdGggY29uZmlkZW5jZSBpbnRlcnZhbHM/IFdoaWNoIG9uZXMgaGF2ZSB0aGUgaGlnaGVzdCBzcHJlYWQ/IAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJzYW1wbGUpCgpzZXQuc2VlZCgyMDI1KQoKbW92aWVfY3V0cyA8LSBkYXRhICU+JQogIGZpbHRlcighaXMubmEobGFuZ3VhZ2UpLCBsYW5ndWFnZSAhPSAiIikgJT4lCiAgY291bnQoY2VydGlmaWNhdGVfaWQsIGxhbmd1YWdlLCBuYW1lID0gIm5fY3V0cyIpCgp0b3BfbGFuZ3VhZ2VzIDwtIG1vdmllX2N1dHMgJT4lCiAgY291bnQobGFuZ3VhZ2UpICU+JQogIHNsaWNlX21heChvcmRlcl9ieSA9IG4sIG4gPSAxNSkgJT4lCiAgcHVsbChsYW5ndWFnZSkKCm1vdmllX2N1dHMgJT4lCiAgZmlsdGVyKGxhbmd1YWdlICVpbiUgdG9wX2xhbmd1YWdlcykgJT4lCiAgYm9vdHN0cmFwcyh0aW1lcyA9IDIwMDApICU+JQogIG11dGF0ZShtb3ZpZV9kYXRhID0gbWFwKHNwbGl0cywgYW5hbHlzaXMpKSAlPiUKICB1bm5lc3QobW92aWVfZGF0YSkgJT4lCiAgZ3JvdXBfYnkobGFuZ3VhZ2UsIGlkKSAlPiUKICBzdW1tYXJpemUoYXZnX2N1dHMgPSBtZWFuKG5fY3V0cyksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGdyb3VwX2J5KGxhbmd1YWdlKSAlPiUKICBzdW1tYXJpemUoCiAgICBtZWFuID0gbWVhbihhdmdfY3V0cyksCiAgICBjb25mLmxvdyA9IHF1YW50aWxlKGF2Z19jdXRzLCAwLjAyNSksCiAgICBjb25mLmhpZ2ggPSBxdWFudGlsZShhdmdfY3V0cywgMC45NzUpCiAgKSAlPiUKICBtdXRhdGUobGFuZ3VhZ2UgPSBmY3RfcmVvcmRlcihsYW5ndWFnZSwgbWVhbikpICU+JQogIGdncGxvdChhZXMoeCA9IG1lYW4sIHkgPSBsYW5ndWFnZSkpICsKICBnZW9tX3ZsaW5lKAogICAgeGludGVyY2VwdCA9IG1lYW4obW92aWVfY3V0cyRuX2N1dHMpLAogICAgbGluZXR5cGUgPSAiZGFzaGVkIiwKICAgIGNvbG9yID0gImdyYXk1MCIKICApICsKICBnZW9tX2Vycm9yYmFyaCgKICAgIGFlcyh4bWluID0gY29uZi5sb3csIHhtYXggPSBjb25mLmhpZ2gpLAogICAgaGVpZ2h0ID0gMC4yLAogICAgY29sb3IgPSAiZ3JheTcwIgogICkgKwogIGdlb21fcG9pbnQoCiAgICBhZXMoY29sb3IgPSBtZWFuID4gbWVhbihtb3ZpZV9jdXRzJG5fY3V0cykpLAogICAgc2l6ZSA9IDMKICApICsKICBzY2FsZV9jb2xvcl9tYW51YWwoZ3VpZGUgPSAibm9uZSIsIHZhbHVlcyA9IGMoIkZBTFNFIiA9ICIjMDA3MkIyIiwgIlRSVUUiID0gIiNENTVFMDAiKSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJXaGljaCBMYW5ndWFnZXMgUmVjZWl2ZSBNb3JlIENlbnNvciBDdXRzPyIsCiAgICBzdWJ0aXRsZSA9ICI5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIHRoZSBhdmVyYWdlIG51bWJlciBvZiBjdXRzIHBlciBmaWxtLiIsCiAgICB4ID0gIkF2ZXJhZ2UgQ3V0cyBwZXIgTW92aWUiLAogICAgeSA9ICJMYW5ndWFnZSIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gInNhbnMiKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkKICApCmBgYAoKCiMjIExhbmd1YWdlcyBjZXJ0aWZpZWQgYnkgZWFjaCBvZmZpY2UKCkp1c3QgY3VyaW91cyBhYm91dCB3aGljaCBvZmZpY2VzIG1vZGlmeSB3aGljaCBsYW5ndWFnZXMKCmBgYHtyfQp2YWxpZF9vZmZpY2VzIDwtIGMoIk11bWJhaSIsICJLb2xrYXRhIiwgIkh5ZGVyYWJhZCIsICJHdXdhaGF0aSIsICJEZWxoaSIsCiAgICAgICAgICAgICAgICAgICAiQ3V0dGFjayIsICJDaGVubmFpIiwgIkJhbmdhbG9yZSIsICJUaGlydXZhbmFudGhwdXJhbSIpCgp0b3Bfbl9sYW5ndWFnZXMgPC0gMTUgCgpkYXRhICU+JQogIG11dGF0ZSgKICAgIG9mZmljZSA9IHN0cl90cmltKG9mZmljZSksCiAgICBvZmZpY2UgPSBzdHJfdG9fdGl0bGUob2ZmaWNlKQogICkgJT4lCiAgICBkaXN0aW5jdChpZCwgbGFuZ3VhZ2UsIG9mZmljZSkgJT4lCiAgICBmaWx0ZXIob2ZmaWNlICVpbiUgdmFsaWRfb2ZmaWNlcywgIWlzLm5hKGxhbmd1YWdlKSwgbGFuZ3VhZ2UgIT0gIiIpICU+JQogICAgY291bnQob2ZmaWNlLCBsYW5ndWFnZSwgbmFtZSA9ICJjb3VudCIpICU+JQogICAgbXV0YXRlKGxhbmd1YWdlID0gZmN0X2x1bXBfbihsYW5ndWFnZSwgbiA9IHRvcF9uX2xhbmd1YWdlcywgdyA9IGNvdW50KSkgJT4lCiAgICBmaWx0ZXIobGFuZ3VhZ2UgIT0gIk90aGVyIikgJT4lIAogICAgbXV0YXRlKAogICAgb2ZmaWNlID0gZmN0X3Jlb3JkZXIob2ZmaWNlLCBjb3VudCwgLmZ1biA9IHN1bSksCiAgICBsYW5ndWFnZSA9IGZjdF9yZW9yZGVyKGxhbmd1YWdlLCBjb3VudCwgLmZ1biA9IHN1bSwgLmRlc2MgPSBUUlVFKQogICkgJT4lCiAgCiAgZ2dwbG90KGFlcyh4ID0gbGFuZ3VhZ2UsIHkgPSBvZmZpY2UsIGZpbGwgPSBjb3VudCkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAiZ3JheTIwIiwgbGluZXdpZHRoID0gMC40KSArCiAgCiAgZ2VvbV90ZXh0KAogICAgYWVzKGxhYmVsID0gY291bnQsIGNvbG9yID0gY291bnQgPiAxMjAwKSwgCiAgICBzaXplID0gMi43NSwKICAgIGZvbnRmYWNlID0gImJvbGQiCiAgKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoCiAgICBvcHRpb24gPSAibWFnbWEiLAogICAgbmFtZSA9ICJNb3ZpZSBDb3VudCIsCiAgICBkaXJlY3Rpb24gPSAtMQogICkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChndWlkZSA9ICJub25lIiwgdmFsdWVzID0gYygiRkFMU0UiID0gImJsYWNrIiwgIlRSVUUiID0gIndoaXRlIikpICsKICBsYWJzKAogICAgdGl0bGUgPSBwYXN0ZSgiRnJlcXVlbmN5IG9mIEZpbG0gTGFuZ3VhZ2UgYnkgQ2VydGlmeWluZyBPZmZpY2UiKSwKICAgIHggPSAiIiwKICAgIHkgPSAiIgogICkgKwogIHRoZW1lX21pbmltYWwoYmFzZV9mYW1pbHkgPSAic2FucyIpICsKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTIsIGNvbG9yID0gImdyYXk0MCIpLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCB2anVzdCA9IDEpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIgogICkKYGBgCgo=