From 1d6c6d93548f91cd52259e3986adae1cac3c04ef Mon Sep 17 00:00:00 2001 From: deathbybandaid Date: Mon, 12 Oct 2020 08:15:35 -0400 Subject: [PATCH] Add More Functionality --- fHDHR/fHDHRweb/__init__.py | 46 +++++++++++------- fHDHR/fHDHRweb/fHDHRdevice/__init__.py | 1 + fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py | 56 ++++++++++++++++++++++ fHDHR/fHDHRweb/fHDHRdevice/watch.py | 42 +++++++++++++--- 4 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py diff --git a/fHDHR/fHDHRweb/__init__.py b/fHDHR/fHDHRweb/__init__.py index 3b23e68..13891c4 100644 --- a/fHDHR/fHDHRweb/__init__.py +++ b/fHDHR/fHDHRweb/__init__.py @@ -22,6 +22,7 @@ class HDHR_Hub(): self.watch = fHDHRdevice.WatchStream(settings, origserv, self.tuners) self.station_scan = fHDHRdevice.Station_Scan(settings, origserv) self.xmltv = fHDHRdevice.xmlTV_XML(settings, epghandling) + self.m3u = fHDHRdevice.channels_M3U(settings, origserv) self.htmlerror = fHDHRdevice.HTMLerror(settings) self.debug = fHDHRdevice.Debug_JSON(settings, origserv, epghandling) @@ -65,11 +66,14 @@ class HDHR_Hub(): def get_image(self, request_args): return self.images.get_image(request_args) + def get_channels_m3u(self, base_url): + return self.m3u.get_channels_m3u(base_url) + def get_stream_info(self, request_args): return self.watch.get_stream_info(request_args) - def get_stream(self, channel_id, method, channelUri, content_type): - return self.watch.get_stream(channel_id, method, channelUri, content_type) + def get_stream(self, channel_id, method, channelUri, content_type, duration): + return self.watch.get_stream(channel_id, method, channelUri, content_type, duration) hdhr = HDHR_Hub() @@ -137,13 +141,13 @@ class HDHR_HTTP_Server(): @app.route('/api/xmltv') def api_xmltv(): - if 'DeviceAuth' in list(request.args.keys()): - if request.args['DeviceAuth'] == hdhr.config.dict["dev"]["device_auth"]: - base_url = request.headers["host"] - xmltv = hdhr.get_xmltv(base_url) - return Response(status=200, - response=xmltv, - mimetype='application/xml') + DeviceAuth = request.args.get('DeviceAuth', default=None, type=str) + if DeviceAuth == hdhr.config.dict["dev"]["device_auth"]: + base_url = request.headers["host"] + xmltv = hdhr.get_xmltv(base_url) + return Response(status=200, + response=xmltv, + mimetype='application/xml') return "not subscribed" @app.route('/debug.json', methods=['GET']) @@ -154,6 +158,15 @@ class HDHR_HTTP_Server(): response=debugreport, mimetype='application/json') + @app.route('/api/channels.m3u') + @app.route('/channels.m3u', methods=['GET']) + def channels_m3u(): + base_url = request.headers["host"] + channels_m3u = hdhr.get_channels_m3u(base_url) + return Response(status=200, + response=channels_m3u, + mimetype='text/plain') + @app.route('/images', methods=['GET']) def images(): image, imagetype = hdhr.get_image(request.args) @@ -163,15 +176,16 @@ class HDHR_HTTP_Server(): def auto(channel): request_args = { "channel": channel.replace('v', ''), - "method": hdhr.config.dict["fhdhr"]["stream_type"] + "method": hdhr.config.dict["fhdhr"]["stream_type"], + "duration": request.args.get('duration', default=0, type=int), } channel_id = request_args["channel"] - method, channelUri, content_type = hdhr.get_stream_info(request_args) + method, channelUri, content_type, duration = hdhr.get_stream_info(request_args) if channelUri: if method == "direct": - return Response(hdhr.get_stream(channel_id, method, channelUri, content_type), content_type=content_type, direct_passthrough=True) + return Response(hdhr.get_stream(channel_id, method, channelUri, content_type, duration), content_type=content_type, direct_passthrough=True) elif method == "ffmpeg": - return Response(stream_with_context(hdhr.get_stream(channel_id, method, channelUri, content_type)), mimetype="video/mpeg") + return Response(stream_with_context(hdhr.get_stream(channel_id, method, channelUri, content_type, duration)), mimetype="video/mpeg") abort(503) @app.route('/watch', methods=['GET']) @@ -179,12 +193,12 @@ class HDHR_HTTP_Server(): if 'method' in list(request.args.keys()) and 'channel' in list(request.args.keys()): channel_id = str(request.args["channel"]) method = str(request.args["method"]) - method, channelUri, content_type = hdhr.get_stream_info(request.args) + method, channelUri, content_type, duration = hdhr.get_stream_info(request.args) if channelUri: if method == "direct": - return Response(hdhr.get_stream(channel_id, method, channelUri, content_type), content_type=content_type, direct_passthrough=True) + return Response(hdhr.get_stream(channel_id, method, channelUri, content_type, duration), content_type=content_type, direct_passthrough=True) elif method == "ffmpeg": - return Response(stream_with_context(hdhr.get_stream(channel_id, method, channelUri, content_type)), mimetype="video/mpeg") + return Response(stream_with_context(hdhr.get_stream(channel_id, method, channelUri, content_type, duration)), mimetype="video/mpeg") abort(503) @app.route('/lineup.post', methods=['POST']) diff --git a/fHDHR/fHDHRweb/fHDHRdevice/__init__.py b/fHDHR/fHDHRweb/fHDHRdevice/__init__.py index fa77db4..80d9f81 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/__init__.py +++ b/fHDHR/fHDHRweb/fHDHRdevice/__init__.py @@ -11,5 +11,6 @@ from .lineup_json import Lineup_JSON from .debug_json import Debug_JSON from .lineup_status_json import Lineup_Status_JSON from .xmltv_xml import xmlTV_XML +from .channels_m3u import channels_M3U from .htmlerror import HTMLerror diff --git a/fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py b/fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py new file mode 100644 index 0000000..2f5be65 --- /dev/null +++ b/fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py @@ -0,0 +1,56 @@ +from io import StringIO + + +class channels_M3U(): + + def __init__(self, settings, origserv): + self.config = settings + self.origserv = origserv + + def get_channels_m3u(self, base_url): + + FORMAT_DESCRIPTOR = "#EXTM3U" + RECORD_MARKER = "#EXTINF" + + fakefile = StringIO() + + xmltvurl = ('%s%s/xmltv.xml' % + ("http://", + base_url)) + + fakefile.write( + "%s\n" % ( + FORMAT_DESCRIPTOR + " " + + "url-tvg=\"" + xmltvurl + "\"" + " " + + "x-tvg-url=\"" + xmltvurl + "\"") + ) + + for channel in self.origserv.get_channels(): + + logourl = ('%s%s/images?source=epg&type=channel&id=%s' % + ("http://", + base_url, + str(channel['id']))) + + fakefile.write( + "%s\n" % ( + RECORD_MARKER + ":0" + " " + + "channelID=\"" + str(channel['id']) + "\" " + + "tvg-chno=\"" + str(channel['number']) + "\" " + + "tvg-name=\"" + str(channel['name']) + "\" " + + "tvg-id=\"" + str(channel['number']) + "\" " + + "tvg-logo=\"" + logourl + "\" " + + "group-title=\"" + self.config.dict["fhdhr"]["friendlyname"] + "," + str(channel['name'])) + ) + + fakefile.write( + "%s\n" % ( + ('%s%s/watch?method=%s&channel=%s' % + ("http://", + base_url, + self.config.dict["fhdhr"]["stream_type"], + str(channel['number']))) + ) + ) + + return fakefile.getvalue() diff --git a/fHDHR/fHDHRweb/fHDHRdevice/watch.py b/fHDHR/fHDHRweb/fHDHRdevice/watch.py index 9123e1f..752eee9 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/watch.py +++ b/fHDHR/fHDHRweb/fHDHRdevice/watch.py @@ -1,4 +1,5 @@ import subprocess +import time from fHDHR.fHDHRerrors import TunerError import fHDHR.tools @@ -12,15 +13,26 @@ class WatchStream(): self.tuners = tuners self.web = fHDHR.tools.WebReq() - def direct_stream(self, channel_id, method, channelUri, content_type): + def direct_stream(self, channel_id, method, channelUri, content_type, duration): + chunksize = int(self.tuners.config.dict["direct_stream"]['chunksize']) + if not duration == 0: + duration += time.time() + req = self.web.session.get(channelUri, stream=True) def generate(): try: for chunk in req.iter_content(chunk_size=chunksize): + + if not duration == 0 and not time.time() < duration: + req.close() + print("Requested Duration Expired.") + break + yield chunk + except GeneratorExit: req.close() print("Connection Closed.") @@ -28,7 +40,8 @@ class WatchStream(): return generate() - def ffmpeg_stream(self, channel_id, method, channelUri, content_type): + def ffmpeg_stream(self, channel_id, method, channelUri, content_type, duration): + bytes_per_read = int(self.config.dict["ffmpeg"]["bytes_per_read"]) ffmpeg_command = [self.config.dict["ffmpeg"]["ffmpeg_path"], @@ -40,28 +53,42 @@ class WatchStream(): "pipe:stdout" ] + if not duration == 0: + duration += time.time() + ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE) def generate(): try: while True: + + if not duration == 0 and not time.time() < duration: + ffmpeg_proc.terminate() + ffmpeg_proc.communicate() + print("Requested Duration Expired.") + break + videoData = ffmpeg_proc.stdout.read(bytes_per_read) if not videoData: break + try: yield videoData + except Exception as e: ffmpeg_proc.terminate() ffmpeg_proc.communicate() print("Connection Closed: " + str(e)) + except GeneratorExit: ffmpeg_proc.terminate() ffmpeg_proc.communicate() print("Connection Closed.") self.tuners.tuner_close() + return generate() - def get_stream(self, channel_id, method, channelUri, content_type): + def get_stream(self, channel_id, method, channelUri, content_type, duration): try: self.tuners.tuner_grab() @@ -73,20 +100,21 @@ class WatchStream(): print("Attempting a " + method + " stream request for channel " + str(channel_id)) if method == "ffmpeg": - return self.ffmpeg_stream(channel_id, method, channelUri, content_type) + return self.ffmpeg_stream(channel_id, method, channelUri, content_type, duration) elif method == "direct": - return self.direct_stream(channel_id, method, channelUri, content_type) + return self.direct_stream(channel_id, method, channelUri, content_type, duration) def get_stream_info(self, request_args): method = str(request_args["method"]) channel_id = str(request_args["channel"]) + duration = int(request_args["duration"]) channelUri = self.origserv.get_channel_stream(channel_id) if not channelUri: - return None, None, None + return None, None, None, None channelUri_headers = self.web.session.head(channelUri).headers content_type = channelUri_headers['Content-Type'] - return method, channelUri, content_type + return method, channelUri, content_type, duration