mirror of
https://github.com/fHDHR/fHDHR_NextPVR.git
synced 2025-12-06 06:26:57 -05:00
Implement Plex Remote Media Grabber
This commit is contained in:
parent
d4dacc5f3b
commit
34ca98881f
39
data/internal_config/database.json
Normal file
39
data/internal_config/database.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
9
data/internal_config/epg.json
Normal file
9
data/internal_config/epg.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"epg":{
|
||||
"images":{
|
||||
"value": "pass",
|
||||
"config_file": true,
|
||||
"config_web": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
data/internal_config/logging.json
Normal file
9
data/internal_config/logging.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"logging":{
|
||||
"level":{
|
||||
"value": "WARNING",
|
||||
"config_file": true,
|
||||
"config_web": true
|
||||
}
|
||||
}
|
||||
}
|
||||
19
data/internal_config/main.json
Normal file
19
data/internal_config/main.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
9
data/internal_config/rmg.json
Normal file
9
data/internal_config/rmg.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"rmg":{
|
||||
"enabled":{
|
||||
"value": true,
|
||||
"config_file": true,
|
||||
"config_web": false
|
||||
}
|
||||
}
|
||||
}
|
||||
33
data/internal_config/streaming.json
Normal file
33
data/internal_config/streaming.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@
|
||||
<button class="pull-left" onclick="OpenLink('/channels')">Channels</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/guide')">Guide</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/cluster')">Cluster</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/streams')">Streams</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/tuners')">Tuners</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/xmltv')">xmltv</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/version')">Version</a></button>
|
||||
<button class="pull-left" onclick="OpenLink('/diagnostics')">Diagnostics</a></button>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<h4 style="text-align: center;">{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels</h4>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<button onclick="OpenLink('/api/channels?method=scan&redirect=%2Fchannels')">Force Channel Update</a></button><p> Note: This may take some time.</p>
|
||||
<button onclick="OpenLink('/api/tuners?method=scan&redirect=%2Fchannels')">Force Channel Update</a></button><p> Note: This may take some time.</p>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
||||
@ -2,10 +2,35 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h4 style="text-align: center;">fHDHR Diagnostic Links</h4>
|
||||
|
||||
<table class="center" style="width:100%">
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>HDHR</th>
|
||||
<th>RMG</th>
|
||||
<th>Non-Specific</th>
|
||||
</tr>
|
||||
|
||||
{% for button_item in button_list %}
|
||||
<div style="text-align: center;">
|
||||
<p><button onclick="OpenLink('{{ button_item[1] }}')">{{ button_item[0] }}</a></button></p>
|
||||
</div>
|
||||
<tr>
|
||||
<td>{{ button_item["label"] }}</td>
|
||||
{% if button_item["hdhr"] %}
|
||||
<td><button onclick="OpenLink('{{ button_item["hdhr"] }}')">{{ button_item["label"] }}</a></button></td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
{% if button_item["rmg"] %}
|
||||
<td><button onclick="OpenLink('{{ button_item["rmg"] }}')">{{ button_item["label"] }}</a></button></td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
{% if button_item["other"] %}
|
||||
<td><button onclick="OpenLink('{{ button_item["other"] }}')">{{ button_item["label"] }}</a></button></td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -19,8 +19,12 @@
|
||||
<tr>
|
||||
<td>{{ tuner_dict["number"] }}</td>
|
||||
<td>{{ tuner_dict["status"] }}</td>
|
||||
{% if tuner_dict["status"] == "Active" %}
|
||||
{% if tuner_dict["status"] in ["Active", "Acquired"] %}
|
||||
<td>{{ tuner_dict["channel_number"] }}</td>
|
||||
{% else %}
|
||||
<td>N/A</td>
|
||||
{% endif %}
|
||||
{% if tuner_dict["status"] == "Active" %}
|
||||
<td>{{ tuner_dict["method"] }}</td>
|
||||
<td>{{ tuner_dict["play_duration"] }}</td>
|
||||
<td>{{ tuner_dict["downloaded"] }}</td>
|
||||
@ -28,12 +32,14 @@
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<div>
|
||||
{% if tuner_dict["status"] in ["Active", "Acquired"] %}
|
||||
<button onclick="OpenLink('/api/watch?method=close&tuner={{ tuner_dict["number"] }}&redirect=%2Fstreams')">Close</a></button>
|
||||
{% if tuner_dict["status"] != "Inactive" %}
|
||||
<button onclick="OpenLink('/api/tuners?method=close&tuner={{ tuner_dict["number"] }}&redirect=%2Ftuners')">Close</a></button>
|
||||
{% endif %}
|
||||
{% if not tuner_scanning and tuner_dict["status"] == "Inactive" %}
|
||||
<button onclick="OpenLink('/api/tuners?method=scan&tuner={{ tuner_dict["number"] }}&redirect=%2Ftuners')">Channel Scan</a></button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"
|
||||
44
fHDHR/device/ssdp/hdhr_ssdp.py
Normal file
44
fHDHR/device/ssdp/hdhr_ssdp.py
Normal file
@ -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")
|
||||
44
fHDHR/device/ssdp/rmg_ssdp.py
Normal file
44
fHDHR/device/ssdp/rmg_ssdp.py
Normal file
@ -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")
|
||||
16
fHDHR/device/ssdp/ssdp_detect.py
Normal file
16
fHDHR/device/ssdp/ssdp_detect.py
Normal file
@ -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 []
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
32
fHDHR/http/api/root_url.py
Normal file
32
fHDHR/http/api/root_url.py
Normal file
@ -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")
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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'<?xml version="1.0" encoding="UTF-8"?>\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")
|
||||
|
||||
31
fHDHR/http/hdhr/__init__.py
Normal file
31
fHDHR/http/hdhr/__init__.py
Normal file
@ -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)
|
||||
@ -3,8 +3,8 @@ import urllib.parse
|
||||
|
||||
|
||||
class Auto():
|
||||
endpoints = ['/auto/<channel>']
|
||||
endpoint_name = "watch_auto"
|
||||
endpoints = ['/auto/<channel>', '/hdhr/auto/<channel>']
|
||||
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', '')
|
||||
53
fHDHR/http/hdhr/device_xml.py
Normal file
53
fHDHR/http/hdhr/device_xml.py
Normal file
@ -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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
@ -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:
|
||||
@ -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()
|
||||
@ -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
|
||||
@ -3,8 +3,8 @@ import urllib.parse
|
||||
|
||||
|
||||
class Tuner():
|
||||
endpoints = ['/tuner<tuner_number>/<channel>']
|
||||
endpoint_name = "watch_tuner"
|
||||
endpoints = ['/tuner<tuner_number>/<channel>', '/hdhr/tuner<tuner_number>/<channel>']
|
||||
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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
30
fHDHR/http/rmg/__init__.py
Normal file
30
fHDHR/http/rmg/__init__.py
Normal file
@ -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)
|
||||
58
fHDHR/http/rmg/device_xml.py
Normal file
58
fHDHR/http/rmg/device_xml.py
Normal file
@ -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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
94
fHDHR/http/rmg/devices_devicekey.py
Normal file
94
fHDHR/http/rmg/devices_devicekey.py
Normal file
@ -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/<devicekey>", "/rmg/devices/<devicekey>"]
|
||||
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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
47
fHDHR/http/rmg/devices_devicekey_channels.py
Normal file
47
fHDHR/http/rmg/devices_devicekey_channels.py
Normal file
@ -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/<devicekey>/channels", "/rmg/devices/<devicekey>/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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
31
fHDHR/http/rmg/devices_devicekey_media.py
Normal file
31
fHDHR/http/rmg/devices_devicekey_media.py
Normal file
@ -0,0 +1,31 @@
|
||||
from flask import request, redirect
|
||||
import urllib.parse
|
||||
|
||||
|
||||
class RMG_Devices_DeviceKey_Media():
|
||||
endpoints = ["/devices/<devicekey>/media/<channel>", "/rmg/devices/<devicekey>/media/<channel>"]
|
||||
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)
|
||||
38
fHDHR/http/rmg/devices_devicekey_networks.py
Normal file
38
fHDHR/http/rmg/devices_devicekey_networks.py
Normal file
@ -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/<devicekey>/networks", "/rmg/devices/<devicekey>/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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
18
fHDHR/http/rmg/devices_devicekey_prefs.py
Normal file
18
fHDHR/http/rmg/devices_devicekey_prefs.py
Normal file
@ -0,0 +1,18 @@
|
||||
from flask import Response
|
||||
|
||||
|
||||
class RMG_Devices_DeviceKey_Prefs():
|
||||
endpoints = ["/devices/<devicekey>/prefs", "/rmg/devices/<devicekey>/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)
|
||||
65
fHDHR/http/rmg/devices_devicekey_scan.py
Normal file
65
fHDHR/http/rmg/devices_devicekey_scan.py
Normal file
@ -0,0 +1,65 @@
|
||||
from flask import Response, request
|
||||
from io import BytesIO
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
class RMG_Devices_DeviceKey_Scan():
|
||||
endpoints = ["/devices/<devicekey>/scan", "/rmg/devices/<devicekey>/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'<?xml version="1.0" encoding="UTF-8"?>\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'<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
|
||||
device_xml = fakefile.getvalue()
|
||||
48
fHDHR/http/rmg/devices_devicekey_scanners.py
Normal file
48
fHDHR/http/rmg/devices_devicekey_scanners.py
Normal file
@ -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/<devicekey>/scanners", "/rmg/devices/<devicekey>/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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
49
fHDHR/http/rmg/devices_discover.py
Normal file
49
fHDHR/http/rmg/devices_discover.py
Normal file
@ -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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
51
fHDHR/http/rmg/devices_probe.py
Normal file
51
fHDHR/http/rmg/devices_probe.py
Normal file
@ -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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
38
fHDHR/http/rmg/rmg_ident_xml.py
Normal file
38
fHDHR/http/rmg/rmg_ident_xml.py
Normal file
@ -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'<?xml version="1.0" encoding="UTF-8"?>\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')
|
||||
@ -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)
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user