bandersnatch.py
· 5.4 KiB · Python
Raw
#!/usr/bin/env python3
from random import choice
import json
from sys import stderr
with open("bandersnatch.json") as f:
bandersnatch = json.load(f)
with open("segmentmap.json") as f:
smap = json.load(f)
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 }
state = dict(initial_state)
info = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"]
moments = info["momentsBySegment"]
preconditions = info["preconditions"]
segmentGroups = info["segmentGroups"]
thumbnails = info["choicePointNavigatorMetadata"]["choicePointsMetadata"]["choicePoints"]
segmentMap = {}
for segmentList in moments.values():
for _segment in segmentList:
for _choice in _segment.get("choices", []):
if "segmentId" in _choice and "text" in _choice:
segmentMap[_choice["segmentId"]] = _choice["text"].title()
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)
def conditionHandler(cond):
global state
if not cond:
return True
if cond[0] == "persistentState":
return state[cond[1]]
if cond[0] == "not":
return not all(conditionHandler(c) for c in cond[1:])
if cond[0] == "and":
return all(conditionHandler(c) for c in cond[1:])
if cond[0] == "eql":
return conditionHandler(cond[1]) == cond[2]
if cond[0] == "or":
return any(conditionHandler(c) for c in cond[1:])
def groupHandler(group, segment=None):
out = []
if segment:
group.append(segment)
for item in group:
if type(item) is str and conditionHandler( preconditions.get(item,[]) ):
out.append(item)
if type(item) is dict:
if "segmentGroup" in item:
out += groupHandler(segmentGroups[item["segmentGroup"]])
if "precondition" in item:
if conditionHandler( preconditions.get(item["precondition"],[]) ):
out.append(item["segment"])
# print("out="+repr(out),file=stderr)
return out
def followTheStory(segment):
global state
global history
possibilities = []
if segment in moments:
m = moments[segment]
for moment in m:
if moment["type"] == "notification:playbackImpression":
state.update( moment.get("impressionData",{}).get("data", {}).get("persistent", {}) )
if moment["type"] == "scene:cs_bs":
for option in moment["choices"]:
state.update( option.get("impressionData",{}).get("data", {}).get("persistent", {}) )
if "segmentId" in option:
p = groupHandler([option["segmentId"]])
elif "sg" in option:
p = groupHandler(segmentGroups[option["sg"]])
elif moment["trackingInfo"]["optionType"] == "fakeOption":
continue
else:
raise Exception(option["id"])
possibilities += p
if moment["type"] == "notification:action":
possibilities.append(segment)
if segment in segmentGroups:
possibilities += groupHandler(segmentGroups[segment])
# print("poss="+repr(possibilities),file=stderr)
if not possibilities:
# raise Exception("hoi")
possibilities += groupHandler(segmentGroups["respawnOptions"])
return choice(possibilities)
def get_segment_info(segment):
_ = thumbnails.get(segment, {})
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))}
def bandersnatch():
global state
concat, options = [], []
state = dict(initial_state)
current_segment = "1A"
while True:
state["length"] += smap[current_segment]["endTimeMs"] - smap[current_segment]["startTimeMs"]
if current_segment[:3].lower() == "0cr":
options.append(get_segment_info(current_segment))
concat.append(current_segment)
options.append(get_segment_info("IDNT"))
concat.append('IDNT')
state["length"] += 10
break
options.append(get_segment_info(current_segment))
concat.append(current_segment)
current_segment = followTheStory(current_segment)
if current_segment is None:
break
return concat, options, msToTS(state["length"])
if __name__ == "__main__":
print(bandersnatch(),file=stderr)
| 1 | #!/usr/bin/env python3 |
| 2 | from random import choice |
| 3 | import json |
| 4 | from sys import stderr |
| 5 | |
| 6 | with open("bandersnatch.json") as f: |
| 7 | bandersnatch = json.load(f) |
| 8 | |
| 9 | with open("segmentmap.json") as f: |
| 10 | smap = json.load(f) |
| 11 | |
| 12 | 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 } |
| 13 | state = dict(initial_state) |
| 14 | |
| 15 | info = bandersnatch["videos"]["80988062"]["interactiveVideoMoments"]["value"] |
| 16 | moments = info["momentsBySegment"] |
| 17 | preconditions = info["preconditions"] |
| 18 | segmentGroups = info["segmentGroups"] |
| 19 | thumbnails = info["choicePointNavigatorMetadata"]["choicePointsMetadata"]["choicePoints"] |
| 20 | |
| 21 | segmentMap = {} |
| 22 | for 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 | |
| 28 | def 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 | |
| 34 | def 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 | |
| 49 | def 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 | |
| 66 | def 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 | |
| 97 | def 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 | |
| 101 | def 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 | |
| 125 | if __name__ == "__main__": |
| 126 | print(bandersnatch(),file=stderr) |
srv.py
· 1.9 KiB · Python
Raw
#!/usr/bin/env python
from flask import Flask, render_template_string, send_file
from bandersnatch import bandersnatch
TEMPLATE = """
<!doctype html>
<html>
<head>
<style>
body {
background-color: black;
color: white;
font-family: sans-serif
}
label {
background-color: black;
}
.segment {
width:472px;
height:266px;
}
div.segment {
margin:30px
}
video.segment {
}
</style>
<title>Bandersnatch</title>
</head>
<body>
<h3>{{ length }}</h3>
<textarea>{% for segment in concat %}
file '{{ segment }}.mkv' {% endfor %}</textarea>
<div style='display: flex; flex-wrap: wrap;'>
{% for option in options %}
<div class='segment' id='{{ option.id }}' style='background-image: url({{ option.url }})'>
<video class='segment' id='{{ option.id }}-vid' controls {% if option.url %}poster='{{ option.url }}' preload='none'{% else %}preload='metadata'{% endif %}>
<source src='https://s3.home.b303.me/bandersnatch/{{ option.id }}.mkv{% if not option.url %}#t=0.5{% endif %}' type='video/mp4'>
</video>
<label for='{{ option.id }}'>{% if option.chose != "No caption (1A)" %}<span class='chose'>Chose {{ option.chose }}</span> >{% endif %} <span class='caption'>{{ option.caption }}</span></label>
</div>
{% endfor %}
</div>
</body>
</html>
"""
app = Flask(__name__)
@app.route("/<segment>.mkv")
def mkv(segment):
return send_file("{}.mkv".format(segment))
@app.route("/")
def index():
concat, options, length = bandersnatch()
return render_template_string(TEMPLATE, concat=concat, options=options, length=length)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=24572)
| 1 | #!/usr/bin/env python |
| 2 | from flask import Flask, render_template_string, send_file |
| 3 | from bandersnatch import bandersnatch |
| 4 | |
| 5 | TEMPLATE = """ |
| 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 %} |
| 33 | file '{{ 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> >{% endif %} <span class='caption'>{{ option.caption }}</span></label> |
| 41 | </div> |
| 42 | {% endfor %} |
| 43 | </div> |
| 44 | </body> |
| 45 | </html> |
| 46 | """ |
| 47 | |
| 48 | app = Flask(__name__) |
| 49 | |
| 50 | @app.route("/<segment>.mkv") |
| 51 | def mkv(segment): |
| 52 | return send_file("{}.mkv".format(segment)) |
| 53 | |
| 54 | @app.route("/") |
| 55 | def index(): |
| 56 | concat, options, length = bandersnatch() |
| 57 | return render_template_string(TEMPLATE, concat=concat, options=options, length=length) |
| 58 | |
| 59 | if __name__ == "__main__": |
| 60 | app.run(debug=True, host="0.0.0.0", port=24572) |