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

Code Restructure, Channel/EPG Enhancements

This commit is contained in:
deathbybandaid 2020-12-07 13:21:53 -05:00
parent f8cee0867e
commit daa5ac92cb
30 changed files with 261 additions and 163 deletions

View File

@ -0,0 +1,2 @@
# pylama:ignore=W0401,W0611
from .zap2it import *

View File

@ -4,6 +4,16 @@
"value": "pass",
"config_file": true,
"config_web": true
},
"method":{
"value": "blocks",
"config_file": true,
"config_web": true
},
"update_frequency":{
"value": 14400,
"config_file": true,
"config_web": true
}
}
}

View File

@ -49,13 +49,26 @@
"value": true,
"config_file": true,
"config_web": true
}
},
"web_ui":{
"theme":{
"value": "none",
"config_file": true,
"config_web": true
}
},
"friendlyname":{
"value": "fHDHR",
"config_file": true,
"config_web": true
},
"stream_type":{
"value": "direct",
"config_file": true,
"config_web": true
},
"tuner_count":{
"value": 4,
"config_file": true,
"config_web": true
},
"reporting_firmware_name":{
"value": "fHDHR",
"config_file": true,
"config_web": true
}
}
}

View File

@ -14,6 +14,31 @@
"value": "multiprocessing",
"config_file": true,
"config_web": true
},
"servicename":{
"value": "fHDHR",
"config_file": false,
"config_web": false
},
"dictpopname":{
"value": "fHDHR",
"config_file": false,
"config_web": false
},
"reponame":{
"value": "fHDHR",
"config_file": false,
"config_web": false
},
"valid_epg_methods":{
"value": "None,blocks",
"config_file": false,
"config_web": false
},
"required":{
"value": "none",
"config_file": false,
"config_web": false
}
}
}

View File

@ -0,0 +1,9 @@
{
"web_ui":{
"theme":{
"value": "none",
"config_file": true,
"config_web": true
}
}
}

View File

@ -1,6 +1,6 @@
# coding=utf-8
from .origin import OriginServiceWrapper
from .originwrapper import OriginServiceWrapper
from .device import fHDHR_Device
import fHDHR.tools
@ -21,12 +21,12 @@ class fHDHR_INT_OBJ():
class fHDHR_OBJ():
def __init__(self, settings, logger, db):
def __init__(self, settings, logger, db, alternative_epg, origin):
self.fhdhr = fHDHR_INT_OBJ(settings, logger, db)
self.origin = OriginServiceWrapper(self.fhdhr)
self.originwrapper = OriginServiceWrapper(self.fhdhr, origin)
self.device = fHDHR_Device(self.fhdhr, self.origin)
self.device = fHDHR_Device(self.fhdhr, self.originwrapper, alternative_epg)
def __getattr__(self, name):
''' will only get called for undefined attributes '''

View File

@ -38,9 +38,9 @@ def get_configuration(args, script_dir):
return fHDHR.config.Config(args.cfg, script_dir)
def run(settings, logger, db):
def run(settings, logger, db, alternative_epg, origin):
fhdhr = fHDHR_OBJ(settings, logger, db)
fhdhr = fHDHR_OBJ(settings, logger, db, alternative_epg, origin)
fhdhrweb = fHDHR_HTTP_Server(fhdhr)
try:
@ -81,7 +81,7 @@ def run(settings, logger, db):
return ERR_CODE
def start(args, script_dir):
def start(args, script_dir, alternative_epg, origin):
"""Get Configuration for fHDHR and start"""
try:
@ -94,17 +94,17 @@ def start(args, script_dir):
db = fHDHRdb(settings)
return run(settings, logger, db)
return run(settings, logger, db, alternative_epg, origin)
def main(script_dir):
def main(script_dir, alternative_epg, origin):
"""fHDHR run script entry point"""
print("Loading fHDHR " + fHDHR_VERSION)
try:
args = build_args_parser()
return start(args, script_dir)
return start(args, script_dir, alternative_epg, origin)
except KeyboardInterrupt:
print("\n\nInterrupted")
return ERR_CODE

