This commit is contained in:
alyssadev 2022-01-10 22:29:51 +10:00
commit 7a5d9a1f04
7 changed files with 32341 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
out/
*.mp4
segments/

7131
SegmentMap.json Normal file

File diff suppressed because it is too large Load diff

24959
bandersnatch.json Normal file

File diff suppressed because it is too large Load diff

153
bandersnatch.py Normal file
View file

@ -0,0 +1,153 @@
#!/usr/bin/env python3
from random import Random, randrange
import json
from sys import stderr, maxsize, argv
from os import environ
seed = randrange(maxsize)
if environ.get("SEED", "").isdigit():
seed = int(environ.get("SEED"))
random = Random(seed)
debug = True if environ.get("DEBUG") else False
with open("bandersnatch.json") as f:
bandersnatch = json.load(f)
with open("segmentmap.json") as f:
smap = json.load(f)["segments"]
with open("state_descriptions.json") as f:
state_desc = 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"])
# if debug: print("out="+repr(out),file=stderr)
return out
def updateState(persistent):
global state
for k,v in persistent.items():
if debug:
if state[k] != v: print(f"{k} [{state_desc[k]}]: {v} (changed)",file=stderr)
else: print(f"{k} [{state_desc[k]}]: {v} (unchanged)",file=stderr)
state[k] = v
def followTheStory(segment):
global state
global history
global random
possibilities = []
if segment in moments:
m = moments[segment]
for moment in m:
if moment["type"] == "notification:playbackImpression":
updateState(moment.get("impressionData",{}).get("data", {}).get("persistent", {}))
if moment["type"] == "scene:cs_bs":
for option in moment["choices"]:
if segment != "1A": updateState(moment.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])
if debug: print("poss="+repr(possibilities),file=stderr)
if not possibilities:
# raise Exception("hoi")
possibilities += groupHandler(segmentGroups["respawnOptions"])
if debug: print("respawn="+repr(possibilities),file=stderr)
return random.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)
if debug: print(f"chose={current_segment}",file=stderr)
options.append(get_segment_info("IDNT"))
concat.append('IDNT')
if debug: print(f"chose=IDNT",file=stderr)
state["length"] += 10
break
options.append(get_segment_info(current_segment))
concat.append(current_segment)
if debug: print(f"chose={current_segment}",file=stderr)
current_segment = followTheStory(current_segment)
if current_segment is None:
break
return concat, options, msToTS(state["length"])
if __name__ == "__main__":
concat,options,length = bandersnatch()
if len(argv) == 1 or argv[1] != "-no":
with open(f"out/{seed}.txt", "w") as f:
f.write("\n".join(f"file '../segments/{_id}.mp4'" for _id in concat))
print(f"{seed} {length}")

23
generate_segment_shell.py Normal file
View file

@ -0,0 +1,23 @@
import json
with open("SegmentMap.json") as f:
smap = json.load(f)["segments"]
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 _id,segment in smap.items():
ss = ""
t = ""
# working around ffmpeg seek to previous keyframe
if segment["startTimeMs"] > 0:
ss = " -ss " + msToTS(segment["startTimeMs"]+40)
if "endTimeMs" in segment:
t = " -t {}".format((segment["endTimeMs"]-segment["startTimeMs"])/1000)
try:
print("""</dev/null ffmpeg{} -y -i Black.Mirror.Bandersnatch.2018.720p.WEB-DL.x264.DUAL.mp4 -c:v libx264 -c:a aac {} {}.mkv""".format(ss, t, _id))
except BrokenPipeError:
break

7
make.sh Normal file
View file

@ -0,0 +1,7 @@
#!/bin/bash
read SEED length <<<$(SEED=${SEED} python3 bandersnatch.py)
echo $length >&2
ffmpeg -hide_banner -v warning -stats -f concat -safe 0 -i out/${SEED}.txt -c copy out/${SEED}.mp4
echo out/${SEED}.mp4

65
state_descriptions.json Normal file
View file

@ -0,0 +1,65 @@
{
"p_sp": "Chose Sugar Puffs",
"p_tt": "Chose Thompson Twins",
"p_8a": "Accepted Tucker's offer (wrong path)",
"p_td": "Chose Phaedra",
"p_cs": "Can't speak (?)",
"p_w1": "Rejected Tucker's offer",
"p_2b": "Talked about mother",
"p_3j": "Involuntarily driven to psych office",
"p_pt": "Poured tea over computer",
"p_cd": "Colin died",
"p_cj": "Colin jumped",
"p_sj": "Stefan jumped",
"p_sj2": "Stefan died",
"p_tud": "Threw away drugs",
"p_lsd": "Followed Colin, had LSD (voluntary or not)",
"p_vh": "Told psych about not being in control",
"p_3l": "Shortcut back to psych office",
"p_3s": "Told psych they've had that conversation before",
"p_3z": "Hit desk (why not commit murder)",
"p_ps": "How were pills disposed of (b=bin, f=flush)",
"p_wb": "Showed Stefan 'White Bear' glyph",
"p_kd": "Killed Dad",
"p_bo": "Backed off from killing Dad",
"p_5v": "",
"p_pc": "? default=n, buried dad once=o, buried dad more than once=t",
"p_sc": "Smashed computer",
"p_ty": "",
"p_cm": "Went back in time (?)",
"p_pr": "",
"p_3ad": "Look door, get key",
"p_s3af": "",
"p_nf": "Told Stefan about Netflix",
"p_np": "",
"p_ne": "",
"p_pp": "",
"p_tp": "",
"p_bup": "",
"p_be": "",
"p_pe": "",
"p_pae": "",
"p_te": "",
"p_snt": "",
"p_8j": "",
"p_8d": "",
"p_8m": "",
"p_8q": "",
"p_8s": "",
"p_8v": "",
"p_vs": "",
"p_scs": "Restarted after smashing computer",
"p_3ab": "",
"p_3ac": "Stabbed by JFD",
"p_3aj": "",
"p_3ah": "",
"p_3ak": "Entered JFD into safe",
"p_3al": "Entered PAX into safe",
"p_3af": "Entered PAC into safe",
"p_5h": "",
"p_5ac": "",
"p_5ag": "",
"p_5ad": "",
"p_6c": "",
"length": ""
}