1
0
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:
Deathbybandaid 2020-11-11 12:47:33 -05:00 committed by GitHub
commit 43493258e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 670 additions and 342 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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