import argparse import logging import os import platform from yaml import safe_load configdata = None if platform.system() == "Windows": APPDATA = os.environ["APPDATA"] CONFIGFILE = os.path.join(APPDATA, "svtplay-dl", "svtplay-dl.yaml") else: CONFIGFILE = os.path.expanduser("~/.svtplay-dl.yaml") FILENAME = "{title}.s{season}e{episode}.{episodename}-{id}-{service}.{ext}" class Options: """ 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.default = {} def set(self, key, value): self.default[key] = value def get(self, key): if key in self.default: return self.default[key] return None def get_variable(self): return self.default def set_variable(self, value): self.default = value def gen_parser(version="unknown"): parser = argparse.ArgumentParser(prog="svtplay-dl") general = parser.add_argument_group() general.add_argument("--version", action="version", version=f"%(prog)s {version}") general.add_argument("-o", "--output", metavar="output", default=None, help="outputs to the given filename or folder") general.add_argument("--filename", metavar="filename", default=FILENAME, help=f"filename format {FILENAME}") general.add_argument( "--subfolder", action="store_true", default=False, help="Create a subfolder titled as the show, non-series gets in folder movies", ) general.add_argument("--config", dest="configfile", metavar="configfile", default=CONFIGFILE, help="Specify configuration file") general.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="overwrite if file exists already") general.add_argument("-r", "--resume", action="store_true", dest="resume", default=False, help="resume a download (RTMP obsolete)") general.add_argument("-l", "--live", action="store_true", dest="live", default=False, help="enable for live streams (RTMP based ones)") general.add_argument("-c", "--capture_time", default=-1, type=int, metavar="capture_time", help="define capture time in minutes of a live stream") general.add_argument("-s", "--silent", action="store_true", dest="silent", default=False, help="be less verbose") general.add_argument( "--silent-semi", action="store_true", dest="silent_semi", default=False, help="only show a message when the file is downloaded", ) general.add_argument("-u", "--username", default=None, help="username") general.add_argument("-p", "--password", default=None, help="password") general.add_argument("--token", default=None, help="token") general.add_argument( "-t", "--thumbnail", action="store_true", dest="thumbnail", default=False, help="download thumbnail from the site if available", ) general.add_argument( "-g", "--get-url", action="store_true", dest="get_url", default=False, help="do not download any video, but instead print the URL.", ) general.add_argument( "--get-only-episode-url", action="store_true", dest="get_only_episode_url", default=False, help="do not get video URLs, only print the episode URL.", ) general.add_argument( "--dont-verify-ssl-cert", action="store_false", dest="ssl_verify", default=True, help="Don't attempt to verify SSL certificates.", ) general.add_argument( "--http-header", dest="http_headers", default=None, metavar="header1=value;header2=value2", help="A header to add to each HTTP request.", ) general.add_argument( "--cookies", dest="cookies", default=None, metavar="cookie1=value;cookie2=value2", help="A cookies to add to each HTTP request.", ) general.add_argument( "--exclude", dest="exclude", default=None, metavar="WORD1,WORD2,...", help="exclude videos with the WORD(s) in the filename. comma separated.", ) general.add_argument("--after-date", dest="after_date", default=None, metavar="yyyy-MM-dd", help="only videos published on or after this date") general.add_argument( "--proxy", dest="proxy", default=None, metavar="proxy", help="Use the specified HTTP/HTTPS/SOCKS proxy. To enable experimental " "SOCKS proxy, specify a proper scheme. For example " "socks5://127.0.0.1:1080/.", ) general.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="explain what is going on") general.add_argument("--nfo", action="store_true", dest="nfo", default=False, help="create a NFO file") general.add_argument("--force-nfo", action="store_true", dest="force_nfo", default=False, help="download only NFO if used with --nfo") general.add_argument( "--only-audio", action="store_true", dest="only_audio", default=False, help="only download audio if audio and video is seperated", ) general.add_argument( "--only-video", action="store_true", dest="only_video", default=False, help="only download video if audio and video is seperated", ) quality = parser.add_argument_group("Quality") quality.add_argument( "-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", ) quality.add_argument( "-Q", "--flexible-quality", default=0, metavar="amount", dest="flexibleq", help="allow given quality (as above) to differ by an amount", ) quality.add_argument("-P", "--preferred", default=None, metavar="preferred", help="preferred download method (dash, hls, or http)") quality.add_argument("--list-quality", dest="list_quality", action="store_true", default=False, help="list the quality for a video") quality.add_argument( "--stream-priority", dest="stream_prio", default=None, metavar="dash,hls,http", help="If two streams have the same quality, choose the one you prefer", ) quality.add_argument( "--format-preferred", dest="format_preferred", default=None, metavar="h264,h264-51", help="Choose the format you prefer, --list-quality to show which one to choose from", ) quality.add_argument( "--audio-language", dest="audio_language", default=None, help="Choose the language of the audio (it can override the default one), --list-quality to show which one to choose from", ) quality.add_argument( "--audio-role", dest="audio_role", default=None, help="Choose the role of the audio (it can override the default one), --list-quality to show which one to choose from", ) quality.add_argument( "--resolution", dest="resolution", default=None, help="Choose what video resolution to download e.g. 480,720,1080. comma seperated", ) subtitle = parser.add_argument_group("Subtitle") subtitle.add_argument( "-S", "--subtitle", action="store_true", dest="subtitle", default=False, help="download subtitle from the site if available. both merged and separately stored if used with -M", ) subtitle.add_argument( "-M", "--merge-subtitle", action="store_true", dest="merge_subtitle", default=False, help="merge subtitle with video/audio file with corresponding ISO639-3 language code. also saved separately if used with -S", ) subtitle.add_argument( "--force-subtitle", dest="force_subtitle", default=False, action="store_true", help="download only subtitle if its used with -S", ) subtitle.add_argument( "--require-subtitle", dest="require_subtitle", default=False, action="store_true", help="download only if a subtitle is available", ) subtitle.add_argument( "--all-subtitles", dest="get_all_subtitles", default=False, action="store_true", help="Download all available subtitles for the video", ) subtitle.add_argument( "--raw-subtitles", dest="get_raw_subtitles", default=False, action="store_true", help="also download the subtitles in their native format", ) subtitle.add_argument( "--convert-subtitle-colors", dest="convert_subtitle_colors", default=False, action="store_true", help='converts the color information in subtitles, to tags', ) alleps = parser.add_argument_group("All") alleps.add_argument("-A", "--all-episodes", action="store_true", dest="all_episodes", default=False, help="try to download all episodes") alleps.add_argument("--all-last", dest="all_last", default=-1, type=int, metavar="NN", help="get last NN episodes instead of all episodes") alleps.add_argument("--include-clips", dest="include_clips", default=False, action="store_true", help="include clips from websites when using -A") cmorep = parser.add_argument_group("C More") cmorep.add_argument("--cmore-operatorlist", dest="cmoreoperatorlist", default=False, action="store_true", help="show operatorlist for cmore") cmorep.add_argument("--cmore-operator", dest="cmoreoperator", default=None, metavar="operator") postprocessing = parser.add_argument_group("Post-processing") postprocessing.add_argument("--no-remux", dest="no_remux", default=False, action="store_true", help="Do not automatically remux to mp4") postprocessing.add_argument( "--no-merge", dest="no_merge", default=False, action="store_true", help="Do not automatically merge video, audio and possibly also subtitle(s) together", ) postprocessing.add_argument("--no-postprocess", dest="no_postprocess", default=False, action="store_true", help="Do not postprocess anything") postprocessing.add_argument( "--keep-original", dest="keep_original", default=False, action="store_true", help="Do postprocessing while also keeping original files", ) postprocessing.add_argument( "--output-format", dest="output_format", default="mp4", choices=["mp4", "mkv"], help="format you want resulting file in (mkv or mp4), mp4 is default", ) parser.add_argument("urls", nargs="*") return parser def parser(version): parser = gen_parser(version) options = parser.parse_args() return parser, options def setup_defaults(): options = Options() options.set("output", None) options.set("subfolder", False) options.set("configfile", CONFIGFILE) options.set("resume", False) options.set("live", False) options.set("capture_time", -1) options.set("silent", False) options.set("force", False) options.set("quality", 0) options.set("flexibleq", 0) options.set("list_quality", False) options.set("other", None) options.set("subtitle", False) options.set("username", None) options.set("password", None) options.set("thumbnail", False) options.set("all_episodes", False) options.set("all_last", -1) options.set("merge_subtitle", False) options.set("force_subtitle", False) options.set("require_subtitle", False) options.set("get_all_subtitles", False) options.set("get_raw_subtitles", False) options.set("convert_subtitle_colors", False) options.set("preferred", None) options.set("verbose", False) options.set("nfo", False) options.set("force_nfo", False) options.set("output_auto", False) options.set("service", None) options.set("cookies", None) options.set("exclude", None) options.set("after_date", None) options.set("get_url", False) options.set("get_only_episode_url", False) options.set("ssl_verify", True) options.set("http_headers", None) options.set("format_preferred", None) options.set("audio_language", None) options.set("audio_role", None) options.set("resolution", None) options.set("stream_prio", None) options.set("no_remux", False) options.set("no_merge", False) options.set("no_postprocess", False) options.set("keep_original", False) options.set("silent_semi", False) options.set("proxy", None) options.set("include_clips", False) options.set("cmoreoperatorlist", False) options.set("filename", FILENAME) options.set("only_audio", False) options.set("only_video", False) options.set("output_format", "mp4") options.set("get_all_subtitles", False) options.set("token", None) return _special_settings(options) def parsertoconfig(config, parser): config.set("output", parser.output) config.set("filename", parser.filename) config.set("subfolder", parser.subfolder) config.set("configfile", parser.configfile) config.set("resume", parser.resume) config.set("live", parser.live) config.set("capture_time", parser.capture_time) config.set("silent", parser.silent) config.set("force", parser.force) config.set("quality", parser.quality) config.set("flexibleq", parser.flexibleq) config.set("list_quality", parser.list_quality) config.set("subtitle", parser.subtitle) config.set("merge_subtitle", parser.merge_subtitle) config.set("silent_semi", parser.silent_semi) config.set("username", parser.username) config.set("password", parser.password) config.set("thumbnail", parser.thumbnail) config.set("all_episodes", parser.all_episodes) config.set("all_last", parser.all_last) config.set("force_subtitle", parser.force_subtitle) config.set("require_subtitle", parser.require_subtitle) config.set("preferred", parser.preferred) config.set("verbose", parser.verbose) config.set("nfo", parser.nfo) config.set("force_nfo", parser.force_nfo) config.set("exclude", parser.exclude) config.set("after_date", parser.after_date) config.set("get_url", parser.get_url) config.set("get_only_episode_url", parser.get_only_episode_url) config.set("ssl_verify", parser.ssl_verify) config.set("http_headers", parser.http_headers) config.set("cookies", parser.cookies) config.set("format_preferred", parser.format_preferred) config.set("audio_role", parser.audio_role) config.set("resolution", parser.resolution) config.set("audio_language", parser.audio_language) config.set("stream_prio", parser.stream_prio) config.set("no_remux", parser.no_remux) config.set("no_merge", parser.no_merge) config.set("no_postprocess", parser.no_postprocess) config.set("keep_original", parser.keep_original) config.set("get_all_subtitles", parser.get_all_subtitles) config.set("get_raw_subtitles", parser.get_raw_subtitles) config.set("convert_subtitle_colors", parser.convert_subtitle_colors) config.set("include_clips", parser.include_clips) config.set("cmoreoperatorlist", parser.cmoreoperatorlist) config.set("cmoreoperator", parser.cmoreoperator) config.set("proxy", parser.proxy) config.set("only_audio", parser.only_audio) config.set("only_video", parser.only_video) config.set("output_format", parser.output_format) config.set("token", parser.token) return _special_settings(config) def _special_settings(config): if config.get("require_subtitle"): if config.get("merge_subtitle"): config.set("merge_subtitle", True) else: config.set("subtitle", True) if config.get("silent_semi"): config.set("silent", True) if config.get("proxy"): config.set("proxy", config.get("proxy").replace("socks5", "socks5h", 1)) config.set("proxy", dict(http=config.get("proxy"), https=config.get("proxy"))) if config.get("get_only_episode_url"): config.set("get_url", True) return config def merge(old, new): if isinstance(new, list): new = {list(i.keys())[0]: i[list(i.keys())[0]] for i in new} config = setup_defaults() if new: for item in new: if item in new: if new[item] != config.get(item): # Check if new value is not a default one. old[item] = new[item] else: old[item] = new[item] options = Options() options.set_variable(old) return options def readconfig(config, configfile, service=None, preset=None): global configdata if configfile and configdata is None: try: with open(configfile) as fd: data = fd.read() configdata = safe_load(data) except PermissionError: logging.error("Permission denied while reading config: %s", configfile) if configdata is None: return config # migrate old service name to new old_name_in_config = False if "service" in configdata and "dplay" in configdata["service"]: old_name_in_config = True logging.warning("'dplay' have been renamed to 'discoveryplus'") configdata["service"]["discoveryplus"] = configdata["service"].pop("dplay") if "service" in configdata and "viaplay" in configdata["service"]: old_name_in_config = True logging.warning("'viaplay' have been renamed to 'viafree'") configdata["service"]["viafree"] = configdata["service"].pop("viaplay") if old_name_in_config: logging.warning("Old service names still work at the moment. To avoid the warnings you need to rename the service(s) to the new name(s)") if "default" in configdata: config = merge(config.get_variable(), configdata["default"]) if service and "service" in configdata and service in configdata["service"]: config = merge(config.get_variable(), configdata["service"][service]) if preset and "presets" in configdata and preset in configdata["presets"]: config = merge(config.get_variable(), configdata["presets"][preset]) return _special_settings(config)