mirror of
https://github.com/fHDHR/fHDHR_NextPVR.git
synced 2025-12-06 10:36:59 -05:00
Improve Tuner Handling
This commit is contained in:
parent
3f3fec7bf4
commit
890cf7c3dd
@ -1,7 +1,6 @@
|
||||
from .channels import Channels
|
||||
from .epg import EPG
|
||||
from .tuners import Tuners
|
||||
from .watch import WatchStream
|
||||
from .images import imageHandler
|
||||
from .station_scan import Station_Scan
|
||||
from .ssdp import SSDPServer
|
||||
@ -16,9 +15,7 @@ class fHDHR_Device():
|
||||
|
||||
self.epg = EPG(fhdhr, self.channels, origin)
|
||||
|
||||
self.tuners = Tuners(fhdhr, self.epg)
|
||||
|
||||
self.watch = WatchStream(fhdhr, self.channels, self.tuners)
|
||||
self.tuners = Tuners(fhdhr, self.epg, self.channels)
|
||||
|
||||
self.images = imageHandler(fhdhr, self.epg)
|
||||
|
||||
|
||||
@ -1,111 +0,0 @@
|
||||
import threading
|
||||
import datetime
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
from fHDHR.tools import humanized_time
|
||||
|
||||
|
||||
class Tuner():
|
||||
def __init__(self, fhdhr, inum, epg):
|
||||
self.fhdhr = fhdhr
|
||||
self.number = inum
|
||||
self.epg = epg
|
||||
self.tuner_lock = threading.Lock()
|
||||
self.set_off_status()
|
||||
|
||||
def grab(self, stream_args):
|
||||
if self.tuner_lock.locked():
|
||||
raise TunerError("Tuner #" + str(self.number) + " is not available.")
|
||||
|
||||
self.fhdhr.logger.info("Tuner #" + str(self.number) + " to be used for stream.")
|
||||
self.tuner_lock.acquire()
|
||||
self.status = {
|
||||
"status": "Active",
|
||||
"method": stream_args["method"],
|
||||
"accessed": stream_args["accessed"],
|
||||
"channel": stream_args["channel"],
|
||||
"proxied_url": stream_args["channelUri"],
|
||||
"time_start": datetime.datetime.utcnow(),
|
||||
}
|
||||
|
||||
def close(self):
|
||||
self.fhdhr.logger.info("Tuner #" + str(self.number) + " Shutting Down.")
|
||||
self.set_off_status()
|
||||
self.tuner_lock.release()
|
||||
|
||||
def get_status(self):
|
||||
current_status = self.status.copy()
|
||||
if current_status["status"] == "Active":
|
||||
current_status["Play Time"] = str(
|
||||
humanized_time(
|
||||
int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds())))
|
||||
current_status["time_start"] = str(current_status["time_start"])
|
||||
current_status["epg"] = self.epg.whats_on_now(current_status["channel"])
|
||||
return current_status
|
||||
|
||||
def set_off_status(self):
|
||||
self.status = {"status": "Inactive"}
|
||||
|
||||
|
||||
class Tuners():
|
||||
|
||||
def __init__(self, fhdhr, epg):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
self.epg = epg
|
||||
self.max_tuners = int(self.fhdhr.config.dict["fhdhr"]["tuner_count"])
|
||||
|
||||
self.tuners = {}
|
||||
|
||||
for i in range(1, self.max_tuners + 1):
|
||||
self.tuners[i] = Tuner(fhdhr, i, epg)
|
||||
|
||||
def tuner_grab(self, stream_args):
|
||||
tunerselected = None
|
||||
|
||||
if stream_args["tuner"]:
|
||||
if int(stream_args["tuner"]) not in list(self.tuners.keys()):
|
||||
raise TunerError("Tuner " + str(stream_args["tuner"]) + " does not exist.")
|
||||
self.tuners[int(stream_args["tuner"])].grab(stream_args)
|
||||
tunerselected = int(stream_args["tuner"])
|
||||
|
||||
else:
|
||||
|
||||
for tunernum in range(1, self.max_tuners + 1):
|
||||
try:
|
||||
self.tuners[int(tunernum)].grab(stream_args)
|
||||
except TunerError:
|
||||
continue
|
||||
else:
|
||||
tunerselected = tunernum
|
||||
break
|
||||
|
||||
if not tunerselected:
|
||||
raise TunerError("No Available Tuners.")
|
||||
else:
|
||||
return tunerselected
|
||||
|
||||
def tuner_close(self, tunernum):
|
||||
self.tuners[int(tunernum)].close()
|
||||
|
||||
def status(self):
|
||||
all_status = {}
|
||||
for tunernum in range(1, self.max_tuners + 1):
|
||||
all_status[tunernum] = self.tuners[int(tunernum)].get_status()
|
||||
return all_status
|
||||
|
||||
def available_tuner_count(self):
|
||||
available_tuners = 0
|
||||
for tunernum in range(1, self.max_tuners + 1):
|
||||
tuner_status = self.tuners[int(tunernum)].get_status()
|
||||
if tuner_status["status"] == "Inactive":
|
||||
available_tuners += 1
|
||||
return available_tuners
|
||||
|
||||
def inuse_tuner_count(self):
|
||||
inuse_tuners = 0
|
||||
for tunernum in range(1, self.max_tuners + 1):
|
||||
tuner_status = self.tuners[int(tunernum)].get_status()
|
||||
if tuner_status["status"] == "Active":
|
||||
inuse_tuners += 1
|
||||
return inuse_tuners
|
||||
86
fHDHR/device/tuners/__init__.py
Normal file
86
fHDHR/device/tuners/__init__.py
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
|
||||
from .tuner import Tuner
|
||||
|
||||
|
||||
class Tuners():
|
||||
|
||||
def __init__(self, fhdhr, epg, channels):
|
||||
self.fhdhr = fhdhr
|
||||
self.channels = channels
|
||||
|
||||
self.epg = epg
|
||||
self.max_tuners = int(self.fhdhr.config.dict["fhdhr"]["tuner_count"])
|
||||
|
||||
self.tuners = {}
|
||||
|
||||
for i in range(1, self.max_tuners + 1):
|
||||
self.tuners[i] = Tuner(fhdhr, i, epg)
|
||||
|
||||
def tuner_grab(self, tuner_number):
|
||||
|
||||
if int(tuner_number) not in list(self.tuners.keys()):
|
||||
self.fhdhr.logger.error("Tuner %s does not exist." % str(tuner_number))
|
||||
raise TunerError("806 - Tune Failed")
|
||||
|
||||
# TunerError will raise if unavailable
|
||||
self.tuners[int(tuner_number)].grab()
|
||||
|
||||
return tuner_number
|
||||
|
||||
def first_available(self):
|
||||
|
||||
if not self.available_tuner_count():
|
||||
raise TunerError("805 - All Tuners In Use")
|
||||
|
||||
for tunernum in list(self.tuners.keys()):
|
||||
try:
|
||||
self.tuners[int(tunernum)].grab()
|
||||
except TunerError:
|
||||
continue
|
||||
else:
|
||||
return tunernum
|
||||
|
||||
raise TunerError("805 - All Tuners In Use")
|
||||
|
||||
def tuner_close(self, tunernum):
|
||||
self.tuners[int(tunernum)].close()
|
||||
|
||||
def status(self):
|
||||
all_status = {}
|
||||
for tunernum in list(self.tuners.keys()):
|
||||
all_status[tunernum] = self.tuners[int(tunernum)].get_status()
|
||||
return all_status
|
||||
|
||||
def available_tuner_count(self):
|
||||
available_tuners = 0
|
||||
for tunernum in list(self.tuners.keys()):
|
||||
tuner_status = self.tuners[int(tunernum)].get_status()
|
||||
if tuner_status["status"] == "Inactive":
|
||||
available_tuners += 1
|
||||
return available_tuners
|
||||
|
||||
def inuse_tuner_count(self):
|
||||
inuse_tuners = 0
|
||||
for tunernum in list(self.tuners.keys()):
|
||||
tuner_status = self.tuners[int(tunernum)].get_status()
|
||||
if tuner_status["status"] == "Active":
|
||||
inuse_tuners += 1
|
||||
return inuse_tuners
|
||||
|
||||
def get_stream_info(self, stream_args):
|
||||
|
||||
stream_args["channelUri"] = self.channels.get_channel_stream(str(stream_args["channel"]))
|
||||
if not stream_args["channelUri"]:
|
||||
raise TunerError("806 - Tune Failed")
|
||||
|
||||
channelUri_headers = self.fhdhr.web.session.head(stream_args["channelUri"]).headers
|
||||
stream_args["true_content_type"] = channelUri_headers['Content-Type']
|
||||
|
||||
if stream_args["true_content_type"].startswith("application/"):
|
||||
stream_args["content_type"] = "video/mpeg"
|
||||
else:
|
||||
stream_args["content_type"] = stream_args["true_content_type"]
|
||||
|
||||
return stream_args
|
||||
22
fHDHR/device/tuners/stream/__init__.py
Normal file
22
fHDHR/device/tuners/stream/__init__.py
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
from .direct_stream import Direct_Stream
|
||||
from .ffmpeg_stream import FFMPEG_Stream
|
||||
from .vlc_stream import VLC_Stream
|
||||
|
||||
|
||||
class Stream():
|
||||
|
||||
def __init__(self, fhdhr, stream_args, tuner):
|
||||
self.fhdhr = fhdhr
|
||||
self.stream_args = stream_args
|
||||
|
||||
if stream_args["method"] == "ffmpeg":
|
||||
self.method = FFMPEG_Stream(fhdhr, stream_args, tuner)
|
||||
if stream_args["method"] == "vlc":
|
||||
self.method = VLC_Stream(fhdhr, stream_args, tuner)
|
||||
elif stream_args["method"] == "direct":
|
||||
self.method = Direct_Stream(fhdhr, stream_args, tuner)
|
||||
|
||||
def get(self):
|
||||
return self.method.get()
|
||||
112
fHDHR/device/tuners/stream/direct_stream.py
Normal file
112
fHDHR/device/tuners/stream/direct_stream.py
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
import time
|
||||
import re
|
||||
import urllib.parse
|
||||
|
||||
# from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class Direct_Stream():
|
||||
|
||||
def __init__(self, fhdhr, stream_args, tuner):
|
||||
self.fhdhr = fhdhr
|
||||
self.stream_args = stream_args
|
||||
self.tuner = tuner
|
||||
|
||||
self.chunksize = int(self.fhdhr.config.dict["direct_stream"]['chunksize'])
|
||||
|
||||
def get(self):
|
||||
|
||||
if not self.stream_args["duration"] == 0:
|
||||
self.stream_args["time_end"] = self.stream_args["duration"] + time.time()
|
||||
|
||||
if not re.match('^(.*m3u8)[\n\r]*$', self.stream_args["channelUri"]):
|
||||
|
||||
self.fhdhr.logger.info("Direct Stream of URL: %s" % self.stream_args["channelUri"])
|
||||
|
||||
req = self.fhdhr.web.session.get(self.stream_args["channelUri"], stream=True)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
while self.tuner.tuner_lock.locked():
|
||||
|
||||
for chunk in req.iter_content(chunk_size=self.chunksize):
|
||||
|
||||
if (not self.stream_args["duration"] == 0 and
|
||||
not time.time() < self.stream_args["time_end"]):
|
||||
req.close()
|
||||
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||
self.tuner.close()
|
||||
|
||||
if not chunk:
|
||||
break
|
||||
# raise TunerError("807 - No Video Data")
|
||||
yield chunk
|
||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||
|
||||
except GeneratorExit:
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
except Exception as e:
|
||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||
finally:
|
||||
req.close()
|
||||
self.tuner.close()
|
||||
# raise TunerError("806 - Tune Failed")
|
||||
|
||||
else:
|
||||
|
||||
self.fhdhr.logger.info("Detected stream URL is m3u8: %s" % self.stream_args["true_content_type"])
|
||||
|
||||
# Determine if this m3u8 contains variants or chunks
|
||||
channelUri = self.stream_args["channelUri"]
|
||||
self.fhdhr.logger.info("Opening m3u8 URL: %s" % channelUri)
|
||||
m3u8_get = self.fhdhr.web.session.get(self.stream_args["channelUri"])
|
||||
m3u8_content = m3u8_get.text
|
||||
variants = [urllib.parse.urljoin(self.stream_args["channelUri"], line) for line in m3u8_content.split('\n') if re.match('^(.*m3u8)[\n\r]*$', line)]
|
||||
if len(variants):
|
||||
channelUri = variants[0]
|
||||
self.fhdhr.logger.info("m3u8 contained variants. Using URL: %s" % channelUri)
|
||||
|
||||
def generate():
|
||||
|
||||
try:
|
||||
|
||||
played_chunk_urls = []
|
||||
|
||||
while self.tuner.tuner_lock.locked():
|
||||
|
||||
m3u8_get = self.fhdhr.web.session.get(channelUri)
|
||||
m3u8_content = m3u8_get.text
|
||||
chunk_urls_detect = [urllib.parse.urljoin(channelUri, line) for line in m3u8_content.split('\n') if re.match('^(.*ts)[\n\r]*$', line)]
|
||||
|
||||
chunk_urls_play = []
|
||||
for chunkurl in chunk_urls_detect:
|
||||
if chunkurl not in played_chunk_urls:
|
||||
chunk_urls_play.append(chunkurl)
|
||||
played_chunk_urls.append(chunkurl)
|
||||
|
||||
for chunkurl in chunk_urls_play:
|
||||
|
||||
self.fhdhr.logger.info("Passing Through Chunk: %s" % chunkurl)
|
||||
|
||||
if (not self.stream_args["duration"] == 0 and
|
||||
not time.time() < self.stream_args["time_end"]):
|
||||
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||
self.tuner.close()
|
||||
|
||||
chunk = self.fhdhr.web.session.get(chunkurl).content
|
||||
if not chunk:
|
||||
break
|
||||
# raise TunerError("807 - No Video Data")
|
||||
yield chunk
|
||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||
|
||||
except GeneratorExit:
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
except Exception as e:
|
||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||
finally:
|
||||
self.tuner.close()
|
||||
# raise TunerError("806 - Tune Failed")
|
||||
|
||||
return generate()
|
||||
127
fHDHR/device/tuners/stream/ffmpeg_stream.py
Normal file
127
fHDHR/device/tuners/stream/ffmpeg_stream.py
Normal file
@ -0,0 +1,127 @@
|
||||
import subprocess
|
||||
|
||||
# from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class FFMPEG_Stream():
|
||||
|
||||
def __init__(self, fhdhr, stream_args, tuner):
|
||||
self.fhdhr = fhdhr
|
||||
self.stream_args = stream_args
|
||||
self.tuner = tuner
|
||||
|
||||
self.bytes_per_read = int(self.fhdhr.config.dict["ffmpeg"]["bytes_per_read"])
|
||||
self.ffmpeg_command = self.ffmpeg_command_assemble(stream_args)
|
||||
|
||||
def get(self):
|
||||
|
||||
ffmpeg_proc = subprocess.Popen(self.ffmpeg_command, stdout=subprocess.PIPE)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
while self.tuner.tuner_lock.locked():
|
||||
|
||||
videoData = ffmpeg_proc.stdout.read(self.bytes_per_read)
|
||||
if not videoData:
|
||||
break
|
||||
# raise TunerError("807 - No Video Data")
|
||||
yield videoData
|
||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||
|
||||
except GeneratorExit:
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
except Exception as e:
|
||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||
finally:
|
||||
ffmpeg_proc.terminate()
|
||||
ffmpeg_proc.communicate()
|
||||
self.tuner.close()
|
||||
# raise TunerError("806 - Tune Failed")
|
||||
|
||||
return generate()
|
||||
|
||||
def ffmpeg_command_assemble(self, stream_args):
|
||||
ffmpeg_command = [
|
||||
self.fhdhr.config.dict["ffmpeg"]["ffmpeg_path"],
|
||||
"-i", stream_args["channelUri"],
|
||||
]
|
||||
ffmpeg_command.extend(self.ffmpeg_duration(stream_args))
|
||||
ffmpeg_command.extend(self.transcode_profiles(stream_args))
|
||||
ffmpeg_command.extend(self.ffmpeg_loglevel())
|
||||
ffmpeg_command.extend(["pipe:stdout"])
|
||||
return ffmpeg_command
|
||||
|
||||
def ffmpeg_duration(self, stream_args):
|
||||
ffmpeg_command = []
|
||||
if stream_args["duration"]:
|
||||
ffmpeg_command.extend(["-t", str(stream_args["duration"])])
|
||||
else:
|
||||
ffmpeg_command.extend(
|
||||
[
|
||||
"-reconnect", "1",
|
||||
"-reconnect_at_eof", "1",
|
||||
"-reconnect_streamed", "1",
|
||||
"-reconnect_delay_max", "2",
|
||||
]
|
||||
)
|
||||
|
||||
return ffmpeg_command
|
||||
|
||||
def ffmpeg_loglevel(self):
|
||||
ffmpeg_command = []
|
||||
log_level = self.fhdhr.config.dict["logging"]["level"].lower()
|
||||
|
||||
loglevel_dict = {
|
||||
"debug": "debug",
|
||||
"info": "info",
|
||||
"error": "error",
|
||||
"warning": "warning",
|
||||
"critical": "fatal",
|
||||
}
|
||||
if log_level not in ["info", "debug"]:
|
||||
ffmpeg_command.extend(["-nostats", "-hide_banner"])
|
||||
ffmpeg_command.extend(["-loglevel", loglevel_dict[log_level]])
|
||||
return ffmpeg_command
|
||||
|
||||
def transcode_profiles(self, stream_args):
|
||||
# TODO implement actual profiles here
|
||||
"""
|
||||
• heavy: transcode to AVC with the same resolution, frame-rate, and interlacing as the
|
||||
original stream. For example 1080i60 AVC 1080i60, 720p60 AVC 720p60. → →
|
||||
• mobile: trancode to AVC progressive not exceeding 1280x720 30fps.
|
||||
• internet720: transcode to low bitrate AVC progressive not exceeding 1280x720 30fps.
|
||||
• internet480: transcode to low bitrate AVC progressive not exceeding 848x480 30fps for
|
||||
16:9 content, not exceeding 640x480 30fps for 4:3 content.
|
||||
• internet360: transcode to low bitrate AVC progressive not exceeding 640x360 30fps for
|
||||
16:9 content, not exceeding 480x360 30fps for 4:3 content.
|
||||
• internet240: transcode to low bitrate AVC progressive not exceeding 432x240 30fps for
|
||||
16:9 content, not exceeding 320x240 30fps for 4:3 content
|
||||
"""
|
||||
|
||||
if stream_args["transcode"]:
|
||||
self.fhdhr.logger.info("Client requested a " + stream_args["transcode"] + " transcode for stream.")
|
||||
stream_args["transcode"] = None
|
||||
|
||||
ffmpeg_command = []
|
||||
|
||||
if not stream_args["transcode"]:
|
||||
ffmpeg_command.extend(
|
||||
[
|
||||
"-c", "copy",
|
||||
"-f", "mpegts",
|
||||
]
|
||||
)
|
||||
elif stream_args["transcode"] == "heavy":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "mobile":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet720":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet480":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet360":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet240":
|
||||
ffmpeg_command.extend([])
|
||||
|
||||
return ffmpeg_command
|
||||
118
fHDHR/device/tuners/stream/vlc_stream.py
Normal file
118
fHDHR/device/tuners/stream/vlc_stream.py
Normal file
@ -0,0 +1,118 @@
|
||||
import subprocess
|
||||
|
||||
# from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class VLC_Stream():
|
||||
|
||||
def __init__(self, fhdhr, stream_args, tuner):
|
||||
self.fhdhr = fhdhr
|
||||
self.stream_args = stream_args
|
||||
self.tuner = tuner
|
||||
|
||||
self.bytes_per_read = int(self.fhdhr.config.dict["vlc"]["bytes_per_read"])
|
||||
self.vlc_command = self.vlc_command_assemble(stream_args)
|
||||
|
||||
def get(self):
|
||||
|
||||
vlc_proc = subprocess.Popen(self.vlc_command, stdout=subprocess.PIPE)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
|
||||
while self.tuner.tuner_lock.locked():
|
||||
|
||||
videoData = vlc_proc.stdout.read(self.bytes_per_read)
|
||||
if not videoData:
|
||||
break
|
||||
# raise TunerError("807 - No Video Data")
|
||||
yield videoData
|
||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||
|
||||
except GeneratorExit:
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
except Exception as e:
|
||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||
finally:
|
||||
vlc_proc.terminate()
|
||||
vlc_proc.communicate()
|
||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||
self.tuner.close()
|
||||
# raise TunerError("806 - Tune Failed")
|
||||
|
||||
return generate()
|
||||
|
||||
def vlc_command_assemble(self, stream_args):
|
||||
vlc_command = [
|
||||
self.fhdhr.config.dict["vlc"]["vlc_path"],
|
||||
"-I", "dummy", stream_args["channelUri"],
|
||||
]
|
||||
vlc_command.extend(self.vlc_duration(stream_args))
|
||||
vlc_command.extend(self.vlc_loglevel())
|
||||
vlc_command.extend(["--sout"])
|
||||
vlc_command.extend(self.transcode_profiles(stream_args))
|
||||
return vlc_command
|
||||
|
||||
def vlc_duration(self, stream_args):
|
||||
vlc_command = []
|
||||
if stream_args["duration"]:
|
||||
vlc_command.extend(["--run-time=%s" % str(stream_args["duration"])])
|
||||
return vlc_command
|
||||
|
||||
def vlc_loglevel(self):
|
||||
vlc_command = []
|
||||
log_level = self.fhdhr.config.dict["logging"]["level"].lower()
|
||||
|
||||
loglevel_dict = {
|
||||
"debug": "3",
|
||||
"info": "0",
|
||||
"error": "1",
|
||||
"warning": "2",
|
||||
"critical": "1",
|
||||
}
|
||||
vlc_command.extend(["--log-verbose=", loglevel_dict[log_level]])
|
||||
if log_level not in ["info", "debug"]:
|
||||
vlc_command.extend(["--quiet"])
|
||||
return vlc_command
|
||||
|
||||
def transcode_profiles(self, stream_args):
|
||||
# TODO implement actual profiles here
|
||||
"""
|
||||
• heavy: transcode to AVC with the same resolution, frame-rate, and interlacing as the
|
||||
original stream. For example 1080i60 AVC 1080i60, 720p60 AVC 720p60. → →
|
||||
• mobile: trancode to AVC progressive not exceeding 1280x720 30fps.
|
||||
• internet720: transcode to low bitrate AVC progressive not exceeding 1280x720 30fps.
|
||||
• internet480: transcode to low bitrate AVC progressive not exceeding 848x480 30fps for
|
||||
16:9 content, not exceeding 640x480 30fps for 4:3 content.
|
||||
• internet360: transcode to low bitrate AVC progressive not exceeding 640x360 30fps for
|
||||
16:9 content, not exceeding 480x360 30fps for 4:3 content.
|
||||
• internet240: transcode to low bitrate AVC progressive not exceeding 432x240 30fps for
|
||||
16:9 content, not exceeding 320x240 30fps for 4:3 content
|
||||
"""
|
||||
vlc_command = []
|
||||
|
||||
if stream_args["transcode"]:
|
||||
self.fhdhr.logger.info("Client requested a " + stream_args["transcode"] + " transcode for stream.")
|
||||
stream_args["transcode"] = None
|
||||
|
||||
vlc_transcode_string = "#std{mux=ts,access=file,dst=-}"
|
||||
return [vlc_transcode_string]
|
||||
|
||||
'#transcode{vcodec=mp2v,vb=4096,acodec=mp2a,ab=192,scale=1,channels=2,deinterlace}:std{access=file,mux=ts,dst=-"}'
|
||||
|
||||
if not stream_args["transcode"]:
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "heavy":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "mobile":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet720":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet480":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet360":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet240":
|
||||
vlc_command.extend([])
|
||||
|
||||
return vlc_command
|
||||
59
fHDHR/device/tuners/tuner.py
Normal file
59
fHDHR/device/tuners/tuner.py
Normal file
@ -0,0 +1,59 @@
|
||||
import threading
|
||||
import datetime
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
from fHDHR.tools import humanized_time
|
||||
|
||||
from .stream import Stream
|
||||
|
||||
|
||||
class Tuner():
|
||||
def __init__(self, fhdhr, inum, epg):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
self.number = inum
|
||||
self.epg = epg
|
||||
|
||||
self.tuner_lock = threading.Lock()
|
||||
self.set_off_status()
|
||||
|
||||
def grab(self):
|
||||
if self.tuner_lock.locked():
|
||||
self.fhdhr.logger.error("Tuner #" + str(self.number) + " is not available.")
|
||||
raise TunerError("804 - Tuner In Use")
|
||||
self.tuner_lock.acquire()
|
||||
self.status["status"] = "Acquired"
|
||||
self.fhdhr.logger.info("Tuner #" + str(self.number) + " Acquired.")
|
||||
|
||||
def close(self):
|
||||
self.set_off_status()
|
||||
if self.tuner_lock.locked():
|
||||
self.tuner_lock.release()
|
||||
self.fhdhr.logger.info("Tuner #" + str(self.number) + " Released.")
|
||||
|
||||
def get_status(self):
|
||||
current_status = self.status.copy()
|
||||
if current_status["status"] == "Active":
|
||||
current_status["Play Time"] = str(
|
||||
humanized_time(
|
||||
int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds())))
|
||||
current_status["time_start"] = str(current_status["time_start"])
|
||||
current_status["epg"] = self.epg.whats_on_now(current_status["channel"])
|
||||
return current_status
|
||||
|
||||
def set_off_status(self):
|
||||
self.status = {"status": "Inactive"}
|
||||
|
||||
def get_stream(self, stream_args, tuner):
|
||||
stream = Stream(self.fhdhr, stream_args, tuner)
|
||||
return stream.get()
|
||||
|
||||
def set_status(self, stream_args):
|
||||
self.status = {
|
||||
"status": "Active",
|
||||
"method": stream_args["method"],
|
||||
"accessed": stream_args["accessed"],
|
||||
"channel": stream_args["channel"],
|
||||
"proxied_url": stream_args["channelUri"],
|
||||
"time_start": datetime.datetime.utcnow(),
|
||||
}
|
||||
@ -1,244 +0,0 @@
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class WatchStream():
|
||||
|
||||
def __init__(self, fhdhr, origserv, tuners):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
self.origserv = origserv
|
||||
self.tuners = tuners
|
||||
|
||||
def direct_stream(self, stream_args, tunernum):
|
||||
|
||||
chunksize = int(self.fhdhr.config.dict["direct_stream"]['chunksize'])
|
||||
|
||||
if not stream_args["duration"] == 0:
|
||||
stream_args["duration"] += time.time()
|
||||
|
||||
req = self.fhdhr.web.session.get(stream_args["channelUri"], stream=True)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
for chunk in req.iter_content(chunk_size=chunksize):
|
||||
|
||||
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
||||
req.close()
|
||||
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||
break
|
||||
|
||||
yield chunk
|
||||
|
||||
except GeneratorExit:
|
||||
req.close()
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
self.tuners.tuner_close(tunernum)
|
||||
|
||||
return generate()
|
||||
|
||||
def ffmpeg_stream(self, stream_args, tunernum):
|
||||
|
||||
bytes_per_read = int(self.fhdhr.config.dict["ffmpeg"]["bytes_per_read"])
|
||||
|
||||
ffmpeg_command = self.transcode_profiles(stream_args)
|
||||
|
||||
if not stream_args["duration"] == 0:
|
||||
stream_args["duration"] += time.time()
|
||||
|
||||
ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
while True:
|
||||
|
||||
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
||||
ffmpeg_proc.terminate()
|
||||
ffmpeg_proc.communicate()
|
||||
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||
break
|
||||
|
||||
videoData = ffmpeg_proc.stdout.read(bytes_per_read)
|
||||
if not videoData:
|
||||
break
|
||||
yield videoData
|
||||
|
||||
except GeneratorExit:
|
||||
ffmpeg_proc.terminate()
|
||||
ffmpeg_proc.communicate()
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
self.tuners.tuner_close(tunernum)
|
||||
except Exception as e:
|
||||
ffmpeg_proc.terminate()
|
||||
ffmpeg_proc.communicate()
|
||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||
self.tuners.tuner_close(tunernum)
|
||||
|
||||
return generate()
|
||||
|
||||
def vlc_stream(self, stream_args, tunernum):
|
||||
|
||||
bytes_per_read = int(self.fhdhr.config.dict["vlc"]["bytes_per_read"])
|
||||
|
||||
vlc_command = self.transcode_profiles(stream_args)
|
||||
|
||||
if not stream_args["duration"] == 0:
|
||||
stream_args["duration"] += time.time()
|
||||
|
||||
vlc_proc = subprocess.Popen(vlc_command, stdout=subprocess.PIPE)
|
||||
|
||||
def generate():
|
||||
try:
|
||||
while True:
|
||||
|
||||
if not stream_args["duration"] == 0 and not time.time() < stream_args["duration"]:
|
||||
vlc_proc.terminate()
|
||||
vlc_proc.communicate()
|
||||
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||
break
|
||||
|
||||
videoData = vlc_proc.stdout.read(bytes_per_read)
|
||||
if not videoData:
|
||||
break
|
||||
yield videoData
|
||||
|
||||
except GeneratorExit:
|
||||
vlc_proc.terminate()
|
||||
vlc_proc.communicate()
|
||||
self.fhdhr.logger.info("Connection Closed.")
|
||||
self.tuners.tuner_close(tunernum)
|
||||
except Exception as e:
|
||||
vlc_proc.terminate()
|
||||
vlc_proc.communicate()
|
||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
||||
self.tuners.tuner_close(tunernum)
|
||||
|
||||
return generate()
|
||||
|
||||
def get_stream(self, stream_args):
|
||||
|
||||
try:
|
||||
tunernum = self.tuners.tuner_grab(stream_args)
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A " + stream_args["method"] + " stream request for channel " +
|
||||
str(stream_args["channel"]) + " was rejected do to " + str(e))
|
||||
return
|
||||
|
||||
self.fhdhr.logger.info("Attempting a " + stream_args["method"] + " stream request for channel " + str(stream_args["channel"]))
|
||||
|
||||
if stream_args["method"] == "ffmpeg":
|
||||
return self.ffmpeg_stream(stream_args, tunernum)
|
||||
if stream_args["method"] == "vlc":
|
||||
return self.vlc_stream(stream_args, tunernum)
|
||||
elif stream_args["method"] == "direct":
|
||||
return self.direct_stream(stream_args, tunernum)
|
||||
|
||||
def get_stream_info(self, stream_args):
|
||||
|
||||
stream_args["channelUri"] = self.origserv.get_channel_stream(str(stream_args["channel"]))
|
||||
if not stream_args["channelUri"]:
|
||||
self.fhdhr.logger.error("Could not Obtain Channel Stream.")
|
||||
stream_args["content_type"] = "video/mpeg"
|
||||
else:
|
||||
channelUri_headers = self.fhdhr.web.session.head(stream_args["channelUri"]).headers
|
||||
stream_args["content_type"] = channelUri_headers['Content-Type']
|
||||
|
||||
return stream_args
|
||||
|
||||
def transcode_profiles(self, stream_args):
|
||||
# TODO implement actual profiles here
|
||||
"""
|
||||
• heavy: transcode to AVC with the same resolution, frame-rate, and interlacing as the
|
||||
original stream. For example 1080i60 AVC 1080i60, 720p60 AVC 720p60. → →
|
||||
• mobile: trancode to AVC progressive not exceeding 1280x720 30fps.
|
||||
• internet720: transcode to low bitrate AVC progressive not exceeding 1280x720 30fps.
|
||||
• internet480: transcode to low bitrate AVC progressive not exceeding 848x480 30fps for
|
||||
16:9 content, not exceeding 640x480 30fps for 4:3 content.
|
||||
• internet360: transcode to low bitrate AVC progressive not exceeding 640x360 30fps for
|
||||
16:9 content, not exceeding 480x360 30fps for 4:3 content.
|
||||
• internet240: transcode to low bitrate AVC progressive not exceeding 432x240 30fps for
|
||||
16:9 content, not exceeding 320x240 30fps for 4:3 content
|
||||
"""
|
||||
|
||||
if stream_args["transcode"]:
|
||||
self.fhdhr.logger.info("Client requested a " + stream_args["transcode"] + " transcode for stream.")
|
||||
|
||||
log_level = self.fhdhr.config.dict["logging"]["level"].lower()
|
||||
|
||||
if stream_args["method"] == "direct":
|
||||
return None
|
||||
|
||||
elif stream_args["method"] == "ffmpeg":
|
||||
ffmpeg_command = [
|
||||
self.fhdhr.config.dict["ffmpeg"]["ffmpeg_path"],
|
||||
"-i", stream_args["channelUri"],
|
||||
"-c", "copy",
|
||||
"-f", "mpegts",
|
||||
]
|
||||
|
||||
if not stream_args["transcode"]:
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "heavy":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "mobile":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet720":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet480":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet360":
|
||||
ffmpeg_command.extend([])
|
||||
elif stream_args["transcode"] == "internet240":
|
||||
ffmpeg_command.extend([])
|
||||
|
||||
loglevel_dict = {
|
||||
"debug": "debug",
|
||||
"info": "info",
|
||||
"error": "error",
|
||||
"warning": "warning",
|
||||
"critical": "fatal",
|
||||
}
|
||||
if log_level not in ["info", "debug"]:
|
||||
ffmpeg_command.extend(["-nostats", "-hide_banner"])
|
||||
ffmpeg_command.extend(["-loglevel", loglevel_dict[log_level]])
|
||||
|
||||
ffmpeg_command.extend(["pipe:stdout"])
|
||||
return ffmpeg_command
|
||||
|
||||
elif stream_args["method"] == "vlc":
|
||||
vlc_command = [
|
||||
self.fhdhr.config.dict["vlc"]["vlc_path"],
|
||||
"-I", "dummy", stream_args["channelUri"],
|
||||
]
|
||||
|
||||
loglevel_dict = {
|
||||
"debug": "3",
|
||||
"info": "0",
|
||||
"error": "1",
|
||||
"warning": "2",
|
||||
"critical": "1",
|
||||
}
|
||||
vlc_command.extend(["--log-verbose=", loglevel_dict[log_level]])
|
||||
if log_level not in ["info", "debug"]:
|
||||
vlc_command.extend(["--quiet"])
|
||||
|
||||
if not stream_args["transcode"]:
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "heavy":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "mobile":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet720":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet480":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet360":
|
||||
vlc_command.extend([])
|
||||
elif stream_args["transcode"] == "internet240":
|
||||
vlc_command.extend([])
|
||||
|
||||
vlc_command.extend(["--sout", "#std{mux=ts,access=file,dst=-}"])
|
||||
|
||||
return vlc_command
|
||||
@ -5,6 +5,7 @@ from .lineup_post import Lineup_Post
|
||||
from .xmltv import xmlTV
|
||||
from .m3u import M3U
|
||||
from .epg import EPG
|
||||
from .watch import Watch
|
||||
from .debug import Debug_JSON
|
||||
|
||||
from .images import Images
|
||||
@ -20,6 +21,7 @@ class fHDHR_API():
|
||||
self.xmltv = xmlTV(fhdhr)
|
||||
self.m3u = M3U(fhdhr)
|
||||
self.epg = EPG(fhdhr)
|
||||
self.watch = Watch(fhdhr)
|
||||
self.debug = Debug_JSON(fhdhr)
|
||||
self.lineup_post = Lineup_Post(fhdhr)
|
||||
|
||||
|
||||
101
fHDHR/http/api/watch.py
Normal file
101
fHDHR/http/api/watch.py
Normal file
@ -0,0 +1,101 @@
|
||||
from flask import Response, request, redirect, abort, stream_with_context
|
||||
import urllib.parse
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class Watch():
|
||||
"""Methods to create xmltv.xml"""
|
||||
endpoints = ["/api/watch"]
|
||||
endpoint_name = "api_watch"
|
||||
|
||||
def __init__(self, fhdhr):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.get(*args)
|
||||
|
||||
def get(self, *args):
|
||||
|
||||
full_url = request.url
|
||||
|
||||
method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str)
|
||||
|
||||
tuner_number = request.args.get('tuner', None, type=str)
|
||||
|
||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||
|
||||
if method in ["direct", "ffmpeg", "vlc"]:
|
||||
|
||||
channel_number = request.args.get('channel', None, type=str)
|
||||
if not channel_number:
|
||||
return "Missing Channel"
|
||||
|
||||
if channel_number not in list(self.fhdhr.device.channels.list.keys()):
|
||||
response = Response("Not Found", status=404)
|
||||
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
||||
abort(response)
|
||||
|
||||
duration = request.args.get('duration', default=0, type=int)
|
||||
|
||||
transcode = request.args.get('transcode', default=None, type=str)
|
||||
valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"]
|
||||
if transcode not in valid_transcode_types:
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile"
|
||||
abort(response)
|
||||
|
||||
stream_args = {
|
||||
"channel": channel_number,
|
||||
"method": method,
|
||||
"duration": duration,
|
||||
"transcode": transcode,
|
||||
"accessed": full_url,
|
||||
}
|
||||
|
||||
try:
|
||||
if not tuner_number:
|
||||
tunernum = self.fhdhr.device.tuners.first_available()
|
||||
else:
|
||||
tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number)
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
abort(response)
|
||||
tuner = self.fhdhr.device.tuners.tuners[int(tunernum)]
|
||||
|
||||
try:
|
||||
stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args)
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
tuner.close()
|
||||
abort(response)
|
||||
|
||||
self.fhdhr.logger.info("Tuner #" + str(tunernum) + " to be used for stream.")
|
||||
tuner.set_status(stream_args)
|
||||
|
||||
if stream_args["method"] == "direct":
|
||||
return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] in ["ffmpeg", "vlc"]:
|
||||
return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"])
|
||||
|
||||
elif method == "close":
|
||||
|
||||
if not tuner_number or int(tuner_number) not in list(self.fhdhr.device.tuners.tuners.keys()):
|
||||
return "%s Invalid tuner" % str(tuner_number)
|
||||
|
||||
tuner = self.fhdhr.device.tuners.tuners[int(tuner_number)]
|
||||
tuner.close()
|
||||
|
||||
else:
|
||||
return "%s Invalid Method" % method
|
||||
|
||||
if redirect_url:
|
||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
||||
else:
|
||||
return "%s Success" % method
|
||||
@ -31,11 +31,10 @@ class Streams_HTML():
|
||||
fakefile.write(" <th>Channel</th>\n")
|
||||
fakefile.write(" <th>Method</th>\n")
|
||||
fakefile.write(" <th>Time Active</th>\n")
|
||||
fakefile.write(" <th>Options</th>\n")
|
||||
fakefile.write(" </tr>\n")
|
||||
|
||||
tuner_status = self.fhdhr.device.tuners.status()
|
||||
for tuner in list(tuner_status.keys()):
|
||||
print(tuner_status[tuner])
|
||||
for tuner in list(tuner_status.keys()):
|
||||
fakefile.write(" <tr>\n")
|
||||
fakefile.write(" <td>%s</td>\n" % (str(tuner)))
|
||||
@ -49,6 +48,17 @@ class Streams_HTML():
|
||||
fakefile.write(" <td>%s</td>\n" % "N/A")
|
||||
fakefile.write(" <td>%s</td>\n" % "N/A")
|
||||
fakefile.write(" <td>%s</td>\n" % "N/A")
|
||||
|
||||
fakefile.write(" <td>\n")
|
||||
fakefile.write(" <div>\n")
|
||||
|
||||
if tuner_status[tuner]["status"] in ["Active", "Acquired"]:
|
||||
fakefile.write(
|
||||
" <button onclick=\"OpenLink('%s')\">%s</a></button>\n" %
|
||||
("/api/watch?method=close&tuner=" + str(tuner) + "&redirect=%2Fstreams", "Close"))
|
||||
fakefile.write(" </div>\n")
|
||||
fakefile.write(" </td>\n")
|
||||
|
||||
fakefile.write(" </tr>\n")
|
||||
|
||||
for line in page_elements["end"]:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
from .watch import Watch
|
||||
from .auto import Auto
|
||||
from .tuner import Tuner
|
||||
|
||||
|
||||
@ -8,5 +8,5 @@ class fHDHR_WATCH():
|
||||
def __init__(self, fhdhr):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
self.watch = Watch(fhdhr)
|
||||
self.auto = Auto(fhdhr)
|
||||
self.tuner = Tuner(fhdhr)
|
||||
|
||||
93
fHDHR/http/watch/auto.py
Normal file
93
fHDHR/http/watch/auto.py
Normal file
@ -0,0 +1,93 @@
|
||||
from flask import Response, request, stream_with_context, abort
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class Auto():
|
||||
endpoints = ['/auto/<channel>']
|
||||
endpoint_name = "auto"
|
||||
|
||||
def __init__(self, fhdhr):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
def __call__(self, channel, *args):
|
||||
return self.get(channel, *args)
|
||||
|
||||
def get(self, channel, *args):
|
||||
|
||||
full_url = request.url
|
||||
|
||||
if channel.startswith("v"):
|
||||
channel_number = channel.replace('v', '')
|
||||
elif channel.startswith("ch"):
|
||||
channel_freq = channel.replace('ch', '').split("-")[0]
|
||||
subchannel = 0
|
||||
if "-" in channel:
|
||||
subchannel = channel.replace('ch', '').split("-")[1]
|
||||
abort(501, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel)))
|
||||
|
||||
if channel_number not in list(self.fhdhr.device.channels.list.keys()):
|
||||
response = Response("Not Found", status=404)
|
||||
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
||||
abort(response)
|
||||
|
||||
method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str)
|
||||
duration = request.args.get('duration', default=0, type=int)
|
||||
|
||||
transcode = request.args.get('transcode', default=None, type=str)
|
||||
valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"]
|
||||
if transcode not in valid_transcode_types:
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile"
|
||||
abort(response)
|
||||
|
||||
stream_args = {
|
||||
"channel": channel_number,
|
||||
"method": method,
|
||||
"duration": duration,
|
||||
"transcode": transcode,
|
||||
"accessed": full_url,
|
||||
}
|
||||
|
||||
try:
|
||||
tunernum = self.fhdhr.device.tuners.first_available()
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
abort(response)
|
||||
tuner = self.fhdhr.device.tuners.tuners[int(tunernum)]
|
||||
|
||||
try:
|
||||
stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args)
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
tuner.close()
|
||||
abort(response)
|
||||
|
||||
self.fhdhr.logger.info("Tuner #" + str(tunernum) + " to be used for stream.")
|
||||
tuner.set_status(stream_args)
|
||||
|
||||
if stream_args["method"] == "direct":
|
||||
return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] in ["ffmpeg", "vlc"]:
|
||||
return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"])
|
||||
|
||||
"""
|
||||
try:
|
||||
if stream_args["method"] == "direct":
|
||||
return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] in ["ffmpeg", "vlc"]:
|
||||
return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"])
|
||||
except TunerError as e:
|
||||
tuner.close()
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s failed due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
abort(response)
|
||||
"""
|
||||
@ -1,17 +1,21 @@
|
||||
from flask import Response, request, stream_with_context, abort
|
||||
|
||||
from fHDHR.exceptions import TunerError
|
||||
|
||||
|
||||
class Tuner():
|
||||
endpoints = ['/tuner<tuner>/<channel>']
|
||||
endpoints = ['/tuner<tuner_number>/<channel>']
|
||||
endpoint_name = "tuner"
|
||||
|
||||
def __init__(self, fhdhr):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
def __call__(self, tuner, channel, *args):
|
||||
return self.get(tuner, channel, *args)
|
||||
def __call__(self, tuner_number, channel, *args):
|
||||
return self.get(tuner_number, channel, *args)
|
||||
|
||||
def get(self, tuner, channel, *args):
|
||||
def get(self, tuner_number, channel, *args):
|
||||
|
||||
full_url = request.url
|
||||
|
||||
if channel.startswith("v"):
|
||||
channel_number = channel.replace('v', '')
|
||||
@ -20,27 +24,70 @@ class Tuner():
|
||||
subchannel = 0
|
||||
if "-" in channel:
|
||||
subchannel = channel.replace('ch', '').split("-")[1]
|
||||
abort(503, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel)))
|
||||
abort(501, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel)))
|
||||
|
||||
if channel_number not in list(self.fhdhr.device.channels.list.keys()):
|
||||
abort(404, "Not Found")
|
||||
response = Response("Not Found", status=404)
|
||||
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
||||
abort(response)
|
||||
|
||||
method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str)
|
||||
duration = request.args.get('duration', default=0, type=int)
|
||||
|
||||
transcode = request.args.get('transcode', default=None, type=str)
|
||||
valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"]
|
||||
if transcode not in valid_transcode_types:
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile"
|
||||
abort(response)
|
||||
|
||||
base_url = request.url_root[:-1]
|
||||
stream_args = {
|
||||
"channel": channel_number,
|
||||
"method": request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str),
|
||||
"duration": request.args.get('duration', default=0, type=int),
|
||||
"transcode": request.args.get('transcode', default=None, type=int),
|
||||
"accessed": self.fhdhr.device.channels.get_fhdhr_stream_url(base_url, channel_number),
|
||||
"tuner": tuner
|
||||
"method": method,
|
||||
"duration": duration,
|
||||
"transcode": transcode,
|
||||
"accessed": full_url,
|
||||
}
|
||||
stream_args = self.fhdhr.device.watch.get_stream_info(stream_args)
|
||||
|
||||
if not stream_args["channelUri"]:
|
||||
abort(503, "Service Unavailable")
|
||||
try:
|
||||
tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number)
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
abort(response)
|
||||
tuner = self.fhdhr.device.tuners.tuners[int(tunernum)]
|
||||
|
||||
try:
|
||||
stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args)
|
||||
except TunerError as e:
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
tuner.close()
|
||||
abort(response)
|
||||
|
||||
self.fhdhr.logger.info("Tuner #" + str(tunernum) + " to be used for stream.")
|
||||
tuner.set_status(stream_args)
|
||||
|
||||
if stream_args["channelUri"]:
|
||||
if stream_args["method"] == "direct":
|
||||
return Response(self.fhdhr.device.watch.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] == "ffmpeg":
|
||||
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
||||
return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] in ["ffmpeg", "vlc"]:
|
||||
return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"])
|
||||
|
||||
"""
|
||||
try:
|
||||
if stream_args["method"] == "direct":
|
||||
return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] in ["ffmpeg", "vlc"]:
|
||||
return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"])
|
||||
except TunerError as e:
|
||||
tuner.close()
|
||||
self.fhdhr.logger.info("A %s stream request for channel %s failed due to %s"
|
||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||
response = Response("Service Unavailable", status=503)
|
||||
response.headers["X-fHDHR-Error"] = str(e)
|
||||
abort(response)
|
||||
"""
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
from flask import Response, request, stream_with_context, abort
|
||||
|
||||
|
||||
class Watch():
|
||||
endpoints = ['/auto/<channel>']
|
||||
endpoint_name = "auto"
|
||||
|
||||
def __init__(self, fhdhr):
|
||||
self.fhdhr = fhdhr
|
||||
|
||||
def __call__(self, channel, *args):
|
||||
return self.get(channel, *args)
|
||||
|
||||
def get(self, channel, *args):
|
||||
|
||||
if channel.startswith("v"):
|
||||
channel_number = channel.replace('v', '')
|
||||
elif channel.startswith("ch"):
|
||||
channel_freq = channel.replace('ch', '').split("-")[0]
|
||||
subchannel = 0
|
||||
if "-" in channel:
|
||||
subchannel = channel.replace('ch', '').split("-")[1]
|
||||
abort(503, "Not Implemented %s-%s" % (str(channel_freq), str(subchannel)))
|
||||
|
||||
if channel_number not in list(self.fhdhr.device.channels.list.keys()):
|
||||
abort(404, "Not Found")
|
||||
|
||||
base_url = request.url_root[:-1]
|
||||
stream_args = {
|
||||
"channel": channel_number,
|
||||
"method": request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str),
|
||||
"duration": request.args.get('duration', default=0, type=int),
|
||||
"transcode": request.args.get('transcode', default=None, type=int),
|
||||
"accessed": self.fhdhr.device.channels.get_fhdhr_stream_url(base_url, channel_number),
|
||||
"tuner": None
|
||||
}
|
||||
stream_args = self.fhdhr.device.watch.get_stream_info(stream_args)
|
||||
|
||||
if not stream_args["channelUri"]:
|
||||
abort(503, "Service Unavailable")
|
||||
|
||||
if stream_args["channelUri"]:
|
||||
if stream_args["method"] == "direct":
|
||||
return Response(self.fhdhr.device.watch.get_stream(stream_args), content_type=stream_args["content_type"], direct_passthrough=True)
|
||||
elif stream_args["method"] == "ffmpeg":
|
||||
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
||||
elif stream_args["method"] == "vlc":
|
||||
return Response(stream_with_context(self.fhdhr.device.watch.get_stream(stream_args)), mimetype="video/mpeg")
|
||||
Loading…
Reference in New Issue
Block a user