# 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)

1 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)
Action Type Count Percentage (%)
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

2 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)
Media Type Count Percentage (%)
visual_scene 47869 46.1
text_dialogue 35207 33.9
metadata 16034 15.4
music 4211 4.1
other 554 0.5

3 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)
language Adults Only (A) General Audience (U/UA) Unrestricted (U) NA / Other S
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

4 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)
Language Total Movie Duration (Hours) Total Modified Duration (Hours) Percent Modified (%)
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)
language mean conf.low conf.high
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

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)
language total_movies total_cuts avg_cuts
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()
  )

6 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=