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.
- The movie (complete, with all clips, should be 05:12:14). See https://mehotkhan.github.io/BandersnatchInteractive/ for info on that.
- Some data from BandersnatchInteractive.
- ffmpeg
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))
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`
| 1 | from random import choice |
| 2 | import json |
| 3 | #from sys import stderr |
| 4 | |
| 5 | with open("bandersnatch.json") as f: |
| 6 | bandersnatch = json.load(f) |
| 7 | |
| 8 | with open("SegmentMap.json") as f: |
| 9 | smap = json.load(f) |
| 10 | |
| 11 | initial_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 } |
| 12 | state = dict(initial_state) |
| 13 | |
| 14 | moments = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]["momentsBySegment"] |
| 15 | preconditions = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]["preconditions"] |
| 16 | segmentGroups = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]["segmentGroups"] |
| 17 | |
| 18 | def 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 | |
| 24 | def 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 | |
| 39 | def 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 | |
| 56 | def 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 | |
| 87 | def 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 smap: |
| 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 | |
| 104 | if __name__ == "__main__": |
| 105 | bandersnatch() |