master
John Lauer Publish thermal bottleneck planner, accepted candidate, analysis and native recording scripts 0049da5 29d ago
"""Native action at 3x, explicitly labeled inspection holds and postproduction analysis."""
import json,subprocess,textwrap
from pathlib import Path
from PIL import Image,ImageDraw,ImageFont
P=Path(__file__).resolve().parent;E=P/'edit';V=next((P/'recording').glob('*.mp4'));voices=json.loads((E/'voices.json').read_text())
font='/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf'
cuts={'intro':(1,4),'neck':(5,11),'N1':(12,42),'VUSB':(43,63),'PGND':(64,92),'tradeoffs':(23,34),'final':(100,121)}
clips=[];manifest=[];pos=0
for i,v in enumerate(voices):
 key=v['key'];dur=v['duration']+1.2;out=E/f'clip-{i:02}.mp4';chart=key not in cuts
 im=Image.new('RGBA',(1920,1080),'#101925');im.paste((0,0,0,0),(10,125,1450,990));d=ImageDraw.Draw(im)
 d.text((30,24),'CODEX ASTRA / FUSION',font=ImageFont.truetype(font,23),fill='#50c9d6')
 d.text((30,60),v['title'],font=ImageFont.truetype(font,34),fill='white')
 y=220
 for line in v['lines']:
  for s in textwrap.wrap(line,27):d.text((1480,y),s,font=ImageFont.truetype(font,23),fill='#d7e7f0');y+=37
  y+=25
 if key in ['N1','VUSB','PGND']:
  d.text((1480,650),'Native bridge calls',font=ImageFont.truetype(font,24),fill='#50c9d6')
  for z,s in enumerate(['fusion_run_modeling_script','RIPUP / LINE +0','RATSNEST / SHOW','Native layer '+('2' if key=='PGND' else '15')]):d.text((1480,700+z*38),s,font=ImageFont.truetype(font,19),fill='#d7e7f0')
 if key=='heat':d.text((1480,680),'ESTIMATE',font=ImageFont.truetype(font,28),fill='#edb659');d.text((1480,725),'Not measured temperature',font=ImageFont.truetype(font,21),fill='#d7e7f0')
 footer='POSTPRODUCTION ANALYSIS / stated assumptions' if chart else 'NATIVE FUSION / 3x action + inspection hold / waits removed'
 d.text((30,1025),footer,font=ImageFont.truetype(font,22),fill='#aebfcf');im.save(E/(key+'-overlay.png'))
 if chart:
  ins=['-loop','1','-i',str(E/(key+'.png'))]
  flt='[0:v]scale=1420:860,pad=1920:1080:20:125:color=0x101925[v]'
 else:
  a,b=cuts[key];ins=['-ss',str(a),'-t',str(b-a),'-i',str(V)]
  flt=f'[0:v]setpts=(PTS-STARTPTS)/3,crop=3020:1570:20:370,scale=1420:-2,pad=1420:860:0:60:color=0x101925,pad=1920:1080:20:125:color=0x101925,tpad=stop_mode=clone:stop_duration={dur}[v]'
 flt+=';[v][1:v]overlay=0:0,fps=30,format=yuv420p[out];[2:a]adelay=400|400,apad[a]'
 if not out.exists():subprocess.run(['ffmpeg','-v','error',*ins,'-i',str(E/(key+'-overlay.png')),'-i',v['audio'],'-filter_complex',flt,'-map','[out]','-map','[a]','-t',str(dur),'-c:v','libx264','-preset','fast','-crf','19','-threads','4','-c:a','aac','-b:a','192k','-ar','48000','-y',str(out)],check=True)
 clips.append(out);manifest.append({**v,'outputStart':pos,'outputDuration':dur,'sourceInterval':cuts.get(key),'sourceFile':str(V) if not chart else str(E/(key+'.png')),'speed':3 if not chart else None,'hold':not chart});pos+=dur;print(key,round(pos,1),flush=True)
# Last frame held for five seconds; silent audio remains compatible for concatenation.
last=E/'hold.mp4';subprocess.run(['ffmpeg','-v','error','-sseof','-0.1','-i',str(clips[-1]),'-f','lavfi','-i','anullsrc=r=48000:cl=mono','-vf','tpad=stop_mode=clone:stop_duration=5','-t','5','-map','0:v','-map','1:a','-c:v','libx264','-preset','fast','-crf','19','-threads','4','-c:a','aac','-y',str(last)],check=True);clips.append(last)
(E/'concat.txt').write_text(''.join("file '"+str(x)+"'\n" for x in clips))
subprocess.run(['ffmpeg','-v','error','-f','concat','-safe','0','-i',str(E/'concat.txt'),'-c','copy','-y',str(E/'joined.mp4')],check=True)
# Loudness normalization is done separately, with measured two-pass values.
(E/'edit-manifest.json').write_text(json.dumps({'chapters':manifest,'duration':pos+5,'source':str(V),'cutMethod':'Actual encoded frames inspected every six seconds; initial black frames and validation waits removed. Native action accelerated 3x followed by explicit inspection holds. Analysis charts are postproduction.'},indent=2))