code wiki / (root) / nx_mp4_faststart.nx

nx_mp4_faststart.nx source

↩ module page · 92 lines · 5387 B

1// nx_mp4_faststart.nx -- qt-faststart: relocate the moov atom to the FRONT of an mp4 so a browser <video> 2// can stream it progressively over HTTP. A non-faststart mp4 has [ftyp][mdat...][moov] (moov at the END), so 3// the player's first Range read has ftyp+mdat but NO moov (the track/sample tables) -> "no video, this format 4// is not supported". Moving moov before mdat fixes it. LOSSLESS: no transcode; the ONLY mutation is rewriting 5// each stco (32-bit) / co64 (64-bit) chunk offset by +moov_size, because inserting moov after ftyp shifts every 6// byte of mdat forward by exactly moov_size. Reads argv[1] (the mp4), writes the faststart mp4 to stdout (fd 1). 7// If the file is ALREADY faststart (moov before mdat) or has no moov, it is copied verbatim (safe no-op). 8// license_tier: ORIGINAL No hw writes (Rule 26). module: nishi-core.torrent.mp4_faststart 9import "nx_syscalls.nx" 10 11const FS_RD: i64 = 262144 12 13func fs_be32(b: *u8, o: i64) -> i64 { return ((b[o] as i64)<<24)|((b[o+1] as i64)<<16)|((b[o+2] as i64)<<8)|(b[o+3] as i64) } 14func fs_be64(b: *u8, o: i64) -> i64 { var v: i64=0; var i: i64=0; while i<8 { v=(v<<8)|(b[o+i] as i64); i=i+1 } return v } 15func fs_put32(b: *u8, o: i64, v: i64) -> i64 { b[o]=((v>>24)&0xff) as u8; b[o+1]=((v>>16)&0xff) as u8; b[o+2]=((v>>8)&0xff) as u8; b[o+3]=(v&0xff) as u8; return 0 } 16func fs_put64(b: *u8, o: i64, v: i64) -> i64 { var i: i64=0; while i<8 { b[o+7-i]=((v>>(i*8))&0xff) as u8; i=i+1 } return 0 } 17func fs_teq(b: *u8, o: i64, t: *u8) -> i64 { var i: i64=0; while i<4 { if b[o+i]!=t[i] { return 0 } i=i+1 } return 1 } 18func fs_iscont(b: *u8, o: i64) -> i64 { 19 if fs_teq(b,o,"moov" as *u8)==1 { return 1 } if fs_teq(b,o,"trak" as *u8)==1 { return 1 } 20 if fs_teq(b,o,"mdia" as *u8)==1 { return 1 } if fs_teq(b,o,"minf" as *u8)==1 { return 1 } 21 if fs_teq(b,o,"stbl" as *u8)==1 { return 1 } if fs_teq(b,o,"edts" as *u8)==1 { return 1 } 22 return 0 23} 24 25// recursively walk the box tree in m[start,end) and add `shift` to every stco/co64 chunk offset. 26func fs_adjust(m: *u8, start: i64, end: i64, shift: i64) -> i64 { 27 var p: i64 = start 28 while p + 8 <= end { 29 var sz: i64 = fs_be32(m, p); var hdr: i64 = 8 30 if sz == 1 { if p+16<=end { sz = fs_be64(m, p+8); hdr = 16 } else { p = end } } 31 if sz < 8 { p = end } else { 32 var boxend: i64 = p + sz; if boxend > end { boxend = end } 33 if fs_iscont(m, p+4) == 1 { fs_adjust(m, p+hdr, boxend, shift) } 34 else { 35 if fs_teq(m,p+4,"stco" as *u8)==1 { 36 let cnt: i64 = fs_be32(m, p+12); var e: i64=0 37 while e < cnt { let eo: i64 = p+16+e*4; if eo+4<=boxend { fs_put32(m, eo, fs_be32(m,eo)+shift) } e=e+1 } 38 } 39 if fs_teq(m,p+4,"co64" as *u8)==1 { 40 let cnt2: i64 = fs_be32(m, p+12); var e2: i64=0 41 while e2 < cnt2 { let eo2: i64 = p+16+e2*8; if eo2+8<=boxend { fs_put64(m, eo2, fs_be64(m,eo2)+shift) } e2=e2+1 } 42 } 43 } 44 p = p + sz 45 } 46 } 47 return 0 48} 49 50// stream file[start..start+len) to stdout in FS_RD chunks. 51func fs_emit(fd: i64, start: i64, len: i64, buf: *u8) -> i64 { 52 sys_lseek(fd, start, 0); var rem: i64 = len 53 while rem > 0 { var want: i64 = FS_RD; if rem < want { want = rem } let r: i64 = sys_read(fd, buf, want); if r<=0 { rem=0 } else { sys_write(1, buf, r); rem=rem-r } } 54 return 0 55} 56 57func main(argc: i64, argv: *i64) -> i64 { 58 if argc < 2 { sys_exit(2); return 2 } 59 let fd: i64 = sys_openat_rd(argv[1] as *u8); if fd < 0 { sys_exit(1); return 1 } 60 let fsz: i64 = sys_lseek(fd, 0, 2) 61 let cbuf: *u8 = sys_mmap(FS_RD) 62 // scan top-level boxes: ftyp end F, moov start V + size moovsz 63 let hb: *u8 = sys_mmap(32) 64 var pos: i64 = 0; var F: i64 = 0; var V: i64 = 0-1; var moovsz: i64 = 0; var go: i64 = 1 65 while go == 1 { 66 if pos + 8 > fsz { go = 0 } else { 67 sys_lseek(fd, pos, 0); let r: i64 = sys_read(fd, hb, 16) 68 if r < 8 { go = 0 } else { 69 var sz: i64 = fs_be32(hb, 0) 70 if sz == 1 { sz = fs_be64(hb, 8) } 71 if sz < 8 { go = 0 } else { 72 if fs_teq(hb,4,"ftyp" as *u8)==1 { F = pos + sz } 73 if fs_teq(hb,4,"moov" as *u8)==1 { V = pos; moovsz = sz; go = 0 } // found moov -> done scanning 74 pos = pos + sz 75 } 76 } 77 } 78 } 79 // no moov found, OR moov already at/near the front (before the data) -> emit verbatim (safe no-op) 80 if V < 0 { fs_emit(fd, 0, fsz, cbuf); sys_close(fd); sys_exit(0); return 0 } 81 if V <= F { fs_emit(fd, 0, fsz, cbuf); sys_close(fd); sys_exit(0); return 0 } 82 // load moov, adjust every chunk offset by +moovsz (mdat shifts forward by moovsz when moov moves to front) 83 let moov: *u8 = sys_mmap(moovsz + 64) 84 sys_lseek(fd, V, 0); var mrem: i64 = moovsz; var mo: i64 = 0 85 while mrem > 0 { var w: i64 = FS_RD; if mrem<w { w=mrem } let r2: i64 = sys_read(fd, (moov as i64 + mo) as *u8, w); if r2<=0 { mrem=0 } else { mo=mo+r2; mrem=mrem-r2 } } 86 fs_adjust(moov, 0, moovsz, moovsz) 87 // emit: ftyp [0..F] + adjusted moov + [F..V] (mdat and anything between ftyp and the old moov) 88 fs_emit(fd, 0, F, cbuf) 89 sys_write(1, moov, moovsz) 90 fs_emit(fd, F, V - F, cbuf) 91 sys_close(fd); sys_exit(0); return 0 92}