View File

@ -32,6 +32,8 @@ class Config():
self.internal["paths"] = {
"script_dir": script_dir,
"data_dir": data_dir,
"alternative_epg": pathlib.Path(script_dir).joinpath('alternative_epg'),
"origin": pathlib.Path(script_dir).joinpath('origin'),
"cache_dir": pathlib.Path(data_dir).joinpath('cache'),
"internal_config": pathlib.Path(data_dir).joinpath('internal_config'),
"www_dir": www_dir,
@ -44,6 +46,13 @@ class Config():
if str(conffilepath).endswith(".json"):
self.read_json_config(conffilepath)
for dir_type in ["alternative_epg", "origin"]:
for file_item in os.listdir(self.internal["paths"][dir_type]):
file_item_path = os.path.join(self.internal["paths"][dir_type], file_item)
if str(file_item_path).endswith("_conf.json"):
self.read_json_config(file_item_path)
print("Loading Configuration File: " + str(self.config_file))
self.read_ini_config(self.config_file)

View File

@ -8,11 +8,11 @@ from .cluster import fHDHR_Cluster
class fHDHR_Device():
def __init__(self, fhdhr, origin):
def __init__(self, fhdhr, originwrapper, alternative_epg):
self.channels = Channels(fhdhr, origin)
self.channels = Channels(fhdhr, originwrapper)
self.epg = EPG(fhdhr, self.channels, origin)
self.epg = EPG(fhdhr, self.channels, originwrapper, alternative_epg)
self.tuners = Tuners(fhdhr, self.epg, self.channels)

View File

@ -9,10 +9,10 @@ from .chan_ident import Channel_IDs
class Channels():
def __init__(self, fhdhr, origin):
def __init__(self, fhdhr, originwrapper):
self.fhdhr = fhdhr
self.origin = origin
self.origin = originwrapper
self.id_system = Channel_IDs(fhdhr)

View File

@ -12,15 +12,38 @@ class Channel():
channel_id = id_system.get(origin_id)
else:
channel_id = id_system.assign()
self.dict = self.fhdhr.db.get_channel_value(str(channel_id), "dict") or self.default_dict(channel_id)
self.channel_id = channel_id
self.dict = self.fhdhr.db.get_channel_value(str(channel_id), "dict") or self.default_dict
self.verify_dict()
self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict)
@property
def thumbnail(self):
if str(self.dict["thumbnail"]).lower() in ["none"]:
return self.generic_image_url
elif self.dict["thumbnail"]:
return self.dict["thumbnail"]
elif self.dict["origin_thumbnail"]:
return self.dict["origin_thumbnail"]
else:
return self.generic_image_url
@property
def epgdict(self):
return {
"callsign": self.dict["callsign"],
"name": self.dict["name"],
"number": self.dict["number"],
"id": self.dict["origin_id"],
"thumbnail": self.dict["thumbnail"],
"listing": [],
}
def verify_dict(self):
"""Development Purposes
Add new Channel dict keys
"""
default_dict = self.default_dict(self.dict["id"])
default_dict = self.default_dict
for key in list(default_dict.keys()):
if key not in list(self.dict.keys()):
self.dict[key] = default_dict[key]
@ -68,9 +91,10 @@ class Channel():
self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict)
def default_dict(self, channel_id):
@property
def default_dict(self):
return {
"id": str(channel_id), "origin_id": None,
"id": str(self.channel_id), "origin_id": None,
"name": None, "origin_name": None,
"callsign": None, "origin_callsign": None,
"number": None, "origin_number": None,
@ -94,21 +118,28 @@ class Channel():
self.dict[key] = updatedict[key]
self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict)
@property
def lineup_dict(self):
return {
'GuideNumber': self.dict['number'],
'GuideName': self.dict['name'],
'Tags': ",".join(self.dict['tags']),
'URL': self.stream_url(),
'URL': self.stream_url,
'HD': self.dict["HD"],
"Favorite": self.dict["favorite"],
}
def stream_url(self):
return ('/auto/v%s' % self.dict['number'])
@property
def generic_image_url(self):
return "/api/images?method=generate&type=channel&message=%s" % self.dict["number"]
@property
def stream_url(self):
return '/auto/v%s' % self.dict['number']
@property
def play_url(self):
return ('/api/m3u?method=get&channel=%s' % self.dict['number'])
return '/api/m3u?method=get&channel=%s' % self.dict['number']
def set_favorite(self, enablement):
if enablement == "+":

