Test Failed
Push — test-coverage ( b95e5a )
by Konstantinos
02:34
created

YoutubeDownloader.download_times()   A

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nop 9
dl 0
loc 12
rs 9.8
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
#!/usr/bin/python3
2
3
import re
4
import abc
5
import subprocess
6
from time import sleep
7
8
9
class YoutubeDownloader:
10
11
    _cmd = 'youtube-dl --extract-audio --audio-quality 0 --audio-format mp3 -o "%(title)s.%(ext)s" "{url}"'
12
    _args = ['youtube-dl', '--extract-audio', '--audio-quality', '0', '--audio-format', 'mp3', '-o', '%(title)s.%(ext)s']
13
    _capture_stdout = {True: {'stdout': subprocess.PIPE},  # captures std out ie: print(str(ro.stdout, encoding='utf-8'))
14
                       False: {}}  # does not capture stdout and it streams it in the terminal}
15
16
    update_command_args = ('sudo', 'pip3', 'install', '--upgrade', 'youtube-dl')
17
    update_backend_command = ' '.join(update_command_args)
18
19
    @classmethod
20
    def download_times(cls, video_url, directory, times=10, spawn=True, verbose=True, supress_stdout=False, suppress_certificate_validation=False, delay=1):
21
        i = 0
22
        while i < times - 1:
23
            try:
24
                cls.download(video_url, directory, spawn=spawn, verbose=verbose, supress_stdout=supress_stdout, suppress_certificate_validation=suppress_certificate_validation)
25
                return
26
            except TooManyRequestsError as e:
27
                print(e)
28
                i += 1
29
                sleep(delay)
30
        cls.download(video_url, directory, spawn=spawn, verbose=verbose, supress_stdout=supress_stdout, suppress_certificate_validation=suppress_certificate_validation)
31
32
    @classmethod
33
    def download(cls, video_url, directory, spawn=True, verbose=True, supress_stdout=False, suppress_certificate_validation=False):
34
        """
35
        Downloads a video from youtube given a url, converts it to mp3 and stores in input directory.\n
36
        :param str video_url:
37
        :param str directory:
38
        :param bool spawn:
39
        :param bool verbose:
40
        :param bool supress_stdout:
41
        :param bool suppress_certificate_validation:
42
        :return:
43
        """
44
        cls.__download(video_url, directory, spawn=spawn, verbose=verbose, supress_stdout=supress_stdout, suppress_certificate_validation=suppress_certificate_validation)
45
46
    @classmethod
47
    def __download(cls, video_url, directory, **kwargs):
48
        args = cls._args[:-1] + ['{}/{}'.format(directory, cls._args[-1])] + [video_url]
49
        # If suppress HTTPS certificate validation
50
        if kwargs.get('suppress_certificate_validation', False):
51
            args.insert(1, '--no-check-certificate')
52
        if kwargs.get('verbose', False):
53
            print("Downloading stream '{}' and converting to mp3 ..".format(video_url))
54
        if kwargs.get('spawn', True):
55
            _ = subprocess.Popen(args, stderr=subprocess.STDOUT, **cls._capture_stdout[kwargs.get('supress_stdout', True)])
56
        else:
57
            ro = subprocess.run(args, stderr=subprocess.PIPE, **cls._capture_stdout[kwargs.get('supress_stdout', True)])
58
            if ro.returncode != 0:
59
                stderr = str(ro.stderr, encoding='utf-8')
60
                raise YoutubeDownloaderErrorFactory.create_from_stderr(stderr, video_url)
61
62
    @classmethod
63
    def update_backend(cls):
64
        ro = subprocess.run(cls.update_command_args, stderr=subprocess.PIPE, **cls._capture_stdout[False])
65
        return ro
66
67
    @classmethod
68
    def cmd(cls, video_url):
69
        return cls._cmd.format(url=video_url)
70
71
72
class YoutubeDownloaderErrorFactory:
73
    @staticmethod
74
    def create_with_message(msg):
75
        return Exception(msg)
76
77
    @staticmethod
78
    def create_from_stderr(stderror, video_url):
79
        exception_classes = (TokenParameterNotInVideoInfoError, InvalidUrlError, UnavailableVideoError, TooManyRequestsError, CertificateVerificationError)
