fix(capture): drop dur-patch; rdd9 self-maintains growing Duration

On-node empirical testing of this bmx v1.6 build showed that raw2bmx's
rdd9 writer with --part already maintains a live, correct header Duration
as the file grows: ffprobe reads a growing duration mid-write (e.g. 2.04s
of a 10s clip while still recording), and the structural-metadata
Duration fields (tags 02020008 / 30020008) hold the real frame count
(0x33 = 51), not -1.

The dur-patch.py added in the previous commit searched the header for
Duration=-1 (0xFF*8) and found 0 fields on rdd9 ("[dur-patch] 0 Duration
fields"), so it was a no-op. Worse, opening the MXF r+b to patch it while
raw2bmx appends over CIFS is a concurrency hazard. Remove it entirely and
rely on raw2bmx's native growing Duration. rdd9 + --index-follows remains
the Premiere-recommended growing flavour (Sony XDCAM essence, index in the
essence partition).

Verified on-node (ffprobe/byte-probe). Live edit-while-record in Premiere
itself still requires user confirmation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-06-01 09:07:28 -04:00
parent 068e2eaa87
commit 252aa713d4
2 changed files with 7 additions and 74 deletions

View file

@ -1,67 +0,0 @@
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)

View file

@ -738,13 +738,13 @@ for i in $(seq 1 200); do
sleep 0.1
done
exec 7>&- 8>&-
# Start the MXF header duration patcher (services/capture/dur-patch.py, shipped
# to /app/dur-patch.py by the image build). It overwrites the header Duration=-1
# fields with 0 immediately (Premiere rejects -1 on import; bmx thread 87ac5750)
# and then with the live frame count every 3s, parsed from the last body
# partition IndexTableSegment, so the clip grows in Premiere.
python3 -u /app/dur-patch.py "$OUT" >&2 &
PATCHPID=$!
# No header-duration patcher is needed. In this bmx v1.6 build, raw2bmx's rdd9
# writer with --part maintains a live, correct header Duration as the file grows
# (verified on-node: ffprobe reads a growing duration mid-write, e.g. 2.04s of a
# 10s clip while still recording). A patcher (the earlier dur-patch.py) was a
# no-op here it searched for Duration=-1, which rdd9 never writes and opening
# the file r+b while raw2bmx appends over CIFS only adds concurrency risk.
PATCHPID=
# Wait for ffmpeg (source end), then for raw2bmx to finalize the footer.
wait "$FFPID"; FFRC=$?
wait "$BMXPID"; BMXRC=$?