mirror of
https://github.com/spaam/svtplay-dl.git
synced 2024-11-24 12:15:40 +01:00
56e07188ee
Added command to download all available subtitles for a video (--all-subtitles) Added support to print all the subtitle urls when the get url parameter is used Fixed so subtitle url and stream url get printed if -S and -g but not --force-subtitles parameter is used Added support for downloading all subtitles and auto subfix them with language name for Urplay and Urskola (even when just one subtitle is downloaded)
416 lines
15 KiB
Python
416 lines
15 KiB
Python
# 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.postprocess import postprocess
|
|
|
|
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__ = "1.0"
|
|
|
|
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
|
|
self.remux = False
|
|
self.get_all_subtitles = False
|
|
|
|
|
|
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:
|
|
get_all_episodes(stream, options, url)
|
|
else:
|
|
get_one_media(stream, 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))
|
|
|
|
# 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
|
|
|
|
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
|
|
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 options.get_all_subtitles:
|
|
for sub in subs[:]:
|
|
print(sub.url)
|
|
else:
|
|
print(subs[0].url)
|
|
if options.force_subtitle:
|
|
return
|
|
|
|
if options.subtitle and options.output != "-" and not options.get_url:
|
|
if subs:
|
|
if options.get_all_subtitles:
|
|
for sub in subs[:]:
|
|
sub.download()
|
|
else:
|
|
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
|
|
try:
|
|
stream = select_quality(options, videos)
|
|
log.info("Selected to download %s, bitrate: %s",
|
|
stream.name(), stream.bitrate)
|
|
if options.get_url:
|
|
print(stream.url)
|
|
return
|
|
stream.download()
|
|
except UIException as e:
|
|
if options.verbose:
|
|
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)
|
|
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()
|
|
|
|
|
|
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="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("--all-subtitles", dest="get_all_subtitles", default=False, action="store_true",
|
|
help="Download all available subtitles for the video")
|
|
(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
|
|
options.remux = parser.remux
|
|
options.get_all_subtitles = parser.get_all_subtitles
|
|
return options
|