From 0a41d8897dfb08475f2c22e59efa99e6f2198275 Mon Sep 17 00:00:00 2001 From: deathbybandaid Date: Tue, 20 Oct 2020 15:16:10 -0400 Subject: [PATCH] Core Rewrite --- fHDHR/__init__.py | 2 +- fHDHR/{fHDHRweb => api}/__init__.py | 138 ++++----------- fHDHR/api/hub/__init__.py | 60 +++++++ fHDHR/api/hub/device/__init__.py | 23 +++ fHDHR/api/hub/device/channels.py | 159 ++++++++++++++++++ .../__init__.py => api/hub/device/epg.py} | 38 ++++- fHDHR/api/hub/device/epgtypes/__init__.py | 0 .../hub/device}/epgtypes/blocks.py | 12 +- .../hub/device}/epgtypes/zap2it.py | 8 +- .../fHDHRdevice => api/hub/device}/images.py | 6 +- .../hub/device}/station_scan.py | 6 +- .../fHDHRdevice => api/hub/device}/tuners.py | 16 +- .../fHDHRdevice => api/hub/device}/watch.py | 2 +- fHDHR/api/hub/files/__init__.py | 28 +++ .../hub/files}/channels_m3u.py | 6 +- fHDHR/api/hub/files/debug_json.py | 16 ++ .../hub/files}/device_xml.py | 0 .../hub/files}/discover_json.py | 0 .../hub/files}/lineup_json.py | 6 +- .../hub/files}/lineup_status_json.py | 11 +- .../hub/files}/lineup_xml.py | 6 +- .../hub/files}/xmltv_xml.py | 6 +- fHDHR/api/hub/pages/__init__.py | 11 ++ .../hub/pages}/htmlerror.py | 0 .../hub/pages}/index_html.py | 26 ++- fHDHR/cli/run.py | 40 ++--- fHDHR/config/__init__.py | 12 +- fHDHR/epghandler/__init__.py | 30 ---- fHDHR/{fHDHRerrors => exceptions}/__init__.py | 4 +- fHDHR/fHDHRweb/fHDHRdevice/__init__.py | 16 -- fHDHR/fHDHRweb/fHDHRdevice/debug_json.py | 16 -- fHDHR/fHDHRweb/fHDHRpages/__init__.py | 2 - fHDHR/origin/__init__.py | 3 + fHDHR/origin/origin_channels.py | 107 ++++++++++++ .../nextpvr.py => origin/origin_epg.py} | 121 ++----------- fHDHR/originservice/__init__.py | 95 ----------- fHDHR/tools/__init__.py | 8 - 37 files changed, 584 insertions(+), 456 deletions(-) rename fHDHR/{fHDHRweb => api}/__init__.py (52%) create mode 100644 fHDHR/api/hub/__init__.py create mode 100644 fHDHR/api/hub/device/__init__.py create mode 100644 fHDHR/api/hub/device/channels.py rename fHDHR/{epghandler/epgtypes/__init__.py => api/hub/device/epg.py} (65%) create mode 100644 fHDHR/api/hub/device/epgtypes/__init__.py rename fHDHR/{epghandler => api/hub/device}/epgtypes/blocks.py (92%) rename fHDHR/{epghandler => api/hub/device}/epgtypes/zap2it.py (98%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/device}/images.py (92%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/device}/station_scan.py (82%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/device}/tuners.py (86%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/device}/watch.py (98%) create mode 100644 fHDHR/api/hub/files/__init__.py rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/channels_m3u.py (93%) create mode 100644 fHDHR/api/hub/files/debug_json.py rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/device_xml.py (100%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/discover_json.py (100%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/lineup_json.py (67%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/lineup_status_json.py (75%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/lineup_xml.py (86%) rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/files}/xmltv_xml.py (97%) create mode 100644 fHDHR/api/hub/pages/__init__.py rename fHDHR/{fHDHRweb/fHDHRdevice => api/hub/pages}/htmlerror.py (100%) rename fHDHR/{fHDHRweb/fHDHRpages => api/hub/pages}/index_html.py (61%) delete mode 100644 fHDHR/epghandler/__init__.py rename fHDHR/{fHDHRerrors => exceptions}/__init__.py (90%) delete mode 100644 fHDHR/fHDHRweb/fHDHRdevice/__init__.py delete mode 100644 fHDHR/fHDHRweb/fHDHRdevice/debug_json.py delete mode 100644 fHDHR/fHDHRweb/fHDHRpages/__init__.py create mode 100644 fHDHR/origin/__init__.py create mode 100644 fHDHR/origin/origin_channels.py rename fHDHR/{originservice/nextpvr.py => origin/origin_epg.py} (57%) delete mode 100644 fHDHR/originservice/__init__.py diff --git a/fHDHR/__init__.py b/fHDHR/__init__.py index 60f4d33..3f4881b 100644 --- a/fHDHR/__init__.py +++ b/fHDHR/__init__.py @@ -1,2 +1,2 @@ # coding=utf-8 -fHDHR_VERSION = "v0.2.0-beta" +fHDHR_VERSION = "v0.2.2-beta" diff --git a/fHDHR/fHDHRweb/__init__.py b/fHDHR/api/__init__.py similarity index 52% rename from fHDHR/fHDHRweb/__init__.py rename to fHDHR/api/__init__.py index 6a0e9ed..6b96c7b 100644 --- a/fHDHR/fHDHRweb/__init__.py +++ b/fHDHR/api/__init__.py @@ -1,87 +1,10 @@ from gevent.pywsgi import WSGIServer from flask import Flask, send_from_directory, request, abort, Response, stream_with_context -from . import fHDHRdevice, fHDHRpages +from . import hub -class HDHR_Hub(): - - def __init__(self): - pass - - def hubprep(self, settings, origserv, epghandling): - self.config = settings - - self.index = fHDHRpages.Index_HTML(settings) - - self.devicexml = fHDHRdevice.Device_XML(settings) - self.discoverjson = fHDHRdevice.Discover_JSON(settings) - self.lineupxml = fHDHRdevice.Lineup_XML(settings, origserv) - self.lineupjson = fHDHRdevice.Lineup_JSON(settings, origserv) - self.lineupstatusjson = fHDHRdevice.Lineup_Status_JSON(settings, origserv) - self.images = fHDHRdevice.imageHandler(settings, epghandling) - self.tuners = fHDHRdevice.Tuners(settings) - 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) - - self.origserv = origserv - self.epghandling = epghandling - - def tuner_grab(self): - self.tuners.tuner_grab() - - def tuner_close(self): - self.tuners.tuner_close() - - def get_xmltv(self, base_url): - return self.xmltv.get_xmltv_xml(base_url) - - def get_device_xml(self, base_url): - return self.devicexml.get_device_xml(base_url) - - def get_discover_json(self, base_url): - return self.discoverjson.get_discover_json(base_url) - - def get_lineup_status_json(self): - return self.lineupstatusjson.get_lineup_json(self.station_scan.scanning()) - - def get_lineup_xml(self, base_url): - return self.lineupxml.get_lineup_xml(base_url) - - def get_lineup_json(self, base_url): - return self.lineupjson.get_lineup_json(base_url) - - def get_debug_json(self, base_url): - return self.debug.get_debug_json(base_url, self.tuners) - - def get_html_error(self, message): - return self.htmlerror.get_html_error(message) - - def post_lineup_scan_start(self): - self.station_scan.scan() - - 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, stream_args): - return self.watch.get_stream_info(stream_args) - - def get_stream(self, stream_args): - return self.watch.get_stream(stream_args) - - def get_index_html(self, base_url): - return self.index.get_index_html(base_url) - - -hdhr = HDHR_Hub() +fhdhrhub = hub.fHDHR_Hub() class HDHR_HTTP_Server(): @@ -90,18 +13,18 @@ class HDHR_HTTP_Server(): @app.route('/') def root_path(): base_url = request.headers["host"] - return hdhr.get_index_html(base_url) + return fhdhrhub.get_index_html(base_url) @app.route('/favicon.ico', methods=['GET']) def favicon(): - return send_from_directory(hdhr.config.dict["filedir"]["www_dir"], + return send_from_directory(fhdhrhub.config.dict["filedir"]["www_dir"], 'favicon.ico', mimetype='image/vnd.microsoft.icon') @app.route('/device.xml', methods=['GET']) def device_xml(): base_url = request.headers["host"] - device_xml = hdhr.get_device_xml(base_url) + device_xml = fhdhrhub.get_device_xml(base_url) return Response(status=200, response=device_xml, mimetype='application/xml') @@ -109,14 +32,14 @@ class HDHR_HTTP_Server(): @app.route('/discover.json', methods=['GET']) def discover_json(): base_url = request.headers["host"] - discover_json = hdhr.get_discover_json(base_url) + discover_json = fhdhrhub.get_discover_json(base_url) return Response(status=200, response=discover_json, mimetype='application/json') @app.route('/lineup_status.json', methods=['GET']) def lineup_status_json(): - linup_status_json = hdhr.get_lineup_status_json() + linup_status_json = fhdhrhub.get_lineup_status_json() return Response(status=200, response=linup_status_json, mimetype='application/json') @@ -124,7 +47,7 @@ class HDHR_HTTP_Server(): @app.route('/lineup.xml', methods=['GET']) def lineup_xml(): base_url = request.headers["host"] - lineupxml = hdhr.get_lineup_xml(base_url) + lineupxml = fhdhrhub.get_lineup_xml(base_url) return Response(status=200, response=lineupxml, mimetype='application/xml') @@ -132,7 +55,7 @@ class HDHR_HTTP_Server(): @app.route('/lineup.json', methods=['GET']) def lineup_json(): base_url = request.headers["host"] - station_list = hdhr.get_lineup_json(base_url) + station_list = fhdhrhub.get_lineup_json(base_url) return Response(status=200, response=station_list, mimetype='application/json') @@ -140,7 +63,7 @@ class HDHR_HTTP_Server(): @app.route('/xmltv.xml', methods=['GET']) def xmltv_xml(): base_url = request.headers["host"] - xmltv = hdhr.get_xmltv(base_url) + xmltv = fhdhrhub.get_xmltv(base_url) return Response(status=200, response=xmltv, mimetype='application/xml') @@ -148,9 +71,9 @@ class HDHR_HTTP_Server(): @app.route('/api/xmltv') def api_xmltv(): DeviceAuth = request.args.get('DeviceAuth', default=None, type=str) - if DeviceAuth == hdhr.config.dict["dev"]["device_auth"]: + if DeviceAuth == fhdhrhub.config.dict["dev"]["device_auth"]: base_url = request.headers["host"] - xmltv = hdhr.get_xmltv(base_url) + xmltv = fhdhrhub.get_xmltv(base_url) return Response(status=200, response=xmltv, mimetype='application/xml') @@ -159,55 +82,64 @@ class HDHR_HTTP_Server(): @app.route('/debug.json', methods=['GET']) def debug_json(): base_url = request.headers["host"] - debugreport = hdhr.get_debug_json(base_url) + debugreport = fhdhrhub.get_debug_json(base_url) return Response(status=200, response=debugreport, mimetype='application/json') - @app.route('/api/channels.m3u') + @app.route('/api/channels.m3u', methods=['GET']) @app.route('/channels.m3u', methods=['GET']) def channels_m3u(): base_url = request.headers["host"] - channels_m3u = hdhr.get_channels_m3u(base_url) + channels_m3u = fhdhrhub.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) + image, imagetype = fhdhrhub.get_image(request.args) return Response(image, content_type=imagetype, direct_passthrough=True) @app.route('/auto/') def auto(channel): + base_url = request.headers["host"] stream_args = { "channel": channel.replace('v', ''), - "method": request.args.get('method', default=hdhr.config.dict["fhdhr"]["stream_type"], type=str), + "method": request.args.get('method', default=fhdhrhub.config.dict["fhdhr"]["stream_type"], type=str), "duration": request.args.get('duration', default=0, type=int), - "accessed": str(request.headers["host"]) + str(request.url_rule), + "accessed": fhdhrhub.device.channels.get_fhdhr_stream_url(base_url, channel.replace('v', '')), } - stream_args = hdhr.get_stream_info(stream_args) + stream_args = fhdhrhub.get_stream_info(stream_args) if stream_args["channelUri"]: if stream_args["method"] == "direct": - return Response(hdhr.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True) + return Response(fhdhrhub.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True) elif stream_args["method"] == "ffmpeg": - return Response(stream_with_context(hdhr.get_stream(stream_args)), mimetype="video/mpeg") + return Response(stream_with_context(fhdhrhub.get_stream(stream_args)), mimetype="video/mpeg") abort(503) + @app.route('/chanscan', methods=['GET']) + def chanscan(): + fhdhrhub.post_lineup_scan_start() + linup_status_json = fhdhrhub.get_lineup_status_json() + return Response(status=200, + response=linup_status_json, + mimetype='application/json') + @app.route('/lineup.post', methods=['POST']) def lineup_post(): if 'scan' in list(request.args.keys()): if request.args['scan'] == 'start': - hdhr.post_lineup_scan_start() + fhdhrhub.post_lineup_scan_start() return Response(status=200, mimetype='text/html') elif request.args['scan'] == 'abort': return Response(status=200, mimetype='text/html') else: print("Unknown scan command " + request.args['scan']) - currenthtmlerror = hdhr.get_html_error("501 - " + request.args['scan'] + " is not a valid scan command") + currenthtmlerror = fhdhrhub.get_html_error("501 - " + request.args['scan'] + " is not a valid scan command") return Response(status=200, response=currenthtmlerror, mimetype='text/html') else: - currenthtmlerror = hdhr.get_html_error("501 - not a valid command") + currenthtmlerror = fhdhrhub.get_html_error("501 - not a valid command") return Response(status=200, response=currenthtmlerror, mimetype='text/html') def __init__(self, settings): @@ -224,8 +156,8 @@ class HDHR_HTTP_Server(): self.http.stop() -def interface_start(settings, origserv, epghandling): +def interface_start(settings, origin): print("Starting fHDHR Web Interface") - hdhr.hubprep(settings, origserv, epghandling) + fhdhrhub.setup(settings, origin) fakhdhrserver = HDHR_HTTP_Server(settings) fakhdhrserver.run() diff --git a/fHDHR/api/hub/__init__.py b/fHDHR/api/hub/__init__.py new file mode 100644 index 0000000..5f3a2bb --- /dev/null +++ b/fHDHR/api/hub/__init__.py @@ -0,0 +1,60 @@ +from . import device, pages, files + + +class fHDHR_Hub(): + + def __init__(self): + pass + + def setup(self, settings, origin): + self.config = settings + + self.origin = origin + + self.pages = pages.fHDHR_Pages(settings) + + self.device = device.fHDHR_Device(settings, origin) + + self.files = files.fHDHR_Files(settings, self.device) + + def get_xmltv(self, base_url): + return self.files.xmltv.get_xmltv_xml(base_url) + + def get_device_xml(self, base_url): + return self.files.devicexml.get_device_xml(base_url) + + def get_discover_json(self, base_url): + return self.files.discoverjson.get_discover_json(base_url) + + def get_lineup_status_json(self): + return self.files.lineupstatusjson.get_lineup_status_json() + + def get_lineup_xml(self, base_url): + return self.files.lineupxml.get_lineup_xml(base_url) + + def get_lineup_json(self, base_url): + return self.files.lineupjson.get_lineup_json(base_url) + + def get_debug_json(self, base_url): + return self.files.debug.get_debug_json(base_url) + + def get_html_error(self, message): + return self.pages.htmlerror.get_html_error(message) + + def post_lineup_scan_start(self): + self.device.station_scan.scan() + + def get_image(self, request_args): + return self.device.images.get_image(request_args) + + def get_channels_m3u(self, base_url): + return self.files.m3u.get_channels_m3u(base_url) + + def get_stream_info(self, stream_args): + return self.device.watch.get_stream_info(stream_args) + + def get_stream(self, stream_args): + return self.device.watch.get_stream(stream_args) + + def get_index_html(self, base_url): + return self.pages.index.get_index_html(base_url) diff --git a/fHDHR/api/hub/device/__init__.py b/fHDHR/api/hub/device/__init__.py new file mode 100644 index 0000000..7b337f3 --- /dev/null +++ b/fHDHR/api/hub/device/__init__.py @@ -0,0 +1,23 @@ +from . import channels, epg +from .tuners import Tuners +from .watch import WatchStream +from .images import imageHandler +from .station_scan import Station_Scan + + +class fHDHR_Device(): + + def __init__(self, settings, origin): + self.config = settings + + self.channels = channels.Channels(settings, origin) + + self.epg = epg.EPG(settings, self.channels) + + self.tuners = Tuners(settings) + + self.watch = WatchStream(settings, self.channels, self.tuners) + + self.images = imageHandler(settings, self.epg) + + self.station_scan = Station_Scan(settings, self.channels) diff --git a/fHDHR/api/hub/device/channels.py b/fHDHR/api/hub/device/channels.py new file mode 100644 index 0000000..e710e66 --- /dev/null +++ b/fHDHR/api/hub/device/channels.py @@ -0,0 +1,159 @@ +import os +import datetime +import json +from collections import OrderedDict + +from fHDHR.tools import hours_between_datetime + + +class ChannelNumbers(): + + def __init__(self, settings): + self.config = settings + + self.channel_numbers_file = self.config.dict["main"]["channel_numbers"] + + self.cnumbers = {} + self.load_channel_list() + + def get_number(self, channel_name): + if channel_name in list(self.cnumbers.keys()): + return self.cnumbers[channel_name] + + used_numbers = [] + for channel_name in list(self.cnumbers.keys()): + used_numbers.append(self.cnumbers[channel_name]) + + for i in range(1, 1000): + if str(float(i)) not in used_numbers: + break + return str(float(i)) + + def set_number(self, channel_name, channel_number): + self.cnumbers[channel_name] = str(float(channel_number)) + + def load_channel_list(self): + if os.path.isfile(self.channel_numbers_file): + print("Loading Previously Saved Channel Numbers.") + with open(self.channel_numbers_file, 'r') as cnumbersfile: + self.cnumbers = json.load(cnumbersfile) + + def save_channel_list(self): + print("Saving Channel Numbers.") + with open(self.channel_numbers_file, 'w') as cnumbersfile: + cnumbersfile.write(json.dumps(self.cnumbers, indent=4)) + + +class Channels(): + + def __init__(self, settings, origin): + self.config = settings + self.origin = origin + + self.channel_numbers = ChannelNumbers(settings) + + self.list = {} + self.list_update_time = None + self.get_channels() + + def get_channels(self, forceupdate=False): + """Pull Channels from origin. + + Output a list. + + Don't pull more often than 12 hours. + """ + + updatelist = False + if not self.list_update_time: + updatelist = True + elif hours_between_datetime(self.list_update_time, datetime.datetime.now()) > 12: + updatelist = True + elif forceupdate: + updatelist = True + + if updatelist: + channel_dict_list = self.origin.get_channels() + channel_dict_list = self.verify_channel_info(channel_dict_list) + self.append_channel_info(channel_dict_list) + if not self.list_update_time: + print("Found " + str(len(self.list)) + " channels for " + str(self.config.dict["main"]["servicename"])) + self.list_update_time = datetime.datetime.now() + self.channel_numbers.save_channel_list() + + channel_list = [] + for chandict in list(self.list.keys()): + channel_list.append(self.list[chandict]) + return channel_list + + def get_station_list(self, base_url): + station_list = [] + + for c in self.get_channels(): + station_list.append({ + 'GuideNumber': c['number'], + 'GuideName': c['name'], + 'URL': self.get_fhdhr_stream_url(base_url, c['number']), + }) + return station_list + + def get_channel_stream(self, channel_number): + if channel_number not in list(self.list.keys()): + self.get_channels() + if channel_number not in list(self.list.keys()): + return None + if "stream_url" not in list(self.list[channel_number].keys()): + chandict = self.get_channel_dict("number", channel_number) + streamlist, caching = self.origin.get_channel_stream(chandict, self.list) + if caching: + self.append_channel_info(streamlist) + return self.list[channel_number]["stream_url"] + else: + chanstreamdict = next(item for item in streamlist if item["number"] == channel_number) + return chanstreamdict["stream_url"] + return self.list[channel_number]["stream_url"] + + def get_station_total(self): + return len(list(self.list.keys())) + + def get_channel_dict(self, keyfind, valfind): + chanlist = self.get_channels() + return next(item for item in chanlist if item[keyfind] == valfind) + + def get_fhdhr_stream_url(self, base_url, channel_number): + return ('%s%s/auto/v%s' % + ("http://", + base_url, + channel_number)) + + def verify_channel_info(self, channel_dict_list): + """Some Channel Information is Critical""" + cleaned_channel_dict_list = [] + for station_item in channel_dict_list: + if "callsign" not in list(station_item.keys()): + station_item["callsign"] = station_item["name"] + if "id" not in list(station_item.keys()): + station_item["id"] = station_item["name"] + if "number" not in list(station_item.keys()): + station_item["number"] = self.channel_numbers.get_number(station_item["name"]) + else: + station_item["number"] = str(float(station_item["number"])) + self.channel_numbers.set_number(station_item["name"], station_item["number"]) + cleaned_channel_dict_list.append(station_item) + return cleaned_channel_dict_list + + def append_channel_info(self, channel_dict_list): + """Update the list dict + + Take the channel dict list given. + """ + for chan in channel_dict_list: + if chan["number"] not in list(self.list.keys()): + self.list[chan["number"]] = {} + for chankey in list(chan.keys()): + self.list[chan["number"]][chankey] = chan[chankey] + self.channel_order() + + def channel_order(self): + """Verify the Channel Order""" + self.list = OrderedDict(sorted(self.list.items())) diff --git a/fHDHR/epghandler/epgtypes/__init__.py b/fHDHR/api/hub/device/epg.py similarity index 65% rename from fHDHR/epghandler/epgtypes/__init__.py rename to fHDHR/api/hub/device/epg.py index 84448a1..f22b1fb 100644 --- a/fHDHR/epghandler/epgtypes/__init__.py +++ b/fHDHR/api/hub/device/epg.py @@ -1,27 +1,36 @@ import os import json +import time from collections import OrderedDict +from multiprocessing import Process -from . import blocks, zap2it +from fHDHR.origin import origin_epg +from .epgtypes import blocks, zap2it -class EPGTypes(): +class EPG(): - def __init__(self, settings, origserv): + def __init__(self, settings, channels): self.config = settings - self.origin = origserv + self.channels = channels - self.blocks = blocks.BlocksEPG(settings, origserv) - self.zap2it = zap2it.ZapEPG(settings, origserv) + self.origin = origin_epg.originEPG(settings, channels) + + self.epg_method_selfadd() self.epg_method = self.config.dict["fhdhr"]["epg_method"] if self.epg_method: + self.sleeptime = self.config.dict[self.epg_method]["epg_update_frequency"] + self.epg_cache_file = self.config.dict["filedir"]["epg_cache"][self.epg_method]["epg_json"] self.epgtypename = self.epg_method - if self.epg_method == self.config.dict["main"]["dictpopname"] or self.epg_method == "origin": + if self.epg_method in [self.config.dict["main"]["dictpopname"], "origin"]: self.epgtypename = self.config.dict["main"]["dictpopname"] + self.epgscan = Process(target=self.epgServerProcess) + self.epgscan.start() + def get_epg(self): epgdict = None if os.path.isfile(self.epg_cache_file): @@ -52,6 +61,11 @@ class EPGTypes(): event_list.extend(epgdict[channel]["listing"]) return next(item for item in event_list if item["id"] == event_id) + def epg_method_selfadd(self): + for method in self.config.dict["main"]["valid_epg_methods"]: + if method not in [None, "None", "origin", self.config.dict["main"]["dictpopname"]]: + exec("%s = %s" % ("self." + str(method), str(method) + "." + str(method) + "EPG(self.config, self.channels)")) + def update(self): print("Updating " + self.epgtypename + " EPG cache file.") @@ -66,3 +80,13 @@ class EPGTypes(): with open(self.epg_cache_file, 'w') as epgfile: epgfile.write(json.dumps(programguide, indent=4)) print("Wrote " + self.epgtypename + " EPG cache file.") + + def epgServerProcess(self): + print("Starting EPG thread...") + + try: + while True: + self.update() + time.sleep(self.sleeptime) + except KeyboardInterrupt: + pass diff --git a/fHDHR/api/hub/device/epgtypes/__init__.py b/fHDHR/api/hub/device/epgtypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fHDHR/epghandler/epgtypes/blocks.py b/fHDHR/api/hub/device/epgtypes/blocks.py similarity index 92% rename from fHDHR/epghandler/epgtypes/blocks.py rename to fHDHR/api/hub/device/epgtypes/blocks.py index 1d3498d..03bdeab 100644 --- a/fHDHR/epghandler/epgtypes/blocks.py +++ b/fHDHR/api/hub/device/epgtypes/blocks.py @@ -1,11 +1,11 @@ import datetime -class BlocksEPG(): +class blocksEPG(): - def __init__(self, settings, origserv): + def __init__(self, settings, channels): self.config = settings - self.origserv = origserv + self.channels = channels def update_epg(self): programguide = {} @@ -28,7 +28,7 @@ class BlocksEPG(): } timestamps.append(timestampdict) - for c in self.origserv.get_channels(): + for c in self.channels.get_channels(): if str(c["number"]) not in list(programguide.keys()): programguide[str(c["number"])] = { "callsign": c["callsign"], @@ -44,7 +44,7 @@ class BlocksEPG(): "time_start": timestamp['time_start'], "time_end": timestamp['time_end'], "duration_minutes": 60, - "thumbnail": ("/images?source=generate&message=%s" % (str(c["id"]) + "_" + str(timestamp['time_start']))), + "thumbnail": ("/images?source=generate&message=%s" % (str(c["id"]) + "_" + str(timestamp['time_start']).split(" ")[0])), "title": "Unavailable", "sub-title": "Unavailable", "description": "Unavailable", @@ -55,7 +55,7 @@ class BlocksEPG(): "seasonnumber": None, "episodenumber": None, "isnew": False, - "id": str(c["id"]) + "_" + str(timestamp['time_start']), + "id": str(c["id"]) + "_" + str(timestamp['time_start']).split(" ")[0], } programguide[str(c["number"])]["listing"].append(clean_prog_dict) diff --git a/fHDHR/epghandler/epgtypes/zap2it.py b/fHDHR/api/hub/device/epgtypes/zap2it.py similarity index 98% rename from fHDHR/epghandler/epgtypes/zap2it.py rename to fHDHR/api/hub/device/epgtypes/zap2it.py index 2fd667f..e90286e 100644 --- a/fHDHR/epghandler/epgtypes/zap2it.py +++ b/fHDHR/api/hub/device/epgtypes/zap2it.py @@ -4,14 +4,14 @@ import datetime import urllib.parse from fHDHR.tools import xmldictmaker, WebReq -from fHDHR.fHDHRerrors import EPGSetupError +from fHDHR.exceptions import EPGSetupError -class ZapEPG(): +class zap2itEPG(): - def __init__(self, settings, origserv): + def __init__(self, settings, channels): self.config = settings - self.origserv = origserv + self.channels = channels self.web = WebReq() self.postalcode = self.config.dict["zap2it"]["postalcode"] diff --git a/fHDHR/fHDHRweb/fHDHRdevice/images.py b/fHDHR/api/hub/device/images.py similarity index 92% rename from fHDHR/fHDHRweb/fHDHRdevice/images.py rename to fHDHR/api/hub/device/images.py index b2eb8de..9089114 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/images.py +++ b/fHDHR/api/hub/device/images.py @@ -7,9 +7,9 @@ import PIL.ImageFont class imageHandler(): - def __init__(self, settings, epghandling): + def __init__(self, settings, epg): self.config = settings - self.epghandling = epghandling + self.epg = epg def get_image(self, request_args): @@ -28,7 +28,7 @@ class imageHandler(): return image, imagetype def get_epg_image(self, request_args): - imageUri = self.epghandling.get_thumbnail(request_args["type"], str(request_args["id"])) + imageUri = self.epg.get_thumbnail(request_args["type"], str(request_args["id"])) if not imageUri: return self.generate_image(request_args["type"], str(request_args["id"])) diff --git a/fHDHR/fHDHRweb/fHDHRdevice/station_scan.py b/fHDHR/api/hub/device/station_scan.py similarity index 82% rename from fHDHR/fHDHRweb/fHDHRdevice/station_scan.py rename to fHDHR/api/hub/device/station_scan.py index 2f9f97b..b2bb8d3 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/station_scan.py +++ b/fHDHR/api/hub/device/station_scan.py @@ -3,9 +3,9 @@ from multiprocessing import Process class Station_Scan(): - def __init__(self, settings, origserv): + def __init__(self, settings, channels): self.config = settings - self.origserv = origserv + self.channels = channels self.chanscan = Process(target=self.runscan) def scan(self): @@ -16,7 +16,7 @@ class Station_Scan(): print("Channel Scan Already In Progress!") def runscan(self): - self.origserv.get_channels(forceupdate=True) + self.channels.get_channels(forceupdate=True) print("Requested Channel Scan Complete.") def scanning(self): diff --git a/fHDHR/fHDHRweb/fHDHRdevice/tuners.py b/fHDHR/api/hub/device/tuners.py similarity index 86% rename from fHDHR/fHDHRweb/fHDHRdevice/tuners.py rename to fHDHR/api/hub/device/tuners.py index e05aa70..503ce11 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/tuners.py +++ b/fHDHR/api/hub/device/tuners.py @@ -1,13 +1,13 @@ import threading -from fHDHR.fHDHRerrors import TunerError +from fHDHR.exceptions import TunerError class Tuner(): def __init__(self, inum): self.number = inum self.tuner_lock = threading.Lock() - self.status = {} + self.set_off_status() def grab(self, stream_args): if self.tuner_lock.locked(): @@ -24,14 +24,20 @@ class Tuner(): def close(self): print("Tuner #" + str(self.number) + " Shutting Down.") - self.status = {} + self.set_off_status() self.tuner_lock.release() def get_status(self): - if not self.tuner_lock.locked(): - return {"status": "Inactive"} return self.status + def set_off_status(self): + self.status = { + "status": "Inactive", + "method": None, + "accessed": None, + "proxied_url": None, + } + class Tuners(): diff --git a/fHDHR/fHDHRweb/fHDHRdevice/watch.py b/fHDHR/api/hub/device/watch.py similarity index 98% rename from fHDHR/fHDHRweb/fHDHRdevice/watch.py rename to fHDHR/api/hub/device/watch.py index 786b805..8c9bd21 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/watch.py +++ b/fHDHR/api/hub/device/watch.py @@ -1,7 +1,7 @@ import subprocess import time -from fHDHR.fHDHRerrors import TunerError +from fHDHR.exceptions import TunerError import fHDHR.tools diff --git a/fHDHR/api/hub/files/__init__.py b/fHDHR/api/hub/files/__init__.py new file mode 100644 index 0000000..e01550c --- /dev/null +++ b/fHDHR/api/hub/files/__init__.py @@ -0,0 +1,28 @@ +# pylama:ignore=W0611 +from .discover_json import Discover_JSON +from .device_xml import Device_XML +from .lineup_xml import Lineup_XML +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 + + +class fHDHR_Files(): + + def __init__(self, settings, device): + self.config = settings + self.device = device + + self.discoverjson = Discover_JSON(settings) + self.devicexml = Device_XML(settings) + + self.lineupxml = Lineup_XML(settings, device) + self.lineupjson = Lineup_JSON(settings, device) + self.lineupstatusjson = Lineup_Status_JSON(settings, device) + + self.xmltv = xmlTV_XML(settings, device) + self.m3u = channels_M3U(settings, device) + + self.debug = Debug_JSON(settings, device) diff --git a/fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py b/fHDHR/api/hub/files/channels_m3u.py similarity index 93% rename from fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py rename to fHDHR/api/hub/files/channels_m3u.py index 791839a..302c6ef 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/channels_m3u.py +++ b/fHDHR/api/hub/files/channels_m3u.py @@ -3,9 +3,9 @@ from io import StringIO class channels_M3U(): - def __init__(self, settings, origserv): + def __init__(self, settings, device): self.config = settings - self.origserv = origserv + self.device = device def get_channels_m3u(self, base_url): @@ -25,7 +25,7 @@ class channels_M3U(): "x-tvg-url=\"" + xmltvurl + "\"") ) - for channel in self.origserv.get_channels(): + for channel in self.device.channels.get_channels(): logourl = ('%s%s/images?source=epg&type=channel&id=%s' % ("http://", diff --git a/fHDHR/api/hub/files/debug_json.py b/fHDHR/api/hub/files/debug_json.py new file mode 100644 index 0000000..ad3f23d --- /dev/null +++ b/fHDHR/api/hub/files/debug_json.py @@ -0,0 +1,16 @@ +import json + + +class Debug_JSON(): + + def __init__(self, settings, device): + self.config = settings + self.device = device + + def get_debug_json(self, base_url): + debugjson = { + "base_url": base_url, + "total channels": self.device.channels.get_station_total(), + "tuner status": self.device.tuners.status(), + } + return json.dumps(debugjson, indent=4) diff --git a/fHDHR/fHDHRweb/fHDHRdevice/device_xml.py b/fHDHR/api/hub/files/device_xml.py similarity index 100% rename from fHDHR/fHDHRweb/fHDHRdevice/device_xml.py rename to fHDHR/api/hub/files/device_xml.py diff --git a/fHDHR/fHDHRweb/fHDHRdevice/discover_json.py b/fHDHR/api/hub/files/discover_json.py similarity index 100% rename from fHDHR/fHDHRweb/fHDHRdevice/discover_json.py rename to fHDHR/api/hub/files/discover_json.py diff --git a/fHDHR/fHDHRweb/fHDHRdevice/lineup_json.py b/fHDHR/api/hub/files/lineup_json.py similarity index 67% rename from fHDHR/fHDHRweb/fHDHRdevice/lineup_json.py rename to fHDHR/api/hub/files/lineup_json.py index d1086e2..a26ddd3 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/lineup_json.py +++ b/fHDHR/api/hub/files/lineup_json.py @@ -4,13 +4,13 @@ import json class Lineup_JSON(): lineup_json = None - def __init__(self, settings, origserv): + def __init__(self, settings, device): self.config = settings - self.origserv = origserv + self.device = device def get_lineup_json(self, base_url, force_update=False): if not self.lineup_json or force_update: - jsonlineup = self.origserv.get_station_list(base_url) + jsonlineup = self.device.channels.get_station_list(base_url) self.lineup_json = json.dumps(jsonlineup, indent=4) return self.lineup_json diff --git a/fHDHR/fHDHRweb/fHDHRdevice/lineup_status_json.py b/fHDHR/api/hub/files/lineup_status_json.py similarity index 75% rename from fHDHR/fHDHRweb/fHDHRdevice/lineup_status_json.py rename to fHDHR/api/hub/files/lineup_status_json.py index 8e49db5..c732051 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/lineup_status_json.py +++ b/fHDHR/api/hub/files/lineup_status_json.py @@ -3,21 +3,22 @@ import json class Lineup_Status_JSON(): - def __init__(self, settings, origserv): + def __init__(self, settings, device): self.config = settings - self.origserv = origserv + self.device = device - def get_lineup_json(self, station_scanning): + def get_lineup_status_json(self): + station_scanning = self.device.station_scan.scanning() if station_scanning: jsonlineup = self.scan_in_progress() - elif not self.origserv.get_station_total(): + elif not self.device.channels.get_station_total(): jsonlineup = self.scan_in_progress() else: jsonlineup = self.not_scanning() return json.dumps(jsonlineup, indent=4) def scan_in_progress(self): - channel_count = self.origserv.get_station_total() + channel_count = self.device.channels.get_station_total() jsonlineup = { "ScanInProgress": "true", "Progress": 99, diff --git a/fHDHR/fHDHRweb/fHDHRdevice/lineup_xml.py b/fHDHR/api/hub/files/lineup_xml.py similarity index 86% rename from fHDHR/fHDHRweb/fHDHRdevice/lineup_xml.py rename to fHDHR/api/hub/files/lineup_xml.py index 10359da..bb526af 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/lineup_xml.py +++ b/fHDHR/api/hub/files/lineup_xml.py @@ -7,14 +7,14 @@ from fHDHR.tools import sub_el class Lineup_XML(): device_xml = None - def __init__(self, settings, origserv): + def __init__(self, settings, device): self.config = settings - self.origserv = origserv + self.device = device def get_lineup_xml(self, base_url, force_update=False): if not self.device_xml or force_update: out = xml.etree.ElementTree.Element('Lineup') - station_list = self.origserv.get_station_list(base_url) + station_list = self.device.channels.get_station_list(base_url) for station_item in station_list: program_out = sub_el(out, 'Program') sub_el(program_out, 'GuideNumber', station_item['GuideNumber']) diff --git a/fHDHR/fHDHRweb/fHDHRdevice/xmltv_xml.py b/fHDHR/api/hub/files/xmltv_xml.py similarity index 97% rename from fHDHR/fHDHRweb/fHDHRdevice/xmltv_xml.py rename to fHDHR/api/hub/files/xmltv_xml.py index c6206fb..a0572ee 100644 --- a/fHDHR/fHDHRweb/fHDHRdevice/xmltv_xml.py +++ b/fHDHR/api/hub/files/xmltv_xml.py @@ -8,13 +8,13 @@ class xmlTV_XML(): """Methods to create xmltv.xml""" xmltv_xml = None - def __init__(self, settings, epghandling): + def __init__(self, settings, device): self.config = settings - self.epghandling = epghandling + self.device = device def get_xmltv_xml(self, base_url, force_update=False): - epgdict = self.epghandling.epgtypes.get_epg() + epgdict = self.device.epg.get_epg() return self.create_xmltv(base_url, epgdict) def xmltv_headers(self): diff --git a/fHDHR/api/hub/pages/__init__.py b/fHDHR/api/hub/pages/__init__.py new file mode 100644 index 0000000..4a2eda1 --- /dev/null +++ b/fHDHR/api/hub/pages/__init__.py @@ -0,0 +1,11 @@ +# pylama:ignore=W0611 +from .index_html import Index_HTML +from .htmlerror import HTMLerror + + +class fHDHR_Pages(): + + def __init__(self, settings): + self.config = settings + self.index = Index_HTML(settings) + self.htmlerror = HTMLerror(settings) diff --git a/fHDHR/fHDHRweb/fHDHRdevice/htmlerror.py b/fHDHR/api/hub/pages/htmlerror.py similarity index 100% rename from fHDHR/fHDHRweb/fHDHRdevice/htmlerror.py rename to fHDHR/api/hub/pages/htmlerror.py diff --git a/fHDHR/fHDHRweb/fHDHRpages/index_html.py b/fHDHR/api/hub/pages/index_html.py similarity index 61% rename from fHDHR/fHDHRweb/fHDHRpages/index_html.py rename to fHDHR/api/hub/pages/index_html.py index eb26fac..f447047 100644 --- a/fHDHR/fHDHRweb/fHDHRpages/index_html.py +++ b/fHDHR/api/hub/pages/index_html.py @@ -23,12 +23,17 @@ class Index_HTML(): fakefile.write("\n") fakefile.write("

%s

\n" % friendlyname) + fakefile.write("\n") + + fakefile.write("

Primary fHDHR Links

") + fakefile.write("\n") # a list of 2 part lists containing button information button_list = [ ["xmltv", "xmltv.xml"], ["m3u", "channels.m3u"], - ["debug", "debug.json"] + ["debug", "debug.json"], + ["Force Channel Update", "chanscan"] ] for button_item in button_list: @@ -37,6 +42,25 @@ class Index_HTML(): fakefile.write("
\n") fakefile.write("

\n" % (button_path, button_label)) fakefile.write("
\n") + fakefile.write("\n") + + fakefile.write("

Other fHDHR Links

") + + # a list of 2 part lists containing button information + button_list = [ + ["device.xml", "device.xml"], + ["discover.json", "discover.json"], + ["lineup.json", "lineup.json"], + ["lineup_status.json", "lineup_status.json"] + ] + + for button_item in button_list: + button_label = button_item[0] + button_path = button_item[1] + fakefile.write("
\n") + fakefile.write("

\n" % (button_path, button_label)) + fakefile.write("
\n") + fakefile.write("\n") fakefile.write("\n") fakefile.write("\n") diff --git a/fHDHR/cli/run.py b/fHDHR/cli/run.py index eabcd78..2c89856 100644 --- a/fHDHR/cli/run.py +++ b/fHDHR/cli/run.py @@ -4,7 +4,13 @@ import time import argparse from multiprocessing import Process -from fHDHR import fHDHR_VERSION, config, originservice, ssdpserver, epghandler, fHDHRerrors, fHDHRweb +from fHDHR import fHDHR_VERSION +import fHDHR.exceptions +import fHDHR.config + +import fHDHR.origin +import fHDHR.api +import fHDHR.ssdpserver ERR_CODE = 1 ERR_CODE_NO_RESTART = 2 @@ -25,25 +31,17 @@ def build_args_parser(): def get_configuration(args, script_dir): if not os.path.isfile(args.cfg): - raise config.ConfigurationNotFound(filename=args.cfg) - return config.Config(args.cfg, script_dir) + raise fHDHR.exceptions.ConfigurationNotFound(filename=args.cfg) + return fHDHR.config.Config(args.cfg, script_dir) -def get_originservice(settings): - return originservice.OriginService(settings) - - -def run(settings, origserv, epghandling): +def run(settings, origin): if settings.dict["fhdhr"]["discovery_address"]: - ssdpServer = Process(target=ssdpserver.ssdpServerProcess, args=(settings,)) + ssdpServer = Process(target=fHDHR.ssdpserver.ssdpServerProcess, args=(settings,)) ssdpServer.start() - if settings.dict["fhdhr"]["epg_method"]: - epgServer = Process(target=epghandler.epgServerProcess, args=(settings, epghandling)) - epgServer.start() - - fhdhrweb = Process(target=fHDHRweb.interface_start, args=(settings, origserv, epghandling)) + fhdhrweb = Process(target=fHDHR.api.interface_start, args=(settings, origin)) fhdhrweb.start() print(settings.dict["fhdhr"]["friendlyname"] + " is now running!") @@ -60,23 +58,17 @@ def start(args, script_dir): try: settings = get_configuration(args, script_dir) - except fHDHRerrors.ConfigurationError as e: + except fHDHR.exceptions.ConfigurationError as e: print(e) return ERR_CODE_NO_RESTART try: - origserv = get_originservice(settings) - except fHDHRerrors.LoginError as e: + origin = fHDHR.origin.origin_channels.OriginService(settings) + except fHDHR.exceptions.OriginSetupError as e: print(e) return ERR_CODE_NO_RESTART - try: - epghandling = epghandler.EPGhandler(settings, origserv) - except fHDHRerrors.EPGSetupError as e: - print(e) - return ERR_CODE_NO_RESTART - - return run(settings, origserv, epghandling) + return run(settings, origin) def main(script_dir): diff --git a/fHDHR/config/__init__.py b/fHDHR/config/__init__.py index 65d6589..0f1acb6 100644 --- a/fHDHR/config/__init__.py +++ b/fHDHR/config/__init__.py @@ -3,7 +3,7 @@ import random import configparser import pathlib -from fHDHR import fHDHRerrors +import fHDHR.exceptions from fHDHR.tools import isint, isfloat, is_arithmetic @@ -100,7 +100,7 @@ class Config(): if not self.dict[req_section][req_key]: required_missing.append(req_item) if len(required_missing): - raise fHDHRerrors.ConfigurationError("Required configuration options missing: " + ", ".join(required_missing)) + raise fHDHR.exceptions.ConfigurationError("Required configuration options missing: " + ", ".join(required_missing)) self.dict["origin"] = self.dict.pop(self.dict["main"]["dictpopname"]) @@ -111,7 +111,7 @@ class Config(): if self.dict["fhdhr"]["epg_method"] == self.dict["main"]["dictpopname"]: self.dict["fhdhr"]["epg_method"] = "origin" elif self.dict["fhdhr"]["epg_method"] not in self.dict["main"]["valid_epg_methods"]: - raise fHDHRerrors.ConfigurationError("Invalid EPG Method. Exiting...") + raise fHDHR.exceptions.ConfigurationError("Invalid EPG Method. Exiting...") else: print("EPG Method not set, will not create EPG/xmltv") @@ -129,11 +129,13 @@ class Config(): if self.dict["main"]["cache_dir"]: print("Verifying cache directory...") if not pathlib.Path(self.dict["main"]["cache_dir"]).is_dir(): - raise fHDHRerrors.ConfigurationError("Invalid Cache Directory. Exiting...") + raise fHDHR.exceptions.ConfigurationError("Invalid Cache Directory. Exiting...") self.dict["filedir"]["cache_dir"] = pathlib.Path(self.dict["main"]["cache_dir"]) print("Cache set to " + str(self.dict["filedir"]["cache_dir"])) cache_dir = self.dict["filedir"]["cache_dir"] + self.dict["main"]["channel_numbers"] = pathlib.Path(cache_dir).joinpath("cnumbers.json") + for epg_method in self.dict["main"]["valid_epg_methods"]: if epg_method and epg_method != "None": epg_cache_dir = pathlib.Path(cache_dir).joinpath(epg_method) @@ -149,7 +151,7 @@ class Config(): self.dict["filedir"]["epg_cache"][epg_method]["epg_json"] = pathlib.Path(epg_cache_dir).joinpath('epg.json') if self.dict["fhdhr"]["stream_type"] not in ["direct", "ffmpeg"]: - raise fHDHRerrors.ConfigurationError("Invalid stream type. Exiting...") + raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...") if not self.dict["fhdhr"]["discovery_address"] and self.dict["fhdhr"]["address"] != "0.0.0.0": self.dict["fhdhr"]["discovery_address"] = self.dict["fhdhr"]["address"] diff --git a/fHDHR/epghandler/__init__.py b/fHDHR/epghandler/__init__.py deleted file mode 100644 index ecdc86a..0000000 --- a/fHDHR/epghandler/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import time - -from . import epgtypes - - -class EPGhandler(): - - def __init__(self, settings, origserv): - self.config = settings - - self.epg_method = self.config.dict["fhdhr"]["epg_method"] - if self.epg_method: - self.sleeptime = self.config.dict[self.epg_method]["epg_update_frequency"] - - self.epgtypes = epgtypes.EPGTypes(settings, origserv) - - def get_thumbnail(self, itemtype, itemid): - return self.epgtypes.get_thumbnail(itemtype, itemid) - - -def epgServerProcess(settings, epghandling): - print("Starting EPG thread...") - try: - - while True: - epghandling.epgtypes.update() - time.sleep(epghandling.sleeptime) - - except KeyboardInterrupt: - pass diff --git a/fHDHR/fHDHRerrors/__init__.py b/fHDHR/exceptions/__init__.py similarity index 90% rename from fHDHR/fHDHRerrors/__init__.py rename to fHDHR/exceptions/__init__.py index 9def67e..cc7a99d 100644 --- a/fHDHR/fHDHRerrors/__init__.py +++ b/fHDHR/exceptions/__init__.py @@ -7,12 +7,12 @@ class TunerError(Exception): return 'TunerError: %s' % self.value -class LoginError(Exception): +class OriginSetupError(Exception): def __init__(self, value): self.value = value def __str__(self): - return 'LoginError: %s' % self.value + return 'OriginSetupError: %s' % self.value class EPGSetupError(Exception): diff --git a/fHDHR/fHDHRweb/fHDHRdevice/__init__.py b/fHDHR/fHDHRweb/fHDHRdevice/__init__.py deleted file mode 100644 index 80d9f81..0000000 --- a/fHDHR/fHDHRweb/fHDHRdevice/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# pylama:ignore=W0611 -from .tuners import Tuners -from .images import imageHandler -from .watch import WatchStream -from .station_scan import Station_Scan - -from .discover_json import Discover_JSON -from .device_xml import Device_XML -from .lineup_xml import Lineup_XML -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/debug_json.py b/fHDHR/fHDHRweb/fHDHRdevice/debug_json.py deleted file mode 100644 index 09f39c7..0000000 --- a/fHDHR/fHDHRweb/fHDHRdevice/debug_json.py +++ /dev/null @@ -1,16 +0,0 @@ -import json - - -class Debug_JSON(): - - def __init__(self, settings, origserv, epghandling): - self.config = settings - self.origserv = origserv - - def get_debug_json(self, base_url, tuners): - debugjson = { - "base_url": base_url, - "total channels": self.origserv.get_station_total(), - "tuner status": tuners.status(), - } - return json.dumps(debugjson, indent=4) diff --git a/fHDHR/fHDHRweb/fHDHRpages/__init__.py b/fHDHR/fHDHRweb/fHDHRpages/__init__.py deleted file mode 100644 index bca1b19..0000000 --- a/fHDHR/fHDHRweb/fHDHRpages/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# pylama:ignore=W0611 -from .index_html import Index_HTML diff --git a/fHDHR/origin/__init__.py b/fHDHR/origin/__init__.py new file mode 100644 index 0000000..8cd2b30 --- /dev/null +++ b/fHDHR/origin/__init__.py @@ -0,0 +1,3 @@ +# pylama:ignore=W0611 +from . import origin_channels +from . import origin_epg diff --git a/fHDHR/origin/origin_channels.py b/fHDHR/origin/origin_channels.py new file mode 100644 index 0000000..df83f02 --- /dev/null +++ b/fHDHR/origin/origin_channels.py @@ -0,0 +1,107 @@ +import xmltodict +import json +import hashlib + +import fHDHR.tools +import fHDHR.exceptions + + +class OriginService(): + + def __init__(self, settings): + self.config = settings + + self.web = fHDHR.tools.WebReq() + self.login() + + def login(self): + print("Logging into NextPVR") + self.sid = self.get_sid() + if not self.sid: + raise fHDHR.exceptions.OriginSetupError("NextPVR Login Failed") + else: + print("NextPVR Login Success") + self.config.write(self.config.dict["main"]["dictpopname"], 'sid', self.sid) + + def get_sid(self): + if self.config.dict["origin"]["sid"]: + return self.config.dict["origin"]["sid"] + + initiate_url = ('%s%s:%s/service?method=session.initiate&ver=1.0&device=fhdhr' % + ("https://" if self.config.dict["origin"]["ssl"] else "http://", + self.config.dict["origin"]["address"], + str(self.config.dict["origin"]["port"]), + )) + + initiate_req = self.web.session.get(initiate_url) + initiate_dict = xmltodict.parse(initiate_req.content) + + sid = initiate_dict['rsp']['sid'] + salt = initiate_dict['rsp']['salt'] + md5PIN = hashlib.md5(str(self.config.dict["origin"]['pin']).encode('utf-8')).hexdigest() + string = ':%s:%s' % (md5PIN, salt) + clientKey = hashlib.md5(string.encode('utf-8')).hexdigest() + + login_url = ('%s%s:%s/service?method=session.login&sid=%s&md5=%s' % + ("https://" if self.config.dict["origin"]["ssl"] else "http://", + self.config.dict["origin"]["address"], + str(self.config.dict["origin"]["port"]), + sid, + clientKey + )) + login_req = self.web.session.get(login_url) + login_dict = xmltodict.parse(login_req.content) + + loginsuccess = None + if login_dict['rsp']['@stat'] == "ok": + if login_dict['rsp']['allow_watch'] == "true": + loginsuccess = sid + + return loginsuccess + + def get_channels(self): + + data_url = ('%s%s:%s/service?method=channel.list&sid=%s' % + ("https://" if self.config.dict["origin"]["ssl"] else "http://", + self.config.dict["origin"]["address"], + str(self.config.dict["origin"]["port"]), + self.sid + )) + + data_req = self.web.session.get(data_url) + data_dict = xmltodict.parse(data_req.content) + + if 'channels' not in list(data_dict['rsp'].keys()): + print("Could not retrieve channel list") + return [] + + channel_o_list = data_dict['rsp']['channels']['channel'] + + channel_list = [] + for c in channel_o_list: + dString = json.dumps(c) + channel_dict = eval(dString) + + clean_station_item = { + "name": channel_dict["name"], + "callsign": channel_dict["name"], + "number": channel_dict["formatted-number"], + "id": channel_dict["id"], + } + channel_list.append(clean_station_item) + return channel_list + + def get_channel_stream(self, chandict, allchandict): + caching = True + streamlist = [] + streamdict = {} + streamurl = ('%s%s:%s/live?channel=%s&client=%s' % + ("https://" if self.config.dict["origin"]["ssl"] else "http://", + self.config.dict["origin"]["address"], + str(self.config.dict["origin"]["port"]), + str(chandict["number"]), + str(chandict["number"]), + )) + streamdict = {"number": chandict["number"], "stream_url": streamurl} + streamlist.append(streamdict) + return streamlist, caching diff --git a/fHDHR/originservice/nextpvr.py b/fHDHR/origin/origin_epg.py similarity index 57% rename from fHDHR/originservice/nextpvr.py rename to fHDHR/origin/origin_epg.py index d11e6ca..1673873 100644 --- a/fHDHR/originservice/nextpvr.py +++ b/fHDHR/origin/origin_epg.py @@ -1,110 +1,17 @@ -import xmltodict -import json -import hashlib import datetime +import xmltodict import fHDHR.tools -class fHDHRservice(): - def __init__(self, settings): +class originEPG(): + + def __init__(self, settings, channels): self.config = settings + self.channels = channels self.web = fHDHR.tools.WebReq() - def login(self): - print("Logging into NextPVR") - self.sid = self.get_sid() - if not self.sid: - return False - else: - print("NextPVR Login Success") - self.config.write(self.config.dict["main"]["dictpopname"], 'sid', self.sid) - return True - - def get_sid(self): - if self.config.dict["origin"]["sid"]: - return self.config.dict["origin"]["sid"] - - initiate_url = ('%s%s:%s/service?method=session.initiate&ver=1.0&device=fhdhr' % - ("https://" if self.config.dict["origin"]["ssl"] else "http://", - self.config.dict["origin"]["address"], - str(self.config.dict["origin"]["port"]), - )) - - initiate_req = self.web.session.get(initiate_url) - initiate_dict = xmltodict.parse(initiate_req.content) - - sid = initiate_dict['rsp']['sid'] - salt = initiate_dict['rsp']['salt'] - md5PIN = hashlib.md5(str(self.config.dict["origin"]['pin']).encode('utf-8')).hexdigest() - string = ':%s:%s' % (md5PIN, salt) - clientKey = hashlib.md5(string.encode('utf-8')).hexdigest() - - login_url = ('%s%s:%s/service?method=session.login&sid=%s&md5=%s' % - ("https://" if self.config.dict["origin"]["ssl"] else "http://", - self.config.dict["origin"]["address"], - str(self.config.dict["origin"]["port"]), - sid, - clientKey - )) - login_req = self.web.session.get(login_url) - login_dict = xmltodict.parse(login_req.content) - - loginsuccess = None - if login_dict['rsp']['@stat'] == "ok": - if login_dict['rsp']['allow_watch'] == "true": - loginsuccess = sid - - return loginsuccess - - def get_channels(self): - - data_url = ('%s%s:%s/service?method=channel.list&sid=%s' % - ("https://" if self.config.dict["origin"]["ssl"] else "http://", - self.config.dict["origin"]["address"], - str(self.config.dict["origin"]["port"]), - self.sid - )) - - data_req = self.web.session.get(data_url) - data_dict = xmltodict.parse(data_req.content) - - if 'channels' not in list(data_dict['rsp'].keys()): - print("Could not retrieve channel list") - return [] - - channel_o_list = data_dict['rsp']['channels']['channel'] - - channel_list = [] - for c in channel_o_list: - dString = json.dumps(c) - channel_dict = eval(dString) - - clean_station_item = { - "name": channel_dict["name"], - "callsign": channel_dict["name"], - "number": channel_dict["formatted-number"], - "id": channel_dict["id"], - } - channel_list.append(clean_station_item) - return channel_list - - def get_channel_stream(self, chandict, allchandict): - caching = True - streamlist = [] - streamdict = {} - streamurl = ('%s%s:%s/live?channel=%s&client=%s' % - ("https://" if self.config.dict["origin"]["ssl"] else "http://", - self.config.dict["origin"]["address"], - str(self.config.dict["origin"]["port"]), - str(chandict["number"]), - str(chandict["number"]), - )) - streamdict = {"number": chandict["number"], "stream_url": streamurl} - streamlist.append(streamdict) - return streamlist, caching - def get_channel_thumbnail(self, channel_id): channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" % ("https://" if self.config.dict["origin"]["ssl"] else "http://", @@ -124,10 +31,18 @@ class fHDHRservice(): )) return item_thumb_url + def xmltimestamp_nextpvr(self, epochtime): + xmltime = datetime.datetime.fromtimestamp(int(epochtime)/1000) + xmltime = str(xmltime.strftime('%Y%m%d%H%M%S')) + " +0000" + return xmltime + + def duration_nextpvr_minutes(self, starttime, endtime): + return ((int(endtime) - int(starttime))/1000/60) + def update_epg(self): programguide = {} - for c in self.get_channels(): + for c in self.channels.get_channels(): cdict = fHDHR.tools.xmldictmaker(c, ["callsign", "name", "number", "id"]) @@ -188,11 +103,3 @@ class fHDHRservice(): programguide[str(cdict["number"])]["listing"].append(clean_prog_dict) return programguide - - def xmltimestamp_nextpvr(self, epochtime): - xmltime = datetime.datetime.fromtimestamp(int(epochtime)/1000) - xmltime = str(xmltime.strftime('%Y%m%d%H%M%S')) + " +0000" - return xmltime - - def duration_nextpvr_minutes(self, starttime, endtime): - return ((int(endtime) - int(starttime))/1000/60) diff --git a/fHDHR/originservice/__init__.py b/fHDHR/originservice/__init__.py deleted file mode 100644 index d9a4dfb..0000000 --- a/fHDHR/originservice/__init__.py +++ /dev/null @@ -1,95 +0,0 @@ -import datetime -from collections import OrderedDict - -from . import nextpvr as serviceorigin -from fHDHR.tools import hours_between_datetime -from fHDHR.fHDHRerrors import LoginError - - -class OriginService(): - - def __init__(self, settings): - self.config = settings - self.serviceorigin = serviceorigin.fHDHRservice(settings) - if not self.serviceorigin.login(): - raise LoginError(self.config.dict["main"]["servicename"] + " Login Failed.") - - self.channels = { - "list": {}, - "list_updated": None, - } - - def append_channel_info(self, chanlist): - for chan in chanlist: - if chan["number"] not in list(self.channels["list"].keys()): - self.channels["list"][chan["number"]] = {} - for chankey in list(chan.keys()): - self.channels["list"][chan["number"]][chankey] = chan[chankey] - self.channel_order() - - def channel_order(self): - self.channels["list"] = OrderedDict(sorted(self.channels["list"].items())) - - def get_channels(self, forceupdate=False): - - updatelist = False - if not self.channels["list_updated"]: - updatelist = True - elif hours_between_datetime(self.channels["list_updated"], datetime.datetime.now()) > 12: - updatelist = True - elif forceupdate: - updatelist = True - - if updatelist: - chanlist = self.serviceorigin.get_channels() - self.append_channel_info(chanlist) - self.channels["list_updated"] = datetime.datetime.now() - - channel_list = [] - for chandict in list(self.channels["list"].keys()): - channel_list.append(self.channels["list"][chandict]) - return channel_list - - def get_fhdhr_stream_url(self, base_url, channel): - return ('%s%s/auto/v%s' % - ("http://", - base_url, - channel['number'])) - - def get_station_list(self, base_url): - station_list = [] - - for c in self.get_channels(): - station_list.append({ - 'GuideNumber': c['number'], - 'GuideName': c['name'], - 'URL': self.get_fhdhr_stream_url(base_url, c), - }) - return station_list - - def get_channel_stream(self, channel_number): - if channel_number not in list(self.channels["list"].keys()): - self.get_channels() - if channel_number not in list(self.channels["list"].keys()): - return None - if "stream_url" not in list(self.channels["list"][channel_number].keys()): - chandict = self.get_channel_dict("number", channel_number) - streamlist, caching = self.serviceorigin.get_channel_stream(chandict, self.channels["list"]) - if caching: - self.append_channel_info(streamlist) - return self.channels["list"][channel_number]["stream_url"] - else: - chanstreamdict = next(item for item in streamlist if item["number"] == channel_number) - return chanstreamdict["stream_url"] - return self.channels["list"][channel_number]["stream_url"] - - def get_station_total(self): - chanlist = self.get_channels() - return len(chanlist) - - def get_channel_dict(self, keyfind, valfind): - chanlist = self.get_channels() - return next(item for item in chanlist if item[keyfind] == valfind) - - def update_epg(self): - return self.serviceorigin.update_epg() diff --git a/fHDHR/tools/__init__.py b/fHDHR/tools/__init__.py index 19a3459..a7b27ca 100644 --- a/fHDHR/tools/__init__.py +++ b/fHDHR/tools/__init__.py @@ -1,5 +1,3 @@ -import os -import sys import ast import requests import xml.etree.ElementTree @@ -8,12 +6,6 @@ UNARY_OPS = (ast.UAdd, ast.USub) BINARY_OPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod) -def clean_exit(): - sys.stderr.flush() - sys.stdout.flush() - os._exit(0) - - def sub_el(parent, name, text=None, **kwargs): el = xml.etree.ElementTree.SubElement(parent, name, **kwargs) if text: