1
0
mirror of https://github.com/spaam/svtplay-dl.git synced 2024-11-27 21:54:17 +01:00

Merge pull request #361 from olof/select_quality_issues_redesign

Select quality issues
This commit is contained in:
Johan Andersson 2016-04-02 17:51:59 +02:00
commit 2c3dcfbaaf
4 changed files with 99 additions and 57 deletions

View File

@ -231,13 +231,13 @@ def get_one_media(stream, options):
if options.list_quality:
list_quality(videos)
return
stream = select_quality(options, videos)
log.info("Selected to download %s, bitrate: %s",
stream.name(), stream.bitrate)
if options.get_url:
print(stream.url)
return
try:
stream = select_quality(options, videos)
log.info("Selected to download %s, bitrate: %s",
stream.name(), stream.bitrate)
if options.get_url:
print(stream.url)
return
stream.download()
except UIException as e:
if options.verbose:

View File

@ -7,4 +7,32 @@ class UIException(Exception):
pass
class ServiceError(Exception):
pass
pass
class NoRequestedProtocols(UIException):
"""
This excpetion is thrown when the service provides streams,
but not using any accepted protocol (as decided by
options.stream_prio).
"""
def __init__(self, requested, found):
"""
The constructor takes two mandatory parameters, requested
and found. Both should be lists. requested is the protocols
we want and found is the protocols that can be used to
access the stream.
"""
self.requested = requested
self.found = found
super(NoRequestedProtocols, self).__init__(
"None of the provided protocols (%s) are in "
"the current list of accepted protocols (%s)" % (
self.found, self.requested
)
)
def __repr__(self):
return "NoRequestedProtocols(requested=%s, found=%s)" % (
self.requested, self.found)

View File

@ -4,7 +4,7 @@
from __future__ import absolute_import
import unittest
from svtplay_dl.utils import prio_streams
from svtplay_dl.utils import protocol_prio
class Stream(object):
def __init__(self, proto, bitrate):
@ -16,44 +16,33 @@ class Stream(object):
return '%s(%d)' % (self.proto.upper(), self.bitrate)
class PrioStreamsTest(unittest.TestCase):
def _gen_proto_case(self, ordered, unordered, default=True, expected=None):
def _gen_proto_case(self, ordered, unordered, expected=None):
streams = [Stream(x, 100) for x in unordered]
kwargs = {}
if not default:
kwargs['protocol_prio'] = ordered
if expected is None:
expected = [str(Stream(x, 100)) for x in ordered]
return self.assertEqual(
[str(x) for x in prio_streams(streams, **kwargs)],
[str(x) for x in protocol_prio(streams, ordered, **kwargs)],
expected
)
def test_default_order(self):
return self._gen_proto_case(
['hls', 'hds', 'http', 'rtmp'],
['rtmp', 'hds', 'hls', 'http']
)
def test_custom_order(self):
return self._gen_proto_case(
['http', 'rtmp', 'hds', 'hls'],
['rtmp', 'hds', 'hls', 'http'],
default=False,
)
def test_custom_order_1(self):
return self._gen_proto_case(
['http'],
['rtmp', 'hds', 'hls', 'http'],
default=False,
)
def test_proto_unavail(self):
return self._gen_proto_case(
['http', 'rtmp'],
['hds', 'hls', 'https'],
default=False,
expected=[],
)

View File

