pax_global_header00006660000000000000000000000064126445405660014526gustar00rootroot0000000000000052 comment=83e63d3e7a45cea14115241922ef246403b3609a svtplay-dl-0.30.2016.01.10/000077500000000000000000000000001264454056600145745ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/.gitignore000066400000000000000000000001331264454056600165610ustar00rootroot00000000000000*~ *.pyc *.DS_Store build/ dist/ cover/ *.egg-info svtplay-dl svtplay-dl.1 svtplay-dl.1.gz svtplay-dl-0.30.2016.01.10/LICENSE000066400000000000000000000021121264454056600155750ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2011-2015 Johan Andersson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. svtplay-dl-0.30.2016.01.10/MANIFEST.in000066400000000000000000000000421264454056600163260ustar00rootroot00000000000000include README.rst include LICENSEsvtplay-dl-0.30.2016.01.10/Makefile000066400000000000000000000050011264454056600162300ustar00rootroot00000000000000all: svtplay-dl .PHONY: test cover doctest pylint svtplay-dl \ release clean_releasedir $(RELEASE_DIR) # These variables describe the latest release: VERSION = 0.30 LATEST_RELEASE_DATE = 2016.01.10 LATEST_RELEASE = $(VERSION).$(LATEST_RELEASE_DATE) # If we build a new release, this is what it will be called: NEW_RELEASE_DATE = $(shell date +%Y.%m.%d) NEW_RELEASE = $(VERSION).$(NEW_RELEASE_DATE) RELEASE_DIR = svtplay-dl-$(NEW_RELEASE) PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin MANDIR = $(PREFIX)/share/man/man1 # Compress the manual if MAN_GZIP is set to y, ifeq ($(MAN_GZIP),y) MANFILE_EXT = .gz endif MANFILE = svtplay-dl.1$(MANFILE_EXT) # As pod2man is a perl tool, we have to jump through some hoops # to remove references to perl.. :-) POD2MAN ?= pod2man --section 1 --utf8 \ --center "svtplay-dl manual" \ --release "svtplay-dl $(VERSION)" \ --date "$(LATEST_RELEASE_DATE)" PYTHON ?= /usr/bin/env python export PYTHONPATH=lib # If you don't have a python3 environment (e.g. mock for py3 and # nosetests3), you can remove the -3 flag. TEST_OPTS ?= -2 -3 install: svtplay-dl $(MANFILE) install -d $(DESTDIR)$(BINDIR) install -d $(DESTDIR)$(MANDIR) install -m 755 svtplay-dl $(DESTDIR)$(BINDIR) install -m 644 $(MANFILE) $(DESTDIR)$(MANDIR) svtplay-dl: $(PYFILES) $(MAKE) -C lib mv lib/svtplay-dl . svtplay-dl.1: svtplay-dl.pod rm -f $@ $(POD2MAN) $< $@ svtplay-dl.1.gz: svtplay-dl.1 rm -f $@ gzip -9 svtplay-dl.1 test: sh scripts/run-tests.sh $(TEST_OPTS) cover: sh scripts/run-tests.sh -C pylint: $(MAKE) -C lib pylint doctest: svtplay-dl sh scripts/diff_man_help.sh $(RELEASE_DIR): clean_releasedir mkdir $(RELEASE_DIR) cd $(RELEASE_DIR) && git clone -b master ../ . && \ make $(MANFILE) clean_releasedir: rm -rf $(RELEASE_DIR) release: $(RELEASE_DIR) release-test set -e; cd $(RELEASE_DIR) && \ sed -i -re 's/^(LATEST_RELEASE_DATE = ).*/\1$(NEW_RELEASE_DATE)/' Makefile;\ sed -i -re 's/^(__version__ = ).*/\1"$(NEW_RELEASE)"/' lib/svtplay_dl/__init__.py;\ git add Makefile lib/svtplay_dl/__init__.py; \ git commit -m "New release $(NEW_RELEASE)"; (cd $(RELEASE_DIR) && git format-patch --stdout HEAD^) | git am git tag -m "New version $(NEW_RELEASE)" \ -m "$$(git log --oneline $$(git describe --tags --abbrev=0 HEAD^)..HEAD^)" \ $(NEW_RELEASE) make clean_releasedir release-test: $(RELEASE_DIR) make -C $(RELEASE_DIR) test make -C $(RELEASE_DIR) doctest clean: $(MAKE) -C lib clean rm -f svtplay-dl rm -f $(MANFILE) svtplay-dl-0.30.2016.01.10/README.rst000066400000000000000000000042221264454056600162630ustar00rootroot00000000000000svtplay-dl ========== Installation ------------ Mac OSX ~~~~~~~ If you have OS X and `Homebrew`_ you can install with: :: brew install svtplay-dl Debian and Ubuntu ~~~~~~~~~~~~~~~~~ svtplay-dl is available in Debian in Jessie and later and Ubuntu in 14.04 and later, which means you can install it using apt: :: apt-get install svtplay-dl … as root. Other systems with python ~~~~~~~~~~~~~~~~~~~~~~~~~ :: pip install svtplay-dl From source ~~~~~~~~~~~ If packaging isn’t available for your operating system, or you want to use a non-released version, you’ll want to install from source. Use git to download the sources: :: git clone git://github.com/spaam/svtplay-dl svtplay-dl requires the following additional tools and libraries. They are usually available from your distribution’s package repositories. If you don’t have them, some features will not be working. - `RTMPDump`_ 2.4 or higher to download RTMP streams. - `PyCrypto`_ to download encrypted HLS streams - `Requests`_ To install it, run :: make # as root: make install Support ------- If you encounter any bugs or problems, don’t hesitate to open an issue `on github`_. Or why not join the ``#svtplay-dl`` IRC channel on Freenode? Supported services ------------------ This script works for: - aftonbladet.se - bambuser.com - comedycentral.se - dbtv.no - di.se - dn.se - dplay.se - dr.dk - efn.se - expressen.se - hbo.com - kanal9play.se - nickelodeon.nl - nickelodeon.no - nickelodeon.se - nrk.no - oppetarkiv.se - ruv.is - svd.se - sverigesradio.se - svtplay.se - tv10play.se - tv3play.dk - tv3play.ee - tv3play.lt - tv3play.lv - tv3play.no - tv3play.se - tv4.se - tv4play.se - tv6play.se - tv8play.se - twitch.tv - ur.se - urplay.se - vg.no - viagame.com - viasat4play.no License ------- This project is licensed under `The MIT License (MIT)`_. .. _Homebrew: http://brew.sh/ .. _RTMPDump: http://rtmpdump.mplayerhq.hu/ .. _PyCrypto: https://www.dlitz.net/software/pycrypto/ .. _Requests: http://www.python-requests.org/ .. _on github: https://github.com/spaam/svtplay-dl/issues .. _The MIT License (MIT): LICENSE svtplay-dl-0.30.2016.01.10/bin/000077500000000000000000000000001264454056600153445ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/bin/svtplay-dl000077500000000000000000000002621264454056600173710ustar00rootroot00000000000000#!/usr/bin/env python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- import svtplay_dl if __name__ == "__main__": svtplay_dl.main() svtplay-dl-0.30.2016.01.10/lib/000077500000000000000000000000001264454056600153425ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/Makefile000066400000000000000000000025731264454056600170110ustar00rootroot00000000000000PYLINT_OPTS = --report=no -d I -d C -d R -d W0511 all: svtplay-dl clean: find . -name '*.pyc' -exec rm {} \; rm -f svtplay-dl pylint: pylint $(PYLINT_OPTS) svtplay_dl export PACKAGES = svtplay_dl \ svtplay_dl.fetcher \ svtplay_dl.utils \ svtplay_dl.service \ svtplay_dl.subtitle export PYFILES = $(sort $(addsuffix /*.py,$(subst .,/,$(PACKAGES)))) PYTHON ?= /usr/bin/env python VERSION = $(shell git describe 2>/dev/null || echo $(LATEST_RELEASE)-unknown) svtplay-dl: $(PYFILES) @# Verify that there's no .build already \ ! [ -d .build ] || { \ echo "ERROR: build already in progress? (or remove $(PWD)/.build/)"; \ exit 1; \ }; \ mkdir -p .build @# Stage the files in .build for postprocessing for py in $(PYFILES); do \ install -d ".build/$${py%/*}"; \ install $$py .build/$$py; \ done # Add git version info to __version__, seen in --version sed -i -e 's/^__version__ = "\([^"]\+\)"$$/__version__ = "$(VERSION)"/' \ .build/svtplay_dl/__init__.py @# reset timestamps, to avoid non-determinism in zip file find .build/ -exec touch -m -t 198001010000 {} \; (cd .build && zip -X --quiet svtplay-dl $(PYFILES)) (cd .build && zip -X --quiet --junk-paths svtplay-dl svtplay_dl/__main__.py) echo '#!$(PYTHON)' > svtplay-dl cat .build/svtplay-dl.zip >> svtplay-dl rm -rf .build chmod a+x svtplay-dl svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/000077500000000000000000000000001264454056600175235ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/__init__.py000066400000000000000000000335061264454056600216430ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import sys import os import logging import copy from optparse import OptionParser from svtplay_dl.error import UIException from svtplay_dl.log import log from svtplay_dl.utils import select_quality, list_quality from svtplay_dl.service import service_handler, Generic from svtplay_dl.fetcher import VideoRetriever from svtplay_dl.subtitle import subtitle from svtplay_dl.output import filename from svtplay_dl.service.aftonbladet import Aftonbladet from svtplay_dl.service.bambuser import Bambuser from svtplay_dl.service.bigbrother import Bigbrother from svtplay_dl.service.dbtv import Dbtv from svtplay_dl.service.disney import Disney from svtplay_dl.service.dplay import Dplay from svtplay_dl.service.dr import Dr from svtplay_dl.service.efn import Efn from svtplay_dl.service.expressen import Expressen from svtplay_dl.service.facebook import Facebook from svtplay_dl.service.hbo import Hbo from svtplay_dl.service.twitch import Twitch from svtplay_dl.service.lemonwhale import Lemonwhale from svtplay_dl.service.mtvnn import Mtvnn from svtplay_dl.service.mtvservices import Mtvservices from svtplay_dl.service.nrk import Nrk from svtplay_dl.service.oppetarkiv import OppetArkiv from svtplay_dl.service.picsearch import Picsearch from svtplay_dl.service.qbrick import Qbrick from svtplay_dl.service.radioplay import Radioplay from svtplay_dl.service.ruv import Ruv from svtplay_dl.service.raw import Raw from svtplay_dl.service.solidtango import Solidtango from svtplay_dl.service.sr import Sr from svtplay_dl.service.svtplay import Svtplay from svtplay_dl.service.tv4play import Tv4play from svtplay_dl.service.urplay import Urplay from svtplay_dl.service.vg import Vg from svtplay_dl.service.viaplay import Viaplay from svtplay_dl.service.vimeo import Vimeo from svtplay_dl.service.youplay import Youplay __version__ = "0.30.2016.01.10" sites = [ Aftonbladet, Bambuser, Bigbrother, Dbtv, Disney, Dplay, Dr, Efn, Expressen, Facebook, Hbo, Twitch, Lemonwhale, Mtvservices, Mtvnn, Nrk, Qbrick, Picsearch, Ruv, Radioplay, Solidtango, Sr, Svtplay, OppetArkiv, Tv4play, Urplay, Viaplay, Vimeo, Vg, Youplay] class Options(object): """ Options used when invoking the script from another Python script. Simple container class used when calling get_media() from another Python script. The variables corresponds to the command line parameters parsed in main() when the script is called directly. When called from a script there are a few more things to consider: * Logging is done to 'log'. main() calls setup_log() which sets the logging to either stdout or stderr depending on the silent level. A user calling get_media() directly can either also use setup_log() or configure the log manually. * Progress information is printed to 'progress_stream' which defaults to sys.stderr but can be changed to any stream. * Many errors results in calls to system.exit() so catch 'SystemExit'- Exceptions to prevent the entire application from exiting if that happens. """ def __init__(self): self.output = None self.resume = False self.live = False self.silent = False self.force = False self.quality = 0 self.flexibleq = None self.list_quality = False self.other = None self.subtitle = False self.username = None self.password = None self.thumbnail = False self.all_episodes = False self.all_last = -1 self.force_subtitle = False self.require_subtitle = False self.preferred = None self.verbose = False self.output_auto = False self.service = None self.cookies = None self.exclude = None self.get_url = False self.ssl_verify = True self.http_headers = None self.stream_prio = None def get_media(url, options): if "http" not in url[:4]: url = "http://%s" % url stream = service_handler(sites, options, url) if not stream: generic = Generic(options, url) url, stream = generic.get(sites) if not stream: if url.find(".f4m") > 0 or url.find(".m3u8") > 0: stream = Raw(options, url) if not stream: log.error("That site is not supported. Make a ticket or send a message") sys.exit(2) if options.all_episodes: if options.output and os.path.isfile(options.output): log.error("Output must be a directory if used with --all-episodes") sys.exit(2) elif options.output and not os.path.exists(options.output): try: os.makedirs(options.output) except OSError as e: log.error("%s: %s" % (e.strerror, e.filename)) return episodes = stream.find_all_episodes(options) if episodes is None: return for idx, o in enumerate(episodes): if o == url: substream = stream else: substream = service_handler(sites, options, o) log.info("Episode %d of %d", idx + 1, len(episodes)) # get_one_media overwrites options.output... get_one_media(substream, copy.copy(options)) else: get_one_media(stream, options) def get_one_media(stream, options): # Make an automagic filename if not filename(stream): return videos = [] subs = [] error = [] streams = stream.get() try: for i in streams: if isinstance(i, VideoRetriever): if options.preferred: if options.preferred.lower() == i.name(): videos.append(i) else: videos.append(i) if isinstance(i, subtitle): subs.append(i) if isinstance(i, Exception): error.append(i) except Exception as e: if options.verbose: raise e else: log.error("svtplay-dl crashed") log.error("Run again and add --verbose as an argument, to get more information") log.error("If the error persists, you can report it at https://github.com/spaam/svtplay-dl/issues") log.error("Include the URL used, the stack trace and the output of svtplay-dl --version in the issue") sys.exit(3) if options.require_subtitle and not subs: log.info("No subtitles available") return if options.subtitle and options.output != "-": if subs: subs[0].download() if options.force_subtitle: return if len(videos) == 0: for exc in error: log.error(str(exc)) else: 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.download() except UIException as e: if options.verbose: raise e log.error(e) sys.exit(2) if options.thumbnail and hasattr(stream, "get_thumbnail"): if options.output != "-": log.info("Getting thumbnail") stream.get_thumbnail(options) else: log.warning("Can not get thumbnail when fetching to stdout") def setup_log(silent, verbose=False): fmt = logging.Formatter('%(levelname)s: %(message)s') if silent: stream = sys.stderr level = logging.WARNING elif verbose: stream = sys.stderr level = logging.DEBUG fmt = logging.Formatter('%(levelname)s [%(created)s] %(pathname)s/%(funcName)s: %(message)s') else: stream = sys.stdout level = logging.INFO hdlr = logging.StreamHandler(stream) hdlr.setFormatter(fmt) log.addHandler(hdlr) log.setLevel(level) def main(): """ Main program """ usage = "Usage: %prog [options] url" parser = OptionParser(usage=usage, version=__version__) parser.add_option("-o", "--output", metavar="OUTPUT", help="outputs to the given filename") parser.add_option("-f", "--force", action="store_true", dest="force", default=False, help="overwrite if file exists already") parser.add_option("-r", "--resume", action="store_true", dest="resume", default=False, help="resume a download (RTMP based ones)") parser.add_option("-l", "--live", action="store_true", dest="live", default=False, help="enable for live streams (RTMP based ones)") parser.add_option("-s", "--silent", action="store_true", dest="silent", default=False, help="be less verbose") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="explain what is going on") parser.add_option("-q", "--quality", default=0, metavar="quality", help="choose what format to download based on bitrate / video resolution." "it will download the best format by default") parser.add_option("-Q", "--flexible-quality", default=0, metavar="amount", dest="flexibleq", help="allow given quality (as above) to differ by an amount") parser.add_option("--list-quality", dest="list_quality", action="store_true", default=False, help="list the quality for a video") parser.add_option("-S", "--subtitle", action="store_true", dest="subtitle", default=False, help="download subtitle from the site if available") parser.add_option("--force-subtitle", dest="force_subtitle", default=False, action="store_true", help="download only subtitle if its used with -S") parser.add_option("--require-subtitle", dest="require_subtitle", default=False, action="store_true", help="download only if a subtitle is available") parser.add_option("-u", "--username", default=None, help="username") parser.add_option("-p", "--password", default=None, help="password") parser.add_option("-t", "--thumbnail", action="store_true", dest="thumbnail", default=False, help="download thumbnail from the site if available") parser.add_option("-A", "--all-episodes", action="store_true", dest="all_episodes", default=False, help="try to download all episodes") parser.add_option("--all-last", dest="all_last", default=-1, type=int, metavar="NN", help="get last NN episodes instead of all episodes") parser.add_option("-P", "--preferred", default=None, metavar="preferred", help="preferred download method (hls, hds, http or rtmp") parser.add_option("--exclude", dest="exclude", default=None, metavar="WORD1,WORD2,...", help="exclude videos with the WORD(s) in the filename. comma separated.") parser.add_option("-g", "--get-url", action="store_true", dest="get_url", default=False, help="do not download any video, but instead print the URL.") parser.add_option("--dont-verify-ssl-cert", action="store_false", dest="ssl_verify", default=True, help="Don't attempt to verify SSL certificates.") parser.add_option("--http-header", dest="http_headers", default=None, metavar="header1=value;header2=value2", help="A header to add to each HTTP request.") parser.add_option("--stream-priority", dest="stream_prio", default=None, metavar="hls,hds,http,rtmp", help="If two streams have the same quality, choose the one you prefer") (options, args) = parser.parse_args() if not args: parser.print_help() sys.exit(0) if len(args) != 1: parser.error("Incorrect number of arguments") if options.exclude: options.exclude = options.exclude.split(",") if options.force_subtitle: options.subtitle = True if options.require_subtitle: options.subtitle = True options = mergeParserOption(Options(), options) setup_log(options.silent, options.verbose) if options.flexibleq and not options.quality: log.error("flexible-quality requires a quality") sys.exit(4) url = args[0] try: get_media(url, options) except KeyboardInterrupt: print("") def mergeParserOption(options, parser): options.output = parser.output options.resume = parser.resume options.live = parser.live options.silent = parser.silent options.force = parser.force options.quality = parser.quality options.flexibleq = parser.flexibleq options.list_quality = parser.list_quality options.subtitle = parser.subtitle options.username = parser.username options.password = parser.password options.thumbnail = parser.thumbnail options.all_episodes = parser.all_episodes options.all_last = parser.all_last options.force_subtitle = parser.force_subtitle options.require_subtitle = parser.require_subtitle options.preferred = parser.preferred options.verbose = parser.verbose options.exclude = parser.exclude options.get_url = parser.get_url options.ssl_verify = parser.ssl_verify options.http_headers = parser.http_headers options.stream_prio = parser.stream_prio return options svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/__main__.py000066400000000000000000000005721264454056600216210ustar00rootroot00000000000000#!/usr/bin/env python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- import sys if __package__ is None and not hasattr(sys, "frozen"): # direct call of __main__.py import os.path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import svtplay_dl if __name__ == '__main__': svtplay_dl.main()svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/error.py000066400000000000000000000003211264454056600212220ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import class UIException(Exception): pass class ServiceError(Exception): passsvtplay-dl-0.30.2016.01.10/lib/svtplay_dl/fetcher/000077500000000000000000000000001264454056600211435ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/fetcher/__init__.py000066400000000000000000000007211264454056600232540ustar00rootroot00000000000000from __future__ import absolute_import from svtplay_dl.utils import HTTP class VideoRetriever(object): def __init__(self, options, url, bitrate=0, **kwargs): self.options = options self.url = url self.bitrate = int(bitrate) self.kwargs = kwargs self.http = HTTP(options) def __repr__(self): return "" % (self.__class__.__name__, self.bitrate) def name(self): pass svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/fetcher/hds.py000066400000000000000000000223471264454056600223030ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import base64 import struct import logging import binascii import copy import xml.etree.ElementTree as ET from svtplay_dl.output import progressbar, progress_stream, ETA, output from svtplay_dl.utils import is_py2_old, is_py2, is_py3 from svtplay_dl.utils.urllib import urlparse from svtplay_dl.error import UIException from svtplay_dl.fetcher import VideoRetriever from svtplay_dl.error import ServiceError log = logging.getLogger('svtplay_dl') if is_py2: def bytes(string=None, encoding="ascii"): if string is None: return "" return string def _chr(temp): return temp if is_py3: def _chr(temp): return chr(temp) class HDSException(UIException): def __init__(self, url, message): self.url = url super(HDSException, self).__init__(message) class LiveHDSException(HDSException): def __init__(self, url): super(LiveHDSException, self).__init__( url, "This is a live HDS stream, and they are not supported.") def hdsparse(options, res, manifest): streams = {} bootstrap = {} if res.status_code == 403: streams[0] = ServiceError("Can't read HDS playlist. permission denied") return streams xml = ET.XML(res.text) if is_py2_old: bootstrapIter = xml.getiterator("{http://ns.adobe.com/f4m/1.0}bootstrapInfo") mediaIter = xml.getiterator("{http://ns.adobe.com/f4m/1.0}media") else: bootstrapIter = xml.iter("{http://ns.adobe.com/f4m/1.0}bootstrapInfo") mediaIter = xml.iter("{http://ns.adobe.com/f4m/1.0}media") if xml.find("{http://ns.adobe.com/f4m/1.0}drmAdditionalHeader") is not None: streams[0] = ServiceError("HDS DRM protected content.") return streams for i in bootstrapIter: if "id" in i.attrib: bootstrap[i.attrib["id"]] = i.text else: bootstrap["0"] = i.text parse = urlparse(manifest) querystring = parse.query manifest = "%s://%s%s" % (parse.scheme, parse.netloc, parse.path) for i in mediaIter: if len(bootstrap) == 1: bootstrapid = bootstrap["0"] else: bootstrapid = bootstrap[i.attrib["bootstrapInfoId"]] streams[int(i.attrib["bitrate"])] = HDS(copy.copy(options), i.attrib["url"], i.attrib["bitrate"], manifest=manifest, bootstrap=bootstrapid, metadata=i.find("{http://ns.adobe.com/f4m/1.0}metadata").text, querystring=querystring, cookies=res.cookies) return streams class HDS(VideoRetriever): def name(self): return "hds" def download(self): if self.options.live and not self.options.force: raise LiveHDSException(self.url) querystring = self.kwargs["querystring"] cookies = self.kwargs["cookies"] bootstrap = base64.b64decode(self.kwargs["bootstrap"]) box = readboxtype(bootstrap, 0) antal = None if box[2] == b"abst": antal = readbox(bootstrap, box[0]) baseurl = self.kwargs["manifest"][0:self.kwargs["manifest"].rfind("/")] file_d = output(self.options, "flv") if hasattr(file_d, "read") is False: return metasize = struct.pack(">L", len(base64.b64decode(self.kwargs["metadata"])))[1:] file_d.write(binascii.a2b_hex(b"464c560105000000090000000012")) file_d.write(metasize) file_d.write(binascii.a2b_hex(b"00000000000000")) file_d.write(base64.b64decode(self.kwargs["metadata"])) file_d.write(binascii.a2b_hex(b"00000000")) i = 1 start = antal[1]["first"] total = antal[1]["total"] eta = ETA(total) while i <= total: url = "%s/%sSeg1-Frag%s?%s" % (baseurl, self.url, start, querystring) if self.options.output != "-" and not self.options.silent: eta.update(i) progressbar(total, i, ''.join(["ETA: ", str(eta)])) data = self.http.request("get", url, cookies=cookies) if data.status_code == 404: break data = data.content number = decode_f4f(i, data) file_d.write(data[number:]) i += 1 start += 1 if self.options.output != "-": file_d.close() progress_stream.write('\n') def readbyte(data, pos): return struct.unpack("B", bytes(_chr(data[pos]), "ascii"))[0] def read16(data, pos): endpos = pos + 2 return struct.unpack(">H", data[pos:endpos])[0] def read24(data, pos): end = pos + 3 return struct.unpack(">L", "\x00" + data[pos:end])[0] def read32(data, pos): end = pos + 4 return struct.unpack(">i", data[pos:end])[0] def readu32(data, pos): end = pos + 4 return struct.unpack(">I", data[pos:end])[0] def read64(data, pos): end = pos + 8 return struct.unpack(">Q", data[pos:end])[0] def readstring(data, pos): length = 0 while bytes(_chr(data[pos + length]), "ascii") != b"\x00": length += 1 endpos = pos + length string = data[pos:endpos] pos += length + 1 return pos, string def readboxtype(data, pos): boxsize = read32(data, pos) tpos = pos + 4 endpos = tpos + 4 boxtype = data[tpos:endpos] if boxsize > 1: boxsize -= 8 pos += 8 return pos, boxsize, boxtype # Note! A lot of variable assignments are commented out. These are # accessible values that we currently don't use. def readbox(data, pos): # version = readbyte(data, pos) pos += 1 # flags = read24(data, pos) pos += 3 # bootstrapversion = read32(data, pos) pos += 4 # byte = readbyte(data, pos) pos += 1 # profile = (byte & 0xC0) >> 6 # live = (byte & 0x20) >> 5 # update = (byte & 0x10) >> 4 # timescale = read32(data, pos) pos += 4 # currentmediatime = read64(data, pos) pos += 8 # smptetimecodeoffset = read64(data, pos) pos += 8 temp = readstring(data, pos) # movieidentifier = temp[1] pos = temp[0] serverentrycount = readbyte(data, pos) pos += 1 serverentrytable = [] i = 0 while i < serverentrycount: temp = readstring(data, pos) serverentrytable.append(temp[1]) pos = temp[0] i += 1 qualityentrycount = readbyte(data, pos) pos += 1 qualityentrytable = [] i = 0 while i < qualityentrycount: temp = readstring(data, pos) qualityentrytable.append(temp[1]) pos = temp[0] i += 1 tmp = readstring(data, pos) # drm = tmp[1] pos = tmp[0] tmp = readstring(data, pos) # metadata = tmp[1] pos = tmp[0] segmentruntable = readbyte(data, pos) pos += 1 if segmentruntable > 0: tmp = readboxtype(data, pos) boxtype = tmp[2] boxsize = tmp[1] pos = tmp[0] if boxtype == b"asrt": antal = readasrtbox(data, pos) pos += boxsize fragRunTableCount = readbyte(data, pos) pos += 1 i = 0 first = 1 while i < fragRunTableCount: tmp = readboxtype(data, pos) boxtype = tmp[2] boxsize = tmp[1] pos = tmp[0] if boxtype == b"afrt": first = readafrtbox(data, pos) pos += boxsize i += 1 antal[1]["first"] = first return antal # Note! A lot of variable assignments are commented out. These are # accessible values that we currently don't use. def readafrtbox(data, pos): # version = readbyte(data, pos) pos += 1 # flags = read24(data, pos) pos += 3 # timescale = read32(data, pos) pos += 4 qualityentry = readbyte(data, pos) pos += 1 i = 0 while i < qualityentry: temp = readstring(data, pos) # qualitysegmulti = temp[1] pos = temp[0] i += 1 fragrunentrycount = read32(data, pos) pos += 4 i = 0 first = 1 skip = False while i < fragrunentrycount: firstfragment = readu32(data, pos) if not skip: first = firstfragment skip = True pos += 4 # timestamp = read64(data, pos) pos += 8 # duration = read32(data, pos) pos += 4 i += 1 return first # Note! A lot of variable assignments are commented out. These are # accessible values that we currently don't use. def readasrtbox(data, pos): # version = readbyte(data, pos) pos += 1 # flags = read24(data, pos) pos += 3 qualityentrycount = readbyte(data, pos) pos += 1 qualitysegmentmodifers = [] i = 0 while i < qualityentrycount: temp = readstring(data, pos) qualitysegmentmodifers.append(temp[1]) pos = temp[0] i += 1 seqCount = read32(data, pos) pos += 4 ret = {} i = 0 while i < seqCount: firstseg = read32(data, pos) pos += 4 fragPerSeg = read32(data, pos) pos += 4 tmp = i + 1 ret[tmp] = {"first": firstseg, "total": fragPerSeg} i += 1 return ret def decode_f4f(fragID, fragData): start = fragData.find(b"mdat") + 4 if fragID > 1: tagLen, = struct.unpack_from(">L", fragData, start) tagLen &= 0x00ffffff start += tagLen + 11 + 4 return start svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/fetcher/hls.py000066400000000000000000000110271264454056600223040ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import sys import os import re import copy from svtplay_dl.output import progressbar, progress_stream, ETA, output from svtplay_dl.log import log from svtplay_dl.utils.urllib import urlparse from svtplay_dl.error import UIException, ServiceError from svtplay_dl.fetcher import VideoRetriever class HLSException(UIException): def __init__(self, url, message): self.url = url super(HLSException, self).__init__(message) class LiveHLSException(HLSException): def __init__(self, url): super(LiveHLSException, self).__init__( url, "This is a live HLS stream, and they are not supported.") def _get_full_url(url, srcurl): if url[:4] == 'http': return url urlp = urlparse(srcurl) # remove everything after last / in the path of the URL baseurl = re.sub(r'^([^\?]+)/[^/]*(\?.*)?$', r'\1', srcurl) returl = "%s/%s" % (baseurl, url) # Append optional query parameters if urlp.query: returl += "?%s" % urlp.query return returl def hlsparse(options, res, url): streams = {} if res.status_code == 403: streams[0] = ServiceError("Can't read HLS playlist. permission denied") return streams files = (parsem3u(res.text))[1] for i in files: bitrate = float(i[1]["BANDWIDTH"])/1000 streams[int(bitrate)] = HLS(copy.copy(options), _get_full_url(i[0], url), bitrate, cookies=res.cookies) return streams class HLS(VideoRetriever): def name(self): return "hls" def download(self): if self.options.live and not self.options.force: raise LiveHLSException(self.url) cookies = self.kwargs["cookies"] m3u8 = self.http.request("get", self.url, cookies=cookies).text globaldata, files = parsem3u(m3u8) encrypted = False key = None if "KEY" in globaldata: keydata = globaldata["KEY"] encrypted = True if encrypted: try: from Crypto.Cipher import AES except ImportError: log.error("You need to install pycrypto to download encrypted HLS streams") sys.exit(2) match = re.search(r'URI="(https?://.*?)"', keydata) key = self.http.request("get", match.group(1)).content rand = os.urandom(16) decryptor = AES.new(key, AES.MODE_CBC, rand) file_d = output(self.options, "ts") if hasattr(file_d, "read") is False: return n = 1 eta = ETA(len(files)) for i in files: item = _get_full_url(i[0], self.url) if self.options.output != "-" and not self.options.silent: eta.increment() progressbar(len(files), n, ''.join(['ETA: ', str(eta)])) n += 1 data = self.http.request("get", item, cookies=cookies) if data.status_code == 404: break data = data.content if encrypted: data = decryptor.decrypt(data) file_d.write(data) if self.options.output != "-": file_d.close() progress_stream.write('\n') def parsem3u(data): if not data.startswith("#EXTM3U"): raise ValueError("Does not apprear to be a ext m3u file") files = [] streaminfo = {} globdata = {} data = data.replace("\r", "\n") for l in data.split("\n")[1:]: if not l: continue if l.startswith("#EXT-X-STREAM-INF:"): # not a proper parser info = [x.strip().split("=", 1) for x in l[18:].split(",")] for i in range(0, len(info)): if info[i][0] == "BANDWIDTH": streaminfo.update({info[i][0]: info[i][1]}) if info[i][0] == "RESOLUTION": streaminfo.update({info[i][0]: info[i][1]}) elif l.startswith("#EXT-X-ENDLIST"): break elif l.startswith("#EXT-X-"): line = [l[7:].strip().split(":", 1)] if len(line[0]) == 1: line[0].append("None") globdata.update(dict(line)) elif l.startswith("#EXTINF:"): dur, title = l[8:].strip().split(",", 1) streaminfo['duration'] = dur streaminfo['title'] = title elif l[0] == '#': pass else: files.append((l, streaminfo)) streaminfo = {} return globdata, files svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/fetcher/http.py000066400000000000000000000022621264454056600224760ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import time from svtplay_dl.output import progress, output # FIXME use progressbar() instead from svtplay_dl.fetcher import VideoRetriever class HTTP(VideoRetriever): def name(self): return "http" def download(self): """ Get the stream from HTTP """ data = self.http.request("get", self.url, stream=True) try: total_size = data.headers['content-length'] except KeyError: total_size = 0 total_size = int(total_size) bytes_so_far = 0 file_d = output(self.options, "mp4") if hasattr(file_d, "read") is False: return lastprogress = 0 for i in data.iter_content(8192): bytes_so_far += len(i) file_d.write(i) if self.options.output != "-" and not self.options.silent: now = time.time() if lastprogress + 1 < now: lastprogress = now progress(bytes_so_far, total_size) if self.options.output != "-": file_d.close() svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/fetcher/rtmp.py000066400000000000000000000024761264454056600225100ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import subprocess import shlex from svtplay_dl.log import log from svtplay_dl.utils import is_py2 from svtplay_dl.fetcher import VideoRetriever from svtplay_dl.output import output class RTMP(VideoRetriever): def name(self): return "rtmp" def download(self): """ Get the stream from RTMP """ args = [] if self.options.live: args.append("-v") if self.options.resume: args.append("-e") file_d = output(self.options, "flv", False) if file_d is None: return args += ["-o", self.options.output] if self.options.silent or self.options.output == "-": args.append("-q") if self.options.other: if is_py2: args += shlex.split(self.options.other.encode("utf-8")) else: args += shlex.split(self.options.other) if self.options.verbose: args.append("-V") command = ["rtmpdump", "-r", self.url] + args log.debug("Running: %s", " ".join(command)) try: subprocess.call(command) except OSError as e: log.error("Could not execute rtmpdump: " + e.strerror) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/log.py000066400000000000000000000003361264454056600206600ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import sys import logging log = logging.getLogger('svtplay_dl') progress_stream = sys.stderr svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/output.py000066400000000000000000000144121264454056600214370ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import sys import time import re import os import io import platform from datetime import timedelta from svtplay_dl.utils import is_py3, is_py2, filenamify, decode_html_entities, ensure_unicode from svtplay_dl.utils.terminal import get_terminal_size from svtplay_dl.log import log progress_stream = sys.stderr class ETA(object): """ An ETA class, used to calculate how long it takes to process an arbitrary set of items. By initiating the object with the number of items and continuously updating with current progress, the class can calculate an estimation of how long time remains. """ def __init__(self, end, start=0): """ Parameters: end: the end (or size, of start is 0) start: the starting position, defaults to 0 """ self.start = start self.end = end self.pos = start self.now = time.time() self.start_time = self.now def update(self, pos): """ Set new absolute progress position. Parameters: pos: new absolute progress """ self.pos = pos self.now = time.time() def increment(self, skip=1): """ Like update, but set new pos relative to old pos. Parameters: skip: progress since last update (defaults to 1) """ self.update(self.pos + skip) @property def left(self): """ returns: How many item remains? """ return self.end - self.pos def __str__(self): """ returns: a time string of the format HH:MM:SS. """ duration = self.now - self.start_time # Calculate how long it takes to process one item try: elm_time = duration / (self.end - self.left) except ZeroDivisionError: return "(unknown)" return str(timedelta(seconds=int(elm_time * self.left))) def progress(byte, total, extra=""): """ Print some info about how much we have downloaded """ if total == 0: progresstr = "Downloaded %dkB bytes" % (byte >> 10) progress_stream.write(progresstr + '\r') return progressbar(total, byte, extra) def progressbar(total, pos, msg=""): """ Given a total and a progress position, output a progress bar to stderr. It is important to not output anything else while using this, as it relies soley on the behavior of carriage return (\\r). Can also take an optioal message to add after the progressbar. It must not contain newlines. The progress bar will look something like this: [099/500][=========...............................] ETA: 13:36:59 Of course, the ETA part should be supplied be the calling function. """ width = get_terminal_size()[0] - 35 rel_pos = int(float(pos)/total*width) bar = ''.join(["=" * rel_pos, "." * (width - rel_pos)]) # Determine how many digits in total (base 10) digits_total = len(str(total)) fmt_width = "%0" + str(digits_total) + "d" fmt = "\r[" + fmt_width + "/" + fmt_width + "][%s] %s" progress_stream.write(fmt % (pos, total, bar, msg)) def filename(stream): if stream.options.output: if is_py2: if platform.system() == "Windows": stream.options.output = stream.options.output.decode("latin1") else: stream.options.output = stream.options.output.decode("utf-8") if not stream.options.output or os.path.isdir(stream.options.output): data = ensure_unicode(stream.get_urldata()) if data is None: return False match = re.search(r"(?i)]*>\s*(.*?)\s*", data, re.S) if match: stream.options.output_auto = True title_tag = decode_html_entities(match.group(1)) if not stream.options.output: stream.options.output = filenamify(title_tag) else: # output is a directory stream.options.output = os.path.join(stream.options.output, filenamify(title_tag)) return True def output(options, extention="mp4", openfd=True, mode="wb", **kwargs): if is_py3: file_d = io.IOBase else: file_d = file if options.output != "-": ext = re.search(r"(\.\w{2,3})$", options.output) if not ext: options.output = "%s.%s" % (options.output, extention) if options.output_auto and ext: options.output = "%s.%s" % (options.output, extention) if extention == "srt" and ext: options.output = "%s.srt" % options.output[:options.output.rfind(ext.group(1))] log.info("Outfile: %s", options.output) if os.path.isfile(options.output) or \ findexpisode(os.path.dirname(os.path.realpath(options.output)), options.service, os.path.basename(options.output)): if extention == "srt": if not options.force_subtitle: log.error("File already exists. Use --force-subtitle to overwrite") return None else: if not options.force: log.error("File already exists. Use --force to overwrite") return None if openfd: file_d = open(options.output, mode, **kwargs) else: if openfd: if is_py3: file_d = sys.stdout.buffer else: file_d = sys.stdout return file_d def findexpisode(directory, service, name): match = re.search(r"-(\w+)-\w+.(\w{2,3})$", name) if not match: return False videoid = match.group(1) extention = match.group(2) files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))] for i in files: match = re.search(r"-(\w+)-\w+.(\w{2,3})$", i) if match: if service: if extention == "srt": if name.find(service) and match.group(1) == videoid and match.group(2) == extention: return True elif match.group(2) != "srt": if name.find(service) and match.group(1) == videoid: return True return False svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/000077500000000000000000000000001264454056600211635ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/__init__.py000066400000000000000000000124571264454056600233050ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re from svtplay_dl.utils.urllib import urlparse from svtplay_dl.utils import download_thumbnail, is_py2, HTTP import logging log = logging.getLogger('svtplay_dl') class Service(object): supported_domains = [] supported_domains_re = [] def __init__(self, options, _url): self.options = options self._url = _url self._urldata = None self._error = False self.subtitle = None self.cookies = {} self.http = HTTP(options) @property def url(self): return self._url def get_urldata(self): if self._urldata is None: self._urldata = self.http.request("get", self.url).text return self._urldata @classmethod def handles(cls, url): urlp = urlparse(url) # Apply supported_domains_re regexp to the netloc. This # is meant for 'dynamic' domains, e.g. containing country # information etc. for domain_re in [re.compile(x) for x in cls.supported_domains_re]: if domain_re.match(urlp.netloc): return True if urlp.netloc in cls.supported_domains: return True # For every listed domain, try with www. subdomain as well. if urlp.netloc in ['www.'+x for x in cls.supported_domains]: return True return False def get_subtitle(self, options): pass def exclude(self, options): if options.exclude: for i in options.exclude: if is_py2: i = i.decode("utf-8") if i in options.output: return True return False # the options parameter is unused, but is part of the # interface, so we don't want to remove it. Thus, the # pylint ignore. def find_all_episodes(self, options): # pylint: disable-msg=unused-argument log.warning("--all-episodes not implemented for this service") return [self.url] def opengraph_get(html, prop): """ Extract specified OpenGraph property from html. >>> opengraph_get('>> opengraph_get('>> opengraph_get(']*property="og:' + prop + '" content="([^"]*)"', html) if match is None: match = re.search(']*content="([^"]*)" property="og:' + prop + '"', html) if match is None: return None return match.group(1) class OpenGraphThumbMixin(object): """ Mix this into the service class to grab thumbnail from OpenGraph properties. """ def get_thumbnail(self, options): url = opengraph_get(self.get_urldata(), "image") if url is None: return download_thumbnail(options, url) class Generic(Service): ''' Videos embed in sites ''' def get(self, sites): data = self.http.request("get", self.url).text match = re.search(r"src=(\"|\')(http://www.svt.se/wd[^\'\"]+)(\"|\')", data) stream = None if match: url = match.group(2) for i in sites: if i.handles(url): url = url.replace("&", "&").replace("&", "&") return url, i(url) match = re.search(r"src=\"(http://player.vimeo.com/video/[0-9]+)\" ", data) if match: for i in sites: if i.handles(match.group(1)): return match.group(1), i(url) match = re.search(r"tv4play.se/iframe/video/(\d+)?", data) if match: url = "http://www.tv4play.se/?video_id=%s" % match.group(1) for i in sites: if i.handles(url): return url, i(url) match = re.search(r"embed.bambuser.com/broadcast/(\d+)", data) if match: url = "http://bambuser.com/v/%s" % match.group(1) for i in sites: if i.handles(url): return url, i(url) match = re.search(r'src="(http://tv.aftonbladet[^"]*)"', data) if match: url = match.group(1) for i in sites: if i.handles(url): return url, i(url) match = re.search(r'a href="(http://tv.aftonbladet[^"]*)" class="abVi', data) if match: url = match.group(1) for i in sites: if i.handles(url): return url, i(url) match = re.search(r"iframe src='(http://www.svtplay[^']*)'", data) if match: url = match.group(1) for i in sites: if i.handles(url): return url, i(url) return self.url, stream def service_handler(sites, options, url): handler = None for i in sites: if i.handles(url): handler = i(options, url) break return handler svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/aftonbladet.py000066400000000000000000000047471264454056600240340ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json from svtplay_dl.service import Service from svtplay_dl.utils import decode_html_entities from svtplay_dl.error import ServiceError from svtplay_dl.fetcher.hls import hlsparse class Aftonbladet(Service): supported_domains = ['tv.aftonbladet.se'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search('data-aptomaId="([-0-9a-z]+)"', data) if not match: match = re.search('data-player-config="([^"]+)"', data) if not match: yield ServiceError("Can't find video info") return janson = json.loads(decode_html_entities(match.group(1))) videoId = janson["videoId"] else: videoId = match.group(1) match = re.search(r'data-isLive="(\w+)"', data) if not match: yield ServiceError("Can't find live info") return if match.group(1) == "true": self.options.live = True if not self.options.live: dataurl = "http://aftonbladet-play-metadata.cdn.drvideo.aptoma.no/video/%s.json" % videoId data = self.http.request("get", dataurl).text data = json.loads(data) videoId = data["videoId"] streamsurl = "http://aftonbladet-play-static-ext.cdn.drvideo.aptoma.no/actions/video/?id=%s&formats&callback=" % videoId data = self.http.request("get", streamsurl).text streams = json.loads(data) hlsstreams = streams["formats"]["hls"] if "level3" in hlsstreams.keys(): hls = hlsstreams["level3"] else: hls = hlsstreams["akamai"] if "csmil" in hls.keys(): hls = hls["csmil"][0] else: hls = hls["m3u8"][0] address = hls["address"] path = hls["path"] for i in hls["files"]: if "filename" in i.keys(): plist = "http://%s/%s/%s/master.m3u8" % (address, path, i["filename"]) else: plist = "http://%s/%s/%s" % (address, path, hls["filename"]) streams = hlsparse(self.options, self.http.request("get", plist), plist) if streams: for n in list(streams.keys()): yield streams[n] svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/bambuser.py000066400000000000000000000024611264454056600233400ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.error import ServiceError from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.http import HTTP class Bambuser(Service, OpenGraphThumbMixin): supported_domains = ["bambuser.com"] def get(self): match = re.search(r"v/(\d+)", self.url) if not match: yield ServiceError("Can't find video id in url") return if self.exclude(self.options): yield ServiceError("Excluding video") return json_url = "http://player-c.api.bambuser.com/getVideo.json?api_key=005f64509e19a868399060af746a00aa&vid=%s" % match.group(1) data = self.http.request("get", json_url).text info = json.loads(data)["result"] video = info["url"] if video[:4] == "rtmp": playpath = info["id"][len(info["id"])-36:] self.options.other = "-y %s" % playpath if info["type"] == "live": self.options.live = True yield RTMP(copy.copy(self.options), video, "0") else: yield HTTP(copy.copy(self.options), video, "0") svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/bigbrother.py000066400000000000000000000055261264454056600236740ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.error import ServiceError from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.fetcher.http import HTTP class Bigbrother(Service, OpenGraphThumbMixin): supported_domains = ["bigbrother.se"] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r'id="(bcPl[^"]+)"', data) if not match: yield ServiceError("Can't find flash id.") return flashid = match.group(1) match = re.search(r'playerID" value="([^"]+)"', self.get_urldata()) if not match: yield ServiceError("Can't find playerID") return playerid = match.group(1) match = re.search(r'playerKey" value="([^"]+)"', self.get_urldata()) if not match: yield ServiceError("Can't find playerKey") return playerkey = match.group(1) match = re.search(r'videoPlayer" value="([^"]+)"', self.get_urldata()) if not match: yield ServiceError("Can't find videoPlayer info") return videoplayer = match.group(1) dataurl = "http://c.brightcove.com/services/viewer/htmlFederated?flashID=%s&playerID=%s&playerKey=%s&isVid=true&isUI=true&dynamicStreaming=true&@videoPlayer=%s" % (flashid, playerid, playerkey, videoplayer) data = self.http.request("get", dataurl).content match = re.search(r'experienceJSON = ({.*});', data) if not match: yield ServiceError("Can't find json data") return jsondata = json.loads(match.group(1)) renditions = jsondata["data"]["programmedContent"]["videoPlayer"]["mediaDTO"]["renditions"] if jsondata["data"]["publisherType"] == "PREMIUM": yield ServiceError("Premium content") return for i in renditions: if i["defaultURL"].endswith("f4m"): streams = hdsparse(copy.copy(self.options), self.http.request("get", i["defaultURL"], params={"hdcore": "3.7.0"}), i["defaultURL"]) if streams: for n in list(streams.keys()): yield streams[n] if i["defaultURL"].endswith("m3u8"): streams = hlsparse(self.options, self.http.request("get", i["defaultURL"]), i["defaultURL"]) for n in list(streams.keys()): yield streams[n] if i["defaultURL"].endswith("mp4"): yield HTTP(copy.copy(self.options), i["defaultURL"], i["encodingRate"]/1024)svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/dbtv.py000066400000000000000000000025151264454056600224770ustar00rootroot00000000000000from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils.urllib import urlparse from svtplay_dl.fetcher.http import HTTP from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.error import ServiceError class Dbtv(Service, OpenGraphThumbMixin): supported_domains = ['dbtv.no'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return parse = urlparse(self.url) vidoid = parse.path[parse.path.rfind("/")+1:] match = re.search(r'JSONdata = ({.*});', data) if not match: yield ServiceError("Cant find json data") return janson = json.loads(match.group(1)) playlist = janson["playlist"] for i in playlist: if i["brightcoveId"] == int(vidoid): if i["HLSURL"]: streams = hlsparse(self.options, self.http.request("get", i["HLSURL"]), i["HLSURL"]) for n in list(streams.keys()): yield streams[n] for n in i["renditions"]: if n["container"] == "MP4": yield HTTP(copy.copy(self.options), n["URL"], int(n["rate"])/1000) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/disney.py000066400000000000000000000112561264454056600230350ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import json import re import copy import os from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils import filenamify from svtplay_dl.utils.urllib import urlparse from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.fetcher.http import HTTP from svtplay_dl.error import ServiceError class Disney(Service, OpenGraphThumbMixin): supported_domains = ['disney.se', 'video.disney.se', 'disneyjunior.disney.se'] def get(self): parse = urlparse(self.url) if parse.hostname == "video.disney.se" or parse.hostname == "disneyjunior.disney.se": data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r"Grill.burger=({.*}):", data) if not match: yield ServiceError("Can't find video info") return jsondata = json.loads(match.group(1)) for n in jsondata["stack"]: if len(n["data"]) > 0: for x in n["data"]: if "flavors" in x: for i in x["flavors"]: if i["format"] == "mp4": yield HTTP(copy.copy(self.options), i["url"], i["bitrate"]) else: data = self.get_urldata() match = re.search(r"uniqueId : '([^']+)'", data) if not match: yield ServiceError("Can't find video info") return uniq = match.group(1) match = re.search("entryId : '([^']+)'", self.get_urldata()) entryid = match.group(1) match = re.search("partnerId : '([^']+)'", self.get_urldata()) partnerid = match.group(1) match = re.search("uiConfId : '([^']+)'", self.get_urldata()) uiconfid = match.group(1) match = re.search("json : ({.*}}),", self.get_urldata()) jsondata = json.loads(match.group(1)) parse = urlparse(self.url) if len(parse.fragment) > 0: entry = parse.fragment[parse.fragment.rindex("/")+1:] if entry in jsondata["idlist"]: entryid = jsondata["idlist"][entry] else: yield ServiceError("Cant find video info") return if self.options.output_auto: for i in jsondata["playlists"][0]["playlist"]: if entryid in i["id"]: title = i["longId"] break directory = os.path.dirname(self.options.output) self.options.service = "disney" title = "%s-%s" % (title, self.options.service) title = filenamify(title) if len(directory): self.options.output = os.path.join(directory, title) else: self.options.output = title if self.exclude(self.options): return url = "http://cdnapi.kaltura.com/html5/html5lib/v1.9.7.6/mwEmbedFrame.php?&wid=%s&uiconf_id=%s&entry_id=%s&playerId=%s&forceMobileHTML5=true&urid=1.9.7.6&callback=mwi" % \ (partnerid, uiconfid, entryid, uniq) data = self.http.request("get", url).text match = re.search(r"mwi\(({.*})\);", data) jsondata = json.loads(match.group(1)) data = jsondata["content"] match = re.search(r"window.kalturaIframePackageData = ({.*});", data) jsondata = json.loads(match.group(1)) ks = jsondata["enviornmentConfig"]["ks"] if self.options.output_auto: name = jsondata["entryResult"]["meta"]["name"] directory = os.path.dirname(self.options.output) self.options.service = "disney" title = "%s-%s" % (name, self.options.service) title = filenamify(title) if len(directory): self.options.output = os.path.join(directory, title) else: self.options.output = title url = "http://cdnapi.kaltura.com/p/%s/sp/%s00/playManifest/entryId/%s/format/applehttp/protocol/http/a.m3u8?ks=%s&referrer=aHR0cDovL3d3dy5kaXNuZXkuc2U=&" % (partnerid[1:], partnerid[1:], entryid, ks) redirect = self.http.check_redirect(url) streams = hlsparse(self.options, self.http.request("get", redirect), redirect) for n in list(streams.keys()): yield streams[n]svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/dplay.py000066400000000000000000000143031264454056600226470ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import copy import json import time import os from svtplay_dl.service import Service from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.subtitle import subtitle from svtplay_dl.utils.urllib import quote from svtplay_dl.error import ServiceError from svtplay_dl.utils import filenamify from svtplay_dl.log import log class Dplay(Service): supported_domains = ['dplay.se', 'dplay.dk', "it.dplay.com"] def get(self): data = self.get_urldata() premium = False if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(" 0: yield subtitle(copy.copy(self.options), "raw", suburl) if self.options.force_subtitle: return data = self.http.request("get", "http://geo.dplay.se/geo.js").text dataj = json.loads(data) geo = dataj["countryCode"] timestamp = (int(time.time())+3600)*1000 cookie = {"dsc-geo": quote('{"countryCode":"%s","expiry":%s}' % (geo, timestamp))} if self.options.cookies: self.options.cookies.update(cookie) else: self.options.cookies = cookie data = self.http.request("get", "https://secure.dplay.se/secure/api/v2/user/authorization/stream/%s?stream_type=hds" % vid, cookies=self.options.cookies) dataj = json.loads(data.text) if "hds" in dataj: streams = hdsparse(copy.copy(self.options), self.http.request("get", dataj["hds"], params={"hdcore": "3.8.0"}), dataj["hds"]) if streams: for n in list(streams.keys()): yield streams[n] data = self.http.request("get", "https://secure.dplay.se/secure/api/v2/user/authorization/stream/%s?stream_type=hls" % vid, cookies=self.options.cookies) dataj = json.loads(data.text) if "hls" in dataj: streams = hlsparse(self.options, self.http.request("get", dataj["hls"]), dataj["hls"]) if streams: for n in list(streams.keys()): yield streams[n] def _autoname(self, jsondata): show = jsondata["data"][0]["video_metadata_show"] season = jsondata["data"][0]["season"] episode = jsondata["data"][0]["episode"] title = jsondata["data"][0]["title"] return filenamify("%s.s%se%s.%s" % (show, season, episode, title)) def _login(self, options): data = self.http.request("get", "https://secure.dplay.se/login/", cookies={}) options.cookies = data.cookies match = re.search('realm_code" value="([^"]+)"', data.text) postdata = {"username" : options.username, "password": options.password, "remember_me": "true", "realm_code": match.group(1)} data = self.http.request("post", "https://secure.dplay.se/secure/api/v1/user/auth/login", data=postdata, cookies=options.cookies) if data.status_code == 200: options.cookies = data.cookies return True else: return False def _playable(self, dataj, premium): if dataj["data"][0]["content_info"]["package_label"]["value"] == "Premium" and not premium: return 1 if dataj["data"][0]["video_metadata_drmid_playready"] != "none": return 2 if dataj["data"][0]["video_metadata_drmid_flashaccess"] != "none": return 2 return 0 def find_all_episodes(self, options): data = self.get_urldata() match = re.search('data-show-id="([^"]+)"', data) if not match: log.error("Cant find show id") return None premium = None if options.username and options.password: premium = self._login(options) url = "http://www.dplay.se/api/v2/ajax/shows/%s/seasons/?items=9999999&sort=episode_number_desc&page=" % match.group(1) episodes = [] page = 0 data = self.http.request("get", "%s%s" % (url, page)).text dataj = json.loads(data) for i in dataj["data"]: what = self._playable(dataj, premium) if what == 0: episodes.append(i["url"]) pages = dataj["total_pages"] for n in range(1, pages): data = self.http.request("get", "%s%s" % (url, n)).text dataj = json.loads(data) for i in dataj["data"]: what = self._playable(dataj, premium) if what == 0: episodes.append(i["url"]) if len(episodes) == 0: log.error("Cant find any playable files") return episodes svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/dr.py000066400000000000000000000074571264454056600221570ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.subtitle import subtitle from svtplay_dl.error import ServiceError class Dr(Service, OpenGraphThumbMixin): supported_domains = ['dr.dk'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r'resource:[ ]*"([^"]*)",', data) if match: resource_url = match.group(1) resource_data = self.http.request("get", resource_url).content resource = json.loads(resource_data) streams = self.find_stream(self.options, resource) for i in streams: yield i else: match = re.search(r'resource="([^"]*)"', data) if not match: yield ServiceError("Cant find resource info for this video") return if match.group(1)[:4] != "http": resource_url = "http:%s" % match.group(1) else: resource_url = match.group(1) resource_data = self.http.request("get", resource_url).text resource = json.loads(resource_data) if "Links" not in resource: yield ServiceError("Cant access this video. its geoblocked.") return if "SubtitlesList" in resource and len(resource["SubtitlesList"]) > 0: suburl = resource["SubtitlesList"][0]["Uri"] yield subtitle(copy.copy(self.options), "wrst", suburl) if "Data" in resource: streams = self.find_stream(self.options, resource) for i in streams: yield i else: for stream in resource['Links']: if stream["Target"] == "HDS": streams = hdsparse(copy.copy(self.options), self.http.request("get", stream["Uri"], params={"hdcore": "3.7.0"}), stream["Uri"]) if streams: for n in list(streams.keys()): yield streams[n] if stream["Target"] == "HLS": streams = hlsparse(self.options, self.http.request("get", stream["Uri"]), stream["Uri"]) for n in list(streams.keys()): yield streams[n] if stream["Target"] == "Streaming": self.options.other = "-v -y '%s'" % stream['Uri'].replace("rtmp://vod.dr.dk/cms/", "") rtmp = "rtmp://vod.dr.dk/cms/" yield RTMP(copy.copy(self.options), rtmp, stream['Bitrate']) def find_stream(self, options, resource): tempresource = resource['Data'][0]['Assets'] # To find the VideoResource, they have Images as well for resources in tempresource: if resources['Kind'] == 'VideoResource': links = resources['Links'] break for i in links: if i["Target"] == "Ios" or i["Target"] == "HLS": streams = hlsparse(options, self.http.request("get", i["Uri"]), i["Uri"]) for n in list(streams.keys()): yield streams[n] else: if i["Target"] == "Streaming": options.other = "-y '%s'" % i["Uri"].replace("rtmp://vod.dr.dk/cms/", "") rtmp = "rtmp://vod.dr.dk/cms/" yield RTMP(copy.copy(options), rtmp, i["Bitrate"]) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/efn.py000066400000000000000000000012671264454056600223130ustar00rootroot00000000000000from __future__ import absolute_import import re from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.error import ServiceError class Efn(Service, OpenGraphThumbMixin): supported_domains_re = ["www.efn.se"] def get(self): data = self.get_urldata() match = re.search('data-hls="([^"]+)"', self.get_urldata()) if not match: yield ServiceError("Cant find video info") return streams = hlsparse(self.options, self.http.request("get", match.group(1)), match.group(1)) if streams: for n in list(streams.keys()): yield streams[n]svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/expressen.py000066400000000000000000000040111264454056600235450ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import copy import xml.etree.ElementTree as ET from svtplay_dl.service import Service from svtplay_dl.error import ServiceError from svtplay_dl.log import log from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.utils import is_py2_old from svtplay_dl.utils.urllib import unquote_plus class Expressen(Service): supported_domains = ['expressen.se'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search("xmlUrl=([^ ]+)\" ", data) if match: xmlurl = unquote_plus(match.group(1)) else: match = re.search( r"moviesList: \[\{\"VideoId\":\"(\d+)\"", self.get_urldata()) if not match: log.error("Can't find video id") return vid = match.group(1) xmlurl = "http://www.expressen.se/Handlers/WebTvHandler.ashx?id=%s" % vid data = self.http.request("get", xmlurl).content xml = ET.XML(data) live = xml.find("live").text if live != "0": self.options.live = True ss = xml.find("vurls") if is_py2_old: sa = list(ss.getiterator("vurl")) else: sa = list(ss.iter("vurl")) for i in sa: options2 = copy.copy(self.options) match = re.search(r"rtmp://([-0-9a-z\.]+/[-a-z0-9]+/)(.*)", i.text) filename = "rtmp://%s" % match.group(1) options2.other = "-y %s" % match.group(2) yield RTMP(options2, filename, int(i.attrib["bitrate"])) ipadurl = xml.find("mobileurls").find("ipadurl").text streams = hlsparse(self.options, self.http.request("get", ipadurl), ipadurl) for n in list(streams.keys()): yield streams[n] svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/facebook.py000066400000000000000000000025521264454056600233120ustar00rootroot00000000000000from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils.urllib import unquote_plus from svtplay_dl.fetcher.http import HTTP from svtplay_dl.error import ServiceError class Facebook(Service, OpenGraphThumbMixin): supported_domains_re = ["www.facebook.com"] def get(self): data = self.get_urldata() match = re.search('params","([^"]+)"', data) if not match: yield ServiceError("Cant find params info. video need to be public.") return data2 = json.loads('["%s"]' % match.group(1)) data2 = json.loads(unquote_plus(data2[0])) if "sd_src_no_ratelimit" in data2["video_data"]["progressive"][0]: yield HTTP(copy.copy(self.options), data2["video_data"]["progressive"][0]["sd_src_no_ratelimit"], "240") else: yield HTTP(copy.copy(self.options), data2["video_data"]["progressive"][0]["sd_src"], "240") if "hd_src_no_ratelimit" in data2["video_data"]["progressive"][0]: yield HTTP(copy.copy(self.options), data2["video_data"]["progressive"][0]["hd_src_no_ratelimit"], "720") else: if data2["video_data"]["progressive"][0]["hd_src"]: yield HTTP(copy.copy(self.options), data2["video_data"]["progressive"][0]["hd_src"], "720") svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/hbo.py000066400000000000000000000034201264454056600223040ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import copy import xml.etree.ElementTree as ET from svtplay_dl.utils.urllib import urlparse from svtplay_dl.service import Service from svtplay_dl.utils import is_py2_old from svtplay_dl.log import log from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.error import ServiceError class Hbo(Service): supported_domains = ['hbo.com'] def get(self): parse = urlparse(self.url) try: other = parse.fragment except KeyError: log.error("Something wrong with that url") return if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search("^/(.*).html", other) if not match: log.error("Cant find video file") return url = "http://www.hbo.com/data/content/%s.xml" % match.group(1) data = self.http.request("get", url).content xml = ET.XML(data) videoid = xml.find("content")[1].find("videoId").text url = "http://render.cdn.hbo.com/data/content/global/videos/data/%s.xml" % videoid data = self.http.request("get", url).content xml = ET.XML(data) ss = xml.find("videos") if is_py2_old: sa = list(ss.getiterator("size")) else: sa = list(ss.iter("size")) for i in sa: videourl = i.find("tv14").find("path").text match = re.search("/([a-z0-9]+:[a-z0-9]+)/", videourl) self.options.other = "-y %s" % videourl[videourl.index(match.group(1)):] yield RTMP(copy.copy(self.options), videourl[:videourl.index(match.group(1))], i.attrib["width"]) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/lemonwhale.py000066400000000000000000000034421264454056600236730ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import copy import json from svtplay_dl.utils.urllib import unquote_plus from svtplay_dl.service import Service from svtplay_dl.error import ServiceError from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.utils import decode_html_entities class Lemonwhale(Service): supported_domains = ['svd.se'] def get(self): vid = None data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r'video url-([^"]+)', data) if not match: match = re.search(r'embed.jsp\?([^"]+)"', self.get_urldata()) if not match: yield ServiceError("Can't find video id") return vid = match.group(1) if not vid: path = unquote_plus(match.group(1)) data = self.http.request("get", "http://www.svd.se%s" % path).content match = re.search(r'embed.jsp\?([^"]+)', data) if not match: yield ServiceError("Can't find video id") return vid = match.group(1) url = "http://ljsp.lwcdn.com/web/public/item.json?type=video&%s" % decode_html_entities(vid) data = self.http.request("get", url).text jdata = json.loads(data) videos = jdata["videos"][0]["media"]["streams"] for i in videos: if i["name"] == "auto": hls = "%s%s" % (jdata["videos"][0]["media"]["base"], i["url"]) streams = hlsparse(self.options, self.http.request("get", hls), hls) if streams: for n in list(streams.keys()): yield streams[n] svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/mtvnn.py000066400000000000000000000063361264454056600227070ustar00rootroot00000000000000from __future__ import absolute_import import re import os import json import xml.etree.ElementTree as ET from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils import is_py2_old from svtplay_dl.error import ServiceError from svtplay_dl.log import log from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.hls import hlsparse # This is _very_ similar to mtvservices.. class Mtvnn(Service, OpenGraphThumbMixin): supported_domains = ['nickelodeon.se', "nickelodeon.nl", "nickelodeon.no", "www.comedycentral.se"] def get(self): data = self.get_urldata() match = re.search(r'data-mrss=[\'"](http://api.mtvnn.com/v2/mrss.xml[^\'"]+)[\'"]', data) if not match: yield ServiceError("Can't find id for the video") return data = self.http.request("get", match.group(1)).content xml = ET.XML(data) mediagen = xml.find("channel").find("item").find("{http://search.yahoo.com/mrss/}group") title = xml.find("channel").find("item").find("title").text if self.options.output_auto: directory = os.path.dirname(self.options.output) if len(directory): self.options.output = os.path.join(directory, title) else: self.options.output = title if self.exclude(self.options): yield ServiceError("Excluding video") return swfurl = mediagen.find("{http://search.yahoo.com/mrss/}player").attrib["url"] self.options.other = "-W %s" % self.http.check_redirect(swfurl) contenturl = mediagen.find("{http://search.yahoo.com/mrss/}content").attrib["url"] filename = os.path.basename(contenturl) data = self.http.request("get", "http://videos.mtvnn.com/api/v2/%s.js?video_format=hls" % filename).text dataj = json.loads(data) content = self.http.request("get", contenturl).content xml = ET.XML(content) ss = xml.find("video").find("item") if is_py2_old: sa = list(ss.getiterator("rendition")) else: sa = list(ss.iter("rendition")) for i in sa: yield RTMP(self.options, i.find("src").text, i.attrib["bitrate"]) streams = hlsparse(self.options, self.http.request("get", dataj["src"]), dataj["src"]) if streams: for n in list(streams.keys()): yield streams[n] def find_all_episodes(self, options): match = re.search(r"data-franchise='([^']+)'", self.get_urldata()) if match is None: log.error("Couldn't program id") return programid = match.group(1) match = re.findall(r"
  • 0: sort = "tid_fallande" else: sort = "tid_stigande" while True: url = "http://www.oppetarkiv.se/etikett/titel/%s/?sida=%s&sort=%s&embed=true" % (program, page, sort) data = self.http.request("get", url) if data.status_code == 404: break data = data.text regex = re.compile(r'href="(/video/[^"]+)"') for match in regex.finditer(data): if n == self.options.all_last: break episodes.append("http://www.oppetarkiv.se%s" % match.group(1)) n += 1 page += 1 return episodes svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/picsearch.py000066400000000000000000000051771264454056600235100ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.error import ServiceError class Picsearch(Service, OpenGraphThumbMixin): supported_domains = ['dn.se', 'mobil.dn.se', 'di.se'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return ajax_auth = re.search(r"picsearch_ajax_auth = '(\w+)'", data) if not ajax_auth: ajax_auth = re.search(r'screen9-ajax-auth="([^"]+)"', data) if not ajax_auth: yield ServiceError("Cant find token for video") return mediaid = re.search(r"mediaId = '([^']+)';", self.get_urldata()) if not mediaid: mediaid = re.search(r'media-id="([^"]+)"', self.get_urldata()) if not mediaid: mediaid = re.search(r'screen9-mid="([^"]+)"', self.get_urldata()) if not mediaid: yield ServiceError("Cant find media id") return jsondata = self.http.request("get", "http://csp.picsearch.com/rest?jsonp=&eventParam=1&auth=%s&method=embed&mediaid=%s" % (ajax_auth.group(1), mediaid.group(1))).text jsondata = json.loads(jsondata) if "playerconfig" not in jsondata["media"]: yield ServiceError(jsondata["error"]) return if "live" in jsondata["media"]["playerconfig"]["clip"]: self.options.live = jsondata["media"]["playerconfig"]["clip"] playlist = jsondata["media"]["playerconfig"]["playlist"][1] if "bitrates" in playlist: files = playlist["bitrates"] server = jsondata["media"]["playerconfig"]["plugins"]["bwcheck"]["netConnectionUrl"] for i in files: self.options.other = "-y '%s'" % i["url"] yield RTMP(copy.copy(self.options), server, i["height"]) if "provider" in playlist: if playlist["provider"] != "rtmp": if "live" in playlist: self.options.live = playlist["live"] if playlist["url"].endswith(".f4m"): streams = hdsparse(self.options, self.http.request("get", playlist["url"], params={"hdcore": "3.7.0"}), playlist["url"]) if streams: for n in list(streams.keys()): yield streams[n] svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/qbrick.py000066400000000000000000000043641264454056600230170ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import copy import xml.etree.ElementTree as ET from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils import is_py2_old from svtplay_dl.error import ServiceError from svtplay_dl.fetcher.rtmp import RTMP class Qbrick(Service, OpenGraphThumbMixin): supported_domains = ['di.seXX'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return if re.findall(r"di.se", self.url): match = re.search("src=\"(http://qstream.*)\">", data) if match: data = json.loads(match.group(1)) for i in list(data["station"]["streams"].keys()): yield HTTP(copy.copy(self.options), data["station"]["streams"][i], i) else: yield ServiceError("Can't find stream info") returnsvtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/raw.py000066400000000000000000000026261264454056600223340ustar00rootroot00000000000000from __future__ import absolute_import import copy import os from svtplay_dl.service import Service from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.log import log class Raw(Service): def get(self): data = self.get_urldata() if self.exclude(self.options): return extention = False filename = os.path.basename(self.url[:self.url.rfind("/")-1]) if self.options.output and os.path.isdir(self.options.output): self.options.output = os.path.join(os.path.dirname(self.options.output), filename) extention = True elif self.options.output is None: self.options.output = "%s" % filename extention = True if self.url.find(".f4m") > 0: if extention: self.options.output = "%s.flv" % self.options.output streams = hdsparse(self.options, self.http.request("get", self.url, params={"hdcore": "3.7.0"}), self.url) if streams: for n in list(streams.keys()): yield streams[n] if self.url.find(".m3u8") > 0: streams = hlsparse(self.options, self.http.request("get", self.url), self.url) if extention: self.options.output = "%s.ts" % self.options.output for n in list(streams.keys()): yield streams[n] svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/ruv.py000066400000000000000000000033611264454056600223540ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import copy import json from svtplay_dl.service import Service from svtplay_dl.fetcher.hls import HLS, hlsparse from svtplay_dl.fetcher.http import HTTP from svtplay_dl.error import ServiceError class Ruv(Service): supported_domains = ['ruv.is'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r'"([^"]+geo.php)"', data) if match: data = self.http.request("get", match.group(1)).content match = re.search(r'punktur=\(([^ ]+)\)', data) if match: janson = json.loads(match.group(1)) self.options.live = checklive(janson["result"][1]) streams = hlsparse(self.options, self.http.request("get", janson["result"][1]), janson["result"][1]) for n in list(streams.keys()): yield streams[n] else: yield ServiceError("Can't find json info") else: match = re.search(r'(http[^<]+)', data) if match: data = self.http.request("get", match.group(1)).text match = re.search('html5_source: "([^"]+)"', data) if match: streams = hlsparse(self.options, self.http.request("get", match.group(1)), match.group(1)) for n in list(streams.keys()): yield streams[n] else: yield ServiceError("Can't find video info. if there is a video on the page. its a bug.") returnsvtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/sr.py000066400000000000000000000025421264454056600221640ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # pylint has issues with urlparse: "some types could not be inferred" # pylint: disable=E1103 from __future__ import absolute_import import json import re import copy from svtplay_dl.utils.urllib import quote_plus from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.fetcher.http import HTTP from svtplay_dl.error import ServiceError class Sr(Service, OpenGraphThumbMixin): supported_domains = ['sverigesradio.se'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r'href="(/sida/[\.\/=a-z0-9&;\?]+play(?:audio|episode)=\d+)"', data) if not match: yield ServiceError("Can't find audio info") return path = quote_plus(match.group(1)) dataurl = "http://sverigesradio.se/sida/ajax/getplayerinfo?url=%s&isios=false&playertype=html5" % path data = self.http.request("get", dataurl).text playerinfo = json.loads(data)["playerInfo"] for i in playerinfo["AudioSources"]: url = i["Url"] if not url.startswith('http'): url = 'http:%s' % url yield HTTP(copy.copy(self.options), url, i["Quality"]/1000) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/svtplay.py000066400000000000000000000160111264454056600232360ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import os import xml.etree.ElementTree as ET import copy import hashlib from svtplay_dl.log import log from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils import filenamify, ensure_unicode from svtplay_dl.utils.urllib import urlparse, urljoin from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.subtitle import subtitle from svtplay_dl.error import ServiceError class Svtplay(Service, OpenGraphThumbMixin): supported_domains = ['svtplay.se', 'svt.se', 'beta.svtplay.se', 'svtflow.se'] def get(self): old = False parse = urlparse(self.url) if parse.netloc == "www.svtplay.se" or parse.netloc == "svtplay.se": if parse.path[:6] != "/video": yield ServiceError("This mode is not supported anymore. need the url with the video") return vid = self.find_video_id() if vid is None: yield ServiceError("Cant find video id for this video") return if re.match("^[0-9]+$", vid): old = True url = "http://www.svt.se/videoplayer-api/video/%s" % vid data = self.http.request("get", url) if data.status_code == 404: yield ServiceError("Can't get the json file for %s" % url) return data = data.json() if "live" in data: self.options.live = data["live"] if old: params = {"output": "json"} dataj = self.http.request("get", self.url, params=params).json() else: dataj = data if self.options.output_auto: self.options.service = "svtplay" self.options.output = self.outputfilename(dataj, self.options.output, ensure_unicode(self.get_urldata())) if self.exclude(self.options): yield ServiceError("Excluding video") return if "subtitleReferences" in data: for i in data["subtitleReferences"]: if i["format"] == "websrt": yield subtitle(copy.copy(self.options), "wrst", i["url"]) if old and dataj["video"]["subtitleReferences"]: try: suburl = dataj["video"]["subtitleReferences"][0]["url"] except KeyError: pass if suburl and len(suburl) > 0: yield subtitle(copy.copy(self.options), "wrst", suburl) if self.options.force_subtitle: return if len(data["videoReferences"]) == 0: yield ServiceError("Media doesn't have any associated videos (yet?)") return for i in data["videoReferences"]: if i["format"] == "hls" or i["format"] == "ios": streams = hlsparse(self.options, self.http.request("get", i["url"]), i["url"]) if streams: for n in list(streams.keys()): yield streams[n] if i["format"] == "hds" or i["format"] == "flash": match = re.search(r"\/se\/secure\/", i["url"]) if not match: streams = hdsparse(self.options, self.http.request("get", i["url"], params={"hdcore": "3.7.0"}), i["url"]) if streams: for n in list(streams.keys()): yield streams[n] def find_video_id(self): match = re.search('data-video-id="([^"]+)"', self.get_urldata()) if match: return match.group(1) parse = urlparse(self.url) match = re.search("/video/([0-9]+)/", parse.path) if match: return match.group(1) match = re.search("/videoEpisod-([^/]+)/", parse.path) if match: self._urldata = None self._url = "http://www.svtplay.se/video/%s/" % match.group(1) self.get_urldata() return self.find_video_id() return None def find_all_episodes(self, options): match = re.search(r']*href="([^"]+)"', self.get_urldata()) if match is None: match = re.findall(r'a class="play[^"]+"\s+href="(/video[^"]+)"', self.get_urldata()) if not match: log.error("Couldn't retrieve episode list") return episodes = [urljoin("http://www.svtplay.se", x) for x in match] else: data = self.http.request("get", match.group(1)).content xml = ET.XML(data) episodes = [x.text for x in xml.findall(".//item/link")] episodes_new = [] n = 1 for i in episodes: episodes_new.append(i) if n == options.all_last: break n += 1 return sorted(episodes_new) def outputfilename(self, data, filename, raw): directory = os.path.dirname(filename) if "statistics" in data: name = data["statistics"]["folderStructure"] if name.find(".") > 0: name = name[:name.find(".")] match = re.search("^arkiv-", name) if match: name = name.replace("arkiv-", "") name = filenamify(name.replace("-", ".")) other = filenamify(data["context"]["title"]) id = data["videoId"] else: name = data["programTitle"] if name.find(".") > 0: name = name[:name.find(".")] name = filenamify(name.replace(" - ", ".")) other = filenamify(data["episodeTitle"]) id = hashlib.sha256(data["programVersionId"]).hexdigest()[:7] if name == other: other = None season = self.seasoninfo(raw) title = name if season: title += ".%s" % season if other: title += ".%s" % other title += "-%s-svtplay" % id title = filenamify(title) if len(directory): output = os.path.join(directory, title) else: output = title return output def seasoninfo(self, data): match = re.search(r'play_video-area-aside__sub-title">([^<]+) 0: for url in self.urls['ok']: self.assertTrue(self.service.handles(url)) if len(self.urls['bad']) > 0: for url in self.urls['bad']: self.assertFalse(self.service.handles(url)) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tests/expressen.py000066400000000000000000000013551264454056600247170ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest from svtplay_dl.service.tests import HandlesURLsTestMixin from svtplay_dl.service.expressen import Expressen class handlesTest(unittest.TestCase, HandlesURLsTestMixin): service = Expressen urls = { "ok": [ "http://www.expressen.se/tv/nyheter/kungligt/se-nar-estelle-stjal-kungens-show/", ], "bad": [ "http://www.oppetarkiv.se/video/1129844/jacobs-stege-ep1", "http://www.dn.se/nyheter/sverige/det-ar-en-dodsfalla" ] } svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tests/oppetarkiv.py000066400000000000000000000012221264454056600250600ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest from svtplay_dl.service.tests import HandlesURLsTestMixin from svtplay_dl.service.oppetarkiv import OppetArkiv class handlesTest(unittest.TestCase, HandlesURLsTestMixin): service = OppetArkiv urls = { 'ok': [ "http://www.oppetarkiv.se/video/1129844/jacobs-stege-avsnitt-1-av-1" ], 'bad': [ "http://www.svtplay.se/video/1090393/del-9" ] } svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tests/picsearch.py000066400000000000000000000014161264454056600246420ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest from svtplay_dl.service.tests import HandlesURLsTestMixin from svtplay_dl.service.picsearch import Picsearch class handlesTest(unittest.TestCase, HandlesURLsTestMixin): service = Picsearch urls = { "ok": [ "http://dn.se/valet-2014/sa-var-peter-wolodarskis-samtal-med-fredrik-reinfeldt/", "http://mobil.dn.se/valet-2014/sa-var-peter-wolodarskis-samtal-med-fredrik-reinfeldt/", ], "bad": [ "http://www.oppetarkiv.se/video/1129844/jacobs-stege-ep1", ] } svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tests/service.py000066400000000000000000000014261264454056600243420ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest import mock from svtplay_dl.service import Service class MockService(Service): supported_domains = ['example.com', 'example.net'] class ServiceTest(unittest.TestCase): def test_supports(self): self.assertTrue(MockService.handles('http://example.com/video.swf?id=1')) self.assertTrue(MockService.handles('http://example.net/video.swf?id=1')) self.assertTrue(MockService.handles('http://www.example.com/video.swf?id=1')) self.assertTrue(MockService.handles('http://www.example.net/video.swf?id=1')) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tests/svtplay.py000066400000000000000000000014061264454056600244020ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest from svtplay_dl.service.tests import HandlesURLsTestMixin from svtplay_dl.service.svtplay import Svtplay class handlesTest(unittest.TestCase, HandlesURLsTestMixin): service = Svtplay urls = { 'ok': [ "http://www.svtplay.se/video/1090393/del-9", "http://www.svt.se/nyheter/sverige/det-ar-en-dodsfalla" ], 'bad': [ "http://www.oppetarkiv.se/video/1129844/jacobs-stege-ep1", "http://www.dn.se/nyheter/sverige/det-ar-en-dodsfalla" ] } svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tests/twitch.py000066400000000000000000000021721264454056600242030ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest from svtplay_dl.service.tests import HandlesURLsTestMixin from svtplay_dl.service.twitch import Twitch class handlesTest(unittest.TestCase, HandlesURLsTestMixin): service = Twitch urls = { 'ok': [ "http://twitch.tv/foo/c/123456", "http://www.twitch.tv/foo/c/123456", "http://en.www.twitch.tv/foo/c/123456", "http://en.twitch.tv/foo/c/123456", "http://pt-br.twitch.tv/foo/c/123456", "http://pt-br.www.twitch.tv/foo/c/123456" ], 'bad': [ "http://www.dn.se/nyheter/sverige/det-ar-en-dodsfalla", "http://pxt-br.www.twitch.tv/foo/c/123456", "http://pxt-bxr.www.twitch.tv/foo/c/123456", "http://p-r.www.twitch.tv/foo/c/123456", "http://pxx.www.twitch.tv/foo/c/123456", "http://en.wwww.twitch.tv/foo/c/123456" ] } svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/tv4play.py000066400000000000000000000216531264454056600231470ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import os import xml.etree.ElementTree as ET import json import copy from svtplay_dl.utils.urllib import urlparse, parse_qs, quote_plus from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils import is_py2_old, filenamify from svtplay_dl.log import log from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.subtitle import subtitle from svtplay_dl.error import ServiceError class Tv4play(Service, OpenGraphThumbMixin): supported_domains = ['tv4play.se', 'tv4.se'] def get(self): data = self.get_urldata() vid = findvid(self.url, data) if vid is None: yield ServiceError("Can't find video id for %s" % self.url) return if self.options.username and self.options.password: work = self._login(self.options.username, self.options.password) if isinstance(work, Exception): yield work return url = "http://premium.tv4play.se/api/web/asset/%s/play" % vid data = self.http.request("get", url, cookies=self.cookies) if data.status_code == 401: xml = ET.XML(data.content) code = xml.find("code").text if code == "SESSION_NOT_AUTHENTICATED": yield ServiceError("Can't access premium content") elif code == "ASSET_PLAYBACK_INVALID_GEO_LOCATION": yield ServiceError("Can't download this video because of geoblock.") else: yield ServiceError("Can't find any info for that video") return if data.status_code == 404: yield ServiceError("Can't find the video api") return xml = ET.XML(data.content) ss = xml.find("items") if is_py2_old: sa = list(ss.getiterator("item")) else: sa = list(ss.iter("item")) if xml.find("live").text: if xml.find("live").text != "false": self.options.live = True if xml.find("drmProtected").text == "true": yield ServiceError("We cant download DRM protected content from this site.") return if self.options.output_auto: directory = os.path.dirname(self.options.output) self.options.service = "tv4play" basename = self._autoname(vid) if basename is None: yield ServiceError("Cant find vid id for autonaming") return title = "%s-%s-%s" % (basename, vid, self.options.service) title = filenamify(title) if len(directory): self.options.output = os.path.join(directory, title) else: self.options.output = title if self.exclude(self.options): yield ServiceError("Excluding video") return for i in sa: if i.find("mediaFormat").text == "mp4": base = urlparse(i.find("base").text) parse = urlparse(i.find("url").text) if "rtmp" in base.scheme: swf = "http://www.tv4play.se/flash/tv4playflashlets.swf" self.options.other = "-W %s -y %s" % (swf, i.find("url").text) yield RTMP(copy.copy(self.options), i.find("base").text, i.find("bitrate").text) elif parse.path[len(parse.path)-3:len(parse.path)] == "f4m": streams = hdsparse(self.options, self.http.request("get", i.find("url").text, params={"hdcore": "3.7.0"}), i.find("url").text) if streams: for n in list(streams.keys()): yield streams[n] elif i.find("mediaFormat").text == "smi": yield subtitle(copy.copy(self.options), "smi", i.find("url").text) url = "http://premium.tv4play.se/api/web/asset/%s/play?protocol=hls" % vid data = self.http.request("get", url, cookies=self.cookies).content xml = ET.XML(data) ss = xml.find("items") if is_py2_old: sa = list(ss.getiterator("item")) else: sa = list(ss.iter("item")) for i in sa: if i.find("mediaFormat").text == "mp4": parse = urlparse(i.find("url").text) if parse.path.endswith("m3u8"): streams = hlsparse(self.options, self.http.request("get", i.find("url").text), i.find("url").text) for n in list(streams.keys()): yield streams[n] def _get_show_info(self): show = self._get_showname(self.url) data = self.http.request("get", "http://webapi.tv4play.se/play/video_assets?type=episode&is_live=false&platform=web&node_nids=%s&per_page=99999" % show).text jsondata = json.loads(data) return jsondata def _get_clip_info(self, vid): show = self._get_showname(self.url) page = 1 assets = page * 1000 run = True while run: data = self.http.request("get", "http://webapi.tv4play.se/play/video_assets?type=clips&is_live=false&platform=web&node_nids=%s&per_page=1000&page=%s" % (show, page)).text jsondata = json.loads(data) for i in jsondata["results"]: if vid == i["id"]: return i["title"] if not run: return None total = jsondata["total_hits"] if assets > total: run = False page += 1 assets = page * 1000 return None def _get_showname(self, url): parse = urlparse(self.url) if parse.path.count("/") > 2: match = re.search("^/([^/]+)/", parse.path) show = match.group(1) else: show = parse.path[parse.path.find("/", 1)+1:] if not re.search("%", show): show = quote_plus(show) return show def _autoname(self, vid): jsondata = self._get_show_info() for i in jsondata["results"]: if vid == i["id"]: return i["title"] return self._get_clip_info(vid) def find_all_episodes(self, options): premium = False if options.username and options.password: premium = self._login(options.username, options.password) if isinstance(premium, Exception): log.error(premium.message) return None jsondata = self._get_show_info() episodes = [] n = 1 for i in jsondata["results"]: if premium: text = "availability_group_premium" else: text = "availability_group_free" try: days = int(i["availability"][text]) except (ValueError, TypeError): days = 999 if days > 0: video_id = i["id"] url = "http://www.tv4play.se/program/%s?video_id=%s" % ( i["program"]["nid"], video_id) episodes.append(url) if n == options.all_last: break n += 1 return episodes def _login(self, username, password): data = self.http.request("get", "https://www.tv4play.se/session/new?https=") auth_token = re.search('name="authenticity_token" ([a-z]+="[^"]+" )?value="([^"]+)"', data.text) if not auth_token: return ServiceError("Can't find authenticity_token needed for user / password") url = "https://www.tv4play.se/session" postdata = {"user_name" : username, "password": password, "authenticity_token":auth_token.group(2), "https": ""} data = self.http.request("post", url, data=postdata, cookies=self.cookies) self.cookies = data.cookies fail = re.search("", data.text) if fail: return ServiceError(fail.group(1)) return True def findvid(url, data): parse = urlparse(url) if "tv4play.se" in url: try: vid = parse_qs(parse.query)["video_id"][0] except KeyError: return None else: match = re.search(r"\"vid\":\"(\d+)\",", data) if match: vid = match.group(1) else: match = re.search(r"-(\d+)$", url) if match: vid = match.group(1) else: match = re.search(r"meta content='([^']+)' property='og:video'", data) if match: match = re.search(r"vid=(\d+)&", match.group(1)) if match: vid = match.group(1) else: log.error("Can't find video id for %s", url) return else: return None return vidsvtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/twitch.py000066400000000000000000000127161264454056600230460ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # pylint has issues with urlparse: "some types could not be inferred" # pylint: disable=E1103 from __future__ import absolute_import import re import json import copy import os from svtplay_dl.utils.urllib import urlparse, quote_plus from svtplay_dl.service import Service from svtplay_dl.utils import filenamify from svtplay_dl.log import log from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.error import ServiceError class TwitchException(Exception): pass class TwitchUrlException(TwitchException): """ Used to indicate an invalid URL for a given media_type. E.g.: TwitchUrlException('video', 'http://twitch.tv/example') """ def __init__(self, media_type, url): super(TwitchUrlException, self).__init__( "'%s' is not recognized as a %s URL" % (url, media_type) ) class Twitch(Service): # Twitch uses language subdomains, e.g. en.www.twitch.tv. They # are usually two characters, but may have a country suffix as well (e.g. # zh-tw, zh-cn and pt-br. supported_domains_re = [ r'^(?:(?:[a-z]{2}-)?[a-z]{2}\.)?(www\.)?twitch\.tv$', ] api_base_url = 'https://api.twitch.tv' hls_base_url = 'http://usher.justin.tv/api/channel/hls' def get(self): urlp = urlparse(self.url) if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.match(r'/(\w+)/([bcv])/(\d+)', urlp.path) if not match: data = self._get_channel(self.options, urlp) else: if match.group(2) in ["b", "c"]: yield ServiceError("This twitch video type is unsupported") return data = self._get_archive(self.options, match.group(3)) try: for i in data: yield i except TwitchUrlException as e: yield ServiceError("This twitch video type is unsupported") return def _get_static_video(self, options, videoid): access = self._get_access_token(videoid) if options.output_auto: data = self.http.request("get", "https://api.twitch.tv/kraken/videos/v%s" % videoid) if data.status_code == 404: yield ServiceError("Can't find the video") return info = json.loads(data.text) name = "twitch-%s-%s" % (info["channel"]["name"], filenamify(info["title"])) directory = os.path.dirname(options.output) if os.path.isdir(directory): name = os.path.join(directory, name) options.output = name if "token" not in access: raise TwitchUrlException('video', self.url) nauth = quote_plus(str(access["token"])) authsig = access["sig"] url = "http://usher.twitch.tv/vod/%s?nauth=%s&nauthsig=%s" % ( videoid, nauth, authsig) streams = hlsparse(options, self.http.request("get", url), url) if streams: for n in list(streams.keys()): yield streams[n] def _get_archive(self, options, vid): try: for n in self._get_static_video(options, vid): yield n except TwitchUrlException as e: log.error(str(e)) def _get_access_token(self, channel, vtype="vods"): """ Get a Twitch access token. It's a three element dict: * mobile_restricted * sig * token `sig` is a hexadecimal string, and `token` is a JSON blob, with information about access expiration. `mobile_restricted` is not important, but is a boolean. Both `sig` and `token` should be added to the HLS URI, and the token should, of course, be URI encoded. """ return self._ajax_get('/api/%s/%s/access_token' % (vtype, channel)) def _ajax_get(self, method): url = "%s/%s" % (self.api_base_url, method) # Logic found in Twitch's global.js. Prepend /kraken/ to url # path unless the API method already is absolute. if method[0] != '/': method = '/kraken/%s' % method # There are references to a api_token in global.js; it's used # with the "Twitch-Api-Token" HTTP header. But it doesn't seem # to be necessary. payload = self.http.request("get", url, headers={ 'Accept': 'application/vnd.twitchtv.v2+json' }) return json.loads(payload.text) def _get_hls_url(self, channel): access = self._get_access_token(channel, "channels") query = "token=%s&sig=%s" % (quote_plus(access['token']), access['sig']) return "%s/%s.m3u8?%s" % (self.hls_base_url, channel, query) def _get_channel(self, options, urlp): match = re.match(r'/(\w+)', urlp.path) if not match: raise TwitchUrlException('channel', urlp.geturl()) channel = match.group(1) if options.output_auto: options.output = "twitch-%s" % channel hls_url = self._get_hls_url(channel) urlp = urlparse(hls_url) options.live = True if not options.output: options.output = channel data = self.http.request("get", hls_url) if data.status_code == 404: yield ServiceError("Stream is not online.") return streams = hlsparse(options, data, hls_url) for n in list(streams.keys()): yield streams[n] svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/urplay.py000066400000000000000000000065001264454056600230520ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json import copy import xml.etree.ElementTree as ET from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils.urllib import urljoin from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.log import log from svtplay_dl.error import ServiceError from svtplay_dl.subtitle import subtitle class Urplay(Service, OpenGraphThumbMixin): supported_domains = ['urplay.se', 'ur.se', 'betaplay.ur.se'] def get(self): data = self.get_urldata() match = re.search(r"urPlayer.init\((.*)\);", data) if not match: yield ServiceError("Can't find json info") return if self.exclude(self.options): yield ServiceError("Excluding video") return data = match.group(1) jsondata = json.loads(data) if len(jsondata["subtitles"]) > 0: yield subtitle(copy.copy(self.options), "tt", jsondata["subtitles"][0]["file"].split(",")[0]) if "streamer" in jsondata["streaming_config"]: basedomain = jsondata["streaming_config"]["streamer"]["redirect"] else: lbjson = self.http.request("get", jsondata["streaming_config"]["loadbalancer"]).text lbjson = json.loads(lbjson) basedomain = lbjson["redirect"] http = "http://%s/%s" % (basedomain, jsondata["file_http"]) hd = None if len(jsondata["file_http_hd"]) > 0: http_hd = "http://%s/%s" % (basedomain, jsondata["file_http_hd"]) hls_hd = "%s%s" % (http_hd, jsondata["streaming_config"]["http_streaming"]["hls_file"]) hd = True hls = "%s%s" % (http, jsondata["streaming_config"]["http_streaming"]["hls_file"]) streams = hlsparse(self.options, self.http.request("get", hls), hls) for n in list(streams.keys()): yield streams[n] if hd: streams = hlsparse(self.options, self.http.request("get", hls_hd), hls_hd) for n in list(streams.keys()): yield streams[n] def scrape_episodes(self, options): res = [] for relurl in re.findall(r']*href="([^"]+)"', self.get_urldata()) if match is None: log.info("Couldn't retrieve episode list as rss, trying to scrape") return self.scrape_episodes(options) url = "http://urplay.se%s" % match.group(1).replace("&", "&") xml = ET.XML(self.http.request("get", url).content) episodes = [x.text for x in xml.findall(".//item/link")] episodes_new = [] n = 0 for i in episodes: if n == options.all_last: break if i not in episodes_new: episodes_new.append(i) n += 1 return episodes_new svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/vg.py000066400000000000000000000042171264454056600221550ustar00rootroot00000000000000from __future__ import absolute_import import re import json import copy import os from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils.urllib import urlparse from svtplay_dl.utils import filenamify from svtplay_dl.fetcher.http import HTTP from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.fetcher.hls import HLS, hlsparse from svtplay_dl.error import ServiceError class Vg(Service, OpenGraphThumbMixin): supported_domains = ['vg.no', 'vgtv.no'] def get(self): data = self.get_urldata() match = re.search(r'data-videoid="([^"]+)"', data) if not match: parse = urlparse(self.url) match = re.search(r'video/(\d+)/', parse.fragment) if not match: yield ServiceError("Can't find video file for: %s" % self.url) return videoid = match.group(1) data = self.http.request("get", "http://svp.vg.no/svp/api/v1/vgtv/assets/%s?appName=vgtv-website" % videoid).text jsondata = json.loads(data) if self.options.output_auto: directory = os.path.dirname(self.options.output) title = "%s" % jsondata["title"] title = filenamify(title) if len(directory): self.options.output = os.path.join(directory, title) else: self.options.output = title if self.exclude(self.options): yield ServiceError("Excluding video") return if "hds" in jsondata["streamUrls"]: streams = hdsparse(self.options, self.http.request("get", jsondata["streamUrls"]["hds"], params={"hdcore": "3.7.0"}), jsondata["streamUrls"]["hds"]) if streams: for n in list(streams.keys()): yield streams[n] if "hls" in jsondata["streamUrls"]: streams = hlsparse(self.options, self.http.request("get", jsondata["streamUrls"]["hls"]), jsondata["streamUrls"]["hls"]) for n in list(streams.keys()): yield streams[n] if "mp4" in jsondata["streamUrls"]: yield HTTP(copy.copy(self.options), jsondata["streamUrls"]["mp4"]) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/viaplay.py000066400000000000000000000140551264454056600232070ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # pylint has issues with urlparse: "some types could not be inferred" # pylint: disable=E1103 from __future__ import absolute_import import re import json import copy import os from svtplay_dl.utils import filenamify from svtplay_dl.utils.urllib import urlparse from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.log import log from svtplay_dl.fetcher.rtmp import RTMP from svtplay_dl.fetcher.hds import hdsparse from svtplay_dl.fetcher.hls import hlsparse from svtplay_dl.subtitle import subtitle from svtplay_dl.error import ServiceError class Viaplay(Service, OpenGraphThumbMixin): supported_domains = [ 'tv3play.se', 'tv6play.se', 'tv8play.se', 'tv10play.se', 'tv3play.no', 'tv3play.dk', 'tv6play.no', 'viasat4play.no', 'tv3play.ee', 'tv3play.lv', 'tv3play.lt', 'tvplay.lv', 'viagame.com', 'juicyplay.se'] def _get_video_id(self): """ Extract video id. It will try to avoid making an HTTP request if it can find the ID in the URL, but otherwise it will try to scrape it from the HTML document. Returns None in case it's unable to extract the ID at all. """ html_data = self.get_urldata() match = re.search(r'data-video-id="([0-9]+)"', html_data) if match: return match.group(1) match = re.search(r'data-videoid="([0-9]+)', html_data) if match: return match.group(1) parse = urlparse(self.url) match = re.search(r'/\w+/(\d+)', parse.path) if match: return match.group(1) match = re.search('iframe src="http://play.juicyplay.se[^\"]+id=(\d+)', html_data) if match: return match.group(1) return None def get(self): vid = self._get_video_id() if vid is None: yield ServiceError("Can't find video file for: %s" % self.url) return url = "http://playapi.mtgx.tv/v3/videos/%s" % vid self.options.other = "" data = self.http.request("get", url) if data.status_code == 403: yield ServiceError("Can't play this because the video is geoblocked.") return dataj = json.loads(data.text) if "msg" in dataj: yield ServiceError(dataj["msg"]) return if dataj["type"] == "live": self.options.live = True if self.exclude(self.options): yield ServiceError("Excluding video") return if dataj["sami_path"]: yield subtitle(copy.copy(self.options), "sami", dataj["sami_path"]) if dataj["subtitles_for_hearing_impaired"]: yield subtitle(copy.copy(self.options), "sami", dataj["subtitles_for_hearing_impaired"]) streams = self.http.request("get", "http://playapi.mtgx.tv/v3/videos/stream/%s" % vid) if streams.status_code == 403: yield ServiceError("Can't play this because the video is geoblocked.") return streamj = json.loads(streams.text) if "msg" in streamj: yield ServiceError("Can't play this because the video is either not found or geoblocked.") return if self.options.output_auto: directory = os.path.dirname(self.options.output) self.options.service = "tv3play" basename = self._autoname(dataj) title = "%s-%s-%s" % (basename, vid, self.options.service) if len(directory): self.options.output = os.path.join(directory, title) else: self.options.output = title if streamj["streams"]["medium"]: filename = streamj["streams"]["medium"] if ".f4m" in filename: streams = hdsparse(self.options, self.http.request("get", filename, params={"hdcore": "3.7.0"}), filename) if streams: for n in list(streams.keys()): yield streams[n] else: parse = urlparse(filename) match = re.search("^(/[^/]+)/(.*)", parse.path) if not match: yield ServiceError("Can't get rtmpparse info") return filename = "%s://%s:%s%s" % (parse.scheme, parse.hostname, parse.port, match.group(1)) path = "-y %s" % match.group(2) self.options.other = "-W http://flvplayer.viastream.viasat.tv/flvplayer/play/swf/player.swf %s" % path yield RTMP(copy.copy(self.options), filename, 800) if streamj["streams"]["hls"]: streams = hlsparse(self.options, self.http.request("get", streamj["streams"]["hls"]), streamj["streams"]["hls"]) if streams: for n in list(streams.keys()): yield streams[n] def find_all_episodes(self, options): format_id = re.search(r'data-format-id="(\d+)"', self.get_urldata()) if not format_id: log.error("Can't find video info for all episodes") return data = self.http.request("get", "http://playapi.mtgx.tv/v1/sections?sections=videos.one,seasons.videolist&format=%s" % format_id.group(1)).text jsondata = json.loads(data) videos = jsondata["_embedded"]["sections"][1]["_embedded"]["seasons"][0]["_embedded"]["episodelist"]["_embedded"]["videos"] n = 0 episodes = [] for i in videos: if n == options.all_last: break episodes.append(i["sharing"]["url"]) n += 1 return episodes def _autoname(self, dataj): program = dataj["format_slug"] season = dataj["format_position"]["season"] episode = None if season: if len(dataj["format_position"]["episode"]) > 0: episode = dataj["format_position"]["episode"] name = filenamify(program) if season: name = "%s.s%s" % (name, season) if episode: name = "%se%s" % (name, episode) return namesvtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/vimeo.py000066400000000000000000000023001264454056600226470ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import json import re import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.fetcher.http import HTTP from svtplay_dl.error import ServiceError class Vimeo(Service, OpenGraphThumbMixin): supported_domains = ['vimeo.com'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search('data-config-url="([^"]+)" data-fallback-url', data) if not match: yield ServiceError("Can't find video file for: %s" % self.url) return player_url = match.group(1).replace("&", "&") player_data = self.http.request("get", player_url).text if player_data: jsondata = json.loads(player_data) avail_quality = jsondata["request"]["files"]["progressive"] for i in avail_quality: yield HTTP(copy.copy(self.options), i["url"], i["height"]) else: yield ServiceError("Can't find any streams.") return svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/service/youplay.py000066400000000000000000000034221264454056600232400ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import import re import json import copy from svtplay_dl.service import Service, OpenGraphThumbMixin from svtplay_dl.utils.urllib import unquote_plus from svtplay_dl.fetcher.http import HTTP from svtplay_dl.error import ServiceError class Youplay(Service, OpenGraphThumbMixin): supported_domains = ['www.affarsvarlden.se'] def get(self): data = self.get_urldata() if self.exclude(self.options): yield ServiceError("Excluding video") return match = re.search(r'script async defer src="(//content.youplay.se[^"]+)"', data) if not match: yield ServiceError("Cant find video info for %s" % self.url) return data = self.http.request("get", "http:%s" % match.group(1)).content match = re.search(r'decodeURIComponent\("([^"]+)"\)\)', data) if not match: yield ServiceError("Can't decode video info") return data = unquote_plus(match.group(1)) match = re.search(r"videoData = ({[^;]+});", data) if not match: yield ServiceError("Cant find video info for %s" % self.url) return # fix broken json. regex = re.compile(r"\s(\w+):") data = regex.sub(r"'\1':", match.group(1)) data = data.replace("'", "\"") j = re.sub(r"{\s*(\w)", r'{"\1', data) j = j.replace("\n", "") j = re.sub(r'",\s*}', '"}', j) jsondata = json.loads(j) for i in jsondata["episode"]["sources"]: match = re.search(r"mp4_(\d+)", i) if match: yield HTTP(copy.copy(self.options), jsondata["episode"]["sources"][i], match.group(1)) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/subtitle/000077500000000000000000000000001264454056600213565ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/subtitle/__init__.py000066400000000000000000000177401264454056600235000ustar00rootroot00000000000000import xml.etree.ElementTree as ET import json import re from svtplay_dl.log import log from svtplay_dl.utils import is_py2, is_py3, decode_html_entities from svtplay_dl.utils.io import StringIO from svtplay_dl.output import output from requests import Session from requests import __build__ as requests_version import platform class subtitle(object): def __init__(self, options, subtype, url): self.url = url self.subtitle = None self.options = options self.subtype = subtype self.http = Session() def download(self): subdata = self.http.request("get", self.url, cookies=self.options.cookies) data = None if self.subtype == "tt": data = self.tt(subdata) if self.subtype == "json": data = self.json(subdata) if self.subtype == "sami": data = self.sami(subdata) if self.subtype == "smi": data = self.smi(subdata) if self.subtype == "wrst": data = self.wrst(subdata) if self.subtype == "raw": if is_py2: data = subdata.text.encode("utf-8") else: data = subdata.text if platform.system() == "Windows" and is_py3: file_d = output(self.options, "srt", mode="wt", encoding="utf-8") else: file_d = output(self.options, "srt", mode="wt") if hasattr(file_d, "read") is False: return file_d.write(data) file_d.close() def tt(self, subdata): i = 1 data = "" if is_py3: subs = subdata.text else: subs = subdata.text.encode("utf8") subdata = re.sub(' xmlns="[^"]+"', '', subs, count=1) tree = ET.XML(subdata) xml = tree.find("body").find("div") plist = list(xml.findall("p")) for node in plist: tag = norm(node.tag) if tag == "p" or tag == "span": begin = node.attrib["begin"] if not ("dur" in node.attrib): duration = node.attrib["duration"] else: duration = node.attrib["dur"] if not ("end" in node.attrib): begin2 = begin.split(":") duration2 = duration.split(":") sec = float(begin2[2]) + float(duration2[2]) end = "%02d:%02d:%06.3f" % (int(begin[0]), int(begin[1]), sec) else: end = node.attrib["end"] data += '%s\n%s --> %s\n' % (i, begin.replace(".", ","), end.replace(".", ",")) data = tt_text(node, data) data += "\n" i += 1 if is_py2: data = data.encode("utf8") return data def json(self, subdata): data = json.loads(subdata.text) number = 1 subs = "" for i in data: subs += "%s\n%s --> %s\n" % (number, timestr(int(i["startMillis"])), timestr(int(i["endMillis"]))) if is_py2: subs += "%s\n\n" % i["text"].encode("utf-8") else: subs += "%s\n\n" % i["text"] number += 1 return subs def sami(self, subdata): tree = ET.XML(subdata.text.encode("utf8")) subt = tree.find("Font") subs = "" n = 0 for i in subt.getiterator(): if i.tag == "Subtitle": n = i.attrib["SpotNumber"] if i.attrib["SpotNumber"] == "1": subs += "%s\n%s --> %s\n" % (i.attrib["SpotNumber"], timecolon(i.attrib["TimeIn"]), timecolon(i.attrib["TimeOut"])) else: subs += "\n%s\n%s --> %s\n" % (i.attrib["SpotNumber"], timecolon(i.attrib["TimeIn"]), timecolon(i.attrib["TimeOut"])) else: if int(n) > 0: subs += "%s\n" % i.text if is_py2: subs = subs.encode('utf8') return subs def smi(self, subdata): if requests_version < 0x20300: subdata = subdata.content if is_py3: subdata = subdata.decode("latin") else: subdata.encoding = "ISO-8859-1" subdata = subdata.text ssubdata = StringIO(subdata) timea = 0 number = 1 data = None subs = "" TAG_RE = re.compile(r'<[^>]+>') bad_char = re.compile(r'\x96') for i in ssubdata.readlines(): i = i.rstrip() sync = re.search(r"", i) if sync: if int(sync.group(1)) != int(timea): if data and data != " ": subs += "%s\n%s --> %s\n" % (number, timestr(timea), timestr(sync.group(1))) text = "%s\n" % TAG_RE.sub('', data.replace("
    ", "\n")) if text[len(text)-2] != "\n": text += "\n" subs += text number += 1 timea = sync.group(1) text = re.search("

    (.*)", i) if text: data = text.group(1) recomp = re.compile(r'\r') text = bad_char.sub('-', recomp.sub('', subs)).replace('"', '"') if is_py2 and isinstance(text, unicode): return text.encode("utf-8") return text def wrst(self, subdata): ssubdata = StringIO(subdata.text) srt = "" subtract = False number_b = 1 number = 0 block = 0 subnr = False for i in ssubdata.readlines(): match = re.search(r"^[\r\n]+", i) match2 = re.search(r"([\d:\.]+ --> [\d:\.]+)", i) match3 = re.search(r"^(\d+)\s", i) if i[:6] == "WEBVTT": pass elif match and number_b > 1: block = 0 srt += "\n" elif match2: if not subnr: srt += "%s\n" % number_b matchx = re.search(r'(\d+):(\d+)[.:]([\d\.]+) --> (\d+):(\d+)[.:]([\d\.]+)', i) hour1 = int(matchx.group(1)) hour2 = int(matchx.group(4)) if int(number) == 1: if hour1 > 9: subtract = True if subtract: hour1 -= 10 hour2 -= 10 time = "%s:%s:%s --> %s:%s:%s\n" % (hour1, matchx.group(2), matchx.group(3).replace(".", ","), hour2, matchx.group(5), matchx.group(6).replace(".", ",")) srt += time block = 1 subnr = False number_b += 1 elif match3 and block == 0: number = match3.group(1) srt += "%s\n" % number subnr = True else: sub = re.sub('<[^>]*>', '', i) srt += sub.lstrip() srt = decode_html_entities(srt) if is_py2: return srt.encode("utf-8") return srt def timestr(msec): """ Convert a millisecond value to a string of the following format: HH:MM:SS,SS with 10 millisecond precision. Note the , seperator in the seconds. """ sec = float(msec) / 1000 hours = int(sec / 3600) sec -= hours * 3600 minutes = int(sec / 60) sec -= minutes * 60 output = "%02d:%02d:%05.2f" % (hours, minutes, sec) return output.replace(".", ",") def timecolon(data): match = re.search(r"(\d+:\d+:\d+):(\d+)", data) return "%s,%s" % (match.group(1), match.group(2)) def norm(name): if name[0] == "{": _, tag = name[1:].split("}") return tag else: return name def tt_text(node, data): if node.text: data += "%s\n" % node.text.strip(' \t\n\r') for i in node: if i.text: data += "%s\n" % i.text.strip(' \t\n\r') if i.tail: text = i.tail.strip(' \t\n\r') if text: data += "%s\n" % text return data svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/tests/000077500000000000000000000000001264454056600206655ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/tests/__init__.py000066400000000000000000000000001264454056600227640ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/tests/filenamify.py000066400000000000000000000015221264454056600233620ustar00rootroot00000000000000#!/usr/bin/python # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil; coding: utf-8 -*- # ex:ts=4:sw=4:sts=4:et:fenc=utf-8 # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest from svtplay_dl.utils import filenamify class filenamifyTest(unittest.TestCase): test_values = [ ["foo", "foo"], ["foo bar", "foo.bar"], ["FOO BAR", "foo.bar"], ['foo-bar baz', "foo-bar.baz"], [u'Jason "Timbuktu" Diakité', "jason.timbuktu.diakite"], [u'Matlagning del 1 av 10 - R\xe4ksm\xf6rg\xe5s | SVT Play', 'matlagning.del.1.av.10.-.raksmorgas.svt.play'], ['$FOOBAR', "foobar"], ] def test(self): for inp, ref in self.test_values: self.assertEqual(filenamify(inp), ref) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/tests/hls.py000066400000000000000000000035241264454056600220310ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest import svtplay_dl.fetcher.hls as hls class HlsTest(unittest.TestCase): def test_get_full_url_1(self): for test in [ # full http:// url as media segment in playlist { 'srcurl': 'INVALID', 'segment': 'http://example.com/', 'expected': 'http://example.com/' }, # full https:// url as media segment in playlist { 'srcurl': 'INVALID', 'segment': 'https://example.com/', 'expected': 'https://example.com/' }, # filename as media segment in playlist (http) { 'srcurl': 'http://example.com/', 'segment': 'foo.ts', 'expected': 'http://example.com/foo.ts' }, # filename as media segment in playlist (https) { 'srcurl': 'https://example.com/', 'segment': 'foo.ts', 'expected': 'https://example.com/foo.ts' }, # replacing srcurl file { 'srcurl': 'http://example.com/bar', 'segment': 'foo.ts', 'expected': 'http://example.com/foo.ts' }, # with query parameters { 'srcurl': 'http://example.com/bar?baz=qux', 'segment': 'foo.ts', 'expected': 'http://example.com/foo.ts?baz=qux' }, ]: self.assertEqual( hls._get_full_url(test['segment'], test['srcurl']), test['expected']) svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/tests/output.py000066400000000000000000000076061264454056600226100ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest import svtplay_dl.output from mock import patch # FIXME: use mock framework instead of this hack class mockfile(object): def __init__(self): self.content = [] def write(self, string): self.content.append(string) def read(self): return self.content.pop() class progressTest(unittest.TestCase): def setUp(self): self.mockfile = mockfile() svtplay_dl.output.progress_stream = self.mockfile @patch('svtplay_dl.output.progressbar') def test_0_0(self, pbar): svtplay_dl.output.progress(0, 0) self.assertFalse(pbar.called) @patch('svtplay_dl.output.progressbar') def test_0_100(self, pbar): svtplay_dl.output.progress(0, 100) pbar.assert_any_call(100, 0, "") class progressbarTest(unittest.TestCase): def setUp(self): self.old_termsiz = svtplay_dl.output.get_terminal_size svtplay_dl.output.get_terminal_size = lambda: (50, 25) self.mockfile = mockfile() svtplay_dl.output.progress_stream = self.mockfile def tearDown(self): svtplay_dl.output.get_terminal_size = self.old_termsiz def test_0_100(self): svtplay_dl.output.progressbar(100, 0) self.assertEqual( self.mockfile.read(), "\r[000/100][...............] " ) def test_progress_1_100(self): svtplay_dl.output.progressbar(100, 1) self.assertEqual( self.mockfile.read(), "\r[001/100][...............] " ) def test_progress_2_100(self): svtplay_dl.output.progressbar(100, 2) self.assertEqual( self.mockfile.read(), "\r[002/100][...............] " ) def test_progress_50_100(self): svtplay_dl.output.progressbar(100, 50) self.assertEqual( self.mockfile.read(), "\r[050/100][=======........] " ) def test_progress_100_100(self): svtplay_dl.output.progressbar(100, 100) self.assertEqual( self.mockfile.read(), "\r[100/100][===============] " ) def test_progress_20_100_msg(self): svtplay_dl.output.progressbar(100, 20, "msg") self.assertEqual( self.mockfile.read(), "\r[020/100][===............] msg" ) def test_progress_20_100_termwidth(self): svtplay_dl.output.get_terminal_size = lambda: (75, 25) svtplay_dl.output.progressbar(100, 20) self.assertEqual( self.mockfile.read(), "\r[020/100][========................................] " ) class EtaTest(unittest.TestCase): @patch('time.time') def test_eta_0_100(self, mock_time): mock_time.return_value = float(0) # Let's make this simple; we'll create something that # processes one item per second, and make the size be # 100. eta = svtplay_dl.output.ETA(100) self.assertEqual(eta.left, 100) # no progress yet self.assertEqual(str(eta), "(unknown)") # no progress yet mock_time.return_value = float(10) # sleep(10) eta.update(10) self.assertEqual(eta.left, 90) self.assertEqual(str(eta), "0:01:30") # 90 items left, 90s left mock_time.return_value += 1 eta.increment() # another item completed in one second! self.assertEqual(eta.left, 89) self.assertEqual(str(eta), "0:01:29") mock_time.return_value += 9 eta.increment(9) # another item completed in one second! self.assertEqual(eta.left, 80) self.assertEqual(str(eta), "0:01:20") mock_time.return_value = float(90) # sleep(79) eta.update(90) self.assertEqual(eta.left, 10) self.assertEqual(str(eta), "0:00:10") svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/tests/subtitle.py000066400000000000000000000013151264454056600230720ustar00rootroot00000000000000#!/usr/bin/python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # The unittest framwork doesn't play nice with pylint: # pylint: disable-msg=C0103 from __future__ import absolute_import import unittest import svtplay_dl.subtitle class timestrTest(unittest.TestCase): def test_1(self): self.assertEqual(svtplay_dl.subtitle.timestr(1), "00:00:00,00") def test_100(self): self.assertEqual(svtplay_dl.subtitle.timestr(100), "00:00:00,10") def test_3600(self): self.assertEqual(svtplay_dl.subtitle.timestr(3600), "00:00:03,60") def test_3600000(self): self.assertEqual(svtplay_dl.subtitle.timestr(3600000), "01:00:00,00") svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/utils/000077500000000000000000000000001264454056600206635ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/utils/__init__.py000066400000000000000000000117431264454056600230020ustar00rootroot00000000000000# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil: coding: utf-8 -*- # ex:ts=4:sw=4:sts=4:et:fenc=utf-8 from __future__ import absolute_import import sys import logging import re import unicodedata try: import HTMLParser except ImportError: # pylint: disable-msg=import-error import html.parser as HTMLParser is_py2 = (sys.version_info[0] == 2) is_py3 = (sys.version_info[0] == 3) 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' log = logging.getLogger('svtplay_dl') progress_stream = sys.stderr try: from requests import Session except ImportError: print("You need to install python-requests to use this script") sys.exit(3) class HTTP(Session): def __init__(self, options, *args, **kwargs): Session.__init__(self, *args, **kwargs) self.verify = options.ssl_verify if options.http_headers: self.headers.update(self.split_header(options.http_headers)) self.headers.update({"User-Agent": FIREFOX_UA}) def check_redirect(self, url): return self.get(url, stream=True).url def request(self, method, url, *args, **kwargs): headers = kwargs.pop("headers", None) if headers: for i in headers.keys(): self.headers[i] = headers[i] log.debug("HTTP getting %r", url) res = Session.request(self, method, url, verify=self.verify, *args, **kwargs) return res def split_header(self, headers): return dict(x.split('=') for x in headers.split(';')) def sort_quality(data): data = sorted(data, key=lambda x: (x.bitrate, x.name()), reverse=True) datas = [] for i in data: datas.append([i.bitrate, i.name()]) return datas def list_quality(videos): data = sort_quality(videos) log.info("Quality\tMethod") for i in data: log.info("%s\t%s" % (i[0], i[1].upper())) def prio_streams(options, streams, selected): prio = options.stream_prio if prio is None: prio = ["hls","hds", "http", "rtmp"] if isinstance(prio, str): prio = prio.split(",") lstreams = [] for i in streams: if int(i.bitrate) == selected: lstreams.append(i) return [x for (y, x) in sorted(zip(prio, lstreams))] 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]] 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) return prio_streams(options, streams, selected)[0] def ensure_unicode(s): """ Ensure string is a unicode string. If it isn't it assumed it is utf-8 and decodes it to a unicode string. """ if (is_py2 and isinstance(s, str)) or (is_py3 and isinstance(s, bytes)): s = s.decode('utf-8', 'replace') return s def decode_html_entities(s): """ Replaces html entities with the character they represent. >>> print(decode_html_entities("<3 &")) <3 & """ parser = HTMLParser.HTMLParser() def unesc(m): return parser.unescape(m.group()) return re.sub(r'(&[^;]+;)', unesc, ensure_unicode(s)) def filenamify(title): """ Convert a string to something suitable as a file name. E.g. Matlagning del 1 av 10 - Räksmörgås | SVT Play -> matlagning.del.1.av.10.-.raksmorgas.svt.play """ # ensure it is unicode title = ensure_unicode(title) # NFD decomposes chars into base char and diacritical mark, which # means that we will get base char when we strip out non-ascii. title = unicodedata.normalize('NFD', title) # Convert to lowercase # Drop any non ascii letters/digits # Drop any leading/trailing whitespace that may have appeared title = re.sub(r'[^a-z0-9 .-]', '', title.lower().strip()) # Replace whitespace with dot title = re.sub(r'\s+', '.', title) return title def download_thumbnail(options, url): data = Session.get(url).content filename = re.search(r"(.*)\.[a-z0-9]{2,3}$", options.output) tbn = "%s.tbn" % filename.group(1) log.info("Thumbnail: %s", tbn) fd = open(tbn, "wb") fd.write(data) fd.close() svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/utils/io.py000066400000000000000000000005631264454056600216500ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # Pylint does not seem to handle conditional imports # pylint: disable=F0401 # pylint: disable=W0611 # pylint: disable=E0611 from __future__ import absolute_import from svtplay_dl.utils import is_py3 if is_py3: from io import StringIO else: from StringIO import StringIO svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/utils/terminal.py000066400000000000000000000050141264454056600230500ustar00rootroot00000000000000import os import shlex import struct import platform import subprocess def get_terminal_size(): """ getTerminalSize() - get width and height of console - works on linux,os x,windows,cygwin(windows) originally retrieved from: http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python https://gist.github.com/jtriley/1108174 """ current_os = platform.system() tuple_xy = None if current_os == 'Windows': tuple_xy = _get_terminal_size_windows() if tuple_xy is None: tuple_xy = _get_terminal_size_tput() # needed for window's python in cygwin's xterm! if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'): tuple_xy = _get_terminal_size_linux() if tuple_xy is None: tuple_xy = (80, 25) # default value return tuple_xy def _get_terminal_size_windows(): try: from ctypes import windll, create_string_buffer # stdin handle is -10 # stdout handle is -11 # stderr handle is -12 h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 return sizex, sizey except: pass def _get_terminal_size_tput(): # get terminal width # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window try: cols = int(subprocess.check_call(shlex.split('tput cols'))) rows = int(subprocess.check_call(shlex.split('tput lines'))) return (cols, rows) except: pass def _get_terminal_size_linux(): def ioctl_GWINSZ(fd): try: import fcntl import termios cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) return cr except: pass cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: try: cr = (os.environ['LINES'], os.environ['COLUMNS']) except: return None return int(cr[1]), int(cr[0])svtplay-dl-0.30.2016.01.10/lib/svtplay_dl/utils/urllib.py000066400000000000000000000007751264454056600225370ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # Pylint does not seem to handle conditional imports # pylint: disable=F0401 # pylint: disable=W0611 from __future__ import absolute_import from svtplay_dl.utils import is_py3 if is_py3: # pylint: disable=E0611 from urllib.parse import quote, unquote_plus, quote_plus, urlparse, parse_qs, urljoin else: from urllib import quote, unquote_plus, quote_plus from urlparse import urlparse, parse_qs, urljoinsvtplay-dl-0.30.2016.01.10/scripts/000077500000000000000000000000001264454056600162635ustar00rootroot00000000000000svtplay-dl-0.30.2016.01.10/scripts/diff_man_help.sh000066400000000000000000000023361264454056600213760ustar00rootroot00000000000000#!/bin/sh # Make sure the options listed in --help and the manual are in sync. TMPDIR=$(mktemp -d svtplay-man-test-XXXXXX) [ "$TMPDIR" ] || { echo "mktemp not available, using static dir" TMPDIR=svtplay-man-test.tmp [ ! -e "$TMPDIR" ] || { echo "$TMPDIR already exists. Aborting." exit 1 } mkdir "$TMPDIR" } trap 'rm -rf "$TMPDIR"' EXIT TERM # FIXME: *Currently* we don't have any =head3 that doesn't # document an option. This is thus fragile to changes. sed -nre 's/^=head3 //p' svtplay-dl.pod > $TMPDIR/options.man ./svtplay-dl --help | grep '^ *-' > $TMPDIR/options.help # --help specific filtering sed -i -re 's/ .*//' $TMPDIR/options.help sed -i -re 's/ excl.*//' $TMPDIR/options.help sed -i -re 's/^ *//' $TMPDIR/options.help sed -i -re 's/OUTPUT/filename/g' $TMPDIR/options.help for file in $TMPDIR/options.*; do sed -i -re 's/, / /' $file sed -i -re 's/ / /' $file # Normalize order of --help -h vs -h --help # "--help -h" => "-h --help" perl -i -pe 's/^(-.(?: [^-][^ ]+)?) (--.*)/\2 \1/' $file done OS=$(uname -s) SHA1="sha1sum" [ "$OS" = "Darwin*" ] || { SHA1="shasum" } [ "$($SHA1<$TMPDIR/options.help)" = "$($SHA1<$TMPDIR/options.man)" ] || { diff -u $TMPDIR/options.help $TMPDIR/options.man exit 1 } svtplay-dl-0.30.2016.01.10/scripts/run-tests.sh000077500000000000000000000013551264454056600205720ustar00rootroot00000000000000#!/bin/sh OPTS='--all-modules --with-doctest ' die() { echo Error: "$@" exit 1 } COVER_OPTS="--with-coverage --cover-package=svtplay_dl" NOSETESTS= while [ "$#" -gt 0 ]; do case $1 in -2) NOSETESTS="$NOSETESTS nosetests" ;; -3) NOSETESTS="$NOSETESTS nosetests3" ;; -c|--coverage) OPTS="$OPTS $COVER_OPTS" ;; -C|--coverage-html) OPTS="$OPTS $COVER_OPTS --cover-html" ;; -v|--verbose) OPTS="$OPTS --verbose" ;; -*) die "Unknown option: '$1'" ;; *) die "Unknown argument: '$1'" ;; esac shift done # Default to only run for python2 NOSETESTS=${NOSETESTS:-nosetests} tests_ok=y for nose in $NOSETESTS; do PYTHONPATH=lib $nose $OPTS [ $? -eq 0 ] || tests_ok= done [ "$tests_ok" = y ] svtplay-dl-0.30.2016.01.10/setup.py000066400000000000000000000032071264454056600163100ustar00rootroot00000000000000#!/usr/bin/env python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from setuptools import setup, find_packages import sys import os srcdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib/") sys.path.insert(0, srcdir) import svtplay_dl deps = [] if sys.version_info[0] == 2 and sys.version_info[1] <= 7 and sys.version_info[1] < 9: deps.append("requests[security]>=2.0.0") else: deps.append(["requests>=2.0.0"]) setup( name = "svtplay-dl", version = svtplay_dl.__version__, packages = find_packages( 'lib', exclude=["tests", "*.tests", "*.tests.*"]), install_requires=deps, package_dir = {'': 'lib'}, scripts = ['bin/svtplay-dl'], author = "Johan Andersson", author_email = "j@i19.se", description = "Command-line program to download videos from various video on demand sites", license = "MIT", url = "https://github.com/spaam/svtplay-dl", classifiers=["Development Status :: 5 - Production/Stable", "Environment :: Console", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Internet :: WWW/HTTP", "Topic :: Multimedia :: Sound/Audio", "Topic :: Multimedia :: Video", "Topic :: Utilities"] ) svtplay-dl-0.30.2016.01.10/svtplay-dl000077500000000000000000000002461264454056600166230ustar00rootroot00000000000000#!/usr/bin/env python print("This file is no longer updated.") print("if you still want to use it. go to https://svtplay-dl.se/archive and download the latest one") svtplay-dl-0.30.2016.01.10/svtplay-dl.pod000066400000000000000000000105711264454056600174030ustar00rootroot00000000000000=encoding utf8 =head1 NAME svtplay-dl - media downloader for "play" sites (e.g. SVT Play) =head1 SYNOPSIS svtplay-dl [OPTIONS] svtplay-dl --help =head1 DESCRIPTION svtplay-dl is able to download media from various 'video on demand' sites (sometimes known as 'play services') (see below for list of supported services). Usually, you just have to give it the URL as an argument, and it will figure out what to do itself. =head2 OPTIONS =head3 --version Show the program's version number and exit. =head3 --help -h Show description of options. =head3 --output=filename -o filename Outputs to the given filename. =head3 --force -f Overwrite the output file if it exists already. =head3 --resume -r Resume a download. =head3 --live -l Enable support for live streams. (rtmp based ones) =head3 --silent -s Be less verbose. =head3 --verbose -v Explain what is going on, including HTTP requests and other useful debugging data. =head3 --quality=quality -q quality Choose what format to download. It will download the best format by default. =head3 --flexible-quality=amount -Q amount Allow given quality (as above) to differ by an amount. =head3 --list-quality List the available qualities for a video. =head3 --subtitle -S Download subtitle with the media if available. =head3 --force-subtitle Download only subtitle if its used with -S. =head3 --require-subtitle Download only if a subtitle is available =head3 --username=USERNAME -u USERNAME Username, if the service requires authentication. =head3 --password=PASSWORD -p PASSWORD Password, if the serivce requires authentication. =head3 -t, --thumbnail Download thumbnail from the site if available. =head3 -A, --all-episodes Try to download all episodes. =head3 --all-last=NN Get last NN episodes instead of all episodes, when used with --all-episodes. =head3 -P preferred, --preferred=preferred Preferred download method. =head3 --exclude=WORD1,WORD2,... Exclude videos with the WORD(s) in the filename. Comma separated. =head3 --get-url -g Do not download any video, but instead print the URL. =head3 --dont-verify-ssl-cert Don't attempt to verify SSL certificates. =head3 --http-header=header1=value;header2=value2 A header to add to each HTTP request. =head3 --stream-priority=hls,hds,http,rtmp If two streams have the same quality, choose the one you prefer =head1 SUPPORTED SERVICES =head2 English =over =item * Bambuser: L =item * HBO: L =item * Twitch: L =item * Vimeo: L =item * Viagame: L =back =head2 Swedish =over =item * Aftonbladet: L =item * Comedycentral.se: L =item * Dagens Industri: L =item * Dagens Nyheter: L =item * Dplay: L[ =item * DR: L =item * EFN: L =item * Expressen: L =item * Kanal9 Play: L =item * Öppet Arkiv: L =item * Svenska kennelklubben play: L =item * Svenska Dagbladet: L =item * Sveriges Radio: L =item * SVT Play: L =item * TV10 Play: L =item * TV3 Play: L =item * TV4: L =item * TV4 Play: L =item * TV6 Play: L =item * TV8 Play: L =item * UR: L =item * UR Play: L =back =head2 Danish =over =item * DR: L =item * TV3 Play: L =back =head2 Norwegian =over =item * NRK: L =item * TV3 Play: L =item * Viasat 4 play: L =back =head2 Icelandic =over =item * RUV: L =back =head2 Estonian =over =item * TV3 Play: L =back =head2 Latvian =over =item * TV3 Play: L =back =head2 Lithuanian =over =item * TV3 Play: L =back =head1 REPOTING BUGS AND CONTRIBUTING If you find an issue with svtplay-dl, you can report them at L. Or better yet, if possible, open a pull request! =head1 COPYRIGHT Copyright (C) 2011--2015, Johan Andersson et al This software is licensed under the MIT license. =cut