TRACKS OUT builds the locations' sets (Location Setup PDF): SC and WS in their own PCO keys (pitched stems transposed, locator, Waves Tune, pads, CtW/Salvation), consolidated in Live; AB as NA; key check per location; never edits a set open in Live

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 12:04:43 -04:00
co-authored by Claude Opus 5.5
parent e9c2ef0b6c
commit 9551e043a7
6 changed files with 331 additions and 18 deletions
+79 -12
View File
@@ -553,7 +553,21 @@ for f in P["finished"]:
out["finished"][f] = sorted(n for n in os.listdir(f) if "[" in n) if os.path.isdir(f) else None
for loc in P["locations"]:
d = folder(loc)
out["bundles"][loc] = {"folder": d, "als": bool(newest_als(d)), "copying": os.path.isdir(d + ".copying")}
a = newest_als(d)
b = {"folder": d, "als": bool(a), "copying": os.path.isdir(d + ".copying")}
if a and loc in P.get("transpose_locs", []):
try:
r = ET.parse(gzip.open(a)).getroot()
b["locators"] = [l.find("Name").get("Value") for l in r.findall("LiveSet/Locators/Locators/Locator")]
b["transposed"] = sum(1 for c in r.iter("AudioClip") if c.find("PitchCoarse") is not None
and float(c.find("PitchCoarse").get("Value")) != 0)
except Exception:
pass
out["bundles"][loc] = b
try:
out["loc_job"] = json.load(open(os.path.expanduser("~/Library/Caches/status-panel-locations.json")))
except Exception:
out["loc_job"] = {}
print(json.dumps(out))
'''
@@ -565,7 +579,7 @@ def ableton_params(rc, plan):
"finished": rc.get("finished_folders", []), "tune_track": rc.get("tune_track", "WAVES TUNE"),
"tune_library": rc.get("tune_library", ""), "lighting_track": rc.get("lighting_track", "LIGHTING CUES"),
"hit_notes": rc.get("light_hit_notes", list(range(30, 40))), "hit_stems": rc.get("hit_stems", ["DRUMS"]),
"light_library": rc.get("light_library", "")}
"light_library": rc.get("light_library", ""), "transpose_locs": rc.get("transpose_locations", [])}
def ableton_look(cfg, rc, plan):
@@ -1257,13 +1271,38 @@ def eval_tune(rc, plan, look, sent=None):
return line
def eval_track_bundle(rc, look):
LOCATOR = re.compile(r"^\s*(\d+)\s*-\s*(.*?)\s*-\s*([A-G](?:#|b)?m?)\s*$")
def eval_track_bundle(rc, look, keys=None, now=None):
"""Every location has this weekend's set; the ones in `transpose_locations` in the keys their own plan has
(`keys`: {"WS": [{"title", "key"}]}) and consolidated."""
line = Line()
job = look.get("loc_job") or {}
now = now or time.time()
running = job.get("state") == "running" and now - (job.get("time") or 0) < 1800
if running:
line.fault(WARN, (job.get("step") or "WORKING")[:24], "TRACKS OUT is making the locations' sets: %s" % job.get("step"))
elif job.get("state") == "failed" and now - (job.get("time") or 0) < 7 * 86400:
line.fault(ERR, "FAILED " + (job.get("step") or "")[:16], "the last TRACKS OUT job stopped: %s" % job.get("step"))
if not running and now - (job.get("time") or 0) < 86400:
line.notes.extend(job.get("log") or [])
for loc, b in sorted((look.get("bundles") or {}).items()):
if b.get("copying"):
line.fault(WARN, "COPYING TO " + loc, "the copy to %s is still running" % loc)
elif not b.get("als"):
line.fault(ERR, "NOT SENT " + loc, "no set in %s" % b.get("folder"))
elif "locators" in b and not running:
for name in b["locators"]:
m = LOCATOR.match(name or "")
want = next((x["key"] for x in (keys or {}).get(loc) or [] if m and x.get("key") and same(x["title"], m.group(2))), None)
if want and not same_key(m.group(3), want, mode=False):
line.fault(ERR, "%s %s %s NOT %s" % (loc, short(m.group(2), 10), m.group(3), want),
"%s's plan has %s in %s; its set's locator \"%s\" says %s: hold TRACKS OUT" % (
loc, m.group(2), want, name, m.group(3)))
if b.get("transposed"):
line.fault(WARN, "CONSOLIDATE " + loc, "%s's set has %d transposed clips not consolidated: hold TRACKS OUT" % (
loc, b["transposed"]))
line.info = " ".join(sorted(look.get("bundles") or {})) or "NO LOCATIONS"
return line
@@ -1835,6 +1874,8 @@ def look(cfg, now=None, data_dir=None, sources=None):
continue
try:
cplan = src["pco_campus"](campus["service_type"])
LAST.setdefault("campus_keys", {})[campus.get("short")] = [
{"title": it["title"], "key": it["key"]} for it in plan_songs(rc, cplan)]
res = campus_ltc(rc, plan, na_songs, campus, cplan)
eval_campus_ltc(rc, res, lines["timecode"])
others.append(dict(res, songs=[dict(s, item={"id": s["item"]["id"], "note_ids": s["item"]["note_ids"]}) for s in res["songs"]]))
@@ -1844,7 +1885,8 @@ def look(cfg, now=None, data_dir=None, sources=None):
lines["timecode"].fault(WARN, "%s %s" % (tag, msg), "%s: %s" % (campus.get("name") or tag, e))
LAST["ltc_other"] = others
if rc.get("locations"):
lines["track_bundle"] = eval_track_bundle(rc, al)
lines["track_bundle"] = eval_track_bundle(rc, al, LAST.get("campus_keys"), now)
loc_job_handback(cfg, rc, al.get("loc_job") or {})
except Exception as e: # noqa: BLE001
failed(["tracks", "mix_foh", "lights", "keys", "tune", "light_timing"] + (["track_bundle"] if rc.get("locations") else []), e)
# START first: while it consolidates in Live it holds AbletonOSC's answers on the Tracks Mac.
@@ -2161,9 +2203,17 @@ def start_look(cfg, rc, services):
def start_in_live():
"""START is consolidating in Live just now (it holds AbletonOSC's answers on the Tracks Mac)."""
st = LAST.get("start_status") or {}
return st.get("state") == "running" and time.time() - (st.get("live") or 0) < 600
"""START or TRACKS OUT is consolidating in Live just now (it holds AbletonOSC's answers on the Tracks Mac)."""
return any(st.get("state") == "running" and time.time() - (st.get("live") or 0) < 600
for st in (LAST.get("start_status") or {}, LAST.get("loc_job") or {}))
def loc_job_handback(cfg, rc, st):
"""TRACKS OUT's job used AbletonOSC from the Tracks Mac: give it back to Companion once it is done."""
LAST["loc_job"] = st
if st.get("live") and st.get("state") != "running" and LAST.get("loc_handed") != st["live"]:
hand_back(cfg, rc["tracks_host"])
LAST["loc_handed"] = st["live"]
def eval_start(rc, services, look, now):
@@ -2464,11 +2514,28 @@ def run_action(cfg, action, chk=None, sleep=time.sleep):
plan = LAST.get("plan")
if not plan:
return "NO PLAN YET"
r = run_remote(cfg, rc["tracks_host"], COPY_SCRIPT % json.dumps(ableton_params(rc, plan)), timeout=60)
if not r.get("source"):
return "NO SET TO COPY"
log.info("FIX tracks copy started to %s", ", ".join(r["started"]) or "nowhere (all there)")
return True
if not rc.get("transpose_locations"):
r = run_remote(cfg, rc["tracks_host"], COPY_SCRIPT % json.dumps(ableton_params(rc, plan)), timeout=60)
if not r.get("source"):
return "NO SET TO COPY"
log.info("FIX tracks copy started to %s", ", ".join(r["started"]) or "nowhere (all there)")
return True
# WS and SC in their own plans' keys (read now), AB as NA's: one job on the Tracks Mac, consolidating in Live.
keys = {}
for c in rc.get("ltc_campuses") or []:
if c.get("short") in rc["transpose_locations"] and c.get("service_type"):
cplan = pco_plan(rc, time.time(), c["service_type"], team=False)
keys[c["short"]] = [{"title": it["title"], "key": it["key"]} for it in plan_songs(rc, cplan)]
missing = [x for x in rc["transpose_locations"] if x not in keys]
if missing:
return "NO PLAN FOR " + " ".join(missing)
params = dict(start_params(rc, [], "run"), **ableton_params(rc, plan))
params.update(job="locations", site=rc.get("site_folder", ""), transpose=keys)
r = run_remote(cfg, rc["tracks_host"], start_script(params), timeout=60)
if r.get("busy"):
return "START IS BUSY"
log.info("FIX TRACKS OUT: the locations' sets (%s in their own keys)", ", ".join(sorted(keys)))
return True if r.get("started") else "DID NOT START"
if action == "mount_share":
# The computer's keep-mounted agent (remote-access/mac-keep-mounted.sh) mounts it again, now.
label = "org.waterschurch.keep-mounted." + rc.get("prop_bundle_share_agent", "location-assets")