Last active 1629393043

Code to make your own Black Mirror: Bandersnatch

Revision cd57df2dafd58c617e38048000536d8d6f45836a

bandersnatch.md Raw

Bandersnatch Maker

This is a program I wrote on a live stream on the 14th (and 15th) of June 2019. Video link coming soon.

You'll need some things.

You'll need to edit the two js files to remove bandersnatch= and SegmentMap= respectively so they parse as valid json. If you encounter an error about utf-8 bom use dos2unix to fix it. Rename the files to bandersnatch.json and SegmentMap.json.

Open a python interpreter in the same directory as SegmentMap.js. I'd probably recommend 3.7. Paste this in:

import json

with open("SegmentMap.json") as f:
  smap = json.load(f)

def msToTS(ms):
  s,ms = divmod(ms,1000)
  m,s = divmod(s,60)
  h,m = divmod(m,60)
  return "{:02d}:{:02d}:{:02d}.{:03d}".format(h,m,s,ms)

for segment in smap.values():
  ss = ""
  t = ""
  if segment["startTimeMs"] > 5000:
    ss = " -ss " + msToTS(segment["startTimeMs"]-5000)
  if "endTimeMs" in segment:
    t = " -t {}".format((segment["endTimeMs"]-segment["startTimeMs"])/1000)
  print("""ffmpeg{} -i bandersnatch.mkv -ss 5{} {}.mkv 2>/dev/null""".format(ss, t, segment["segment"]))

You'll get a bunch of lines to run to generate clips from the full movie. I'd suggest putting these lines into a bash script and leaving it to run for a while. This will take a very long time.

While that's going, generate some movies! They're produced in ffmpeg concat format, render them with:

ffmpeg -f concat -i FILENAME.txt -c copy `date +%Y%m%d-%H%M%S.mkv`
bandersnatch.py Raw
1from random import choice
2import json
3#from sys import stderr
4
5with open("bandersnatch.json") as f:
6 bandersnatch = json.load(f)
7
8with open("SegmentMap.json") as f:
9 smap = json.load(f)
10
11initial_state = { "p_sp": True, "p_tt": True, "p_8a": False, "p_td": True, "p_cs": False, "p_w1": False, "p_2b": False, "p_3j": False, "p_pt": False, "p_cd": False, "p_cj": False, "p_sj": False, "p_sj2": False, "p_tud": False, "p_lsd": False, "p_vh": False, "p_3l": False, "p_3s": False, "p_3z": False, "p_ps": "n", "p_wb": False, "p_kd": False, "p_bo": False, "p_5v": False, "p_pc": "n", "p_sc": False, "p_ty": False, "p_cm": False, "p_pr": False, "p_3ad": False, "p_s3af": False, "p_nf": False, "p_np": False, "p_ne": False, "p_pp": False, "p_tp": False, "p_bup": False, "p_be": False, "p_pe": False, "p_pae": False, "p_te": False, "p_snt": False, "p_8j": False, "p_8d": False, "p_8m": False, "p_8q": False, "p_8s": False, "p_8v": False, "p_vs": "n", "p_scs": False, "p_3ab": False, "p_3ac": False, "p_3aj": False, "p_3ah": False, "p_3ak": False, "p_3al": False, "p_3af": False, "p_5h": False, "p_5ac": False, "p_5ag": False, "p_5ad": False, "p_6c": False, "length": 0 }
12state = dict(initial_state)
13
14moments = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]["momentsBySegment"]
15preconditions = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]["preconditions"]
16segmentGroups = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]["segmentGroups"]
17
18def msToTS(ms):
19 s,ms = divmod(ms,1000)
20 m,s = divmod(s,60)
21 h,m = divmod(m,60)
22 return "{:02d}:{:02d}:{:02d}.{:03d}".format(h,m,s,ms)
23
24def conditionHandler(cond):
25 global state
26 if not cond:
27 return True
28 if cond[0] == "persistentState":
29 return state[cond[1]]
30 if cond[0] == "not":
31 return not all(conditionHandler(c) for c in cond[1:])
32 if cond[0] == "and":
33 return all(conditionHandler(c) for c in cond[1:])
34 if cond[0] == "eql":
35 return conditionHandler(cond[1]) == cond[2]
36 if cond[0] == "or":
37 return any(conditionHandler(c) for c in cond[1:])
38
39def groupHandler(group, segment=None):
40 out = []
41 if segment:
42 group.append(segment)
43 for item in group:
44 if type(item) is str and conditionHandler( preconditions.get(item,[]) ):
45 out.append(item)
46 if type(item) is dict:
47 if "segmentGroup" in item:
48 out += groupHandler(segmentGroups[item["segmentGroup"]])
49 if "precondition" in item:
50 if conditionHandler( preconditions.get(item["precondition"],[]) ):
51 out.append(item["segment"])
52# print("out="+repr(out),file=stderr)
53 return out
54
55
56def followTheStory(segment):
57 global state
58 global history
59 possibilities = []
60 if segment in moments:
61 m = moments[segment]
62 for moment in m:
63 if moment["type"] == "notification:playbackImpression":
64 state.update( moment.get("impressionData",{}).get("data", {}).get("persistent", {}) )
65 if moment["type"] == "scene:cs_bs":
66 for option in moment["choices"]:
67 state.update( option.get("impressionData",{}).get("data", {}).get("persistent", {}) )
68 if "segmentId" in option:
69 p = groupHandler([option["segmentId"]])
70 elif "sg" in option:
71 p = groupHandler(segmentGroups[option["sg"]])
72 elif moment["trackingInfo"]["optionType"] == "fakeOption":
73 continue
74 else:
75 raise Exception(option["id"])
76 possibilities += p
77 if moment["type"] == "notification:action":
78 possibilities.append(segment)
79 if segment in segmentGroups:
80 possibilities += groupHandler(segmentGroups[segment])
81# print("poss="+repr(possibilities),file=stderr)
82 if not possibilities:
83# raise Exception("hoi")
84 possibilities += groupHandler(segmentGroups["respawnOptions"])
85 return choice(possibilities)
86
87def bandersnatch():
88 global state
89 state = dict(initial_state)
90 current_segment = "1A"
91 while True:
92 if current_segment[:3].lower() == "0cr":
93 print("file '{}.mkv'".format(current_segment))
94 print("file 'IDNT.mkv'")
95 break
96 if current_segment in moments:
97 print("file '{}.mkv'".format(current_segment))
98 state["length"] += smap[current_segment]["endTimeMs"] - smap[current_segment]["startTimeMs"]
99 current_segment = followTheStory(current_segment)
100 if current_segment is None:
101 break
102 return msToTS(state["length"])
103
104if __name__ == "__main__":
105 bandersnatch()