import gi
gi.require_version('Gst','1.0')
from gi.repository import Gst, GLib
import json
import struct
import sys
import time
from pathlib import Path
Gst.init(None)
uri,seconds,output=sys.argv[1],float(sys.argv[2]),Path(sys.argv[3])
loop=GLib.MainLoop()
pipeline=Gst.Pipeline.new('receiver')
src=Gst.ElementFactory.make('rtspsrc');src.set_property('location',uri)
src.set_property('protocols',4);src.set_property('latency',50)
sink=Gst.ElementFactory.make('fakesink');sink.set_property('sync',False)
pipeline.add(src);pipeline.add(sink)
report={'packets':0,'frames':0,'clock_rate':None,'encoding':None,'sdp':False,
        'backward_timestamp':0,'sequence_gaps':0,'pts_backwards':0,'timestamp_samples':[],'errors':[]}
previous_seq=None;previous_ts=None;previous_pts=None;first=time.monotonic()
def probe(pad,info):
    global previous_seq,previous_ts,previous_pts
    buf=info.get_buffer()
    ok,mapped=buf.map(Gst.MapFlags.READ)
    if not ok:return Gst.PadProbeReturn.OK
    header=bytes(mapped.data[:12]);buf.unmap(mapped)
    if len(header)<12 or header[0]>>6!=2:return Gst.PadProbeReturn.OK
    seq,ts=struct.unpack('!HI',header[2:8]);report['packets']+=1
    if previous_seq is not None and (seq-previous_seq)&65535 != 1:report['sequence_gaps']+=1
    previous_seq=seq
    if previous_ts!=ts:
        report['frames']+=1
        delta=None if previous_ts is None else (ts-previous_ts)&0xffffffff
        if delta is not None and delta>=0x80000000:report['backward_timestamp']+=1
        if previous_pts is not None and buf.pts!=Gst.CLOCK_TIME_NONE and buf.pts<previous_pts:report['pts_backwards']+=1
        previous_pts=buf.pts
        if len(report['timestamp_samples'])<150:
            report['timestamp_samples'].append({'rtp':ts,'delta':delta,'receiver_pts_ns':buf.pts,'host_elapsed':round(time.monotonic()-first,4)})
        previous_ts=ts
    return Gst.PadProbeReturn.OK
def pad_added(_src,pad):
    caps=pad.get_current_caps().get_structure(0)
    if caps.get_value('media')!='video':return
    report['clock_rate']=caps.get_value('clock-rate');report['encoding']=caps.get_value('encoding-name')
    pad.add_probe(Gst.PadProbeType.BUFFER,probe)
    pad.link(sink.get_static_pad('sink'))
def sdp(_src,_sdp):report['sdp']=True
def message(_bus,msg):
    if msg.type==Gst.MessageType.ERROR:
        err,debug=msg.parse_error();report['errors'].append(str(err)[:500]);report['error_detail']=debug;loop.quit()
    elif msg.type==Gst.MessageType.EOS:report['eos']=True;loop.quit()
src.connect('pad-added',pad_added);src.connect('on-sdp',sdp)
bus=pipeline.get_bus();bus.add_signal_watch();bus.connect('message',message)
GLib.timeout_add(int(seconds*1000),lambda:(loop.quit(),False)[1])
pipeline.set_state(Gst.State.PLAYING)
try:loop.run()
finally:
    pipeline.set_state(Gst.State.NULL)
    report['elapsed']=time.monotonic()-first
    output.write_text(json.dumps(report,indent=2))
    print(json.dumps({k:v for k,v in report.items() if k!='timestamp_samples'}))
