The previous sed/python in-place edits on the node broke capture: the hires stderr parser was written with literal 0x08 BACKSPACE bytes instead of regex word boundaries, so it never matched ffmpeg output. framesReceived stayed 0, the shutdown handler saw "no frames" and marked every asset as an error even though video was captured. The ffmpeg base args had also been changed to -progress pipe:2, whose key=value output puts frame= and fps= on separate lines and does not match a combined regex. Fixes: - Parser: single robust regex matching ffmpeg's classic -stats line (frame= and fps= together). No backspace bytes, no word boundaries. - ffmpeg base args back to -stats (drop -progress pipe:2). Growing-file (Premiere edit-while-record), per bmx thread 87ac5750 and Drastic/Softron edit-while-ingest docs: - raw2bmx clip type op1a -> rdd9 (Sony XDCAM / RDD-9, the flavour Premiere reads while growing) with --index-follows so the IndexTableSegment is written in the same partition as the essence it indexes (lets a reader re-scanning body partitions seek toward the record head). NOT --avid-gf (Avid OP-Atom, Media-Composer-only, needs a companion AAF). - dur-patch.py: overwrite header Duration=-1 to 0 immediately at clip-open (Premiere rejects -1 on import), then track the live frame count every 3s from the last body partition IndexTableSegment. Shipped as services/capture/dur-patch.py (/app/dur-patch.py in the image). Deployed to wild-dragon-capture:latest on zampp2 via overlay build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.3 KiB
Python
67 lines
2.3 KiB
Python
import sys,time,struct,os
|
|
BODY=bytes([6,14,43,52,2,5,1,1,13,1,2,1,1,3])
|
|
ITS=bytes([6,14,43,52,2,83,1,1,13,1,2,1,1,16,1,0])
|
|
def get_frames(p):
|
|
try:
|
|
sz=os.path.getsize(p)
|
|
if sz<50000:return 0
|
|
with open(p,"rb") as f:
|
|
f.seek(max(0,sz-3000000));d=f.read()
|
|
pos=len(d)
|
|
while True:
|
|
i=d.rfind(BODY,0,pos)
|
|
if i<0:break
|
|
pos=i;bs=i+16;b0=d[bs]
|
|
if b0==0x83:bl,bv=4,struct.unpack(">I",b"\x00"+d[bs+1:bs+4])[0]
|
|
elif b0==0x82:bl,bv=3,struct.unpack(">H",d[bs+1:bs+3])[0]
|
|
else:bl,bv=1,b0
|
|
if len(d)<bs+bl+48:break
|
|
ibc=struct.unpack(">Q",d[bs+bl+40:bs+bl+48])[0]
|
|
if not ibc:continue
|
|
itd=d[i+16+bl+bv:i+16+bl+bv+ibc]
|
|
ii=itd.find(ITS)
|
|
if ii<0:break
|
|
ib0=itd[ii+16]
|
|
if ib0==0x83:ivl,ivv=4,struct.unpack(">I",b"\x00"+itd[ii+17:ii+20])[0]
|
|
elif ib0==0x82:ivl,ivv=3,struct.unpack(">H",itd[ii+17:ii+19])[0]
|
|
else:ivl,ivv=1,ib0
|
|
sd=itd[ii+16+ivl:ii+16+ivl+ivv];p2=isp=dur=0
|
|
while p2<len(sd)-3:
|
|
t=struct.unpack(">H",sd[p2:p2+2])[0];l=struct.unpack(">H",sd[p2+2:p2+4])[0]
|
|
if p2+4+l>len(sd):break
|
|
v=sd[p2+4:p2+4+l]
|
|
if t==0x3F0C and l==8:isp=struct.unpack(">q",v)[0]
|
|
elif t==0x3F0D and l==8:dur=struct.unpack(">q",v)[0]
|
|
p2+=4+l
|
|
return max(0,isp+dur)
|
|
except:return 0
|
|
return 0
|
|
mxf=sys.argv[1]
|
|
while not os.path.exists(mxf) or os.path.getsize(mxf)<20000:time.sleep(0.3)
|
|
with open(mxf,"rb") as f:hdr=f.read(200000)
|
|
offs=[]
|
|
for pat in [b"\x02\x02\x00\x08\xff\xff\xff\xff\xff\xff\xff\xff",b"\x30\x02\x00\x08\xff\xff\xff\xff\xff\xff\xff\xff"]:
|
|
p=0
|
|
while True:
|
|
i=hdr.find(pat,p)
|
|
if i<0:break
|
|
offs.append(i+4);p=i+1
|
|
print("[dur-patch] %d Duration fields"%len(offs),flush=True)
|
|
def patch(val):
|
|
if not offs:return
|
|
try:
|
|
with open(mxf,"r+b") as f:
|
|
for o in offs:f.seek(o);f.write(struct.pack(">q",val))
|
|
except Exception as e:print("[dur-patch] err:",e,flush=True)
|
|
# Premiere rejects a growing OP1a/RDD9 whose header Duration is -1 on import
|
|
# (bmx thread 87ac5750: Premiere prefers 0). raw2bmx writes -1 at clip-open, so
|
|
# overwrite every -1 with 0 immediately, BEFORE any frames exist, then track the
|
|
# live frame count every 3s.
|
|
patch(0)
|
|
print("[dur-patch] Duration=0 (initial)",flush=True)
|
|
while True:
|
|
time.sleep(3)
|
|
fc=get_frames(mxf)
|
|
if fc>0:
|
|
patch(fc)
|
|
print("[dur-patch] Duration=%d"%fc,flush=True)
|