From 252aa713d40dd54b18c213ffa811745b18fe4235 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Mon, 1 Jun 2026 09:07:28 -0400 Subject: [PATCH] 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 --- services/capture/dur-patch.py | 67 ------------------------- services/capture/src/capture-manager.js | 14 +++--- 2 files changed, 7 insertions(+), 74 deletions(-) delete mode 100644 services/capture/dur-patch.py diff --git a/services/capture/dur-patch.py b/services/capture/dur-patch.py deleted file mode 100644 index cec7cf0..0000000 --- a/services/capture/dur-patch.py +++ /dev/null @@ -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)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 p2H",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) diff --git a/services/capture/src/capture-manager.js b/services/capture/src/capture-manager.js index cdf9470..9d17959 100644 --- a/services/capture/src/capture-manager.js +++ b/services/capture/src/capture-manager.js @@ -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=$?