This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
unifi-cam-proxy/unifi/clock_sync.py

180 lines
5.0 KiB
Python
Raw Permalink Normal View History

2024-03-02 11:37:04 +00:00
""""
Helper program to inject absolute wall clock time into FLV stream for recordings
"""
import argparse
import struct
import sys
import time
from flvlib3.astypes import FLVObject
from flvlib3.primitives import make_ui8, make_ui32
from flvlib3.tags import create_script_tag
def read_bytes(source, num_bytes):
read_bytes = 0
buf = b""
while read_bytes < num_bytes:
d_in = source.read(num_bytes - read_bytes)
if d_in:
read_bytes += len(d_in)
buf += d_in
else:
return buf
return buf
def write(data):
sys.stdout.buffer.write(data)
def write_log(data):
sys.stderr.buffer.write(f"{data}\n".encode())
def write_timestamp_trailer(is_packet, ts):
# Write 15 byte trailer
write(make_ui8(0))
if is_packet:
write(bytes([1, 95, 144, 0, 0, 0, 0, 0, 0, 0, 0]))
else:
write(bytes([0, 43, 17, 0, 0, 0, 0, 0, 0, 0, 0]))
write(make_ui32(int(ts * 1000 * 100)))
def main(args):
source = sys.stdin.buffer
header = read_bytes(source, 3)
if header != b"FLV":
print("Not a valid FLV file")
return
write(header)
# Skip rest of FLV header
write(read_bytes(source, 1))
read_bytes(source, 1)
# Write custom bitmask for FLV type
write(make_ui8(7))
write(read_bytes(source, 4))
# Tag 0 previous size
write(read_bytes(source, 4))
last_ts = time.time()
start = time.time()
i = 0
while True:
# Packet structure from Wikipedia:
#
# Size of previous packet uint32_be 0 For first packet set to NULL
#
# Packet Type uint8 18 For first packet set to AMF Metadata
# Payload Size uint24_be varies Size of packet data only
# Timestamp Lower uint24_be 0 For first packet set to NULL
# Timestamp Upper uint8 0 Extension to create a uint32_be value
# Stream ID uint24_be 0 For first stream of same type set to NULL
#
# Payload Data freeform varies Data as defined by packet type
header = read_bytes(source, 12)
if len(header) != 12:
write(header)
return
# Packet type
packet_type = header[0]
# Get payload size to know how many bytes to read
high, low = struct.unpack(">BH", header[1:4])
payload_size = (high << 16) + low
# Get timestamp to inject into clock sync tag
low_high = header[4:8]
combined = bytes([low_high[3]]) + low_high[:3]
timestamp = struct.unpack(">i", combined)[0]
now = time.time()
if not last_ts or now - last_ts >= 5:
last_ts = now
# Insert a custom packet every so often for time synchronization
data = FLVObject()
data["streamClock"] = int(timestamp)
data["streamClockBase"] = 0
data["wallClock"] = now * 1000
packet_to_inject = create_script_tag("onClockSync", data, timestamp)
write(packet_to_inject)
# Write 15 byte trailer
write_timestamp_trailer(False, now - start)
# Write mpma tag
# {'cs': {'cur': 1500000.0,
# 'max': 1500000.0,
# 'min': 32000.0},
# 'm': {'cur': 750000.0,
# 'max': 1500000.0,
# 'min': 750000.0},
# 'r': 0.0,
# 'sp': {'cur': 1500000.0,
# 'max': 1500000.0,
# 'min': 150000.0},
# 't': 750000.0}
data = FLVObject()
data["cs"] = FLVObject()
data["cs"]["cur"] = 1500000
data["cs"]["max"] = 1500000
data["cs"]["min"] = 1500000
data["m"] = FLVObject()
data["m"]["cur"] = 1500000
data["m"]["max"] = 1500000
data["m"]["min"] = 1500000
data["r"] = 0
data["sp"] = FLVObject()
data["sp"]["cur"] = 1500000
data["sp"]["max"] = 1500000
data["sp"]["min"] = 1500000
data["t"] = 75000.0
packet_to_inject = create_script_tag("onMpma", data, 0)
write(packet_to_inject)
# Write 15 byte trailer
write_timestamp_trailer(False, now - start)
# Write rest of original packet minus previous packet size
write(header)
write(read_bytes(source, payload_size))
else:
# Write the original packet
write(header)
write(read_bytes(source, payload_size))
# Write previous packet size
write(read_bytes(source, 3))
# Write 15 byte trailer
write_timestamp_trailer(packet_type == 9, now - start)
# Write mpma tag
i += 1
def parse_args():
parser = argparse.ArgumentParser(description="Modify Protect FLV stream")
parser.add_argument(
"--write-timestamps",
action="store_true",
help="Indicates we should write timestamp in between packets",
)
return parser.parse_args()
if __name__ == "__main__":
main(parse_args())