mirror of
https://github.com/fHDHR/fHDHR_NextPVR.git
synced 2025-12-06 13:06:59 -05:00
Replicate changes in the Locast repo
This commit is contained in:
parent
5e0353d7a2
commit
fc589b60a9
24
data/internal_config/fakehdhr.ini
Normal file
24
data/internal_config/fakehdhr.ini
Normal file
@ -0,0 +1,24 @@
|
||||
[main]
|
||||
uuid = fHDHR_None
|
||||
cache_dir = fHDHR_None
|
||||
|
||||
[empty]
|
||||
epg_update_frequency = 43200
|
||||
|
||||
[fakehdhr]
|
||||
address = 0.0.0.0
|
||||
port = 5004
|
||||
discovery_address = 0.0.0.0
|
||||
|
||||
[ffmpeg]
|
||||
ffmpeg_path = ffmpeg
|
||||
bytes_per_read = 1152000
|
||||
|
||||
[direct_stream]
|
||||
chunksize = 1048576
|
||||
|
||||
[dev]
|
||||
reporting_model = HDHR4-2DT
|
||||
reporting_firmware_name = hdhomerun4_dvbt
|
||||
reporting_firmware_ver = 20150826
|
||||
reporting_tuner_type = Antenna
|
||||
35
data/internal_config/serviceconf.ini
Normal file
35
data/internal_config/serviceconf.ini
Normal file
@ -0,0 +1,35 @@
|
||||
[main]
|
||||
servicename = NextPVR
|
||||
dictpopname = nextpvr
|
||||
credentials = pin
|
||||
reponame = FakeHDHR_NextPVR
|
||||
|
||||
[fakehdhr]
|
||||
friendlyname = fHDHR-NextPVR
|
||||
stream_type = direct
|
||||
epg_method = proxy
|
||||
tuner_count = 4
|
||||
|
||||
[nextpvr]
|
||||
address = localhost
|
||||
port = 8866
|
||||
ssl = fHDHR_False
|
||||
pin = fHDHR_None
|
||||
weight = 300
|
||||
sidfile = fHDHR_None
|
||||
epg_update_frequency = 43200
|
||||
|
||||
[zap2it]
|
||||
delay = 5
|
||||
postalcode = fHDHR_None
|
||||
affiliate_id = gapzap
|
||||
country = USA
|
||||
device = -
|
||||
headendid = lineupId
|
||||
isoverride = True
|
||||
languagecode = en
|
||||
pref =
|
||||
timespan = 6
|
||||
timezone =
|
||||
userid = -
|
||||
epg_update_frequency = 43200
|
||||
@ -2,11 +2,9 @@ import os
|
||||
import sys
|
||||
import time
|
||||
from io import BytesIO
|
||||
import json
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from . import zap2it
|
||||
from . import emptyepg
|
||||
from . import epgtypes
|
||||
|
||||
|
||||
def sub_el(parent, name, text=None, **kwargs):
|
||||
@ -26,40 +24,20 @@ class EPGhandler():
|
||||
|
||||
def __init__(self, config, serviceproxy):
|
||||
self.config = config.config
|
||||
self.serviceproxy = serviceproxy
|
||||
self.zapepg = zap2it.ZapEPG(config)
|
||||
self.emptyepg = emptyepg.EmptyEPG(config)
|
||||
|
||||
self.epg_cache = None
|
||||
|
||||
def get_epg(self):
|
||||
if self.config["fakehdhr"]["epg_method"] == "empty":
|
||||
epgdict = self.emptyepg.EmptyEPG()
|
||||
elif self.config["fakehdhr"]["epg_method"] == "proxy":
|
||||
epgdict = self.serviceproxy.epg_cache_open()
|
||||
elif self.config["fakehdhr"]["epg_method"] == "zap2it":
|
||||
epgdict = self.zapepg.epg_cache_open()
|
||||
return epgdict
|
||||
|
||||
def epg_cache_open(self):
|
||||
epg_cache = None
|
||||
if os.path.isfile(self.empty_cache_file):
|
||||
with open(self.empty_cache_file, 'r') as epgfile:
|
||||
epg_cache = json.load(epgfile)
|
||||
return epg_cache
|
||||
self.epgtypes = epgtypes.EPGTypes(config, serviceproxy)
|
||||
|
||||
def get_xmltv(self, base_url):
|
||||
epgdict = self.get_epg()
|
||||
epgdict = self.epgtypes.get_epg()
|
||||
if not epgdict:
|
||||
return self.dummyxml()
|
||||
|
||||
epg_method = self.config["fakehdhr"]["epg_method"]
|
||||
|
||||
out = ET.Element('tv')
|
||||
out.set('source-info-url', 'NextPVR')
|
||||
out.set('source-info-name', 'NextPVR')
|
||||
out.set('source-info-url', self.config["fakehdhr"]["friendlyname"])
|
||||
out.set('source-info-name', self.config["main"]["servicename"])
|
||||
out.set('generator-info-name', 'FAKEHDHR')
|
||||
out.set('generator-info-url', 'FAKEHDHR/FakeHDHR_NextPVR')
|
||||
out.set('generator-info-url', 'FAKEHDHR/' + self.config["main"]["reponame"])
|
||||
|
||||
for c in list(epgdict.keys()):
|
||||
|
||||
@ -76,16 +54,10 @@ class EPGhandler():
|
||||
sub_el(c_out, 'display-name', text=epgdict[c]['name'])
|
||||
|
||||
if epgdict[c]["thumbnail"] is not None:
|
||||
if epg_method == "empty":
|
||||
sub_el(c_out, 'icon', src=("http://" + str(base_url) + str(epgdict[c]['thumbnail'])))
|
||||
elif epg_method == "proxy":
|
||||
sub_el(c_out, 'icon', src=("http://" + str(base_url) + str(epgdict[c]['thumbnail'])))
|
||||
elif epg_method == "zap2it":
|
||||
sub_el(c_out, 'icon', src=(str(epgdict[c]['thumbnail'])))
|
||||
else:
|
||||
sub_el(c_out, 'icon', src=(str(epgdict[c]['thumbnail'])))
|
||||
channel_thumbnail = self.epgtypes.thumb_url(epg_method, "channel", base_url, str(epgdict[c]['thumbnail']))
|
||||
sub_el(c_out, 'icon', src=(str(channel_thumbnail)))
|
||||
else:
|
||||
sub_el(c_out, 'icon', src=("http://" + str(base_url) + "/images?source=empty&type=channel&id=empty"))
|
||||
sub_el(c_out, 'icon', src=("http://" + str(base_url) + "/images?source=empty&type=channel&id=" + c['number']))
|
||||
|
||||
for progitem in list(epgdict.keys()):
|
||||
|
||||
@ -102,12 +74,7 @@ class EPGhandler():
|
||||
|
||||
sub_el(prog_out, 'desc', lang='en', text=program['description'])
|
||||
|
||||
if ('movie' in program["genres"] or 'Movie' in program["genres"]) and program['releaseyear']:
|
||||
sub_el(prog_out, 'sub-title', lang='en', text='Movie: ' + program['releaseyear'])
|
||||
elif 'episodetitle' in program.keys():
|
||||
sub_el(prog_out, 'sub-title', lang='en', text=program['episodetitle'])
|
||||
else:
|
||||
sub_el(prog_out, 'sub-title', lang='en', text='Movie: ' + program['sub-title'])
|
||||
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'])))
|
||||
|
||||
@ -128,16 +95,10 @@ class EPGhandler():
|
||||
text='S%02dE%02d' % (s_, e_))
|
||||
|
||||
if program["thumbnail"] is not None:
|
||||
if epg_method == "empty":
|
||||
sub_el(prog_out, 'icon', src=("http://" + str(base_url) + str(program['thumbnail'])))
|
||||
elif epg_method == "proxy":
|
||||
sub_el(prog_out, 'icon', src=("http://" + str(base_url) + str(program['thumbnail'])))
|
||||
elif epg_method == "zap2it":
|
||||
sub_el(prog_out, 'icon', src=(str(program['thumbnail'])))
|
||||
else:
|
||||
sub_el(prog_out, 'icon', src=(str(program['thumbnail'])))
|
||||
content_thumbnail = self.epgtypes.thumb_url(epg_method, "content", base_url, str(epgdict[c]['thumbnail']))
|
||||
sub_el(prog_out, 'icon', src=(str(content_thumbnail)))
|
||||
else:
|
||||
sub_el(prog_out, 'icon', src=("http://" + str(base_url) + "/images?source=empty&type=content&id=empty"))
|
||||
sub_el(prog_out, 'icon', src=("http://" + str(base_url) + "/images?source=empty&type=content&id=" + program['title']))
|
||||
|
||||
if program['rating']:
|
||||
rating_out = sub_el(prog_out, 'rating', system="MPAA")
|
||||
@ -153,38 +114,25 @@ class EPGhandler():
|
||||
|
||||
def dummyxml(self):
|
||||
out = ET.Element('tv')
|
||||
out.set('source-info-url', 'NextPVR')
|
||||
out.set('source-info-name', 'NextPVR')
|
||||
out.set('source-info-url', self.config["fakehdhr"]["friendlyname"])
|
||||
out.set('source-info-name', self.config["main"]["servicename"])
|
||||
out.set('generator-info-name', 'FAKEHDHR')
|
||||
out.set('generator-info-url', 'FAKEHDHR/FakeHDHR_NextPVR')
|
||||
out.set('generator-info-url', 'FAKEHDHR/' + self.config["main"]["reponame"])
|
||||
|
||||
fakefile = BytesIO()
|
||||
fakefile.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
fakefile.write(ET.tostring(out, encoding='UTF-8'))
|
||||
return fakefile.getvalue()
|
||||
|
||||
def update(self):
|
||||
if self.config["fakehdhr"]["epg_method"] == "empty":
|
||||
self.emptyepg.update_epg()
|
||||
elif self.config["fakehdhr"]["epg_method"] == "proxy":
|
||||
self.serviceproxy.update_epg()
|
||||
elif self.config["fakehdhr"]["epg_method"] == "zap2it":
|
||||
self.zapepg.update_epg()
|
||||
|
||||
|
||||
def epgServerProcess(config, epghandling):
|
||||
|
||||
if config.config["fakehdhr"]["epg_method"] == "empty":
|
||||
sleeptime = config.config["main"]["empty_epg_update_frequency"]
|
||||
elif config.config["fakehdhr"]["epg_method"] == "proxy":
|
||||
sleeptime = config.config["nextpvr"]["epg_update_frequency"]
|
||||
elif config.config["fakehdhr"]["epg_method"] == "zap2it":
|
||||
sleeptime = config.config["zap2xml"]["epg_update_frequency"]
|
||||
sleeptime = int(config.config[config.config["fakehdhr"]["epg_method"]]["epg_update_frequency"])
|
||||
|
||||
try:
|
||||
|
||||
while True:
|
||||
epghandling.update()
|
||||
epghandling.epgtypes.update()
|
||||
time.sleep(sleeptime)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
||||
28
epghandler/epgtypes/__init__.py
Normal file
28
epghandler/epgtypes/__init__.py
Normal file
@ -0,0 +1,28 @@
|
||||
from . import zap2it
|
||||
from . import empty
|
||||
|
||||
|
||||
class EPGTypes():
|
||||
|
||||
def __init__(self, config, serviceproxy):
|
||||
self.config = config.config
|
||||
self.proxy = serviceproxy
|
||||
self.zap2it = zap2it.ZapEPG(config, serviceproxy)
|
||||
self.empty = empty.EmptyEPG(config, serviceproxy)
|
||||
|
||||
def get_epg(self):
|
||||
method_to_call = getattr(self, self.config["fakehdhr"]["epg_method"])
|
||||
func_to_call = getattr(method_to_call, 'epg_cache_open')
|
||||
epgdict = func_to_call()
|
||||
return epgdict
|
||||
|
||||
def thumb_url(self, epg_method, thumb_type, base_url, thumbnail):
|
||||
method_to_call = getattr(self, self.config["fakehdhr"]["epg_method"])
|
||||
func_to_call = getattr(method_to_call, 'thumb_url')
|
||||
thumbnail = func_to_call(thumb_type, base_url, thumbnail)
|
||||
return thumbnail
|
||||
|
||||
def update(self):
|
||||
method_to_call = getattr(self, self.config["fakehdhr"]["epg_method"])
|
||||
func_to_call = getattr(method_to_call, 'update_epg')
|
||||
func_to_call()
|
||||
@ -5,15 +5,16 @@ import datetime
|
||||
|
||||
class EmptyEPG():
|
||||
|
||||
def __init__(self, config):
|
||||
def __init__(self, config, serviceproxy):
|
||||
|
||||
self.config = config.config
|
||||
self.serviceproxy = serviceproxy
|
||||
|
||||
self.postalcode = None
|
||||
|
||||
self.epg_cache = None
|
||||
self.cache_dir = config.config["main"]["empty_cache"]
|
||||
self.epg_cache_file = config.config["main"]["empty_cache_file"]
|
||||
self.cache_dir = config.config["empty"]["empty_cache"]
|
||||
self.epg_cache_file = config.config["empty"]["empty_cache_file"]
|
||||
self.epg_cache = self.epg_cache_open()
|
||||
|
||||
def epg_cache_open(self):
|
||||
@ -23,6 +24,12 @@ class EmptyEPG():
|
||||
epg_cache = json.load(epgfile)
|
||||
return epg_cache
|
||||
|
||||
def thumb_url(self, thumb_type, base_url, thumbnail):
|
||||
if thumb_type == "channel":
|
||||
return "http://" + str(base_url) + str(thumbnail)
|
||||
elif thumb_type == "content":
|
||||
return "http://" + str(base_url) + str(thumbnail)
|
||||
|
||||
def update_epg(self):
|
||||
print('Updating Empty EPG cache file.')
|
||||
|
||||
@ -79,7 +86,7 @@ class EmptyEPG():
|
||||
programguide[str(c["number"])]["listing"].append(clean_prog_dict)
|
||||
|
||||
self.epg_cache = programguide
|
||||
with open(self.empty_cache_file, 'w') as epgfile:
|
||||
with open(self.epg_cache_file, 'w') as epgfile:
|
||||
epgfile.write(json.dumps(programguide, indent=4))
|
||||
print('Wrote updated Empty EPG cache file.')
|
||||
return programguide
|
||||
@ -34,19 +34,20 @@ def xmldictmaker(inputdict, req_items, list_items=[], str_items=[]):
|
||||
|
||||
class ZapEPG():
|
||||
|
||||
def __init__(self, config):
|
||||
def __init__(self, config, serviceproxy):
|
||||
|
||||
self.config = config.config
|
||||
self.serviceproxy = serviceproxy
|
||||
|
||||
self.postalcode = None
|
||||
|
||||
self.epg_cache = None
|
||||
self.cache_dir = config.config["main"]["zap_web_cache"]
|
||||
self.epg_cache_file = config.config["zap2xml"]["epg_cache"]
|
||||
self.epg_cache_file = config.config["zap2it"]["epg_cache"]
|
||||
self.epg_cache = self.epg_cache_open()
|
||||
|
||||
def get_location(self):
|
||||
self.postalcode = self.config["zap2xml"]["postalcode"]
|
||||
self.postalcode = self.config["zap2it"]["postalcode"]
|
||||
if self.postalcode:
|
||||
url = 'http://ipinfo.io/json'
|
||||
response = urllib.request.urlopen(url)
|
||||
@ -60,6 +61,12 @@ class ZapEPG():
|
||||
epg_cache = json.load(epgfile)
|
||||
return epg_cache
|
||||
|
||||
def thumb_url(self, thumb_type, base_url, thumbnail):
|
||||
if thumb_type == "channel":
|
||||
return thumbnail
|
||||
elif thumb_type == "content":
|
||||
return thumbnail
|
||||
|
||||
def get_cached(self, cache_key, delay, url):
|
||||
cache_path = self.cache_dir.joinpath(cache_key)
|
||||
if cache_path.is_file():
|
||||
@ -83,6 +90,7 @@ class ZapEPG():
|
||||
raise
|
||||
with open(cache_path, 'wb') as f:
|
||||
f.write(result)
|
||||
time.sleep(int(delay))
|
||||
return result
|
||||
|
||||
def remove_stale_cache(self, todaydate):
|
||||
@ -106,26 +114,26 @@ class ZapEPG():
|
||||
|
||||
# Start time parameter is now rounded down to nearest `zap_timespan`, in s.
|
||||
zap_time = time.mktime(time.localtime())
|
||||
zap_time_window = int(self.config["zap2xml"]["timespan"]) * 3600
|
||||
zap_time_window = int(self.config["zap2it"]["timespan"]) * 3600
|
||||
zap_time = int(zap_time - (zap_time % zap_time_window))
|
||||
|
||||
# Fetch data in `zap_timespan` chunks.
|
||||
for i in range(int(7 * 24 / int(self.config["zap2xml"]["timespan"]))):
|
||||
for i in range(int(7 * 24 / int(self.config["zap2it"]["timespan"]))):
|
||||
i_time = zap_time + (i * zap_time_window)
|
||||
|
||||
parameters = {
|
||||
'aid': self.config["zap2xml"]['affiliate_id'],
|
||||
'country': self.config["zap2xml"]['country'],
|
||||
'device': self.config["zap2xml"]['device'],
|
||||
'headendId': self.config["zap2xml"]['headendid'],
|
||||
'aid': self.config["zap2it"]['affiliate_id'],
|
||||
'country': self.config["zap2it"]['country'],
|
||||
'device': self.config["zap2it"]['device'],
|
||||
'headendId': self.config["zap2it"]['headendid'],
|
||||
'isoverride': "true",
|
||||
'languagecode': self.config["zap2xml"]['languagecode'],
|
||||
'languagecode': self.config["zap2it"]['languagecode'],
|
||||
'pref': 'm,p',
|
||||
'timespan': self.config["zap2xml"]['timespan'],
|
||||
'timezone': self.config["zap2xml"]['timezone'],
|
||||
'userId': self.config["zap2xml"]['userid'],
|
||||
'timespan': self.config["zap2it"]['timespan'],
|
||||
'timezone': self.config["zap2it"]['timezone'],
|
||||
'userId': self.config["zap2it"]['userid'],
|
||||
'postalCode': self.postalcode,
|
||||
'lineupId': '%s-%s-DEFAULT' % (self.config["zap2xml"]['country'], self.config["zap2xml"]['device']),
|
||||
'lineupId': '%s-%s-DEFAULT' % (self.config["zap2it"]['country'], self.config["zap2it"]['device']),
|
||||
'time': i_time,
|
||||
'Activity_ID': 1,
|
||||
'FromPage': "TV%20Guide",
|
||||
@ -134,7 +142,7 @@ class ZapEPG():
|
||||
url = 'https://tvlistings.zap2it.com/api/grid?'
|
||||
url += urllib.parse.urlencode(parameters)
|
||||
|
||||
result = self.get_cached(str(i_time), self.config["zap2xml"]['delay'], url)
|
||||
result = self.get_cached(str(i_time), self.config["zap2it"]['delay'], url)
|
||||
d = json.loads(result)
|
||||
|
||||
for c in d['channels']:
|
||||
@ -7,6 +7,7 @@ import json
|
||||
import time
|
||||
import requests
|
||||
import subprocess
|
||||
import threading
|
||||
import PIL.Image
|
||||
import PIL.ImageDraw
|
||||
import PIL.ImageFont
|
||||
@ -31,14 +32,38 @@ class HDHR_Hub():
|
||||
epghandling = None
|
||||
station_scan = False
|
||||
station_list = []
|
||||
http = None
|
||||
|
||||
def __init__(self):
|
||||
self.tuner_lock = threading.Lock()
|
||||
self.tuners = 0
|
||||
|
||||
def hubprep(self, config, serviceproxy, epghandling):
|
||||
self.config = config.config
|
||||
self.max_tuners = int(self.config["fakehdhr"]["tuner_count"])
|
||||
self.station_scan = False
|
||||
self.serviceproxy = serviceproxy
|
||||
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 get_tuner(self):
|
||||
if self.tuners <= self.max_tuners:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_xmltv(self, base_url):
|
||||
return self.epghandling.get_xmltv(base_url)
|
||||
|
||||
def get_image(self, req_args):
|
||||
|
||||
imageid = req_args["id"]
|
||||
|
||||
if req_args["source"] == "proxy":
|
||||
@ -247,67 +272,77 @@ class HDHR_HTTP_Server():
|
||||
|
||||
@app.route('/watch', methods=['GET'])
|
||||
def watch():
|
||||
if 'method' in list(request.args.keys()):
|
||||
if 'channel' in list(request.args.keys()):
|
||||
|
||||
channelUri = hdhr.serviceproxy.get_channel_stream(str(request.args["channel"]))
|
||||
if not channelUri:
|
||||
abort(404)
|
||||
if 'method' in list(request.args.keys()) and 'channel' in list(request.args.keys()):
|
||||
|
||||
if request.args["method"] == "direct":
|
||||
duration = request.args.get('duration', default=0, type=int)
|
||||
method = str(request.args["method"])
|
||||
channel_id = str(request.args["channel"])
|
||||
|
||||
if not duration == 0:
|
||||
duration += time.time()
|
||||
tuner = hdhr.get_tuner()
|
||||
if not tuner:
|
||||
abort(503)
|
||||
|
||||
req = requests.get(channelUri, stream=True)
|
||||
channelUri = hdhr.serviceproxy.get_channel_stream(channel_id)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
yield ''
|
||||
for chunk in req.iter_content(chunk_size=hdhr.config["direct_stream"]['chunksize']):
|
||||
if not duration == 0 and not time.time() < duration:
|
||||
req.close()
|
||||
break
|
||||
yield chunk
|
||||
except GeneratorExit:
|
||||
req.close()
|
||||
print("Connection Closed.")
|
||||
if method == "direct":
|
||||
duration = request.args.get('duration', default=0, type=int)
|
||||
|
||||
return Response(generate(), content_type=req.headers['content-type'], direct_passthrough=True)
|
||||
if not duration == 0:
|
||||
duration += time.time()
|
||||
|
||||
if request.args["method"] == "ffmpeg":
|
||||
req = requests.get(channelUri, stream=True)
|
||||
hdhr.tuner_usage(1)
|
||||
|
||||
ffmpeg_command = [hdhr.config["ffmpeg"]["ffmpeg_path"],
|
||||
"-i", channelUri,
|
||||
"-c", "copy",
|
||||
"-f", "mpegts",
|
||||
"-nostats", "-hide_banner",
|
||||
"-loglevel", "warning",
|
||||
"pipe:stdout"
|
||||
]
|
||||
def generate():
|
||||
try:
|
||||
yield ''
|
||||
for chunk in req.iter_content(chunk_size=int(hdhr.config["direct_stream"]['chunksize'])):
|
||||
if not duration == 0 and not time.time() < duration:
|
||||
req.close()
|
||||
hdhr.tuner_usage(-1)
|
||||
break
|
||||
yield chunk
|
||||
except GeneratorExit:
|
||||
req.close()
|
||||
print("Connection Closed.")
|
||||
hdhr.tuner_usage(-1)
|
||||
|
||||
ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE)
|
||||
return Response(generate(), content_type=req.headers['content-type'], direct_passthrough=True)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
while True:
|
||||
videoData = ffmpeg_proc.stdout.read(int(hdhr.config["ffmpeg"]["bytes_per_read"]))
|
||||
if not videoData:
|
||||
break
|
||||
try:
|
||||
yield videoData
|
||||
except Exception as e:
|
||||
ffmpeg_proc.terminate()
|
||||
ffmpeg_proc.communicate()
|
||||
print("Connection Closed: " + str(e))
|
||||
except GeneratorExit:
|
||||
ffmpeg_proc.terminate()
|
||||
ffmpeg_proc.communicate()
|
||||
print("Connection Closed.")
|
||||
elif method == "ffmpeg":
|
||||
|
||||
return Response(stream_with_context(generate()), mimetype="audio/mpeg")
|
||||
abort(404)
|
||||
ffmpeg_command = [hdhr.config["ffmpeg"]["ffmpeg_path"],
|
||||
"-i", channelUri,
|
||||
"-c", "copy",
|
||||
"-f", "mpegts",
|
||||
"-nostats", "-hide_banner",
|
||||
"-loglevel", "warning",
|
||||
"pipe:stdout"
|
||||
]
|
||||
|
||||
ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE)
|
||||
hdhr.tuner_usage(1)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
while True:
|
||||
videoData = ffmpeg_proc.stdout.read(int(hdhr.config["ffmpeg"]["bytes_per_read"]))
|
||||
if not videoData:
|
||||
break
|
||||
try:
|
||||
yield videoData
|
||||
except Exception as e:
|
||||
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)
|
||||
|
||||
return Response(stream_with_context(generate()), mimetype="audio/mpeg")
|
||||
|
||||
@app.route('/lineup.post', methods=['POST'])
|
||||
def lineup_post():
|
||||
@ -334,17 +369,17 @@ class HDHR_HTTP_Server():
|
||||
self.config = config.config
|
||||
|
||||
def run(self):
|
||||
http = WSGIServer((
|
||||
self.http = WSGIServer((
|
||||
self.config["fakehdhr"]["address"],
|
||||
int(self.config["fakehdhr"]["port"])
|
||||
), self.app.wsgi_app)
|
||||
http.serve_forever()
|
||||
try:
|
||||
self.http.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
self.http.stop()
|
||||
|
||||
|
||||
def interface_start(config, serviceproxy, epghandling):
|
||||
hdhr.config = config.config
|
||||
hdhr.station_scan = False
|
||||
hdhr.serviceproxy = serviceproxy
|
||||
hdhr.epghandling = epghandling
|
||||
hdhr.hubprep(config, serviceproxy, epghandling)
|
||||
fakhdhrserver = HDHR_HTTP_Server(config)
|
||||
fakhdhrserver.run()
|
||||
|
||||
@ -17,67 +17,16 @@ class HDHRConfig():
|
||||
config_handler = configparser.ConfigParser()
|
||||
script_dir = None
|
||||
|
||||
config = {
|
||||
"main": {
|
||||
'uuid': None,
|
||||
"cache_dir": None,
|
||||
"empty_epg_update_frequency": 43200,
|
||||
},
|
||||
"nextpvr": {
|
||||
"address": "localhost",
|
||||
"port": 8866,
|
||||
"ssl": False,
|
||||
"pin": None,
|
||||
"weight": 300, # subscription priority
|
||||
"sidfile": None,
|
||||
"epg_update_frequency": 43200,
|
||||
},
|
||||
"fakehdhr": {
|
||||
"address": "0.0.0.0",
|
||||
"port": 5004,
|
||||
"discovery_address": "0.0.0.0",
|
||||
"tuner_count": 4, # number of tuners in tvh
|
||||
"concurrent_listeners": 10,
|
||||
"friendlyname": "fHDHR-NextPVR",
|
||||
"stream_type": "direct",
|
||||
"epg_method": "proxy",
|
||||
"font": None,
|
||||
},
|
||||
"zap2xml": {
|
||||
"delay": 5,
|
||||
"postalcode": None,
|
||||
"affiliate_id": 'gapzap',
|
||||
"country": 'USA',
|
||||
"device": '-',
|
||||
"headendid": "lineupId",
|
||||
"isoverride": True,
|
||||
"languagecode": 'en',
|
||||
"pref": "",
|
||||
"timespan": 6,
|
||||
"timezone": "",
|
||||
"userid": "-",
|
||||
"epg_update_frequency": 43200,
|
||||
},
|
||||
"ffmpeg": {
|
||||
'ffmpeg_path': "ffmpeg",
|
||||
'bytes_per_read': '1152000',
|
||||
"font": None,
|
||||
},
|
||||
"direct_stream": {
|
||||
'chunksize': 1024*1024 # usually you don't need to edit this
|
||||
},
|
||||
"dev": {
|
||||
'reporting_model': 'HDHR4-2DT',
|
||||
'reporting_firmware_name': 'hdhomerun4_dvbt',
|
||||
'reporting_firmware_ver': '20150826',
|
||||
'reporting_tuner_type': "Antenna",
|
||||
}
|
||||
}
|
||||
config = {}
|
||||
|
||||
def __init__(self, script_dir, args):
|
||||
self.get_config_path(script_dir, args)
|
||||
self.import_default_config(script_dir)
|
||||
self.import_service_config(script_dir)
|
||||
self.import_config()
|
||||
self.config_adjustments(script_dir)
|
||||
self.critical_config(script_dir)
|
||||
self.config_adjustments_this()
|
||||
self.config_adjustments()
|
||||
|
||||
def get_config_path(self, script_dir, args):
|
||||
if args.cfg:
|
||||
@ -95,6 +44,42 @@ class HDHRConfig():
|
||||
for (each_key, each_val) in self.config_handler.items(each_section):
|
||||
self.config[each_section.lower()][each_key.lower()] = each_val
|
||||
|
||||
def import_default_config(self, script_dir):
|
||||
config_handler = configparser.ConfigParser()
|
||||
data_dir = pathlib.Path(script_dir).joinpath('data')
|
||||
internal_config_dir = pathlib.Path(data_dir).joinpath('internal_config')
|
||||
serviceconf = pathlib.Path(internal_config_dir).joinpath('fakehdhr.ini')
|
||||
config_handler.read(serviceconf)
|
||||
for each_section in config_handler.sections():
|
||||
if each_section not in list(self.config.keys()):
|
||||
self.config[each_section] = {}
|
||||
for (each_key, each_val) in config_handler.items(each_section):
|
||||
if each_val == "fHDHR_None":
|
||||
each_val = None
|
||||
elif each_val == "fHDHR_True":
|
||||
each_val = True
|
||||
elif each_val == "fHDHR_False":
|
||||
each_val = False
|
||||
self.config[each_section.lower()][each_key.lower()] = each_val
|
||||
|
||||
def import_service_config(self, script_dir):
|
||||
config_handler = configparser.ConfigParser()
|
||||
data_dir = pathlib.Path(script_dir).joinpath('data')
|
||||
internal_config_dir = pathlib.Path(data_dir).joinpath('internal_config')
|
||||
serviceconf = pathlib.Path(internal_config_dir).joinpath('serviceconf.ini')
|
||||
config_handler.read(serviceconf)
|
||||
for each_section in config_handler.sections():
|
||||
if each_section not in list(self.config.keys()):
|
||||
self.config[each_section] = {}
|
||||
for (each_key, each_val) in config_handler.items(each_section):
|
||||
if each_val == "fHDHR_None":
|
||||
each_val = None
|
||||
elif each_val == "fHDHR_True":
|
||||
each_val = True
|
||||
elif each_val == "fHDHR_False":
|
||||
each_val = False
|
||||
self.config[each_section.lower()][each_key.lower()] = each_val
|
||||
|
||||
def write(self, section, key, value):
|
||||
self.config[section][key] = value
|
||||
self.config_handler.set(section, key, value)
|
||||
@ -102,7 +87,7 @@ class HDHRConfig():
|
||||
with open(self.config_file, 'w') as config_file:
|
||||
self.config_handler.write(config_file)
|
||||
|
||||
def config_adjustments(self, script_dir):
|
||||
def critical_config(self, script_dir):
|
||||
|
||||
self.config["main"]["script_dir"] = script_dir
|
||||
|
||||
@ -120,41 +105,17 @@ class HDHRConfig():
|
||||
clean_exit()
|
||||
cache_dir = self.config["main"]["cache_dir"]
|
||||
|
||||
if not self.config["nextpvr"]["pin"]:
|
||||
print("NextPVR Login Credentials Missing. Exiting...")
|
||||
clean_exit()
|
||||
|
||||
empty_cache = pathlib.Path(cache_dir).joinpath('empty_cache')
|
||||
self.config["main"]["empty_cache"] = empty_cache
|
||||
self.config["empty"]["empty_cache"] = empty_cache
|
||||
if not empty_cache.is_dir():
|
||||
empty_cache.mkdir()
|
||||
self.config["main"]["empty_cache_file"] = pathlib.Path(empty_cache).joinpath('epg.json')
|
||||
|
||||
nextpvr_cache = pathlib.Path(cache_dir).joinpath('nextpvr')
|
||||
self.config["main"]["nextpvr_cache"] = nextpvr_cache
|
||||
if not nextpvr_cache.is_dir():
|
||||
nextpvr_cache.mkdir()
|
||||
self.config["nextpvr"]["sidfile"] = pathlib.Path(nextpvr_cache).joinpath('sid.txt')
|
||||
self.config["nextpvr"]["epg_cache"] = pathlib.Path(nextpvr_cache).joinpath('epg.json')
|
||||
|
||||
zap_cache = pathlib.Path(cache_dir).joinpath('zap2it')
|
||||
self.config["main"]["zap_cache"] = zap_cache
|
||||
if not zap_cache.is_dir():
|
||||
zap_cache.mkdir()
|
||||
self.config["zap2xml"]["epg_cache"] = pathlib.Path(zap_cache).joinpath('epg.json')
|
||||
zap_web_cache = pathlib.Path(zap_cache).joinpath('zap_web_cache')
|
||||
self.config["main"]["zap_web_cache"] = zap_web_cache
|
||||
if not zap_web_cache.is_dir():
|
||||
zap_web_cache.mkdir()
|
||||
self.config["empty"]["empty_cache_file"] = pathlib.Path(empty_cache).joinpath('epg.json')
|
||||
|
||||
www_dir = pathlib.Path(data_dir).joinpath('www')
|
||||
self.config["main"]["www_dir"] = www_dir
|
||||
self.config["main"]["favicon"] = pathlib.Path(www_dir).joinpath('favicon.ico')
|
||||
|
||||
www_image_dir = pathlib.Path(www_dir).joinpath('images')
|
||||
self.config["main"]["www_image_dir"] = www_image_dir
|
||||
self.config["main"]["image_def_channel"] = pathlib.Path(www_image_dir).joinpath("default-channel-thumb.png")
|
||||
self.config["main"]["image_def_content"] = pathlib.Path(www_image_dir).joinpath("default-content-thumb.png")
|
||||
def config_adjustments(self):
|
||||
|
||||
# generate UUID here for when we are not using docker
|
||||
if self.config["main"]["uuid"] is None:
|
||||
@ -165,6 +126,52 @@ class HDHRConfig():
|
||||
self.write('main', 'uuid', self.config["main"]["uuid"])
|
||||
print("UUID set to: " + self.config["main"]["uuid"] + "...")
|
||||
|
||||
if not self.config["fakehdhr"]["discovery_address"]:
|
||||
if self.config["fakehdhr"]["address"] != "0.0.0.0":
|
||||
self.config["fakehdhr"]["discovery_address"] = self.config["fakehdhr"]["address"]
|
||||
|
||||
print("Server is set to run on " +
|
||||
str(self.config["fakehdhr"]["address"]) + ":" +
|
||||
str(self.config["fakehdhr"]["port"]))
|
||||
|
||||
def config_adjustments_this(self):
|
||||
self.config["proxy"] = self.config.pop(self.config["main"]["dictpopname"])
|
||||
self.config_adjustments_proxy()
|
||||
self.config_adjustments_zap2it()
|
||||
|
||||
def config_adjustments_proxy(self):
|
||||
cache_dir = self.config["main"]["cache_dir"]
|
||||
|
||||
credentials_list = self.config["main"]["credentials"].split(",")
|
||||
creds_missing = False
|
||||
if len(credentials_list):
|
||||
for cred_item in credentials_list:
|
||||
if not self.config["proxy"][cred_item]:
|
||||
creds_missing = True
|
||||
if creds_missing:
|
||||
print(self.config["main"]["servicename"] + " Login Credentials Missing. Exiting...")
|
||||
clean_exit()
|
||||
|
||||
proxy_cache = pathlib.Path(cache_dir).joinpath('proxy')
|
||||
self.config["main"]["proxy_cache"] = proxy_cache
|
||||
if not proxy_cache.is_dir():
|
||||
proxy_cache.mkdir()
|
||||
self.config["proxy"]["sidfile"] = pathlib.Path(proxy_cache).joinpath('sid.txt')
|
||||
self.config["proxy"]["epg_cache"] = pathlib.Path(proxy_cache).joinpath('epg.json')
|
||||
proxy_web_cache = pathlib.Path(proxy_cache).joinpath('proxy_web_cache')
|
||||
self.config["main"]["proxy_web_cache"] = proxy_web_cache
|
||||
if not proxy_web_cache.is_dir():
|
||||
proxy_web_cache.mkdir()
|
||||
|
||||
def config_adjustments_zap2it(self):
|
||||
cache_dir = self.config["main"]["cache_dir"]
|
||||
|
||||
zap_cache = pathlib.Path(cache_dir).joinpath('zap2it')
|
||||
self.config["main"]["zap_cache"] = zap_cache
|
||||
if not zap_cache.is_dir():
|
||||
zap_cache.mkdir()
|
||||
self.config["zap2it"]["epg_cache"] = pathlib.Path(zap_cache).joinpath('epg.json')
|
||||
zap_web_cache = pathlib.Path(zap_cache).joinpath('zap_web_cache')
|
||||
self.config["main"]["zap_web_cache"] = zap_web_cache
|
||||
if not zap_web_cache.is_dir():
|
||||
zap_web_cache.mkdir()
|
||||
|
||||
17
main.py
17
main.py
@ -55,11 +55,20 @@ if __name__ == '__main__':
|
||||
fhdhrServer = Process(target=fakehdhr.interface_start, args=(config, serviceproxy, epghandling))
|
||||
fhdhrServer.start()
|
||||
|
||||
print("Starting SSDP server...")
|
||||
ssdpServer = Process(target=ssdpserver.ssdpServerProcess, args=(config,))
|
||||
ssdpServer.daemon = True
|
||||
ssdpServer.start()
|
||||
if (config.config["fakehdhr"]["discovery_address"] and
|
||||
config.config["fakehdhr"]["discovery_address"] != "0.0.0.0"):
|
||||
print("Starting SSDP server...")
|
||||
ssdpServer = Process(target=ssdpserver.ssdpServerProcess, args=(config,))
|
||||
ssdpServer.daemon = True
|
||||
ssdpServer.start()
|
||||
else:
|
||||
ssdpServer = None
|
||||
print("Not Starting SSDP server...")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('^C received, shutting down the server')
|
||||
epgServer.terminate()
|
||||
fhdhrServer.terminate()
|
||||
if ssdpServer:
|
||||
ssdpServer.terminate()
|
||||
clean_exit()
|
||||
|
||||
@ -35,12 +35,12 @@ class NextPVR_Auth():
|
||||
sidfile = None
|
||||
|
||||
def __init__(self, config):
|
||||
self.sidfile = config.config["nextpvr"]["sidfile"]
|
||||
self.config["npvrPIN"] = config.config["nextpvr"]["pin"]
|
||||
self.sidfile = config.config["proxy"]["sidfile"]
|
||||
self.config["npvrPIN"] = config.config["proxy"]["pin"]
|
||||
self.config["npvrURL"] = ('%s%s:%s' %
|
||||
("https://" if config.config["nextpvr"]["ssl"] else "http://",
|
||||
config.config["nextpvr"]["address"],
|
||||
str(config.config["nextpvr"]["port"]),
|
||||
("https://" if config.config["proxy"]["ssl"] else "http://",
|
||||
config.config["proxy"]["address"],
|
||||
str(config.config["proxy"]["port"]),
|
||||
))
|
||||
|
||||
def _check_sid(self):
|
||||
@ -100,7 +100,7 @@ class proxyserviceFetcher():
|
||||
self.config = config.config
|
||||
|
||||
self.epg_cache = None
|
||||
self.epg_cache_file = config.config["nextpvr"]["epg_cache"]
|
||||
self.epg_cache_file = config.config["proxy"]["epg_cache"]
|
||||
|
||||
self.servicename = "NextPVRProxy"
|
||||
|
||||
@ -118,6 +118,12 @@ class proxyserviceFetcher():
|
||||
epg_cache = json.load(epgfile)
|
||||
return epg_cache
|
||||
|
||||
def thumb_url(self, thumb_type, base_url, thumbnail):
|
||||
if thumb_type == "channel":
|
||||
return "http://" + str(base_url) + str(thumbnail)
|
||||
elif thumb_type == "content":
|
||||
return "http://" + str(base_url) + str(thumbnail)
|
||||
|
||||
def url_assembler(self):
|
||||
pass
|
||||
|
||||
@ -125,9 +131,9 @@ class proxyserviceFetcher():
|
||||
self.auth._check_sid()
|
||||
|
||||
url = ('%s%s:%s/service?method=channel.list&sid=%s' %
|
||||
("https://" if self.config["nextpvr"]["ssl"] else "http://",
|
||||
self.config["nextpvr"]["address"],
|
||||
str(self.config["nextpvr"]["port"]),
|
||||
("https://" if self.config["proxy"]["ssl"] else "http://",
|
||||
self.config["proxy"]["address"],
|
||||
str(self.config["proxy"]["port"]),
|
||||
self.auth.config['sid']
|
||||
))
|
||||
|
||||
@ -183,9 +189,9 @@ class proxyserviceFetcher():
|
||||
|
||||
def get_channel_stream(self, id):
|
||||
url = ('%s%s:%s/live?channel=%s&client=%s' %
|
||||
("https://" if self.config["nextpvr"]["ssl"] else "http://",
|
||||
self.config["nextpvr"]["address"],
|
||||
str(self.config["nextpvr"]["port"]),
|
||||
("https://" if self.config["proxy"]["ssl"] else "http://",
|
||||
self.config["proxy"]["address"],
|
||||
str(self.config["proxy"]["port"]),
|
||||
str(id),
|
||||
str(id),
|
||||
))
|
||||
@ -195,9 +201,9 @@ class proxyserviceFetcher():
|
||||
streamdict = {}
|
||||
for c in self.get_channels():
|
||||
url = ('%s%s:%s/live?channel=%s&client=%s' %
|
||||
("https://" if self.config["nextpvr"]["ssl"] else "http://",
|
||||
self.config["nextpvr"]["address"],
|
||||
str(self.config["nextpvr"]["port"]),
|
||||
("https://" if self.config["proxy"]["ssl"] else "http://",
|
||||
self.config["proxy"]["address"],
|
||||
str(self.config["proxy"]["port"]),
|
||||
str(c["number"]),
|
||||
str(c["number"]),
|
||||
))
|
||||
@ -206,9 +212,9 @@ class proxyserviceFetcher():
|
||||
|
||||
def get_channel_thumbnail(self, channel_id):
|
||||
channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" %
|
||||
("https://" if self.config["nextpvr"]["ssl"] else "http://",
|
||||
self.config["nextpvr"]["address"],
|
||||
str(self.config["nextpvr"]["port"]),
|
||||
("https://" if self.config["proxy"]["ssl"] else "http://",
|
||||
self.config["proxy"]["address"],
|
||||
str(self.config["proxy"]["port"]),
|
||||
str(channel_id)
|
||||
))
|
||||
return channel_thumb_url
|
||||
@ -216,9 +222,9 @@ class proxyserviceFetcher():
|
||||
def get_content_thumbnail(self, content_id):
|
||||
self.auth._check_sid()
|
||||
item_thumb_url = ("%s%s:%s/service?method=channel.show.artwork&sid=%s&event_id=%s" %
|
||||
("https://" if self.config["nextpvr"]["ssl"] else "http://",
|
||||
self.config["nextpvr"]["address"],
|
||||
str(self.config["nextpvr"]["port"]),
|
||||
("https://" if self.config["proxy"]["ssl"] else "http://",
|
||||
self.config["proxy"]["address"],
|
||||
str(self.config["proxy"]["port"]),
|
||||
self.auth.config['sid'],
|
||||
str(content_id)
|
||||
))
|
||||
@ -246,9 +252,9 @@ class proxyserviceFetcher():
|
||||
}
|
||||
|
||||
epg_url = ('%s%s:%s/service?method=channel.listings&channel_id=%s' %
|
||||
("https://" if self.config["nextpvr"]["ssl"] else "http://",
|
||||
self.config["nextpvr"]["address"],
|
||||
str(self.config["nextpvr"]["port"]),
|
||||
("https://" if self.config["proxy"]["ssl"] else "http://",
|
||||
self.config["proxy"]["address"],
|
||||
str(self.config["proxy"]["port"]),
|
||||
str(cdict["id"]),
|
||||
))
|
||||
epg_req = urllib.request.urlopen(epg_url)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user