@ -19,6 +19,8 @@ except ImportError:
print("You need to install python-requests to use this script")
sys.exit(3)
from svtplay_dl import error
is_py2 = (sys.version_info[0] == 2)
is_py3 = (sys.version_info[0] == 3)
is_py2_old = (sys.version_info < (2, 7))
@ -26,6 +28,9 @@ is_py2_old = (sys.version_info < (2, 7))
# Used for UA spoofing in get_http_data()
FIREFOX_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.3'
# TODO: should be set as the default option in the argument parsing?
DEFAULT_PROTOCOL_PRIO = ["dash", "hls", "hds", "http", "rtmp"]
log = logging.getLogger('svtplay_dl')
progress_stream = sys.stderr
@ -69,12 +74,17 @@ def list_quality(videos):
for i in data:
log.info("%s\t%s" % (i[0], i[1].upper()))
def prio_streams(streams, protocol_prio=None):
if protocol_prio is None:
protocol_prio = ["dash", "hls", "hds", "http", "rtmp"]
def protocol_prio(streams, priolist):
"""
Given a list of VideoRetriever objects and a prioritized list of
accepted protocols (as strings) (highest priority first), return
a list of VideoRetriever objects that are accepted, and sorted
by bitrate, and then protocol priority.
"""
# Map score's to the reverse of the list's index values
proto_score = dict(zip(protocol_prio, range(len(protocol_prio), 0, -1)))
proto_score = dict(zip(priolist, range(len(priolist), 0, -1)))
log.debug("Protocol priority scores (higher is better): %s",
str(proto_score))
# Build a tuple (bitrate, proto_score, stream), and use it
# for sorting.
@ -84,45 +94,60 @@ def prio_streams(streams, protocol_prio=None):
x in sorted(prioritized, key=itemgetter(0,1), reverse=True)]
def select_quality(options, streams):
available = sorted(int(x.bitrate) for x in streams)
try:
optq = int(options.quality)
except ValueError:
log.error("Requested quality need to be a number")
sys.exit(4)
if optq:
try:
optf = int(options.flexibleq)
except ValueError:
log.error("Flexible-quality need to be a number")
sys.exit(4)
if not optf:
wanted = [optq]
else:
wanted = range(optq-optf, optq+optf+1)
else:
wanted = [available[-1]]
raise error.UIException("Requested quality needs to be a number")
selected = None
for q in available:
if q in wanted:
selected = q
break
if not selected and selected != 0:
data = sort_quality(streams)
quality = ", ".join("%s (%s)" % (str(x), str(y)) for x, y in data)
log.error("Can't find that quality. Try one of: %s (or try --flexible-quality)", quality)
sys.exit(4)
try:
optf = int(options.flexibleq)
except ValueError:
raise error.UIException("Flexible-quality needs to be a number")
# Extract protocol prio, in the form of "hls,hds,http,rtmp",
# we want it as a list
proto_prio = (options.stream_prio or '').split() or None
proto_prio = DEFAULT_PROTOCOL_PRIO
if options.stream_prio:
proto_prio = options.stream_prio.split(',')
return [x for
x in prio_streams(streams, protocol_prio=proto_prio)
if x.bitrate == selected][0]
# Filter away any unwanted protocols, and prioritize
# based on --stream-priority.
streams = protocol_prio(streams, proto_prio)
if len(streams) == 0:
raise error.NoRequestedProtocols(
requested=proto_prio,
found=list(set([s.name() for s in streams]))
)
# Build a dict indexed by bitrate, where each value
# is the stream with the highest priority protocol.
stream_hash = {}
for s in streams:
if not s.bitrate in stream_hash:
stream_hash[s.bitrate] = s
avail = sorted(stream_hash.keys(), reverse=True)
# wanted_lim is a two element tuple defines lower/upper bounds
# (inclusive). By default, we want only the best for you
# (literally!).
wanted_lim = (avail[0],)*2
if optq:
wanted_lim = (optq - optf, optq + optf)
# wanted is the filtered list of available streams, having
# a bandwidth within the wanted_lim range.
wanted = [a for a in avail if a >= wanted_lim[0] and a <= wanted_lim[1]]
# If none remains, the bitrate filtering was too tight.
if len(wanted) == 0:
data = sort_quality(streams)
quality = ", ".join("%s (%s)" % (str(x), str(y)) for x, y in data)
raise error.UIException("Can't find that quality. Try one of: %s (or "
"try --flexible-quality)" % quality)
return stream_hash[wanted[0]]
def ensure_unicode(s):
"""
@ -208,4 +233,4 @@ def which(program):
exe_file = os.path.join(os.getcwd(), program)
if is_exe(exe_file):
return exe_file
return None
return None