1
0
mirror of https://github.com/fHDHR/fHDHR_NextPVR.git synced 2025-12-06 08:26:57 -05:00

Implement Plex Remote Media Grabber

This commit is contained in:
deathbybandaid 2020-12-04 08:25:48 -05:00
parent d4dacc5f3b
commit 34ca98881f
56 changed files with 1234 additions and 344 deletions

View 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
}
}
}

View File

@ -0,0 +1,9 @@
{
"epg":{
"images":{
"value": "pass",
"config_file": true,
"config_web": true
}
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,9 @@
{
"logging":{
"level":{
"value": "WARNING",
"config_file": true,
"config_web": true
}
}
}

View 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
}
}
}

View File

@ -0,0 +1,9 @@
{
"rmg":{
"enabled":{
"value": true,
"config_file": true,
"config_web": false
}
}
}

View 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
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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>

View File

@ -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)

View File

@ -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:

View File

@ -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,7 +109,18 @@ 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
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)
@ -137,7 +134,8 @@ class SSDPServer():
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"

View 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")

View 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")

View 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 []

View File

@ -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

View File

@ -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()
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

View File

@ -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:

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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"

View 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")

View File

@ -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

View File

@ -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)

View File

@ -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")

View 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)

View File

@ -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', '')

View 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')

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View 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)

View 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')

View 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')

View 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')

View 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)

View 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')

View 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)

View 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()

View 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')

View 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')

View 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')

View 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')

View File

@ -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)

View File

@ -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