pax_global_header00006660000000000000000000000064131600463470014516gustar00rootroot0000000000000052 comment=928f25c4c3349029a2b727aa0854b4800fad0dbf svtplay-dl-1.9.6/000077500000000000000000000000001316004634700136325ustar00rootroot00000000000000svtplay-dl-1.9.6/.gitignore000066400000000000000000000001501316004634700156160ustar00rootroot00000000000000*~ *.pyc *.DS_Store build/ dist/ cover/ *.egg-info svtplay-dl svtplay-dl.1 svtplay-dl.1.gz github.com/* svtplay-dl-1.9.6/LICENSE000066400000000000000000000021121316004634700146330ustar00rootroot00000000000000The 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-1.9.6/MANIFEST.in000066400000000000000000000000421316004634700153640ustar00rootroot00000000000000include README.rst include LICENSEsvtplay-dl-1.9.6/Makefile000066400000000000000000000044761316004634700153050ustar00rootroot00000000000000all: svtplay-dl .PHONY: test cover doctest pylint svtplay-dl \ release clean_releasedir $(RELEASE_DIR) # These variables describe the latest release: VERSION = 1.9.6 LATEST_RELEASE = $(VERSION) # If we build a new release, this is what it will be called: NEW_RELEASE = $(VERSION) 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/^\(__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-1.9.6/README.rst000066400000000000000000000073061316004634700153270ustar00rootroot00000000000000svtplay-dl ========== Installation ------------ Mac OSX ~~~~~~~ If you have OS X and `Homebrew`_ you can install with: :: brew install svtplay-dl Make sure you notice that you need to run `brew install ffmpeg` or `brew install libav` afterwards, if you don't already have one of these packages. Debian and Ubuntu ~~~~~~~~~~~~~~~~~ svtplay-dl(v 0.30) is available in Debian in Jessie and later and Ubuntu in 14.04 and later, which means you can install it straight away using apt (even though version included in the official Debian and Ubuntu apt repos is very old and we **strongly** recommend using our own apt repo which always include the latest version.) **svtplay-dl apt repo for debian / ubuntu (https://apt.svtplay-dl.se/)** # Add the release PGP keys: curl -s https://svtplay-dl.se/release-key.txt | sudo apt-key add - # Add the "release" channel to your APT sources: echo "deb http://apt.svtplay-dl.se/ svtplay-dl release" | sudo tee /etc/apt/sources.list.d/svtplay-dl.list # Update and install svtplay-dl: sudo apt-get update sudo apt-get install svtplay-dl … as root. Windows ~~~~~~~ You can download windows binaries from `svtplay-dl.se`_ If you want to build your own windows binaries: 1. Install pyinstaller 3.1.1 (https://pypi.python.org/pypi/PyInstaller/3.1.1) 2. Follow the steps listed under **From source** 3. Run :: pyinstaller.exe --noupx --onefile c:\path\to\svtplay-dl-clone\spec\svtplay-dl.spec (where you replace the path with the correct one) 4. Find binary in dist folder. 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`_ - `ffmpeg`_ or `avconv`_ for postprocessing and/or for DASH streams To install it, run :: # as root: python setup.py install # or the old method make # as root: make install After install ~~~~~~~~~~~~~ :: svtplay-dl [options] URL 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 - viafree.se (former tv3play.se, tv6play.se, tv8play.se, tv10play.se) - viafree.dk (former tv3play.dk) - viafree.no (former tv3play.no, viasat4play.no) - tv3play.ee - tv3play.lt - tv3play.lv - tv4.se - tv4play.se - twitch.tv - ur.se - urplay.se - vg.no - viagame.com License ------- This project is licensed under `The MIT License (MIT)`_. Homepage: `svtplay-dl.se`_ .. _Homebrew: http://brew.sh/ .. _RTMPDump: http://rtmpdump.mplayerhq.hu/ .. _PyCrypto: https://www.dlitz.net/software/pycrypto/ .. _Requests: http://www.python-requests.org/ .. _ffmpeg: https://ffmpeg.org .. _avconv: https://libav.org .. _on github: https://github.com/spaam/svtplay-dl/issues .. _svtplay-dl.se: https://svtplay-dl.se .. _The MIT License (MIT): LICENSE svtplay-dl-1.9.6/bin/000077500000000000000000000000001316004634700144025ustar00rootroot00000000000000svtplay-dl-1.9.6/bin/svtplay-dl000077500000000000000000000002621316004634700164270ustar00rootroot00000000000000#!/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-1.9.6/lib/000077500000000000000000000000001316004634700144005ustar00rootroot00000000000000svtplay-dl-1.9.6/lib/Makefile000066400000000000000000000026461316004634700160500ustar00rootroot00000000000000PYLINT_OPTS = --reports=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 \ svtplay_dl.postprocess 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-1.9.6/lib/svtplay_dl/000077500000000000000000000000001316004634700165615ustar00rootroot00000000000000svtplay-dl-1.9.6/lib/svtplay_dl/__init__.py000066400000000000000000000467761316004634700207160ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import, unicode_literals 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, is_py2, ensure_unicode 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.postprocess import postprocess from svtplay_dl.service.aftonbladet import Aftonbladet, Aftonbladettv from svtplay_dl.service.bambuser import Bambuser from svtplay_dl.service.bigbrother import Bigbrother from svtplay_dl.service.cmore import Cmore 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.filmarkivet import Filmarkivet from svtplay_dl.service.flowonline import Flowonline 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.nhl import NHL 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.pokemon import Pokemon from svtplay_dl.service.qbrick import Qbrick from svtplay_dl.service.radioplay import Radioplay from svtplay_dl.service.riksdagen import Riksdagen 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.svt import Svt 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.viasatsport import Viasatsport from svtplay_dl.service.vimeo import Vimeo from svtplay_dl.service.youplay import Youplay __version__ = "1.9.6" sites = [ Aftonbladet, Aftonbladettv, Bambuser, Bigbrother, Cmore, Dbtv, Disney, Dplay, Dr, Efn, Expressen, Facebook, Filmarkivet, Flowonline, Hbo, Twitch, Lemonwhale, Mtvservices, Mtvnn, NHL, Nrk, Qbrick, Picsearch, Pokemon, Ruv, Radioplay, Solidtango, Sr, Svt, Svtplay, OppetArkiv, Tv4play, Urplay, Viaplay, Viasatsport, Vimeo, Vg, Youplay, Riksdagen, Raw] 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 = 0 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.merge_subtitle = False self.force_subtitle = False self.require_subtitle = False self.get_all_subtitles = False self.get_raw_subtitles = False self.convert_subtitle_colors = 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 self.remux = False self.silent_semi = False def get_multiple_media(urls, options): if options.output and os.path.isfile(options.output): log.error("Output must be a directory if used with multiple URLs") 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 for url in urls: get_media(url, copy.copy(options)) def get_media(url, options): if "http" not in url[:4]: url = "http://%s" % url if options.silent_semi: options.silent = True 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 is_py2: url = ensure_unicode(url) if options.all_episodes: get_all_episodes(stream, copy.copy(options), url) else: get_one_media(stream, copy.copy(options)) def get_all_episodes(stream, options, url): 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, copy.copy(options), o) log.info("Episode %d of %d", idx + 1, len(episodes)) log.info("Url: %s",o) # get_one_media overwrites options.output... get_one_media(substream, copy.copy(options)) def get_one_media(stream, options): # Make an automagic filename if not filename(stream): return if options.merge_subtitle: from svtplay_dl.utils import which if not which('ffmpeg'): log.error("--merge-subtitle needs ffmpeg. Please install ffmpeg.") log.info("https://ffmpeg.org/download.html") sys.exit(2) videos = [] subs = [] subfixes = [] 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: log.error("version: %s" % __version__) raise 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.get_url: if subs: if options.get_all_subtitles: for sub in subs: print(sub.url) else: print(subs[0].url) if options.force_subtitle: return def options_subs_dl(subfixes): if subs: if options.get_all_subtitles: for sub in subs: sub.download() if options.merge_subtitle: if sub.subfix: subfixes += [sub.subfix] else: options.get_all_subtitles = False else: subs[0].download() elif options.merge_subtitle: options.merge_subtitle = False if options.subtitle and options.output != "-" and not options.get_url: options_subs_dl(subfixes) if options.force_subtitle: return if options.merge_subtitle and not options.subtitle: options_subs_dl(subfixes) if len(videos) == 0: for exc in error: log.error(str(exc)) else: if options.list_quality: list_quality(videos) return try: stream = select_quality(options, videos) if options.get_url: print(stream.url) return log.info("Selected to download %s, bitrate: %s", stream.name(), stream.bitrate) 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") post = postprocess(stream, options, subfixes) if stream.name() == "dash" and post.detect: post.merge() if stream.name() == "dash" and not post.detect and stream.finished: log.warning("Cant find ffmpeg/avconv. audio and video is in seperate files. if you dont want this use -P hls or hds") if options.remux: post.remux() if options.silent_semi and stream.finished: log.log(25, "Download of %s was completed" % stream.options.output) def setup_log(silent, verbose=False): logging.addLevelName(25, "INFO") fmt = logging.Formatter('%(levelname)s: %(message)s') if silent: stream = sys.stderr level = 25 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] [urls]" parser = OptionParser(usage=usage, version=__version__) parser.add_option("-o", "--output", metavar="OUTPUT", help="outputs to the given filename or folder") 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("--silent-semi", action="store_true", dest="silent_semi", default=False, help="only show a message when the file is downloaded") 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("-M", "--merge-subtitle", action="store_true", dest="merge_subtitle", default=False, help="merge subtitle with video/audio file with corresponding ISO639-3 language code. this invokes --remux automatically. use with -S for external also.") 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("--all-subtitles", dest="get_all_subtitles", default=False, action="store_true", help="Download all available subtitles for the video") parser.add_option("--raw-subtitles", dest="get_raw_subtitles", default=False, action="store_true", help="also download the subtitles in their native format") parser.add_option("--convert-subtitle-colors", dest="convert_subtitle_colors", default=False, action="store_true", help="converts the color information in subtitles, to tags") 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 (dash, 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="dash,hls,hds,http,rtmp", help="If two streams have the same quality, choose the one you prefer") parser.add_option("--remux", dest="remux", default=False, action="store_true", help="Remux from one container to mp4 using ffmpeg or avconv") parser.add_option("--include-clips", dest="include_clips", default=False, action="store_true", help="include clips from websites when using -A") parser.add_option("--cmore-operatorlist", dest="cmoreoperatorlist", default=False, action="store_true", help="show operatorlist for cmore") parser.add_option("--cmore-operator", dest="cmoreoperator", default=None, metavar="operator") (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.require_subtitle: if options.merge_subtitle: options.merge_subtitle = True else: options.subtitle = True if options.merge_subtitle: options.remux = True options = mergeParserOption(Options(), options) if options.silent_semi: options.silent = True setup_log(options.silent, options.verbose) if options.cmoreoperatorlist: c = Cmore(options, args) c.operatorlist() sys.exit(0) if options.flexibleq and not options.quality: log.error("flexible-quality requires a quality") sys.exit(4) urls = args try: if len(urls) == 1: get_media(urls[0], options) else: get_multiple_media(urls, 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.merge_subtitle = parser.merge_subtitle options.silent_semi = parser.silent_semi 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 options.remux = parser.remux options.get_all_subtitles = parser.get_all_subtitles options.get_raw_subtitles = parser.get_raw_subtitles options.convert_subtitle_colors = parser.convert_subtitle_colors options.include_clips = parser.include_clips options.cmoreoperatorlist = parser.cmoreoperatorlist options.cmoreoperator = parser.cmoreoperator return options svtplay-dl-1.9.6/lib/svtplay_dl/__main__.py000066400000000000000000000005721316004634700206570ustar00rootroot00000000000000#!/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-1.9.6/lib/svtplay_dl/error.py000066400000000000000000000021611316004634700202640ustar00rootroot00000000000000# 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): pass class NoRequestedProtocols(UIException): """ This excpetion is thrown when the service provides streams, but not using any accepted protocol (as decided by options.stream_prio). """ def __init__(self, requested, found): """ The constructor takes two mandatory parameters, requested and found. Both should be lists. requested is the protocols we want and found is the protocols that can be used to access the stream. """ self.requested = requested self.found = found super(NoRequestedProtocols, self).__init__( "None of the provided protocols (%s) are in " "the current list of accepted protocols (%s)" % ( self.found, self.requested ) ) def __repr__(self): return "NoRequestedProtocols(requested=%s, found=%s)" % ( self.requested, self.found) svtplay-dl-1.9.6/lib/svtplay_dl/fetcher/000077500000000000000000000000001316004634700202015ustar00rootroot00000000000000svtplay-dl-1.9.6/lib/svtplay_dl/fetcher/__init__.py000066400000000000000000000011151316004634700223100ustar00rootroot00000000000000from __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) self.finished = False self.audio = kwargs.pop("audio", None) self.files = kwargs.pop("files", None) def __repr__(self): return "" % (self.__class__.__name__, self.bitrate) def name(self): pass svtplay-dl-1.9.6/lib/svtplay_dl/fetcher/dash.py000066400000000000000000000167771316004634700215140ustar00rootroot00000000000000# 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 copy import xml.etree.ElementTree as ET import os import re from svtplay_dl.output import progress_stream, output, ETA, progressbar from svtplay_dl.utils.urllib import urljoin from svtplay_dl.error import UIException, ServiceError from svtplay_dl.fetcher import VideoRetriever class DASHException(UIException): def __init__(self, url, message): self.url = url super(DASHException, self).__init__(message) class LiveDASHException(DASHException): def __init__(self, url): super(LiveDASHException, self).__init__( url, "This is a live DASH stream, and they are not supported.") def templateelemt(element, filename, idnumber): files = [] init = element.attrib["initialization"] media = element.attrib["media"] if "startNumber" in element.attrib: start = int(element.attrib["startNumber"]) else: start = 0 timeline = element.find("{urn:mpeg:dash:schema:mpd:2011}SegmentTimeline") rvalue = timeline.findall(".//{urn:mpeg:dash:schema:mpd:2011}S[@r]") selements = timeline.findall(".//{urn:mpeg:dash:schema:mpd:2011}S") selements.pop() if rvalue: total = int(rvalue[0].attrib["r"]) + len(selements) + 1 name = media.replace("$RepresentationID$", idnumber) files.append(urljoin(filename, init.replace("$RepresentationID$", idnumber))) if "$Time$" in media: time = [] time.append(0) for n in selements: time.append(int(n.attrib["d"])) match = re.search("\$Time\$", name) if match: number = 0 if len(selements) < 3: for n in range(start, start + total): new = name.replace("$Time$", str(n * int(rvalue[0].attrib["d"]))) files.append(urljoin(filename, new)) else: for n in time: number += int(n) new = name.replace("$Time$", str(number)) files.append(urljoin(filename, new)) if "$Number" in name: if re.search("\$Number(\%\d+)d\$", name): vname = name.replace("$Number", "").replace("$", "") for n in range(start, start + total): files.append(urljoin(filename, vname % n)) else: for n in range(start, start + total): newname = name.replace("$Number$", str(n)) files.append(urljoin(filename, newname)) return files def adaptionset(element, url, baseurl=None): streams = {} dirname = os.path.dirname(url) + "/" if baseurl: dirname = urljoin(dirname, baseurl) template = element[0].find("{urn:mpeg:dash:schema:mpd:2011}SegmentTemplate") represtation = element[0].findall(".//{urn:mpeg:dash:schema:mpd:2011}Representation") for i in represtation: files = [] segments = False filename = dirname bitrate = int(int(i.attrib["bandwidth"]) / 1000) idnumber = i.attrib["id"] if i.find("{urn:mpeg:dash:schema:mpd:2011}BaseURL") is not None: filename = urljoin(filename, i.find("{urn:mpeg:dash:schema:mpd:2011}BaseURL").text) if i.find("{urn:mpeg:dash:schema:mpd:2011}SegmentBase") is not None: files.append(filename) if template is not None: segments = True files = templateelemt(template, filename, idnumber) elif i.find("{urn:mpeg:dash:schema:mpd:2011}SegmentTemplate"): segments = True files = templateelemt(i.find("{urn:mpeg:dash:schema:mpd:2011}SegmentTemplate"), filename, idnumber) streams[bitrate] = {"segments": segments, "files": files} return streams def dashparse(options, res, url): streams = {} if not res: return None if res.status_code >= 400: streams[0] = ServiceError("Can't read DASH playlist. {0}".format(res.status_code)) return streams xml = ET.XML(res.text) temp = xml.findall('.//{urn:mpeg:dash:schema:mpd:2011}AdaptationSet[@mimeType="audio/mp4"]') audiofiles = adaptionset(temp, url) temp = xml.findall('.//{urn:mpeg:dash:schema:mpd:2011}AdaptationSet[@mimeType="video/mp4"]') videofiles = adaptionset(temp, url) for i in videofiles.keys(): bitrate = (int(i) + int(list(audiofiles.keys())[0])) options.other = "mp4" options.segments = videofiles[i]["segments"] streams[int(bitrate)] = DASH(copy.copy(options), url, bitrate, cookies=res.cookies, audio=audiofiles[list(audiofiles.keys())[0]]["files"], files=videofiles[i]["files"]) return streams class DASH(VideoRetriever): def name(self): return "dash" def download(self): if self.options.live and not self.options.force: raise LiveDASHException(self.url) if self.options.segments: if self.audio: self._download2(self.audio, audio=True) self._download2(self.files) else: if self.audio: self._download(self.audio, audio=True) self._download(self.url) def _download(self, url, audio=False): cookies = self.kwargs["cookies"] data = self.http.request("get", url, cookies=cookies, headers={'Range': 'bytes=0-8192'}) try: total_size = data.headers['Content-Range'] total_size = total_size[total_size.find("/")+1:] except KeyError: total_size = 0 total_size = int(total_size) bytes_so_far = 8192 if audio: file_d = output(copy.copy(self.options), "m4a") else: file_d = output(self.options, self.options.other) if hasattr(file_d, "read") is False: return file_d.write(data.content) eta = ETA(total_size) while bytes_so_far < total_size: old = bytes_so_far + 1 bytes_so_far = old + 1000000 if bytes_so_far > total_size: bytes_so_far = total_size bytes_range = "bytes=%s-%s" % (old, bytes_so_far) data = self.http.request("get", url, cookies=cookies, headers={'Range': bytes_range}) file_d.write(data.content) if self.options.output != "-" and not self.options.silent: eta.update(old) progressbar(total_size, old, ''.join(["ETA: ", str(eta)])) if self.options.output != "-": file_d.close() progressbar(bytes_so_far, total_size, "ETA: complete") progress_stream.write('\n') self.finished = True def _download2(self, files, audio=False): cookies = self.kwargs["cookies"] if audio: file_d = output(copy.copy(self.options), "m4a") else: file_d = output(self.options, self.options.other) if hasattr(file_d, "read") is False: return eta = ETA(len(files)) n = 1 for i in files: 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", i, cookies=cookies) if data.status_code == 404: break data = data.content file_d.write(data) if self.options.output != "-": file_d.close() if not self.options.silent: progress_stream.write('\n') self.finished = True svtplay-dl-1.9.6/lib/svtplay_dl/fetcher/hds.py000066400000000000000000000225361316004634700213410ustar00rootroot00000000000000# 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 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 else: 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 not res: return None if res.status_code >= 400: streams[0] = ServiceError("Can't read HDS playlist. {0}".format(res.status_code)) return streams data = res.text if is_py2 and isinstance(data, unicode): data = data.encode("utf-8") xml = ET.XML(data) 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: 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() if not self.options.silent: progress_stream.write('\n') self.finished = True 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-1.9.6/lib/svtplay_dl/fetcher/hls.py000066400000000000000000000120071316004634700213410ustar00rootroot00000000000000# 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.error import UIException, ServiceError from svtplay_dl.fetcher import VideoRetriever from svtplay_dl.utils import HTTP 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 if url[0] == '/': baseurl = re.search(r'^(http[s]{0,1}://[^/]+)/', srcurl) return "%s%s" % (baseurl.group(1), url) # remove everything after last / in the path of the URL baseurl = re.sub(r'^([^\?]+)/[^/]*(\?.*)?$', r'\1', srcurl) returl = "%s/%s" % (baseurl, url) return returl def hlsparse(options, res, url): streams = {} if not res: return None if res.status_code > 400: streams[0] = ServiceError("Can't read HLS playlist. {0}".format(res.status_code)) return streams files = (parsem3u(res.text))[1] http = HTTP(options) for i in files: try: bitrate = float(i[1]["BANDWIDTH"])/1000 except KeyError: streams[0] = ServiceError("Can't read HLS playlist") return streams urls = _get_full_url(i[0], url) res2 = http.get(urls, cookies=res.cookies) if res2.status_code < 400: streams[int(bitrate)] = HLS(copy.copy(options), urls, 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() if not self.options.silent: progress_stream.write('\n') self.finished = True 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:"): try: dur, title = l[8:].strip().split(",", 1) except: dur = l[8:].strip() title = None streaminfo['duration'] = dur streaminfo['title'] = title elif l[0] == '#': pass else: files.append((l, streaminfo)) streaminfo = {} return globdata, files svtplay-dl-1.9.6/lib/svtplay_dl/fetcher/http.py000066400000000000000000000022051316004634700215310ustar00rootroot00000000000000# ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- from __future__ import absolute_import from svtplay_dl.output import output, ETA, progressbar 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 eta = ETA(total_size) 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: eta.update(bytes_so_far) progressbar(total_size, bytes_so_far, ''.join(["ETA: ", str(eta)])) if self.options.output != "-": file_d.close() self.finished = True svtplay-dl-1.9.6/lib/svtplay_dl/fetcher/rtmp.py000066400000000000000000000025571316004634700215460ustar00rootroot00000000000000# 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) return self.finished = True svtplay-dl-1.9.6/lib/svtplay_dl/log.py000066400000000000000000000003361316004634700177160ustar00rootroot00000000000000# 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-1.9.6/lib/svtplay_dl/output.py000066400000000000000000000152221316004634700204750ustar00rootroot00000000000000# 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_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] - 40 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, extension="mp4", openfd=True, mode="wb", **kwargs): subtitlefiles = ["srt", "smi", "tt","sami", "wrst"] if is_py2: file_d = file else: file_d = io.IOBase if options.output != "-": ext = re.search(r"(\.\w{2,4})$", options.output) if not ext: options.output = "%s.%s" % (options.output, extension) if options.output_auto and ext: options.output = "%s.%s" % (options.output, extension) elif extension == "srt" and ext: options.output = "%s.srt" % options.output[:options.output.rfind(ext.group(1))] if ext and extension == "srt" and ext.group(1).split(".")[-1] in subtitlefiles: 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 extension in subtitlefiles: if not options.force_subtitle: log.error("File (%s) already exists. Use --force-subtitle to overwrite" % options.output) return None else: if not options.force: log.error("File (%s) already exists. Use --force to overwrite" % options.output) return None if openfd: file_d = open(options.output, mode, **kwargs) else: if openfd: if is_py2: file_d = sys.stdout else: file_d = sys.stdout.buffer return file_d def findexpisode(directory, service, name): subtitlefiles = ["srt", "smi", "tt","sami", "wrst"] match = re.search(r"-(\w+)-\w+.(\w{2,3})$", name) if not match: return False videoid = match.group(1) extension = 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 extension in subtitlefiles: if name.find(service) and match.group(1) == videoid and match.group(2) == extension: return True elif match.group(2) not in subtitlefiles and match.group(2) != "m4a": if name.find(service) and match.group(1) == videoid: return True return False svtplay-dl-1.9.6/lib/svtplay_dl/postprocess/000077500000000000000000000000001316004634700211455ustar00rootroot00000000000000svtplay-dl-1.9.6/lib/svtplay_dl/postprocess/__init__.py000066400000000000000000000205371316004634700232650ustar00rootroot00000000000000from json import dumps from random import sample import subprocess import os import platform from requests import post, codes, Timeout from svtplay_dl.log import log from svtplay_dl.utils import which, is_py3 class postprocess(object): def __init__(self, stream, options, subfixes = []): self.stream = stream self.merge_subtitle = options.merge_subtitle self.external_subtitle = options.subtitle self.get_all_subtitles = options.get_all_subtitles self.subfixes = subfixes self.detect = None for i in ["ffmpeg", "avconv"]: self.detect = which(i) if self.detect: break def sublanguage(self): # parse() function partly borrowed from a guy on github. /thanks! # https://github.com/riobard/srt.py/blob/master/srt.py def parse(self): def parse_block(block): lines = block.strip('-').split('\n') txt = '\r\n'.join(lines[2:]) return txt if platform.system() == "Windows" and is_py3: fd = open(self, encoding="utf8") else: fd = open(self) return list(map(parse_block, fd.read().strip().replace('\r', '').split('\n\n'))) def query(self): random_sentences = ' '.join(sample(parse(self), 8)).replace('\r\n', '') url = 'https://whatlanguage.herokuapp.com' payload = {"query": random_sentences} headers = {'content-type': 'application/json'} # Note: requests handles json from version 2.4.2 and onwards so i use json.dumps for now. try: r = post(url, data=dumps(payload), headers=headers, timeout=30) # Note: reasonable timeout i guess? svtplay-dl is mainly used while multitasking i presume, and it is heroku after all (fast enough) if r.status_code == codes.ok: try: response = r.json() return response['language'] except TypeError: return 'und' else: log.error("Server error appeared. Setting language as undetermined.") return 'und' except Timeout: log.error("30 seconds server timeout reached. Setting language as undetermined.") return 'und' langs = [] exceptions = { 'lulesamiska': 'smj', 'meankieli': 'fit', 'jiddisch': 'yid' } if len(self.subfixes) >= 2: log.info("Determining the languages of the subtitles.") else: log.info("Determining the language of the subtitle.") if self.get_all_subtitles: from re import match for subfix in self.subfixes: if [exceptions[key] for key in exceptions.keys() if match(key, subfix.strip('-'))]: if 'oversattning' in subfix.strip('-'): subfix = subfix.strip('-').split('.')[0] else: subfix = subfix.strip('-') langs += [exceptions[subfix]] continue subfile = "{0}.srt".format(os.path.splitext(self.stream.options.output)[0] + subfix) langs += [query(subfile)] else: subfile = "{0}.srt".format(os.path.splitext(self.stream.options.output)[0]) langs += [query(subfile)] if len(langs) >= 2: log.info("Language codes: " + ', '.join(langs)) else: log.info("Language code: " + langs[0]) return langs def remux(self): if self.detect is None: log.error("Cant detect ffmpeg or avconv. Cant mux files without it.") return if self.stream.finished is False: return if self.stream.options.output.endswith('.mp4') is False: orig_filename = self.stream.options.output name, ext = os.path.splitext(orig_filename) new_name = u"{0}.mp4".format(name) if self.merge_subtitle: log.info(u"Muxing {0} and merging its subtitle into {1}".format(orig_filename, new_name)) else: log.info(u"Muxing {0} into {1}".format(orig_filename, new_name)) tempfile = u"{0}.temp".format(orig_filename) arguments = ["-map", "0:v", "-map", "0:a", "-c", "copy", "-copyts", "-f", "mp4"] if ext == ".ts": arguments += ["-bsf:a", "aac_adtstoasc"] cmd = [self.detect, "-i", orig_filename] if self.merge_subtitle: langs = self.sublanguage() for stream_num, language in enumerate(langs): arguments += ["-map", str(stream_num + 1), "-c:s:" + str(stream_num), "mov_text", "-metadata:s:s:" + str(stream_num), "language=" + language] if len(self.subfixes) >= 2: for subfix in self.subfixes: subfile = "{0}.srt".format(name + subfix) cmd += ["-i", subfile] else: subfile = "{0}.srt".format(name) cmd += ["-i", subfile] arguments += ["-y", tempfile] cmd += arguments p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) stdout, stderr = p.communicate() if p.returncode != 0: stderr = stderr.decode('utf-8', 'replace') msg = stderr.strip().split('\n')[-1] log.error("Something went wrong: {0}".format(msg)) return if self.merge_subtitle and not self.external_subtitle: log.info("Muxing done, removing the old files.") if len(self.subfixes) >= 2: for subfix in self.subfixes: subfile = "{0}.srt".format(name + subfix) os.remove(subfile) else: os.remove(subfile) else: log.info("Muxing done, removing the old file.") os.remove(orig_filename) os.rename(tempfile, new_name) def merge(self): if self.detect is None: log.error("Cant detect ffmpeg or avconv. Cant mux files without it.") return if self.stream.finished is False: return orig_filename = self.stream.options.output if self.merge_subtitle: log.info("Merge audio, video and subtitle into {0}".format(orig_filename)) else: log.info("Merge audio and video into {0}".format(orig_filename)) tempfile = u"{0}.temp".format(orig_filename) name = os.path.splitext(orig_filename)[0] audio_filename = u"{0}.m4a".format(name) arguments = ["-c:v", "copy", "-c:a", "copy", "-f", "mp4"] cmd = [self.detect, "-i", orig_filename, "-i", audio_filename] if self.merge_subtitle: langs = self.sublanguage() for stream_num, language in enumerate(langs, start=2): arguments += ["-map", "0", "-map", "1", "-map", str(stream_num), "-c:s:" + str(stream_num - 2), "mov_text", "-metadata:s:s:" + str(stream_num - 2), "language=" + language] if len(self.subfixes) >= 2: for subfix in self.subfixes: subfile = "{0}.srt".format(name + subfix) cmd += ["-i", subfile] else: subfile = "{0}.srt".format(name) cmd += ["-i", subfile] arguments += ["-y", tempfile] cmd += arguments p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) stdout, stderr = p.communicate() if p.returncode != 0: stderr = stderr.decode('utf-8', 'replace') msg = stderr.strip().split('\n')[-1] log.error("Something went wrong: {0}".format(msg)) return log.info("Merging done, removing old files.") os.remove(orig_filename) os.remove(audio_filename) if self.merge_subtitle and not self.external_subtitle: if len(self.subfixes) >= 2: for subfix in self.subfixes: subfile = "{0}.srt".format(name + subfix) os.remove(subfile) else: os.remove(subfile) os.rename(tempfile, orig_filename) svtplay-dl-1.9.6/lib/svtplay_dl/service/000077500000000000000000000000001316004634700202215ustar00rootroot00000000000000svtplay-dl-1.9.6/lib/svtplay_dl/service/__init__.py000066400000000000000000000166671316004634700223520ustar00rootroot00000000000000# 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): if self.options.exclude: for i in self.options.exclude: if is_py2: i = i.decode("utf-8") if i in self.options.output: return True return False def exclude2(self, filename): if self.options.exclude: for i in self.options.exclude: if is_py2: i = i.decode("utf-8") if i in filename: 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(self.options, 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(self.options, 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(self.options, 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(self.options, 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(self.options, 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(self.options, 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(self.options, url) match = re.search('src="(http://mm-resource-service.herokuapp.com[^"]*)"', data) if match: url = match.group(1) for i in sites: if i.handles(url): return self.url, i(self.options, self.url) match = re.search(r'src="([^.]+\.solidtango.com[^"+]+)"', data) if match: url = match.group(1) for i in sites: if i.handles(url): return self.url, i(self.options, url) match = re.search('(lemonwhale|lwcdn.com)', data) if match: url = "http://lemonwhale.com" for i in sites: if i.handles(url): return self.url, i(self.options, self.url) match = re.search('s.src="(https://csp-ssl.picsearch.com[^"]+|http://csp.picsearch.com/rest[^"]+)', data) if match: url = match.group(1) for i in sites: if i.handles(url): return self.url, i(self.options, self.url) match = re.search('(picsearch_ajax_auth|screen9-ajax-auth)', data) if match: url = "http://csp.picsearch.com" for i in sites: if i.handles(url): return self.url, i(self.options, self.url) match = re.search('iframe src="(//csp.screen9.com[^"]+)"', data) if match: url = "http:%s" % match.group(1) for i in sites: if i.handles(url): return self.url, i(self.options, self.url) match = re.search('source src="([^"]+)" type="application/x-mpegURL"', data) if match: for i in sites: if i.__name__ == "Raw": return self.url, i(self.options, match.group(1)) 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-1.9.6/lib/svtplay_dl/service/aftonbladet.py000066400000000000000000000051571316004634700230660ustar00rootroot00000000000000# 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.utils.urllib import urlparse from svtplay_dl.error import ServiceError from svtplay_dl.fetcher.hls import hlsparse class Aftonbladettv(Service): supported_domains = ['tv.aftonbladet.se', "svd.se"] def get(self): data = self.get_urldata() if self.exclude(): yield ServiceError("Excluding video") return apiurl = None match = re.search('data-player-config="([^"]+)"', data) if not match: match = re.search('data-svpPlayer-video="([^"]+)"', data) if not match: yield ServiceError("Can't find video info") return data = json.loads(decode_html_entities(match.group(1))) videoId = data["playerOptions"]["id"] apiurl = data["playerOptions"]["api"] vendor = data["playerOptions"]["vendor"] self.options.live = data["live"] if not self.options.live: dataurl = "{0}{1}/assets/{2}?appName=svp-player".format(apiurl, vendor, videoId) data = self.http.request("get", dataurl).text data = json.loads(data) streams = hlsparse(self.options, self.http.request("get", data["streamUrls"]["hls"]), data["streamUrls"]["hls"]) if streams: for n in list(streams.keys()): yield streams[n] class Aftonbladet(Service): supported_domains = ["aftonbladet.se"] def get(self): data = self.get_urldata() if self.exclude(): yield ServiceError("Excluding video") return match = re.search('window.FLUX_STATE = ({.*})', data) if not match: yield ServiceError("Can't find video info") return janson = json.loads(match.group(1)) articleid = janson["article"]["currentArticleId"] components = janson["articles"][articleid]["article"]["components"] for i in components: if "components" in i: for n in i["components"]: if "type" in n and n["type"] == "video": streams = hlsparse(self.options, self.http.request("get", n["videoAsset"]["streamUrls"]["hls"]), n["videoAsset"]["streamUrls"]["hls"]) if streams: for n in list(streams.keys()): yield streams[n] svtplay-dl-1.9.6/lib/svtplay_dl/service/bambuser.py000066400000000000000000000024451316004634700224000ustar00rootroot00000000000000# 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(): 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-1.9.6/lib/svtplay_dl/service/bigbrother.py000066400000000000000000000055121316004634700227250ustar00rootroot00000000000000# 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(): 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-1.9.6/lib/svtplay_dl/service/cmore.py000066400000000000000000000126431316004634700217060ustar00rootroot00000000000000from __future__ import absolute_import import re import copy import os from svtplay_dl.service import Service from svtplay_dl.log import log from svtplay_dl.fetcher.dash import dashparse from svtplay_dl.subtitle import subtitle from svtplay_dl.utils import filenamify from svtplay_dl.utils.urllib import urljoin from svtplay_dl.error import ServiceError class Cmore(Service): supported_domains = ['www.cmore.se'] def get(self): if not self.options.username or not self.options.password: yield ServiceError("You need username and password to download things from this site.") return token, message = self._login() if not token: yield ServiceError(message) return res = self.http.get(self.url) match = re.search('data-asset-splash-section data-asset-id="([^"]+)"', res.text) if not match: yield ServiceError("Can't find video id") return url = "https://restapi.cmore.se/api/tve_web/asset/{0}/play.json?protocol=VUDASH".format(match.group(1)) res = self.http.get(url, headers={"authorization": "Bearer {0}".format(token)}) janson = res.json() if self.options.output_auto: directory = os.path.dirname(self.options.output) self.options.service = "cmore" basename = self._autoname(match.group(1)) if basename is None: yield ServiceError("Cant find vid id for autonaming") return title = "%s-%s-%s" % (basename, match.group(1), 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(): yield ServiceError("Excluding video") return if "drmProtected" in janson["playback"]: if janson["playback"]["drmProtected"]: yield ServiceError("DRM protected. Can't do anything") return if isinstance(janson["playback"]["items"]["item"], list): for i in janson["playback"]["items"]["item"]: if i["mediaFormat"] == "ism": streams = dashparse(self.options, self.http.request("get", i["url"]), i["url"]) if streams: for n in list(streams.keys()): yield streams[n] if i["mediaFormat"] == "webvtt": yield subtitle(copy.copy(self.options), "wrst", i["url"]) else: i = janson["playback"]["items"]["item"] if i["mediaFormat"] == "ism": streams = dashparse(self.options, self.http.request("get", i["url"]), i["url"]) if streams: for n in list(streams.keys()): yield streams[n] def _autoname(self, vid): url = "https://restapi.cmore.se/api/tve_web/asset/{0}.json?expand=metadata".format(vid) res = self.http.get(url) janson = res.json()["asset"]["metadata"] if isinstance(janson["title"], list): for i in janson["title"]: if i["@xml:lang"] == "sv_SE": # if we add other .tld, we might need to change this. name = i["$"] else: name = janson["title"]["$"] if "season" in janson: season = "{0:02d}".format(int(janson["season"]["$"])) name = "{0}.S{1}E{2:02d}".format(name, season, int(janson["episode"]["$"])) return name def find_all_episodes(self, options): episodes = [] token, message = self._login() if not token: log.error(message) return res = self.http.get(self.url) tags = re.findall(' 0: return sorted(episodes[-options.all_last:]) return sorted(episodes) def _login(self): url = "https://www.cmore.se/login" res = self.http.get(url, cookies=self.cookies) if self.options.cmoreoperator: post = {"username": self.options.username, "password": self.options.password, "operator": self.options.cmoreoperator, "country_code": "se"} else: match = re.search('authenticity_token" value="([^"]+)"', res.text) if not match: return None, "Can't find authenticity_token needed to login" post = {"username": self.options.username, "password": self.options.password, "authenticity_token": match.group(1), "redirect": "true"} res = self.http.post("https://account.cmore.se/session?client=web", json=post, cookies=self.cookies) if res.status_code >= 400: return None, "Wrong username or password" janson = res.json() token = janson["data"]["vimond_token"] return token, None def operatorlist(self): res = self.http.get("https://www.cmore.se/operator/login") res.encoding = "utf-8" match = re.findall('', res.text) for i in match: message = "operator: '{0}' value: '{1}'".format(i[1], i[0].replace("-", "")) print(message) svtplay-dl-1.9.6/lib/svtplay_dl/service/dbtv.py000066400000000000000000000025011316004634700215300ustar00rootroot00000000000000from __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(): 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-1.9.6/lib/svtplay_dl/service/disney.py000066400000000000000000000115621316004634700220730ustar00rootroot00000000000000# 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(): 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": res = self.http.get(i["url"]) match = re.search('button primary" href="([^"]+)"', res.text) if match: yield HTTP(copy.copy(self.options), match.group(1), 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 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 if self.exclude(): return 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-1.9.6/lib/svtplay_dl/service/dplay.py000066400000000000000000000164611316004634700217140ustar00rootroot00000000000000# 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, urlparse from svtplay_dl.error import ServiceError from svtplay_dl.utils import filenamify, is_py2 from svtplay_dl.log import log class Dplay(Service): supported_domains = ['dplay.se', 'dplay.dk', "dplay.no"] def get(self): data = self.get_urldata() premium = False parse = urlparse(self.url) domain = re.search(r"(dplay\.\w\w)", parse.netloc).group(1) match = re.search(r" 0: yield subtitle(copy.copy(self.options), "raw", suburl) data = self.http.request("get", "http://geo.%s/geo.js" % domain).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.%s/secure/api/v2/user/authorization/stream/%s?stream_type=hds" % (domain, vid), cookies=self.options.cookies) if data.status_code == 403 or data.status_code == 401: yield ServiceError("Geoblocked video") return 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.%s/secure/api/v2/user/authorization/stream/%s?stream_type=hls" % (domain, 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"] if is_py2: show = filenamify(show).encode("latin1") title = filenamify(title).encode("latin1") else: show = filenamify(show) title = filenamify(title) return filenamify("{0}.s{1:02d}e{2:02d}.{3}".format(show, int(season), int(episode), title)) def _login(self, options): parse = urlparse(self.url) domain = re.search(r"(dplay\.\w\w)", parse.netloc).group(1) data = self.http.request("get", "https://secure.%s/login/" % domain, 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.%s/secure/api/v1/user/auth/login" % domain, data=postdata, cookies=options.cookies) if data.status_code == 200: options.cookies = data.cookies return True else: return False def _country2lang(self): parse = urlparse(self.url) domain = re.search(r"dplay\.(\w\w)", parse.netloc).group(1) country = {"se": "sv", "no": "no", "dk": "da"} if domain and domain in country: return country[domain] else: return "sv" def _playable(self, dataj, premium): if dataj["content_info"]["package_label"]["value"] == "Premium" and not premium: return 1 if dataj["video_metadata_drmid_playready"] != "none": return 2 if dataj["video_metadata_drmid_flashaccess"] != "none": return 2 return 0 def find_all_episodes(self, options): data = self.get_urldata() parse = urlparse(self.url) domain = re.search(r"(dplay\.\w\w)", parse.netloc).group(1) 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.%s/api/v2/ajax/shows/%s/seasons/?items=9999999&sort=episode_number_desc&page=" % (domain, 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(i, 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(i, premium) if what == 0: episodes.append(i["url"]) if len(episodes) == 0: log.error("Cant find any playable files") if options.all_last > 0: return episodes[:options.all_last] return episodes svtplay-dl-1.9.6/lib/svtplay_dl/service/dr.py000066400000000000000000000141321316004634700212010ustar00rootroot00000000000000# 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 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 from svtplay_dl.utils.urllib import urlparse, urljoin from svtplay_dl.utils import is_py3 class Dr(Service, OpenGraphThumbMixin): supported_domains = ['dr.dk'] def get(self): data = self.get_urldata() if self.exclude(): 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_all_episodes(self, options): episodes = [] matches = re.findall(r'