diff --git a/data/internal_config/serviceconf.json b/data/internal_config/ZZ_serviceconf.json similarity index 100% rename from data/internal_config/serviceconf.json rename to data/internal_config/ZZ_serviceconf.json diff --git a/data/internal_config/database.json b/data/internal_config/database.json new file mode 100644 index 0000000..2f56146 --- /dev/null +++ b/data/internal_config/database.json @@ -0,0 +1,39 @@ +{ + "database":{ + "type":{ + "value": "sqlite", + "config_file": true, + "config_web": false + }, + "driver":{ + "value": "none", + "config_file": true, + "config_web": false + }, + "user":{ + "value": "none", + "config_file": true, + "config_web": false + }, + "pass":{ + "value": "none", + "config_file": true, + "config_web": false + }, + "host":{ + "value": "none", + "config_file": true, + "config_web": false + }, + "port":{ + "value": "none", + "config_file": true, + "config_web": false + }, + "name":{ + "value": "none", + "config_file": true, + "config_web": false + } + } +} diff --git a/data/internal_config/epg.json b/data/internal_config/epg.json new file mode 100644 index 0000000..4f6f657 --- /dev/null +++ b/data/internal_config/epg.json @@ -0,0 +1,9 @@ +{ + "epg":{ + "images":{ + "value": "pass", + "config_file": true, + "config_web": true + } + } +} diff --git a/data/internal_config/fhdhr.json b/data/internal_config/fhdhr.json index c621b07..333247f 100644 --- a/data/internal_config/fhdhr.json +++ b/data/internal_config/fhdhr.json @@ -1,21 +1,4 @@ { - "main":{ - "uuid":{ - "value": "none", - "config_file": true, - "config_web": false - }, - "cache_dir":{ - "value": "none", - "config_file": true, - "config_web": true - }, - "thread_method":{ - "value": "multiprocessing", - "config_file": true, - "config_web": true - } - }, "fhdhr":{ "address":{ "value": "0.0.0.0", @@ -74,87 +57,5 @@ "config_file": true, "config_web": true } - }, - "epg":{ - "images":{ - "value": "pass", - "config_file": true, - "config_web": true - } - }, - "ffmpeg":{ - "path":{ - "value": "ffmpeg", - "config_file": true, - "config_web": true - }, - "bytes_per_read":{ - "value": 1152000, - "config_file": true, - "config_web": true - } - }, - "vlc":{ - "path":{ - "value": "cvlc", - "config_file": true, - "config_web": true - }, - "bytes_per_read":{ - "value": 1152000, - "config_file": true, - "config_web": true - } - }, - "direct_stream":{ - "chunksize":{ - "value": 1048576, - "config_file": true, - "config_web": true - } - }, - "logging":{ - "level":{ - "value": "WARNING", - "config_file": true, - "config_web": true - } - }, - "database":{ - "type":{ - "value": "sqlite", - "config_file": true, - "config_web": false - }, - "driver":{ - "value": "none", - "config_file": true, - "config_web": false - }, - "user":{ - "value": "none", - "config_file": true, - "config_web": false - }, - "pass":{ - "value": "none", - "config_file": true, - "config_web": false - }, - "host":{ - "value": "none", - "config_file": true, - "config_web": false - }, - "port":{ - "value": "none", - "config_file": true, - "config_web": false - }, - "name":{ - "value": "none", - "config_file": true, - "config_web": false - } } } diff --git a/data/internal_config/logging.json b/data/internal_config/logging.json new file mode 100644 index 0000000..87b53ec --- /dev/null +++ b/data/internal_config/logging.json @@ -0,0 +1,9 @@ +{ + "logging":{ + "level":{ + "value": "WARNING", + "config_file": true, + "config_web": true + } + } +} diff --git a/data/internal_config/main.json b/data/internal_config/main.json new file mode 100644 index 0000000..bbc6776 --- /dev/null +++ b/data/internal_config/main.json @@ -0,0 +1,19 @@ +{ + "main":{ + "uuid":{ + "value": "none", + "config_file": true, + "config_web": false + }, + "cache_dir":{ + "value": "none", + "config_file": true, + "config_web": true + }, + "thread_method":{ + "value": "multiprocessing", + "config_file": true, + "config_web": true + } + } +} diff --git a/data/internal_config/rmg.json b/data/internal_config/rmg.json new file mode 100644 index 0000000..bf088aa --- /dev/null +++ b/data/internal_config/rmg.json @@ -0,0 +1,9 @@ +{ + "rmg":{ + "enabled":{ + "value": true, + "config_file": true, + "config_web": false + } + } +} diff --git a/data/internal_config/streaming.json b/data/internal_config/streaming.json new file mode 100644 index 0000000..9f78279 --- /dev/null +++ b/data/internal_config/streaming.json @@ -0,0 +1,33 @@ +{ + "ffmpeg":{ + "path":{ + "value": "ffmpeg", + "config_file": true, + "config_web": true + }, + "bytes_per_read":{ + "value": 1152000, + "config_file": true, + "config_web": true + } + }, + "vlc":{ + "path":{ + "value": "cvlc", + "config_file": true, + "config_web": true + }, + "bytes_per_read":{ + "value": 1152000, + "config_file": true, + "config_web": true + } + }, + "direct_stream":{ + "chunksize":{ + "value": 1048576, + "config_file": true, + "config_web": true + } + } +} diff --git a/data/www/templates/base.html b/data/www/templates/base.html index b49e7fd..3465dcc 100644 --- a/data/www/templates/base.html +++ b/data/www/templates/base.html @@ -22,7 +22,7 @@ - + diff --git a/data/www/templates/channels.html b/data/www/templates/channels.html index 1a0f806..c2081b4 100644 --- a/data/www/templates/channels.html +++ b/data/www/templates/channels.html @@ -5,7 +5,7 @@

