diff --git a/data/internal_config/streaming.json b/data/internal_config/streaming.json index 83d83c6..35cc1a4 100644 --- a/data/internal_config/streaming.json +++ b/data/internal_config/streaming.json @@ -5,7 +5,12 @@ "config_file": true, "config_web": true }, - "quality": { + "origin_quality": { + "value": "none", + "config_file": true, + "config_web": true + }, + "transcode_quality": { "value": "none", "config_file": true, "config_web": true diff --git a/fHDHR/device/tuners/__init__.py b/fHDHR/device/tuners/__init__.py index 6dafb2f..c72d97e 100644 --- a/fHDHR/device/tuners/__init__.py +++ b/fHDHR/device/tuners/__init__.py @@ -1,3 +1,4 @@ +import m3u8 from fHDHR.exceptions import TunerError @@ -96,12 +97,15 @@ class Tuners(): raise TunerError("806 - Tune Failed") if isinstance(stream_info, str): - stream_info = {"url": stream_info} + stream_info = {"url": stream_info, "headers": None} stream_args["stream_info"] = stream_info if not stream_args["stream_info"]["url"]: raise TunerError("806 - Tune Failed") + if "headers" not in list(stream_args["stream_info"].keys()): + stream_args["stream_info"]["headers"] = None + if stream_args["stream_info"]["url"].startswith("udp://"): stream_args["true_content_type"] = "video/mpeg" stream_args["content_type"] = "video/mpeg" @@ -112,7 +116,81 @@ class Tuners(): if stream_args["true_content_type"].startswith(tuple(["application/", "text/"])): stream_args["content_type"] = "video/mpeg" + if stream_args["origin_quality"] != -1: + stream_args["stream_info"]["url"] = self.m3u8_quality(stream_args) else: stream_args["content_type"] = stream_args["true_content_type"] return stream_args + + def m3u8_quality(self, stream_args): + + m3u8_url = stream_args["stream_info"]["url"] + quality_profile = stream_args["origin_quality"] + + if not quality_profile: + quality_profile = "high" + self.fhdhr.logger.info("Origin Quality not set in config. Defaulting to Highest Quality") + else: + quality_profile = quality_profile.lower() + self.fhdhr.logger.info("Origin Quality set in config to %s" % (quality_profile)) + + while True: + self.fhdhr.logger.info("Opening m3u8 for reading %s" % m3u8_url) + + if stream_args["stream_info"]["headers"]: + videoUrlM3u = m3u8.load(m3u8_url, headers=stream_args["stream_info"]["headers"]) + else: + videoUrlM3u = m3u8.load(m3u8_url) + + if len(videoUrlM3u.playlists): + self.fhdhr.logger.info("%s m3u8 varients found" % len(videoUrlM3u.playlists)) + + # Create list of dicts + playlists, playlist_index = {}, 0 + for playlist_item in videoUrlM3u.playlists: + playlist_index += 1 + playlist_dict = { + "url": playlist_item.absolute_uri, + "bandwidth": playlist_item.stream_info.bandwidth, + } + + if not playlist_item.stream_info.resolution: + playlist_dict["width"] = None + playlist_dict["height"] = None + else: + try: + playlist_dict["width"] = playlist_item.stream_info.resolution[0] + playlist_dict["height"] = playlist_item.stream_info.resolution[1] + except TypeError: + playlist_dict["width"] = None + playlist_dict["height"] = None + + playlists[playlist_index] = playlist_dict + + sorted_playlists = sorted(playlists, key=lambda i: ( + int(playlists[i]['bandwidth']), + int(playlists[i]['width'] or 0), + int(playlists[i]['height'] or 0) + )) + sorted_playlists = [playlists[x] for x in sorted_playlists] + + if not quality_profile or quality_profile == "high": + selected_index = -1 + elif quality_profile == "medium": + selected_index = int((len(sorted_playlists) - 1)/2) + elif quality_profile == "low": + selected_index = 0 + + m3u8_stats = ",".join( + ["%s %s" % (x, sorted_playlists[selected_index][x]) + for x in list(sorted_playlists[selected_index].keys()) + if x != "url" and sorted_playlists[selected_index][x]]) + self.fhdhr.logger.info("Selected m3u8 details: %s" % m3u8_stats) + m3u8_url = sorted_playlists[selected_index]["url"] + + else: + self.fhdhr.logger.info("No m3u8 varients found") + break + + return m3u8_url diff --git a/fHDHR/device/tuners/stream/direct_m3u8_stream.py b/fHDHR/device/tuners/stream/direct_m3u8_stream.py index 7a3d8ac..4e6bd32 100644 --- a/fHDHR/device/tuners/stream/direct_m3u8_stream.py +++ b/fHDHR/device/tuners/stream/direct_m3u8_stream.py @@ -21,18 +21,7 @@ class Direct_M3U8_Stream(): if not self.stream_args["duration"] == 0: self.stream_args["time_end"] = self.stream_args["duration"] + time.time() - self.fhdhr.logger.info("Detected stream URL is m3u8: %s" % self.stream_args["true_content_type"]) - - channel_stream_url = self.stream_args["stream_info"]["url"] - while True: - - self.fhdhr.logger.info("Opening m3u8 for reading %s" % channel_stream_url) - videoUrlM3u = m3u8.load(channel_stream_url) - if len(videoUrlM3u.playlists): - self.fhdhr.logger.info("%s m3u8 varients found" % len(videoUrlM3u.playlists)) - channel_stream_url = videoUrlM3u.playlists[0].absolute_uri - else: - break + self.fhdhr.logger.info("Detected stream of m3u8 URL: %s" % self.stream_args["stream_info"]["url"]) def generate(): @@ -42,7 +31,11 @@ class Direct_M3U8_Stream(): while self.tuner.tuner_lock.locked(): - playlist = m3u8.load(channel_stream_url) + if self.stream_args["stream_info"]["headers"]: + playlist = m3u8.load(self.stream_args["stream_info"]["url"], headers=self.stream_args["stream_info"]["headers"]) + else: + playlist = m3u8.load(self.stream_args["stream_info"]["url"]) + segments = playlist.segments if len(played_chunk_urls): @@ -70,13 +63,19 @@ class Direct_M3U8_Stream(): self.fhdhr.logger.info("Requested Duration Expired.") self.tuner.close() - chunk = self.fhdhr.web.session.get(chunkurl).content + if self.stream_args["stream_info"]["headers"]: + chunk = self.fhdhr.web.session.get(chunkurl, headers=self.stream_args["stream_info"]["headers"]).content + else: + chunk = self.fhdhr.web.session.get(chunkurl).content if not chunk: break # raise TunerError("807 - No Video Data") if key: if key["url"]: - keyfile = self.fhdhr.web.session.get(key["url"]).content + if self.stream_args["stream_info"]["headers"]: + keyfile = self.fhdhr.web.session.get(key["url"], headers=self.stream_args["stream_info"]["headers"]).content + else: + keyfile = self.fhdhr.web.session.get(key["url"]).content cryptor = AES.new(keyfile, AES.MODE_CBC, keyfile) self.fhdhr.logger.info("Decrypting Chunk #%s with key: %s" % (len(played_chunk_urls), key["url"])) chunk = cryptor.decrypt(chunk) diff --git a/fHDHR/device/tuners/stream/direct_stream.py b/fHDHR/device/tuners/stream/direct_stream.py index 23dfd9a..fdc78e9 100644 --- a/fHDHR/device/tuners/stream/direct_stream.py +++ b/fHDHR/device/tuners/stream/direct_stream.py @@ -20,7 +20,10 @@ class Direct_Stream(): self.fhdhr.logger.info("Direct Stream of %s URL: %s" % (self.stream_args["true_content_type"], self.stream_args["stream_info"]["url"])) - req = self.fhdhr.web.session.get(self.stream_args["stream_info"]["url"], stream=True) + if self.stream_args["stream_info"]["headers"]: + req = self.fhdhr.web.session.get(self.stream_args["stream_info"]["url"], stream=True, headers=self.stream_args["stream_info"]["headers"]) + else: + req = self.fhdhr.web.session.get(self.stream_args["stream_info"]["url"], stream=True) def generate(): diff --git a/fHDHR/device/tuners/stream/ffmpeg_stream.py b/fHDHR/device/tuners/stream/ffmpeg_stream.py index 4daf52c..834031a 100644 --- a/fHDHR/device/tuners/stream/ffmpeg_stream.py +++ b/fHDHR/device/tuners/stream/ffmpeg_stream.py @@ -50,12 +50,26 @@ class FFMPEG_Stream(): self.fhdhr.config.dict["ffmpeg"]["path"], "-i", stream_args["stream_info"]["url"], ] + ffmpeg_command.extend(self.ffmpeg_headers(stream_args)) 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_headers(self, stream_args): + ffmpeg_command = [] + if stream_args["stream_info"]["headers"]: + headers_string = "" + if len(list(stream_args["stream_info"]["headers"].keys())) > 1: + for x in list(stream_args["stream_info"]["headers"].keys()): + headers_string += "%s: %s\r\n" % (x, stream_args["stream_info"]["headers"][x]) + else: + for x in list(stream_args["stream_info"]["headers"].keys()): + headers_string += "%s: %s" % (x, stream_args["stream_info"]["headers"][x]) + ffmpeg_command.extend(["-headers", '\"%s\"' % headers_string]) + return ffmpeg_command + def ffmpeg_duration(self, stream_args): ffmpeg_command = [] if stream_args["duration"]: @@ -103,30 +117,30 @@ class FFMPEG_Stream(): 16:9 content, not exceeding 320x240 30fps for 4:3 content """ - if stream_args["transcode"]: - self.fhdhr.logger.info("Client requested a %s transcode for stream." % stream_args["transcode"]) - stream_args["transcode"] = None + if stream_args["transcode_quality"]: + self.fhdhr.logger.info("Client requested a %s transcode for stream." % stream_args["transcode_quality"]) + stream_args["transcode_quality"] = None ffmpeg_command = [] - if not stream_args["transcode"]: + if not stream_args["transcode_quality"]: ffmpeg_command.extend( [ "-c", "copy", "-f", "mpegts", ] ) - elif stream_args["transcode"] == "heavy": + elif stream_args["transcode_quality"] == "heavy": ffmpeg_command.extend([]) - elif stream_args["transcode"] == "mobile": + elif stream_args["transcode_quality"] == "mobile": ffmpeg_command.extend([]) - elif stream_args["transcode"] == "internet720": + elif stream_args["transcode_quality"] == "internet720": ffmpeg_command.extend([]) - elif stream_args["transcode"] == "internet480": + elif stream_args["transcode_quality"] == "internet480": ffmpeg_command.extend([]) - elif stream_args["transcode"] == "internet360": + elif stream_args["transcode_quality"] == "internet360": ffmpeg_command.extend([]) - elif stream_args["transcode"] == "internet240": + elif stream_args["transcode_quality"] == "internet240": ffmpeg_command.extend([]) return ffmpeg_command diff --git a/fHDHR/device/tuners/stream/vlc_stream.py b/fHDHR/device/tuners/stream/vlc_stream.py index 0c586e9..702ba08 100644 --- a/fHDHR/device/tuners/stream/vlc_stream.py +++ b/fHDHR/device/tuners/stream/vlc_stream.py @@ -51,12 +51,17 @@ class VLC_Stream(): self.fhdhr.config.dict["vlc"]["path"], "-I", "dummy", stream_args["stream_info"]["url"], ] + # vlc_command.extend(self.vlc_headers(stream_args)) 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_headers(self, stream_args): + vlc_command = [] + return vlc_command + def vlc_duration(self, stream_args): vlc_command = [] if stream_args["duration"]: @@ -95,28 +100,28 @@ class VLC_Stream(): """ vlc_command = [] - if stream_args["transcode"]: - self.fhdhr.logger.info("Client requested a %s transcode for stream." % stream_args["transcode"]) - stream_args["transcode"] = None + if stream_args["transcode_quality"]: + self.fhdhr.logger.info("Client requested a %s transcode for stream." % stream_args["transcode_quality"]) + stream_args["transcode_quality"] = 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"]: + if not stream_args["transcode_quality"]: vlc_command.extend([]) - elif stream_args["transcode"] == "heavy": + elif stream_args["transcode_quality"] == "heavy": vlc_command.extend([]) - elif stream_args["transcode"] == "mobile": + elif stream_args["transcode_quality"] == "mobile": vlc_command.extend([]) - elif stream_args["transcode"] == "internet720": + elif stream_args["transcode_quality"] == "internet720": vlc_command.extend([]) - elif stream_args["transcode"] == "internet480": + elif stream_args["transcode_quality"] == "internet480": vlc_command.extend([]) - elif stream_args["transcode"] == "internet360": + elif stream_args["transcode_quality"] == "internet360": vlc_command.extend([]) - elif stream_args["transcode"] == "internet240": + elif stream_args["transcode_quality"] == "internet240": vlc_command.extend([]) return vlc_command diff --git a/fHDHR_web/api/tuners.py b/fHDHR_web/api/tuners.py index 1dfdcb5..f5b3084 100644 --- a/fHDHR_web/api/tuners.py +++ b/fHDHR_web/api/tuners.py @@ -16,10 +16,6 @@ class Tuners(): def __init__(self, fhdhr): self.fhdhr = fhdhr - self.quality = self.fhdhr.config.dict["streaming"]["quality"] - if self.quality: - self.quality = str(self.quality).lower() - def __call__(self, *args): return self.get(*args) @@ -56,12 +52,12 @@ class Tuners(): duration = request.args.get('duration', default=0, type=int) - transcode = request.args.get('transcode', default=self.quality, type=str) + transcode_quality = request.args.get('transcode', default=None, type=str) valid_transcode_types = [ None, "high", "medium", "low" "heavy", "mobile", "internet720", "internet480", "internet360", "internet240" ] - if transcode not in valid_transcode_types: + if transcode_quality not in valid_transcode_types: response = Response("Service Unavailable", status=503) response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile" self.fhdhr.logger.error(response.headers["X-fHDHR-Error"]) @@ -71,7 +67,8 @@ class Tuners(): "channel": channel_number, "method": method, "duration": duration, - "transcode": transcode, + "origin_quality": self.fhdhr.config.dict["streaming"]["origin_quality"], + "transcode_quality": transcode_quality, "accessed": accessed_url, "client": client_address, "client_id": session["session_id"] diff --git a/fHDHR_web/hdhr/auto.py b/fHDHR_web/hdhr/auto.py index 26062aa..196d31e 100644 --- a/fHDHR_web/hdhr/auto.py +++ b/fHDHR_web/hdhr/auto.py @@ -36,9 +36,9 @@ class Auto(): if duration: redirect_url += "&duration=%s" % str(duration) - transcode = request.args.get('transcode', default=None, type=str) - if transcode: - redirect_url += "&transcode=%s" % str(transcode) + transcode_quality = request.args.get('transcode', default=None, type=str) + if transcode_quality: + redirect_url += "&transcode=%s" % str(transcode_quality) redirect_url += "&accessed=%s" % urllib.parse.quote(request.url) diff --git a/fHDHR_web/hdhr/tuner.py b/fHDHR_web/hdhr/tuner.py index a4812ff..6a6245a 100644 --- a/fHDHR_web/hdhr/tuner.py +++ b/fHDHR_web/hdhr/tuner.py @@ -38,9 +38,9 @@ class Tuner(): if duration: redirect_url += "&duration=%s" % str(duration) - transcode = request.args.get('transcode', default=None, type=str) - if transcode: - redirect_url += "&transcode=%s" % str(transcode) + transcode_quality = request.args.get('transcode', default=None, type=str) + if transcode_quality: + redirect_url += "&transcode=%s" % str(transcode_quality) redirect_url += "&accessed=%s" % urllib.parse.quote(request.url)