View File

@ -3,30 +3,30 @@ import time
import datetime
from collections import OrderedDict
epgtype_list = []
device_dir = os.path.dirname(__file__)
for entry in os.scandir(device_dir + '/epgtypes'):
if entry.is_file():
if entry.name[0] != '_':
epgtype_list.append(str(entry.name[:-3]))
impstring = f'from .epgtypes import {entry.name}'[:-3]
exec(impstring)
from .blocks import blocksEPG
class EPG():
def __init__(self, fhdhr, channels, origin):
def __init__(self, fhdhr, channels, originwrapper, alternative_epg):
self.fhdhr = fhdhr
self.origin = origin
self.origin = originwrapper
self.channels = channels
self.alternative_epg = alternative_epg
self.epgdict = {}
self.epg_method_selfadd()
self.epg_methods = self.fhdhr.config.dict["epg"]["method"]
self.valid_epg_methods = [x for x in self.fhdhr.config.dict["main"]["valid_epg_methods"] if x and x not in [None, "None"]]
self.blocks = blocksEPG(self.fhdhr, self.channels)
self.epg_handling = {
"origin": self.origin,
"blocks": self.blocks,
}
self.epg_method_selfadd()
self.def_method = self.fhdhr.config.dict["epg"]["def_method"]
self.sleeptime = {}
for epg_method in self.epg_methods:
@ -50,10 +50,8 @@ class EPG():
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 hasattr(self.epg_handling[method], 'clear_cache'):
self.epg_handling[method].clear_cache()
if method in list(self.epgdict.keys()):
del self.epgdict[method]
@ -134,17 +132,23 @@ class EPG():
return next(item for item in event_list if item["id"] == event_id)
def epg_method_selfadd(self):
self.fhdhr.logger.info("Checking for Optional EPG methods.")
for method in epgtype_list:
self.fhdhr.logger.info("Checking for Alternative EPG methods.")
new_epgtype_list = []
for entry in os.scandir(self.fhdhr.config.internal["paths"]["alternative_epg"]):
if entry.is_file():
if entry.name[0] != '_' and entry.name.endswith(".py"):
new_epgtype_list.append(str(entry.name[:-3]))
for method in new_epgtype_list:
self.fhdhr.logger.info("Found %s EPG method." % method)
exec("%s = %s" % ("self." + str(method), str(method) + "." + str(method) + "EPG(self.fhdhr, self.channels)"))
self.epg_handling[method] = eval("self.alternative_epg.%s.%sEPG(self.fhdhr, self.channels)" % (method, method))
def update(self, method=None):
if not method:
method = self.def_method
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
if (not method or
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
method = self.def_method
if method == self.fhdhr.config.dict["main"]["dictpopname"]:
method = "origin"
epgtypename = method
@ -152,12 +156,10 @@ class EPG():
epgtypename = self.fhdhr.config.dict["main"]["dictpopname"]
self.fhdhr.logger.info("Updating " + epgtypename + " EPG cache.")
method_to_call = getattr(self, method)
func_to_call = getattr(method_to_call, 'update_epg')
if method == 'origin':
programguide = func_to_call(self.channels)
programguide = self.epg_handling['origin'].update_epg(self.channels)
else:
programguide = func_to_call()
programguide = self.epg_handling[method].update_epg()
for chan in list(programguide.keys()):
floatnum = str(float(chan))

View File

@ -0,0 +1,74 @@
import datetime
class blocksEPG():
def __init__(self, fhdhr, channels):
self.fhdhr = fhdhr
self.channels = channels
def update_epg(self):
programguide = {}
timestamps = self.timestamps
for fhdhr_id in list(self.channels.list.keys()):
chan_obj = self.channels.list[fhdhr_id]
if str(chan_obj.dict["number"]) not in list(programguide.keys()):
programguide[str(chan_obj.dict["number"])] = chan_obj.epgdict
clean_prog_dicts = self.empty_channel_epg(timestamps, chan_obj)
for clean_prog_dict in clean_prog_dicts:
programguide[str(chan_obj.dict["number"])]["listing"].append(clean_prog_dict)
return programguide
def get_content_thumbnail(self, content_id):
return "/api/images?method=generate&type=content&message=%s" % content_id
@property
def timestamps(self):
timestamps = []
todaydate = datetime.date.today()
for x in range(0, 6):
xdate = todaydate + datetime.timedelta(days=x)
xtdate = xdate + datetime.timedelta(days=1)
for hour in range(0, 24):
time_start = datetime.datetime.combine(xdate, datetime.time(hour, 0))
if hour + 1 < 24:
time_end = datetime.datetime.combine(xdate, datetime.time(hour + 1, 0))
else:
time_end = datetime.datetime.combine(xtdate, datetime.time(0, 0))
timestampdict = {
"time_start": str(time_start.strftime('%Y%m%d%H%M%S')) + " +0000",
"time_end": str(time_end.strftime('%Y%m%d%H%M%S')) + " +0000",
}
timestamps.append(timestampdict)
return timestamps
def empty_channel_epg(self, timestamps, chan_obj):
clean_prog_dicts = []
for timestamp in timestamps:
content_id = "%s_%s" % (chan_obj.dict["origin_id"], str(timestamp['time_start']).split(" ")[0])
clean_prog_dict = {
"time_start": timestamp['time_start'],
"time_end": timestamp['time_end'],
"duration_minutes": 60,
"thumbnail": chan_obj.dict["thumbnail"] or self.get_content_thumbnail(content_id),
"title": "Unavailable",
"sub-title": "Unavailable",
"description": "Unavailable",
"rating": "N/A",
"episodetitle": None,
"releaseyear": None,
"genres": [],
"seasonnumber": None,
"episodenumber": None,
"isnew": False,
"id": content_id,
}
clean_prog_dicts.append(clean_prog_dict)
return clean_prog_dicts

View File

@ -1,66 +0,0 @@
import datetime
class blocksEPG():
def __init__(self, fhdhr, channels):
self.fhdhr = fhdhr
self.channels = channels
def update_epg(self):
programguide = {}
timestamps = []
todaydate = datetime.date.today()
for x in range(0, 6):
xdate = todaydate + datetime.timedelta(days=x)
xtdate = xdate + datetime.timedelta(days=1)
for hour in range(0, 24):
time_start = datetime.datetime.combine(xdate, datetime.time(hour, 0))
if hour + 1 < 24:
time_end = datetime.datetime.combine(xdate, datetime.time(hour + 1, 0))
else:
time_end = datetime.datetime.combine(xtdate, datetime.time(0, 0))
timestampdict = {
"time_start": str(time_start.strftime('%Y%m%d%H%M%S')) + " +0000",
"time_end": str(time_end.strftime('%Y%m%d%H%M%S')) + " +0000",
}
timestamps.append(timestampdict)
for fhdhr_id in list(self.channels.list.keys()):
c = self.channels.list[fhdhr_id].dict
if str(c["number"]) not in list(programguide.keys()):
programguide[str(c["number"])] = {
"callsign": c["callsign"],
"name": c["name"],
"number": c["number"],
"id": c["origin_id"],
"thumbnail": ("/api/images?method=generate&type=channel&message=%s" % (str(c['number']))),
"listing": [],
}
for timestamp in timestamps:
clean_prog_dict = {
"time_start": timestamp['time_start'],
"time_end": timestamp['time_end'],
"duration_minutes": 60,
"thumbnail": ("/api/images?method=generate&type=content&message=%s" % (str(c["origin_id"]) + "_" + str(timestamp['time_start']).split(" ")[0])),
"title": "Unavailable",
"sub-title": "Unavailable",
"description": "Unavailable",
"rating": "N/A",
"episodetitle": None,
"releaseyear": None,
"genres": [],
"seasonnumber": None,
"episodenumber": None,
"isnew": False,
"id": str(c["origin_id"]) + "_" + str(timestamp['time_start']).split(" ")[0],
}
programguide[str(c["number"])]["listing"].append(clean_prog_dict)
return programguide

View File

@ -24,8 +24,8 @@ class Channels():
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
channel_dict = channel_obj.dict.copy()
channel_dict["play_url"] = channel_obj.play_url()
channel_dict["stream_url"] = channel_obj.stream_url()
channel_dict["play_url"] = channel_obj.play_url
channel_dict["stream_url"] = channel_obj.stream_url
channels_info.append(channel_dict)
channels_info_json = json.dumps(channels_info, indent=4)

View File

@ -75,7 +75,7 @@ class M3U():
"group-title=\"" + self.fhdhr.config.dict["fhdhr"]["friendlyname"] + "\"," + str(channel_obj.dict['name']))
)
fakefile.write("%s\n" % (base_url + channel_obj.stream_url()))
fakefile.write("%s%s\n" % (base_url, channel_obj.stream_url))
channels_m3u = fakefile.getvalue()

