Last active
June 1, 2024 14:07
-
-
Save mosquito/989dd7e70eb1244c269e to your computer and use it in GitHub Desktop.
RTSP to HTTP python proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
tornado>4 | |
construct |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# encoding: utf-8 | |
import time | |
import sys | |
import threading | |
import tornado.web | |
import tornado.ioloop | |
import tornado.gen | |
from functools import partial | |
from tornado.log import app_log as log | |
from tornado.options import define, options | |
import os | |
import re | |
import tempfile | |
import uuid | |
import subprocess | |
import construct | |
MPEGTSHeader = construct.Struct( | |
"MPEGTSHeader", | |
construct.Magic('G'), | |
construct.EmbeddedBitStruct( | |
construct.Flag('transport_error_indicator'), | |
construct.Flag('payload_unit_start'), | |
construct.Flag('transport_priority'), | |
construct.Bits('pid', 13), | |
construct.Bits('scrambling', 2), | |
construct.Bits("adaptation_field_control", 2), | |
construct.Bits("continuity_counter", 4), | |
) | |
) | |
define('url', type=str, default="") | |
define('protocol', default='tcp') | |
define('port', default=8888, type=int) | |
define('address', default='127.0.0.1') | |
define('no_audio', default=False, type=bool) | |
class HTTPHandler(tornado.web.RequestHandler): | |
CLIENTS = set([]) | |
INFO = [] | |
def initialize(self): | |
self.CLIENTS.add(self) | |
self.lock = True | |
self.alive = True | |
self.initiated = False | |
def finish(self, *args, **kwargs): | |
self.alive = False | |
self.CLIENTS.remove(self) | |
return super(HTTPHandler, self).finish(*args, **kwargs) | |
def on_connection_close(self, *args, **kwargs): | |
self.finish() | |
return super(HTTPHandler, self).on_connection_close(*args, **kwargs) | |
@tornado.gen.coroutine | |
def write_media(self, is_pat, frame): | |
if not self.lock and self.alive: | |
if self.initiated: | |
self.write(frame) | |
yield self.flush() | |
elif not self.initiated and is_pat: | |
self.initiated = True | |
self.write(frame) | |
@tornado.web.asynchronous | |
def get(self): | |
codecs = ",".join("{0[codec]}.{0[pid]}".format(i) for i in self.INFO) | |
self.set_header('Content-Type', 'video/mp2t;codecs="%s"' % codecs) | |
self.flush() | |
self.lock = False | |
def Pusher(): | |
ioloop = tornado.ioloop.IOLoop.instance() | |
while True: | |
is_pat, frame = yield | |
try: | |
for client in HTTPHandler.CLIENTS: | |
ioloop.add_callback(partial(client.write_media, is_pat, frame)) | |
except Exception as e: | |
log.exception(e) | |
if __name__ == '__main__': | |
options.parse_command_line() | |
stream_parser = re.compile(r'\s*Stream\s#\d+\:(?P<pid>\d+)\:\s\w+\:\s(?P<codec>\S+)') | |
def worker(): | |
env = dict() | |
env['PATH'] = os.environ.get('PATH', '') | |
env['TERM'] = 'vt100' | |
while True: | |
cmd = ("avconv", "-rtsp_transport", options.protocol, "-i", str(options.url)) | |
cmd += ("-c:v", "copy") | |
if options.no_audio: | |
cmd += ("-map", "0:0") | |
cmd += ("-f", "mpegts", '-streamid', '0:0', "-") | |
else: | |
cmd += ("-map", "0", "-c:a", "copy") | |
cmd += ("-f", "mpegts", '-streamid', '0:0', '-streamid', '-muxrate', '100', '1:1', "-") | |
process = subprocess.Popen( | |
cmd, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
shell=False, | |
env=env, | |
cwd=tempfile.gettempdir() | |
) | |
log.info('Capturing running') | |
def get_info(process): | |
info = '' | |
payload = False | |
data = process.stderr.readline() | |
out = list() | |
while data: | |
if "Output " in data: | |
break | |
info += data | |
data = process.stderr.readline() | |
buf = '' | |
for line in info.split('\n'): | |
if 'Audio' in line and options.no_audio: | |
continue | |
if 'Stream ' in line: | |
m = stream_parser.match(line) | |
if m is not None: | |
out.append(m.groupdict()) | |
return out | |
HTTPHandler.INFO = get_info(process) | |
pusher = Pusher() | |
pusher.next() | |
ioloop = tornado.ioloop.IOLoop.instance() | |
retcode = process.poll() | |
while retcode is None: | |
retcode = process.poll() | |
# MPEG-TS has fixed packet size | |
chunk = process.stdout.read(188) | |
if not chunk: | |
continue | |
try: | |
hdr = MPEGTSHeader.parse(chunk[0:5]) | |
pusher.send((hdr.pid == 0, chunk)) | |
except Exception as e: | |
log.error("%r", chunk[0:5]) | |
log.exception(e) | |
log.info('Capturing stoped') | |
t = threading.Thread(target=worker) | |
t.daemon = True | |
t.start() | |
application = tornado.web.Application([ | |
("/", HTTPHandler), | |
]) | |
application.listen(options.port, address=options.address) | |
tornado.ioloop.IOLoop.instance().start() |
Hello,
would you write please a short usage?
Thank you in advance!
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/local/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/usr/local/lib/python3.7/threading.py", line 865, in run
self._target(*self._args, **self._kwargs)
File "rtsp2http.py", line 116, in worker
cwd=tempfile.gettempdir()
File "/usr/local/lib/python3.7/subprocess.py", line 756, in init
restore_signals, start_new_session)
File "/usr/local/lib/python3.7/subprocess.py", line 1499, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'avconv': 'avconv'
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The current version of the contsruct has diferrent API. Could you try with my fork cython-construct?
pip install cython-construct