File size: 5,942 Bytes
88a11cc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | #!/usr/bin/env python3
"""Deterministic FLV pre-start metadata tests; no network or FFmpeg required."""
import os
import pathlib
import sys
import tempfile
ROOT=pathlib.Path(sys.argv[1] if len(sys.argv)>1 else pathlib.Path(__file__).parent).resolve()
sys.path.insert(0,str(ROOT))
os.environ.setdefault('PASSWORD','test-pass-not-for-prod')
os.environ.setdefault('DATA_DIR',tempfile.mkdtemp(prefix='source-probe-test-'))
os.environ['YOUTUBE_FLV_PROBE_SECONDS']='0.2'
import youtube_manager as manager_module # noqa: E402
from relay import Sender # noqa: E402
from stream_monitor import video_dimensions # noqa: E402
passed=failed=0
def check(name,ok,detail=''):
global passed,failed
if ok:
passed+=1;print('PASS ',name)
else:
failed+=1;print('FAIL ',name,detail)
class Bits:
def __init__(self):self.data=[]
def bit(self,value):self.data.append(1 if value else 0)
def bits(self,value,count):
for shift in range(count-1,-1,-1):self.bit((value>>shift)&1)
def ue(self,value):
code=value+1;size=code.bit_length()
self.bits(0,size-1);self.bits(code,size)
def rbsp(self):
self.bit(1)
while len(self.data)%8:self.bit(0)
raw=bytes(sum(self.data[i+j]<<(7-j) for j in range(8)) for i in range(0,len(self.data),8))
out=bytearray();zeros=0
for byte in raw:
if zeros>=2 and byte<=3:
out.append(3);zeros=0
out.append(byte);zeros=zeros+1 if byte==0 else 0
return bytes(out)
def avcc(width,height):
"""Minimal baseline H.264 SPS/PPS with uncropped, frame-only 4:2:0 dimensions."""
assert width%16==0 and height%16==0
b=Bits();b.bits(66,8);b.bits(0,8);b.bits(30,8) # profile/constraints/level
b.ue(0);b.ue(0);b.ue(0);b.ue(0);b.ue(1);b.bit(0)
b.ue(width//16-1);b.ue(height//16-1)
b.bit(1);b.bit(1);b.bit(0);b.bit(0)
sps=b'\x67'+b.rbsp();pps=bytes.fromhex('68ce0fc8')
return (bytes([1,66,0,30,0xff,0xe1])+len(sps).to_bytes(2,'big')+sps+
b'\x01'+len(pps).to_bytes(2,'big')+pps)
def tag(config,ts=0):
body=b'\x17\x00\x00\x00\x00'+config;size=len(body)
header=(b'\x09'+size.to_bytes(3,'big')+bytes([
(ts>>16)&255,(ts>>8)&255,ts&255,(ts>>24)&255,0,0,0]))
return header+body+(11+size).to_bytes(4,'big')
def flv(*tags):
return b'FLV\x01\x05\x00\x00\x00\x09\x00\x00\x00\x00'+b''.join(tags)
class Response:
def __init__(self,chunks):self.chunks=list(chunks);self.closed=False
def raise_for_status(self):return None
def iter_content(self):yield from self.chunks
def close(self):self.closed=True
def manager():
obj=manager_module.ManagedYouTube.__new__(manager_module.ManagedYouTube)
obj.headers_for=lambda user:{}
obj._unstable={};obj.starve={};obj._skip_probe_once={};obj._probe_note=''
obj.logs=[];obj.log=lambda user,text:obj.logs.append(str(text))
return obj
def candidate(width=432,height=864):
return {'url':'https://media.example.test/live.flv','media':'flv','codec':'h264',
'quality_name':'hd','label':'hd','brief':'hd H264','width':width,'height':height}
def probe_with(chunks,cand=None):
obj=manager();responses=[]
original=manager_module.requests.get
def fake_get(*args,**kwargs):
response=Response(chunks);responses.append(response);return response
manager_module.requests.get=fake_get
try:result=obj.probe('alice',cand or candidate())
finally:manager_module.requests.get=original
return result,responses
small=avcc(16,16);real=avcc(640,1280)
check('test SPS generator carries exact dimensions',
video_dimensions('avc1',small)==(16,16) and video_dimensions('avc1',real)==(640,1280))
blob=flv(tag(real));chunks=[blob[i:i+7] for i in range(0,len(blob),7)]
result,responses=probe_with(chunks)
check('fragmented FLV probe returns actual decoder dimensions',
isinstance(result,dict) and (result.get('width'),result.get('height'))==(640,1280),str(result))
check('FLV probe closes the source response',bool(responses) and responses[0].closed)
blob=flv(tag(small),tag(real));split=13+len(tag(small))
result,_=probe_with([blob[:split],blob[split:]])
check('sub-32px startup config is not accepted as the final canvas',
isinstance(result,dict) and (result.get('width'),result.get('height'))==(640,1280),str(result))
result,responses=probe_with([b'FLV'])
check('signature-only FLV keeps the old truthy fallback',result is True,str(result))
check('signature-only fallback also closes the response',bool(responses) and responses[0].closed)
result,_=probe_with([b'NOT AN FLV'])
check('invalid FLV signature is still rejected',result is False,str(result))
obj=manager();obj.probe=lambda user,c:{'width':640,'height':1280,'codec':'avc1'}
selected=obj._select_source('alice',{'sources':[candidate()]})
check('source selection retains a usable candidate',len(selected)==1,str(selected))
if selected:
chosen=selected[0]
check('actual dimensions are attached without rewriting the rendition label',
(chosen.get('width'),chosen.get('height'))==(432,864) and
(chosen.get('probed_width'),chosen.get('probed_height'))==(640,1280) and
obj._source_pin(chosen)==obj._source_pin(candidate()),str(chosen))
sender=Sender('ffmpeg',lambda *args:None)
check('matchgop locks the actual HD canvas instead of the stale SD label',
sender.lock_gold(chosen)==(720,1280),str(sender.lock_gold(chosen)))
check('a declared/probed mismatch is surfaced in operational logs',
any('640x1280' in line and '432x864' in line for line in obj.logs),str(obj.logs))
fallback=Sender('ffmpeg',lambda *args:None)
check('without decoder metadata the old declared-canvas fallback is unchanged',
fallback.lock_gold(candidate())==(432,864),str(fallback.lock_gold(candidate())))
print('source probe tests: %d passed, %d failed'%(passed,failed))
raise SystemExit(1 if failed else 0)
|