View File

@ -22,7 +22,7 @@ class Lineup_JSON():
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
if channel_obj.enabled or show == "found":
lineup_dict = channel_obj.lineup_dict()
lineup_dict = channel_obj.lineup_dict
lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"])
if show == "found" and channel_obj.enabled:
lineup_dict["Enabled"] = 1

View File

@ -26,7 +26,7 @@ class Lineup_XML():
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
if channel_obj.enabled or show == "found":
program_out = sub_el(out, 'Program')
lineup_dict = channel_obj.lineup_dict()
lineup_dict = channel_obj.lineup_dict
lineup_dict["URL"] = base_url + lineup_dict["URL"]
if show == "found" and channel_obj.enabled:
lineup_dict["Enabled"] = 1

View File

@ -17,7 +17,7 @@ class Channels_Editor_HTML():
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
channel_dict = channel_obj.dict.copy()
channel_dict["play_url"] = channel_obj.play_url()
channel_dict["play_url"] = channel_obj.play_url
channelslist.append(channel_dict)
return render_template('channels_editor.html', request=request, fhdhr=self.fhdhr, channelslist=channelslist)

View File

@ -22,7 +22,7 @@ class Channels_HTML():
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
channel_dict = channel_obj.dict.copy()
channel_dict["play_url"] = channel_obj.play_url()
channel_dict["play_url"] = channel_obj.play_url
channelslist.append(channel_dict)
if channel_dict["enabled"]:
channels_dict["Enabled"] += 1

