diff --git a/fHDHR/epghandler/__init__.py b/fHDHR/epghandler/__init__.py
index 3406e3b..a8fef0b 100644
--- a/fHDHR/epghandler/__init__.py
+++ b/fHDHR/epghandler/__init__.py
@@ -1,6 +1,6 @@
import time
-from fHDHR.epghandler import epgtypes, xmltv
+from fHDHR.epghandler import epgtypes
class EPGhandler():
@@ -13,11 +13,6 @@ class EPGhandler():
self.sleeptime = self.config.dict[self.epg_method]["epg_update_frequency"]
self.epgtypes = epgtypes.EPGTypes(settings, origserv)
- self.xmltv = xmltv.xmlTV(settings)
-
- def get_xmltv(self, base_url):
- epgdict = self.epgtypes.get_epg()
- return self.xmltv.create_xmltv(base_url, epgdict)
def get_thumbnail(self, itemtype, itemid):
return self.epgtypes.get_thumbnail(itemtype, itemid)
diff --git a/fHDHR/epghandler/epgtypes/zap2it.py b/fHDHR/epghandler/epgtypes/zap2it.py
index b9ff3cd..e8902e2 100644
--- a/fHDHR/epghandler/epgtypes/zap2it.py
+++ b/fHDHR/epghandler/epgtypes/zap2it.py
@@ -142,12 +142,11 @@ class ZapEPG():
time.sleep(int(delay))
return result
- def remove_stale_cache(self, todaydate):
+ def remove_stale_cache(self, zap_time):
for p in self.web_cache_dir.glob('*'):
try:
- cachedate = datetime.datetime.strptime(str(p.name), "%Y-%m-%d")
- todaysdate = datetime.datetime.strptime(str(todaydate), "%Y-%m-%d")
- if cachedate >= todaysdate:
+ t = int(p.name)
+ if t >= zap_time:
continue
except Exception as e:
print(e)
diff --git a/fHDHR/epghandler/xmltv.py b/fHDHR/epghandler/xmltv.py
deleted file mode 100644
index 8d8a0b3..0000000
--- a/fHDHR/epghandler/xmltv.py
+++ /dev/null
@@ -1,110 +0,0 @@
-import xml.etree.ElementTree
-from io import BytesIO
-
-
-class xmlTV():
- """Methods to create xmltv.xml"""
-
- def __init__(self, settings):
- self.config = settings
- self.epg_method = self.config.dict["fhdhr"]["epg_method"]
-
- def sub_el(self, parent, name, text=None, **kwargs):
- el = xml.etree.ElementTree.SubElement(parent, name, **kwargs)
- if text:
- el.text = text
- return el
-
- def xmltv_headers(self):
- """This method creates the XML headers for our xmltv"""
- xmltvgen = xml.etree.ElementTree.Element('tv')
- xmltvgen.set('source-info-url', self.config.dict["fhdhr"]["friendlyname"])
- xmltvgen.set('source-info-name', self.config.dict["main"]["servicename"])
- xmltvgen.set('generator-info-name', 'fHDHR')
- xmltvgen.set('generator-info-url', 'fHDHR/' + self.config.dict["main"]["reponame"])
- return xmltvgen
-
- def xmltv_file(self, xmltvgen):
- """This method is used to close out the xml file"""
- xmltvfile = BytesIO()
- xmltvfile.write(b'\n')
- xmltvfile.write(xml.etree.ElementTree.tostring(xmltvgen, encoding='UTF-8'))
- return xmltvfile.getvalue()
-
- def xmltv_empty(self):
- """This method is called when creation of a full xmltv is not possible"""
- return self.xmltv_file(self.xmltv_headers())
-
- def create_xmltv(self, base_url, epgdict):
- if not epgdict:
- return self.xmltv_empty()
-
- out = self.xmltv_headers()
-
- for c in list(epgdict.keys()):
-
- c_out = self.sub_el(out, 'channel', id=str(epgdict[c]['number']))
- self.sub_el(c_out, 'display-name',
- text='%s %s' % (epgdict[c]['number'], epgdict[c]['callsign']))
- self.sub_el(c_out, 'display-name',
- text='%s %s %s' % (epgdict[c]['number'], epgdict[c]['callsign'], str(epgdict[c]['id'])))
- self.sub_el(c_out, 'display-name', text=epgdict[c]['number'])
- self.sub_el(c_out, 'display-name',
- text='%s %s fcc' % (epgdict[c]['number'], epgdict[c]['callsign']))
- self.sub_el(c_out, 'display-name', text=epgdict[c]['callsign'])
- self.sub_el(c_out, 'display-name', text=epgdict[c]['callsign'])
- self.sub_el(c_out, 'display-name', text=epgdict[c]['name'])
-
- if epgdict[c]["thumbnail"] is not None:
- self.sub_el(c_out, 'icon', src=("http://" + str(base_url) + "/images?source=epg&type=channel&id=" + epgdict[c]['id']))
- else:
- self.sub_el(c_out, 'icon', src=("http://" + str(base_url) + "/images?source=generate&message=" + epgdict[c]['number']))
-
- for channelnum in list(epgdict.keys()):
-
- channel_listing = epgdict[channelnum]['listing']
-
- for program in channel_listing:
-
- prog_out = self.sub_el(out, 'programme',
- start=program['time_start'],
- stop=program['time_end'],
- channel=str(channelnum))
-
- self.sub_el(prog_out, 'title', lang='en', text=program['title'])
-
- self.sub_el(prog_out, 'desc', lang='en', text=program['description'])
-
- self.sub_el(prog_out, 'sub-title', lang='en', text='Movie: ' + program['sub-title'])
-
- self.sub_el(prog_out, 'length', units='minutes', text=str(int(program['duration_minutes'])))
-
- for f in program['genres']:
- self.sub_el(prog_out, 'category', lang='en', text=f)
- self.sub_el(prog_out, 'genre', lang='en', text=f)
-
- if program['seasonnumber'] and program['episodenumber']:
- s_ = int(str(program['seasonnumber']), 10)
- e_ = int(str(program['episodenumber']), 10)
- self.sub_el(prog_out, 'episode-num', system='dd_progid',
- text=str(program['id']))
- self.sub_el(prog_out, 'episode-num', system='common',
- text='S%02dE%02d' % (s_, e_))
- self.sub_el(prog_out, 'episode-num', system='xmltv_ns',
- text='%d.%d.' % (int(s_)-1, int(e_)-1))
- self.sub_el(prog_out, 'episode-num', system='SxxExx">S',
- text='S%02dE%02d' % (s_, e_))
-
- if program["thumbnail"]:
- self.sub_el(prog_out, 'icon', src=("http://" + str(base_url) + "/images?source=epg&type=content&id=" + program['id']))
- else:
- self.sub_el(prog_out, 'icon', src=("http://" + str(base_url) + "/images?source=generate&message=" + program['title'].replace(" ", "")))
-
- if program['rating']:
- rating_out = self.sub_el(prog_out, 'rating', system="MPAA")
- self.sub_el(rating_out, 'value', text=program['rating'])
-
- if program['isnew']:
- self.sub_el(prog_out, 'new')
-
- return self.xmltv_file(out)
diff --git a/fHDHR/fHDHRerrors/__init__.py b/fHDHR/fHDHRerrors/__init__.py
index a44cf38..9bdf882 100644
--- a/fHDHR/fHDHRerrors/__init__.py
+++ b/fHDHR/fHDHRerrors/__init__.py
@@ -1,4 +1,12 @@
+class TunerError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return 'LoginError: %s' % self.value
+
+
class LoginError(Exception):
def __init__(self, value):
self.value = value
diff --git a/fHDHR/fHDHRweb/__init__.py b/fHDHR/fHDHRweb/__init__.py
index 8352220..0c034ed 100644
--- a/fHDHR/fHDHRweb/__init__.py
+++ b/fHDHR/fHDHRweb/__init__.py
@@ -1,203 +1,69 @@
from gevent.pywsgi import WSGIServer
from flask import (Flask, send_from_directory, request, Response,
abort, stream_with_context)
-from io import BytesIO
-import xml.etree.ElementTree as ET
-import json
import requests
import subprocess
-import threading
-import PIL.Image
-import PIL.ImageDraw
-import PIL.ImageFont
-
-def sub_el(parent, name, text=None, **kwargs):
- el = ET.SubElement(parent, name, **kwargs)
- if text:
- el.text = text
- return el
-
-
-def getSize(txt, font):
- testImg = PIL.Image.new('RGB', (1, 1))
- testDraw = PIL.ImageDraw.Draw(testImg)
- return testDraw.textsize(txt, font)
+from . import fHDHRdevice
+from fHDHR.fHDHRerrors import TunerError
class HDHR_Hub():
- config = None
- origserv = None
- epghandling = None
- station_scan = False
- station_list = []
- http = None
def __init__(self):
- self.tuner_lock = threading.Lock()
- self.tuners = 0
+ pass
+
+ def hubprep(self, settings, origserv, epghandling):
+ self.config = settings
+
+ self.devicexml = fHDHRdevice.Device_XML(settings)
+ self.discoverjson = fHDHRdevice.Discover_JSON(settings)
+ self.lineupxml = fHDHRdevice.Lineup_XML(settings, origserv)
+ self.lineupjson = fHDHRdevice.Lineup_JSON(settings, origserv)
+ self.lineupstatusjson = fHDHRdevice.Lineup_Status_JSON(settings, origserv)
+ self.images = fHDHRdevice.imageHandler(settings, epghandling)
+ self.tuners = fHDHRdevice.Tuners(settings)
+ self.station_scan = fHDHRdevice.Station_Scan(settings, origserv)
+ self.xmltv = fHDHRdevice.xmlTV_XML(settings, epghandling)
+ self.htmlerror = fHDHRdevice.HTMLerror(settings)
+
+ self.debug = fHDHRdevice.Debug_JSON(settings, origserv, epghandling)
- def hubprep(self, config, origserv, epghandling):
- self.config = config
- self.max_tuners = int(self.config.dict["fhdhr"]["tuner_count"])
- self.station_scan = False
self.origserv = origserv
self.epghandling = epghandling
- def tuner_usage(self, number):
- self.tuner_lock.acquire()
- self.tuners += number
- if self.tuners < 0:
- self.tuners = 0
- elif self.tuners > self.max_tuners:
- self.tuners = self.max_tuners
- self.tuner_lock.release()
+ def tuner_grab(self):
+ self.tuners.tuner_grab()
- def get_tuner(self):
- if self.tuners <= self.max_tuners:
- return True
- return False
+ def tuner_close(self):
+ self.tuners.tuner_close()
def get_xmltv(self, base_url):
- return self.epghandling.get_xmltv(base_url)
+ return self.xmltv.get_xmltv_xml(base_url)
- def generate_image(self, messagetype, message):
- if messagetype == "channel":
- width = 360
- height = 270
- fontsize = 72
- elif messagetype == "content":
- width = 1080
- height = 1440
- fontsize = 100
-
- colorBackground = "#228822"
- colorText = "#717D7E"
- colorOutline = "#717D7E"
- fontname = str(self.config.dict["filedir"]["font"])
-
- font = PIL.ImageFont.truetype(fontname, fontsize)
- text_width, text_height = getSize(message, font)
- img = PIL.Image.new('RGBA', (width+4, height+4), colorBackground)
- d = PIL.ImageDraw.Draw(img)
- d.text(((width-text_width)/2, (height-text_height)/2), message, fill=colorText, font=font)
- d.rectangle((0, 0, width+3, height+3), outline=colorOutline)
-
- s = BytesIO()
- img.save(s, 'png')
- return s.getvalue()
-
- def get_image(self, req_args):
-
- imageUri = self.epghandling.get_thumbnail(req_args["type"], req_args["id"])
- if not imageUri:
- return self.generate_image(req_args["type"], req_args["id"])
-
- try:
- req = requests.get(imageUri)
- return req.content
- except Exception as e:
- print(e)
- return self.generate_image(req_args["type"], req_args["id"])
-
- def get_image_type(self, image_data):
- header_byte = image_data[0:3].hex().lower()
- if header_byte == '474946':
- return "image/gif"
- elif header_byte == '89504e':
- return "image/png"
- elif header_byte == 'ffd8ff':
- return "image/jpeg"
- else:
- return "image/jpeg"
-
- def get_xmldiscover(self, base_url):
- out = ET.Element('root')
- out.set('xmlns', "urn:schemas-upnp-org:device-1-0")
-
- sub_el(out, 'URLBase', "http://" + base_url)
-
- specVersion_out = sub_el(out, 'specVersion')
- sub_el(specVersion_out, 'major', "1")
- sub_el(specVersion_out, 'minor', "0")
-
- device_out = sub_el(out, 'device')
- sub_el(device_out, 'deviceType', "urn:schemas-upnp-org:device:MediaServer:1")
- sub_el(device_out, 'friendlyName', self.config.dict["fhdhr"]["friendlyname"])
- sub_el(device_out, 'manufacturer', self.config.dict["dev"]["reporting_manufacturer"])
- sub_el(device_out, 'modelName', self.config.dict["dev"]["reporting_model"])
- sub_el(device_out, 'modelNumber', self.config.dict["dev"]["reporting_model"])
- sub_el(device_out, 'serialNumber')
- sub_el(device_out, 'UDN', "uuid:" + self.config.dict["main"]["uuid"])
-
- fakefile = BytesIO()
- fakefile.write(b'\n')
- fakefile.write(ET.tostring(out, encoding='UTF-8'))
- return fakefile.getvalue()
+ def get_device_xml(self, base_url):
+ return self.devicexml.get_device_xml(base_url)
def get_discover_json(self, base_url):
- jsondiscover = {
- "FriendlyName": self.config.dict["fhdhr"]["friendlyname"],
- "Manufacturer": "Borondust",
- "ModelNumber": self.config.dict["dev"]["reporting_model"],
- "FirmwareName": self.config.dict["dev"]["reporting_firmware_name"],
- "TunerCount": self.config.dict["fhdhr"]["tuner_count"],
- "FirmwareVersion": self.config.dict["dev"]["reporting_firmware_ver"],
- "DeviceID": self.config.dict["main"]["uuid"],
- "DeviceAuth": "fHDHR",
- "BaseURL": "http://" + base_url,
- "LineupURL": "http://" + base_url + "/lineup.json"
- }
- return jsondiscover
+ return self.discoverjson.get_discover_json(base_url)
- def get_lineup_status(self):
- if self.station_scan:
- channel_count = self.origserv.get_station_total()
- jsonlineup = {
- "ScanInProgress": "true",
- "Progress": 99,
- "Found": channel_count
- }
- else:
- jsonlineup = {
- "ScanInProgress": "false",
- "ScanPossible": "true",
- "Source": self.config.dict["dev"]["reporting_tuner_type"],
- "SourceList": [self.config.dict["dev"]["reporting_tuner_type"]],
- }
- return jsonlineup
+ def get_lineup_status_json(self):
+ return self.lineupstatusjson.get_lineup_json(self.station_scan.scanning())
def get_lineup_xml(self, base_url):
- out = ET.Element('Lineup')
- station_list = self.origserv.get_station_list(base_url)
- for station_item in station_list:
- program_out = sub_el(out, 'Program')
- sub_el(program_out, 'GuideNumber', station_item['GuideNumber'])
- sub_el(program_out, 'GuideName', station_item['GuideName'])
- sub_el(program_out, 'URL', station_item['URL'])
+ return self.lineupxml.get_lineup_xml(base_url)
- fakefile = BytesIO()
- fakefile.write(b'\n')
- fakefile.write(ET.tostring(out, encoding='UTF-8'))
- return fakefile.getvalue()
+ def get_lineup_json(self, base_url):
+ return self.lineupjson.get_lineup_json(base_url)
- def get_debug(self, base_url):
- debugjson = {
- "base_url": base_url,
- }
- return debugjson
+ def get_debug_json(self, base_url):
+ return self.debug.get_debug_json(base_url, self.tuners.tuners)
def get_html_error(self, message):
- htmlerror = """
-
-
- {}
-
- """
- return htmlerror.format(message)
+ return self.htmlerror.get_html_error(message)
- def station_scan_change(self, enablement):
- self.station_scan = enablement
+ def post_lineup_scan_start(self):
+ self.station_scan.scan()
hdhr = HDHR_Hub()
@@ -219,24 +85,24 @@ class HDHR_HTTP_Server():
@app.route('/device.xml', methods=['GET'])
def device_xml():
base_url = request.headers["host"]
- devicexml = hdhr.get_xmldiscover(base_url)
+ device_xml = hdhr.get_device_xml(base_url)
return Response(status=200,
- response=devicexml,
+ response=device_xml,
mimetype='application/xml')
@app.route('/discover.json', methods=['GET'])
def discover_json():
base_url = request.headers["host"]
- jsondiscover = hdhr.get_discover_json(base_url)
+ discover_json = hdhr.get_discover_json(base_url)
return Response(status=200,
- response=json.dumps(jsondiscover, indent=4),
+ response=discover_json,
mimetype='application/json')
@app.route('/lineup_status.json', methods=['GET'])
def lineup_status_json():
- linup_status_json = hdhr.get_lineup_status()
+ linup_status_json = hdhr.get_lineup_status_json()
return Response(status=200,
- response=json.dumps(linup_status_json, indent=4),
+ response=linup_status_json,
mimetype='application/json')
@app.route('/lineup.xml', methods=['GET'])
@@ -250,9 +116,9 @@ class HDHR_HTTP_Server():
@app.route('/lineup.json', methods=['GET'])
def lineup_json():
base_url = request.headers["host"]
- station_list = hdhr.origserv.get_station_list(base_url)
+ station_list = hdhr.get_lineup_json(base_url)
return Response(status=200,
- response=json.dumps(station_list, indent=4),
+ response=station_list,
mimetype='application/json')
@app.route('/xmltv.xml', methods=['GET'])
@@ -266,16 +132,16 @@ class HDHR_HTTP_Server():
@app.route('/debug.json', methods=['GET'])
def debug_json():
base_url = request.headers["host"]
- debugreport = hdhr.get_debug(base_url)
+ debugreport = hdhr.get_debug_json(base_url)
return Response(status=200,
- response=json.dumps(debugreport, indent=4),
+ response=debugreport,
mimetype='application/json')
@app.route('/images', methods=['GET'])
def images():
if 'source' not in list(request.args.keys()):
- image = hdhr.generate_image("content", "Unknown Request")
+ image = hdhr.images.generate_image("content", "Unknown Request")
else:
itemtype = 'content'
@@ -289,19 +155,19 @@ class HDHR_HTTP_Server():
"type": request.args["type"],
"id": request.args["id"],
}
- image = hdhr.get_image(req_dict)
+ image = hdhr.images.get_image(req_dict)
else:
itemmessage = "Unknown Request"
- image = hdhr.generate_image(itemtype, itemmessage)
+ image = hdhr.images.generate_image(itemtype, itemmessage)
elif request.args['source'] == 'generate':
itemmessage = "Unknown Request"
if 'message' in list(request.args.keys()):
itemmessage = request.args["message"]
- image = hdhr.generate_image(itemtype, itemmessage)
+ image = hdhr.images.generate_image(itemtype, itemmessage)
else:
itemmessage = "Unknown Request"
- image = hdhr.generate_image(itemtype, itemmessage)
- return Response(image, content_type=hdhr.get_image_type(image), direct_passthrough=True)
+ image = hdhr.images.generate_image(itemtype, itemmessage)
+ return Response(image, content_type=hdhr.images.get_image_type(image), direct_passthrough=True)
@app.route('/watch', methods=['GET'])
def watch():
@@ -311,14 +177,14 @@ class HDHR_HTTP_Server():
method = str(request.args["method"])
channel_id = str(request.args["channel"])
- tuner = hdhr.get_tuner()
- if not tuner:
+ try:
+ hdhr.tuner_grab()
+ except TunerError:
print("A " + method + " stream request for channel " +
str(channel_id) + " was rejected do to a lack of available tuners.")
abort(503)
print("Attempting a " + method + " stream request for channel " + str(channel_id))
- hdhr.tuner_usage(1)
channelUri = hdhr.origserv.get_channel_stream(channel_id)
# print("Proxy URL determined as " + str(channelUri))
@@ -335,7 +201,7 @@ class HDHR_HTTP_Server():
except GeneratorExit:
req.close()
print("Connection Closed.")
- hdhr.tuner_usage(-1)
+ hdhr.tuner_close()
return Response(generate(), content_type=req.headers['content-type'], direct_passthrough=True)
@@ -366,12 +232,11 @@ class HDHR_HTTP_Server():
ffmpeg_proc.terminate()
ffmpeg_proc.communicate()
print("Connection Closed: " + str(e))
- hdhr.tuner_usage(-1)
except GeneratorExit:
ffmpeg_proc.terminate()
ffmpeg_proc.communicate()
print("Connection Closed.")
- hdhr.tuner_usage(-1)
+ hdhr.tuner_close()
return Response(stream_with_context(generate()), mimetype="audio/mpeg")
@@ -379,25 +244,20 @@ class HDHR_HTTP_Server():
def lineup_post():
if 'scan' in list(request.args.keys()):
if request.args['scan'] == 'start':
- hdhr.station_scan_change(True)
- hdhr.station_list = []
- hdhr.station_scan_change(False)
+ hdhr.post_lineup_scan_start()
return Response(status=200, mimetype='text/html')
-
elif request.args['scan'] == 'abort':
return Response(status=200, mimetype='text/html')
-
else:
print("Unknown scan command " + request.args['scan'])
currenthtmlerror = hdhr.get_html_error("501 - " + request.args['scan'] + " is not a valid scan command")
return Response(status=200, response=currenthtmlerror, mimetype='text/html')
-
else:
currenthtmlerror = hdhr.get_html_error("501 - not a valid command")
return Response(status=200, response=currenthtmlerror, mimetype='text/html')
- def __init__(self, config):
- self.config = config
+ def __init__(self, settings):
+ self.config = settings
def run(self):
self.http = WSGIServer((
@@ -410,8 +270,8 @@ class HDHR_HTTP_Server():
self.http.stop()
-def interface_start(config, origserv, epghandling):
+def interface_start(settings, origserv, epghandling):
print("Starting fHDHR Web Interface")
- hdhr.hubprep(config, origserv, epghandling)
- fakhdhrserver = HDHR_HTTP_Server(config)
+ hdhr.hubprep(settings, origserv, epghandling)
+ fakhdhrserver = HDHR_HTTP_Server(settings)
fakhdhrserver.run()
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/__init__.py b/fHDHR/fHDHRweb/fHDHRdevice/__init__.py
new file mode 100644
index 0000000..5045fe9
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/__init__.py
@@ -0,0 +1,14 @@
+# pylama:ignore=W0611
+from .tuners import Tuners
+from .images import imageHandler
+from .station_scan import Station_Scan
+
+from .discover_json import Discover_JSON
+from .device_xml import Device_XML
+from .lineup_xml import Lineup_XML
+from .lineup_json import Lineup_JSON
+from .debug_json import Debug_JSON
+from .lineup_status_json import Lineup_Status_JSON
+from .xmltv_xml import xmlTV_XML
+
+from .htmlerror import HTMLerror
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/debug_json.py b/fHDHR/fHDHRweb/fHDHRdevice/debug_json.py
new file mode 100644
index 0000000..a82ee0c
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/debug_json.py
@@ -0,0 +1,14 @@
+import json
+
+
+class Debug_JSON():
+
+ def __init__(self, settings, origserv, epghandling):
+ self.config = settings
+
+ def get_debug_json(self, base_url, tuners):
+ debugjson = {
+ "base_url": base_url,
+ "available tuners": tuners
+ }
+ return json.dumps(debugjson, indent=4)
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/device_xml.py b/fHDHR/fHDHRweb/fHDHRdevice/device_xml.py
new file mode 100644
index 0000000..8b6f0d9
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/device_xml.py
@@ -0,0 +1,38 @@
+import xml.etree.ElementTree
+from io import BytesIO
+
+from fHDHR.tools import sub_el
+
+
+class Device_XML():
+ device_xml = None
+
+ def __init__(self, settings):
+ self.config = settings
+
+ def get_device_xml(self, base_url, force_update=False):
+ if not self.device_xml or force_update:
+ out = xml.etree.ElementTree.Element('root')
+ out.set('xmlns', "urn:schemas-upnp-org:device-1-0")
+
+ sub_el(out, 'URLBase', "http://" + base_url)
+
+ specVersion_out = sub_el(out, 'specVersion')
+ sub_el(specVersion_out, 'major', "1")
+ sub_el(specVersion_out, 'minor', "0")
+
+ device_out = sub_el(out, 'device')
+ sub_el(device_out, 'deviceType', "urn:schemas-upnp-org:device:MediaServer:1")
+ sub_el(device_out, 'friendlyName', self.config.dict["fhdhr"]["friendlyname"])
+ sub_el(device_out, 'manufacturer', self.config.dict["dev"]["reporting_manufacturer"])
+ sub_el(device_out, 'modelName', self.config.dict["dev"]["reporting_model"])
+ sub_el(device_out, 'modelNumber', self.config.dict["dev"]["reporting_model"])
+ sub_el(device_out, 'serialNumber')
+ sub_el(device_out, 'UDN', "uuid:" + self.config.dict["main"]["uuid"])
+
+ fakefile = BytesIO()
+ fakefile.write(b'\n')
+ fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
+ self.device_xml = fakefile.getvalue()
+
+ return self.device_xml
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/discover_json.py b/fHDHR/fHDHRweb/fHDHRdevice/discover_json.py
new file mode 100644
index 0000000..c673057
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/discover_json.py
@@ -0,0 +1,26 @@
+import json
+
+
+class Discover_JSON():
+ discover_json = None
+
+ def __init__(self, settings):
+ self.config = settings
+
+ def get_discover_json(self, base_url, force_update=False):
+ if not self.discover_json or force_update:
+ jsondiscover = {
+ "FriendlyName": self.config.dict["fhdhr"]["friendlyname"],
+ "Manufacturer": self.config.dict["dev"]["reporting_manufacturer"],
+ "ModelNumber": self.config.dict["dev"]["reporting_model"],
+ "FirmwareName": self.config.dict["dev"]["reporting_firmware_name"],
+ "TunerCount": self.config.dict["fhdhr"]["tuner_count"],
+ "FirmwareVersion": self.config.dict["dev"]["reporting_firmware_ver"],
+ "DeviceID": self.config.dict["main"]["uuid"],
+ "DeviceAuth": "fHDHR",
+ "BaseURL": "http://" + base_url,
+ "LineupURL": "http://" + base_url + "/lineup.json"
+ }
+ self.discover_json = json.dumps(jsondiscover, indent=4)
+
+ return self.discover_json
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/htmlerror.py b/fHDHR/fHDHRweb/fHDHRdevice/htmlerror.py
new file mode 100644
index 0000000..9e6026e
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/htmlerror.py
@@ -0,0 +1,13 @@
+
+class HTMLerror():
+ def __init__(self, settings):
+ self.config = settings
+
+ def get_html_error(self, message):
+ htmlerror = """
+
+
+ {}
+
+ """
+ return htmlerror.format(message)
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/images.py b/fHDHR/fHDHRweb/fHDHRdevice/images.py
new file mode 100644
index 0000000..dd1c76d
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/images.py
@@ -0,0 +1,67 @@
+from io import BytesIO
+import requests
+import PIL.Image
+import PIL.ImageDraw
+import PIL.ImageFont
+
+
+class imageHandler():
+
+ def __init__(self, settings, epghandling):
+ self.config = settings
+ self.epghandling = epghandling
+
+ def getSize(self, txt, font):
+ testImg = PIL.Image.new('RGB', (1, 1))
+ testDraw = PIL.ImageDraw.Draw(testImg)
+ return testDraw.textsize(txt, font)
+
+ def generate_image(self, messagetype, message):
+ if messagetype == "channel":
+ width = 360
+ height = 270
+ fontsize = 72
+ elif messagetype == "content":
+ width = 1080
+ height = 1440
+ fontsize = 100
+
+ colorBackground = "#228822"
+ colorText = "#717D7E"
+ colorOutline = "#717D7E"
+ fontname = str(self.config.dict["filedir"]["font"])
+
+ font = PIL.ImageFont.truetype(fontname, fontsize)
+ text_width, text_height = self.getSize(message, font)
+ img = PIL.Image.new('RGBA', (width+4, height+4), colorBackground)
+ d = PIL.ImageDraw.Draw(img)
+ d.text(((width-text_width)/2, (height-text_height)/2), message, fill=colorText, font=font)
+ d.rectangle((0, 0, width+3, height+3), outline=colorOutline)
+
+ s = BytesIO()
+ img.save(s, 'png')
+ return s.getvalue()
+
+ def get_image(self, req_args):
+
+ imageUri = self.epghandling.get_thumbnail(req_args["type"], req_args["id"])
+ if not imageUri:
+ return self.generate_image(req_args["type"], req_args["id"])
+
+ try:
+ req = requests.get(imageUri)
+ return req.content
+ except Exception as e:
+ print(e)
+ return self.generate_image(req_args["type"], req_args["id"])
+
+ def get_image_type(self, image_data):
+ header_byte = image_data[0:3].hex().lower()
+ if header_byte == '474946':
+ return "image/gif"
+ elif header_byte == '89504e':
+ return "image/png"
+ elif header_byte == 'ffd8ff':
+ return "image/jpeg"
+ else:
+ return "image/jpeg"
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/lineup_json.py b/fHDHR/fHDHRweb/fHDHRdevice/lineup_json.py
new file mode 100644
index 0000000..d1086e2
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/lineup_json.py
@@ -0,0 +1,16 @@
+import json
+
+
+class Lineup_JSON():
+ lineup_json = None
+
+ def __init__(self, settings, origserv):
+ self.config = settings
+ self.origserv = origserv
+
+ def get_lineup_json(self, base_url, force_update=False):
+ if not self.lineup_json or force_update:
+ jsonlineup = self.origserv.get_station_list(base_url)
+ self.lineup_json = json.dumps(jsonlineup, indent=4)
+
+ return self.lineup_json
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/lineup_status_json.py b/fHDHR/fHDHRweb/fHDHRdevice/lineup_status_json.py
new file mode 100644
index 0000000..8e49db5
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/lineup_status_json.py
@@ -0,0 +1,35 @@
+import json
+
+
+class Lineup_Status_JSON():
+
+ def __init__(self, settings, origserv):
+ self.config = settings
+ self.origserv = origserv
+
+ def get_lineup_json(self, station_scanning):
+ if station_scanning:
+ jsonlineup = self.scan_in_progress()
+ elif not self.origserv.get_station_total():
+ jsonlineup = self.scan_in_progress()
+ else:
+ jsonlineup = self.not_scanning()
+ return json.dumps(jsonlineup, indent=4)
+
+ def scan_in_progress(self):
+ channel_count = self.origserv.get_station_total()
+ jsonlineup = {
+ "ScanInProgress": "true",
+ "Progress": 99,
+ "Found": channel_count
+ }
+ return jsonlineup
+
+ def not_scanning(self):
+ jsonlineup = {
+ "ScanInProgress": "false",
+ "ScanPossible": "true",
+ "Source": self.config.dict["dev"]["reporting_tuner_type"],
+ "SourceList": [self.config.dict["dev"]["reporting_tuner_type"]],
+ }
+ return jsonlineup
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/lineup_xml.py b/fHDHR/fHDHRweb/fHDHRdevice/lineup_xml.py
new file mode 100644
index 0000000..10359da
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/lineup_xml.py
@@ -0,0 +1,29 @@
+import xml.etree.ElementTree
+from io import BytesIO
+
+from fHDHR.tools import sub_el
+
+
+class Lineup_XML():
+ device_xml = None
+
+ def __init__(self, settings, origserv):
+ self.config = settings
+ self.origserv = origserv
+
+ def get_lineup_xml(self, base_url, force_update=False):
+ if not self.device_xml or force_update:
+ out = xml.etree.ElementTree.Element('Lineup')
+ station_list = self.origserv.get_station_list(base_url)
+ for station_item in station_list:
+ program_out = sub_el(out, 'Program')
+ sub_el(program_out, 'GuideNumber', station_item['GuideNumber'])
+ sub_el(program_out, 'GuideName', station_item['GuideName'])
+ sub_el(program_out, 'URL', station_item['URL'])
+
+ fakefile = BytesIO()
+ fakefile.write(b'\n')
+ fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
+ self.device_xml = fakefile.getvalue()
+
+ return self.device_xml
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/station_scan.py b/fHDHR/fHDHRweb/fHDHRdevice/station_scan.py
new file mode 100644
index 0000000..2f9f97b
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/station_scan.py
@@ -0,0 +1,27 @@
+from multiprocessing import Process
+
+
+class Station_Scan():
+
+ def __init__(self, settings, origserv):
+ self.config = settings
+ self.origserv = origserv
+ self.chanscan = Process(target=self.runscan)
+
+ def scan(self):
+ print("Channel Scan Requested by Client.")
+ try:
+ self.chanscan.start()
+ except AssertionError:
+ print("Channel Scan Already In Progress!")
+
+ def runscan(self):
+ self.origserv.get_channels(forceupdate=True)
+ print("Requested Channel Scan Complete.")
+
+ def scanning(self):
+ try:
+ self.chanscan.join(timeout=0)
+ return self.chanscan.is_alive()
+ except AssertionError:
+ return False
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/tuners.py b/fHDHR/fHDHRweb/fHDHRdevice/tuners.py
new file mode 100644
index 0000000..a89f6ee
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/tuners.py
@@ -0,0 +1,26 @@
+import threading
+
+from fHDHR.fHDHRerrors import TunerError
+
+
+class Tuners():
+
+ def __init__(self, settings):
+ self.config = settings
+
+ self.max_tuners = int(self.config.dict["fhdhr"]["tuner_count"])
+ self.tuners = self.max_tuners
+ self.tuner_lock = threading.Lock()
+
+ def tuner_grab(self):
+ self.tuner_lock.acquire()
+ if self.tuners == 0:
+ self.tuner_lock.release()
+ raise TunerError("No Available Tuners.")
+ self.tuners -= 1
+ self.tuner_lock.release()
+
+ def tuner_close(self):
+ self.tuner_lock.acquire()
+ self.tuners += 1
+ self.tuner_lock.release()
diff --git a/fHDHR/fHDHRweb/fHDHRdevice/xmltv_xml.py b/fHDHR/fHDHRweb/fHDHRdevice/xmltv_xml.py
new file mode 100644
index 0000000..5d0c267
--- /dev/null
+++ b/fHDHR/fHDHRweb/fHDHRdevice/xmltv_xml.py
@@ -0,0 +1,130 @@
+import xml.etree.ElementTree
+from io import BytesIO
+import time
+
+from fHDHR.tools import sub_el
+
+
+class xmlTV_XML():
+ """Methods to create xmltv.xml"""
+ xmltv_xml = None
+
+ def __init__(self, settings, epghandling):
+ self.config = settings
+ self.epghandling = epghandling
+ self.updated_at = None
+ self.epg_method = self.config.dict["fhdhr"]["epg_method"]
+ self.epg_sleeptime = self.config.dict[self.epg_method]["epg_update_frequency"]
+
+ def get_xmltv_xml(self, base_url, force_update=False):
+ nowtime = time.time()
+ update_xmltv = False
+
+ if not self.xmltv_xml or force_update:
+ update_xmltv = True
+ elif not self.updated_at:
+ update_xmltv = True
+ elif nowtime >= (self.updated_at + self.epg_sleeptime):
+ update_xmltv = True
+
+ if update_xmltv:
+ print("Updating xmltv cache.")
+ epgdict = self.epghandling.epgtypes.get_epg()
+ self.xmltv_xml = self.create_xmltv(base_url, epgdict)
+ self.updated_at = nowtime
+
+ return self.xmltv_xml
+
+ def xmltv_headers(self):
+ """This method creates the XML headers for our xmltv"""
+ xmltvgen = xml.etree.ElementTree.Element('tv')
+ xmltvgen.set('source-info-url', self.config.dict["fhdhr"]["friendlyname"])
+ xmltvgen.set('source-info-name', self.config.dict["main"]["servicename"])
+ xmltvgen.set('generator-info-name', 'fHDHR')
+ xmltvgen.set('generator-info-url', 'fHDHR/' + self.config.dict["main"]["reponame"])
+ return xmltvgen
+
+ def xmltv_file(self, xmltvgen):
+ """This method is used to close out the xml file"""
+ xmltvfile = BytesIO()
+ xmltvfile.write(b'\n')
+ xmltvfile.write(xml.etree.ElementTree.tostring(xmltvgen, encoding='UTF-8'))
+ return xmltvfile.getvalue()
+
+ def xmltv_empty(self):
+ """This method is called when creation of a full xmltv is not possible"""
+ return self.xmltv_file(self.xmltv_headers())
+
+ def create_xmltv(self, base_url, epgdict):
+ if not epgdict:
+ return self.xmltv_empty()
+
+ out = self.xmltv_headers()
+
+ for c in list(epgdict.keys()):
+
+ c_out = sub_el(out, 'channel', id=str(epgdict[c]['number']))
+ sub_el(c_out, 'display-name',
+ text='%s %s' % (epgdict[c]['number'], epgdict[c]['callsign']))
+ sub_el(c_out, 'display-name',
+ text='%s %s %s' % (epgdict[c]['number'], epgdict[c]['callsign'], str(epgdict[c]['id'])))
+ sub_el(c_out, 'display-name', text=epgdict[c]['number'])
+ sub_el(c_out, 'display-name',
+ text='%s %s fcc' % (epgdict[c]['number'], epgdict[c]['callsign']))
+ sub_el(c_out, 'display-name', text=epgdict[c]['callsign'])
+ sub_el(c_out, 'display-name', text=epgdict[c]['callsign'])
+ sub_el(c_out, 'display-name', text=epgdict[c]['name'])
+
+ if epgdict[c]["thumbnail"] is not None:
+ sub_el(c_out, 'icon', src=("http://" + str(base_url) + "/images?source=epg&type=channel&id=" + epgdict[c]['id']))
+ else:
+ sub_el(c_out, 'icon', src=("http://" + str(base_url) + "/images?source=generate&message=" + epgdict[c]['number']))
+
+ for channelnum in list(epgdict.keys()):
+
+ channel_listing = epgdict[channelnum]['listing']
+
+ for program in channel_listing:
+
+ prog_out = sub_el(out, 'programme',
+ start=program['time_start'],
+ stop=program['time_end'],
+ channel=str(channelnum))
+
+ sub_el(prog_out, 'title', lang='en', text=program['title'])
+
+ sub_el(prog_out, 'desc', lang='en', text=program['description'])
+
+ sub_el(prog_out, 'sub-title', lang='en', text='Movie: ' + program['sub-title'])
+
+ sub_el(prog_out, 'length', units='minutes', text=str(int(program['duration_minutes'])))
+
+ for f in program['genres']:
+ sub_el(prog_out, 'category', lang='en', text=f)
+ sub_el(prog_out, 'genre', lang='en', text=f)
+
+ if program['seasonnumber'] and program['episodenumber']:
+ s_ = int(str(program['seasonnumber']), 10)
+ e_ = int(str(program['episodenumber']), 10)
+ sub_el(prog_out, 'episode-num', system='dd_progid',
+ text=str(program['id']))
+ sub_el(prog_out, 'episode-num', system='common',
+ text='S%02dE%02d' % (s_, e_))
+ sub_el(prog_out, 'episode-num', system='xmltv_ns',
+ text='%d.%d.' % (int(s_)-1, int(e_)-1))
+ sub_el(prog_out, 'episode-num', system='SxxExx">S',
+ text='S%02dE%02d' % (s_, e_))
+
+ if program["thumbnail"]:
+ sub_el(prog_out, 'icon', src=("http://" + str(base_url) + "/images?source=epg&type=content&id=" + program['id']))
+ else:
+ sub_el(prog_out, 'icon', src=("http://" + str(base_url) + "/images?source=generate&message=" + program['title'].replace(" ", "")))
+
+ if program['rating']:
+ rating_out = sub_el(prog_out, 'rating', system="MPAA")
+ sub_el(rating_out, 'value', text=program['rating'])
+
+ if program['isnew']:
+ sub_el(prog_out, 'new')
+
+ return self.xmltv_file(out)
diff --git a/fHDHR/originservice/__init__.py b/fHDHR/originservice/__init__.py
index 92355d5..dfa093b 100644
--- a/fHDHR/originservice/__init__.py
+++ b/fHDHR/originservice/__init__.py
@@ -26,13 +26,15 @@ class OriginService():
for chankey in list(chan.keys()):
self.channels["list"][chan["number"]][chankey] = chan[chankey]
- def get_channels(self):
+ def get_channels(self, forceupdate=False):
updatelist = False
if not self.channels["list_updated"]:
updatelist = True
elif hours_between_datetime(self.channels["list_updated"], datetime.datetime.now()) > 12:
updatelist = True
+ elif forceupdate:
+ updatelist = True
if updatelist:
chanlist = self.serviceorigin.get_channels()
diff --git a/fHDHR/tools/__init__.py b/fHDHR/tools/__init__.py
index e5e99f8..19a3459 100644
--- a/fHDHR/tools/__init__.py
+++ b/fHDHR/tools/__init__.py
@@ -2,6 +2,7 @@ import os
import sys
import ast
import requests
+import xml.etree.ElementTree
UNARY_OPS = (ast.UAdd, ast.USub)
BINARY_OPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)
@@ -13,6 +14,13 @@ def clean_exit():
os._exit(0)
+def sub_el(parent, name, text=None, **kwargs):
+ el = xml.etree.ElementTree.SubElement(parent, name, **kwargs)
+ if text:
+ el.text = text
+ return el
+
+
def xmldictmaker(inputdict, req_items, list_items=[], str_items=[]):
xml_dict = {}
diff --git a/main.py b/main.py
index 3f55775..b2ac62b 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,8 @@
#!/usr/bin/env python3
# coding=utf-8
+# pylama:ignore=E402
+from gevent import monkey
+monkey.patch_all()
import os
import sys
import pathlib