1# -*- coding: utf-8 -*- 2 3# Copyright 2018-2022 Mike Fährmann 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License version 2 as 7# published by the Free Software Foundation. 8 9"""Downloader module for URLs requiring youtube-dl support""" 10 11from .common import DownloaderBase 12from .. import ytdl, text 13import os 14 15 16class YoutubeDLDownloader(DownloaderBase): 17 scheme = "ytdl" 18 19 def __init__(self, job): 20 DownloaderBase.__init__(self, job) 21 22 extractor = job.extractor 23 retries = self.config("retries", extractor._retries) 24 self.ytdl_opts = { 25 "retries": retries+1 if retries >= 0 else float("inf"), 26 "socket_timeout": self.config("timeout", extractor._timeout), 27 "nocheckcertificate": not self.config("verify", extractor._verify), 28 "proxy": self.proxies.get("http") if self.proxies else None, 29 } 30 31 self.ytdl_instance = None 32 self.forward_cookies = self.config("forward-cookies", False) 33 self.progress = self.config("progress", 3.0) 34 self.outtmpl = self.config("outtmpl") 35 36 def download(self, url, pathfmt): 37 kwdict = pathfmt.kwdict 38 39 ytdl_instance = kwdict.pop("_ytdl_instance", None) 40 if not ytdl_instance: 41 ytdl_instance = self.ytdl_instance 42 if not ytdl_instance: 43 try: 44 module = ytdl.import_module(self.config("module")) 45 except ImportError as exc: 46 self.log.error("Cannot import module '%s'", exc.name) 47 self.log.debug("", exc_info=True) 48 self.download = lambda u, p: False 49 return False 50 self.ytdl_instance = ytdl_instance = ytdl.construct_YoutubeDL( 51 module, self, self.ytdl_opts) 52 if self.outtmpl == "default": 53 self.outtmpl = module.DEFAULT_OUTTMPL 54 if self.forward_cookies: 55 set_cookie = ytdl_instance.cookiejar.set_cookie 56 for cookie in self.session.cookies: 57 set_cookie(cookie) 58 59 if self.progress is not None and not ytdl_instance._progress_hooks: 60 ytdl_instance.add_progress_hook(self._progress_hook) 61 62 info_dict = kwdict.pop("_ytdl_info_dict", None) 63 if not info_dict: 64 try: 65 info_dict = ytdl_instance.extract_info(url[5:], download=False) 66 except Exception: 67 return False 68 69 if "entries" in info_dict: 70 index = kwdict.get("_ytdl_index") 71 if index is None: 72 return self._download_playlist( 73 ytdl_instance, pathfmt, info_dict) 74 else: 75 info_dict = info_dict["entries"][index] 76 77 extra = kwdict.get("_ytdl_extra") 78 if extra: 79 info_dict.update(extra) 80 81 return self._download_video(ytdl_instance, pathfmt, info_dict) 82 83 def _download_video(self, ytdl_instance, pathfmt, info_dict): 84 if "url" in info_dict: 85 text.nameext_from_url(info_dict["url"], pathfmt.kwdict) 86 87 formats = info_dict.get("requested_formats") 88 if formats and not compatible_formats(formats): 89 info_dict["ext"] = "mkv" 90 91 if self.outtmpl: 92 self._set_outtmpl(ytdl_instance, self.outtmpl) 93 pathfmt.filename = filename = \ 94 ytdl_instance.prepare_filename(info_dict) 95 pathfmt.extension = info_dict["ext"] 96 pathfmt.path = pathfmt.directory + filename 97 pathfmt.realpath = pathfmt.temppath = ( 98 pathfmt.realdirectory + filename) 99 else: 100 pathfmt.set_extension(info_dict["ext"]) 101 102 if pathfmt.exists(): 103 pathfmt.temppath = "" 104 return True 105 if self.part and self.partdir: 106 pathfmt.temppath = os.path.join( 107 self.partdir, pathfmt.filename) 108 109 self._set_outtmpl(ytdl_instance, pathfmt.temppath.replace("%", "%%")) 110 111 self.out.start(pathfmt.path) 112 try: 113 ytdl_instance.process_info(info_dict) 114 except Exception: 115 self.log.debug("Traceback", exc_info=True) 116 return False 117 return True 118 119 def _download_playlist(self, ytdl_instance, pathfmt, info_dict): 120 pathfmt.set_extension("%(playlist_index)s.%(ext)s") 121 self._set_outtmpl(ytdl_instance, pathfmt.realpath) 122 123 for entry in info_dict["entries"]: 124 ytdl_instance.process_info(entry) 125 return True 126 127 def _progress_hook(self, info): 128 if info["status"] == "downloading" and \ 129 info["elapsed"] >= self.progress: 130 total = info.get("total_bytes") or info.get("total_bytes_estimate") 131 speed = info.get("speed") 132 self.out.progress( 133 None if total is None else int(total), 134 info["downloaded_bytes"], 135 int(speed) if speed else 0, 136 ) 137 138 @staticmethod 139 def _set_outtmpl(ytdl_instance, outtmpl): 140 try: 141 ytdl_instance.outtmpl_dict["default"] = outtmpl 142 except AttributeError: 143 ytdl_instance.params["outtmpl"] = outtmpl 144 145 146def compatible_formats(formats): 147 """Returns True if 'formats' are compatible for merge""" 148 video_ext = formats[0].get("ext") 149 audio_ext = formats[1].get("ext") 150 151 if video_ext == "webm" and audio_ext == "webm": 152 return True 153 154 exts = ("mp3", "mp4", "m4a", "m4p", "m4b", "m4r", "m4v", "ismv", "isma") 155 return video_ext in exts and audio_ext in exts 156 157 158__downloader__ = YoutubeDLDownloader 159