View File

@ -1,6 +1,4 @@
from .origin_service import OriginService
from .origin_channels import OriginChannels
from .origin_epg import OriginEPG
import fHDHR.exceptions
@ -26,8 +24,9 @@ class OriginChannels_StandIN():
class OriginServiceWrapper():
def __init__(self, fhdhr):
def __init__(self, fhdhr, origin):
self.fhdhr = fhdhr
self.origin = origin
self.servicename = fhdhr.config.dict["main"]["servicename"]
@ -37,7 +36,7 @@ class OriginServiceWrapper():
def setup(self):
try:
self.origin = OriginService(self.fhdhr)
self.originservice = self.origin.OriginService(self.fhdhr)
self.setup_success = True
self.fhdhr.logger.info("%s Setup Success" % self.servicename)
except fHDHR.exceptions.OriginSetupError as e:
@ -45,8 +44,8 @@ class OriginServiceWrapper():
self.setup_success = False
if self.setup_success:
self.channels = OriginChannels(self.fhdhr, self.origin)
self.epg = OriginEPG(self.fhdhr)
self.channels = self.origin.OriginChannels(self.fhdhr, self.originservice)
self.epg = self.origin.OriginEPG(self.fhdhr)
else:
self.channels = OriginChannels_StandIN()
self.epg = OriginEPG_StandIN()
@ -83,8 +82,8 @@ class OriginServiceWrapper():
''' will only get called for undefined attributes '''
if hasattr(self.fhdhr, name):
return eval("self.fhdhr." + name)
if hasattr(self.origin, name):
return eval("self.origin." + name)
if hasattr(self.originservice, name):
return eval("self.originservice." + name)
elif hasattr(self.channels, name):
return eval("self.channels." + name)
elif hasattr(self.epg, name):

