mirror of
https://github.com/fHDHR/fHDHR_NextPVR.git
synced 2025-12-06 16:16:58 -05:00
Merge pull request #47 from deathbybandaid/dev
EPG and VLC and Tuner Improvements
This commit is contained in:
commit
43493258e8
@ -20,6 +20,10 @@ images = pass
|
|||||||
ffmpeg_path = ffmpeg
|
ffmpeg_path = ffmpeg
|
||||||
bytes_per_read = 1152000
|
bytes_per_read = 1152000
|
||||||
|
|
||||||
|
[vlc]
|
||||||
|
vlc_path = cvlc
|
||||||
|
bytes_per_read = 1152000
|
||||||
|
|
||||||
[direct_stream]
|
[direct_stream]
|
||||||
chunksize = 1048576
|
chunksize = 1048576
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import fHDHR.tools
|
|||||||
fHDHR_VERSION = "v0.4.0-beta"
|
fHDHR_VERSION = "v0.4.0-beta"
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_OBJ():
|
class fHDHR_INT_OBJ():
|
||||||
|
|
||||||
def __init__(self, settings, logger, db):
|
def __init__(self, settings, logger, db):
|
||||||
self.version = fHDHR_VERSION
|
self.version = fHDHR_VERSION
|
||||||
@ -18,6 +18,17 @@ class fHDHR_OBJ():
|
|||||||
|
|
||||||
self.web = fHDHR.tools.WebReq()
|
self.web = fHDHR.tools.WebReq()
|
||||||
|
|
||||||
self.origin = OriginServiceWrapper(settings, logger, self.web, db)
|
|
||||||
|
|
||||||
self.device = fHDHR_Device(settings, self.version, self.origin, logger, self.web, db)
|
class fHDHR_OBJ():
|
||||||
|
|
||||||
|
def __init__(self, settings, logger, db):
|
||||||
|
self.fhdhr = fHDHR_INT_OBJ(settings, logger, db)
|
||||||
|
|
||||||
|
self.origin = OriginServiceWrapper(self.fhdhr)
|
||||||
|
|
||||||
|
self.device = fHDHR_Device(self.fhdhr, self.origin)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
''' will only get called for undefined attributes '''
|
||||||
|
if hasattr(self.fhdhr, name):
|
||||||
|
return eval("self.fhdhr." + name)
|
||||||
|
|||||||
@ -156,7 +156,7 @@ class Config():
|
|||||||
self.dict["filedir"]["epg_cache"][epg_method]["web_cache"] = epg_web_cache_dir
|
self.dict["filedir"]["epg_cache"][epg_method]["web_cache"] = epg_web_cache_dir
|
||||||
self.dict["filedir"]["epg_cache"][epg_method]["epg_json"] = pathlib.Path(epg_cache_dir).joinpath('epg.json')
|
self.dict["filedir"]["epg_cache"][epg_method]["epg_json"] = pathlib.Path(epg_cache_dir).joinpath('epg.json')
|
||||||
|
|
||||||
if self.dict["fhdhr"]["stream_type"] not in ["direct", "ffmpeg"]:
|
if self.dict["fhdhr"]["stream_type"] not in ["direct", "ffmpeg", "vlc"]:
|
||||||
raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...")
|
raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...")
|
||||||
|
|
||||||
opersystem = platform.system()
|
opersystem = platform.system()
|
||||||
@ -193,6 +193,24 @@ class Config():
|
|||||||
else:
|
else:
|
||||||
self.dict["ffmpeg"]["version"] = "N/A"
|
self.dict["ffmpeg"]["version"] = "N/A"
|
||||||
|
|
||||||
|
if self.dict["fhdhr"]["stream_type"] == "vlc":
|
||||||
|
try:
|
||||||
|
vlc_command = [self.dict["vlc"]["vlc_path"],
|
||||||
|
"--version",
|
||||||
|
"pipe:stdout"
|
||||||
|
]
|
||||||
|
|
||||||
|
vlc_proc = subprocess.Popen(vlc_command, stdout=subprocess.PIPE)
|
||||||
|
vlc_version = vlc_proc.stdout.read()
|
||||||
|
vlc_proc.terminate()
|
||||||
|
vlc_proc.communicate()
|
||||||
|
vlc_version = vlc_version.decode().split("version ")[1].split('\n')[0]
|
||||||
|
except FileNotFoundError:
|
||||||
|
vlc_version = None
|
||||||
|
self.dict["vlc"]["version"] = vlc_version
|
||||||
|
else:
|
||||||
|
self.dict["vlc"]["version"] = "N/A"
|
||||||
|
|
||||||
if not self.dict["fhdhr"]["discovery_address"] and self.dict["fhdhr"]["address"] != "0.0.0.0":
|
if not self.dict["fhdhr"]["discovery_address"] and self.dict["fhdhr"]["address"] != "0.0.0.0":
|
||||||
self.dict["fhdhr"]["discovery_address"] = self.dict["fhdhr"]["address"]
|
self.dict["fhdhr"]["discovery_address"] = self.dict["fhdhr"]["address"]
|
||||||
if not self.dict["fhdhr"]["discovery_address"] or self.dict["fhdhr"]["discovery_address"] == "0.0.0.0":
|
if not self.dict["fhdhr"]["discovery_address"] or self.dict["fhdhr"]["discovery_address"] == "0.0.0.0":
|
||||||
@ -223,3 +241,8 @@ class Config():
|
|||||||
# logger.addHandler(c_handler)
|
# logger.addHandler(c_handler)
|
||||||
logger.addHandler(f_handler)
|
logger.addHandler(f_handler)
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
''' will only get called for undefined attributes '''
|
||||||
|
if name in list(self.dict.keys()):
|
||||||
|
return self.dict[name]
|
||||||
|
|||||||
@ -10,20 +10,20 @@ from .cluster import fHDHR_Cluster
|
|||||||
|
|
||||||
class fHDHR_Device():
|
class fHDHR_Device():
|
||||||
|
|
||||||
def __init__(self, settings, fhdhr_version, origin, logger, web, db):
|
def __init__(self, fhdhr, origin):
|
||||||
|
|
||||||
self.channels = Channels(settings, origin, logger, db)
|
self.channels = Channels(fhdhr, origin)
|
||||||
|
|
||||||
self.epg = EPG(settings, self.channels, origin, logger, web, db)
|
self.epg = EPG(fhdhr, self.channels, origin)
|
||||||
|
|
||||||
self.tuners = Tuners(settings, self.epg, logger)
|
self.tuners = Tuners(fhdhr, self.epg)
|
||||||
|
|
||||||
self.watch = WatchStream(settings, self.channels, self.tuners, logger, web)
|
self.watch = WatchStream(fhdhr, self.channels, self.tuners)
|
||||||
|
|
||||||
self.images = imageHandler(settings, self.epg, logger, web)
|
self.images = imageHandler(fhdhr, self.epg)
|
||||||
|
|
||||||
self.station_scan = Station_Scan(settings, self.channels, logger, db)
|
self.station_scan = Station_Scan(fhdhr, self.channels)
|
||||||
|
|
||||||
self.ssdp = SSDPServer(settings, fhdhr_version, logger, db)
|
self.ssdp = SSDPServer(fhdhr)
|
||||||
|
|
||||||
self.cluster = fHDHR_Cluster(settings, self.ssdp, logger, db, web)
|
self.cluster = fHDHR_Cluster(fhdhr, self.ssdp)
|
||||||
|
|||||||
@ -6,13 +6,11 @@ from fHDHR.tools import hours_between_datetime
|
|||||||
|
|
||||||
class ChannelNumbers():
|
class ChannelNumbers():
|
||||||
|
|
||||||
def __init__(self, settings, logger, db):
|
def __init__(self, fhdhr):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
def get_number(self, channel_id):
|
def get_number(self, channel_id):
|
||||||
cnumbers = self.db.get_fhdhr_value("channel_numbers", "list") or {}
|
cnumbers = self.fhdhr.db.get_fhdhr_value("channel_numbers", "list") or {}
|
||||||
if channel_id in list(cnumbers.keys()):
|
if channel_id in list(cnumbers.keys()):
|
||||||
return cnumbers[channel_id]
|
return cnumbers[channel_id]
|
||||||
|
|
||||||
@ -26,20 +24,19 @@ class ChannelNumbers():
|
|||||||
return str(float(i))
|
return str(float(i))
|
||||||
|
|
||||||
def set_number(self, channel_id, channel_number):
|
def set_number(self, channel_id, channel_number):
|
||||||
cnumbers = self.db.get_fhdhr_value("channel_numbers", "list") or {}
|
cnumbers = self.fhdhr.db.get_fhdhr_value("channel_numbers", "list") or {}
|
||||||
cnumbers[channel_id] = str(float(channel_number))
|
cnumbers[channel_id] = str(float(channel_number))
|
||||||
self.db.set_fhdhr_value("channel_numbers", "list", cnumbers)
|
self.fhdhr.db.set_fhdhr_value("channel_numbers", "list", cnumbers)
|
||||||
|
|
||||||
|
|
||||||
class Channels():
|
class Channels():
|
||||||
|
|
||||||
def __init__(self, settings, origin, logger, db):
|
def __init__(self, fhdhr, origin):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.origin = origin
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
self.channel_numbers = ChannelNumbers(settings, logger, db)
|
self.origin = origin
|
||||||
|
|
||||||
|
self.channel_numbers = ChannelNumbers(fhdhr)
|
||||||
|
|
||||||
self.list = {}
|
self.list = {}
|
||||||
self.list_update_time = None
|
self.list_update_time = None
|
||||||
@ -72,7 +69,7 @@ class Channels():
|
|||||||
channel_dict_list = self.verify_channel_info(channel_dict_list)
|
channel_dict_list = self.verify_channel_info(channel_dict_list)
|
||||||
self.append_channel_info(channel_dict_list)
|
self.append_channel_info(channel_dict_list)
|
||||||
if not self.list_update_time:
|
if not self.list_update_time:
|
||||||
self.logger.info("Found " + str(len(self.list)) + " channels for " + str(self.config.dict["main"]["servicename"]))
|
self.fhdhr.logger.info("Found " + str(len(self.list)) + " channels for " + str(self.fhdhr.config.dict["main"]["servicename"]))
|
||||||
self.list_update_time = datetime.datetime.now()
|
self.list_update_time = datetime.datetime.now()
|
||||||
|
|
||||||
channel_list = []
|
channel_list = []
|
||||||
@ -87,6 +84,7 @@ class Channels():
|
|||||||
station_list.append({
|
station_list.append({
|
||||||
'GuideNumber': c['number'],
|
'GuideNumber': c['number'],
|
||||||
'GuideName': c['name'],
|
'GuideName': c['name'],
|
||||||
|
'Tags': ",".join(c['tags']),
|
||||||
'URL': self.get_fhdhr_stream_url(base_url, c['number']),
|
'URL': self.get_fhdhr_stream_url(base_url, c['number']),
|
||||||
})
|
})
|
||||||
return station_list
|
return station_list
|
||||||
@ -123,15 +121,22 @@ class Channels():
|
|||||||
"""Some Channel Information is Critical"""
|
"""Some Channel Information is Critical"""
|
||||||
cleaned_channel_dict_list = []
|
cleaned_channel_dict_list = []
|
||||||
for station_item in channel_dict_list:
|
for station_item in channel_dict_list:
|
||||||
|
|
||||||
if "callsign" not in list(station_item.keys()):
|
if "callsign" not in list(station_item.keys()):
|
||||||
station_item["callsign"] = station_item["name"]
|
station_item["callsign"] = station_item["name"]
|
||||||
|
|
||||||
if "id" not in list(station_item.keys()):
|
if "id" not in list(station_item.keys()):
|
||||||
station_item["id"] = station_item["name"]
|
station_item["id"] = station_item["name"]
|
||||||
|
|
||||||
|
if "tags" not in list(station_item.keys()):
|
||||||
|
station_item["tags"] = []
|
||||||
|
|
||||||
if "number" not in list(station_item.keys()):
|
if "number" not in list(station_item.keys()):
|
||||||
station_item["number"] = self.channel_numbers.get_number(station_item["id"])
|
station_item["number"] = self.channel_numbers.get_number(station_item["id"])
|
||||||
else:
|
else:
|
||||||
station_item["number"] = str(float(station_item["number"]))
|
station_item["number"] = str(float(station_item["number"]))
|
||||||
self.channel_numbers.set_number(station_item["id"], station_item["number"])
|
self.channel_numbers.set_number(station_item["id"], station_item["number"])
|
||||||
|
|
||||||
cleaned_channel_dict_list.append(station_item)
|
cleaned_channel_dict_list.append(station_item)
|
||||||
return cleaned_channel_dict_list
|
return cleaned_channel_dict_list
|
||||||
|
|
||||||
|
|||||||
@ -4,28 +4,26 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
class fHDHR_Cluster():
|
class fHDHR_Cluster():
|
||||||
|
|
||||||
def __init__(self, settings, ssdp, logger, db, web):
|
def __init__(self, fhdhr, ssdp):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.ssdp = ssdp
|
|
||||||
self.db = db
|
|
||||||
self.web = web
|
|
||||||
|
|
||||||
self.friendlyname = self.config.dict["fhdhr"]["friendlyname"]
|
self.ssdp = ssdp
|
||||||
|
|
||||||
|
self.friendlyname = self.fhdhr.config.dict["fhdhr"]["friendlyname"]
|
||||||
self.location = None
|
self.location = None
|
||||||
self.location_url = None
|
self.location_url = None
|
||||||
if settings.dict["fhdhr"]["discovery_address"]:
|
if fhdhr.config.dict["fhdhr"]["discovery_address"]:
|
||||||
self.location = ('http://' + settings.dict["fhdhr"]["discovery_address"] + ':' +
|
self.location = ('http://' + fhdhr.config.dict["fhdhr"]["discovery_address"] + ':' +
|
||||||
str(settings.dict["fhdhr"]["port"]))
|
str(fhdhr.config.dict["fhdhr"]["port"]))
|
||||||
self.location_url = urllib.parse.quote(self.location)
|
self.location_url = urllib.parse.quote(self.location)
|
||||||
|
|
||||||
self.startup_sync()
|
self.startup_sync()
|
||||||
|
|
||||||
def cluster(self):
|
def cluster(self):
|
||||||
return self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
return self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
|
|
||||||
def get_list(self):
|
def get_list(self):
|
||||||
cluster = self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
return_dict = {}
|
return_dict = {}
|
||||||
for location in list(cluster.keys()):
|
for location in list(cluster.keys()):
|
||||||
if location != self.location:
|
if location != self.location:
|
||||||
@ -51,90 +49,90 @@ class fHDHR_Cluster():
|
|||||||
return defdict
|
return defdict
|
||||||
|
|
||||||
def startup_sync(self):
|
def startup_sync(self):
|
||||||
cluster = self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
for location in list(cluster.keys()):
|
for location in list(cluster.keys()):
|
||||||
if location != self.location:
|
if location != self.location:
|
||||||
sync_url = location + "/api/cluster?method=get"
|
sync_url = location + "/api/cluster?method=get"
|
||||||
try:
|
try:
|
||||||
sync_open = self.web.session.get(sync_url)
|
sync_open = self.fhdhr.web.session.get(sync_url)
|
||||||
retrieved_cluster = sync_open.json()
|
retrieved_cluster = sync_open.json()
|
||||||
if self.location not in list(retrieved_cluster.keys()):
|
if self.location not in list(retrieved_cluster.keys()):
|
||||||
return self.leave()
|
return self.leave()
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
|
|
||||||
def leave(self):
|
def leave(self):
|
||||||
self.db.set_fhdhr_value("cluster", "dict", self.default_cluster())
|
self.fhdhr.db.set_fhdhr_value("cluster", "dict", self.default_cluster())
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
cluster = self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
for location in list(cluster.keys()):
|
for location in list(cluster.keys()):
|
||||||
if location != self.location:
|
if location != self.location:
|
||||||
sync_url = location + "/api/cluster?method=del&location=" + self.location
|
sync_url = location + "/api/cluster?method=del&location=" + self.location
|
||||||
try:
|
try:
|
||||||
self.web.session.get(sync_url)
|
self.fhdhr.web.session.get(sync_url)
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
self.leave()
|
self.leave()
|
||||||
|
|
||||||
def sync(self, location):
|
def sync(self, location):
|
||||||
sync_url = location + "/api/cluster?method=get"
|
sync_url = location + "/api/cluster?method=get"
|
||||||
try:
|
try:
|
||||||
sync_open = self.web.session.get(sync_url)
|
sync_open = self.fhdhr.web.session.get(sync_url)
|
||||||
self.db.set_fhdhr_value("cluster", "dict", sync_open.json())
|
self.fhdhr.db.set_fhdhr_value("cluster", "dict", sync_open.json())
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
|
|
||||||
def push_sync(self):
|
def push_sync(self):
|
||||||
cluster = self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
for location in list(cluster.keys()):
|
for location in list(cluster.keys()):
|
||||||
if location != self.location:
|
if location != self.location:
|
||||||
sync_url = location + "/api/cluster?method=sync&location=" + self.location_url
|
sync_url = location + "/api/cluster?method=sync&location=" + self.location_url
|
||||||
try:
|
try:
|
||||||
self.web.session.get(sync_url)
|
self.fhdhr.web.session.get(sync_url)
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
|
|
||||||
def add(self, location):
|
def add(self, location):
|
||||||
cluster = self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
if location not in list(cluster.keys()):
|
if location not in list(cluster.keys()):
|
||||||
cluster[location] = {"base_url": location}
|
cluster[location] = {"base_url": location}
|
||||||
|
|
||||||
location_info_url = location + "/discover.json"
|
location_info_url = location + "/discover.json"
|
||||||
try:
|
try:
|
||||||
location_info_req = self.web.session.get(location_info_url)
|
location_info_req = self.fhdhr.web.session.get(location_info_url)
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
del cluster[location]
|
del cluster[location]
|
||||||
self.db.set_fhdhr_value("cluster", "dict", cluster)
|
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
||||||
return
|
return
|
||||||
location_info = location_info_req.json()
|
location_info = location_info_req.json()
|
||||||
cluster[location]["name"] = location_info["FriendlyName"]
|
cluster[location]["name"] = location_info["FriendlyName"]
|
||||||
|
|
||||||
cluster_info_url = location + "/api/cluster?method=get"
|
cluster_info_url = location + "/api/cluster?method=get"
|
||||||
try:
|
try:
|
||||||
cluster_info_req = self.web.session.get(cluster_info_url)
|
cluster_info_req = self.fhdhr.web.session.get(cluster_info_url)
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
del cluster[location]
|
del cluster[location]
|
||||||
self.db.set_fhdhr_value("cluster", "dict", cluster)
|
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
||||||
return
|
return
|
||||||
cluster_info = cluster_info_req.json()
|
cluster_info = cluster_info_req.json()
|
||||||
for cluster_key in list(cluster_info.keys()):
|
for cluster_key in list(cluster_info.keys()):
|
||||||
if cluster_key not in list(cluster.keys()):
|
if cluster_key not in list(cluster.keys()):
|
||||||
cluster[cluster_key] = cluster_info[cluster_key]
|
cluster[cluster_key] = cluster_info[cluster_key]
|
||||||
|
|
||||||
self.db.set_fhdhr_value("cluster", "dict", cluster)
|
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
||||||
self.push_sync()
|
self.push_sync()
|
||||||
|
|
||||||
def remove(self, location):
|
def remove(self, location):
|
||||||
cluster = self.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
||||||
if location in list(cluster.keys()):
|
if location in list(cluster.keys()):
|
||||||
del cluster[location]
|
del cluster[location]
|
||||||
sync_url = location + "/api/cluster?method=leave"
|
sync_url = location + "/api/cluster?method=leave"
|
||||||
try:
|
try:
|
||||||
self.web.session.get(sync_url)
|
self.fhdhr.web.session.get(sync_url)
|
||||||
except self.web.exceptions.ConnectionError:
|
except self.fhdhr.web.exceptions.ConnectionError:
|
||||||
self.logger.error("Unreachable: " + location)
|
self.fhdhr.logger.error("Unreachable: " + location)
|
||||||
self.push_sync()
|
self.push_sync()
|
||||||
self.db.set_fhdhr_value("cluster", "dict", cluster)
|
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
||||||
|
|||||||
@ -15,27 +15,49 @@ for entry in os.scandir(device_dir + '/epgtypes'):
|
|||||||
|
|
||||||
class EPG():
|
class EPG():
|
||||||
|
|
||||||
def __init__(self, settings, channels, origin, logger, web, db):
|
def __init__(self, fhdhr, channels, origin):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.web = web
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
self.epgdict = {}
|
self.epgdict = {}
|
||||||
|
|
||||||
self.epg_method_selfadd()
|
self.epg_method_selfadd()
|
||||||
|
|
||||||
self.epg_methods = self.config.dict["epg"]["method"]
|
self.epg_methods = self.fhdhr.config.dict["epg"]["method"]
|
||||||
self.def_method = self.config.dict["epg"]["def_method"]
|
self.def_method = self.fhdhr.config.dict["epg"]["def_method"]
|
||||||
self.sleeptime = {}
|
self.sleeptime = {}
|
||||||
for epg_method in self.epg_methods:
|
for epg_method in self.epg_methods:
|
||||||
if epg_method in list(self.config.dict.keys()):
|
if epg_method in list(self.fhdhr.config.dict.keys()):
|
||||||
if "update_frequency" in list(self.config.dict[epg_method].keys()):
|
if "update_frequency" in list(self.fhdhr.config.dict[epg_method].keys()):
|
||||||
self.sleeptime[epg_method] = self.config.dict[epg_method]["update_frequency"]
|
self.sleeptime[epg_method] = self.fhdhr.config.dict[epg_method]["update_frequency"]
|
||||||
if epg_method not in list(self.sleeptime.keys()):
|
if epg_method not in list(self.sleeptime.keys()):
|
||||||
self.sleeptime[epg_method] = self.config.dict["epg"]["update_frequency"]
|
self.sleeptime[epg_method] = self.fhdhr.config.dict["epg"]["update_frequency"]
|
||||||
|
|
||||||
|
def clear_epg_cache(self, method=None):
|
||||||
|
|
||||||
|
if not method:
|
||||||
|
method = self.def_method
|
||||||
|
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
|
||||||
|
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
||||||
|
method = "origin"
|
||||||
|
|
||||||
|
epgtypename = method
|
||||||
|
if method in [self.fhdhr.config.dict["main"]["dictpopname"], "origin"]:
|
||||||
|
epgtypename = self.fhdhr.config.dict["main"]["dictpopname"]
|
||||||
|
|
||||||
|
self.fhdhr.logger.info("Clearing " + epgtypename + " EPG cache.")
|
||||||
|
|
||||||
|
method_to_call = getattr(self, method)
|
||||||
|
if hasattr(method_to_call, 'clear_cache'):
|
||||||
|
func_to_call = getattr(method_to_call, 'clear_cache')
|
||||||
|
func_to_call()
|
||||||
|
|
||||||
|
if method in list(self.epgdict.keys()):
|
||||||
|
del self.epgdict[method]
|
||||||
|
|
||||||
|
self.fhdhr.db.delete_fhdhr_value("epg_dict", method)
|
||||||
|
|
||||||
def whats_on_now(self, channel):
|
def whats_on_now(self, channel):
|
||||||
epgdict = self.get_epg()
|
epgdict = self.get_epg()
|
||||||
@ -62,16 +84,16 @@ class EPG():
|
|||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
method = self.def_method
|
method = self.def_method
|
||||||
if (method == self.config.dict["main"]["dictpopname"] or
|
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
|
||||||
method not in self.config.dict["main"]["valid_epg_methods"]):
|
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
||||||
method = "origin"
|
method = "origin"
|
||||||
|
|
||||||
if method not in list(self.epgdict.keys()):
|
if method not in list(self.epgdict.keys()):
|
||||||
|
|
||||||
epgdict = self.db.get_fhdhr_value("epg_dict", method) or None
|
epgdict = self.fhdhr.db.get_fhdhr_value("epg_dict", method) or None
|
||||||
if not epgdict:
|
if not epgdict:
|
||||||
self.update(method)
|
self.update(method)
|
||||||
self.epgdict[method] = self.db.get_fhdhr_value("epg_dict", method) or {}
|
self.epgdict[method] = self.fhdhr.db.get_fhdhr_value("epg_dict", method) or {}
|
||||||
else:
|
else:
|
||||||
self.epgdict[method] = epgdict
|
self.epgdict[method] = epgdict
|
||||||
return self.epgdict[method]
|
return self.epgdict[method]
|
||||||
@ -103,21 +125,21 @@ class EPG():
|
|||||||
|
|
||||||
def epg_method_selfadd(self):
|
def epg_method_selfadd(self):
|
||||||
for method in epgtype_list:
|
for method in epgtype_list:
|
||||||
exec("%s = %s" % ("self." + str(method), str(method) + "." + str(method) + "EPG(self.config, self.channels, self.logger, self.web, self.db)"))
|
exec("%s = %s" % ("self." + str(method), str(method) + "." + str(method) + "EPG(self.fhdhr, self.channels)"))
|
||||||
|
|
||||||
def update(self, method=None):
|
def update(self, method=None):
|
||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
method = self.def_method
|
method = self.def_method
|
||||||
if (method == self.config.dict["main"]["dictpopname"] or
|
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
|
||||||
method not in self.config.dict["main"]["valid_epg_methods"]):
|
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
||||||
method = "origin"
|
method = "origin"
|
||||||
|
|
||||||
epgtypename = method
|
epgtypename = method
|
||||||
if method in [self.config.dict["main"]["dictpopname"], "origin"]:
|
if method in [self.fhdhr.config.dict["main"]["dictpopname"], "origin"]:
|
||||||
epgtypename = self.config.dict["main"]["dictpopname"]
|
epgtypename = self.fhdhr.config.dict["main"]["dictpopname"]
|
||||||
|
|
||||||
self.logger.info("Updating " + epgtypename + " EPG cache.")
|
self.fhdhr.logger.info("Updating " + epgtypename + " EPG cache.")
|
||||||
method_to_call = getattr(self, method)
|
method_to_call = getattr(self, method)
|
||||||
func_to_call = getattr(method_to_call, 'update_epg')
|
func_to_call = getattr(method_to_call, 'update_epg')
|
||||||
if method == 'origin':
|
if method == 'origin':
|
||||||
@ -136,9 +158,9 @@ class EPG():
|
|||||||
programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start'])
|
programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start'])
|
||||||
|
|
||||||
self.epgdict = programguide
|
self.epgdict = programguide
|
||||||
self.db.set_fhdhr_value("epg_dict", method, programguide)
|
self.fhdhr.db.set_fhdhr_value("epg_dict", method, programguide)
|
||||||
self.db.set_fhdhr_value("update_time", method, time.time())
|
self.fhdhr.db.set_fhdhr_value("update_time", method, time.time())
|
||||||
self.logger.info("Wrote " + epgtypename + " EPG cache.")
|
self.fhdhr.logger.info("Wrote " + epgtypename + " EPG cache.")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
for epg_method in self.epg_methods:
|
for epg_method in self.epg_methods:
|
||||||
@ -146,7 +168,7 @@ class EPG():
|
|||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
for epg_method in self.epg_methods:
|
for epg_method in self.epg_methods:
|
||||||
if time.time() >= (self.db.get_fhdhr_value("update_time", epg_method) + self.sleeptime[epg_method]):
|
if time.time() >= (self.fhdhr.db.get_fhdhr_value("update_time", epg_method) + self.sleeptime[epg_method]):
|
||||||
self.update(epg_method)
|
self.update(epg_method)
|
||||||
time.sleep(3600)
|
time.sleep(3600)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
@ -3,11 +3,10 @@ import datetime
|
|||||||
|
|
||||||
class blocksEPG():
|
class blocksEPG():
|
||||||
|
|
||||||
def __init__(self, settings, channels, logger, web, db):
|
def __init__(self, fhdhr, channels):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.db = db
|
|
||||||
|
|
||||||
def update_epg(self):
|
def update_epg(self):
|
||||||
programguide = {}
|
programguide = {}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import json
|
|
||||||
import time
|
|
||||||
import datetime
|
import datetime
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
@ -9,23 +7,21 @@ from fHDHR.exceptions import EPGSetupError
|
|||||||
|
|
||||||
class zap2itEPG():
|
class zap2itEPG():
|
||||||
|
|
||||||
def __init__(self, settings, channels, logger, web, db):
|
def __init__(self, fhdhr, channels):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.web = web
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
self.postalcode = self.config.dict["zap2it"]["postalcode"]
|
self.postalcode = self.fhdhr.config.dict["zap2it"]["postalcode"]
|
||||||
|
|
||||||
self.web_cache_dir = self.config.dict["filedir"]["epg_cache"]["zap2it"]["web_cache"]
|
self.fhdhr.web_cache_dir = self.fhdhr.config.dict["filedir"]["epg_cache"]["zap2it"]["web_cache"]
|
||||||
|
|
||||||
def get_location(self):
|
def get_location(self):
|
||||||
self.logger.warning("Zap2it postalcode not set, attempting to retrieve.")
|
self.fhdhr.logger.warning("Zap2it postalcode not set, attempting to retrieve.")
|
||||||
if not self.postalcode:
|
if not self.postalcode:
|
||||||
try:
|
try:
|
||||||
postalcode_url = 'http://ipinfo.io/json'
|
postalcode_url = 'http://ipinfo.io/json'
|
||||||
postalcode_req = self.web.session.get(postalcode_url)
|
postalcode_req = self.fhdhr.web.session.get(postalcode_url)
|
||||||
data = postalcode_req.json()
|
data = postalcode_req.json()
|
||||||
self.postalcode = data["postal"]
|
self.postalcode = data["postal"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -37,40 +33,19 @@ class zap2itEPG():
|
|||||||
|
|
||||||
# Start time parameter is now rounded down to nearest `zap_timespan`, in s.
|
# Start time parameter is now rounded down to nearest `zap_timespan`, in s.
|
||||||
zap_time = datetime.datetime.utcnow().timestamp()
|
zap_time = datetime.datetime.utcnow().timestamp()
|
||||||
zap_time_window = int(self.config.dict["zap2it"]["timespan"]) * 3600
|
self.remove_stale_cache(zap_time)
|
||||||
|
zap_time_window = int(self.fhdhr.config.dict["zap2it"]["timespan"]) * 3600
|
||||||
zap_time = int(zap_time - (zap_time % zap_time_window))
|
zap_time = int(zap_time - (zap_time % zap_time_window))
|
||||||
|
|
||||||
self.remove_stale_cache(zap_time)
|
|
||||||
|
|
||||||
# Fetch data in `zap_timespan` chunks.
|
# Fetch data in `zap_timespan` chunks.
|
||||||
for i in range(int(7 * 24 / int(self.config.dict["zap2it"]["timespan"]))):
|
i_times = []
|
||||||
i_time = zap_time + (i * zap_time_window)
|
for i in range(int(7 * 24 / int(self.fhdhr.config.dict["zap2it"]["timespan"]))):
|
||||||
|
i_times.append(zap_time + (i * zap_time_window))
|
||||||
|
|
||||||
parameters = {
|
cached_items = self.get_cached(i_times)
|
||||||
'aid': self.config.dict["zap2it"]['affiliate_id'],
|
for result in cached_items:
|
||||||
'country': self.config.dict["zap2it"]['country'],
|
|
||||||
'device': self.config.dict["zap2it"]['device'],
|
|
||||||
'headendId': self.config.dict["zap2it"]['headendid'],
|
|
||||||
'isoverride': "true",
|
|
||||||
'languagecode': self.config.dict["zap2it"]['languagecode'],
|
|
||||||
'pref': 'm,p',
|
|
||||||
'timespan': self.config.dict["zap2it"]['timespan'],
|
|
||||||
'timezone': self.config.dict["zap2it"]['timezone'],
|
|
||||||
'userId': self.config.dict["zap2it"]['userid'],
|
|
||||||
'postalCode': str(self.postalcode or self.get_location()),
|
|
||||||
'lineupId': '%s-%s-DEFAULT' % (self.config.dict["zap2it"]['country'], self.config.dict["zap2it"]['device']),
|
|
||||||
'time': i_time,
|
|
||||||
'Activity_ID': 1,
|
|
||||||
'FromPage': "TV%20Guide",
|
|
||||||
}
|
|
||||||
|
|
||||||
url = 'https://tvlistings.zap2it.com/api/grid?'
|
for c in result['channels']:
|
||||||
url += urllib.parse.urlencode(parameters)
|
|
||||||
|
|
||||||
result = self.get_cached(str(i_time), self.config.dict["zap2it"]['delay'], url)
|
|
||||||
d = json.loads(result)
|
|
||||||
|
|
||||||
for c in d['channels']:
|
|
||||||
|
|
||||||
cdict = xmldictmaker(c, ["callSign", "name", "channelNo", "channelId", "thumbnail"])
|
cdict = xmldictmaker(c, ["callSign", "name", "channelNo", "channelId", "thumbnail"])
|
||||||
|
|
||||||
@ -119,6 +94,7 @@ class zap2itEPG():
|
|||||||
if 'New' in eventdict['flag'] and 'live' not in eventdict['flag']:
|
if 'New' in eventdict['flag'] and 'live' not in eventdict['flag']:
|
||||||
clean_prog_dict["isnew"] = True
|
clean_prog_dict["isnew"] = True
|
||||||
|
|
||||||
|
if not any(d['id'] == clean_prog_dict['id'] for d in programguide[str(cdict["channelNo"])]["listing"]):
|
||||||
programguide[str(cdict["channelNo"])]["listing"].append(clean_prog_dict)
|
programguide[str(cdict["channelNo"])]["listing"].append(clean_prog_dict)
|
||||||
|
|
||||||
return programguide
|
return programguide
|
||||||
@ -129,29 +105,69 @@ class zap2itEPG():
|
|||||||
xmltime = xmltime.strftime('%Y%m%d%H%M%S %z')
|
xmltime = xmltime.strftime('%Y%m%d%H%M%S %z')
|
||||||
return xmltime
|
return xmltime
|
||||||
|
|
||||||
def get_cached(self, cache_key, delay, url):
|
def get_cached(self, i_times):
|
||||||
cache_path = self.web_cache_dir.joinpath(cache_key)
|
|
||||||
if cache_path.is_file():
|
# Fetch data in `zap_timespan` chunks.
|
||||||
self.logger.info('FROM CACHE: ' + str(cache_path))
|
for i_time in i_times:
|
||||||
with open(cache_path, 'rb') as f:
|
|
||||||
return f.read()
|
parameters = {
|
||||||
|
'aid': self.fhdhr.config.dict["zap2it"]['affiliate_id'],
|
||||||
|
'country': self.fhdhr.config.dict["zap2it"]['country'],
|
||||||
|
'device': self.fhdhr.config.dict["zap2it"]['device'],
|
||||||
|
'headendId': self.fhdhr.config.dict["zap2it"]['headendid'],
|
||||||
|
'isoverride': "true",
|
||||||
|
'languagecode': self.fhdhr.config.dict["zap2it"]['languagecode'],
|
||||||
|
'pref': 'm,p',
|
||||||
|
'timespan': self.fhdhr.config.dict["zap2it"]['timespan'],
|
||||||
|
'timezone': self.fhdhr.config.dict["zap2it"]['timezone'],
|
||||||
|
'userId': self.fhdhr.config.dict["zap2it"]['userid'],
|
||||||
|
'postalCode': str(self.postalcode or self.get_location()),
|
||||||
|
'lineupId': '%s-%s-DEFAULT' % (self.fhdhr.config.dict["zap2it"]['country'], self.fhdhr.config.dict["zap2it"]['device']),
|
||||||
|
'time': i_time,
|
||||||
|
'Activity_ID': 1,
|
||||||
|
'FromPage': "TV%20Guide",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = 'https://tvlistings.zap2it.com/api/grid?'
|
||||||
|
url += urllib.parse.urlencode(parameters)
|
||||||
|
self.get_cached_item(str(i_time), url)
|
||||||
|
cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or []
|
||||||
|
return [self.fhdhr.db.get_cacheitem_value(x, "offline_cache", "zap2it") for x in cache_list]
|
||||||
|
|
||||||
|
def get_cached_item(self, cache_key, url):
|
||||||
|
cacheitem = self.fhdhr.db.get_cacheitem_value(cache_key, "offline_cache", "zap2it")
|
||||||
|
if cacheitem:
|
||||||
|
self.fhdhr.logger.info('FROM CACHE: ' + str(cache_key))
|
||||||
|
return cacheitem
|
||||||
else:
|
else:
|
||||||
self.logger.info('Fetching: ' + url)
|
self.fhdhr.logger.info('Fetching: ' + url)
|
||||||
resp = self.web.session.get(url)
|
try:
|
||||||
result = resp.content
|
resp = self.fhdhr.web.session.get(url)
|
||||||
with open(cache_path, 'wb') as f:
|
except self.fhdhr.web.exceptions.HTTPError:
|
||||||
f.write(result)
|
self.fhdhr.logger.info('Got an error! Ignoring it.')
|
||||||
time.sleep(int(delay))
|
return
|
||||||
return result
|
result = resp.json()
|
||||||
|
|
||||||
|
self.fhdhr.db.set_cacheitem_value(cache_key, "offline_cache", result, "zap2it")
|
||||||
|
cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or []
|
||||||
|
cache_list.append(cache_key)
|
||||||
|
self.fhdhr.db.set_cacheitem_value("cache_list", "offline_cache", cache_list, "zap2it")
|
||||||
|
|
||||||
def remove_stale_cache(self, zap_time):
|
def remove_stale_cache(self, zap_time):
|
||||||
for p in self.web_cache_dir.glob('*'):
|
|
||||||
try:
|
cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or []
|
||||||
t = int(p.name)
|
cache_to_kill = []
|
||||||
if t >= zap_time:
|
for cacheitem in cache_list:
|
||||||
continue
|
cachedate = int(cacheitem)
|
||||||
except Exception as e:
|
if cachedate < zap_time:
|
||||||
self.logger.error(e)
|
cache_to_kill.append(cacheitem)
|
||||||
pass
|
self.fhdhr.db.delete_cacheitem_value(cacheitem, "offline_cache", "zap2it")
|
||||||
self.logger.info('Removing stale cache file: ' + p.name)
|
self.fhdhr.logger.info('Removing stale cache: ' + str(cacheitem))
|
||||||
p.unlink()
|
self.fhdhr.db.set_cacheitem_value("cache_list", "offline_cache", [x for x in cache_list if x not in cache_to_kill], "zap2it")
|
||||||
|
|
||||||
|
def clear_cache(self):
|
||||||
|
cache_list = self.fhdhr.db.get_cacheitem_value("cache_list", "offline_cache", "zap2it") or []
|
||||||
|
for cacheitem in cache_list:
|
||||||
|
self.fhdhr.db.delete_cacheitem_value(cacheitem, "offline_cache", "zap2it")
|
||||||
|
self.fhdhr.logger.info('Removing cache: ' + str(cacheitem))
|
||||||
|
self.fhdhr.db.delete_cacheitem_value("cache_list", "offline_cache", "zap2it")
|
||||||
|
|||||||
@ -6,18 +6,15 @@ import PIL.ImageFont
|
|||||||
|
|
||||||
class imageHandler():
|
class imageHandler():
|
||||||
|
|
||||||
def __init__(self, settings, epg, logger, web):
|
def __init__(self, fhdhr, epg):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.epg = epg
|
|
||||||
self.web = web
|
|
||||||
|
|
||||||
def get_epg_image(self, image_type, content_id):
|
def get_epg_image(self, image_type, content_id):
|
||||||
imageUri = self.epg.get_thumbnail(image_type, str(content_id))
|
imageUri = self.epg.get_thumbnail(image_type, str(content_id))
|
||||||
if not imageUri:
|
if not imageUri:
|
||||||
return self.generate_image(image_type, str(content_id))
|
return self.generate_image(image_type, str(content_id))
|
||||||
|
|
||||||
req = self.web.session.get(imageUri)
|
req = self.fhdhr.web.session.get(imageUri)
|
||||||
return req.content
|
return req.content
|
||||||
|
|
||||||
def getSize(self, txt, font):
|
def getSize(self, txt, font):
|
||||||
@ -38,7 +35,7 @@ class imageHandler():
|
|||||||
colorBackground = "#228822"
|
colorBackground = "#228822"
|
||||||
colorText = "#717D7E"
|
colorText = "#717D7E"
|
||||||
colorOutline = "#717D7E"
|
colorOutline = "#717D7E"
|
||||||
fontname = str(self.config.dict["filedir"]["font"])
|
fontname = str(self.fhdhr.config.dict["filedir"]["font"])
|
||||||
|
|
||||||
font = PIL.ImageFont.truetype(fontname, fontsize)
|
font = PIL.ImageFont.truetype(fontname, fontsize)
|
||||||
text_width, text_height = self.getSize(message, font)
|
text_width, text_height = self.getSize(message, font)
|
||||||
|
|||||||
@ -5,47 +5,44 @@ import struct
|
|||||||
|
|
||||||
class fHDHR_Detect():
|
class fHDHR_Detect():
|
||||||
|
|
||||||
def __init__(self, settings, logger, db):
|
def __init__(self, fhdhr):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.db = db
|
self.fhdhr.db.delete_fhdhr_value("ssdp_detect", "list")
|
||||||
self.db.delete_fhdhr_value("ssdp_detect", "list")
|
|
||||||
|
|
||||||
def set(self, location):
|
def set(self, location):
|
||||||
detect_list = self.db.get_fhdhr_value("ssdp_detect", "list") or []
|
detect_list = self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or []
|
||||||
if location not in detect_list:
|
if location not in detect_list:
|
||||||
detect_list.append(location)
|
detect_list.append(location)
|
||||||
self.db.set_fhdhr_value("ssdp_detect", "list", detect_list)
|
self.fhdhr.db.set_fhdhr_value("ssdp_detect", "list", detect_list)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return self.db.get_fhdhr_value("ssdp_detect", "list") or []
|
return self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or []
|
||||||
|
|
||||||
|
|
||||||
class SSDPServer():
|
class SSDPServer():
|
||||||
|
|
||||||
def __init__(self, settings, fhdhr_version, logger, db):
|
def __init__(self, fhdhr):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
self.detect_method = fHDHR_Detect(settings, logger, db)
|
self.detect_method = fHDHR_Detect(fhdhr)
|
||||||
|
|
||||||
if settings.dict["fhdhr"]["discovery_address"]:
|
if fhdhr.config.dict["fhdhr"]["discovery_address"]:
|
||||||
|
|
||||||
self.sock = None
|
self.sock = None
|
||||||
self.proto = "ipv4"
|
self.proto = "ipv4"
|
||||||
self.port = 1900
|
self.port = 1900
|
||||||
self.iface = None
|
self.iface = None
|
||||||
self.address = None
|
self.address = None
|
||||||
self.server = 'fHDHR/%s UPnP/1.0' % fhdhr_version
|
self.server = 'fHDHR/%s UPnP/1.0' % fhdhr.version
|
||||||
|
|
||||||
allowed_protos = ("ipv4", "ipv6")
|
allowed_protos = ("ipv4", "ipv6")
|
||||||
if self.proto not in allowed_protos:
|
if self.proto not in allowed_protos:
|
||||||
raise ValueError("Invalid proto - expected one of {}".format(allowed_protos))
|
raise ValueError("Invalid proto - expected one of {}".format(allowed_protos))
|
||||||
|
|
||||||
self.nt = 'urn:schemas-upnp-org:device:MediaServer:1'
|
self.nt = 'urn:schemas-upnp-org:device:MediaServer:1'
|
||||||
self.usn = 'uuid:' + settings.dict["main"]["uuid"] + '::' + self.nt
|
self.usn = 'uuid:' + fhdhr.config.dict["main"]["uuid"] + '::' + self.nt
|
||||||
self.location = ('http://' + settings.dict["fhdhr"]["discovery_address"] + ':' +
|
self.location = ('http://' + fhdhr.config.dict["fhdhr"]["discovery_address"] + ':' +
|
||||||
str(settings.dict["fhdhr"]["port"]) + '/device.xml')
|
str(fhdhr.config.dict["fhdhr"]["port"]) + '/device.xml')
|
||||||
self.al = self.location
|
self.al = self.location
|
||||||
self.max_age = 1800
|
self.max_age = 1800
|
||||||
self._iface = None
|
self._iface = None
|
||||||
@ -104,14 +101,14 @@ class SSDPServer():
|
|||||||
self.m_search()
|
self.m_search()
|
||||||
|
|
||||||
def on_recv(self, data, address):
|
def on_recv(self, data, address):
|
||||||
self.logger.debug("Received packet from {}: {}".format(address, data))
|
self.fhdhr.logger.debug("Received packet from {}: {}".format(address, data))
|
||||||
|
|
||||||
(host, port) = address
|
(host, port) = address
|
||||||
|
|
||||||
try:
|
try:
|
||||||
header, payload = data.decode().split('\r\n\r\n')[:2]
|
header, payload = data.decode().split('\r\n\r\n')[:2]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.logger.error("Error with Received packet from {}: {}".format(address, data))
|
self.fhdhr.logger.error("Error with Received packet from {}: {}".format(address, data))
|
||||||
return
|
return
|
||||||
|
|
||||||
lines = header.split('\r\n')
|
lines = header.split('\r\n')
|
||||||
@ -124,19 +121,19 @@ class SSDPServer():
|
|||||||
|
|
||||||
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
|
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
|
||||||
# SSDP discovery
|
# SSDP discovery
|
||||||
self.logger.debug("Received qualifying M-SEARCH from {}".format(address))
|
self.fhdhr.logger.debug("Received qualifying M-SEARCH from {}".format(address))
|
||||||
self.logger.debug("M-SEARCH data: {}".format(headers))
|
self.fhdhr.logger.debug("M-SEARCH data: {}".format(headers))
|
||||||
notify = self.notify_payload
|
notify = self.notify_payload
|
||||||
self.logger.debug("Created NOTIFY: {}".format(notify))
|
self.fhdhr.logger.debug("Created NOTIFY: {}".format(notify))
|
||||||
try:
|
try:
|
||||||
self.sock.sendto(notify, address)
|
self.sock.sendto(notify, address)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# Most commonly: We received a multicast from an IP not in our subnet
|
# Most commonly: We received a multicast from an IP not in our subnet
|
||||||
self.logger.debug("Unable to send NOTIFY to {}: {}".format(address, e))
|
self.fhdhr.logger.debug("Unable to send NOTIFY to {}: {}".format(address, e))
|
||||||
pass
|
pass
|
||||||
elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
|
elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
|
||||||
# SSDP presence
|
# SSDP presence
|
||||||
self.logger.debug("NOTIFY data: {}".format(headers))
|
self.fhdhr.logger.debug("NOTIFY data: {}".format(headers))
|
||||||
try:
|
try:
|
||||||
if headers["server"].startswith("fHDHR"):
|
if headers["server"].startswith("fHDHR"):
|
||||||
if headers["location"] != self.location:
|
if headers["location"] != self.location:
|
||||||
@ -144,7 +141,7 @@ class SSDPServer():
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.logger.debug('Unknown SSDP command %s %s' % (cmd[0], cmd[1]))
|
self.fhdhr.logger.debug('Unknown SSDP command %s %s' % (cmd[0], cmd[1]))
|
||||||
|
|
||||||
def m_search(self):
|
def m_search(self):
|
||||||
data = self.msearch_payload
|
data = self.msearch_payload
|
||||||
|
|||||||
@ -3,31 +3,31 @@ from multiprocessing import Process
|
|||||||
|
|
||||||
class Station_Scan():
|
class Station_Scan():
|
||||||
|
|
||||||
def __init__(self, settings, channels, logger, db):
|
def __init__(self, fhdhr, channels):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.db = db
|
|
||||||
self.db.delete_fhdhr_value("station_scan", "scanning")
|
self.fhdhr.db.delete_fhdhr_value("station_scan", "scanning")
|
||||||
|
|
||||||
def scan(self):
|
def scan(self):
|
||||||
self.logger.info("Channel Scan Requested by Client.")
|
self.fhdhr.logger.info("Channel Scan Requested by Client.")
|
||||||
|
|
||||||
scan_status = self.db.get_fhdhr_value("station_scan", "scanning")
|
scan_status = self.fhdhr.db.get_fhdhr_value("station_scan", "scanning")
|
||||||
if not scan_status:
|
if not scan_status:
|
||||||
self.db.set_fhdhr_value("station_scan", "scanning", 1)
|
self.fhdhr.db.set_fhdhr_value("station_scan", "scanning", 1)
|
||||||
chanscan = Process(target=self.runscan)
|
chanscan = Process(target=self.runscan)
|
||||||
chanscan.start()
|
chanscan.start()
|
||||||
else:
|
else:
|
||||||
self.logger.info("Channel Scan Already In Progress!")
|
self.fhdhr.logger.info("Channel Scan Already In Progress!")
|
||||||
|
|
||||||
def runscan(self):
|
def runscan(self):
|
||||||
self.channels.get_channels(forceupdate=True)
|
self.channels.get_channels(forceupdate=True)
|
||||||
self.logger.info("Requested Channel Scan Complete.")
|
self.fhdhr.logger.info("Requested Channel Scan Complete.")
|
||||||
self.db.delete_fhdhr_value("station_scan", "scanning")
|
self.fhdhr.db.delete_fhdhr_value("station_scan", "scanning")
|
||||||
|
|
||||||
def scanning(self):
|
def scanning(self):
|
||||||
scan_status = self.db.get_fhdhr_value("station_scan", "scanning")
|
scan_status = self.fhdhr.db.get_fhdhr_value("station_scan", "scanning")
|
||||||
if not scan_status:
|
if not scan_status:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -6,8 +6,8 @@ from fHDHR.tools import humanized_time
|
|||||||
|
|
||||||
|
|
||||||
class Tuner():
|
class Tuner():
|
||||||
def __init__(self, inum, epg, logger):
|
def __init__(self, fhdhr, inum, epg):
|
||||||
self.logger = logger
|
self.fhdhr = fhdhr
|
||||||
self.number = inum
|
self.number = inum
|
||||||
self.epg = epg
|
self.epg = epg
|
||||||
self.tuner_lock = threading.Lock()
|
self.tuner_lock = threading.Lock()
|
||||||
@ -17,18 +17,19 @@ class Tuner():
|
|||||||
if self.tuner_lock.locked():
|
if self.tuner_lock.locked():
|
||||||
raise TunerError("Tuner #" + str(self.number) + " is not available.")
|
raise TunerError("Tuner #" + str(self.number) + " is not available.")
|
||||||
|
|
||||||
self.logger.info("Tuner #" + str(self.number) + " to be used for stream.")
|
self.fhdhr.logger.info("Tuner #" + str(self.number) + " to be used for stream.")
|
||||||
self.tuner_lock.acquire()
|
self.tuner_lock.acquire()
|
||||||
self.status = {
|
self.status = {
|
||||||
"status": "Active",
|
"status": "Active",
|
||||||
"method": stream_args["method"],
|
"method": stream_args["method"],
|
||||||
"accessed": stream_args["accessed"],
|
"accessed": stream_args["accessed"],
|
||||||
|
"channel": stream_args["channel"],
|
||||||
"proxied_url": stream_args["channelUri"],
|
"proxied_url": stream_args["channelUri"],
|
||||||
"time_start": datetime.datetime.utcnow(),
|
"time_start": datetime.datetime.utcnow(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.logger.info("Tuner #" + str(self.number) + " Shutting Down.")
|
self.fhdhr.logger.info("Tuner #" + str(self.number) + " Shutting Down.")
|
||||||
self.set_off_status()
|
self.set_off_status()
|
||||||
self.tuner_lock.release()
|
self.tuner_lock.release()
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class Tuner():
|
|||||||
humanized_time(
|
humanized_time(
|
||||||
int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds())))
|
int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds())))
|
||||||
current_status["time_start"] = str(current_status["time_start"])
|
current_status["time_start"] = str(current_status["time_start"])
|
||||||
current_status["epg"] = self.epg.whats_on_now(current_status["accessed"].split("v")[-1])
|
current_status["epg"] = self.epg.whats_on_now(current_status["channel"])
|
||||||
return current_status
|
return current_status
|
||||||
|
|
||||||
def set_off_status(self):
|
def set_off_status(self):
|
||||||
@ -48,29 +49,31 @@ class Tuner():
|
|||||||
|
|
||||||
class Tuners():
|
class Tuners():
|
||||||
|
|
||||||
def __init__(self, settings, epg, logger):
|
def __init__(self, fhdhr, epg):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.epg = epg
|
self.epg = epg
|
||||||
self.max_tuners = int(self.config.dict["fhdhr"]["tuner_count"])
|
self.max_tuners = int(self.fhdhr.config.dict["fhdhr"]["tuner_count"])
|
||||||
|
|
||||||
|
self.tuners = {}
|
||||||
|
|
||||||
for i in range(1, self.max_tuners + 1):
|
for i in range(1, self.max_tuners + 1):
|
||||||
exec("%s = %s" % ("self.tuner_" + str(i), "Tuner(i, epg, logger)"))
|
self.tuners[i] = Tuner(fhdhr, i, epg)
|
||||||
|
|
||||||
def tuner_grab(self, stream_args, tunernum=None):
|
def tuner_grab(self, stream_args):
|
||||||
tunerselected = None
|
tunerselected = None
|
||||||
|
|
||||||
if tunernum:
|
if stream_args["tuner"]:
|
||||||
if tunernum not in range(1, self.max_tuners + 1):
|
if int(stream_args["tuner"]) not in list(self.tuners.keys()):
|
||||||
raise TunerError("Tuner " + str(tunernum) + " does not exist.")
|
raise TunerError("Tuner " + str(stream_args["tuner"]) + " does not exist.")
|
||||||
eval("self.tuner_" + str(tunernum) + ".grab(stream_args)")
|
self.tuners[int(stream_args["tuner"])].grab(stream_args)
|
||||||
tunerselected = tunernum
|
tunerselected = int(stream_args["tuner"])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
for tunernum in range(1, self.max_tuners + 1):
|
for tunernum in range(1, self.max_tuners + 1):
|
||||||
try:
|
try:
|
||||||
eval("self.tuner_" + str(tunernum) + ".grab(stream_args)")
|
self.tuners[int(tunernum)].grab(stream_args)
|
||||||
except TunerError:
|
except TunerError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@ -83,18 +86,18 @@ class Tuners():
|
|||||||
return tunerselected
|
return tunerselected
|
||||||
|
|
||||||
def tuner_close(self, tunernum):
|
def tuner_close(self, tunernum):
|
||||||
eval("self.tuner_" + str(tunernum) + ".close()")
|
self.tuners[int(tunernum)].close()
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
all_status = {}
|
all_status = {}
|
||||||
for tunernum in range(1, self.max_tuners + 1):
|
for tunernum in range(1, self.max_tuners + 1):
|
||||||
all_status[tunernum] = eval("self.tuner_" + str(tunernum) + ".get_status()")
|
all_status[tunernum] = self.tuners[int(tunernum)].get_status()
|
||||||
return all_status
|
return all_status
|
||||||
|
|
||||||
def available_tuner_count(self):
|
def available_tuner_count(self):
|
||||||
available_tuners = 0
|
available_tuners = 0
|
||||||
for tunernum in range(1, self.max_tuners + 1):
|
for tunernum in range(1, self.max_tuners + 1):
|
||||||
tuner_status = eval("self.tuner_" + str(tunernum) + ".get_status()")
|
tuner_status = self.tuners[int(tunernum)].get_status()
|
||||||
if tuner_status["status"] == "Inactive":
|
if tuner_status["status"] == "Inactive":
|
||||||
available_tuners += 1
|
available_tuners += 1
|
||||||
return available_tuners
|
return available_tuners
|
||||||
@ -102,7 +105,7 @@ class Tuners():
|
|||||||
def inuse_tuner_count(self):
|
def inuse_tuner_count(self):
|
||||||
inuse_tuners = 0
|
inuse_tuners = 0
|
||||||
for tunernum in range(1, self.max_tuners + 1):
|
for tunernum in range(1, self.max_tuners + 1):
|
||||||
tuner_status = eval("self.tuner_" + str(tunernum) + ".get_status()")
|
tuner_status = self.tuners[int(tunernum)].get_status()
|
||||||
if tuner_status["status"] == "Active":
|
if tuner_status["status"] == "Active":
|
||||||
inuse_tuners += 1
|
inuse_tuners += 1
|
||||||
return inuse_tuners
|
return inuse_tuners
|
||||||
|
|||||||
@ -6,21 +6,20 @@ from fHDHR.exceptions import TunerError
|
|||||||
|
|
||||||
class WatchStream():
|
class WatchStream():
|
||||||
|
|
||||||
def __init__(self, settings, origserv, tuners, logger, web):
|
def __init__(self, fhdhr, origserv, tuners):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.origserv = origserv
|
self.origserv = origserv
|
||||||
self.tuners = tuners
|
self.tuners = tuners
|
||||||
self.web = web
|
|
||||||
|
|
||||||
def direct_stream(self, stream_args, tunernum):
|
def direct_stream(self, stream_args, tunernum):
|
||||||
|
|
||||||
chunksize = int(self.tuners.config.dict["direct_stream"]['chunksize'])
|
chunksize = int(self.fhdhr.config.dict["direct_stream"]['chunksize'])
|
||||||
|
|
||||||
if not stream_args["duration"] == 0:
|
if not stream_args["duration"] == 0:
|
||||||
stream_args["duration"] += time.time()
|
stream_args["duration"] += time.time()
|
||||||
|
|
||||||
req = self.web.session.get(stream_args["channelUri"], stream=True)
|
req = self.fhdhr.web.session.get(stream_args["channelUri"], stream=True)
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
try:
|
try:
|
||||||
@ -28,30 +27,23 @@ class WatchStream():
|
|||||||
|
|
||||||
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
||||||
req.close()
|
req.close()
|
||||||
self.logger.info("Requested Duration Expired.")
|
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||||
break
|
break
|
||||||
|
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
req.close()
|
req.close()
|
||||||
self.logger.info("Connection Closed.")
|
self.fhdhr.logger.info("Connection Closed.")
|
||||||
self.tuners.tuner_close(tunernum)
|
self.tuners.tuner_close(tunernum)
|
||||||
|
|
||||||
return generate()
|
return generate()
|
||||||
|
|
||||||
def ffmpeg_stream(self, stream_args, tunernum):
|
def ffmpeg_stream(self, stream_args, tunernum):
|
||||||
|
|
||||||
bytes_per_read = int(self.config.dict["ffmpeg"]["bytes_per_read"])
|
bytes_per_read = int(self.fhdhr.config.dict["ffmpeg"]["bytes_per_read"])
|
||||||
|
|
||||||
ffmpeg_command = [self.config.dict["ffmpeg"]["ffmpeg_path"],
|
ffmpeg_command = self.transcode_profiles(stream_args)
|
||||||
"-i", stream_args["channelUri"],
|
|
||||||
"-c", "copy",
|
|
||||||
"-f", "mpegts",
|
|
||||||
"-nostats", "-hide_banner",
|
|
||||||
"-loglevel", "fatal",
|
|
||||||
"pipe:stdout"
|
|
||||||
]
|
|
||||||
|
|
||||||
if not stream_args["duration"] == 0:
|
if not stream_args["duration"] == 0:
|
||||||
stream_args["duration"] += time.time()
|
stream_args["duration"] += time.time()
|
||||||
@ -65,25 +57,62 @@ class WatchStream():
|
|||||||
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
||||||
ffmpeg_proc.terminate()
|
ffmpeg_proc.terminate()
|
||||||
ffmpeg_proc.communicate()
|
ffmpeg_proc.communicate()
|
||||||
self.logger.info("Requested Duration Expired.")
|
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||||
break
|
break
|
||||||
|
|
||||||
videoData = ffmpeg_proc.stdout.read(bytes_per_read)
|
videoData = ffmpeg_proc.stdout.read(bytes_per_read)
|
||||||
if not videoData:
|
if not videoData:
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
|
||||||
yield videoData
|
yield videoData
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
ffmpeg_proc.terminate()
|
|
||||||
ffmpeg_proc.communicate()
|
|
||||||
self.logger.info("Connection Closed: " + str(e))
|
|
||||||
|
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
ffmpeg_proc.terminate()
|
ffmpeg_proc.terminate()
|
||||||
ffmpeg_proc.communicate()
|
ffmpeg_proc.communicate()
|
||||||
self.logger.info("Connection Closed.")
|
self.fhdhr.logger.info("Connection Closed.")
|
||||||
|
self.tuners.tuner_close(tunernum)
|
||||||
|
except Exception as e:
|
||||||
|
ffmpeg_proc.terminate()
|
||||||
|
ffmpeg_proc.communicate()
|
||||||
|
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||||
|
self.tuners.tuner_close(tunernum)
|
||||||
|
|
||||||
|
return generate()
|
||||||
|
|
||||||
|
def vlc_stream(self, stream_args, tunernum):
|
||||||
|
|
||||||
|
bytes_per_read = int(self.fhdhr.config.dict["vlc"]["bytes_per_read"])
|
||||||
|
|
||||||
|
vlc_command = self.transcode_profiles(stream_args)
|
||||||
|
|
||||||
|
if not stream_args["duration"] == 0:
|
||||||
|
stream_args["duration"] += time.time()
|
||||||
|
|
||||||
|
vlc_proc = subprocess.Popen(vlc_command, stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
def generate():
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
|
||||||
|
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
||||||
|
vlc_proc.terminate()
|
||||||
|
vlc_proc.communicate()
|
||||||
|
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||||
|
break
|
||||||
|
|
||||||
|
videoData = vlc_proc.stdout.read(bytes_per_read)
|
||||||
|
if not videoData:
|
||||||
|
break
|
||||||
|
yield videoData
|
||||||
|
|
||||||
|
except GeneratorExit:
|
||||||
|
vlc_proc.terminate()
|
||||||
|
vlc_proc.communicate()
|
||||||
|
self.fhdhr.logger.info("Connection Closed.")
|
||||||
|
self.tuners.tuner_close(tunernum)
|
||||||
|
except Exception as e:
|
||||||
|
vlc_proc.terminate()
|
||||||
|
vlc_proc.communicate()
|
||||||
|
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||||
self.tuners.tuner_close(tunernum)
|
self.tuners.tuner_close(tunernum)
|
||||||
|
|
||||||
return generate()
|
return generate()
|
||||||
@ -93,14 +122,16 @@ class WatchStream():
|
|||||||
try:
|
try:
|
||||||
tunernum = self.tuners.tuner_grab(stream_args)
|
tunernum = self.tuners.tuner_grab(stream_args)
|
||||||
except TunerError as e:
|
except TunerError as e:
|
||||||
self.logger.info("A " + stream_args["method"] + " stream request for channel " +
|
self.fhdhr.logger.info("A " + stream_args["method"] + " stream request for channel " +
|
||||||
str(stream_args["channel"]) + " was rejected do to " + str(e))
|
str(stream_args["channel"]) + " was rejected do to " + str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.info("Attempting a " + stream_args["method"] + " stream request for channel " + str(stream_args["channel"]))
|
self.fhdhr.logger.info("Attempting a " + stream_args["method"] + " stream request for channel " + str(stream_args["channel"]))
|
||||||
|
|
||||||
if stream_args["method"] == "ffmpeg":
|
if stream_args["method"] == "ffmpeg":
|
||||||
return self.ffmpeg_stream(stream_args, tunernum)
|
return self.ffmpeg_stream(stream_args, tunernum)
|
||||||
|
if stream_args["method"] == "vlc":
|
||||||
|
return self.vlc_stream(stream_args, tunernum)
|
||||||
elif stream_args["method"] == "direct":
|
elif stream_args["method"] == "direct":
|
||||||
return self.direct_stream(stream_args, tunernum)
|
return self.direct_stream(stream_args, tunernum)
|
||||||
|
|
||||||
@ -108,10 +139,106 @@ class WatchStream():
|
|||||||
|
|
||||||
stream_args["channelUri"] = self.origserv.get_channel_stream(str(stream_args["channel"]))
|
stream_args["channelUri"] = self.origserv.get_channel_stream(str(stream_args["channel"]))
|
||||||
if not stream_args["channelUri"]:
|
if not stream_args["channelUri"]:
|
||||||
self.logger.error("Could not Obtain Channel Stream.")
|
self.fhdhr.logger.error("Could not Obtain Channel Stream.")
|
||||||
stream_args["content_type"] = "video/mpeg"
|
stream_args["content_type"] = "video/mpeg"
|
||||||
else:
|
else:
|
||||||
channelUri_headers = self.web.session.head(stream_args["channelUri"]).headers
|
channelUri_headers = self.fhdhr.web.session.head(stream_args["channelUri"]).headers
|
||||||
stream_args["content_type"] = channelUri_headers['Content-Type']
|
stream_args["content_type"] = channelUri_headers['Content-Type']
|
||||||
|
|
||||||
return stream_args
|
return stream_args
|
||||||
|
|
||||||
|
def transcode_profiles(self, stream_args):
|
||||||
|
# TODO implement actual profiles here
|
||||||
|
"""
|
||||||
|
• heavy: transcode to AVC with the same resolution, frame-rate, and interlacing as the
|
||||||
|
original stream. For example 1080i60 AVC 1080i60, 720p60 AVC 720p60. → →
|
||||||
|
• mobile: trancode to AVC progressive not exceeding 1280x720 30fps.
|
||||||
|
• internet720: transcode to low bitrate AVC progressive not exceeding 1280x720 30fps.
|
||||||
|
• internet480: transcode to low bitrate AVC progressive not exceeding 848x480 30fps for
|
||||||
|
16:9 content, not exceeding 640x480 30fps for 4:3 content.
|
||||||
|
• internet360: transcode to low bitrate AVC progressive not exceeding 640x360 30fps for
|
||||||
|
16:9 content, not exceeding 480x360 30fps for 4:3 content.
|
||||||
|
• internet240: transcode to low bitrate AVC progressive not exceeding 432x240 30fps for
|
||||||
|
16:9 content, not exceeding 320x240 30fps for 4:3 content
|
||||||
|
"""
|
||||||
|
|
||||||
|
if stream_args["transcode"]:
|
||||||
|
self.fhdhr.logger.info("Client requested a " + stream_args["transcode"] + " transcode for stream.")
|
||||||
|
|
||||||
|
log_level = self.fhdhr.config.dict["logging"]["level"].lower()
|
||||||
|
|
||||||
|
if stream_args["method"] == "direct":
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif stream_args["method"] == "ffmpeg":
|
||||||
|
ffmpeg_command = [
|
||||||
|
self.fhdhr.config.dict["ffmpeg"]["ffmpeg_path"],
|
||||||
|
"-i", stream_args["channelUri"],
|
||||||
|
"-c", "copy",
|
||||||
|
"-f", "mpegts",
|
||||||
|
]
|
||||||
|
|
||||||
|
if not stream_args["transcode"]:
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "heavy":
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "mobile":
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet720":
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet480":
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet360":
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet240":
|
||||||
|
ffmpeg_command.extend([])
|
||||||
|
|
||||||
|
loglevel_dict = {
|
||||||
|
"debug": "debug",
|
||||||
|
"info": "info",
|
||||||
|
"error": "error",
|
||||||
|
"warning": "warning",
|
||||||
|
"critical": "fatal",
|
||||||
|
}
|
||||||
|
if log_level not in ["info", "debug"]:
|
||||||
|
ffmpeg_command.extend(["-nostats", "-hide_banner"])
|
||||||
|
ffmpeg_command.extend(["-loglevel", loglevel_dict[log_level]])
|
||||||
|
|
||||||
|
ffmpeg_command.extend(["pipe:stdout"])
|
||||||
|
return ffmpeg_command
|
||||||
|
|
||||||
|
elif stream_args["method"] == "vlc":
|
||||||
|
vlc_command = [
|
||||||
|
self.fhdhr.config.dict["vlc"]["vlc_path"],
|
||||||
|
"-I", "dummy", stream_args["channelUri"],
|
||||||
|
]
|
||||||
|
|
||||||
|
loglevel_dict = {
|
||||||
|
"debug": "3",
|
||||||
|
"info": "0",
|
||||||
|
"error": "1",
|
||||||
|
"warning": "2",
|
||||||
|
"critical": "1",
|
||||||
|
}
|
||||||
|
vlc_command.extend(["--log-verbose=", loglevel_dict[log_level]])
|
||||||
|
if log_level not in ["info", "debug"]:
|
||||||
|
vlc_command.extend(["--quiet"])
|
||||||
|
|
||||||
|
if not stream_args["transcode"]:
|
||||||
|
vlc_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "heavy":
|
||||||
|
vlc_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "mobile":
|
||||||
|
vlc_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet720":
|
||||||
|
vlc_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet480":
|
||||||
|
vlc_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet360":
|
||||||
|
vlc_command.extend([])
|
||||||
|
elif stream_args["transcode"] == "internet240":
|
||||||
|
vlc_command.extend([])
|
||||||
|
|
||||||
|
vlc_command.extend(["--sout", "#std{mux=ts,access=file,dst=-}"])
|
||||||
|
|
||||||
|
return vlc_command
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from flask import Flask
|
|||||||
from .pages import fHDHR_Pages
|
from .pages import fHDHR_Pages
|
||||||
from .files import fHDHR_Files
|
from .files import fHDHR_Files
|
||||||
from .api import fHDHR_API
|
from .api import fHDHR_API
|
||||||
|
from .watch import fHDHR_WATCH
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_HTTP_Server():
|
class fHDHR_HTTP_Server():
|
||||||
@ -23,6 +24,9 @@ class fHDHR_HTTP_Server():
|
|||||||
self.api = fHDHR_API(fhdhr)
|
self.api = fHDHR_API(fhdhr)
|
||||||
self.add_endpoints(self.api, "api")
|
self.add_endpoints(self.api, "api")
|
||||||
|
|
||||||
|
self.watch = fHDHR_WATCH(fhdhr)
|
||||||
|
self.add_endpoints(self.watch, "watch")
|
||||||
|
|
||||||
def add_endpoints(self, index_list, index_name):
|
def add_endpoints(self, index_list, index_name):
|
||||||
item_list = [x for x in dir(index_list) if self.isapath(x)]
|
item_list = [x for x in dir(index_list) if self.isapath(x)]
|
||||||
for item in item_list:
|
for item in item_list:
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from .channels import Channels
|
|||||||
from .lineup_post import Lineup_Post
|
from .lineup_post import Lineup_Post
|
||||||
from .xmltv import xmlTV
|
from .xmltv import xmlTV
|
||||||
from .m3u import M3U
|
from .m3u import M3U
|
||||||
|
from .epg import EPG
|
||||||
from .debug import Debug_JSON
|
from .debug import Debug_JSON
|
||||||
|
|
||||||
from .images import Images
|
from .images import Images
|
||||||
@ -18,6 +19,7 @@ class fHDHR_API():
|
|||||||
self.channels = Channels(fhdhr)
|
self.channels = Channels(fhdhr)
|
||||||
self.xmltv = xmlTV(fhdhr)
|
self.xmltv = xmlTV(fhdhr)
|
||||||
self.m3u = M3U(fhdhr)
|
self.m3u = M3U(fhdhr)
|
||||||
|
self.epg = EPG(fhdhr)
|
||||||
self.debug = Debug_JSON(fhdhr)
|
self.debug = Debug_JSON(fhdhr)
|
||||||
self.lineup_post = Lineup_Post(fhdhr)
|
self.lineup_post = Lineup_Post(fhdhr)
|
||||||
|
|
||||||
|
|||||||
48
fHDHR/http/api/epg.py
Normal file
48
fHDHR/http/api/epg.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from flask import Response, request, redirect
|
||||||
|
import urllib.parse
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class EPG():
|
||||||
|
"""Methods to create xmltv.xml"""
|
||||||
|
endpoints = ["/api/epg"]
|
||||||
|
endpoint_name = "api_epg"
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
method = request.args.get('method', default="get", type=str)
|
||||||
|
|
||||||
|
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str)
|
||||||
|
if source not in self.fhdhr.config.dict["main"]["valid_epg_methods"]:
|
||||||
|
return "%s Invalid xmltv method" % source
|
||||||
|
|
||||||
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
|
if method == "get":
|
||||||
|
|
||||||
|
epgdict = self.fhdhr.device.epg.get_epg(source)
|
||||||
|
epg_json = json.dumps(epgdict, indent=4)
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=epg_json,
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
elif method == "update":
|
||||||
|
self.fhdhr.device.epg.update(source)
|
||||||
|
|
||||||
|
elif method == "clearcache":
|
||||||
|
self.fhdhr.device.epg.clear_epg_cache(source)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return "%s Invalid Method" % method
|
||||||
|
|
||||||
|
if redirect_url:
|
||||||
|
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
||||||
|
else:
|
||||||
|
return "%s Success" % method
|
||||||
@ -47,6 +47,9 @@ class xmlTV():
|
|||||||
elif method == "update":
|
elif method == "update":
|
||||||
self.fhdhr.device.epg.update(source)
|
self.fhdhr.device.epg.update(source)
|
||||||
|
|
||||||
|
elif method == "clearcache":
|
||||||
|
self.fhdhr.device.epg.clear_epg_cache(source)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return "%s Invalid Method" % method
|
return "%s Invalid Method" % method
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,6 @@ from .discover_json import Discover_JSON
|
|||||||
from .lineup_json import Lineup_JSON
|
from .lineup_json import Lineup_JSON
|
||||||
from .lineup_status_json import Lineup_Status_JSON
|
from .lineup_status_json import Lineup_Status_JSON
|
||||||
|
|
||||||
from .watch import Watch
|
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_Files():
|
class fHDHR_Files():
|
||||||
|
|
||||||
@ -27,5 +25,3 @@ class fHDHR_Files():
|
|||||||
self.discover_json = Discover_JSON(fhdhr)
|
self.discover_json = Discover_JSON(fhdhr)
|
||||||
self.lineup_json = Lineup_JSON(fhdhr)
|
self.lineup_json = Lineup_JSON(fhdhr)
|
||||||
self.lineup_status_json = Lineup_Status_JSON(fhdhr)
|
self.lineup_status_json = Lineup_Status_JSON(fhdhr)
|
||||||
|
|
||||||
self.watch = Watch(fhdhr)
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class Lineup_XML():
|
|||||||
program_out = sub_el(out, 'Program')
|
program_out = sub_el(out, 'Program')
|
||||||
sub_el(program_out, 'GuideNumber', station_item['GuideNumber'])
|
sub_el(program_out, 'GuideNumber', station_item['GuideNumber'])
|
||||||
sub_el(program_out, 'GuideName', station_item['GuideName'])
|
sub_el(program_out, 'GuideName', station_item['GuideName'])
|
||||||
|
sub_el(program_out, 'Tags', ",".join(station_item['Tags']))
|
||||||
sub_el(program_out, 'URL', station_item['URL'])
|
sub_el(program_out, 'URL', station_item['URL'])
|
||||||
|
|
||||||
fakefile = BytesIO()
|
fakefile = BytesIO()
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class Diagnostics_HTML():
|
|||||||
["device.xml", "device.xml"],
|
["device.xml", "device.xml"],
|
||||||
["discover.json", "discover.json"],
|
["discover.json", "discover.json"],
|
||||||
["lineup.json", "lineup.json"],
|
["lineup.json", "lineup.json"],
|
||||||
|
["lineup.xml", "lineup.xml"],
|
||||||
["lineup_status.json", "lineup_status.json"],
|
["lineup_status.json", "lineup_status.json"],
|
||||||
["cluster.json", "/api/cluster?method=get"]
|
["cluster.json", "/api/cluster?method=get"]
|
||||||
]
|
]
|
||||||
|
|||||||
@ -31,19 +31,6 @@ class Guide_HTML():
|
|||||||
fakefile.write("<h4 id=\"mcetoc_1cdobsl3g0\" style=\"text-align: center;\"><span style=\"text-decoration: underline;\"><strong><em>What's On %s</em></strong></span></h4>\n" % friendlyname)
|
fakefile.write("<h4 id=\"mcetoc_1cdobsl3g0\" style=\"text-align: center;\"><span style=\"text-decoration: underline;\"><strong><em>What's On %s</em></strong></span></h4>\n" % friendlyname)
|
||||||
fakefile.write("\n")
|
fakefile.write("\n")
|
||||||
|
|
||||||
# a list of 2 part lists containing button information
|
|
||||||
button_list = [
|
|
||||||
["Force xmlTV Update", "/api/xmltv?method=update&redirect=%2Fguide"],
|
|
||||||
]
|
|
||||||
|
|
||||||
fakefile.write("<div style=\"text-align: center;\">\n")
|
|
||||||
for button_item in button_list:
|
|
||||||
button_label = button_item[0]
|
|
||||||
button_path = button_item[1]
|
|
||||||
fakefile.write(" <p><button onclick=\"OpenLink('%s')\">%s</a></button></p>\n" % (button_path, button_label))
|
|
||||||
fakefile.write("</div>\n")
|
|
||||||
fakefile.write("\n")
|
|
||||||
|
|
||||||
fakefile.write("<table style=\"width:100%\">\n")
|
fakefile.write("<table style=\"width:100%\">\n")
|
||||||
fakefile.write(" <tr>\n")
|
fakefile.write(" <tr>\n")
|
||||||
fakefile.write(" <th>Play</th>\n")
|
fakefile.write(" <th>Play</th>\n")
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class fHDHR_Page_Elements():
|
|||||||
"<button class=\"pull-left\" onclick=\"OpenLink('%s')\">%s</a></button>" % ("/version", "Version"),
|
"<button class=\"pull-left\" onclick=\"OpenLink('%s')\">%s</a></button>" % ("/version", "Version"),
|
||||||
"<button class=\"pull-left\" onclick=\"OpenLink('%s')\">%s</a></button>" % ("/diagnostics", "Diagnostics"),
|
"<button class=\"pull-left\" onclick=\"OpenLink('%s')\">%s</a></button>" % ("/diagnostics", "Diagnostics"),
|
||||||
|
|
||||||
"<a class=\"pull-right\" style=\"padding: 5px;\" href=\"%s\">%s</a>" % ("/api/xmltv?method=get&source=origin", "xmltv"),
|
"<a class=\"pull-right\" style=\"padding: 5px;\" href=\"%s\">%s</a>" % ("/api/xmltv?method=get&source=" + self.fhdhr.device.epg.def_method, "xmltv"),
|
||||||
"<a class=\"pull-right\" style=\"padding: 5px;\" href=\"%s\">%s</a>" % ("/api/m3u?method=get&channel=all", "m3u"),
|
"<a class=\"pull-right\" style=\"padding: 5px;\" href=\"%s\">%s</a>" % ("/api/m3u?method=get&channel=all", "m3u"),
|
||||||
|
|
||||||
"</div>",
|
"</div>",
|
||||||
|
|||||||
@ -34,6 +34,8 @@ class Streams_HTML():
|
|||||||
fakefile.write(" </tr>\n")
|
fakefile.write(" </tr>\n")
|
||||||
|
|
||||||
tuner_status = self.fhdhr.device.tuners.status()
|
tuner_status = self.fhdhr.device.tuners.status()
|
||||||
|
for tuner in list(tuner_status.keys()):
|
||||||
|
print(tuner_status[tuner])
|
||||||
for tuner in list(tuner_status.keys()):
|
for tuner in list(tuner_status.keys()):
|
||||||
fakefile.write(" <tr>\n")
|
fakefile.write(" <tr>\n")
|
||||||
fakefile.write(" <td>%s</td>\n" % (str(tuner)))
|
fakefile.write(" <td>%s</td>\n" % (str(tuner)))
|
||||||
|
|||||||
@ -36,7 +36,8 @@ class Version_HTML():
|
|||||||
["Python", sys.version],
|
["Python", sys.version],
|
||||||
["Operating System", self.fhdhr.config.dict["main"]["opersystem"]],
|
["Operating System", self.fhdhr.config.dict["main"]["opersystem"]],
|
||||||
["Using Docker", self.fhdhr.config.dict["main"]["isdocker"]],
|
["Using Docker", self.fhdhr.config.dict["main"]["isdocker"]],
|
||||||
["ffmpeg", self.fhdhr.config.dict["ffmpeg"]["version"]]
|
["ffmpeg", self.fhdhr.config.dict["ffmpeg"]["version"]],
|
||||||
|
["vlc", self.fhdhr.config.dict["vlc"]["version"]]
|
||||||
]
|
]
|
||||||
|
|
||||||
for item in table_guts:
|
for item in table_guts:
|
||||||
|
|||||||
@ -27,7 +27,8 @@ class xmlTV_HTML():
|
|||||||
fakefile.write("<table class=\"center\" style=\"width:50%\">\n")
|
fakefile.write("<table class=\"center\" style=\"width:50%\">\n")
|
||||||
fakefile.write(" <tr>\n")
|
fakefile.write(" <tr>\n")
|
||||||
fakefile.write(" <th>Version</th>\n")
|
fakefile.write(" <th>Version</th>\n")
|
||||||
fakefile.write(" <th>Link</th>\n")
|
fakefile.write(" <th>XMLTV Link</th>\n")
|
||||||
|
fakefile.write(" <th>EPG Link</th>\n")
|
||||||
fakefile.write(" <th>Options</th>\n")
|
fakefile.write(" <th>Options</th>\n")
|
||||||
fakefile.write(" </tr>\n")
|
fakefile.write(" </tr>\n")
|
||||||
|
|
||||||
@ -39,12 +40,16 @@ class xmlTV_HTML():
|
|||||||
fakefile.write(" <tr>\n")
|
fakefile.write(" <tr>\n")
|
||||||
fakefile.write(" <td>%s</td>\n" % (epg_method_name))
|
fakefile.write(" <td>%s</td>\n" % (epg_method_name))
|
||||||
fakefile.write(" <td><a href=\"%s\">%s</a>\n" % ("/api/xmltv?method=get&source=" + epg_method, epg_method_name))
|
fakefile.write(" <td><a href=\"%s\">%s</a>\n" % ("/api/xmltv?method=get&source=" + epg_method, epg_method_name))
|
||||||
|
fakefile.write(" <td><a href=\"%s\">%s</a>\n" % ("/api/epg?method=get&source=" + epg_method, epg_method_name))
|
||||||
|
|
||||||
fakefile.write(" <td>\n")
|
fakefile.write(" <td>\n")
|
||||||
fakefile.write(" <div>\n")
|
fakefile.write(" <div>\n")
|
||||||
fakefile.write(
|
fakefile.write(
|
||||||
" <button onclick=\"OpenLink('%s')\">%s</a></button>\n" %
|
" <button onclick=\"OpenLink('%s')\">%s</a></button>\n" %
|
||||||
("/api/xmltv?method=update&source=" + epg_method + "&redirect=%2Fxmltv", "Update"))
|
("/api/xmltv?method=update&source=" + epg_method + "&redirect=%2Fxmltv", "Update"))
|
||||||
|
fakefile.write(
|
||||||
|
" <button onclick=\"OpenLink('%s')\">%s</a></button>\n" %
|
||||||
|
("/api/xmltv?method=clearcache&source=" + epg_method + "&redirect=%2Fxmltv", "Clear Cache"))
|
||||||
fakefile.write(" </div>\n")
|
fakefile.write(" </div>\n")
|
||||||
fakefile.write(" </td>\n")
|
fakefile.write(" </td>\n")
|
||||||
|
|
||||||
|
|||||||
12
fHDHR/http/watch/__init__.py
Normal file
12
fHDHR/http/watch/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
from .watch import Watch
|
||||||
|
from .tuner import Tuner
|
||||||
|
|
||||||
|
|
||||||
|
class fHDHR_WATCH():
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.watch = Watch(fhdhr)
|
||||||
|
self.tuner = Tuner(fhdhr)
|
||||||
46
fHDHR/http/watch/tuner.py
Normal file
46
fHDHR/http/watch/tuner.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from flask import Response, request, stream_with_context, abort
|
||||||
|
|
||||||
|
|
||||||
|
class Tuner():
|
||||||
|
endpoints = ['/tuner<tuner>/<channel>']
|
||||||
|
endpoint_name = "tuner"
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
def __call__(self, tuner, channel, *args):
|
||||||
|
return self.get(tuner, channel, *args)
|
||||||
|
|
||||||
|
def get(self, tuner, channel, *args):
|
||||||
|
|
||||||
|
if channel.startswith("v"):
|
||||||
|
channel_number = channel.replace('v', '')
|
||||||
|
elif channel.startswith("ch"):
|
||||||
|
channel_freq = channel.replace('ch', '').split("-")[0]
|
||||||
|
subchannel = 0
|
||||||
|
if "-" in channel:
|
||||||
|
subchannel = channel.replace('ch', '').split("-")[1]
|
||||||
|
abort(503, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel)))
|
||||||
|
|
||||||
|
if channel_number not in list(self.fhdhr.device.channels.list.keys()):
|
||||||
|
abort(404, "Not Found")
|
||||||
|
|
||||||
|
base_url = request.url_root[:-1]
|
||||||
|
stream_args = {
|
||||||
|
"channel": channel_number,
|
||||||
|
"method": request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str),
|
||||||
|
"duration": request.args.get('duration', default=0, type=int),
|
||||||
|
"transcode": request.args.get('transcode', default=None, type=int),
|
||||||
|
"accessed": self.fhdhr.device.channels.get_fhdhr_stream_url(base_url, channel_number),
|
||||||
|
"tuner": tuner
|
||||||
|
}
|
||||||
|
stream_args = self.fhdhr.device.watch.get_stream_info(stream_args)
|
||||||
|
|
||||||
|
if not stream_args["channelUri"]:
|
||||||
|
abort(503, "Service Unavailable")
|
||||||
|
|
||||||
|
if stream_args["channelUri"]:
|
||||||
|
if stream_args["method"] == "direct":
|
||||||
|
return Response(self.fhdhr.device.watch.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||||
|
elif stream_args["method"] == "ffmpeg":
|
||||||
|
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
||||||
@ -13,17 +13,36 @@ class Watch():
|
|||||||
|
|
||||||
def get(self, channel, *args):
|
def get(self, channel, *args):
|
||||||
|
|
||||||
|
if channel.startswith("v"):
|
||||||
|
channel_number = channel.replace('v', '')
|
||||||
|
elif channel.startswith("ch"):
|
||||||
|
channel_freq = channel.replace('ch', '').split("-")[0]
|
||||||
|
subchannel = 0
|
||||||
|
if "-" in channel:
|
||||||
|
subchannel = channel.replace('ch', '').split("-")[1]
|
||||||
|
abort(503, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel)))
|
||||||
|
|
||||||
|
if channel_number not in list(self.fhdhr.device.channels.list.keys()):
|
||||||
|
abort(404, "Not Found")
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
base_url = request.url_root[:-1]
|
||||||
stream_args = {
|
stream_args = {
|
||||||
"channel": channel.replace('v', ''),
|
"channel": channel_number,
|
||||||
"method": request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str),
|
"method": request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str),
|
||||||
"duration": request.args.get('duration', default=0, type=int),
|
"duration": request.args.get('duration', default=0, type=int),
|
||||||
"accessed": self.fhdhr.device.channels.get_fhdhr_stream_url(base_url, channel.replace('v', '')),
|
"transcode": request.args.get('transcode', default=None, type=int),
|
||||||
|
"accessed": self.fhdhr.device.channels.get_fhdhr_stream_url(base_url, channel_number),
|
||||||
|
"tuner": None
|
||||||
}
|
}
|
||||||
stream_args = self.fhdhr.device.watch.get_stream_info(stream_args)
|
stream_args = self.fhdhr.device.watch.get_stream_info(stream_args)
|
||||||
|
|
||||||
|
if not stream_args["channelUri"]:
|
||||||
|
abort(503, "Service Unavailable")
|
||||||
|
|
||||||
if stream_args["channelUri"]:
|
if stream_args["channelUri"]:
|
||||||
if stream_args["method"] == "direct":
|
if stream_args["method"] == "direct":
|
||||||
return Response(self.fhdhr.device.watch.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True)
|
return Response(self.fhdhr.device.watch.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||||
elif stream_args["method"] == "ffmpeg":
|
elif stream_args["method"] == "ffmpeg":
|
||||||
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
||||||
abort(503)
|
elif stream_args["method"] == "vlc":
|
||||||
|
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
||||||
@ -26,12 +26,10 @@ class OriginChannels_StandIN():
|
|||||||
|
|
||||||
class OriginServiceWrapper():
|
class OriginServiceWrapper():
|
||||||
|
|
||||||
def __init__(self, settings, logger, web, db):
|
def __init__(self, fhdhr):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.web = web
|
|
||||||
|
|
||||||
self.servicename = settings.dict["main"]["servicename"]
|
self.servicename = fhdhr.config.dict["main"]["servicename"]
|
||||||
|
|
||||||
self.setup_success = None
|
self.setup_success = None
|
||||||
self.setup()
|
self.setup()
|
||||||
@ -39,16 +37,16 @@ class OriginServiceWrapper():
|
|||||||
def setup(self):
|
def setup(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.origin = OriginService(self.config, self.logger, self.web)
|
self.origin = OriginService(self.fhdhr)
|
||||||
self.setup_success = True
|
self.setup_success = True
|
||||||
self.logger.info("%s Setup Success" % self.servicename)
|
self.fhdhr.logger.info("%s Setup Success" % self.servicename)
|
||||||
except fHDHR.exceptions.OriginSetupError as e:
|
except fHDHR.exceptions.OriginSetupError as e:
|
||||||
self.logger.error(e)
|
self.fhdhr.logger.error(e)
|
||||||
self.setup_success = False
|
self.setup_success = False
|
||||||
|
|
||||||
if self.setup_success:
|
if self.setup_success:
|
||||||
self.channels = OriginChannels(self.config, self.origin, self.logger, self.web)
|
self.channels = OriginChannels(self.fhdhr, self.origin)
|
||||||
self.epg = OriginEPG(self.config, self.logger, self.web)
|
self.epg = OriginEPG(self.fhdhr)
|
||||||
else:
|
else:
|
||||||
self.channels = OriginChannels_StandIN()
|
self.channels = OriginChannels_StandIN()
|
||||||
self.epg = OriginEPG_StandIN()
|
self.epg = OriginEPG_StandIN()
|
||||||
@ -83,7 +81,13 @@ class OriginServiceWrapper():
|
|||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
''' will only get called for undefined attributes '''
|
''' will only get called for undefined attributes '''
|
||||||
|
if hasattr(self.fhdhr, name):
|
||||||
|
return eval("self.fhdhr." + name)
|
||||||
if hasattr(self.origin, name):
|
if hasattr(self.origin, name):
|
||||||
return eval("self.origin." + name)
|
return eval("self.origin." + name)
|
||||||
elif hasattr(self.channels, name):
|
elif hasattr(self.channels, name):
|
||||||
return eval("self.channels." + name)
|
return eval("self.channels." + name)
|
||||||
|
elif hasattr(self.epg, name):
|
||||||
|
return eval("self.epg." + name)
|
||||||
|
else:
|
||||||
|
raise AttributeError(name)
|
||||||
|
|||||||
@ -4,26 +4,24 @@ import json
|
|||||||
|
|
||||||
class OriginChannels():
|
class OriginChannels():
|
||||||
|
|
||||||
def __init__(self, settings, origin, logger, web):
|
def __init__(self, fhdhr, origin):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.logger = logger
|
|
||||||
self.web = web
|
|
||||||
|
|
||||||
def get_channels(self):
|
def get_channels(self):
|
||||||
|
|
||||||
data_url = ('%s%s:%s/service?method=channel.list&sid=%s' %
|
data_url = ('%s%s:%s/service?method=channel.list&sid=%s' %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
self.origin.sid
|
self.origin.sid
|
||||||
))
|
))
|
||||||
|
|
||||||
data_req = self.origin.web.session.get(data_url)
|
data_req = self.fhdhr.web.session.get(data_url)
|
||||||
data_dict = xmltodict.parse(data_req.content)
|
data_dict = xmltodict.parse(data_req.content)
|
||||||
|
|
||||||
if 'channels' not in list(data_dict['rsp'].keys()):
|
if 'channels' not in list(data_dict['rsp'].keys()):
|
||||||
self.logger.error("Could not retrieve channel list")
|
self.fhdhr.logger.error("Could not retrieve channel list")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
channel_o_list = data_dict['rsp']['channels']['channel']
|
channel_o_list = data_dict['rsp']['channels']['channel']
|
||||||
@ -47,9 +45,9 @@ class OriginChannels():
|
|||||||
streamlist = []
|
streamlist = []
|
||||||
streamdict = {}
|
streamdict = {}
|
||||||
streamurl = ('%s%s:%s/live?channel=%s&client=%s' %
|
streamurl = ('%s%s:%s/live?channel=%s&client=%s' %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
str(chandict["number"]),
|
str(chandict["number"]),
|
||||||
str(chandict["number"]),
|
str(chandict["number"]),
|
||||||
))
|
))
|
||||||
|
|||||||
@ -6,26 +6,24 @@ import fHDHR.tools
|
|||||||
|
|
||||||
class OriginEPG():
|
class OriginEPG():
|
||||||
|
|
||||||
def __init__(self, settings, logger, web):
|
def __init__(self, fhdhr):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.web = web
|
|
||||||
|
|
||||||
def get_channel_thumbnail(self, channel_id):
|
def get_channel_thumbnail(self, channel_id):
|
||||||
channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" %
|
channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
str(channel_id)
|
str(channel_id)
|
||||||
))
|
))
|
||||||
return channel_thumb_url
|
return channel_thumb_url
|
||||||
|
|
||||||
def get_content_thumbnail(self, content_id):
|
def get_content_thumbnail(self, content_id):
|
||||||
item_thumb_url = ("%s%s:%s/service?method=channel.show.artwork&sid=%s&event_id=%s" %
|
item_thumb_url = ("%s%s:%s/service?method=channel.show.artwork&sid=%s&event_id=%s" %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
self.config.dict["origin"]["sid"],
|
self.fhdhr.config.dict["origin"]["sid"],
|
||||||
str(content_id)
|
str(content_id)
|
||||||
))
|
))
|
||||||
return item_thumb_url
|
return item_thumb_url
|
||||||
@ -57,12 +55,12 @@ class OriginEPG():
|
|||||||
}
|
}
|
||||||
|
|
||||||
epg_url = ('%s%s:%s/service?method=channel.listings&channel_id=%s' %
|
epg_url = ('%s%s:%s/service?method=channel.listings&channel_id=%s' %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
str(cdict["id"]),
|
str(cdict["id"]),
|
||||||
))
|
))
|
||||||
epg_req = self.web.session.get(epg_url)
|
epg_req = self.fhdhr.web.session.get(epg_url)
|
||||||
epg_dict = xmltodict.parse(epg_req.content)
|
epg_dict = xmltodict.parse(epg_req.content)
|
||||||
|
|
||||||
for program_listing in epg_dict["rsp"]["listings"]:
|
for program_listing in epg_dict["rsp"]["listings"]:
|
||||||
|
|||||||
@ -7,48 +7,47 @@ import fHDHR.exceptions
|
|||||||
|
|
||||||
class OriginService():
|
class OriginService():
|
||||||
|
|
||||||
def __init__(self, settings, logger, web):
|
def __init__(self, fhdhr):
|
||||||
self.config = settings
|
self.fhdhr = fhdhr
|
||||||
self.logger = logger
|
|
||||||
self.web = web
|
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
self.logger.info("Logging into NextPVR")
|
self.fhdhr.logger.info("Logging into NextPVR")
|
||||||
self.sid = self.get_sid()
|
self.sid = self.get_sid()
|
||||||
if not self.sid:
|
if not self.sid:
|
||||||
raise fHDHR.exceptions.OriginSetupError("NextPVR Login Failed")
|
raise fHDHR.exceptions.OriginSetupError("NextPVR Login Failed")
|
||||||
else:
|
else:
|
||||||
self.logger.info("NextPVR Login Success")
|
self.fhdhr.logger.info("NextPVR Login Success")
|
||||||
self.config.write(self.config.dict["main"]["dictpopname"], 'sid', self.sid)
|
self.fhdhr.config.write(self.fhdhr.config.dict["main"]["dictpopname"], 'sid', self.sid)
|
||||||
|
|
||||||
def get_sid(self):
|
def get_sid(self):
|
||||||
if self.config.dict["origin"]["sid"]:
|
if self.fhdhr.config.dict["origin"]["sid"]:
|
||||||
return self.config.dict["origin"]["sid"]
|
return self.fhdhr.config.dict["origin"]["sid"]
|
||||||
|
|
||||||
initiate_url = ('%s%s:%s/service?method=session.initiate&ver=1.0&device=fhdhr' %
|
initiate_url = ('%s%s:%s/service?method=session.initiate&ver=1.0&device=fhdhr' %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
))
|
))
|
||||||
|
|
||||||
initiate_req = self.web.session.get(initiate_url)
|
initiate_req = self.fhdhr.web.session.get(initiate_url)
|
||||||
initiate_dict = xmltodict.parse(initiate_req.content)
|
initiate_dict = xmltodict.parse(initiate_req.content)
|
||||||
|
|
||||||
sid = initiate_dict['rsp']['sid']
|
sid = initiate_dict['rsp']['sid']
|
||||||
salt = initiate_dict['rsp']['salt']
|
salt = initiate_dict['rsp']['salt']
|
||||||
md5PIN = hashlib.md5(str(self.config.dict["origin"]['pin']).encode('utf-8')).hexdigest()
|
md5PIN = hashlib.md5(str(self.fhdhr.config.dict["origin"]['pin']).encode('utf-8')).hexdigest()
|
||||||
string = ':%s:%s' % (md5PIN, salt)
|
string = ':%s:%s' % (md5PIN, salt)
|
||||||
clientKey = hashlib.md5(string.encode('utf-8')).hexdigest()
|
clientKey = hashlib.md5(string.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
login_url = ('%s%s:%s/service?method=session.login&sid=%s&md5=%s' %
|
login_url = ('%s%s:%s/service?method=session.login&sid=%s&md5=%s' %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
sid,
|
sid,
|
||||||
clientKey
|
clientKey
|
||||||
))
|
))
|
||||||
login_req = self.web.session.get(login_url)
|
login_req = self.fhdhr.web.session.get(login_url)
|
||||||
login_dict = xmltodict.parse(login_req.content)
|
login_dict = xmltodict.parse(login_req.content)
|
||||||
|
|
||||||
loginsuccess = None
|
loginsuccess = None
|
||||||
@ -60,9 +59,9 @@ class OriginService():
|
|||||||
|
|
||||||
def get_status_dict(self):
|
def get_status_dict(self):
|
||||||
nextpvr_address = ('%s%s:%s' %
|
nextpvr_address = ('%s%s:%s' %
|
||||||
("https://" if self.config.dict["origin"]["ssl"] else "http://",
|
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
||||||
self.config.dict["origin"]["address"],
|
self.fhdhr.config.dict["origin"]["address"],
|
||||||
str(self.config.dict["origin"]["port"]),
|
str(self.fhdhr.config.dict["origin"]["port"]),
|
||||||
))
|
))
|
||||||
ret_status_dict = {
|
ret_status_dict = {
|
||||||
"Login": "Success",
|
"Login": "Success",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user