{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels

-

Note: This may take some time.

+

Note: This may take some time.


diff --git a/data/www/templates/diagnostics.html b/data/www/templates/diagnostics.html index ff6f38f..1c75f76 100644 --- a/data/www/templates/diagnostics.html +++ b/data/www/templates/diagnostics.html @@ -2,10 +2,35 @@ {% block content %} +

fHDHR Diagnostic Links

+ + + + + + + + + {% for button_item in button_list %} -
-

-
+ + + {% if button_item["hdhr"] %} + + {% else %} + + {% endif %} + {% if button_item["rmg"] %} + + {% else %} + + {% endif %} + {% if button_item["other"] %} + + {% else %} + + {% endif %} + {% endfor %} {% endblock %} diff --git a/data/www/templates/streams.html b/data/www/templates/tuners.html similarity index 66% rename from data/www/templates/streams.html rename to data/www/templates/tuners.html index fc387a4..e85a295 100644 --- a/data/www/templates/streams.html +++ b/data/www/templates/tuners.html @@ -19,8 +19,12 @@ - {% if tuner_dict["status"] == "Active" %} + {% if tuner_dict["status"] in ["Active", "Acquired"] %} + {% else %} + + {% endif %} + {% if tuner_dict["status"] == "Active" %} @@ -28,12 +32,14 @@ - {% endif %} diff --git a/fHDHR/device/__init__.py b/fHDHR/device/__init__.py index e3f7484..fc442e7 100644 --- a/fHDHR/device/__init__.py +++ b/fHDHR/device/__init__.py @@ -2,7 +2,6 @@ from .channels import Channels from .epg import EPG from .tuners import Tuners from .images import imageHandler -from .station_scan import Station_Scan from .ssdp import SSDPServer from .cluster import fHDHR_Cluster @@ -19,8 +18,6 @@ class fHDHR_Device(): self.images = imageHandler(fhdhr, self.epg) - self.station_scan = Station_Scan(fhdhr, self.channels) - self.ssdp = SSDPServer(fhdhr) self.cluster = fHDHR_Cluster(fhdhr, self.ssdp) diff --git a/fHDHR/device/cluster.py b/fHDHR/device/cluster.py index 1bf15a5..3a6e629 100644 --- a/fHDHR/device/cluster.py +++ b/fHDHR/device/cluster.py @@ -124,7 +124,7 @@ class fHDHR_Cluster(): self.fhdhr.logger.info("Adding %s to cluster." % location) cluster[location] = {"base_url": location} - location_info_url = location + "/discover.json" + location_info_url = "%s/hdhr/discover.json" % location try: location_info_req = self.fhdhr.web.session.get(location_info_url) except self.fhdhr.web.exceptions.ConnectionError: diff --git a/fHDHR/device/ssdp.py b/fHDHR/device/ssdp/__init__.py similarity index 71% rename from fHDHR/device/ssdp.py rename to fHDHR/device/ssdp/__init__.py index 5fdae10..1c0ea75 100644 --- a/fHDHR/device/ssdp.py +++ b/fHDHR/device/ssdp/__init__.py @@ -2,21 +2,9 @@ import socket import struct - -class fHDHR_Detect(): - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - self.fhdhr.db.delete_fhdhr_value("ssdp_detect", "list") - - def set(self, location): - detect_list = self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] - if location not in detect_list: - detect_list.append(location) - self.fhdhr.db.set_fhdhr_value("ssdp_detect", "list", detect_list) - - def get(self): - return self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] +from .ssdp_detect import fHDHR_Detect +from .rmg_ssdp import RMG_SSDP +from .hdhr_ssdp import HDHR_SSDP class SSDPServer(): @@ -33,18 +21,14 @@ class SSDPServer(): self.port = 1900 self.iface = None self.address = None - self.server = 'fHDHR/%s UPnP/1.0' % fhdhr.version allowed_protos = ("ipv4", "ipv6") if self.proto not in allowed_protos: raise ValueError("Invalid proto - expected one of {}".format(allowed_protos)) - self.nt = 'urn:schemas-upnp-org:device:MediaServer:1' - self.usn = 'uuid:' + fhdhr.config.dict["main"]["uuid"] + '::' + self.nt self.location = ('http://' + fhdhr.config.dict["fhdhr"]["discovery_address"] + ':' + str(fhdhr.config.dict["fhdhr"]["port"]) + '/device.xml') - self.al = self.location - self.max_age = 1800 + self._iface = None if self.proto == "ipv4": @@ -95,9 +79,11 @@ class SSDPServer(): self.sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, 1) self.sock.bind((self.bind_address, self.port)) - self.notify_payload = self.create_notify_payload() self.msearch_payload = self.create_msearch_payload() + self.rmg_ssdp = RMG_SSDP(fhdhr, self._broadcast_ip) + self.hdhr_ssdp = HDHR_SSDP(fhdhr, self._broadcast_ip) + self.m_search() def on_recv(self, data, address): @@ -123,21 +109,33 @@ class SSDPServer(): # SSDP discovery self.fhdhr.logger.debug("Received qualifying M-SEARCH from {}".format(address)) self.fhdhr.logger.debug("M-SEARCH data: {}".format(headers)) - notify = self.notify_payload - self.fhdhr.logger.debug("Created NOTIFY: {}".format(notify)) - try: - self.sock.sendto(notify, address) - except OSError as e: - # Most commonly: We received a multicast from an IP not in our subnet - self.fhdhr.logger.debug("Unable to send NOTIFY to {}: {}".format(address, e)) - pass + + notify_list = [] + + hdhr_notify = self.hdhr_ssdp.get() + notify_list.append(hdhr_notify) + + if self.fhdhr.config.dict["rmg"]["enabled"]: + rmg_notify = self.rmg_ssdp.get() + notify_list.append(rmg_notify) + + for notify in notify_list: + + self.fhdhr.logger.debug("Created NOTIFY: {}".format(notify)) + try: + self.sock.sendto(notify, address) + except OSError as e: + # Most commonly: We received a multicast from an IP not in our subnet + self.fhdhr.logger.debug("Unable to send NOTIFY to {}: {}".format(address, e)) + pass elif cmd[0] == 'NOTIFY' and cmd[1] == '*': # SSDP presence self.fhdhr.logger.debug("NOTIFY data: {}".format(headers)) try: if headers["server"].startswith("fHDHR"): if headers["location"] != self.location: - self.detect_method.set(headers["location"].split("/device.xml")[0]) + savelocation = headers["location"].split("/device.xml")[0] + self.detect_method.set(savelocation) except KeyError: return else: @@ -147,31 +145,6 @@ class SSDPServer(): data = self.msearch_payload self.sock.sendto(data, self._address) - def create_notify_payload(self): - if self.max_age is not None and not isinstance(self.max_age, int): - raise ValueError("max_age must by of type: int") - data = ( - "NOTIFY * HTTP/1.1\r\n" - "HOST:{}\r\n" - "NT:{}\r\n" - "NTS:ssdp:alive\r\n" - "USN:{}\r\n" - "SERVER:{}\r\n" - ).format( - self._broadcast_ip, - self.nt, - self.usn, - self.server - ) - if self.location is not None: - data += "LOCATION:{}\r\n".format(self.location) - if self.al is not None: - data += "AL:{}\r\n".format(self.al) - if self.max_age is not None: - data += "Cache-Control:max-age={}\r\n".format(self.max_age) - data += "\r\n" - return data.encode("utf-8") - def create_msearch_payload(self): data = ( "M-SEARCH * HTTP/1.1\r\n" diff --git a/fHDHR/device/ssdp/hdhr_ssdp.py b/fHDHR/device/ssdp/hdhr_ssdp.py new file mode 100644 index 0000000..7f8cb11 --- /dev/null +++ b/fHDHR/device/ssdp/hdhr_ssdp.py @@ -0,0 +1,44 @@ + + +class HDHR_SSDP(): + + def __init__(self, fhdhr, _broadcast_ip): + self.fhdhr = fhdhr + + self.ssdp_content = None + + self._broadcast_ip = _broadcast_ip + self.nt = 'urn:schemas-upnp-org:device:MediaServer:1' + self.usn = 'uuid:' + fhdhr.config.dict["main"]["uuid"] + '::' + self.nt + self.server = 'fHDHR/%s UPnP/1.0' % fhdhr.version + self.location = ('http://' + fhdhr.config.dict["fhdhr"]["discovery_address"] + ':' + + str(fhdhr.config.dict["fhdhr"]["port"]) + '/device.xml') + self.al = self.location + self.max_age = 1800 + + def get(self): + if self.ssdp_content: + return self.ssdp_content.encode("utf-8") + + data = ( + "NOTIFY * HTTP/1.1\r\n" + "HOST:{}\r\n" + "NT:{}\r\n" + "NTS:ssdp:alive\r\n" + "USN:{}\r\n" + "SERVER:{}\r\n" + ).format( + self._broadcast_ip, + self.nt, + self.usn, + self.server + ) + if self.location is not None: + data += "LOCATION:{}\r\n".format(self.location) + if self.al is not None: + data += "AL:{}\r\n".format(self.al) + if self.max_age is not None: + data += "Cache-Control:max-age={}\r\n".format(self.max_age) + data += "\r\n" + self.ssdp_content = data + return data.encode("utf-8") diff --git a/fHDHR/device/ssdp/rmg_ssdp.py b/fHDHR/device/ssdp/rmg_ssdp.py new file mode 100644 index 0000000..31d6466 --- /dev/null +++ b/fHDHR/device/ssdp/rmg_ssdp.py @@ -0,0 +1,44 @@ + + +class RMG_SSDP(): + + def __init__(self, fhdhr, _broadcast_ip): + self.fhdhr = fhdhr + + self.ssdp_content = None + + self._broadcast_ip = _broadcast_ip + self.nt = 'urn:schemas-upnp-org:device-1-0' + self.usn = 'uuid:' + fhdhr.config.dict["main"]["uuid"] + '::' + self.nt + self.server = 'fHDHR/%s UPnP/1.0' % fhdhr.version + self.location = ('http://' + fhdhr.config.dict["fhdhr"]["discovery_address"] + ':' + + str(fhdhr.config.dict["fhdhr"]["port"]) + '/device.xml') + self.al = self.location + self.max_age = 1800 + + def get(self): + if self.ssdp_content: + return self.ssdp_content.encode("utf-8") + + data = ( + "NOTIFY * HTTP/1.1\r\n" + "HOST:{}\r\n" + "NT:{}\r\n" + "NTS:ssdp:alive\r\n" + "USN:{}\r\n" + "SERVER:{}\r\n" + ).format( + self._broadcast_ip, + self.nt, + self.usn, + self.server + ) + if self.location is not None: + data += "LOCATION:{}\r\n".format(self.location) + if self.al is not None: + data += "AL:{}\r\n".format(self.al) + if self.max_age is not None: + data += "Cache-Control:max-age={}\r\n".format(self.max_age) + data += "\r\n" + self.ssdp_content = data + return data.encode("utf-8") diff --git a/fHDHR/device/ssdp/ssdp_detect.py b/fHDHR/device/ssdp/ssdp_detect.py new file mode 100644 index 0000000..e9800b5 --- /dev/null +++ b/fHDHR/device/ssdp/ssdp_detect.py @@ -0,0 +1,16 @@ + + +class fHDHR_Detect(): + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + self.fhdhr.db.delete_fhdhr_value("ssdp_detect", "list") + + def set(self, location): + detect_list = self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] + if location not in detect_list: + detect_list.append(location) + self.fhdhr.db.set_fhdhr_value("ssdp_detect", "list", detect_list) + + def get(self): + return self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or [] diff --git a/fHDHR/device/station_scan.py b/fHDHR/device/station_scan.py deleted file mode 100644 index 69c7347..0000000 --- a/fHDHR/device/station_scan.py +++ /dev/null @@ -1,43 +0,0 @@ -import multiprocessing -import threading - - -class Station_Scan(): - - def __init__(self, fhdhr, channels): - self.fhdhr = fhdhr - - self.channels = channels - - self.fhdhr.db.delete_fhdhr_value("station_scan", "scanning") - - def scan(self, waitfordone=False): - self.fhdhr.logger.info("Channel Scan Requested by Client.") - - scan_status = self.fhdhr.db.get_fhdhr_value("station_scan", "scanning") - if scan_status: - self.fhdhr.logger.info("Channel Scan Already In Progress!") - else: - self.fhdhr.db.set_fhdhr_value("station_scan", "scanning", 1) - - if waitfordone: - self.runscan() - else: - if self.fhdhr.config.dict["main"]["thread_method"] in ["multiprocessing"]: - chanscan = multiprocessing.Process(target=self.runscan) - elif self.fhdhr.config.dict["main"]["thread_method"] in ["threading"]: - chanscan = threading.Thread(target=self.runscan) - if self.fhdhr.config.dict["main"]["thread_method"] in ["multiprocessing", "threading"]: - chanscan.start() - - def runscan(self): - self.channels.get_channels(forceupdate=True) - self.fhdhr.logger.info("Requested Channel Scan Complete.") - self.fhdhr.db.delete_fhdhr_value("station_scan", "scanning") - - def scanning(self): - scan_status = self.fhdhr.db.get_fhdhr_value("station_scan", "scanning") - if not scan_status: - return False - else: - return True diff --git a/fHDHR/device/tuners/__init__.py b/fHDHR/device/tuners/__init__.py index 65610cb..7ddbb8d 100644 --- a/fHDHR/device/tuners/__init__.py +++ b/fHDHR/device/tuners/__init__.py @@ -20,31 +20,51 @@ class Tuners(): for i in range(0, self.max_tuners): self.tuners[str(i)] = Tuner(fhdhr, i, epg) - def tuner_grab(self, tuner_number): + def get_available_tuner(self): + return next(tunernum for tunernum in list(self.tuners.keys()) if not self.tuners[tunernum].tuner_lock.locked()) or None + + def get_scanning_tuner(self): + return next(tunernum for tunernum in list(self.tuners.keys()) if self.tuners[tunernum].status["status"] == "Scanning") or None + + def stop_tuner_scan(self): + tunernum = self.get_scanning_tuner() + if tunernum: + self.tuners[str(tunernum)].close() + + def tuner_scan(self): + """Temporarily use a tuner for a scan""" + if not self.available_tuner_count(): + raise TunerError("805 - All Tuners In Use") + + tunernumber = self.get_available_tuner() + self.tuners[str(tunernumber)].channel_scan() + + if not tunernumber: + raise TunerError("805 - All Tuners In Use") + + def tuner_grab(self, tuner_number, channel_number): if str(tuner_number) not in list(self.tuners.keys()): self.fhdhr.logger.error("Tuner %s does not exist." % str(tuner_number)) raise TunerError("806 - Tune Failed") # TunerError will raise if unavailable - self.tuners[str(tuner_number)].grab() + self.tuners[str(tuner_number)].grab(channel_number) return tuner_number - def first_available(self): + def first_available(self, channel_number): if not self.available_tuner_count(): raise TunerError("805 - All Tuners In Use") - for tunernum in list(self.tuners.keys()): - try: - self.tuners[str(tunernum)].grab() - except TunerError: - continue - else: - return tunernum + tunernumber = self.get_available_tuner() - raise TunerError("805 - All Tuners In Use") + if not tunernumber: + raise TunerError("805 - All Tuners In Use") + else: + self.tuners[str(tunernumber)].grab(channel_number) + return tunernumber def tuner_close(self, tunernum): self.tuners[str(tunernum)].close() @@ -58,16 +78,14 @@ class Tuners(): def available_tuner_count(self): available_tuners = 0 for tunernum in list(self.tuners.keys()): - tuner_status = self.tuners[str(tunernum)].get_status() - if tuner_status["status"] == "Inactive": + if not self.tuners[str(tunernum)].tuner_lock.locked(): available_tuners += 1 return available_tuners def inuse_tuner_count(self): inuse_tuners = 0 for tunernum in list(self.tuners.keys()): - tuner_status = self.tuners[str(tunernum)].get_status() - if tuner_status["status"] == "Active": + if self.tuners[str(tunernum)].tuner_lock.locked(): inuse_tuners += 1 return inuse_tuners diff --git a/fHDHR/device/tuners/stream/direct_m3u8_stream.py b/fHDHR/device/tuners/stream/direct_m3u8_stream.py index aace1f1..8140e22 100644 --- a/fHDHR/device/tuners/stream/direct_m3u8_stream.py +++ b/fHDHR/device/tuners/stream/direct_m3u8_stream.py @@ -86,9 +86,6 @@ class Direct_M3U8_Stream(): yield chunk self.tuner.add_downloaded_size(chunk_size) - if playlist.target_duration: - time.sleep(int(playlist.target_duration)) - self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") except GeneratorExit: diff --git a/fHDHR/device/tuners/tuner.py b/fHDHR/device/tuners/tuner.py index a431012..09c7421 100644 --- a/fHDHR/device/tuners/tuner.py +++ b/fHDHR/device/tuners/tuner.py @@ -1,3 +1,4 @@ +import multiprocessing import threading import datetime @@ -17,17 +18,51 @@ class Tuner(): self.tuner_lock = threading.Lock() self.set_off_status() + if fhdhr.config.dict["fhdhr"]["address"] == "0.0.0.0": + self.location = ('http://127.0.0.1:%s' % str(fhdhr.config.dict["fhdhr"]["port"])) + else: + self.location = ('http://%s:%s' % (fhdhr.config.dict["fhdhr"]["address"], str(fhdhr.config.dict["fhdhr"]["port"]))) + + self.chanscan_url = "%s/api/channels?method=scan" % (self.location) + self.close_url = "%s/api/tuners?method=close&tuner=%s" % (self.location, str(self.number)) + + def channel_scan(self): + if self.tuner_lock.locked(): + self.fhdhr.logger.error("Tuner #%s is not available." % str(self.number)) + raise TunerError("804 - Tuner In Use") + + if self.status["status"] == "Scanning": + self.fhdhr.logger.info("Channel Scan Already In Progress!") + else: + + self.tuner_lock.acquire() + self.status["status"] = "Scanning" + self.fhdhr.logger.info("Tuner #%s Performing Channel Scan." % str(self.number)) + + if self.fhdhr.config.dict["main"]["thread_method"] in ["multiprocessing"]: + chanscan = multiprocessing.Process(target=self.runscan) + elif self.fhdhr.config.dict["main"]["thread_method"] in ["threading"]: + chanscan = threading.Thread(target=self.runscan) + if self.fhdhr.config.dict["main"]["thread_method"] in ["multiprocessing", "threading"]: + chanscan.start() + + def runscan(self): + self.fhdhr.web.session.get(self.chanscan_url) + self.fhdhr.logger.info("Requested Channel Scan Complete.") + self.fhdhr.web.session.get(self.close_url) + def add_downloaded_size(self, bytes_count): if "downloaded" in list(self.status.keys()): self.status["downloaded"] += bytes_count - def grab(self): + def grab(self, channel_number): if self.tuner_lock.locked(): self.fhdhr.logger.error("Tuner #" + str(self.number) + " is not available.") raise TunerError("804 - Tuner In Use") self.tuner_lock.acquire() self.status["status"] = "Acquired" - self.fhdhr.logger.info("Tuner #" + str(self.number) + " Acquired.") + self.status["channel"] = channel_number + self.fhdhr.logger.info("Tuner #%s Acquired." % str(self.number)) def close(self): self.set_off_status() diff --git a/fHDHR/http/__init__.py b/fHDHR/http/__init__.py index b96fc26..1fd3ec7 100644 --- a/fHDHR/http/__init__.py +++ b/fHDHR/http/__init__.py @@ -3,8 +3,9 @@ from flask import Flask, request from .pages import fHDHR_Pages from .files import fHDHR_Files +from .hdhr import fHDHR_HDHR +from .rmg import fHDHR_RMG from .api import fHDHR_API -from .watch import fHDHR_WATCH class fHDHR_HTTP_Server(): @@ -27,14 +28,18 @@ class fHDHR_HTTP_Server(): self.files = fHDHR_Files(fhdhr) self.add_endpoints(self.files, "files") + self.fhdhr.logger.info("Loading HTTP HDHR Endpoints.") + self.hdhr = fHDHR_HDHR(fhdhr) + self.add_endpoints(self.hdhr, "hdhr") + + self.fhdhr.logger.info("Loading HTTP RMG Endpoints.") + self.rmg = fHDHR_RMG(fhdhr) + self.add_endpoints(self.rmg, "rmg") + self.fhdhr.logger.info("Loading HTTP API Endpoints.") self.api = fHDHR_API(fhdhr) self.add_endpoints(self.api, "api") - self.fhdhr.logger.info("Loading HTTP Stream Endpoints.") - self.watch = fHDHR_WATCH(fhdhr) - self.add_endpoints(self.watch, "watch") - self.app.before_request(self.before_request) self.app.after_request(self.after_request) diff --git a/fHDHR/http/api/__init__.py b/fHDHR/http/api/__init__.py index 0e8e65f..359f9a6 100644 --- a/fHDHR/http/api/__init__.py +++ b/fHDHR/http/api/__init__.py @@ -1,12 +1,13 @@ +from .root_url import Root_URL + from .cluster import Cluster from .settings import Settings from .channels import Channels -from .lineup_post import Lineup_Post from .xmltv import xmlTV from .m3u import M3U from .epg import EPG -from .watch import Watch +from .tuners import Tuners from .debug import Debug_JSON from .images import Images @@ -17,14 +18,15 @@ class fHDHR_API(): def __init__(self, fhdhr): self.fhdhr = fhdhr + self.root_url = Root_URL(fhdhr) + self.cluster = Cluster(fhdhr) self.settings = Settings(fhdhr) self.channels = Channels(fhdhr) self.xmltv = xmlTV(fhdhr) self.m3u = M3U(fhdhr) self.epg = EPG(fhdhr) - self.watch = Watch(fhdhr) + self.tuners = Tuners(fhdhr) self.debug = Debug_JSON(fhdhr) - self.lineup_post = Lineup_Post(fhdhr) self.images = Images(fhdhr) diff --git a/fHDHR/http/api/channels.py b/fHDHR/http/api/channels.py index b242829..855bd1f 100644 --- a/fHDHR/http/api/channels.py +++ b/fHDHR/http/api/channels.py @@ -94,7 +94,7 @@ class Channels(): self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict) elif method == "scan": - self.fhdhr.device.station_scan.scan(waitfordone=True) + self.fhdhr.device.channels.get_channels(forceupdate=True) else: return "Invalid Method" diff --git a/fHDHR/http/api/root_url.py b/fHDHR/http/api/root_url.py new file mode 100644 index 0000000..68a8d75 --- /dev/null +++ b/fHDHR/http/api/root_url.py @@ -0,0 +1,32 @@ +from flask import redirect, request + + +class Root_URL(): + endpoints = ["/"] + endpoint_name = "page_root_html" + endpoint_methods = ["GET", "POST"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + + user_agent = request.headers.get('User-Agent') + + # Client Devices Discovering Device Information + if not user_agent or str(user_agent).lower().startswith("plexmediaserver"): + + # Plex Remote Media Grabber redirect + if self.fhdhr.config.dict["rmg"]["enabled"] and str(user_agent).lower().startswith("plexmediaserver"): + return redirect("/rmg") + + # Client Device is looking for HDHR type device + else: + return redirect("/hdhr/device.xml") + + # Anything Else is likely a Web Browser + else: + return redirect("/index") diff --git a/fHDHR/http/api/watch.py b/fHDHR/http/api/tuners.py similarity index 92% rename from fHDHR/http/api/watch.py rename to fHDHR/http/api/tuners.py index fdab038..b2e2fbd 100644 --- a/fHDHR/http/api/watch.py +++ b/fHDHR/http/api/tuners.py @@ -5,10 +5,9 @@ import uuid from fHDHR.exceptions import TunerError -class Watch(): - """Methods to create xmltv.xml""" - endpoints = ["/api/watch"] - endpoint_name = "api_watch" +class Tuners(): + endpoints = ["/api/tuners"] + endpoint_name = "api_tuners" endpoint_methods = ["GET", "POST"] def __init__(self, fhdhr): @@ -70,9 +69,9 @@ class Watch(): try: if not tuner_number: - tunernum = self.fhdhr.device.tuners.first_available() + tunernum = self.fhdhr.device.tuners.first_available(channel_number) else: - tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number) + tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, channel_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))) @@ -109,6 +108,14 @@ class Watch(): tuner = self.fhdhr.device.tuners.tuners[str(tuner_number)] tuner.close() + elif method == "scan": + + if not tuner_number: + self.fhdhr.device.tuners.tuner_scan() + else: + tuner = self.fhdhr.device.tuners.tuners[str(tuner_number)] + tuner.channel_scan() + else: return "%s Invalid Method" % method diff --git a/fHDHR/http/files/__init__.py b/fHDHR/http/files/__init__.py index 1821936..905566b 100644 --- a/fHDHR/http/files/__init__.py +++ b/fHDHR/http/files/__init__.py @@ -2,13 +2,7 @@ from .favicon_ico import Favicon_ICO from .style_css import Style_CSS - from .device_xml import Device_XML -from .lineup_xml import Lineup_XML - -from .discover_json import Discover_JSON -from .lineup_json import Lineup_JSON -from .lineup_status_json import Lineup_Status_JSON class fHDHR_Files(): @@ -18,10 +12,4 @@ class fHDHR_Files(): self.favicon = Favicon_ICO(fhdhr) self.style = Style_CSS(fhdhr) - self.device_xml = Device_XML(fhdhr) - self.lineup_xml = Lineup_XML(fhdhr) - - self.discover_json = Discover_JSON(fhdhr) - self.lineup_json = Lineup_JSON(fhdhr) - self.lineup_status_json = Lineup_Status_JSON(fhdhr) diff --git a/fHDHR/http/files/device_xml.py b/fHDHR/http/files/device_xml.py index b1e590c..3bca8cf 100644 --- a/fHDHR/http/files/device_xml.py +++ b/fHDHR/http/files/device_xml.py @@ -1,8 +1,4 @@ -from flask import Response, request -from io import BytesIO -import xml.etree.ElementTree - -from fHDHR.tools import sub_el +from flask import request, redirect class Device_XML(): @@ -17,31 +13,9 @@ class Device_XML(): def get(self, *args): - base_url = request.url_root[:-1] - - out = xml.etree.ElementTree.Element('root') - out.set('xmlns', "urn:schemas-upnp-org:device-1-0") - - sub_el(out, 'URLBase', base_url) - - specVersion_out = sub_el(out, 'specVersion') - sub_el(specVersion_out, 'major', "1") - sub_el(specVersion_out, 'minor', "0") - - device_out = sub_el(out, 'device') - sub_el(device_out, 'deviceType', "urn:schemas-upnp-org:device:MediaServer:1") - sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) - sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"]) - sub_el(device_out, 'modelName', self.fhdhr.config.dict["fhdhr"]["reporting_model"]) - sub_el(device_out, 'modelNumber', self.fhdhr.config.dict["fhdhr"]["reporting_model"]) - sub_el(device_out, 'serialNumber') - sub_el(device_out, 'UDN', "uuid:" + self.fhdhr.config.dict["main"]["uuid"]) - - fakefile = BytesIO() - fakefile.write(b'\n') - fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) - device_xml = fakefile.getvalue() - - return Response(status=200, - response=device_xml, - mimetype='application/xml') + user_agent = request.headers.get('User-Agent') + if (self.fhdhr.config.dict["rmg"]["enabled"] and + str(user_agent).lower().startswith("plexmediaserver")): + return redirect("/rmg/device.xml") + else: + return redirect("/hdhr/device.xml") diff --git a/fHDHR/http/hdhr/__init__.py b/fHDHR/http/hdhr/__init__.py new file mode 100644 index 0000000..a545826 --- /dev/null +++ b/fHDHR/http/hdhr/__init__.py @@ -0,0 +1,31 @@ + + +from .lineup_xml import Lineup_XML +from .discover_json import Discover_JSON +from .lineup_json import Lineup_JSON +from .lineup_status_json import Lineup_Status_JSON + +from .lineup_post import Lineup_Post +from .device_xml import HDHR_Device_XML + +from .auto import Auto +from .tuner import Tuner + + +class fHDHR_HDHR(): + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + self.lineup_post = Lineup_Post(fhdhr) + + self.device_xml = HDHR_Device_XML(fhdhr) + + self.auto = Auto(fhdhr) + self.tuner = Tuner(fhdhr) + + self.lineup_xml = Lineup_XML(fhdhr) + + self.discover_json = Discover_JSON(fhdhr) + self.lineup_json = Lineup_JSON(fhdhr) + self.lineup_status_json = Lineup_Status_JSON(fhdhr) diff --git a/fHDHR/http/watch/auto.py b/fHDHR/http/hdhr/auto.py similarity index 90% rename from fHDHR/http/watch/auto.py rename to fHDHR/http/hdhr/auto.py index 789cd5b..63e4664 100644 --- a/fHDHR/http/watch/auto.py +++ b/fHDHR/http/hdhr/auto.py @@ -3,8 +3,8 @@ import urllib.parse class Auto(): - endpoints = ['/auto/'] - endpoint_name = "watch_auto" + endpoints = ['/auto/', '/hdhr/auto/'] + endpoint_name = "hdhr_auto" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -16,7 +16,7 @@ class Auto(): method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) - redirect_url = "/api/watch?method=%s" % (method) + redirect_url = "/api/tuners?method=%s" % (method) if channel.startswith("v"): channel_number = channel.replace('v', '') diff --git a/fHDHR/http/hdhr/device_xml.py b/fHDHR/http/hdhr/device_xml.py new file mode 100644 index 0000000..6dc0250 --- /dev/null +++ b/fHDHR/http/hdhr/device_xml.py @@ -0,0 +1,53 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class HDHR_Device_XML(): + endpoints = ["/hdhr/device.xml"] + endpoint_name = "hdhr_device_xml" + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """Device.xml referenced from SSDP""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('root') + out.set('xmlns', "urn:schemas-upnp-org:device-1-0") + + sub_el(out, 'URLBase', "%s" % base_url) + + specVersion_out = sub_el(out, 'specVersion') + sub_el(specVersion_out, 'major', "1") + sub_el(specVersion_out, 'minor', "0") + + device_out = sub_el(out, 'device') + + sub_el(device_out, 'deviceType', "urn:schemas-upnp-org:device:MediaServer:1") + + sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) + sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"]) + sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) + sub_el(device_out, 'modelName', self.fhdhr.config.dict["fhdhr"]["reporting_model"]) + sub_el(device_out, 'modelNumber', self.fhdhr.config.internal["versions"]["fHDHR"]) + + sub_el(device_out, 'serialNumber') + + sub_el(device_out, 'UDN', "uuid:" + self.fhdhr.config.dict["main"]["uuid"]) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/files/discover_json.py b/fHDHR/http/hdhr/discover_json.py similarity index 85% rename from fHDHR/http/files/discover_json.py rename to fHDHR/http/hdhr/discover_json.py index 79d57ca..a31326a 100644 --- a/fHDHR/http/files/discover_json.py +++ b/fHDHR/http/hdhr/discover_json.py @@ -3,8 +3,8 @@ import json class Discover_JSON(): - endpoints = ["/discover.json"] - endpoint_name = "file_discover_json" + endpoints = ["/discover.json", "/hdhr/discover.json"] + endpoint_name = "hdhr_discover_json" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -25,8 +25,8 @@ class Discover_JSON(): "FirmwareVersion": self.fhdhr.config.dict["fhdhr"]["reporting_firmware_ver"], "DeviceID": self.fhdhr.config.dict["main"]["uuid"], "DeviceAuth": self.fhdhr.config.dict["fhdhr"]["device_auth"], - "BaseURL": base_url, - "LineupURL": base_url + "/lineup.json" + "BaseURL": "%s" % base_url, + "LineupURL": "%s/lineup.json" % base_url } discover_json = json.dumps(jsondiscover, indent=4) diff --git a/fHDHR/http/files/lineup_json.py b/fHDHR/http/hdhr/lineup_json.py similarity index 86% rename from fHDHR/http/files/lineup_json.py rename to fHDHR/http/hdhr/lineup_json.py index aaf9187..0638deb 100644 --- a/fHDHR/http/files/lineup_json.py +++ b/fHDHR/http/hdhr/lineup_json.py @@ -3,8 +3,8 @@ import json class Lineup_JSON(): - endpoints = ["/lineup.json"] - endpoint_name = "file_lineup_json" + endpoints = ["/lineup.json", "/hdhr/lineup.json"] + endpoint_name = "hdhr_lineup_json" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -23,7 +23,7 @@ class Lineup_JSON(): channel_obj = self.fhdhr.device.channels.list[fhdhr_id] if channel_obj.enabled or show == "found": lineup_dict = channel_obj.lineup_dict() - lineup_dict["URL"] = base_url + lineup_dict["URL"] + lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"]) if show == "found" and channel_obj.enabled: lineup_dict["Enabled"] = 1 elif show == "found" and not channel_obj.enabled: diff --git a/fHDHR/http/api/lineup_post.py b/fHDHR/http/hdhr/lineup_post.py similarity index 84% rename from fHDHR/http/api/lineup_post.py rename to fHDHR/http/hdhr/lineup_post.py index 014c6cd..adea967 100644 --- a/fHDHR/http/api/lineup_post.py +++ b/fHDHR/http/hdhr/lineup_post.py @@ -1,9 +1,11 @@ from flask import request, abort, Response +from fHDHR.exceptions import TunerError + class Lineup_Post(): - endpoints = ["/lineup.post"] - endpoint_name = "api_lineup_post" + endpoints = ["/lineup.post", "/hdhr/lineup.post"] + endpoint_name = "hdhr_lineup_post" endpoint_methods = ["POST"] def __init__(self, fhdhr): @@ -17,10 +19,14 @@ class Lineup_Post(): if 'scan' in list(request.args.keys()): if request.args['scan'] == 'start': - self.fhdhr.device.station_scan.scan(waitfordone=False) + try: + self.fhdhr.device.tuners.tuner_scan() + except TunerError as e: + self.fhdhr.logger.info(str(e)) return Response(status=200, mimetype='text/html') elif request.args['scan'] == 'abort': + self.fhdhr.device.tuners.stop_tuner_scan() return Response(status=200, mimetype='text/html') else: diff --git a/fHDHR/http/files/lineup_status_json.py b/fHDHR/http/hdhr/lineup_status_json.py similarity index 76% rename from fHDHR/http/files/lineup_status_json.py rename to fHDHR/http/hdhr/lineup_status_json.py index e5273f1..7c0abf5 100644 --- a/fHDHR/http/files/lineup_status_json.py +++ b/fHDHR/http/hdhr/lineup_status_json.py @@ -3,8 +3,8 @@ import json class Lineup_Status_JSON(): - endpoints = ["/lineup_status.json"] - endpoint_name = "file_lineup_status_json" + endpoints = ["/lineup_status.json", "/hdhr/lineup_status.json"] + endpoint_name = "hdhr_lineup_status_json" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -14,8 +14,13 @@ class Lineup_Status_JSON(): def get(self, *args): - station_scanning = self.fhdhr.device.station_scan.scanning() - if station_scanning: + tuner_status = self.fhdhr.device.tuners.status() + tuners_scanning = 0 + for tuner_number in list(tuner_status.keys()): + if tuner_status[tuner_number]["status"] == "Scanning": + tuners_scanning += 1 + + if tuners_scanning: jsonlineup = self.scan_in_progress() elif not len(self.fhdhr.device.channels.list): jsonlineup = self.scan_in_progress() diff --git a/fHDHR/http/files/lineup_xml.py b/fHDHR/http/hdhr/lineup_xml.py similarity index 94% rename from fHDHR/http/files/lineup_xml.py rename to fHDHR/http/hdhr/lineup_xml.py index 695c6f2..c5aa8c5 100644 --- a/fHDHR/http/files/lineup_xml.py +++ b/fHDHR/http/hdhr/lineup_xml.py @@ -6,8 +6,8 @@ from fHDHR.tools import sub_el class Lineup_XML(): - endpoints = ["/lineup.xml"] - endpoint_name = "file_lineup_xml" + endpoints = ["/lineup.xml", "/hdhr/lineup.xml"] + endpoint_name = "hdhr_lineup_xml" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/watch/tuner.py b/fHDHR/http/hdhr/tuner.py similarity index 89% rename from fHDHR/http/watch/tuner.py rename to fHDHR/http/hdhr/tuner.py index 418207f..072b79b 100644 --- a/fHDHR/http/watch/tuner.py +++ b/fHDHR/http/hdhr/tuner.py @@ -3,8 +3,8 @@ import urllib.parse class Tuner(): - endpoints = ['/tuner/'] - endpoint_name = "watch_tuner" + endpoints = ['/tuner/', '/hdhr/tuner/'] + endpoint_name = "hdhr_tuner" def __init__(self, fhdhr): self.fhdhr = fhdhr @@ -16,7 +16,7 @@ class Tuner(): method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str) - redirect_url = "/api/watch?method=%s" % (method) + redirect_url = "/api/tuners?method=%s" % (method) redirect_url += "&tuner=%s" % str(tuner_number) diff --git a/fHDHR/http/pages/__init__.py b/fHDHR/http/pages/__init__.py index 1f65ac8..95d6eea 100644 --- a/fHDHR/http/pages/__init__.py +++ b/fHDHR/http/pages/__init__.py @@ -5,7 +5,7 @@ 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 +from .tuners_html import Tuners_HTML from .xmltv_html import xmlTV_HTML from .version_html import Version_HTML from .diagnostics_html import Diagnostics_HTML @@ -24,7 +24,7 @@ class fHDHR_Pages(): self.channels_editor = Channels_Editor_HTML(fhdhr) self.guide_html = Guide_HTML(fhdhr) self.cluster_html = Cluster_HTML(fhdhr) - self.streams_html = Streams_HTML(fhdhr) + self.tuners_html = Tuners_HTML(fhdhr) self.xmltv_html = xmlTV_HTML(fhdhr) self.version_html = Version_HTML(fhdhr) self.diagnostics_html = Diagnostics_HTML(fhdhr) diff --git a/fHDHR/http/pages/diagnostics_html.py b/fHDHR/http/pages/diagnostics_html.py index 456f799..04eda62 100644 --- a/fHDHR/http/pages/diagnostics_html.py +++ b/fHDHR/http/pages/diagnostics_html.py @@ -13,15 +13,113 @@ class Diagnostics_HTML(): def get(self, *args): - # a list of 2 part lists containing button information - button_list = [ - ["debug.json", "/api/debug"], - ["device.xml", "device.xml"], - ["discover.json", "discover.json"], - ["lineup.json", "lineup.json"], - ["lineup.xml", "lineup.xml"], - ["lineup_status.json", "lineup_status.json"], - ["cluster.json", "/api/cluster?method=get"] - ] + base_url = request.url_root[:-1] + + button_list = [] + + button_list.append({ + "label": "Debug Json", + "hdhr": None, + "rmg": None, + "other": "/api/debug", + }) + + button_list.append({ + "label": "Cluster Json", + "hdhr": None, + "rmg": None, + "other": "/api/cluster?method=get", + }) + + button_list.append({ + "label": "Lineup XML", + "hdhr": "/lineup.xml", + "rmg": None, + "other": None, + }) + + button_list.append({ + "label": "Lineup JSON", + "hdhr": "/hdhr/lineup.json", + "rmg": None, + "other": None, + }) + + button_list.append({ + "label": "Lineup Status", + "hdhr": "/hdhr/lineup_status.json", + "rmg": None, + "other": None, + }) + + button_list.append({ + "label": "Discover Json", + "hdhr": "/hdhr/discover.json", + "rmg": None, + "other": None, + }) + + button_list.append({ + "label": "Device XML", + "hdhr": "/hdhr/device.xml", + "rmg": "/rmg/device.xml", + "other": None, + }) + + button_list.append({ + "label": "RMG Identification XML", + "hdhr": "", + "rmg": "/rmg", + "other": None, + }) + + button_list.append({ + "label": "RMG Devices Discover", + "hdhr": "", + "rmg": "/rmg/devices/discover", + "other": None, + }) + + button_list.append({ + "label": "RMG Devices Probe", + "hdhr": "", + "rmg": "/rmg/devices/probe?uri=%s" % base_url, + "other": None, + }) + + button_list.append({ + "label": "RMG Devices by DeviceKey", + "hdhr": "", + "rmg": "/rmg/devices/%s" % self.fhdhr.config.dict["main"]["uuid"], + "other": None, + }) + + button_list.append({ + "label": "RMG Channels by DeviceKey", + "hdhr": "", + "rmg": "/rmg/devices/%s/channels" % self.fhdhr.config.dict["main"]["uuid"], + "other": None, + }) + + button_list.append({ + "label": "RMG Scanners by DeviceKey", + "hdhr": "", + "rmg": "/rmg/devices/%s/scanners" % self.fhdhr.config.dict["main"]["uuid"], + "other": None, + }) + + button_list.append({ + "label": "RMG Networks by DeviceKey", + "hdhr": "", + "rmg": "/rmg/devices/%s/networks" % self.fhdhr.config.dict["main"]["uuid"], + "other": None, + }) + + button_list.append({ + "label": "RMG Scan by DeviceKey", + "hdhr": "", + "rmg": "/rmg/devices/%s/scan" % self.fhdhr.config.dict["main"]["uuid"], + "other": None, + }) return render_template('diagnostics.html', request=request, fhdhr=self.fhdhr, button_list=button_list) diff --git a/fHDHR/http/pages/index_html.py b/fHDHR/http/pages/index_html.py index 4392472..9da681a 100644 --- a/fHDHR/http/pages/index_html.py +++ b/fHDHR/http/pages/index_html.py @@ -2,8 +2,8 @@ from flask import request, render_template class Index_HTML(): - endpoints = ["/", "/index", "/index.html"] - endpoint_name = "page_root_html" + endpoints = ["/index", "/index.html"] + endpoint_name = "page_index_html" def __init__(self, fhdhr): self.fhdhr = fhdhr diff --git a/fHDHR/http/pages/streams_html.py b/fHDHR/http/pages/tuners_html.py similarity index 75% rename from fHDHR/http/pages/streams_html.py rename to fHDHR/http/pages/tuners_html.py index 5edd00c..787076b 100644 --- a/fHDHR/http/pages/streams_html.py +++ b/fHDHR/http/pages/tuners_html.py @@ -3,8 +3,8 @@ from flask import request, render_template from fHDHR.tools import humanized_filesize -class Streams_HTML(): - endpoints = ["/streams", "/streams.html"] +class Tuners_HTML(): + endpoints = ["/tuners", "/tuners.html"] endpoint_name = "page_streams_html" def __init__(self, fhdhr): @@ -17,6 +17,7 @@ class Streams_HTML(): tuner_list = [] tuner_status = self.fhdhr.device.tuners.status() + tuner_scanning = 0 for tuner in list(tuner_status.keys()): tuner_dict = { "number": str(tuner), @@ -27,7 +28,9 @@ class Streams_HTML(): tuner_dict["method"] = tuner_status[tuner]["method"] tuner_dict["play_duration"] = str(tuner_status[tuner]["Play Time"]) tuner_dict["downloaded"] = humanized_filesize(tuner_status[tuner]["downloaded"]) + elif tuner_status[tuner]["status"] == "Scanning": + tuner_scanning += 1 tuner_list.append(tuner_dict) - return render_template('streams.html', request=request, fhdhr=self.fhdhr, tuner_list=tuner_list) + return render_template('tuners.html', request=request, fhdhr=self.fhdhr, tuner_list=tuner_list, tuner_scanning=tuner_scanning) diff --git a/fHDHR/http/rmg/__init__.py b/fHDHR/http/rmg/__init__.py new file mode 100644 index 0000000..bccf4e0 --- /dev/null +++ b/fHDHR/http/rmg/__init__.py @@ -0,0 +1,30 @@ + +from .rmg_ident_xml import RMG_Ident_XML +from .device_xml import RMG_Device_XML +from .devices_discover import RMG_Devices_Discover +from .devices_probe import RMG_Devices_Probe +from .devices_devicekey import RMG_Devices_DeviceKey +from .devices_devicekey_channels import RMG_Devices_DeviceKey_Channels +from .devices_devicekey_scanners import RMG_Devices_DeviceKey_Scanners +from .devices_devicekey_networks import RMG_Devices_DeviceKey_Networks +from .devices_devicekey_scan import RMG_Devices_DeviceKey_Scan +from .devices_devicekey_prefs import RMG_Devices_DeviceKey_Prefs +from .devices_devicekey_media import RMG_Devices_DeviceKey_Media + + +class fHDHR_RMG(): + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + self.rmg_ident_xml = RMG_Ident_XML(fhdhr) + self.device_xml = RMG_Device_XML(fhdhr) + self.devices_discover = RMG_Devices_Discover(fhdhr) + self.devices_probe = RMG_Devices_Probe(fhdhr) + self.devices_devicekey = RMG_Devices_DeviceKey(fhdhr) + self.devices_devicekey_channels = RMG_Devices_DeviceKey_Channels(fhdhr) + self.devices_devicekey_scanners = RMG_Devices_DeviceKey_Scanners(fhdhr) + self.devices_devicekey_networks = RMG_Devices_DeviceKey_Networks(fhdhr) + self.devices_devicekey_scan = RMG_Devices_DeviceKey_Scan(fhdhr) + self.devices_devicekey_prefs = RMG_Devices_DeviceKey_Prefs(fhdhr) + self.devices_devicekey_media = RMG_Devices_DeviceKey_Media(fhdhr) diff --git a/fHDHR/http/rmg/device_xml.py b/fHDHR/http/rmg/device_xml.py new file mode 100644 index 0000000..8740bc9 --- /dev/null +++ b/fHDHR/http/rmg/device_xml.py @@ -0,0 +1,58 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Device_XML(): + endpoints = ["/rmg/device.xml"] + endpoint_name = "rmg_device_xml" + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """Device.xml referenced from SSDP""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('root') + out.set('xmlns', "urn:schemas-upnp-org:device-1-0") + + specVersion_out = sub_el(out, 'specVersion') + sub_el(specVersion_out, 'major', "1") + sub_el(specVersion_out, 'minor', "0") + + device_out = sub_el(out, 'device') + + sub_el(device_out, 'deviceType', "urn:plex-tv:device:Media:1") + + sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) + sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"]) + sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) + sub_el(device_out, 'modelName', self.fhdhr.config.dict["fhdhr"]["reporting_model"]) + sub_el(device_out, 'modelNumber', self.fhdhr.config.internal["versions"]["fHDHR"]) + + sub_el(device_out, 'modelDescription', self.fhdhr.config.dict["fhdhr"]["friendlyname"]) + sub_el(device_out, 'modelURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"]) + + serviceList_out = sub_el(device_out, 'serviceList') + service_out = sub_el(serviceList_out, 'service') + sub_el(out, 'URLBase', "%s" % base_url) + sub_el(service_out, 'serviceType', "urn:plex-tv:service:MediaGrabber:1") + sub_el(service_out, 'serviceId', "urn:plex-tv:serviceId:MediaGrabber") + + sub_el(device_out, 'UDN', "uuid:%s" % self.fhdhr.config.dict["main"]["uuid"]) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/devices_devicekey.py b/fHDHR/http/rmg/devices_devicekey.py new file mode 100644 index 0000000..697b6bc --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey.py @@ -0,0 +1,94 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_DeviceKey(): + endpoints = ["/devices/", "/rmg/devices/"] + endpoint_name = "rmg_devices_devicekey" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """Returns the identity, capabilities, and current status of the devices and each of its tuners.""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('MediaContainer') + if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + out.set('size', "1") + device_out = sub_el(out, 'Device', + key=self.fhdhr.config.dict["main"]["uuid"], + make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], + model=self.fhdhr.config.dict["fhdhr"]["reporting_model"], + modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], + protocol="livetv", + status="alive", + title=self.fhdhr.config.dict["fhdhr"]["friendlyname"], + tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]), + uri=base_url, + uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"], + ) + + tuner_status = self.fhdhr.device.tuners.status() + + for tuner_number in list(tuner_status.keys()): + tuner_dict = tuner_status[tuner_number] + + # Idle + if tuner_dict["status"] in ["Inactive"]: + sub_el(device_out, 'Tuner', + index=tuner_number, + status="idle", + ) + + # Streaming + elif tuner_dict["status"] in ["Active", "Acquired"]: + sub_el(device_out, 'Tuner', + index=tuner_number, + status="streaming", + channelIdentifier="id://%s" % tuner_dict["channel"], + signalStrength="100", + signalQuality="100", + symbolQuality="100", + lock="1", + ) + + # Scanning + elif tuner_dict["status"] in ["Scanning"]: + sub_el(device_out, 'Tuner', + index=tuner_number, + status="scanning", + progress="99", + channelsFound=str(len(self.fhdhr.device.channels.list)), + ) + + # TODO networksScanned + elif tuner_dict["status"] in ["networksScanned"]: + sub_el(device_out, 'Tuner', + index=tuner_number, + status="networksScanned", + ) + + # Error + elif tuner_dict["status"] in ["Error"]: + sub_el(device_out, 'Tuner', + index=tuner_number, + status="error", + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/devices_devicekey_channels.py b/fHDHR/http/rmg/devices_devicekey_channels.py new file mode 100644 index 0000000..8122f93 --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey_channels.py @@ -0,0 +1,47 @@ +from flask import Response +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_DeviceKey_Channels(): + endpoints = ["/devices//channels", "/rmg/devices//channels"] + endpoint_name = "rmg_devices_devicekey_channels" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """Returns the current channels.""" + + out = xml.etree.ElementTree.Element('MediaContainer') + if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + out.set('size', str(len(self.fhdhr.device.channels.list))) + for fhdhr_id in list(self.fhdhr.device.channels.list.keys()): + channel_obj = self.fhdhr.device.channels.list[fhdhr_id] + if channel_obj.enabled: + sub_el(out, 'Channel', + drm="0", + channelIdentifier="id://%s" % channel_obj.dict["number"], + name=channel_obj.dict["name"], + origin=channel_obj.dict["callsign"], + number=str(channel_obj.dict["number"]), + type="tv", + # TODO param + signalStrength="100", + signalQuality="100", + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/devices_devicekey_media.py b/fHDHR/http/rmg/devices_devicekey_media.py new file mode 100644 index 0000000..6cf7c7b --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey_media.py @@ -0,0 +1,31 @@ +from flask import request, redirect +import urllib.parse + + +class RMG_Devices_DeviceKey_Media(): + endpoints = ["/devices//media/", "/rmg/devices//media/"] + endpoint_name = "rmg_devices_devicekey_media" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, channel, *args): + return self.get(devicekey, channel, *args) + + def get(self, devicekey, channel, *args): + + param = request.args.get('method', default=None, type=str) + self.fhdhr.logger.debug("param:%s" % param) + + method = self.fhdhr.config.dict["fhdhr"]["stream_type"] + + redirect_url = "/api/tuners?method=%s" % (method) + + if str(channel).startswith('id://'): + channel = str(channel).replace('id://', '') + redirect_url += "&channel=%s" % str(channel) + + redirect_url += "&accessed=%s" % urllib.parse.quote(request.url) + + return redirect(redirect_url) diff --git a/fHDHR/http/rmg/devices_devicekey_networks.py b/fHDHR/http/rmg/devices_devicekey_networks.py new file mode 100644 index 0000000..c635965 --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey_networks.py @@ -0,0 +1,38 @@ +from flask import Response +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_DeviceKey_Networks(): + endpoints = ["/devices//networks", "/rmg/devices//networks"] + endpoint_name = "rmg_devices_devicekey_networks" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """In some cases, channel scanning is a two-step process, where the first stage consists of scanning for networks (this is called "fast scan").""" + + out = xml.etree.ElementTree.Element('MediaContainer') + if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + out.set('size', "1") + + sub_el(out, 'Network', + key="1", + title="fHDHR" + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/devices_devicekey_prefs.py b/fHDHR/http/rmg/devices_devicekey_prefs.py new file mode 100644 index 0000000..90a1e06 --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey_prefs.py @@ -0,0 +1,18 @@ +from flask import Response + + +class RMG_Devices_DeviceKey_Prefs(): + endpoints = ["/devices//prefs", "/rmg/devices//prefs"] + endpoint_name = "rmg_devices_devicekey_prefs" + endpoint_methods = ["GET", "PUT"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """Prefs sent back from Plex in Key-Pair format""" + + return Response(status=200) diff --git a/fHDHR/http/rmg/devices_devicekey_scan.py b/fHDHR/http/rmg/devices_devicekey_scan.py new file mode 100644 index 0000000..f7d3aea --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey_scan.py @@ -0,0 +1,65 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + + +class RMG_Devices_DeviceKey_Scan(): + endpoints = ["/devices//scan", "/rmg/devices//scan"] + endpoint_name = "rmg_devices_devicekey_scan" + endpoint_methods = ["GET", "POST", "DELETE"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """Starts a background channel scan.""" + + if request.method in ["GET", "POST"]: + + network = request.args.get('network', default=None, type=str) + source = request.args.get('source', default=None, type=int) + provider = request.args.get('provider', default=1, type=int) + + self.fhdhr.logger.debug("Scan Requested network:%s, source:%s, provider:%s" % (network, source, provider)) + + out = xml.etree.ElementTree.Element('MediaContainer') + if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + + tuner_status = self.fhdhr.device.tuners.status() + tuner_scanning = 0 + for tuner in list(tuner_status.keys()): + if tuner_status[tuner]["status"] == "Scanning": + tuner_scanning += 1 + + if tuner_scanning: + out.set('status', "1") + out.set('message', "Scanning") + else: + out.set('status', "0") + out.set('message', "Not Scanning") + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') + + elif request.method in ["DELETE"]: + + out = xml.etree.ElementTree.Element('MediaContainer') + if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + + self.fhdhr.device.tuners.stop_tuner_scan() + out.set('status', "0") + out.set('message', "Scan Aborted") + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() diff --git a/fHDHR/http/rmg/devices_devicekey_scanners.py b/fHDHR/http/rmg/devices_devicekey_scanners.py new file mode 100644 index 0000000..4a2d4ec --- /dev/null +++ b/fHDHR/http/rmg/devices_devicekey_scanners.py @@ -0,0 +1,48 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_DeviceKey_Scanners(): + endpoints = ["/devices//scanners", "/rmg/devices//scanners"] + endpoint_name = "rmg_devices_devicekey_scanners" + endpoint_methods = ["GET"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, devicekey, *args): + return self.get(devicekey, *args) + + def get(self, devicekey, *args): + """ascertain which type of scanners are supported.""" + + method = request.args.get('type', default="0", type=str) + # 0 (atsc), 1 (cqam), 2 (dvb-s), 3 (iptv), 4 (virtual), 5 (dvb-t), 6 (dvb-c), 7 (isdbt) + + out = xml.etree.ElementTree.Element('MediaContainer') + if devicekey == self.fhdhr.config.dict["main"]["uuid"]: + if method == "0": + out.set('size', "1") + out.set('simultaneousScanners', "1") + + scanner_out = sub_el(out, 'Scanner', + type="atsc", + # TODO country + ) + sub_el(scanner_out, 'Setting', + id="provider", + type="text", + enumValues=self.fhdhr.config.dict["main"]["servicename"] + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/devices_discover.py b/fHDHR/http/rmg/devices_discover.py new file mode 100644 index 0000000..82daca1 --- /dev/null +++ b/fHDHR/http/rmg/devices_discover.py @@ -0,0 +1,49 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_Discover(): + endpoints = ["/devices/discover", "/rmg/devices/discover"] + endpoint_name = "rmg_devices_discover" + endpoint_methods = ["GET", "POST"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """This endpoint requests the grabber attempt to discover any devices it can, and it returns zero or more devices.""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('MediaContainer') + out.set('size', "1") + sub_el(out, 'Device', + key=self.fhdhr.config.dict["main"]["uuid"], + make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], + model=self.fhdhr.config.dict["fhdhr"]["reporting_model"], + modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], + protocol="livetv", + status="alive", + title=self.fhdhr.config.dict["fhdhr"]["friendlyname"], + tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]), + uri=base_url, + uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"], + thumb="favicon.ico", + interface='network' + # TODO add preferences + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/devices_probe.py b/fHDHR/http/rmg/devices_probe.py new file mode 100644 index 0000000..e59ec2d --- /dev/null +++ b/fHDHR/http/rmg/devices_probe.py @@ -0,0 +1,51 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Devices_Probe(): + endpoints = ["/devices/probe", "/rmg/devices/probe"] + endpoint_name = "rmg_devices_probe" + endpoint_methods = ["GET", "POST"] + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """Probes a specific URI for a network device, and returns a device, if it exists at the given URI.""" + + base_url = request.url_root[:-1] + + uri = request.args.get('uri', default=None, type=str) + + out = xml.etree.ElementTree.Element('MediaContainer') + out.set('size', "1") + if uri == base_url: + sub_el(out, 'Device', + key=self.fhdhr.config.dict["main"]["uuid"], + make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"], + model=self.fhdhr.config.dict["fhdhr"]["reporting_model"], + modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"], + protocol="livetv", + status="alive", + title=self.fhdhr.config.dict["fhdhr"]["friendlyname"], + tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]), + uri=base_url, + uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"], + thumb="favicon.ico", + interface='network' + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/rmg/rmg_ident_xml.py b/fHDHR/http/rmg/rmg_ident_xml.py new file mode 100644 index 0000000..8b5749b --- /dev/null +++ b/fHDHR/http/rmg/rmg_ident_xml.py @@ -0,0 +1,38 @@ +from flask import Response, request +from io import BytesIO +import xml.etree.ElementTree + +from fHDHR.tools import sub_el + + +class RMG_Ident_XML(): + endpoints = ["/rmg", "/rmg/"] + endpoint_name = "rmg_ident_xml" + + def __init__(self, fhdhr): + self.fhdhr = fhdhr + + def __call__(self, *args): + return self.get(*args) + + def get(self, *args): + """Provides general information about the media grabber""" + + base_url = request.url_root[:-1] + + out = xml.etree.ElementTree.Element('MediaContainer') + sub_el(out, 'MediaGrabber', + identifier="tv.plex.grabbers.fHDHR", + title=str(self.fhdhr.config.dict["fhdhr"]["friendlyname"]), + protocols="livetv", + icon="%s/favicon.ico" % base_url + ) + + fakefile = BytesIO() + fakefile.write(b'\n') + fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8')) + device_xml = fakefile.getvalue() + + return Response(status=200, + response=device_xml, + mimetype='application/xml') diff --git a/fHDHR/http/watch/__init__.py b/fHDHR/http/watch/__init__.py deleted file mode 100644 index 22bd4df..0000000 --- a/fHDHR/http/watch/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ - -from .auto import Auto -from .tuner import Tuner - - -class fHDHR_WATCH(): - - def __init__(self, fhdhr): - self.fhdhr = fhdhr - - self.auto = Auto(fhdhr) - self.tuner = Tuner(fhdhr) diff --git a/fHDHR/tools/__init__.py b/fHDHR/tools/__init__.py index 240b14b..0295ad2 100644 --- a/fHDHR/tools/__init__.py +++ b/fHDHR/tools/__init__.py @@ -19,8 +19,8 @@ def is_docker(): return False -def sub_el(parent, name, text=None, **kwargs): - el = xml.etree.ElementTree.SubElement(parent, name, **kwargs) +def sub_el(parent, sub_el_item_name, text=None, **kwargs): + el = xml.etree.ElementTree.SubElement(parent, sub_el_item_name, **kwargs) if text: el.text = text return el
ItemHDHRRMGNon-Specific
{{ button_item["label"] }}
{{ tuner_dict["number"] }} {{ tuner_dict["status"] }}{{ tuner_dict["channel_number"] }}N/A{{ tuner_dict["method"] }} {{ tuner_dict["play_duration"] }} {{ tuner_dict["downloaded"] }}N/A N/A N/AN/A
- {% if tuner_dict["status"] in ["Active", "Acquired"] %} - + {% if tuner_dict["status"] != "Inactive" %} + + {% endif %} + {% if not tuner_scanning and tuner_dict["status"] == "Inactive" %} + {% endif %}