View File

@ -9,9 +9,11 @@ import pathlib
from multiprocessing import freeze_support
from fHDHR.cli import run
import alternative_epg
import origin
SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
if __name__ == '__main__':
freeze_support()
sys.exit(run.main(SCRIPT_DIR))
sys.exit(run.main(SCRIPT_DIR, alternative_epg, origin))

4
origin/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# pylama:ignore=W0401,W0611
from .origin_service import *
from .origin_channels import *
from .origin_epg import *

View File

@ -9,15 +9,6 @@ class OriginEPG():
def __init__(self, fhdhr):
self.fhdhr = fhdhr
def get_channel_thumbnail(self, channel_id):
channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" %
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
self.fhdhr.config.dict["origin"]["address"],
str(self.fhdhr.config.dict["origin"]["port"]),
str(channel_id)
))
return channel_thumb_url
def get_content_thumbnail(self, content_id):
item_thumb_url = ("%s%s:%s/service?method=channel.show.artwork&sid=%s&event_id=%s" %
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
@ -39,26 +30,18 @@ class OriginEPG():
def update_epg(self, fhdhr_channels):
programguide = {}
for c in fhdhr_channels.get_channels():
for fhdhr_id in list(self.channels.list.keys()):
chan_obj = self.channels.list[fhdhr_id]
cdict = fHDHR.tools.xmldictmaker(c, ["callsign", "name", "number", "id"])
if str(chan_obj.dict['number']) not in list(programguide.keys()):
if str(cdict['number']) not in list(programguide.keys()):
programguide[str(cdict['number'])] = {
"callsign": cdict["callsign"],
"name": cdict["name"] or cdict["callsign"],
"number": cdict["number"],
"id": str(cdict["origin_id"]),
"thumbnail": self.get_channel_thumbnail(cdict['origin_id']),
"listing": [],
}
programguide[str(chan_obj.dict["number"])] = chan_obj.epgdict
epg_url = ('%s%s:%s/service?method=channel.listings&channel_id=%s' %
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
self.fhdhr.config.dict["origin"]["address"],
str(self.fhdhr.config.dict["origin"]["port"]),
str(cdict["origin_id"]),
str(chan_obj.dict["origin_id"]),
))
epg_req = self.fhdhr.web.session.get(epg_url)
epg_dict = xmltodict.parse(epg_req.content)
@ -97,6 +80,7 @@ class OriginEPG():
# TODO isNEW
programguide[str(cdict["number"])]["listing"].append(clean_prog_dict)
if not any(d['id'] == clean_prog_dict['id'] for d in programguide[str(chan_obj.dict["number"])]["listing"]):
programguide[str(chan_obj.dict["number"])]["listing"].append(clean_prog_dict)
return programguide