80
        for subclass in exception_classes:
81
            if subclass.reg.search(stderror):
82
                return subclass(video_url, stderror)
83
        s = "NOTE: None of the predesinged exceptions' regexs [{}] matched. Perhaps you want to derive a new subclass from AbstractYoutubeDownloaderError to account for this youtube-dl exception with string to parse <S>{}</S>'".format(', '.join(['"{}"'.format(_.reg) for _ in exception_classes]), stderror)
84
        return Exception(AbstractYoutubeDownloaderError(video_url, stderror)._msg + '\n' + s)
85
86
87
#### EXCEPTIONS
88
89
class AbstractYoutubeDownloaderError(metaclass=abc.ABCMeta):
90
    def __init__(self, *args, **kwargs):
91
        super().__init__()
92
        if len(args) > 1:
93
            self.video_url = args[0]
94
            self.stderr = args[1]
95
        elif len(args) > 0:
96
            self.video_url = args[0]
97
        self._msg = "YoutubeDownloader generic error."
98
        self._short_msg = kwargs.get('msg')
99
        if args or 'msg' in kwargs:
100
            self._msg = '\n'.join([_ for _ in [kwargs.get('msg', ''), getattr(self, 'stderr', '')] if _])
101
102
103
class TokenParameterNotInVideoInfoError(Exception, AbstractYoutubeDownloaderError):
104
    """Token error"""
105
    reg = re.compile('"token" parameter not in video info for unknown reason')
106
107
    def __init__(self, video_url, stderror):
108
        AbstractYoutubeDownloaderError.__init__(self, video_url, stderror)
109
        Exception.__init__(self, self._msg)
110
111
class InvalidUrlError(Exception, AbstractYoutubeDownloaderError):
112
    """Invalid url error"""
113
    reg = re.compile(r'is not a valid URL\.')
114
115
    def __init__(self, video_url, stderror):
116
        AbstractYoutubeDownloaderError.__init__(self, video_url, stderror, msg="Invalid url '{}'.".format(video_url))
117
        Exception.__init__(self, self._short_msg)
118
119
class UnavailableVideoError(Exception, AbstractYoutubeDownloaderError):
120
    """Wrong url error"""
121
    reg = re.compile(r'ERROR: This video is unavailable\.')
122
123
    def __init__(self, video_url, stderror):
124
        AbstractYoutubeDownloaderError.__init__(self, video_url, stderror, msg="Unavailable video at '{}'.".format(video_url))
125
        Exception.__init__(self, self._msg)
126
127
class TooManyRequestsError(Exception, AbstractYoutubeDownloaderError):
128
    """Too many requests (for youtube) to serve"""
129
    reg = re.compile(r"(?:ERROR: Unable to download webpage: HTTP Error 429: Too Many Requests|WARNING: unable to download video info webpage: HTTP Error 429)")
130
131
    def __init__(self, video_url, stderror):
132
        AbstractYoutubeDownloaderError.__init__(self, video_url, stderror, msg="Too many requests for youtube at the moment.".format(video_url))
133
        Exception.__init__(self, self._msg)
134
135
class CertificateVerificationError(Exception, AbstractYoutubeDownloaderError):
136
    """This can happen when downloading is requested from a server like scrutinizer.io\n
137
    ERROR: Unable to download webpage: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get
138
    local issuer certificate (_ssl.c:1056)> (caused by URLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED]
139
    certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)')))
140
    """
141
    reg = re.compile(r"ERROR: Unable to download webpage: <urlopen error \[SSL: CERTIFICATE_VERIFY_FAILED\]")
142
143
    def __init__(self, video_url, stderror):
144
        AbstractYoutubeDownloaderError.__init__(self, video_url, stderror,
145
                                                msg="Unable to download webpage because ssl certificate verification failed:\n[SSL: CERTIFICATE_VERIFY_FAILED] certificate "
146
                                                    "verify failed: unable to get local issuer certificate (_ssl.c:1056)> (caused by "
147
                                                    "URLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: "
148
                                                    "unable to get local issuer certificate (_ssl.c:1056)')))")
149
        Exception.__init__(self, self._msg)
150