Last active 1568011293

Bandersnatch v2

bandersnatch.py Raw
1#!/usr/bin/env python3
2from random import choice
3import json
4from sys import stderr
5
6with open("bandersnatch.json") as f:
7 bandersnatch = json.load(f)
8
9with open("segmentmap.json") as f:
10 smap = json.load(f)
11
12initial_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 }
13state = dict(initial_state)
14
15info = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]
16moments = info["momentsBySegment"]
17preconditions = info["preconditions"]
18segmentGroups = info["segmentGroups"]
19thumbnails = info["choicePointNavigatorMetadata"]["choicePointsMetadata"]["choicePoints"]
20
21segmentMap = {}
22for segmentList in moments.values():
23 for _segment in segmentList:
24 for _choice in _segment.get("choices", []):
25 if "segmentId" in _choice and "text" in _choice:
26 segmentMap[_choice["segmentId"]] = _choice["text"].title()
27
28def msToTS(ms):
29 s,ms = divmod(ms,1000)
30 m,s = divmod(s,60)
31 h,m = divmod(m,60)
32 return "{:02d}:{:02d}:{:02d}.{:03d}".format(h,m,s,ms)
33
34def conditionHandler(cond):
35 global state
36 if not cond:
37 return True
38 if cond[0] == "persistentState":
39 return state[cond[1]]
40 if cond[0] == "not":
41 return not all(conditionHandler(c) for c in cond[1:])
42 if cond[0] == "and":
43 return all(conditionHandler(c) for c in cond[1:])
44 if cond[0] == "eql":
45 return conditionHandler(cond[1]) == cond[2]
46 if cond[0] == "or":
47 return any(conditionHandler(c) for c in cond[1:])
48
49def groupHandler(group, segment=None):
50 out = []
51 if segment:
52 group.append(segment)
53 for item in group:
54 if type(item) is str and conditionHandler( preconditions.get(item,[]) ):
55 out.append(item)
56 if type(item) is dict:
57 if "segmentGroup" in item:
58 out += groupHandler(segmentGroups[item["segmentGroup"]])
59 if "precondition" in item:
60 if conditionHandler( preconditions.get(item["precondition"],[]) ):
61 out.append(item["segment"])
62# print("out="+repr(out),file=stderr)
63 return out
64
65
66def followTheStory(segment):
67 global state
68 global history
69 possibilities = []
70 if segment in moments:
71 m = moments[segment]
72 for moment in m:
73 if moment["type"] == "notification:playbackImpression":
74 state.update( moment.get("impressionData",{}).get("data", {}).get("persistent", {}) )
75 if moment["type"] == "scene:cs_bs":
76 for option in moment["choices"]:
77 state.update( option.get("impressionData",{}).get("data", {}).get("persistent", {}) )
78 if "segmentId" in option:
79 p = groupHandler([option["segmentId"]])
80 elif "sg" in option:
81 p = groupHandler(segmentGroups[option["sg"]])
82 elif moment["trackingInfo"]["optionType"] == "fakeOption":
83 continue
84 else:
85 raise Exception(option["id"])
86 possibilities += p
87 if moment["type"] == "notification:action":
88 possibilities.append(segment)
89 if segment in segmentGroups:
90 possibilities += groupHandler(segmentGroups[segment])
91# print("poss="+repr(possibilities),file=stderr)
92 if not possibilities:
93# raise Exception("hoi")
94 possibilities += groupHandler(segmentGroups["respawnOptions"])
95 return choice(possibilities)
96
97def get_segment_info(segment):
98 _ = thumbnails.get(segment, {})
99 return {"id": segment, "url": _["image"]["styles"]["backgroundImage"][4:-1] if "image" in _ else "", "caption": _.get("description", "No caption"), "chose": segmentMap.get(segment, "No caption ({})".format(segment))}
100
101def bandersnatch():
102 global state
103 concat, options = [], []
104 state = dict(initial_state)
105 current_segment = "1A"
106 while True:
107 state["length"] += smap[current_segment]["endTimeMs"] - smap[current_segment]["startTimeMs"]
108 if current_segment[:3].lower() == "0cr":
109
110 options.append(get_segment_info(current_segment))
111 concat.append(current_segment)
112
113 options.append(get_segment_info("IDNT"))
114 concat.append('IDNT')
115
116 state["length"] += 10
117 break
118 options.append(get_segment_info(current_segment))
119 concat.append(current_segment)
120 current_segment = followTheStory(current_segment)
121 if current_segment is None:
122 break
123 return concat, options, msToTS(state["length"])
124
125if __name__ == "__main__":
126 print(bandersnatch(),file=stderr)
srv.py Raw
1#!/usr/bin/env python
2from flask import Flask, render_template_string, send_file
3from bandersnatch import bandersnatch
4
5TEMPLATE = """
6<!doctype html>
7<html>
8 <head>
9 <style>
10 body {
11 background-color: black;
12 color: white;
13 font-family: sans-serif
14 }
15 label {
16 background-color: black;
17 }
18 .segment {
19 width:472px;
20 height:266px;
21 }
22 div.segment {
23 margin:30px
24 }
25 video.segment {
26 }
27 </style>
28 <title>Bandersnatch</title>
29 </head>
30 <body>
31 <h3>{{ length }}</h3>
32 <textarea>{% for segment in concat %}
33file '{{ segment }}.mkv' {% endfor %}</textarea>
34 <div style='display: flex; flex-wrap: wrap;'>
35 {% for option in options %}
36 <div class='segment' id='{{ option.id }}' style='background-image: url({{ option.url }})'>
37 <video class='segment' id='{{ option.id }}-vid' controls {% if option.url %}poster='{{ option.url }}' preload='none'{% else %}preload='metadata'{% endif %}>
38 <source src='https://s3.home.b303.me/bandersnatch/{{ option.id }}.mkv{% if not option.url %}#t=0.5{% endif %}' type='video/mp4'>
39 </video>
40 <label for='{{ option.id }}'>{% if option.chose != "No caption (1A)" %}<span class='chose'>Chose {{ option.chose }}</span> &gt;{% endif %} <span class='caption'>{{ option.caption }}</span></label>
41 </div>
42 {% endfor %}
43 </div>
44 </body>
45</html>
46"""
47
48app = Flask(__name__)
49
50@app.route("/<segment>.mkv")
51def mkv(segment):
52 return send_file("{}.mkv".format(segment))
53
54@app.route("/")
55def index():
56 concat, options, length = bandersnatch()
57 return render_template_string(TEMPLATE, concat=concat, options=options, length=length)
58
59if __name__ == "__main__":
60 app.run(debug=True, host="0.0.0.0", port=24572)