From 0423265d32392ef313c19fa5584c7680e1407fe7 Mon Sep 17 00:00:00 2001 From: Prasun Biswas Date: Mon, 10 Jan 2022 16:38:32 +0530 Subject: [PATCH] Add files via upload hotfix for pafy (fix for https://github.com/mps-youtube/pafy/issues/306) --- hotfix/backend_youtube_dl.py | 206 +++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 hotfix/backend_youtube_dl.py diff --git a/hotfix/backend_youtube_dl.py b/hotfix/backend_youtube_dl.py new file mode 100644 index 0000000..cce89cd --- /dev/null +++ b/hotfix/backend_youtube_dl.py @@ -0,0 +1,206 @@ +import sys +import time +import logging +import os +import subprocess + +if sys.version_info[:2] >= (3, 0): + # pylint: disable=E0611,F0401,I0011 + uni = str +else: + uni = unicode + +import youtube_dl + +from . import g +from .backend_shared import BasePafy, BaseStream, remux, get_status_string, get_size_done + +dbg = logging.debug + + +early_py_version = sys.version_info[:2] < (2, 7) + + +class YtdlPafy(BasePafy): + def __init__(self, *args, **kwargs): + self._ydl_info = None + self._ydl_opts = g.def_ydl_opts + ydl_opts = kwargs.get("ydl_opts") + if ydl_opts: + self._ydl_opts.update(ydl_opts) + super(YtdlPafy, self).__init__(*args, **kwargs) + + def _fetch_basic(self): + """ Fetch basic data and streams. """ + if self._have_basic: + return + + with youtube_dl.YoutubeDL(self._ydl_opts) as ydl: + try: + self._ydl_info = ydl.extract_info(self.videoid, download=False) + # Turn into an IOError since that is what pafy previously raised + except youtube_dl.utils.DownloadError as e: + raise IOError(str(e).replace('YouTube said', 'Youtube says')) + + if self.callback: + self.callback("Fetched video info") + + self._title = self._ydl_info['title'] + self._author = self._ydl_info['uploader'] + self._rating = self._ydl_info['average_rating'] + self._length = self._ydl_info['duration'] + self._viewcount = self._ydl_info['view_count'] + self._likes = self._ydl_info.get('like_count', 0) + self._dislikes = self._ydl_info.get('dislike_count', 0) + self._username = self._ydl_info['uploader_id'] + self._category = self._ydl_info['categories'][0] if self._ydl_info['categories'] else '' + self._bestthumb = self._ydl_info['thumbnails'][0]['url'] + self._bigthumb = g.urls['bigthumb'] % self.videoid + self._bigthumbhd = g.urls['bigthumbhd'] % self.videoid + self.expiry = time.time() + g.lifespan + + self._have_basic = True + + def _fetch_gdata(self): + """ Extract gdata values, fetch gdata if necessary. """ + if self._have_gdata: + return + + item = self._get_video_gdata(self.videoid)['items'][0] + snippet = item['snippet'] + self._published = uni(snippet['publishedAt']) + self._description = uni(snippet["description"]) + # Note: using snippet.get since some videos have no tags object + self._keywords = [uni(i) for i in snippet.get('tags', ())] + self._have_gdata = True + + def _process_streams(self): + """ Create Stream object lists from internal stream maps. """ + + if not self._have_basic: + self._fetch_basic() + + allstreams = [YtdlStream(z, self) for z in self._ydl_info['formats']] + self._streams = [i for i in allstreams if i.mediatype == 'normal'] + self._audiostreams = [i for i in allstreams if i.mediatype == 'audio'] + self._videostreams = [i for i in allstreams if i.mediatype == 'video'] + self._m4astreams = [i for i in allstreams if i.extension == 'm4a'] + self._oggstreams = [i for i in allstreams if i.extension == 'ogg'] + self._allstreams = allstreams + + +class YtdlStream(BaseStream): + def __init__(self, info, parent): + super(YtdlStream, self).__init__(parent) + self._itag = info['format_id'] + + if (info.get('acodec') != 'none' and + info.get('vcodec') == 'none'): + self._mediatype = 'audio' + elif (info.get('acodec') == 'none' and + info.get('vcodec') != 'none'): + self._mediatype = 'video' + else: + self._mediatype = 'normal' + + self._threed = info.get('format_note') == '3D' + self._rawbitrate = info.get('abr', 0) * 1024 + + height = info.get('height') or 0 + width = info.get('width') or 0 + self._resolution = str(width) + 'x' + str(height) + self._dimensions = width, height + self._bitrate = str(info.get('abr', 0)) + 'k' + self._quality = self._bitrate if self._mediatype == 'audio' else self._resolution + + self._extension = info['ext'] + self._notes = info.get('format_note') or '' + self._url = info.get('url') + + self._info = info + + def get_filesize(self): + """ Return filesize of the stream in bytes. Set member variable. """ + + # Faster method + if 'filesize' in self._info and self._info['filesize'] is not None: + return self._info['filesize'] + + # Fallback + return super(YtdlStream, self).get_filesize() + + def download(self, filepath="", quiet=False, progress="Bytes", + callback=None, meta=False, remux_audio=False): + + downloader = youtube_dl.downloader.http.HttpFD(ydl(), + {'http_chunk_size': 10485760}) + + progress_available = ["KB", "MB", "GB"] + if progress not in progress_available: + progress = "Bytes" + + status_string = get_status_string(progress) + + def progress_hook(s): + if s['status'] == 'downloading': + bytesdone = s['downloaded_bytes'] + total = s['total_bytes'] + if s.get('speed') is not None: + rate = s['speed'] / 1024 + else: + rate = 0 + if s.get('eta') is None: + eta = 0 + else: + eta = s['eta'] + + progress_stats = (get_size_done(bytesdone, progress), + bytesdone*1.0/total, rate, eta) + if not quiet: + status = status_string.format(*progress_stats) + sys.stdout.write("\r" + status + ' ' * 4 + "\r") + sys.stdout.flush() + + if callback: + callback(total, *progress_stats) + + downloader._progress_hooks = [progress_hook] + + if filepath and os.path.isdir(filepath): + filename = self.generate_filename(max_length=256 - len('.temp')) + filepath = os.path.join(filepath, filename) + + elif filepath: + pass + + else: + filepath = self.generate_filename(meta=meta, max_length=256 - len('.temp')) + + infodict = {'url': self.url} + + downloader.download(filepath, infodict) + print() + + if remux_audio and self.mediatype == "audio": + subprocess.run(['mv', filepath, filepath + '.temp']) + remux(filepath + '.temp', filepath, quiet=quiet, muxer=remux_audio) + + +class ydl: + def urlopen(self, url): + return g.opener.open(url) + + def to_screen(self, *args, **kwargs): + pass + + def to_console_title(self, *args, **kwargs): + pass + + def trouble(self, *args, **kwargs): + pass + + def report_warning(self, *args, **kwargs): + pass + + def report_error(self, *args, **kwargs): + pass