diff --git a/data/www/templates/base.html b/data/www/templates/base.html index 1418272..5d812fc 100644 --- a/data/www/templates/base.html +++ b/data/www/templates/base.html @@ -18,6 +18,7 @@ + diff --git a/data/www/templates/channels.html b/data/www/templates/channels.html new file mode 100644 index 0000000..c01384a --- /dev/null +++ b/data/www/templates/channels.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% block content %} + +

What's On {{ fhdhr.config.dict["fhdhr"]["friendlyname"] }}

+ + + + + + + + + + + + {% for chan_dict in channelslist %} + + + + + + + + + {% endfor %} + +{% endblock %} diff --git a/data/www/templates/guide.html b/data/www/templates/guide.html index d63cd10..e61d514 100644 --- a/data/www/templates/guide.html +++ b/data/www/templates/guide.html @@ -4,9 +4,14 @@

What's On {{ fhdhr.config.dict["fhdhr"]["friendlyname"] }}

+

+ {% for epg_method in epg_methods %} + + {% endfor %} +

+
PlayChannel NameChannel CallSignChannel NumberStatusOptions
+ {% if chan_dict["enabled"] %} + Play + {% endif %} + {{ chan_dict["name"] }}{{ chan_dict["callsign"] }}{{ chan_dict["number"] }} + {% if chan_dict["enabled"] %} + Enabled + {% elif not chan_dict["enabled"] %} + Disabled + {% endif %} + +
+ {% if chan_dict["enabled"] %} + + {% elif not chan_dict["enabled"] %} + + {% endif %} +
+
- @@ -18,7 +23,6 @@ {% for chan_dict in chan_guide_list %} - - - + +
Play Channel Name Channel Number Channel Thumbnail
Play {{ chan_dict["name"] }} {{ chan_dict["number"] }} {{ chan_dict[ diff --git a/data/www/templates/xmltv.html b/data/www/templates/xmltv.html index 66a4835..697febc 100644 --- a/data/www/templates/xmltv.html +++ b/data/www/templates/xmltv.html @@ -21,8 +21,8 @@
{{ epg_method_name }}{{ epg_method_name }}{{ epg_method_name }}{{ epg_method_name }}{{ epg_method_name }}
diff --git a/fHDHR/device/channels.py b/fHDHR/device/channels.py deleted file mode 100644 index 5184b57..0000000 --- a/fHDHR/device/channels.py +++ /dev/null @@ -1,157 +0,0 @@ -import datetime -from collections import OrderedDict - -from fHDHR.tools import hours_between_datetime - - -class ChannelNumbers(): - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - def get_number(self, channel_id): - cnumbers = self.fhdhr.db.get_fhdhr_value("channel_numbers", "list") or {} - if channel_id in list(cnumbers.keys()): - return cnumbers[channel_id] - - used_numbers = [] - for channel_id in list(cnumbers.keys()): - used_numbers.append(cnumbers[channel_id]) - - for i in range(1, 1000): - if str(float(i)) not in used_numbers: - break - return str(float(i)) - - def set_number(self, channel_id, channel_number): - cnumbers = self.fhdhr.db.get_fhdhr_value("channel_numbers", "list") or {} - cnumbers[channel_id] = str(float(channel_number)) - self.fhdhr.db.set_fhdhr_value("channel_numbers", "list", cnumbers) - - -class Channels(): - - def __init__(self, fhdhr, origin): - self.fhdhr = fhdhr - - self.origin = origin - - self.channel_numbers = ChannelNumbers(fhdhr) - - self.list = {} - self.list_update_time = None - self.get_channels() - - def get_origin_status(self): - try: - return self.origin.get_status_dict() - except AttributeError: - return {} - - 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: - self.fhdhr.logger.info("Found " + str(len(self.list)) + " channels for " + str(self.fhdhr.config.dict["main"]["servicename"])) - self.list_update_time = datetime.datetime.now() - - 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'], - 'Tags': ",".join(c['tags']), - '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/auto/v%s' % - (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 "tags" not in list(station_item.keys()): - station_item["tags"] = [] - - if "number" not in list(station_item.keys()): - station_item["number"] = self.channel_numbers.get_number(station_item["id"]) - else: - station_item["number"] = str(float(station_item["number"])) - self.channel_numbers.set_number(station_item["id"], 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/device/channels/__init__.py b/fHDHR/device/channels/__init__.py new file mode 100644 index 0000000..a848137 --- /dev/null +++ b/fHDHR/device/channels/__init__.py @@ -0,0 +1,76 @@ +import datetime + +from fHDHR.tools import hours_between_datetime + +from .channel import Channel +from .chan_ident import Channel_IDs + + +class Channels(): + + def __init__(self, fhdhr, origin): + self.fhdhr = fhdhr + + self.origin = origin + + self.id_system = Channel_IDs(fhdhr) + + self.list = {} + self.list_update_time = None + self.get_db_channels() + self.get_channels() + + def get_channel_obj(self, keyfind, valfind): + return next(self.list[fhdhr_id] for fhdhr_id in list(self.list.keys()) if self.list[fhdhr_id].dict[keyfind] == valfind) + + def get_channel_list(self, keyfind): + return [self.list[x].dict[keyfind] for x in list(self.list.keys())] + + def set_channel_status(self, keyfind, valfind, enablement): + self.get_channel_obj(keyfind, valfind).set_status(enablement) + + def get_db_channels(self): + channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "IDs") or [] + for channel_id in channel_ids: + channel_obj = Channel(self.fhdhr, self.id_system, channel_id=channel_id) + channel_id = channel_obj.dict["fhdhr_id"] + self.list[channel_id] = channel_obj + + 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() + for channel_info in channel_dict_list: + channel_obj = Channel(self.fhdhr, self.id_system, origin_id=channel_info["id"]) + channel_id = channel_obj.dict["fhdhr_id"] + channel_obj.basics(channel_info) + self.list[channel_id] = channel_obj + + if not self.list_update_time: + self.fhdhr.logger.info("Found " + str(len(self.list)) + " channels for " + str(self.fhdhr.config.dict["main"]["servicename"])) + self.list_update_time = datetime.datetime.now() + + channel_list = [] + for chan_obj in list(self.list.keys()): + channel_list.append(self.list[chan_obj].dict) + return channel_list + + def get_channel_stream(self, channel_number): + return self.origin.get_channel_stream(self.get_channel_dict("number", channel_number)) + + def get_channel_dict(self, keyfind, valfind): + return self.get_channel_obj(keyfind, valfind).dict diff --git a/fHDHR/device/channels/chan_ident.py b/fHDHR/device/channels/chan_ident.py new file mode 100644 index 0000000..d3729ae --- /dev/null +++ b/fHDHR/device/channels/chan_ident.py @@ -0,0 +1,38 @@ +import uuid + + +class Channel_IDs(): + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def get(self, origin_id): + existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "IDs") or [] + existing_channel_info = [self.fhdhr.db.get_channel_value(channel_id, "info") or {} for channel_id in existing_ids] + for existing_channel in existing_channel_info: + if existing_channel["origin_id"] == origin_id: + return existing_channel["fhdhr_id"] + return self.assign() + + def assign(self): + existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "IDs") or [] + channel_id = None + while not channel_id: + unique_id = str(uuid.uuid4()) + if str(unique_id) not in existing_ids: + channel_id = str(unique_id) + existing_ids.append(channel_id) + self.fhdhr.db.set_fhdhr_value("channels", "IDs", existing_ids) + return channel_id + + def get_number(self, channel_id): + existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "IDs") or [] + existing_channel_info = [self.fhdhr.db.get_channel_value(channel_id, "info") or {} for channel_id in existing_ids] + cnumber = [existing_channel["number"] for existing_channel in existing_channel_info if existing_channel["fhdhr_id"] == channel_id] or None + if cnumber: + return cnumber + + used_numbers = [existing_channel["number"] for existing_channel in existing_channel_info] + for i in range(1000, 2000): + if str(float(i)) not in used_numbers: + break + return str(float(i)) diff --git a/fHDHR/device/channels/channel.py b/fHDHR/device/channels/channel.py new file mode 100644 index 0000000..b057ab5 --- /dev/null +++ b/fHDHR/device/channels/channel.py @@ -0,0 +1,92 @@ + + +class Channel(): + + def __init__(self, fhdhr, id_system, origin_id=None, channel_id=None): + self.fhdhr = fhdhr + + self.id_system = id_system + + if not channel_id: + if origin_id: + channel_id = id_system.get(origin_id) + else: + channel_id = id_system.assign() + self.dict = self.fhdhr.db.get_channel_value(str(channel_id), "info") or self.create_empty_channel(channel_id) + self.fhdhr.db.set_channel_value(self.dict["fhdhr_id"], "info", self.dict) + + def basics(self, channel_info): + """Some Channel Information is Critical""" + + if "id" in list(channel_info.keys()): + channel_info["origin_id"] = channel_info["id"] + del channel_info["id"] + + if "name" not in list(channel_info.keys()): + channel_info["name"] = self.dict["fhdhr_id"] + + if "callsign" not in list(channel_info.keys()): + channel_info["callsign"] = channel_info["name"] + + if "origin_id" not in list(channel_info.keys()): + channel_info["origin_id"] = channel_info["name"] + + if "tags" not in list(channel_info.keys()): + channel_info["tags"] = [] + + if "number" not in list(channel_info.keys()): + channel_info["number"] = self.id_system.get_number(channel_info["origin_id"]) + channel_info["number"] = str(float(channel_info["number"])) + + self.append_channel_info(channel_info) + + def create_empty_channel(self, channel_id): + return { + "fhdhr_id": str(channel_id), + "origin_id": None, + "name": None, + "callsign": None, + "number": None, + "tags": [], + "enabled": True + } + + def destroy(self): + self.fhdhr.db.delete_channel_value(self.dict["fhdhr_id"], "info") + channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "IDs") or [] + if self.dict["fhdhr_id"] in channel_ids: + channel_ids.remove(self.dict["fhdhr_id"]) + self.fhdhr.db.set_fhdhr_value("channels", "IDs", channel_ids) + + def append_channel_info(self, channel_info): + for chankey in list(channel_info.keys()): + self.dict[chankey] = channel_info[chankey] + self.fhdhr.db.set_channel_value(self.dict["fhdhr_id"], "info", self.dict) + + def set_status(self, enablement): + if enablement == "disable": + self.dict["enabled"] = False + elif enablement == "enable": + self.dict["enabled"] = True + self.fhdhr.db.set_channel_value(self.dict["fhdhr_id"], "info", self.dict) + + def lineup_dict(self): + return { + 'GuideNumber': self.dict['number'], + 'GuideName': self.dict['name'], + 'Tags': ",".join(self.dict['tags']), + 'URL': self.stream_url(), + } + + def stream_url(self): + return ('/auto/v%s' % self.dict['number']) + + def play_url(self): + return ('/api/m3u?method=get&channel=%s' % self.dict['number']) + + def __getattr__(self, name): + ''' will only get called for undefined attributes ''' + if name in list(self.dict.keys()): + return self.dict[name] + else: + return None diff --git a/fHDHR/device/epg.py b/fHDHR/device/epg.py index 6e864f6..d5e89e5 100644 --- a/fHDHR/device/epg.py +++ b/fHDHR/device/epg.py @@ -26,6 +26,7 @@ class EPG(): self.epg_method_selfadd() self.epg_methods = self.fhdhr.config.dict["epg"]["method"] + self.valid_epg_methods = [x for x in self.fhdhr.config.dict["main"]["valid_epg_methods"] if x and x not in [None, "None"]] self.def_method = self.fhdhr.config.dict["epg"]["def_method"] self.sleeptime = {} for epg_method in self.epg_methods: @@ -59,8 +60,8 @@ class EPG(): self.fhdhr.db.delete_fhdhr_value("epg_dict", method) - def whats_on_now(self, channel): - epgdict = self.get_epg() + def whats_on_now(self, channel, method=None): + epgdict = self.get_epg(method) listings = epgdict[channel]["listing"] for listing in listings: nowtime = datetime.datetime.utcnow() @@ -72,10 +73,19 @@ class EPG(): return epgitem return None - def whats_on_allchans(self): + def whats_on_allchans(self, method=None): + + if not method: + method = self.def_method + if (method == self.fhdhr.config.dict["main"]["dictpopname"] or + method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]): + method = "origin" + channel_guide_list = [] - for channel in self.channels.get_channels(): - whatson = self.whats_on_now(channel["number"]) + epgdict = self.get_epg(method) + channels = list(epgdict.keys()) + for channel in channels: + whatson = self.whats_on_now(epgdict[channel]["number"], method) if whatson: channel_guide_list.append(whatson) return channel_guide_list diff --git a/fHDHR/device/epgtypes/blocks.py b/fHDHR/device/epgtypes/blocks.py index 536fa50..b03b906 100644 --- a/fHDHR/device/epgtypes/blocks.py +++ b/fHDHR/device/epgtypes/blocks.py @@ -35,7 +35,7 @@ class blocksEPG(): "callsign": c["callsign"], "name": c["name"], "number": c["number"], - "id": c["id"], + "id": c["origin_id"], "thumbnail": ("/api/images?method=generate&type=channel&message=%s" % (str(c['number']))), "listing": [], } @@ -45,7 +45,7 @@ class blocksEPG(): "time_start": timestamp['time_start'], "time_end": timestamp['time_end'], "duration_minutes": 60, - "thumbnail": ("/api/images?method=generate&type=content&message=%s" % (str(c["id"]) + "_" + str(timestamp['time_start']).split(" ")[0])), + "thumbnail": ("/api/images?method=generate&type=content&message=%s" % (str(c["origin_id"]) + "_" + str(timestamp['time_start']).split(" ")[0])), "title": "Unavailable", "sub-title": "Unavailable", "description": "Unavailable", @@ -56,7 +56,7 @@ class blocksEPG(): "seasonnumber": None, "episodenumber": None, "isnew": False, - "id": str(c["id"]) + "_" + str(timestamp['time_start']).split(" ")[0], + "id": str(c["origin_id"]) + "_" + str(timestamp['time_start']).split(" ")[0], } programguide[str(c["number"])]["listing"].append(clean_prog_dict) diff --git a/fHDHR/http/api/channels.py b/fHDHR/http/api/channels.py index 04ebb64..a099e4c 100644 --- a/fHDHR/http/api/channels.py +++ b/fHDHR/http/api/channels.py @@ -1,5 +1,6 @@ -from flask import request, redirect +from flask import request, redirect, Response import urllib.parse +import json class Channels(): @@ -18,7 +19,30 @@ class Channels(): method = request.args.get('method', default=None, type=str) redirect_url = request.args.get('redirect', default=None, type=str) - if method == "scan": + if method == "get": + channels_info = [] + for fhdhr_id in list(self.fhdhr.device.channels.list.keys()): + channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + channel_dict = channel_obj.dict.copy() + channel_dict["play_url"] = channel_obj.play_url() + channel_dict["stream_url"] = channel_obj.stream_url() + channels_info.append(channel_dict) + channels_info_json = json.dumps(channels_info, indent=4) + + return Response(status=200, + response=channels_info_json, + mimetype='application/json') + + elif method in ["enable", "disable"]: + channel = request.args.get('channel', default=None, type=str) + if not channel: + if redirect_url: + return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Failed" % method)) + else: + return "%s Falied" % method + self.fhdhr.device.channels.set_channel_status("number", channel, method) + + elif method == "scan": self.fhdhr.device.station_scan.scan() else: diff --git a/fHDHR/http/api/debug.py b/fHDHR/http/api/debug.py index ff24cb8..7ca9df7 100644 --- a/fHDHR/http/api/debug.py +++ b/fHDHR/http/api/debug.py @@ -19,7 +19,7 @@ class Debug_JSON(): debugjson = { "base_url": base_url, - "total channels": self.fhdhr.device.channels.get_station_total(), + "total channels": len(self.fhdhr.device.channels.list), "tuner status": self.fhdhr.device.tuners.status(), } cluster_json = json.dumps(debugjson, indent=4) diff --git a/fHDHR/http/api/m3u.py b/fHDHR/http/api/m3u.py index ed06ff4..1bafffc 100644 --- a/fHDHR/http/api/m3u.py +++ b/fHDHR/http/api/m3u.py @@ -38,44 +38,50 @@ class M3U(): "x-tvg-url=\"" + xmltvurl + "\"") ) - channel_list = self.fhdhr.device.channels.get_channels() - channel_number_list = [x["number"] for x in channel_list] + channel_items = [] if channel == "all": - channel_items = channel_list - elif channel in channel_number_list: - channel_items = [self.fhdhr.device.channels.get_channel_dict("number", channel)] + fileName = "channels.m3u" + for fhdhr_id in list(self.fhdhr.device.channels.list.keys()): + channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + if channel_obj.enabled: + channel_items.append(channel_obj) + elif channel in self.fhdhr.device.channels.get_channel_list("number"): + channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel) + fileName = str(channel_obj.number) + ".m3u" + if channel_obj.enabled: + channel_items.append(channel_obj) + else: + return "Channel Disabled" else: return "Invalid Channel" - for channel_item in channel_items: + for channel_obj in channel_items: - logourl = ('%s/api/images?method=get&type=channel&id=%s' % - (base_url, str(channel_item['id']))) + if self.fhdhr.config.dict["epg"]["images"] == "proxy" or not channel_obj.thumbnail: + logourl = ('%s/api/images?method=get&type=channel&id=%s' % + (base_url, str(channel_obj.dict['origin_id']))) + else: + logourl = channel_obj.thumbnail fakefile.write( "%s\n" % ( RECORD_MARKER + ":0" + " " + - "channelID=\"" + str(channel_item['id']) + "\" " + - "tvg-chno=\"" + str(channel_item['number']) + "\" " + - "tvg-name=\"" + str(channel_item['name']) + "\" " + - "tvg-id=\"" + str(channel_item['number']) + "\" " + + "channelID=\"" + str(channel_obj.dict['origin_id']) + "\" " + + "tvg-chno=\"" + str(channel_obj.dict['number']) + "\" " + + "tvg-name=\"" + str(channel_obj.dict['name']) + "\" " + + "tvg-id=\"" + str(channel_obj.dict['number']) + "\" " + "tvg-logo=\"" + logourl + "\" " + - "group-title=\"" + self.fhdhr.config.dict["fhdhr"]["friendlyname"] + "\"," + str(channel_item['name'])) + "group-title=\"" + self.fhdhr.config.dict["fhdhr"]["friendlyname"] + "\"," + str(channel_obj.dict['name'])) ) - fakefile.write( - "%s\n" % ( - ('%s/auto/v%s' % - (base_url, str(channel_item['number']))) - ) - ) + fakefile.write("%s\n" % (base_url + channel_obj.stream_url())) channels_m3u = fakefile.getvalue() - return Response(status=200, - response=channels_m3u, - mimetype='audio/x-mpegurl') + resp = Response(status=200, response=channels_m3u, mimetype='audio/x-mpegurl') + resp.headers["content-disposition"] = "attachment; filename=" + fileName + return resp if redirect_url: return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method)) diff --git a/fHDHR/http/api/watch.py b/fHDHR/http/api/watch.py index d036a16..7424cb9 100644 --- a/fHDHR/http/api/watch.py +++ b/fHDHR/http/api/watch.py @@ -18,7 +18,9 @@ class Watch(): def get(self, *args): - full_url = request.url + client_address = request.remote_addr + + accessed_url = request.args.get('accessed', default=request.url, type=str) method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) @@ -32,9 +34,17 @@ class Watch(): if not channel_number: return "Missing Channel" - if channel_number not in list(self.fhdhr.device.channels.list.keys()): + if channel_number not in self.fhdhr.device.channels.get_channel_list("number"): response = Response("Not Found", status=404) response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) + abort(response) + + channel_dict = self.fhdhr.device.channels.get_channel_dict("number", channel_number) + if not channel_dict["enabled"]: + response = Response("Service Unavailable", status=503) + response.headers["X-fHDHR-Error"] = str("806 - Tune Failed") + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) duration = request.args.get('duration', default=0, type=int) @@ -44,6 +54,7 @@ class Watch(): if transcode not in valid_transcode_types: response = Response("Service Unavailable", status=503) response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile" + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) stream_args = { @@ -51,7 +62,8 @@ class Watch(): "method": method, "duration": duration, "transcode": transcode, - "accessed": full_url, + "accessed": accessed_url, + "client": client_address } try: @@ -64,6 +76,7 @@ class Watch(): % (stream_args["method"], str(stream_args["channel"]), str(e))) response = Response("Service Unavailable", status=503) response.headers["X-fHDHR-Error"] = str(e) + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) abort(response) tuner = self.fhdhr.device.tuners.tuners[int(tunernum)] @@ -74,6 +87,7 @@ class Watch(): % (stream_args["method"], str(stream_args["channel"]), str(e))) response = Response("Service Unavailable", status=503) response.headers["X-fHDHR-Error"] = str(e) + self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) tuner.close() abort(response) diff --git a/fHDHR/http/files/lineup_json.py b/fHDHR/http/files/lineup_json.py index 00133b1..463f737 100644 --- a/fHDHR/http/files/lineup_json.py +++ b/fHDHR/http/files/lineup_json.py @@ -16,7 +16,14 @@ class Lineup_JSON(): base_url = request.url_root[:-1] - jsonlineup = self.fhdhr.device.channels.get_station_list(base_url) + jsonlineup = [] + for fhdhr_id in list(self.fhdhr.device.channels.list.keys()): + channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + if channel_obj.enabled: + lineup_dict = channel_obj.lineup_dict() + lineup_dict["URL"] = base_url + lineup_dict["URL"] + jsonlineup.append(lineup_dict) + lineup_json = json.dumps(jsonlineup, indent=4) return Response(status=200, diff --git a/fHDHR/http/files/lineup_status_json.py b/fHDHR/http/files/lineup_status_json.py index 80a15a0..f50b3c0 100644 --- a/fHDHR/http/files/lineup_status_json.py +++ b/fHDHR/http/files/lineup_status_json.py @@ -17,7 +17,7 @@ class Lineup_Status_JSON(): station_scanning = self.fhdhr.device.station_scan.scanning() if station_scanning: jsonlineup = self.scan_in_progress() - elif not self.fhdhr.device.channels.get_station_total(): + elif not len(self.fhdhr.device.channels.list): jsonlineup = self.scan_in_progress() else: jsonlineup = self.not_scanning() @@ -28,11 +28,10 @@ class Lineup_Status_JSON(): mimetype='application/json') def scan_in_progress(self): - channel_count = self.fhdhr.device.channels.get_station_total() jsonlineup = { "ScanInProgress": "true", "Progress": 99, - "Found": channel_count + "Found": len(self.fhdhr.device.channels.list) } return jsonlineup diff --git a/fHDHR/http/files/lineup_xml.py b/fHDHR/http/files/lineup_xml.py index 31e2bdf..3f787c1 100644 --- a/fHDHR/http/files/lineup_xml.py +++ b/fHDHR/http/files/lineup_xml.py @@ -20,13 +20,16 @@ class Lineup_XML(): base_url = request.url_root[:-1] out = xml.etree.ElementTree.Element('Lineup') - station_list = self.fhdhr.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']) - sub_el(program_out, 'GuideName', station_item['GuideName']) - sub_el(program_out, 'Tags', ",".join(station_item['Tags'])) - sub_el(program_out, 'URL', station_item['URL']) + for fhdhr_id in list(self.fhdhr.device.channels.list.keys()): + channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + if channel_obj.enabled: + lineup_dict = channel_obj.lineup_dict() + lineup_dict["URL"] = base_url + lineup_dict["URL"] + program_out = sub_el(out, 'Program') + sub_el(program_out, 'GuideNumber', lineup_dict['GuideNumber']) + sub_el(program_out, 'GuideName', lineup_dict['GuideName']) + sub_el(program_out, 'Tags', lineup_dict['Tags']) + sub_el(program_out, 'URL', lineup_dict['URL']) fakefile = BytesIO() fakefile.write(b'\n') diff --git a/fHDHR/http/pages/__init__.py b/fHDHR/http/pages/__init__.py index 20c94e4..96c4a86 100644 --- a/fHDHR/http/pages/__init__.py +++ b/fHDHR/http/pages/__init__.py @@ -2,6 +2,7 @@ from .index_html import Index_HTML from .origin_html import Origin_HTML +from .channels_html import Channels_HTML from .guide_html import Guide_HTML from .cluster_html import Cluster_HTML from .streams_html import Streams_HTML @@ -18,6 +19,7 @@ class fHDHR_Pages(): self.index_html = Index_HTML(fhdhr) self.origin_html = Origin_HTML(fhdhr) + self.channels_html = Channels_HTML(fhdhr) self.guide_html = Guide_HTML(fhdhr) self.cluster_html = Cluster_HTML(fhdhr) self.streams_html = Streams_HTML(fhdhr) diff --git a/fHDHR/http/pages/channels_html.py b/fHDHR/http/pages/channels_html.py new file mode 100644 index 0000000..fe90fb3 --- /dev/null +++ b/fHDHR/http/pages/channels_html.py @@ -0,0 +1,23 @@ +from flask import request, render_template + + +class Channels_HTML(): + endpoints = ["/channels", "/channels.html"] + endpoint_name = "channels_html" + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + channelslist = [] + for fhdhr_id in list(self.fhdhr.device.channels.list.keys()): + channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + channel_dict = channel_obj.dict.copy() + channel_dict["play_url"] = channel_obj.play_url() + channelslist.append(channel_dict) + + return render_template('channels.html', request=request, fhdhr=self.fhdhr, channelslist=channelslist) diff --git a/fHDHR/http/pages/cluster_html.py b/fHDHR/http/pages/cluster_html.py index 19bd80c..d88d1c6 100644 --- a/fHDHR/http/pages/cluster_html.py +++ b/fHDHR/http/pages/cluster_html.py @@ -4,7 +4,7 @@ import urllib.parse class Cluster_HTML(): endpoints = ["/cluster", "/cluster.html"] - endpoint_name = "cluster" + endpoint_name = "cluster_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/pages/diagnostics_html.py b/fHDHR/http/pages/diagnostics_html.py index bdb8ce9..8f0ec79 100644 --- a/fHDHR/http/pages/diagnostics_html.py +++ b/fHDHR/http/pages/diagnostics_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class Diagnostics_HTML(): endpoints = ["/diagnostics", "/diagnostics.html"] - endpoint_name = "diagnostics" + endpoint_name = "diagnostics_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/pages/guide_html.py b/fHDHR/http/pages/guide_html.py index 306b30f..a8dd779 100644 --- a/fHDHR/http/pages/guide_html.py +++ b/fHDHR/http/pages/guide_html.py @@ -6,7 +6,7 @@ from fHDHR.tools import humanized_time class Guide_HTML(): endpoints = ["/guide", "/guide.html"] - endpoint_name = "guide" + endpoint_name = "guide_html" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -20,13 +20,16 @@ class Guide_HTML(): chan_guide_list = [] - for channel in self.fhdhr.device.epg.whats_on_allchans(): + source = request.args.get('source', default=self.fhdhr.device.epg.def_method, type=str) + epg_methods = self.fhdhr.device.epg.valid_epg_methods + if source not in epg_methods: + source = self.fhdhr.device.epg.def_method + + for channel in self.fhdhr.device.epg.whats_on_allchans(source): end_time = datetime.datetime.strptime(channel["listing"][0]["time_end"], '%Y%m%d%H%M%S +0000') remaining_time = humanized_time(int((end_time - nowtime).total_seconds())) - play_url = ("/api/m3u?method=get&channel=%s\n" % (channel["number"])) chan_dict = { - "play_url": play_url, "name": channel["name"], "number": channel["number"], "chan_thumbnail": channel["thumbnail"], @@ -37,4 +40,4 @@ class Guide_HTML(): } chan_guide_list.append(chan_dict) - return render_template('guide.html', request=request, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list) + return render_template('guide.html', request=request, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods) diff --git a/fHDHR/http/pages/index_html.py b/fHDHR/http/pages/index_html.py index c2aa00f..673b1ea 100644 --- a/fHDHR/http/pages/index_html.py +++ b/fHDHR/http/pages/index_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class Index_HTML(): endpoints = ["/", "/index", "/index.html"] - endpoint_name = "root" + endpoint_name = "root_html" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -20,7 +20,7 @@ class Index_HTML(): "Script Directory": str(self.fhdhr.config.internal["paths"]["script_dir"]), "Config File": str(self.fhdhr.config.config_file), "Cache Path": str(self.fhdhr.config.internal["paths"]["cache_dir"]), - "Total Channels": str(self.fhdhr.device.channels.get_station_total()), + "Total Channels": len(self.fhdhr.device.channels.list), "Tuner Usage": ("%s/%s" % (str(tuners_in_use), str(max_tuners))), } diff --git a/fHDHR/http/pages/origin_html.py b/fHDHR/http/pages/origin_html.py index 6a40aad..2de0b05 100644 --- a/fHDHR/http/pages/origin_html.py +++ b/fHDHR/http/pages/origin_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class Origin_HTML(): endpoints = ["/origin", "/origin.html"] - endpoint_name = "origin" + endpoint_name = "origin_html" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -13,6 +13,6 @@ class Origin_HTML(): def get(self, *args): - origin_status_dict = self.fhdhr.device.channels.get_origin_status() - origin_status_dict["Total Channels"] = str(self.fhdhr.device.channels.get_station_total()) + origin_status_dict = self.fhdhr.origin.get_status_dict() + origin_status_dict["Total Channels"] = len(self.fhdhr.device.channels.list) return render_template('origin.html', request=request, fhdhr=self.fhdhr, origin_status_dict=origin_status_dict, list=list) diff --git a/fHDHR/http/pages/settings_html.py b/fHDHR/http/pages/settings_html.py index 80674b4..aff7ee9 100644 --- a/fHDHR/http/pages/settings_html.py +++ b/fHDHR/http/pages/settings_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class Settings_HTML(): endpoints = ["/settings", "/settings.html"] - endpoint_name = "settings" + endpoint_name = "settings_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/pages/streams_html.py b/fHDHR/http/pages/streams_html.py index af82885..97b0d10 100644 --- a/fHDHR/http/pages/streams_html.py +++ b/fHDHR/http/pages/streams_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class Streams_HTML(): endpoints = ["/streams", "/streams.html"] - endpoint_name = "streams" + endpoint_name = "streams_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/pages/version_html.py b/fHDHR/http/pages/version_html.py index c0c1607..7c0c72a 100644 --- a/fHDHR/http/pages/version_html.py +++ b/fHDHR/http/pages/version_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class Version_HTML(): endpoints = ["/version", "/version.html"] - endpoint_name = "version" + endpoint_name = "version_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/pages/xmltv_html.py b/fHDHR/http/pages/xmltv_html.py index 82747fa..33cfafe 100644 --- a/fHDHR/http/pages/xmltv_html.py +++ b/fHDHR/http/pages/xmltv_html.py @@ -3,7 +3,7 @@ from flask import request, render_template class xmlTV_HTML(): endpoints = ["/xmltv", "/xmltv.html"] - endpoint_name = "xmltv" + endpoint_name = "xmltv_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/watch/auto.py b/fHDHR/http/watch/auto.py index f442c29..d2df5f5 100644 --- a/fHDHR/http/watch/auto.py +++ b/fHDHR/http/watch/auto.py @@ -1,6 +1,5 @@ -from flask import Response, request, stream_with_context, abort - -from fHDHR.exceptions import TunerError +from flask import request, abort, redirect +import urllib.parse class Auto(): @@ -15,7 +14,9 @@ class Auto(): def get(self, channel, *args): - full_url = request.url + method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) + + redirect_url = "/api/watch?method=%s" % (method) if channel.startswith("v"): channel_number = channel.replace('v', '') @@ -24,70 +25,21 @@ class Auto(): subchannel = 0 if "-" in channel: subchannel = channel.replace('ch', '').split("-")[1] + self.fhdhr.logger.error("Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) abort(501, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) + else: + channel_number = channel - if channel_number not in list(self.fhdhr.device.channels.list.keys()): - response = Response("Not Found", status=404) - response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" - abort(response) + redirect_url += "&channel=%s" % str(channel_number) - method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) duration = request.args.get('duration', default=0, type=int) + if duration: + redirect_url += "&duration=%s" % str(duration) transcode = request.args.get('transcode', default=None, type=str) - valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"] - if transcode not in valid_transcode_types: - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile" - abort(response) + if transcode: + redirect_url += "&transcode=%s" % str(transcode) - stream_args = { - "channel": channel_number, - "method": method, - "duration": duration, - "transcode": transcode, - "accessed": full_url, - } + redirect_url += "&accessed=%s" % urllib.parse.quote(request.url) - try: - tunernum = self.fhdhr.device.tuners.first_available() - except TunerError as e: - self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = str(e) - abort(response) - tuner = self.fhdhr.device.tuners.tuners[int(tunernum)] - - try: - stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args) - except TunerError as e: - self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = str(e) - tuner.close() - abort(response) - - self.fhdhr.logger.info("Tuner #" + str(tunernum) + " to be used for stream.") - tuner.set_status(stream_args) - - if stream_args["method"] == "direct": - return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True) - elif stream_args["method"] in ["ffmpeg", "vlc"]: - return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"]) - - """ - try: - if stream_args["method"] == "direct": - return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True) - elif stream_args["method"] in ["ffmpeg", "vlc"]: - return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"]) - except TunerError as e: - tuner.close() - self.fhdhr.logger.info("A %s stream request for channel %s failed due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = str(e) - abort(response) - """ + return redirect(redirect_url) diff --git a/fHDHR/http/watch/tuner.py b/fHDHR/http/watch/tuner.py index 1a377be..e555ea1 100644 --- a/fHDHR/http/watch/tuner.py +++ b/fHDHR/http/watch/tuner.py @@ -1,6 +1,5 @@ -from flask import Response, request, stream_with_context, abort - -from fHDHR.exceptions import TunerError +from flask import request, abort, redirect +import urllib.parse class Tuner(): @@ -15,7 +14,11 @@ class Tuner(): def get(self, tuner_number, channel, *args): - full_url = request.url + method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) + + redirect_url = "/api/watch?method=%s" % (method) + + redirect_url += "&tuner=%s" % str(tuner_number) if channel.startswith("v"): channel_number = channel.replace('v', '') @@ -24,70 +27,21 @@ class Tuner(): subchannel = 0 if "-" in channel: subchannel = channel.replace('ch', '').split("-")[1] + self.fhdhr.logger.error("Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) abort(501, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel))) + else: + channel_number = channel - if channel_number not in list(self.fhdhr.device.channels.list.keys()): - response = Response("Not Found", status=404) - response.headers["X-fHDHR-Error"] = "801 - Unknown Channel" - abort(response) + redirect_url += "&channel=%s" % str(channel_number) - method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) duration = request.args.get('duration', default=0, type=int) + if duration: + redirect_url += "&duration=%s" % str(duration) transcode = request.args.get('transcode', default=None, type=str) - valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"] - if transcode not in valid_transcode_types: - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile" - abort(response) + if transcode: + redirect_url += "&transcode=%s" % str(transcode) - stream_args = { - "channel": channel_number, - "method": method, - "duration": duration, - "transcode": transcode, - "accessed": full_url, - } + redirect_url += "&accessed=%s" % urllib.parse.quote(request.url) - try: - tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number) - except TunerError as e: - self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = str(e) - abort(response) - tuner = self.fhdhr.device.tuners.tuners[int(tunernum)] - - try: - stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args) - except TunerError as e: - self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = str(e) - tuner.close() - abort(response) - - self.fhdhr.logger.info("Tuner #" + str(tunernum) + " to be used for stream.") - tuner.set_status(stream_args) - - if stream_args["method"] == "direct": - return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True) - elif stream_args["method"] in ["ffmpeg", "vlc"]: - return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"]) - - """ - try: - if stream_args["method"] == "direct": - return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True) - elif stream_args["method"] in ["ffmpeg", "vlc"]: - return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"]) - except TunerError as e: - tuner.close() - self.fhdhr.logger.info("A %s stream request for channel %s failed due to %s" - % (stream_args["method"], str(stream_args["channel"]), str(e))) - response = Response("Service Unavailable", status=503) - response.headers["X-fHDHR-Error"] = str(e) - abort(response) - """ + return redirect(redirect_url) diff --git a/fHDHR/origin/__init__.py b/fHDHR/origin/__init__.py index 4612e75..c8e1412 100644 --- a/fHDHR/origin/__init__.py +++ b/fHDHR/origin/__init__.py @@ -20,8 +20,8 @@ class OriginChannels_StandIN(): def get_channels(self): return [] - def get_channel_stream(self, chandict, allchandict): - return [{"number": chandict["number"], "stream_url": None}], False + def get_channel_stream(self, chandict): + return None class OriginServiceWrapper(): @@ -54,8 +54,8 @@ class OriginServiceWrapper(): def get_channels(self): return self.channels.get_channels() - def get_channel_stream(self, chandict, allchandict): - return self.channels.get_channel_stream(chandict, allchandict) + def get_channel_stream(self, chandict): + return self.channels.get_channel_stream(chandict) def update_epg(self, channels): return self.epg.update_epg(channels) diff --git a/fHDHR/origin/origin_channels.py b/fHDHR/origin/origin_channels.py index acda873..ed01bf9 100644 --- a/fHDHR/origin/origin_channels.py +++ b/fHDHR/origin/origin_channels.py @@ -40,10 +40,7 @@ class OriginChannels(): channel_list.append(clean_station_item) return channel_list - def get_channel_stream(self, chandict, allchandict): - caching = True - streamlist = [] - streamdict = {} + def get_channel_stream(self, chandict): streamurl = ('%s%s:%s/live?channel=%s&client=%s' % ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", self.fhdhr.config.dict["origin"]["address"], @@ -51,6 +48,4 @@ class OriginChannels(): str(chandict["number"]), str(chandict["number"]), )) - streamdict = {"number": chandict["number"], "stream_url": streamurl} - streamlist.append(streamdict) - return streamlist, caching + return streamurl diff --git a/fHDHR/origin/origin_epg.py b/fHDHR/origin/origin_epg.py index 84e6f09..bad6892 100644 --- a/fHDHR/origin/origin_epg.py +++ b/fHDHR/origin/origin_epg.py @@ -49,8 +49,8 @@ class OriginEPG(): "callsign": cdict["callsign"], "name": cdict["name"] or cdict["callsign"], "number": cdict["number"], - "id": str(cdict["id"]), - "thumbnail": self.get_channel_thumbnail(cdict['id']), + "id": str(cdict["origin_id"]), + "thumbnail": self.get_channel_thumbnail(cdict['origin_id']), "listing": [], } @@ -58,7 +58,7 @@ class OriginEPG(): ("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://", self.fhdhr.config.dict["origin"]["address"], str(self.fhdhr.config.dict["origin"]["port"]), - str(cdict["id"]), + str(cdict["origin_id"]), )) epg_req = self.fhdhr.web.session.get(epg_url) epg_dict = xmltodict.parse(epg_req.content)