Commit 56a402ed by PLN (Algolia)

feat(release): publish the mix as an artefact, with a real tracklist

PLN: "cant push the single tracks when samples hit, but full mix might well
pass!" — which matches the measurement (an isolated track was flagged where the
87-minute mix passed). So the continuous mix is not a by-product of the release,
it is the artefact most likely to survive, and --include-mix makes it a first-
class entry in the upload plan.

It is listed FIRST on purpose: if an upload run dies halfway, it dies having
landed the important thing.

The description carries the tracklist with timecodes, and those come from the
CUMULATIVE RENDERED durations rather than from segment arithmetic. The mix is
literally these files concatenated, so summing what came out is exact — and it
stays exact through effects that lengthen a track, which is not hypothetical
here: CRIME carries a 2s rest and REVOLUTION a 650ms reverb tail, so segment
maths would have every timecode after track 13 drifting.

Timecodes switch to h:mm:ss past the hour. The last four tracks of a 73-minute
set are all past 60 minutes, and "69:15" is a number nobody can place.

The mix title is composed from canonical FIELDS (title · venue · year), never
from a remembered string: "Sunset Forest — Opal Festival 2026 (full set)". The
format lives in the code, the facts stay in Web/www.
parent 719f0764
...@@ -116,6 +116,8 @@ def main() -> int: ...@@ -116,6 +116,8 @@ def main() -> int:
ap.add_argument("spec") ap.add_argument("spec")
ap.add_argument("--variant", default="streaming") ap.add_argument("--variant", default="streaming")
ap.add_argument("--artwork", help="cover image applied to every track") ap.add_argument("--artwork", help="cover image applied to every track")
ap.add_argument("--include-mix", action="store_true",
help="also publish the continuous *_nodrop mix, listed first")
ap.add_argument("--out", required=True) ap.add_argument("--out", required=True)
ap.add_argument("--titles", default="canonical", choices=["canonical", "file"], ap.add_argument("--titles", default="canonical", choices=["canonical", "file"],
help="track names from the canonical tracklist (default) or " help="track names from the canonical tracklist (default) or "
...@@ -203,6 +205,50 @@ def main() -> int: ...@@ -203,6 +205,50 @@ def main() -> int:
"_rights_checked": False, "_rights_checked": False,
}) })
# The continuous mix goes FIRST in the list, deliberately. It is the
# lower-risk artefact (an isolated track gets flagged where the long mix
# passes) and the one PLN would most want on the shelf, so if an upload run
# dies halfway it dies having landed the important thing.
if a.include_mix:
mix = Path(spec["variants"][a.variant])
mix = mix.with_name(mix.stem + "_nodrop" + mix.suffix)
if not mix.exists():
sys.exit(f"continuous mix not rendered: {mix}\n"
f" run: python3 render_release.py {a.spec} --only continuous")
# Timecodes are CUMULATIVE RENDERED durations, not segment arithmetic:
# the mix is literally these files concatenated, so summing what came
# out is exact — and it stays exact through effects that lengthen a
# track (the reverb tail, the 2s rest) which segment maths would miss.
def tc(s: float) -> str:
# h:mm:ss past the hour. A 73-minute set written as "69:15" reads
# as a timestamp nobody can place, and the last five tracks of this
# record are all past 60 minutes.
s = int(s)
return (f"{s//3600}:{s//60%60:02d}:{s%60:02d}" if s >= 3600
else f"{s//60}:{s%60:02d}")
lines, t = [], 0.0
for tr in tracks:
lines.append(f"{tc(t)} {tr['title']}")
t += tr["_duration_s"]
tracks.insert(0, {
# Composed from canonical FIELDS (title · venue · date), never from
# a remembered string. The format is here; the facts are theirs.
"title": f"{album} — {fm.get('address','')} {fm.get('date','')[:4]} (full set)",
"audio": str(mix),
"artwork": a.artwork,
"genre": "livecoding",
"tag_list": " ".join(dict.fromkeys(base_tags)),
"description": (
f"{fm.get('description','')}\n\n"
f"{fm.get('stage','')}, {fm.get('location','')}. "
f"{len(tracks)} tracks, {t/60:.0f} minutes, live coded in "
f"TidalCycles.\n\n" + "\n".join(lines)),
"_duration_s": round(ffprobe_dur(mix), 3),
"_is_continuous_mix": True,
"_rights_checked": False,
})
plan = { plan = {
"_comment": [ "_comment": [
"GENERATED by build_release_plan.py — do not hand-edit.", "GENERATED by build_release_plan.py — do not hand-edit.",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment