├── GUIApp ├── __init__.py ├── start.py ├── Core.py └── GUI.py ├── .gitignore ├── requirements.txt ├── script ├── clint │ ├── packages │ │ ├── __init__.py │ │ ├── __init__.pyc │ │ ├── colorama │ │ │ ├── ansi.pyc │ │ │ ├── win32.pyc │ │ │ ├── __init__.pyc │ │ │ ├── winterm.pyc │ │ │ ├── ansitowin32.pyc │ │ │ ├── initialise.pyc │ │ │ ├── __pycache__ │ │ │ │ ├── ansi.cpython-37.pyc │ │ │ │ ├── win32.cpython-37.pyc │ │ │ │ ├── __init__.cpython-37.pyc │ │ │ │ ├── winterm.cpython-37.pyc │ │ │ │ ├── initialise.cpython-37.pyc │ │ │ │ └── ansitowin32.cpython-37.pyc │ │ │ ├── __init__.py │ │ │ ├── ansi.py │ │ │ ├── initialise.py │ │ │ ├── winterm.py │ │ │ ├── win32.py │ │ │ └── ansitowin32.py │ │ ├── __pycache__ │ │ │ └── __init__.cpython-37.pyc │ │ ├── ordereddict.py │ │ └── appdirs.py │ ├── pipes.pyc │ ├── utils.pyc │ ├── __init__.pyc │ ├── arguments.pyc │ ├── textui │ │ ├── cols.pyc │ │ ├── core.pyc │ │ ├── colored.pyc │ │ ├── prompt.pyc │ │ ├── __init__.pyc │ │ ├── progress.pyc │ │ ├── formatters.pyc │ │ ├── validators.pyc │ │ ├── __pycache__ │ │ │ ├── cols.cpython-37.pyc │ │ │ ├── core.cpython-37.pyc │ │ │ ├── prompt.cpython-37.pyc │ │ │ ├── __init__.cpython-37.pyc │ │ │ ├── colored.cpython-37.pyc │ │ │ ├── progress.cpython-37.pyc │ │ │ ├── formatters.cpython-37.pyc │ │ │ └── validators.cpython-37.pyc │ │ ├── __init__.py │ │ ├── core.py │ │ ├── validators.py │ │ ├── cols.py │ │ ├── formatters.py │ │ ├── colored.py │ │ ├── progress.py │ │ └── prompt.py │ ├── __pycache__ │ │ ├── pipes.cpython-37.pyc │ │ ├── utils.cpython-37.pyc │ │ ├── __init__.cpython-37.pyc │ │ └── arguments.cpython-37.pyc │ ├── pipes.py │ ├── __init__.py │ ├── eng.py │ ├── utils.py │ ├── resources.py │ └── arguments.py ├── video_ID.txt └── wistia-downloader.py └── README.md /GUIApp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.20.0 -------------------------------------------------------------------------------- /script/clint/packages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/clint/pipes.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/pipes.pyc -------------------------------------------------------------------------------- /script/clint/utils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/utils.pyc -------------------------------------------------------------------------------- /script/clint/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/__init__.pyc -------------------------------------------------------------------------------- /script/clint/arguments.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/arguments.pyc -------------------------------------------------------------------------------- /script/clint/textui/cols.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/cols.pyc -------------------------------------------------------------------------------- /script/clint/textui/core.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/core.pyc -------------------------------------------------------------------------------- /script/clint/textui/colored.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/colored.pyc -------------------------------------------------------------------------------- /script/clint/textui/prompt.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/prompt.pyc -------------------------------------------------------------------------------- /script/clint/textui/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__init__.pyc -------------------------------------------------------------------------------- /script/clint/textui/progress.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/progress.pyc -------------------------------------------------------------------------------- /script/clint/packages/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/__init__.pyc -------------------------------------------------------------------------------- /script/clint/textui/formatters.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/formatters.pyc -------------------------------------------------------------------------------- /script/clint/textui/validators.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/validators.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/ansi.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/ansi.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/win32.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/win32.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__init__.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/winterm.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/winterm.pyc -------------------------------------------------------------------------------- /script/clint/__pycache__/pipes.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/__pycache__/pipes.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/ansitowin32.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/ansitowin32.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/initialise.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/initialise.pyc -------------------------------------------------------------------------------- /script/clint/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/__pycache__/arguments.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/__pycache__/arguments.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/cols.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/cols.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/core.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/core.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/prompt.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/prompt.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/colored.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/colored.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/progress.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/progress.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/formatters.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/formatters.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__pycache__/validators.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/textui/__pycache__/validators.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__pycache__/ansi.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__pycache__/ansi.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__pycache__/win32.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__pycache__/win32.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__pycache__/winterm.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__pycache__/winterm.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__pycache__/initialise.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__pycache__/initialise.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/packages/colorama/__init__.py: -------------------------------------------------------------------------------- 1 | from .initialise import init, deinit, reinit 2 | from .ansi import Fore, Back, Style 3 | from .ansitowin32 import AnsiToWin32 4 | 5 | VERSION = '0.2.3' 6 | 7 | -------------------------------------------------------------------------------- /script/clint/packages/colorama/__pycache__/ansitowin32.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abdlalisalmi/wistia-downloader/HEAD/script/clint/packages/colorama/__pycache__/ansitowin32.cpython-37.pyc -------------------------------------------------------------------------------- /script/clint/textui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.textui 5 | ~~~~~~~~~~~~ 6 | 7 | This module provides the text output helper system. 8 | 9 | """ 10 | import sys 11 | if sys.platform.startswith('win'): 12 | from ..packages import colorama 13 | colorama.init() 14 | 15 | from . import colored 16 | from . import progress 17 | from . import prompt 18 | 19 | from .core import * 20 | -------------------------------------------------------------------------------- /script/video_ID.txt: -------------------------------------------------------------------------------- 1 | cu1xdspoew 2 | 5ychx6uvem 3 | c058eu144c 4 | 9b5h01hael 5 | 80jwnnilbk 6 | q6ur327j9o 7 | 2m610i0pi4 8 | dugj21e0ub 9 | v7wskq1or2 10 | y3yqfdhm2p 11 | x6xvzynwiv 12 | g9l1lqcd5p 13 | ff2vtb1560 14 | wwrldhcphl 15 | w8t63itcrb 16 | z1qopxalfe 17 | jolnkppgci 18 | 1iskt6rv42 19 | mi4kdl5c6h 20 | o6elplsyi9 21 | oysv9zc86p 22 | 9sy6jb1le5 23 | k4ivtqj9ff 24 | 32dejxsq2q 25 | kyu6g7ns0q 26 | crlhoei8pq -------------------------------------------------------------------------------- /GUIApp/start.py: -------------------------------------------------------------------------------- 1 | from GUI import WistiaDownloaderGUI 2 | from tkinter import * 3 | 4 | 5 | App = Tk() 6 | 7 | # screen_width = int(root.winfo_screenwidth() * 0.5) 8 | # screen_height = int(root.winfo_screenheight() * 0.65) 9 | 10 | screen_width = 720 11 | screen_height = 510 12 | 13 | App.title('Wistia Downloader') 14 | App.geometry("{}x{}".format(screen_width, screen_height)) 15 | App.resizable(False, False) 16 | 17 | GUI = WistiaDownloaderGUI(App, screen_width, screen_height) 18 | 19 | App.mainloop() 20 | -------------------------------------------------------------------------------- /script/clint/pipes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.pipes 5 | ~~~~~~~~~~~ 6 | 7 | This module contains the helper functions for dealing with unix pipes. 8 | 9 | """ 10 | 11 | from __future__ import absolute_import 12 | from __future__ import with_statement 13 | 14 | import sys 15 | 16 | 17 | __all__ = ('piped_in', ) 18 | 19 | 20 | 21 | def piped_in(): 22 | """Returns piped input via stdin, else None.""" 23 | with sys.stdin as stdin: 24 | # TTY is only way to detect if stdin contains data 25 | if not stdin.isatty(): 26 | return stdin.read() 27 | else: 28 | return None 29 | -------------------------------------------------------------------------------- /script/clint/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint 5 | ~~~~~ 6 | 7 | This module sets up the main interface for all of clint. 8 | 9 | """ 10 | 11 | 12 | from __future__ import absolute_import 13 | 14 | try: 15 | from collections import OrderedDict 16 | except ImportError: 17 | from .packages.ordereddict import OrderedDict 18 | import collections 19 | collections.OrderedDict = OrderedDict 20 | 21 | from .arguments import * 22 | from . import textui 23 | from . import utils 24 | from .pipes import piped_in 25 | 26 | 27 | 28 | __title__ = 'clint' 29 | __version__ = '0.5.1' 30 | __build__ = 0x000501 31 | __author__ = 'Kenneth Reitz' 32 | __license__ = 'ISC' 33 | __copyright__ = 'Copyright 2012 Kenneth Reitz' 34 | __docformat__ = 'restructuredtext' 35 | -------------------------------------------------------------------------------- /script/clint/packages/colorama/ansi.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module generates ANSI character codes to printing colors to terminals. 3 | See: http://en.wikipedia.org/wiki/ANSI_escape_code 4 | ''' 5 | 6 | CSI = '\033[' 7 | 8 | def code_to_chars(code): 9 | return CSI + str(code) + 'm' 10 | 11 | class AnsiCodes(object): 12 | def __init__(self, codes): 13 | for name in dir(codes): 14 | if not name.startswith('_'): 15 | value = getattr(codes, name) 16 | setattr(self, name, code_to_chars(value)) 17 | 18 | class AnsiFore: 19 | BLACK = 30 20 | RED = 31 21 | GREEN = 32 22 | YELLOW = 33 23 | BLUE = 34 24 | MAGENTA = 35 25 | CYAN = 36 26 | WHITE = 37 27 | RESET = 39 28 | 29 | class AnsiBack: 30 | BLACK = 40 31 | RED = 41 32 | GREEN = 42 33 | YELLOW = 43 34 | BLUE = 44 35 | MAGENTA = 45 36 | CYAN = 46 37 | WHITE = 47 38 | RESET = 49 39 | 40 | class AnsiStyle: 41 | BRIGHT = 1 42 | DIM = 2 43 | NORMAL = 22 44 | RESET_ALL = 0 45 | 46 | Fore = AnsiCodes( AnsiFore ) 47 | Back = AnsiCodes( AnsiBack ) 48 | Style = AnsiCodes( AnsiStyle ) 49 | 50 | -------------------------------------------------------------------------------- /script/clint/eng.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.eng 5 | ~~~~~~~~~ 6 | 7 | This module provides English language string helpers. 8 | 9 | """ 10 | from __future__ import print_function 11 | 12 | MORON_MODE = False 13 | COMMA = ',' 14 | CONJUNCTION = 'and' 15 | SPACE = ' ' 16 | 17 | try: 18 | unicode 19 | except NameError: 20 | unicode = str 21 | 22 | 23 | def join(l, conj=CONJUNCTION, im_a_moron=MORON_MODE, separator=COMMA): 24 | """Joins lists of words. Oxford comma and all.""" 25 | 26 | collector = [] 27 | left = len(l) 28 | separator = separator + SPACE 29 | conj = conj + SPACE 30 | 31 | for _l in l[:]: 32 | 33 | left += -1 34 | 35 | collector.append(_l) 36 | if left == 1: 37 | if len(l) == 2 or im_a_moron: 38 | collector.append(SPACE) 39 | else: 40 | collector.append(separator) 41 | 42 | collector.append(conj) 43 | 44 | elif left is not 0: 45 | collector.append(separator) 46 | 47 | return unicode(str().join(collector)) 48 | 49 | if __name__ == '__main__': 50 | print(join(['blue', 'red', 'yellow'], conj='or', im_a_moron=True)) 51 | print(join(['blue', 'red', 'yellow'], conj='or')) 52 | print(join(['blue', 'red'], conj='or')) 53 | print(join(['blue', 'red'], conj='and')) 54 | print(join(['blue'], conj='and')) 55 | print(join(['blue', 'red', 'yellow', 'green', 'ello'], conj='and')) 56 | -------------------------------------------------------------------------------- /script/clint/packages/colorama/initialise.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import sys 3 | 4 | from .ansitowin32 import AnsiToWin32 5 | 6 | 7 | orig_stdout = sys.stdout 8 | orig_stderr = sys.stderr 9 | 10 | wrapped_stdout = sys.stdout 11 | wrapped_stderr = sys.stderr 12 | 13 | atexit_done = False 14 | 15 | 16 | def reset_all(): 17 | AnsiToWin32(orig_stdout).reset_all() 18 | 19 | 20 | def init(autoreset=False, convert=None, strip=None, wrap=True): 21 | 22 | if not wrap and any([autoreset, convert, strip]): 23 | raise ValueError('wrap=False conflicts with any other arg=True') 24 | 25 | global wrapped_stdout, wrapped_stderr 26 | sys.stdout = wrapped_stdout = \ 27 | wrap_stream(orig_stdout, convert, strip, autoreset, wrap) 28 | sys.stderr = wrapped_stderr = \ 29 | wrap_stream(orig_stderr, convert, strip, autoreset, wrap) 30 | 31 | global atexit_done 32 | if not atexit_done: 33 | atexit.register(reset_all) 34 | atexit_done = True 35 | 36 | 37 | def deinit(): 38 | sys.stdout = orig_stdout 39 | sys.stderr = orig_stderr 40 | 41 | 42 | def reinit(): 43 | sys.stdout = wrapped_stdout 44 | sys.stderr = wrapped_stdout 45 | 46 | 47 | def wrap_stream(stream, convert, strip, autoreset, wrap): 48 | if wrap: 49 | wrapper = AnsiToWin32(stream, 50 | convert=convert, strip=strip, autoreset=autoreset) 51 | if wrapper.should_wrap(): 52 | stream = wrapper.stream 53 | return stream 54 | 55 | 56 | -------------------------------------------------------------------------------- /script/clint/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.utils 5 | ~~~~~~~~~~~~ 6 | 7 | Various Python helpers used within clint. 8 | 9 | """ 10 | 11 | from __future__ import absolute_import 12 | from __future__ import with_statement 13 | 14 | import errno 15 | import os.path 16 | from os import makedirs 17 | from glob import glob 18 | 19 | try: 20 | basestring = basestring 21 | except NameError: 22 | basestring = str 23 | 24 | def expand_path(path): 25 | """Expands directories and globs in given path.""" 26 | 27 | paths = [] 28 | path = os.path.expanduser(path) 29 | path = os.path.expandvars(path) 30 | 31 | if os.path.isdir(path): 32 | 33 | for (dir, dirs, files) in os.walk(path): 34 | for file in files: 35 | paths.append(os.path.join(dir, file)) 36 | else: 37 | paths.extend(glob(path)) 38 | 39 | return paths 40 | 41 | 42 | 43 | def is_collection(obj): 44 | """Tests if an object is a collection. Strings don't count.""" 45 | 46 | if isinstance(obj, basestring): 47 | return False 48 | 49 | return hasattr(obj, '__getitem__') 50 | 51 | 52 | def mkdir_p(path): 53 | """Emulates `mkdir -p` behavior.""" 54 | try: 55 | makedirs(path) 56 | except OSError as exc: # Python >2.5 57 | if exc.errno == errno.EEXIST: 58 | pass 59 | else: 60 | raise 61 | 62 | def tsplit(string, delimiters): 63 | """Behaves str.split but supports tuples of delimiters.""" 64 | delimiters = tuple(delimiters) 65 | if len(delimiters) < 1: 66 | return [string,] 67 | final_delimiter = delimiters[0] 68 | for i in delimiters[1:]: 69 | string = string.replace(i, final_delimiter) 70 | return string.split(final_delimiter) 71 | 72 | 73 | def schunk(string, size): 74 | """Splits string into n sized chunks.""" 75 | return [string[i:i+size] for i in range(0, len(string), size)] 76 | -------------------------------------------------------------------------------- /script/clint/textui/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.textui.core 5 | ~~~~~~~~~~~~~~~~~ 6 | 7 | Core TextUI functionality for Puts/Indent/Writer. 8 | 9 | """ 10 | 11 | 12 | from __future__ import absolute_import 13 | 14 | import sys 15 | 16 | from contextlib import contextmanager 17 | 18 | from .formatters import max_width, min_width, _get_max_width_context 19 | from .cols import columns 20 | from ..utils import tsplit 21 | 22 | 23 | __all__ = ('puts', 'puts_err', 'indent', 'dedent', 'columns', 'max_width', 24 | 'min_width', 'STDOUT', 'STDERR') 25 | 26 | 27 | STDOUT = sys.stdout.write 28 | STDERR = sys.stderr.write 29 | 30 | NEWLINES = ('\n', '\r', '\r\n') 31 | 32 | INDENT_STRINGS = [] 33 | 34 | # Private 35 | 36 | def _indent(indent=0, quote='', indent_char=' '): 37 | """Indent util function, compute new indent_string""" 38 | if indent > 0: 39 | indent_string = ''.join(( 40 | str(quote), 41 | (indent_char * (indent - len(quote))) 42 | )) 43 | else: 44 | indent_string = ''.join(( 45 | ('\x08' * (-1 * (indent - len(quote)))), 46 | str(quote)) 47 | ) 48 | 49 | if len(indent_string): 50 | INDENT_STRINGS.append(indent_string) 51 | 52 | # Public 53 | 54 | def puts(s='', newline=True, stream=STDOUT): 55 | """Prints given string to stdout.""" 56 | max_width_ctx = _get_max_width_context() 57 | if max_width_ctx: 58 | cols, separator = max_width_ctx[-1] 59 | s = max_width(s, cols, separator) 60 | 61 | if newline: 62 | s = tsplit(s, NEWLINES) 63 | s = map(str, s) 64 | indent = ''.join(INDENT_STRINGS) 65 | 66 | s = (str('\n' + indent)).join(s) 67 | 68 | _str = ''.join(( 69 | ''.join(INDENT_STRINGS), 70 | str(s), 71 | '\n' if newline else '' 72 | )) 73 | stream(_str) 74 | 75 | def puts_err(s='', newline=True, stream=STDERR): 76 | """Prints given string to stderr.""" 77 | puts(s, newline, stream) 78 | 79 | def dedent(): 80 | """Dedent next strings, use only if you use indent otherwise than as a 81 | context.""" 82 | INDENT_STRINGS.pop() 83 | 84 | @contextmanager 85 | def _indent_context(): 86 | """Indentation context manager.""" 87 | try: 88 | yield 89 | finally: 90 | dedent() 91 | 92 | def indent(indent=4, quote=''): 93 | """Indentation manager, return an indentation context manager.""" 94 | _indent(indent, quote) 95 | return _indent_context() 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wistia Downloader 2 | Python script/GUIApp to download from wistia hosted videos 3 |
4 | Buy Me A Coffee 5 | ## Requirements 6 | to run the program without problems you have to make sure you have this things in your machine. 7 | 8 | for the script you just need : 9 | > - python3 10 | > - requests 11 | 12 | for run the GUIApp you need : 13 | > - python3 14 | > - requests 15 | > - tkinter 16 | 17 | ## install tkinter : 18 | as we say before, to run the GUIApp you need also the library tkinter, now we will explain how to install it. 19 | ### Windows: 20 | Tkinter (and, since Python 3.1, ttk) are included with all standard Python distributions, The Tkinter library is built-in with every Python installation. And since you are on Windows, I believe you installed Python through the binaries on their website? 21 | ### MacOS: 22 | If you are using a Python from any current python.org Python installer for macOS (3.8.0+, 3.7.2+, 3.6.8, or 2.7.16+), no further action is needed to use tkinter. 23 | ### Linux: 24 | Actually, you just need to use the following to install the tkinter 25 | >sudo apt-get install python3-tk 26 | 27 | In addition, for Fedora users, use the following command: 28 | >sudo dnf install python3-tkinter 29 | 30 | ## Images from the GUIApp: 31 | Python 32 | 33 | ## Features: 34 | - Download all video Automatique. 35 | - The videos will be named with the original name. 36 | - You can see the download progress. 37 | - You can choose the video resolution. 38 | 39 | ## How to use 40 | 41 | ### step 1: (Install requests) 42 | >pip install requests==2.20.0 43 | 44 | ### step 2: (Cloning the script) 45 | >git clone https://github.com/abdlalisalmi/wistia-downloader.git
46 | >cd wistia-downloader
47 | 48 | ### step 3: (Grabbing the videos id) 49 | ![](https://media.giphy.com/media/YkJhH3iHcuXNaeRBCR/giphy.gif) 50 | 51 | ### step 4: (adding the IDs) 52 | for the script you need to add the IDs in video_ID.txt file, but in the GUIApp you will find the IDs input filed. 53 | 54 | ### step 5: (Execute the script/GUIApp) 55 | 56 | #### The GUIApp: 57 | >python3 GUIApp/start.py 58 | 59 | #### The script 60 | >cd script 61 | >python3 wistia-downloader.py
62 | 63 | ## Created by 64 | 65 | [Abdelaali es salmi](https://github.com/salmiabdlali) & 66 | [Amine tahiri](https://github.com/aminetahiri1998) 67 | 68 | 69 | -------------------------------------------------------------------------------- /script/clint/textui/validators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.textui.validators 5 | ~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Core TextUI functionality for input validation. 8 | 9 | """ 10 | 11 | from __future__ import absolute_import 12 | 13 | import os 14 | import sys 15 | import re 16 | 17 | # Useful for very coarse version differentiation. 18 | PY2 = sys.version_info[0] == 2 19 | PY3 = sys.version_info[0] == 3 20 | 21 | if PY3: 22 | string_types = str, 23 | else: 24 | string_types = basestring, 25 | 26 | 27 | class ValidationError(Exception): 28 | """An error while validating data.""" 29 | 30 | def __init__(self, message): 31 | self.message = message 32 | self.error_list = [self] 33 | 34 | 35 | class RegexValidator(object): 36 | regex = '' 37 | message = 'Enter a valid value.' 38 | 39 | def __init__(self, regex=None, message=None): 40 | if regex is not None: 41 | self.regex = regex 42 | if message is not None: 43 | self.message = message 44 | 45 | # Compile the regex if it was not passed pre-compiled. 46 | if isinstance(self.regex, string_types): 47 | self.regex = re.compile(self.regex) 48 | 49 | def __call__(self, value): 50 | """ 51 | Validates that the input matches the regular expression. 52 | """ 53 | if not self.regex.search(value): 54 | raise ValidationError(self.message) 55 | return value 56 | 57 | 58 | class PathValidator(object): 59 | message = 'Enter a valid path.' 60 | 61 | def __init__(self, message=None): 62 | if message is not None: 63 | self.message = message 64 | 65 | def __call__(self, value): 66 | """ 67 | Validates that the input is a valid directory. 68 | """ 69 | if not os.path.isdir(value): 70 | raise ValidationError(self.message) 71 | return value 72 | 73 | 74 | class FileValidator(object): 75 | message = 'Enter a valid file.' 76 | 77 | def __init__(self, message=None): 78 | if message is not None: 79 | self.message = message 80 | 81 | def __call__(self, value): 82 | """ 83 | Validates that the input is a valid file. 84 | """ 85 | if not os.path.isfile(value): 86 | raise ValidationError(self.message) 87 | return value 88 | 89 | 90 | class IntegerValidator(object): 91 | message = 'Enter a valid number.' 92 | 93 | def __init__(self, message=None): 94 | if message is not None: 95 | self.message = message 96 | 97 | def __call__(self, value): 98 | """ 99 | Validates that the input is a integer. 100 | """ 101 | try: 102 | return int(value) 103 | except (TypeError, ValueError): 104 | raise ValidationError(self.message) 105 | 106 | class OptionValidator(object): 107 | message = 'Select from the list of valid options.' 108 | 109 | def __init__(self, options, message=None): 110 | self.options = options 111 | if message is not None: 112 | self.message = message 113 | 114 | def __call__(self, value): 115 | """ 116 | Validates that the input is in the options list. 117 | """ 118 | if value in self.options: 119 | return value 120 | else: 121 | raise ValidationError(self.message) 122 | 123 | -------------------------------------------------------------------------------- /script/clint/textui/cols.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.textui.columns 5 | ~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Core TextUI functionality for column formatting. 8 | 9 | """ 10 | 11 | from __future__ import absolute_import 12 | 13 | from .formatters import max_width, min_width 14 | from ..utils import tsplit 15 | 16 | import sys 17 | 18 | 19 | NEWLINES = ('\n', '\r', '\r\n') 20 | 21 | 22 | 23 | def _find_unix_console_width(): 24 | import termios, fcntl, struct, sys 25 | 26 | # fcntl.ioctl will fail if stdout is not a tty 27 | if not sys.stdout.isatty(): 28 | return None 29 | 30 | s = struct.pack("HHHH", 0, 0, 0, 0) 31 | fd_stdout = sys.stdout.fileno() 32 | size = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s) 33 | height, width = struct.unpack("HHHH", size)[:2] 34 | return width 35 | 36 | 37 | def _find_windows_console_width(): 38 | # http://code.activestate.com/recipes/440694/ 39 | from ctypes import windll, create_string_buffer 40 | STDIN, STDOUT, STDERR = -10, -11, -12 41 | 42 | h = windll.kernel32.GetStdHandle(STDERR) 43 | csbi = create_string_buffer(22) 44 | res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) 45 | 46 | if res: 47 | import struct 48 | (bufx, bufy, curx, cury, wattr, 49 | left, top, right, bottom, 50 | maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) 51 | sizex = right - left + 1 52 | sizey = bottom - top + 1 53 | return sizex 54 | 55 | 56 | def console_width(kwargs): 57 | """"Determine console_width.""" 58 | 59 | if sys.platform.startswith('win'): 60 | console_width = _find_windows_console_width() 61 | else: 62 | console_width = _find_unix_console_width() 63 | 64 | _width = kwargs.get('width', None) 65 | if _width: 66 | console_width = _width 67 | else: 68 | if not console_width: 69 | console_width = 80 70 | 71 | return console_width 72 | 73 | 74 | 75 | def columns(*cols, **kwargs): 76 | 77 | columns = list(cols) 78 | 79 | cwidth = console_width(kwargs) 80 | 81 | _big_col = None 82 | _total_cols = 0 83 | 84 | 85 | for i, (string, width) in enumerate(cols): 86 | 87 | if width is not None: 88 | _total_cols += (width + 1) 89 | cols[i][0] = max_width(string, width).split('\n') 90 | else: 91 | _big_col = i 92 | 93 | if _big_col: 94 | cols[_big_col][1] = (cwidth - _total_cols) - len(cols) 95 | cols[_big_col][0] = max_width(cols[_big_col][0], cols[_big_col][1]).split('\n') 96 | 97 | height = len(max([c[0] for c in cols], key=len)) 98 | 99 | for i, (strings, width) in enumerate(cols): 100 | 101 | for _ in range(height - len(strings)): 102 | cols[i][0].append('') 103 | 104 | for j, string in enumerate(strings): 105 | cols[i][0][j] = min_width(string, width) 106 | 107 | stack = [c[0] for c in cols] 108 | _out = [] 109 | 110 | for i in range(height): 111 | _row = '' 112 | 113 | for col in stack: 114 | _row += col[i] 115 | _row += ' ' 116 | 117 | _out.append(_row) 118 | # try: 119 | # pass 120 | # except: 121 | # pass 122 | 123 | 124 | 125 | 126 | return '\n'.join(_out) 127 | 128 | 129 | # string = max_width(string, width) 130 | # string = min_width(string, width) 131 | # pass 132 | # columns.append() 133 | 134 | 135 | 136 | ########################### 137 | 138 | a = 'this is text that goes into a small column\n cool?' 139 | b = 'this is other text\nothertext\nothertext' 140 | 141 | #columns((a, 10), (b, 20), (b, None)) 142 | -------------------------------------------------------------------------------- /script/clint/packages/colorama/winterm.py: -------------------------------------------------------------------------------- 1 | 2 | from . import win32 3 | 4 | 5 | # from wincon.h 6 | class WinColor(object): 7 | BLACK = 0 8 | BLUE = 1 9 | GREEN = 2 10 | CYAN = 3 11 | RED = 4 12 | MAGENTA = 5 13 | YELLOW = 6 14 | GREY = 7 15 | 16 | # from wincon.h 17 | class WinStyle(object): 18 | NORMAL = 0x00 # dim text, dim background 19 | BRIGHT = 0x08 # bright text, dim background 20 | 21 | 22 | class WinTerm(object): 23 | 24 | def __init__(self): 25 | self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes 26 | self.set_attrs(self._default) 27 | self._default_fore = self._fore 28 | self._default_back = self._back 29 | self._default_style = self._style 30 | 31 | def get_attrs(self): 32 | return self._fore + self._back * 16 + self._style 33 | 34 | def set_attrs(self, value): 35 | self._fore = value & 7 36 | self._back = (value >> 4) & 7 37 | self._style = value & WinStyle.BRIGHT 38 | 39 | def reset_all(self, on_stderr=None): 40 | self.set_attrs(self._default) 41 | self.set_console(attrs=self._default) 42 | 43 | def fore(self, fore=None, on_stderr=False): 44 | if fore is None: 45 | fore = self._default_fore 46 | self._fore = fore 47 | self.set_console(on_stderr=on_stderr) 48 | 49 | def back(self, back=None, on_stderr=False): 50 | if back is None: 51 | back = self._default_back 52 | self._back = back 53 | self.set_console(on_stderr=on_stderr) 54 | 55 | def style(self, style=None, on_stderr=False): 56 | if style is None: 57 | style = self._default_style 58 | self._style = style 59 | self.set_console(on_stderr=on_stderr) 60 | 61 | def set_console(self, attrs=None, on_stderr=False): 62 | if attrs is None: 63 | attrs = self.get_attrs() 64 | handle = win32.STDOUT 65 | if on_stderr: 66 | handle = win32.STDERR 67 | win32.SetConsoleTextAttribute(handle, attrs) 68 | 69 | def set_cursor_position(self, position=None, on_stderr=False): 70 | if position is None: 71 | #I'm not currently tracking the position, so there is no default. 72 | #position = self.get_position() 73 | return 74 | handle = win32.STDOUT 75 | if on_stderr: 76 | handle = win32.STDERR 77 | win32.SetConsoleCursorPosition(handle, position) 78 | 79 | def erase_data(self, mode=0, on_stderr=False): 80 | # 0 (or None) should clear from the cursor to the end of the screen. 81 | # 1 should clear from the cursor to the beginning of the screen. 82 | # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) 83 | # 84 | # At the moment, I only support mode 2. From looking at the API, it 85 | # should be possible to calculate a different number of bytes to clear, 86 | # and to do so relative to the cursor position. 87 | if mode[0] not in (2,): 88 | return 89 | handle = win32.STDOUT 90 | if on_stderr: 91 | handle = win32.STDERR 92 | # here's where we'll home the cursor 93 | coord_screen = win32.COORD(0,0) 94 | csbi = win32.GetConsoleScreenBufferInfo(handle) 95 | # get the number of character cells in the current buffer 96 | dw_con_size = csbi.dwSize.X * csbi.dwSize.Y 97 | # fill the entire screen with blanks 98 | win32.FillConsoleOutputCharacter(handle, ord(' '), dw_con_size, coord_screen) 99 | # now set the buffer's attributes accordingly 100 | win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ); 101 | # put the cursor at (0, 0) 102 | win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) 103 | -------------------------------------------------------------------------------- /script/clint/resources.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.resources 5 | ~~~~~~~~~~~~~~~ 6 | 7 | This module contains all the application resource features of clint. 8 | 9 | """ 10 | 11 | 12 | from __future__ import absolute_import 13 | from __future__ import with_statement 14 | 15 | import errno 16 | from os import remove, removedirs 17 | from os.path import isfile, join as path_join 18 | 19 | from .packages.appdirs import AppDirs, AppDirsError 20 | from .utils import mkdir_p, is_collection 21 | 22 | 23 | __all__ = ( 24 | 'init', 'user', 'site', 'cache', 25 | 'log', 'NotConfigured' 26 | ) 27 | 28 | 29 | class AppDir(object): 30 | """Application Directory object.""" 31 | 32 | def __init__(self, path=None): 33 | self.path = path 34 | self._exists = False 35 | 36 | if path: 37 | self._create() 38 | 39 | 40 | def __repr__(self): 41 | return '' % (self.path) 42 | 43 | 44 | def __getattribute__(self, name): 45 | 46 | if not name in ('_exists', 'path', '_create', '_raise_if_none'): 47 | if not self._exists: 48 | self._create() 49 | return object.__getattribute__(self, name) 50 | 51 | 52 | def _raise_if_none(self): 53 | """Raises if operations are carried out on an unconfigured AppDir.""" 54 | if not self.path: 55 | raise NotConfigured() 56 | 57 | 58 | def _create(self): 59 | """Creates current AppDir at AppDir.path.""" 60 | 61 | self._raise_if_none() 62 | if not self._exists: 63 | mkdir_p(self.path) 64 | self._exists = True 65 | 66 | 67 | def open(self, filename, mode='r'): 68 | """Returns file object from given filename.""" 69 | 70 | self._raise_if_none() 71 | fn = path_join(self.path, filename) 72 | 73 | return open(fn, mode) 74 | 75 | 76 | def write(self, filename, content, binary=False): 77 | """Writes given content to given filename.""" 78 | self._raise_if_none() 79 | fn = path_join(self.path, filename) 80 | 81 | if binary: 82 | flags = 'wb' 83 | else: 84 | flags = 'w' 85 | 86 | 87 | with open(fn, flags) as f: 88 | f.write(content) 89 | 90 | 91 | def append(self, filename, content, binary=False): 92 | """Appends given content to given filename.""" 93 | 94 | self._raise_if_none() 95 | fn = path_join(self.path, filename) 96 | 97 | if binary: 98 | flags = 'ab' 99 | else: 100 | flags = 'a' 101 | 102 | with open(fn, 'a') as f: 103 | f.write(content) 104 | return True 105 | 106 | def delete(self, filename=''): 107 | """Deletes given file or directory. If no filename is passed, current 108 | directory is removed. 109 | """ 110 | self._raise_if_none() 111 | fn = path_join(self.path, filename) 112 | 113 | try: 114 | if isfile(fn): 115 | remove(fn) 116 | else: 117 | removedirs(fn) 118 | except OSError as why: 119 | if why.errno == errno.ENOENT: 120 | pass 121 | else: 122 | raise why 123 | 124 | 125 | def read(self, filename, binary=False): 126 | """Returns contents of given file with AppDir. 127 | If file doesn't exist, returns None.""" 128 | 129 | self._raise_if_none() 130 | fn = path_join(self.path, filename) 131 | 132 | if binary: 133 | flags = 'br' 134 | else: 135 | flags = 'r' 136 | 137 | try: 138 | with open(fn, flags) as f: 139 | return f.read() 140 | except IOError: 141 | return None 142 | 143 | 144 | def sub(self, path): 145 | """Returns AppDir instance for given subdirectory name.""" 146 | 147 | if is_collection(path): 148 | path = path_join(path) 149 | 150 | return AppDir(path_join(self.path, path)) 151 | 152 | 153 | # Module locals 154 | 155 | user = AppDir() 156 | site = AppDir() 157 | cache = AppDir() 158 | log = AppDir() 159 | 160 | 161 | def init(vendor, name): 162 | 163 | global user, site, cache, log 164 | 165 | ad = AppDirs(name, vendor) 166 | 167 | user.path = ad.user_data_dir 168 | 169 | site.path = ad.site_data_dir 170 | cache.path = ad.user_cache_dir 171 | log.path = ad.user_log_dir 172 | 173 | 174 | class NotConfigured(IOError): 175 | """Application configuration required. Please run resources.init() first.""" 176 | -------------------------------------------------------------------------------- /script/clint/packages/ordereddict.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Raymond Hettinger 2 | # 3 | # Permission is hereby granted, free of charge, to any person 4 | # obtaining a copy of this software and associated documentation files 5 | # (the "Software"), to deal in the Software without restriction, 6 | # including without limitation the rights to use, copy, modify, merge, 7 | # publish, distribute, sublicense, and/or sell copies of the Software, 8 | # and to permit persons to whom the Software is furnished to do so, 9 | # subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 18 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 21 | # OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | from UserDict import DictMixin 24 | 25 | class OrderedDict(dict, DictMixin): 26 | 27 | def __init__(self, *args, **kwds): 28 | if len(args) > 1: 29 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 30 | try: 31 | self.__end 32 | except AttributeError: 33 | self.clear() 34 | self.update(*args, **kwds) 35 | 36 | def clear(self): 37 | self.__end = end = [] 38 | end += [None, end, end] # sentinel node for doubly linked list 39 | self.__map = {} # key --> [key, prev, next] 40 | dict.clear(self) 41 | 42 | def __setitem__(self, key, value): 43 | if key not in self: 44 | end = self.__end 45 | curr = end[1] 46 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 47 | dict.__setitem__(self, key, value) 48 | 49 | def __delitem__(self, key): 50 | dict.__delitem__(self, key) 51 | key, prev, next = self.__map.pop(key) 52 | prev[2] = next 53 | next[1] = prev 54 | 55 | def __iter__(self): 56 | end = self.__end 57 | curr = end[2] 58 | while curr is not end: 59 | yield curr[0] 60 | curr = curr[2] 61 | 62 | def __reversed__(self): 63 | end = self.__end 64 | curr = end[1] 65 | while curr is not end: 66 | yield curr[0] 67 | curr = curr[1] 68 | 69 | def popitem(self, last=True): 70 | if not self: 71 | raise KeyError('dictionary is empty') 72 | if last: 73 | key = reversed(self).next() 74 | else: 75 | key = iter(self).next() 76 | value = self.pop(key) 77 | return key, value 78 | 79 | def __reduce__(self): 80 | items = [[k, self[k]] for k in self] 81 | tmp = self.__map, self.__end 82 | del self.__map, self.__end 83 | inst_dict = vars(self).copy() 84 | self.__map, self.__end = tmp 85 | if inst_dict: 86 | return (self.__class__, (items,), inst_dict) 87 | return self.__class__, (items,) 88 | 89 | def keys(self): 90 | return list(self) 91 | 92 | setdefault = DictMixin.setdefault 93 | update = DictMixin.update 94 | pop = DictMixin.pop 95 | values = DictMixin.values 96 | items = DictMixin.items 97 | iterkeys = DictMixin.iterkeys 98 | itervalues = DictMixin.itervalues 99 | iteritems = DictMixin.iteritems 100 | 101 | def __repr__(self): 102 | if not self: 103 | return '%s()' % (self.__class__.__name__,) 104 | return '%s(%r)' % (self.__class__.__name__, self.items()) 105 | 106 | def copy(self): 107 | return self.__class__(self) 108 | 109 | @classmethod 110 | def fromkeys(cls, iterable, value=None): 111 | d = cls() 112 | for key in iterable: 113 | d[key] = value 114 | return d 115 | 116 | def __eq__(self, other): 117 | if isinstance(other, OrderedDict): 118 | if len(self) != len(other): 119 | return False 120 | for p, q in zip(self.items(), other.items()): 121 | if p != q: 122 | return False 123 | return True 124 | return dict.__eq__(self, other) 125 | 126 | def __ne__(self, other): 127 | return not self == other 128 | -------------------------------------------------------------------------------- /script/clint/textui/formatters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.textui.formatters 5 | ~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Core TextUI functionality for text formatting. 8 | 9 | """ 10 | 11 | from __future__ import absolute_import 12 | from contextlib import contextmanager 13 | 14 | from .colored import ColoredString, clean 15 | from ..utils import tsplit, schunk 16 | 17 | 18 | NEWLINES = ('\n', '\r', '\r\n') 19 | MAX_WIDTHS = [] 20 | 21 | 22 | def min_width(string, cols, padding=' '): 23 | """Returns given string with right padding.""" 24 | 25 | is_color = isinstance(string, ColoredString) 26 | 27 | stack = tsplit(str(string), NEWLINES) 28 | 29 | for i, substring in enumerate(stack): 30 | _sub = clean(substring).ljust((cols + 0), padding) 31 | if is_color: 32 | _sub = (_sub.replace(clean(substring), substring)) 33 | stack[i] = _sub 34 | 35 | return '\n'.join(stack) 36 | 37 | 38 | def _get_max_width_context(): 39 | return MAX_WIDTHS 40 | 41 | @contextmanager 42 | def _max_width_context(): 43 | """Max width context manager.""" 44 | try: 45 | yield 46 | finally: 47 | MAX_WIDTHS.pop() 48 | 49 | def max_width(*args, **kwargs): 50 | """Returns formatted text or context manager for textui:puts. 51 | 52 | >>> from clint.textui import puts, max_width 53 | >>> max_width('123 5678', 8) 54 | '123 5678' 55 | >>> max_width('123 5678', 7) 56 | '123 \n5678' 57 | >>> with max_width(7): 58 | ... puts('123 5678') 59 | '123 \n5678' 60 | """ 61 | args = list(args) 62 | 63 | if not args: 64 | args.append(kwargs.get('string')) 65 | args.append(kwargs.get('cols')) 66 | args.append(kwargs.get('separator')) 67 | elif len(args) == 1: 68 | args.append(kwargs.get('cols')) 69 | args.append(kwargs.get('separator')) 70 | elif len(args) == 2: 71 | args.append(kwargs.get('separator')) 72 | 73 | string, cols, separator = args 74 | if separator is None: 75 | separator = '\n' # default value 76 | if cols is None: 77 | # cols should be specified vitally 78 | # because string can be specified at textui:puts function 79 | string, cols = cols, string 80 | 81 | if string is None: 82 | MAX_WIDTHS.append((cols, separator)) 83 | return _max_width_context() 84 | else: 85 | return _max_width_formatter(string, cols, separator) 86 | 87 | 88 | def _max_width_formatter(string, cols, separator='\n'): 89 | """Returns a freshly formatted 90 | :param string: string to be formatted 91 | :type string: basestring or clint.textui.colored.ColoredString 92 | :param cols: max width the text to be formatted 93 | :type cols: int 94 | :param separator: separator to break rows 95 | :type separator: basestring 96 | """ 97 | 98 | is_color = isinstance(string, ColoredString) 99 | 100 | if is_color: 101 | string_copy = string._new('') 102 | string = string.s 103 | 104 | stack = tsplit(string, NEWLINES) 105 | 106 | for i, substring in enumerate(stack): 107 | stack[i] = substring.split() 108 | 109 | _stack = [] 110 | 111 | for row in stack: 112 | _row = ['',] 113 | _row_i = 0 114 | 115 | for word in row: 116 | if (len(_row[_row_i]) + len(word)) <= cols: 117 | _row[_row_i] += word 118 | _row[_row_i] += ' ' 119 | 120 | elif len(word) > cols: 121 | 122 | # ensure empty row 123 | if len(_row[_row_i]): 124 | _row[_row_i] = _row[_row_i].rstrip() 125 | _row.append('') 126 | _row_i += 1 127 | 128 | chunks = schunk(word, cols) 129 | for i, chunk in enumerate(chunks): 130 | if not (i + 1) == len(chunks): 131 | _row[_row_i] += chunk 132 | _row[_row_i] = _row[_row_i].rstrip() 133 | _row.append('') 134 | _row_i += 1 135 | else: 136 | _row[_row_i] += chunk 137 | _row[_row_i] += ' ' 138 | else: 139 | _row[_row_i] = _row[_row_i].rstrip() 140 | _row.append('') 141 | _row_i += 1 142 | _row[_row_i] += word 143 | _row[_row_i] += ' ' 144 | else: 145 | _row[_row_i] = _row[_row_i].rstrip() 146 | 147 | _row = map(str, _row) 148 | _stack.append(separator.join(_row)) 149 | 150 | _s = '\n'.join(_stack) 151 | if is_color: 152 | _s = string_copy._new(_s) 153 | return _s 154 | -------------------------------------------------------------------------------- /script/clint/textui/colored.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.colored 5 | ~~~~~~~~~~~~~ 6 | 7 | This module provides a simple and elegant wrapper for colorama. 8 | 9 | """ 10 | 11 | 12 | from __future__ import absolute_import 13 | 14 | import os 15 | import re 16 | import sys 17 | 18 | PY3 = sys.version_info[0] >= 3 19 | 20 | from ..packages import colorama 21 | from ..utils import basestring 22 | 23 | __all__ = ( 24 | 'red', 'green', 'yellow', 'blue', 25 | 'black', 'magenta', 'cyan', 'white', 26 | 'clean', 'disable' 27 | ) 28 | 29 | COLORS = __all__[:-2] 30 | 31 | if 'get_ipython' in dir(): 32 | """ 33 | when ipython is fired lot of variables like _oh, etc are used. 34 | There are so many ways to find current python interpreter is ipython. 35 | get_ipython is easiest is most appealing for readers to understand. 36 | """ 37 | DISABLE_COLOR = True 38 | else: 39 | DISABLE_COLOR = False 40 | 41 | 42 | class ColoredString(object): 43 | """Enhanced string for __len__ operations on Colored output.""" 44 | def __init__(self, color, s, always_color=False, bold=False): 45 | super(ColoredString, self).__init__() 46 | if not PY3 and isinstance(s, unicode): 47 | self.s = s.encode('utf-8') 48 | else: 49 | self.s = s 50 | self.color = color 51 | self.always_color = always_color 52 | self.bold = bold 53 | if os.environ.get('CLINT_FORCE_COLOR'): 54 | self.always_color = True 55 | 56 | def __getattr__(self, att): 57 | def func_help(*args, **kwargs): 58 | result = getattr(self.s, att)(*args, **kwargs) 59 | try: 60 | is_result_string = isinstance(result, basestring) 61 | except NameError: 62 | is_result_string = isinstance(result, str) 63 | if is_result_string: 64 | return self._new(result) 65 | elif isinstance(result, list): 66 | return [self._new(x) for x in result] 67 | else: 68 | return result 69 | return func_help 70 | 71 | @property 72 | def color_str(self): 73 | style = 'BRIGHT' if self.bold else 'NORMAL' 74 | c = '%s%s%s%s%s' % (getattr(colorama.Fore, self.color), getattr(colorama.Style, style), self.s, colorama.Fore.RESET, getattr(colorama.Style, 'NORMAL')) 75 | 76 | if self.always_color: 77 | return c 78 | elif sys.stdout.isatty() and not DISABLE_COLOR: 79 | return c 80 | else: 81 | return self.s 82 | 83 | 84 | def __len__(self): 85 | return len(self.s) 86 | 87 | def __repr__(self): 88 | return "<%s-string: '%s'>" % (self.color, self.s) 89 | 90 | def __unicode__(self): 91 | value = self.color_str 92 | if isinstance(value, bytes): 93 | return value.decode('utf8') 94 | return value 95 | 96 | if PY3: 97 | __str__ = __unicode__ 98 | else: 99 | def __str__(self): 100 | return self.color_str 101 | 102 | def __iter__(self): 103 | return iter(self.color_str) 104 | 105 | def __add__(self, other): 106 | return str(self.color_str) + str(other) 107 | 108 | def __radd__(self, other): 109 | return str(other) + str(self.color_str) 110 | 111 | def __mul__(self, other): 112 | return (self.color_str * other) 113 | 114 | def _new(self, s): 115 | return ColoredString(self.color, s) 116 | 117 | 118 | def clean(s): 119 | strip = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)") 120 | txt = strip.sub('', str(s)) 121 | 122 | strip = re.compile(r'\[\d+m') 123 | txt = strip.sub('', txt) 124 | 125 | return txt 126 | 127 | 128 | def black(string, always=False, bold=False): 129 | return ColoredString('BLACK', string, always_color=always, bold=bold) 130 | 131 | def red(string, always=False, bold=False): 132 | return ColoredString('RED', string, always_color=always, bold=bold) 133 | 134 | def green(string, always=False, bold=False): 135 | return ColoredString('GREEN', string, always_color=always, bold=bold) 136 | 137 | def yellow(string, always=False, bold=False): 138 | return ColoredString('YELLOW', string, always_color=always, bold=bold) 139 | 140 | def blue(string, always=False, bold=False): 141 | return ColoredString('BLUE', string, always_color=always, bold=bold) 142 | 143 | def magenta(string, always=False, bold=False): 144 | return ColoredString('MAGENTA', string, always_color=always, bold=bold) 145 | 146 | def cyan(string, always=False, bold=False): 147 | return ColoredString('CYAN', string, always_color=always, bold=bold) 148 | 149 | def white(string, always=False, bold=False): 150 | return ColoredString('WHITE', string, always_color=always, bold=bold) 151 | 152 | def disable(): 153 | """Disables colors.""" 154 | global DISABLE_COLOR 155 | 156 | DISABLE_COLOR = True 157 | -------------------------------------------------------------------------------- /script/clint/packages/colorama/win32.py: -------------------------------------------------------------------------------- 1 | 2 | # from winbase.h 3 | STDOUT = -11 4 | STDERR = -12 5 | 6 | try: 7 | from ctypes import windll 8 | except ImportError: 9 | windll = None 10 | SetConsoleTextAttribute = lambda *_: None 11 | else: 12 | from ctypes import ( 13 | byref, Structure, c_char, c_short, c_uint32, c_ushort 14 | ) 15 | 16 | handles = { 17 | STDOUT: windll.kernel32.GetStdHandle(STDOUT), 18 | STDERR: windll.kernel32.GetStdHandle(STDERR), 19 | } 20 | 21 | SHORT = c_short 22 | WORD = c_ushort 23 | DWORD = c_uint32 24 | TCHAR = c_char 25 | 26 | class COORD(Structure): 27 | """struct in wincon.h""" 28 | _fields_ = [ 29 | ('X', SHORT), 30 | ('Y', SHORT), 31 | ] 32 | 33 | class SMALL_RECT(Structure): 34 | """struct in wincon.h.""" 35 | _fields_ = [ 36 | ("Left", SHORT), 37 | ("Top", SHORT), 38 | ("Right", SHORT), 39 | ("Bottom", SHORT), 40 | ] 41 | 42 | class CONSOLE_SCREEN_BUFFER_INFO(Structure): 43 | """struct in wincon.h.""" 44 | _fields_ = [ 45 | ("dwSize", COORD), 46 | ("dwCursorPosition", COORD), 47 | ("wAttributes", WORD), 48 | ("srWindow", SMALL_RECT), 49 | ("dwMaximumWindowSize", COORD), 50 | ] 51 | def __str__(self): 52 | return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( 53 | self.dwSize.Y, self.dwSize.X 54 | , self.dwCursorPosition.Y, self.dwCursorPosition.X 55 | , self.wAttributes 56 | , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right 57 | , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X 58 | ) 59 | 60 | def GetConsoleScreenBufferInfo(stream_id=STDOUT): 61 | handle = handles[stream_id] 62 | csbi = CONSOLE_SCREEN_BUFFER_INFO() 63 | success = windll.kernel32.GetConsoleScreenBufferInfo( 64 | handle, byref(csbi)) 65 | # This fails when imported via setup.py when installing using 'pip' 66 | # presumably the fix is that running setup.py should not trigger all 67 | # this activity. 68 | # assert success 69 | return csbi 70 | 71 | def SetConsoleTextAttribute(stream_id, attrs): 72 | handle = handles[stream_id] 73 | return windll.kernel32.SetConsoleTextAttribute(handle, attrs) 74 | 75 | def SetConsoleCursorPosition(stream_id, position): 76 | position = COORD(*position) 77 | # If the position is out of range, do nothing. 78 | if position.Y <= 0 or position.X <= 0: 79 | return 80 | # Adjust for Windows' SetConsoleCursorPosition: 81 | # 1. being 0-based, while ANSI is 1-based. 82 | # 2. expecting (x,y), while ANSI uses (y,x). 83 | adjusted_position = COORD(position.Y - 1, position.X - 1) 84 | # Adjust for viewport's scroll position 85 | sr = GetConsoleScreenBufferInfo(STDOUT).srWindow 86 | adjusted_position.Y += sr.Top 87 | adjusted_position.X += sr.Left 88 | # Resume normal processing 89 | handle = handles[stream_id] 90 | success = windll.kernel32.SetConsoleCursorPosition(handle, adjusted_position) 91 | return success 92 | 93 | def FillConsoleOutputCharacter(stream_id, char, length, start): 94 | handle = handles[stream_id] 95 | char = TCHAR(char) 96 | length = DWORD(length) 97 | num_written = DWORD(0) 98 | # Note that this is hard-coded for ANSI (vs wide) bytes. 99 | success = windll.kernel32.FillConsoleOutputCharacterA( 100 | handle, char, length, start, byref(num_written)) 101 | return num_written.value 102 | 103 | def FillConsoleOutputAttribute(stream_id, attr, length, start): 104 | ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' 105 | handle = handles[stream_id] 106 | attribute = WORD(attr) 107 | length = DWORD(length) 108 | num_written = DWORD(0) 109 | # Note that this is hard-coded for ANSI (vs wide) bytes. 110 | success = windll.kernel32.FillConsoleOutputAttribute( 111 | handle, attribute, length, start, byref(num_written)) 112 | return success 113 | 114 | 115 | if __name__=='__main__': 116 | x = GetConsoleScreenBufferInfo(STDOUT) 117 | print(x) 118 | print('dwSize(height,width) = (%d,%d)' % (x.dwSize.Y, x.dwSize.X)) 119 | print('dwCursorPosition(y,x) = (%d,%d)' % (x.dwCursorPosition.Y, x.dwCursorPosition.X)) 120 | print('wAttributes(color) = %d = 0x%02x' % (x.wAttributes, x.wAttributes)) 121 | print('srWindow(Top,Left)-(Bottom,Right) = (%d,%d)-(%d,%d)' % (x.srWindow.Top, x.srWindow.Left, x.srWindow.Bottom, x.srWindow.Right)) 122 | print('dwMaximumWindowSize(maxHeight,maxWidth) = (%d,%d)' % (x.dwMaximumWindowSize.Y, x.dwMaximumWindowSize.X)) 123 | 124 | -------------------------------------------------------------------------------- /script/clint/textui/progress.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.textui.progress 5 | ~~~~~~~~~~~~~~~~~ 6 | 7 | This module provides the progressbar functionality. 8 | 9 | """ 10 | 11 | from __future__ import absolute_import 12 | 13 | import sys 14 | import time 15 | 16 | STREAM = sys.stderr 17 | 18 | BAR_TEMPLATE = '%s[%s%s] %i/%i - %s\r' 19 | MILL_TEMPLATE = '%s %s %i/%i\r' 20 | 21 | DOTS_CHAR = '.' 22 | BAR_FILLED_CHAR = '#' 23 | BAR_EMPTY_CHAR = ' ' 24 | MILL_CHARS = ['|', '/', '-', '\\'] 25 | 26 | # How long to wait before recalculating the ETA 27 | ETA_INTERVAL = 1 28 | # How many intervals (excluding the current one) to calculate the simple moving 29 | # average 30 | ETA_SMA_WINDOW = 9 31 | 32 | 33 | class Bar(object): 34 | def __enter__(self): 35 | return self 36 | 37 | def __exit__(self, exc_type, exc_val, exc_tb): 38 | self.done() 39 | return False # we're not suppressing exceptions 40 | 41 | def __init__(self, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR, 42 | filled_char=BAR_FILLED_CHAR, expected_size=None, every=1): 43 | self.label = label 44 | self.width = width 45 | self.hide = hide 46 | # Only show bar in terminals by default (better for piping, logging etc.) 47 | if hide is None: 48 | try: 49 | self.hide = not STREAM.isatty() 50 | except AttributeError: # output does not support isatty() 51 | self.hide = True 52 | self.empty_char = empty_char 53 | self.filled_char = filled_char 54 | self.expected_size = expected_size 55 | self.every = every 56 | self.start = time.time() 57 | self.ittimes = [] 58 | self.eta = 0 59 | self.etadelta = time.time() 60 | self.etadisp = self.format_time(self.eta) 61 | self.last_progress = 0 62 | if (self.expected_size): 63 | self.show(0) 64 | 65 | def show(self, progress, count=None): 66 | if count is not None: 67 | self.expected_size = count 68 | if self.expected_size is None: 69 | raise Exception("expected_size not initialized") 70 | self.last_progress = progress 71 | if (time.time() - self.etadelta) > ETA_INTERVAL: 72 | self.etadelta = time.time() 73 | self.ittimes = \ 74 | self.ittimes[-ETA_SMA_WINDOW:] + \ 75 | [-(self.start - time.time()) / (progress+1)] 76 | self.eta = \ 77 | sum(self.ittimes) / float(len(self.ittimes)) * \ 78 | (self.expected_size - progress) 79 | self.etadisp = self.format_time(self.eta) 80 | x = int(self.width * progress / self.expected_size) 81 | if not self.hide: 82 | if ((progress % self.every) == 0 or # True every "every" updates 83 | (progress == self.expected_size)): # And when we're done 84 | STREAM.write(BAR_TEMPLATE % ( 85 | self.label, self.filled_char * x, 86 | self.empty_char * (self.width - x), progress, 87 | self.expected_size, self.etadisp)) 88 | STREAM.flush() 89 | 90 | def done(self): 91 | self.elapsed = time.time() - self.start 92 | elapsed_disp = self.format_time(self.elapsed) 93 | if not self.hide: 94 | # Print completed bar with elapsed time 95 | STREAM.write(BAR_TEMPLATE % ( 96 | self.label, self.filled_char * self.width, 97 | self.empty_char * 0, self.last_progress, 98 | self.expected_size, elapsed_disp)) 99 | STREAM.write('\n') 100 | STREAM.flush() 101 | 102 | def format_time(self, seconds): 103 | return time.strftime('%H:%M:%S', time.gmtime(seconds)) 104 | 105 | 106 | def bar(it, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR, 107 | filled_char=BAR_FILLED_CHAR, expected_size=None, every=1): 108 | """Progress iterator. Wrap your iterables with it.""" 109 | 110 | count = len(it) if expected_size is None else expected_size 111 | 112 | with Bar(label=label, width=width, hide=hide, empty_char=empty_char, 113 | filled_char=filled_char, expected_size=count, every=every) \ 114 | as bar: 115 | for i, item in enumerate(it): 116 | yield item 117 | bar.show(i + 1) 118 | 119 | 120 | def dots(it, label='', hide=None, every=1): 121 | """Progress iterator. Prints a dot for each item being iterated""" 122 | 123 | count = 0 124 | 125 | if not hide: 126 | STREAM.write(label) 127 | 128 | for i, item in enumerate(it): 129 | if not hide: 130 | if i % every == 0: # True every "every" updates 131 | STREAM.write(DOTS_CHAR) 132 | sys.stderr.flush() 133 | 134 | count += 1 135 | 136 | yield item 137 | 138 | STREAM.write('\n') 139 | STREAM.flush() 140 | 141 | 142 | def mill(it, label='', hide=None, expected_size=None, every=1): 143 | """Progress iterator. Prints a mill while iterating over the items.""" 144 | 145 | def _mill_char(_i): 146 | if _i >= count: 147 | return ' ' 148 | else: 149 | return MILL_CHARS[(_i // every) % len(MILL_CHARS)] 150 | 151 | def _show(_i): 152 | if not hide: 153 | if ((_i % every) == 0 or # True every "every" updates 154 | (_i == count)): # And when we're done 155 | 156 | STREAM.write(MILL_TEMPLATE % ( 157 | label, _mill_char(_i), _i, count)) 158 | STREAM.flush() 159 | 160 | count = len(it) if expected_size is None else expected_size 161 | 162 | if count: 163 | _show(0) 164 | 165 | for i, item in enumerate(it): 166 | yield item 167 | _show(i + 1) 168 | 169 | if not hide: 170 | STREAM.write('\n') 171 | STREAM.flush() 172 | -------------------------------------------------------------------------------- /script/clint/textui/prompt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | """ 4 | clint.textui.prompt 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | Module for simple interactive prompts handling 8 | 9 | """ 10 | 11 | from __future__ import absolute_import, print_function 12 | 13 | from re import match, I 14 | import getpass 15 | 16 | from .core import puts 17 | from .colored import yellow 18 | from .validators import RegexValidator, OptionValidator 19 | 20 | try: 21 | raw_input 22 | except NameError: 23 | raw_input = input 24 | 25 | 26 | def yn(prompt, default='y', batch=False): 27 | # A sanity check against default value 28 | # If not y/n then y is assumed 29 | if default not in ['y', 'n']: 30 | default = 'y' 31 | 32 | # Let's build the prompt 33 | choicebox = '[Y/n]' if default == 'y' else '[y/N]' 34 | prompt = prompt + ' ' + choicebox + ' ' 35 | 36 | # If input is not a yes/no variant or empty 37 | # keep asking 38 | while True: 39 | # If batch option is True then auto reply 40 | # with default input 41 | if not batch: 42 | input = raw_input(prompt).strip() 43 | else: 44 | print(prompt) 45 | input = '' 46 | 47 | # If input is empty default choice is assumed 48 | # so we return True 49 | if input == '': 50 | return True 51 | 52 | # Given 'yes' as input if default choice is y 53 | # then return True, False otherwise 54 | if match('y(?:es)?', input, I): 55 | return True if default == 'y' else False 56 | 57 | # Given 'no' as input if default choice is n 58 | # then return True, False otherwise 59 | elif match('n(?:o)?', input, I): 60 | return True if default == 'n' else False 61 | 62 | 63 | def query(prompt, default='', validators=None, batch=False, mask_input=False): 64 | # Set the nonempty validator as default 65 | if validators is None: 66 | validators = [RegexValidator(r'.+')] 67 | 68 | # Let's build the prompt 69 | if prompt[-1] is not ' ': 70 | prompt += ' ' 71 | 72 | if default: 73 | prompt += '[' + default + '] ' 74 | 75 | # If input is not valid keep asking 76 | while True: 77 | # If batch option is True then auto reply 78 | # with default input 79 | if not batch: 80 | user_input_fn = getpass.getpass if mask_input else raw_input 81 | user_input = user_input_fn(prompt).strip() or default 82 | else: 83 | print(prompt) 84 | user_input = '' 85 | 86 | # Validate the user input 87 | try: 88 | for validator in validators: 89 | user_input = validator(user_input) 90 | return user_input 91 | except Exception as e: 92 | puts(yellow(e.message)) 93 | 94 | 95 | 96 | def options(prompt, options, default=None, batch=False): 97 | ''' 98 | 99 | :param prompt: 100 | :param options: 101 | this can be either a list of strings, in which case it will be presented like: 102 | prompt: 103 | (1) this is the first string 104 | (2) this is the second string 105 | (3) this is the third string 106 | 107 | please select 1-3: 108 | 109 | or a list of dictionaries in the format of: 110 | { { 'selector' : 'this is what the user will enter to select the option' 111 | 'prompt': 'this is the string that will be displayed, this can be omitted if the selector is also a prompt', 112 | 'return': 'this is what is returned to the calling procedure, if omitted, the option selector will be used' } 113 | 114 | so, to replicate the above, the dict could look like: 115 | 116 | [ {'selector':1,'prompt':'this is the first string','return':1}, 117 | {'selector':2,'prompt':'this is the second string','return':2}, 118 | {'selector':3,'prompt':'this is the third string'} 119 | 120 | :param default: should be set to the default selector (if desired) 121 | :param batch: True/False, will auto-return the default 122 | :return: 123 | ''' 124 | # Build fix options and build validator 125 | 126 | validator_list = [] 127 | return_dict = {} 128 | 129 | if isinstance(options[0],dict): 130 | for item in options: 131 | item['selector'] = str(item['selector']) 132 | item['prompt'] = str(item['prompt']) 133 | if 'return' not in item: 134 | item['return'] = item['selector'] 135 | validator_list.append(item['selector']) 136 | return_dict[item['selector']] = item['return'] 137 | else: 138 | options_strings = options 139 | options = [] 140 | for key, opt in enumerate(options_strings): 141 | item = {} 142 | item['selector'] = str(key+1) 143 | item['prompt'] = str(opt) 144 | item['return'] = key+1 145 | 146 | return_dict[item['selector']] = item['return'] 147 | validator_list.append(item['selector']) 148 | options.append(item) 149 | 150 | validators = [OptionValidator(validator_list)] 151 | 152 | # Let's build the prompt 153 | 154 | prompt += '\n' 155 | 156 | # building the options list 157 | for o in options: 158 | prompt += '[{selector}] {prompt}\n'.format(**o) 159 | 160 | prompt += '\n' 161 | 162 | if default: 163 | prompt += '[' + default + '] ' 164 | 165 | # If input is not valid keep asking 166 | while True: 167 | # If batch option is True then auto reply 168 | # with default input 169 | if not batch: 170 | user_input = raw_input(prompt).strip() or default 171 | else: 172 | print(prompt) 173 | user_input = '' 174 | 175 | 176 | # Validate the user input 177 | try: 178 | for validator in validators: 179 | user_input = validator(user_input) 180 | # convert user input to defined return value 181 | user_input = return_dict[user_input] 182 | return user_input 183 | except Exception as e: 184 | puts(yellow(e.message)) 185 | -------------------------------------------------------------------------------- /GUIApp/Core.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from tkinter import messagebox 3 | from tkinter.ttk import Progressbar 4 | import os 5 | import time 6 | import sys 7 | import json 8 | import requests 9 | from shutil import copy 10 | 11 | 12 | class WistiaDownloaderCore(): 13 | def __init__(self, master, videos, resolution, downloadFolder, btnBorderX, btnBorderY): 14 | self.master = master 15 | self.videos = videos 16 | self.resolution = resolution 17 | self.resolution_list = ["224p", "360p", "540p", "720p", "Best Quality"] 18 | self.downloadFolder = downloadFolder 19 | self.URL = "http://fast.wistia.net/embed/iframe/" 20 | self.videoIndex = 1 21 | self.logs = "" 22 | 23 | self.btnBorderX = btnBorderX 24 | self.btnBorderY = btnBorderY 25 | 26 | if self.dataCheck(): 27 | for video in self.videos: 28 | videoURL = self.URL + video.get('id') 29 | html_file = self.download_HTML_file(videoURL) 30 | self.html_page_handle_and_download(html_file, self.resolution, video.get('name')) 31 | try: 32 | self.downloadLable.configure(text='Download Completed') 33 | except: 34 | pass 35 | messagebox.showinfo( 36 | "Wistia Downloader", "Download completed, check logs for any error.") 37 | 38 | def dataCheck(self): 39 | if not self.videos: 40 | messagebox.showinfo( 41 | "Wistia Downloader", "You need to enter at least one ID.") 42 | return 0 43 | if not self.resolution in self.resolution_list: 44 | messagebox.showinfo( 45 | "Wistia Downloader", "You have to select a resolution from the list.") 46 | return 0 47 | if not self.downloadFolder: 48 | messagebox.showinfo( 49 | "Wistia Downloader", "You need to select the download folder.") 50 | return 0 51 | return 1 52 | 53 | def download_HTML_file(self, url): 54 | local_filename = url.split('/')[-1] 55 | r = requests.get(url, stream=True) 56 | with open(local_filename, 'wb') as f: 57 | for chunk in r.iter_content(chunk_size=1024): 58 | if chunk: 59 | f.write(chunk) 60 | return local_filename 61 | 62 | def html_page_handle_and_download(self, file_name, resolution, viedo_name): 63 | with open(file_name, "r") as file: 64 | i = 1 65 | for con in file: 66 | # ERROR 67 | if i == 1 and con == '{"error":true,"iframe":true}': 68 | video_data = con 69 | if i == 63: 70 | video_data = con 71 | break 72 | i += 1 73 | if video_data == '{"error":true,"iframe":true}': 74 | self.logs += '{} ID {}, is not valid.\n'.format( 75 | viedo_name, 76 | self.videoIndex) 77 | self.showLogs(self.logs) 78 | self.videoIndex += 1 79 | else: 80 | video_data = video_data.split('[')[1].split(']')[0] 81 | video_data = video_data.replace('},{', '}abdlalisalmi{') 82 | video_data_list = video_data.split('abdlalisalmi') 83 | 84 | video_url = None 85 | for video in video_data_list: 86 | video_data_json = json.loads(video) 87 | if video_data_json['display_name'] == resolution: 88 | video_url = video_data_json['url'] 89 | 90 | self.showProgressBar() 91 | self.downloadLable.configure( 92 | text='{} is Downloading...'.format(viedo_name)) 93 | 94 | if (video_url): 95 | video_file_name = self.download_video(video_url) 96 | 97 | video_name_and_index = "{}-{}".format( 98 | str(self.videoIndex), viedo_name) 99 | self.videoIndex += 1 100 | os.rename(video_file_name, video_name_and_index) 101 | else: 102 | video_url = json.loads(video_data_list[0])['url'] 103 | print(json.loads(video_data_list[0])) 104 | video_file_name = self.download_video(video_url) 105 | 106 | video_name_and_index = "{}-{}".format( 107 | str(self.videoIndex), viedo_name) 108 | self.videoIndex += 1 109 | os.rename(video_file_name, video_name_and_index) 110 | 111 | os.remove(file_name) 112 | 113 | def download_video(self, url): 114 | video_name = url.split('/')[-1] 115 | reponse = requests.get(url, stream=True) 116 | with open(video_name, 'wb') as video: 117 | total_length = int(reponse.headers.get('content-length')) 118 | total_received = 0 119 | for chunk in reponse.iter_content(chunk_size=1024): 120 | if chunk: 121 | progress = ((total_received / total_length) * 100) + 1 122 | self.progressBar['value'] = progress 123 | self.progressLabel.configure( 124 | text='{}%'.format(int(progress))) 125 | self.progressBar.update() 126 | 127 | video.write(chunk) 128 | total_received += 1024 129 | 130 | return video_name 131 | 132 | def showProgressBar(self): 133 | self.progressBar = Progressbar(self.master, length=300) 134 | self.progressBar['value'] = 0 135 | 136 | self.downloadLable = Label(self.master, text="Downloading...", 137 | fg='#2c3e50', font=("Arial Bold", 9)) 138 | self.downloadLable.place( 139 | x=self.btnBorderX + 10, y=self.btnBorderY + 187) 140 | 141 | self.progressLabel = Label(self.master, text='0%', 142 | fg='#2c3e50', font=("Arial Bold", 10)) 143 | self.progressLabel.place( 144 | x=self.btnBorderX + 10, y=self.btnBorderY + 210) 145 | Label(self.master, text="100%", 146 | fg='#2c3e50', font=("Arial Bold", 10)).place(x=self.btnBorderX + 340, y=self.btnBorderY + 210) 147 | 148 | self.progressBar.place(x=self.btnBorderX + 40, y=self.btnBorderY + 210) 149 | 150 | def showLogs(self, error): 151 | self.errorLabel = Label(self.master, text='Logs:', 152 | fg='#2c3e50', font=("Arial Bold", 10)) 153 | self.errorLabel.place( 154 | x=self.btnBorderX + 10, y=self.btnBorderY + 245) 155 | 156 | self.errorField = Text(self.master, background="#ff7979", borderwidth=2, height=3, 157 | width=52, font=("Arial Bold", 8)) 158 | self.errorField.place(x=self.btnBorderX + 10, y=self.btnBorderY + 265) 159 | self.errorField.insert( 160 | END, error) 161 | self.errorField.config(state=DISABLED) 162 | -------------------------------------------------------------------------------- /script/clint/packages/colorama/ansitowin32.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | import sys 4 | 5 | from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style 6 | from .winterm import WinTerm, WinColor, WinStyle 7 | from .win32 import windll 8 | 9 | 10 | if windll is not None: 11 | winterm = WinTerm() 12 | 13 | 14 | def is_a_tty(stream): 15 | return hasattr(stream, 'isatty') and stream.isatty() 16 | 17 | 18 | class StreamWrapper(object): 19 | ''' 20 | Wraps a stream (such as stdout), acting as a transparent proxy for all 21 | attribute access apart from method 'write()', which is delegated to our 22 | Converter instance. 23 | ''' 24 | def __init__(self, wrapped, converter): 25 | # double-underscore everything to prevent clashes with names of 26 | # attributes on the wrapped stream object. 27 | self.__wrapped = wrapped 28 | self.__convertor = converter 29 | 30 | def __getattr__(self, name): 31 | return getattr(self.__wrapped, name) 32 | 33 | def write(self, text): 34 | self.__convertor.write(text) 35 | 36 | 37 | class AnsiToWin32(object): 38 | ''' 39 | Implements a 'write()' method which, on Windows, will strip ANSI character 40 | sequences from the text, and if outputting to a tty, will convert them into 41 | win32 function calls. 42 | ''' 43 | ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') 44 | 45 | def __init__(self, wrapped, convert=None, strip=None, autoreset=False): 46 | # The wrapped stream (normally sys.stdout or sys.stderr) 47 | self.wrapped = wrapped 48 | 49 | # should we reset colors to defaults after every .write() 50 | self.autoreset = autoreset 51 | 52 | # create the proxy wrapping our output stream 53 | self.stream = StreamWrapper(wrapped, self) 54 | 55 | on_windows = sys.platform.startswith('win') 56 | 57 | # should we strip ANSI sequences from our output? 58 | if strip is None: 59 | strip = on_windows 60 | self.strip = strip 61 | 62 | # should we should convert ANSI sequences into win32 calls? 63 | if convert is None: 64 | convert = on_windows and is_a_tty(wrapped) 65 | self.convert = convert 66 | 67 | # dict of ansi codes to win32 functions and parameters 68 | self.win32_calls = self.get_win32_calls() 69 | 70 | # are we wrapping stderr? 71 | self.on_stderr = self.wrapped is sys.stderr 72 | 73 | 74 | def should_wrap(self): 75 | ''' 76 | True if this class is actually needed. If false, then the output 77 | stream will not be affected, nor will win32 calls be issued, so 78 | wrapping stdout is not actually required. This will generally be 79 | False on non-Windows platforms, unless optional functionality like 80 | autoreset has been requested using kwargs to init() 81 | ''' 82 | return self.convert or self.strip or self.autoreset 83 | 84 | 85 | def get_win32_calls(self): 86 | if self.convert and winterm: 87 | return { 88 | AnsiStyle.RESET_ALL: (winterm.reset_all, ), 89 | AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), 90 | AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), 91 | AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), 92 | AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), 93 | AnsiFore.RED: (winterm.fore, WinColor.RED), 94 | AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), 95 | AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), 96 | AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), 97 | AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), 98 | AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), 99 | AnsiFore.WHITE: (winterm.fore, WinColor.GREY), 100 | AnsiFore.RESET: (winterm.fore, ), 101 | AnsiBack.BLACK: (winterm.back, WinColor.BLACK), 102 | AnsiBack.RED: (winterm.back, WinColor.RED), 103 | AnsiBack.GREEN: (winterm.back, WinColor.GREEN), 104 | AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), 105 | AnsiBack.BLUE: (winterm.back, WinColor.BLUE), 106 | AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), 107 | AnsiBack.CYAN: (winterm.back, WinColor.CYAN), 108 | AnsiBack.WHITE: (winterm.back, WinColor.GREY), 109 | AnsiBack.RESET: (winterm.back, ), 110 | } 111 | 112 | 113 | def write(self, text): 114 | if self.strip or self.convert: 115 | self.write_and_convert(text) 116 | else: 117 | self.wrapped.write(text) 118 | self.wrapped.flush() 119 | if self.autoreset: 120 | self.reset_all() 121 | 122 | 123 | def reset_all(self): 124 | if self.convert: 125 | self.call_win32('m', (0,)) 126 | elif is_a_tty(self.wrapped): 127 | self.wrapped.write(Style.RESET_ALL) 128 | 129 | 130 | def write_and_convert(self, text): 131 | ''' 132 | Write the given text to our wrapped stream, stripping any ANSI 133 | sequences from the text, and optionally converting them into win32 134 | calls. 135 | ''' 136 | cursor = 0 137 | for match in self.ANSI_RE.finditer(text): 138 | start, end = match.span() 139 | self.write_plain_text(text, cursor, start) 140 | self.convert_ansi(*match.groups()) 141 | cursor = end 142 | self.write_plain_text(text, cursor, len(text)) 143 | 144 | 145 | def write_plain_text(self, text, start, end): 146 | if start < end: 147 | self.wrapped.write(text[start:end]) 148 | self.wrapped.flush() 149 | 150 | 151 | def convert_ansi(self, paramstring, command): 152 | if self.convert: 153 | params = self.extract_params(paramstring) 154 | self.call_win32(command, params) 155 | 156 | 157 | def extract_params(self, paramstring): 158 | def split(paramstring): 159 | for p in paramstring.split(';'): 160 | if p != '': 161 | yield int(p) 162 | return tuple(split(paramstring)) 163 | 164 | 165 | def call_win32(self, command, params): 166 | if params == []: 167 | params = [0] 168 | if command == 'm': 169 | for param in params: 170 | if param in self.win32_calls: 171 | func_args = self.win32_calls[param] 172 | func = func_args[0] 173 | args = func_args[1:] 174 | kwargs = dict(on_stderr=self.on_stderr) 175 | func(*args, **kwargs) 176 | elif command in ('H', 'f'): # set cursor position 177 | func = winterm.set_cursor_position 178 | func(params, on_stderr=self.on_stderr) 179 | elif command in ('J'): 180 | func = winterm.erase_data 181 | func(params, on_stderr=self.on_stderr) 182 | 183 | -------------------------------------------------------------------------------- /script/wistia-downloader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sys 4 | import json 5 | import requests 6 | import getpass 7 | from shutil import copy 8 | from clint.textui import progress # for the progress effects 9 | 10 | video_index = 1 11 | 12 | 13 | def logo(): 14 | sprint("""\033[1;33;40m 15 | _ _ _ _ _ _____ 16 | | || || (_) _ (_) (____ \\ 17 | | || || |_ ___| |_ _ ____ _ \ \ ___ _ _ _ ____ 18 | | ||_|| | |/___) _)| |/ _ | | | | / _ \| | | | _ \\ 19 | | |___| | |___ | |__| ( ( | | | |__/ / |_| | | | | | | | 20 | \______|_(___/ \___)_|\_||_| |_____/ \___/ \____|_| |_| 21 | https://github.com/abdlalisalmi 22 | \033[0;37;40m""", 0.1) 23 | 24 | 25 | def sprint(string, tm=10): 26 | for c in string + '\n': 27 | sys.stdout.write(c) 28 | sys.stdout.flush() 29 | time.sleep(tm / 100) 30 | 31 | 32 | def download_video(url): 33 | video_name = url.split('/')[-1] 34 | reponse = requests.get(url, stream=True) 35 | with open(video_name, 'wb') as video: 36 | total_length = int(reponse.headers.get('content-length')) 37 | for chunk in progress.bar(reponse.iter_content(chunk_size=1024), expected_size=(total_length/1024) + 1): 38 | if chunk: 39 | video.write(chunk) 40 | video.flush() 41 | return video_name 42 | 43 | 44 | def html_page_handle_and_download(file_name, resolution): 45 | print("\033[1;32;40m") 46 | global video_index 47 | with open(file_name, "r") as file: 48 | i = 1 49 | for con in file: 50 | # ERROR 51 | if i == 1 and con == '{"error":true,"iframe":true}': 52 | video_data = con 53 | if i == 63: 54 | video_data = con 55 | break 56 | i += 1 57 | if video_data == '{"error":true,"iframe":true}': 58 | sprint("\033[1;31;40mYour video id is not valid\033[0;37;40m", 2) 59 | else: 60 | video_data = video_data.split('[')[1].split(']')[0] 61 | video_data = video_data.replace('},{', '}abdlalisalmi{') 62 | video_data_list = video_data.split('abdlalisalmi') 63 | 64 | resolution_dic = { 65 | '1': '224p', 66 | '2': '360p', 67 | '3': '540p', 68 | '4': '720p', 69 | '5': 'Original file' 70 | } 71 | video_url = None 72 | for video in video_data_list: 73 | video_data_json = json.loads(video) 74 | if video_data_json['display_name'] == resolution_dic[str(resolution)]: 75 | video_url = video_data_json['url'] 76 | if (video_url): 77 | video_name = download_video(video_url) 78 | 79 | video_name_and_index = "Video{}.mp4".format(str(video_index)) 80 | video_index += 1 81 | os.rename(video_name, video_name_and_index) 82 | else: 83 | print( 84 | "\033[1;31;40mThe resulotion ({}) does not exist for this video, we will download it with the original resolution.\033[0;37;40m".format(resolution_dic[str(resolution)])) 85 | video_url = json.loads(video_data_list[0])['url'] 86 | print(json.loads(video_data_list[0])) 87 | video_name = download_video(video_url) 88 | 89 | video_name_and_index = "Video{}.mp4".format(str(video_index)) 90 | video_index += 1 91 | os.rename(video_name, video_name_and_index) 92 | 93 | os.remove(file_name) 94 | print("\033[0;37;40m") 95 | 96 | 97 | def download_folder(): 98 | try: 99 | username = getpass.getuser() 100 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 101 | 102 | sprint( 103 | "\033[1;32;40mPlease enter the PATH of download folder: \033[0;37;40m", 2) 104 | sprint( 105 | "\033[1;31;40m\t\tExample: \033[0;37;40m/Users/{}/Desktop".format(username), 2) 106 | sprint( 107 | "\033[1;31;40m\t\tExample: \033[0;37;40m/Volumes/Storage/goinfre/{}".format(username), 2) 108 | sprint( 109 | "\033[1;31;40m\t\tExample: \033[0;37;40m{}".format(BASE_DIR), 2) 110 | 111 | folder_path = input("PATH: \033[1;33;40m") 112 | print("\033[0;37;40m") 113 | 114 | slash = '/' if '/' in folder_path else '\\' 115 | if folder_path[-1] == slash: 116 | folder_path += "wistia-downloader" 117 | else: 118 | folder_path += slash + "wistia-downloader" 119 | 120 | os.mkdir(folder_path) 121 | copy('video_ID.txt', folder_path) 122 | 123 | os.chdir("{}".format(folder_path)) 124 | sprint("\033[1;33;40mYou will find the videos in {}\033[0;37;40m".format( 125 | folder_path), 2) 126 | 127 | except FileNotFoundError: 128 | print("\033[1;31;40mThe PATH does not exist !! try again\033[0;37;40m") 129 | sprint(".....", 100) 130 | main() 131 | except FileExistsError: 132 | print( 133 | "\033[1;31;40mThe wistia-downloader folder is exist !! try another PATH\033[0;37;40m") 134 | sprint(".....", 100) 135 | main() 136 | 137 | 138 | def resolution_select(): 139 | resolution = 0 140 | print('') 141 | sprint( 142 | "\033[1;32;40mPlease select download resolution:\033[0;37;40m", 2) 143 | sprint( 144 | "\033[1;31;40m\t\t1) \033[0;37;40m224p", 2) 145 | sprint( 146 | "\033[1;31;40m\t\t2) \033[0;37;40m360p", 2) 147 | sprint( 148 | "\033[1;31;40m\t\t3) \033[0;37;40m540p", 2) 149 | sprint( 150 | "\033[1;31;40m\t\t4) \033[0;37;40m720p", 2) 151 | sprint( 152 | "\033[1;31;40m\t\t5) \033[0;37;40mBest Quality", 2) 153 | choices = ['1', '2', '3', '4', '5'] 154 | while not resolution in choices: 155 | resolution = input('> ') 156 | if not resolution in choices: 157 | print('Please select a number between 1 and 5.') 158 | return int(resolution) 159 | 160 | 161 | def download_file(url): 162 | local_filename = url.split('/')[-1] 163 | r = requests.get(url, stream=True) 164 | with open(local_filename, 'wb') as f: 165 | for chunk in r.iter_content(chunk_size=1024): 166 | if chunk: 167 | f.write(chunk) 168 | return local_filename 169 | 170 | 171 | def main(): 172 | os.system('cls' if os.name == 'nt' else 'clear') 173 | logo() 174 | 175 | url = "http://fast.wistia.net/embed/iframe/" 176 | 177 | i = 0 178 | with open("video_ID.txt", "r+") as video_id: 179 | if os.stat("video_ID.txt").st_size == 0: 180 | print("") 181 | sprint( 182 | "\033[1;31;40mThe file video_ID.txt is empty !!\033[0;37;40m", 0.2) 183 | sprint(".....", 100) 184 | else: 185 | start = time.time() 186 | download_folder() 187 | resolution = resolution_select() 188 | 189 | for id in video_id: 190 | 191 | if '\n' in id: 192 | new_id = id[:-1] 193 | else: 194 | new_id = id 195 | 196 | video_url = url + new_id 197 | local_filename = download_file(video_url) 198 | print("Video ID " + str(i + 1) + 199 | "--------------------------------------------------------------------------") 200 | html_page_handle_and_download( 201 | local_filename, resolution) 202 | i += 1 203 | 204 | end = time.time() 205 | sprint( 206 | "\033[1;33;40mDownload finished, Total time {}\033[0;37;40m".format(end - start), 2) 207 | 208 | 209 | if __name__ == '__main__': 210 | main() 211 | -------------------------------------------------------------------------------- /GUIApp/GUI.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from tkinter import messagebox 3 | from tkinter.ttk import Combobox 4 | from tkinter import filedialog 5 | from tkinter.ttk import Progressbar 6 | import webbrowser 7 | 8 | 9 | import os 10 | import sys 11 | import time 12 | 13 | from Core import WistiaDownloaderCore 14 | 15 | 16 | class WistiaDownloaderGUI(): 17 | def __init__(self, master, screen_width, screen_height): 18 | self.master = master 19 | 20 | self.screen_width = screen_width 21 | self.screen_height = screen_height 22 | 23 | self.borderX = int(screen_width * 0.06) 24 | self.borderY = int(screen_width - (screen_width * 0.06)) 25 | 26 | self.btnBorderX = int(self.screen_width / 2.5) 27 | self.btnBorderY = int(self.screen_height / 5) 28 | 29 | self.videos = [] 30 | 31 | self.IDsList = [] 32 | self.resolution = "224p" 33 | self.downloadFolder = None 34 | 35 | self.showLogo() 36 | self.showTextInput() 37 | self.showImmport() 38 | self.showResolutionSelect() 39 | self.showDownloadFolderSelect() 40 | self.showDownloadBtn() 41 | self.showLogs() 42 | self.showProgressBar() 43 | self.showHelpBtn() 44 | self.showQuitBtn() 45 | 46 | def showLogo(self): 47 | Label(self.master, text="Wistia Downloader ", 48 | fg='#574b90', font=('Helvetica', 30)).place(x=int(self.screen_width/3.6), y=15) 49 | Label(self.master, text="https://github.com/abdlalisalmi", 50 | fg='#786fa6', font=("Arial Bold", 10)).place(x=int(self.screen_width/3.6) + 5, y=55) 51 | 52 | def showImmport(self): 53 | # Importing the IDs from a file 54 | def importIDsFromFile(): 55 | filePATH = filedialog.askopenfilename( 56 | initialdir=os.path.dirname(os.path.realpath(__file__)), 57 | title="Select file", 58 | filetypes=(("text files", "*.txt"),) 59 | ) 60 | if filePATH: 61 | with open(filePATH, 'r') as f: 62 | 63 | content = f.read().split('\n') 64 | content = list(filter(None, content)) 65 | self.video_data_handle(content) 66 | 67 | i = 1 68 | str_content = "" 69 | for video in self.videos: 70 | str_content += '{}-{}'.format(i, video['name']) + '\n' 71 | i = i + 1 72 | self.textArea.config(state=NORMAL) 73 | self.textArea.insert(END, str_content) 74 | self.textArea.config(state=DISABLED) 75 | 76 | Button( 77 | self.master, font=("Arial Bold", 8), 78 | fg="white", bg='#5799cf', 79 | text="Import", command=importIDsFromFile 80 | ).place(x=self.borderX + 130, y=122) 81 | 82 | def showTextInput(self): 83 | 84 | self.showBorder(self.borderX, self.btnBorderY+10, width=25, height=2) 85 | 86 | self.textInputLable = Label(self.master, text="Import Videos IDs File:", 87 | fg='#303952', font=("Arial Bold", 10)) 88 | self.textInputLable.place(x=self.borderX, y=self.btnBorderY) 89 | 90 | self.textArea = Text(self.master, bg='#dff9fb', borderwidth=2, height=19, 91 | width=25, font=("Arial Bold", 9)) 92 | self.textArea.place(x=self.borderX, y=160) 93 | self.textArea.config(state=DISABLED) 94 | 95 | def showBorder(self, x, y, height=2, width=48): 96 | Label(self.master, 97 | borderwidth=2, 98 | width=width, 99 | height=height, 100 | relief="ridge",).place(x=x, y=y) 101 | 102 | def showResolutionSelect(self): 103 | self.showBorder(self.btnBorderX, self.btnBorderY+10) 104 | resolutionLable = Label(self.master, 105 | text="Select The Resolution:", 106 | fg='#303952', font=("Arial Bold", 10)) 107 | resolutionLable.place(x=self.btnBorderX, y=self.btnBorderY) 108 | 109 | self.resolutions_list = {"224p": "224p", 110 | "360p": "360p", 111 | "540p": "540p", 112 | "720p": "720p", 113 | "Best Quality": "Best Quality"} 114 | 115 | def choiceClicked(): 116 | self.resolution = choice.get() 117 | choice = StringVar(self.master, "224p") 118 | x = self.btnBorderX + 6 119 | for (resolution, value) in self.resolutions_list.items(): 120 | Radiobutton(self.master, 121 | text=resolution, 122 | variable=choice, 123 | value=value, 124 | command=choiceClicked 125 | ).place(x=x, y=self.btnBorderY + 20) 126 | x = x + 65 127 | 128 | def showDownloadFolderSelect(self): 129 | self.showBorder(self.btnBorderX, y=self.btnBorderY + 75) 130 | 131 | def selectDownloadFolder(): 132 | self.downloadFolder = filedialog.askdirectory() 133 | try: 134 | self.downloadPATH.destroy() 135 | except: 136 | pass 137 | self.downloadPATH = Label(self.master, text=self.downloadFolder, 138 | fg='#303952', font=("Arial Bold", 7)) 139 | self.downloadPATH.place( 140 | x=self.btnBorderX + 5, y=self.btnBorderY + 90) 141 | 142 | folderLable = Label(self.master, text="Select The Download Folder:", 143 | fg='#2c3e50', font=("Arial Bold", 10)) 144 | folderLable.place(x=self.btnBorderX, y=self.btnBorderY + 65) 145 | 146 | self.selectDownloadFolderBtn = Button( 147 | self.master, font=("Arial Bold", 9), 148 | fg="white", bg='#6a89cc', text="Browse", 149 | command=selectDownloadFolder 150 | ) 151 | self.selectDownloadFolderBtn.place( 152 | x=self.btnBorderX + 310, y=self.btnBorderY + 80) 153 | 154 | def showDownloadBtn(self): 155 | self.showBorder(self.btnBorderX, y=self.btnBorderY + 140, height=10) 156 | 157 | Label(self.master, text="Download:", 158 | fg='#303952', font=("Arial Bold", 10) 159 | ).place(x=self.btnBorderX, y=self.btnBorderY + 130) 160 | self.downloadBtn = Button( 161 | self.master, text="Start Download", 162 | fg="white", bg='#079992', 163 | command=self.download 164 | ) 165 | self.downloadBtn.place(x=self.btnBorderX + 253, 166 | y=self.btnBorderY + 150) 167 | 168 | def showQuitBtn(self): 169 | frame = Frame(self.master) 170 | frame.place(x=int(self.screen_width - (self.borderX * 2)), 171 | y=int(self.screen_height - (self.borderX * 1.2))) 172 | self.quit = Button(frame, 173 | text="QUIT", fg="white", bg='#ff3f34', 174 | command=frame.quit) 175 | self.quit.grid(row=0, column=0) 176 | 177 | def showHelpBtn(self): 178 | def openweb(): 179 | url = 'https://github.com/abdlalisalmi/wistia-downloader/blob/master/README.md' 180 | webbrowser.open(url) 181 | 182 | frame = Frame(self.master) 183 | frame.place(x=int(self.screen_width - (self.borderX * 4)), 184 | y=int(self.screen_height - (self.borderX * 1.2))) 185 | self.quit = Button(frame, 186 | text="Help", fg="white", bg='#ff793f', 187 | command=openweb) 188 | self.quit.grid(row=0, column=0) 189 | 190 | # the function called when we click on Start Download button 191 | def download(self): 192 | self.downloadBtn['state'] = DISABLED 193 | core = WistiaDownloaderCore( 194 | self.master, self.videos, 195 | self.resolution, self.downloadFolder, 196 | self.btnBorderX, self.btnBorderY, 197 | ) 198 | self.downloadBtn['state'] = NORMAL 199 | 200 | def showProgressBar(self): 201 | self.progressBar = Progressbar(self.master, length=300) 202 | self.progressBar['value'] = 0 203 | 204 | self.progressLabel = Label(self.master, text='0%', 205 | fg='#2c3e50', font=("Arial Bold", 10)) 206 | self.progressLabel.place( 207 | x=self.btnBorderX + 10, y=self.btnBorderY + 210) 208 | Label(self.master, text="100%", 209 | fg='#2c3e50', font=("Arial Bold", 10)).place(x=self.btnBorderX + 340, y=self.btnBorderY + 210) 210 | 211 | self.progressBar.place(x=self.btnBorderX + 40, y=self.btnBorderY + 210) 212 | 213 | def showLogs(self): 214 | self.logsLabel = Label(self.master, text='Logs:', 215 | fg='#2c3e50', font=("Arial Bold", 10)) 216 | self.logsLabel.place( 217 | x=self.btnBorderX + 10, y=self.btnBorderY + 245) 218 | 219 | self.logsField = Text(self.master, background='#d1d8e0', borderwidth=2, height=3, 220 | width=52, font=("Arial Bold", 8)) 221 | self.logsField.place(x=self.btnBorderX + 10, y=self.btnBorderY + 265) 222 | self.logsField.config(state=DISABLED) 223 | 224 | def video_data_handle(self, content): 225 | try: 226 | for line in content: 227 | line = line.split('

')[2] 228 | video_id = line.split('"')[1].split('=')[1] 229 | video_name = line.split('>')[1].split('<')[0] 230 | self.videos.append({'id': video_id, 'name': video_name}) 231 | except: 232 | messagebox.showinfo( 233 | "Wistia Downloader", "You Need To Enter a Valid Videos File IDs.") -------------------------------------------------------------------------------- /script/clint/arguments.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | clint.arguments 5 | ~~~~~~~~~~~~~~~ 6 | 7 | This module provides the CLI argument interface. 8 | 9 | """ 10 | 11 | 12 | from __future__ import absolute_import 13 | 14 | import os 15 | from sys import argv 16 | 17 | try: 18 | from collections import OrderedDict 19 | except ImportError: 20 | from .packages.ordereddict import OrderedDict 21 | 22 | from .utils import expand_path, is_collection 23 | 24 | __all__ = ('Args', ) 25 | 26 | 27 | class Args(object): 28 | """CLI Argument management.""" 29 | 30 | def __init__(self, args=None, no_argv=False): 31 | if not args: 32 | if not no_argv: 33 | self._args = argv[1:] 34 | else: 35 | self._args = [] 36 | else: 37 | self._args = args 38 | 39 | 40 | def __len__(self): 41 | return len(self._args) 42 | 43 | 44 | def __repr__(self): 45 | return '' % (repr(self._args)) 46 | 47 | 48 | def __getitem__(self, i): 49 | try: 50 | return self.all[i] 51 | except IndexError: 52 | return None 53 | 54 | 55 | def __contains__(self, x): 56 | return self.first(x) is not None 57 | 58 | 59 | def get(self, x): 60 | """Returns argument at given index, else none.""" 61 | try: 62 | return self.all[x] 63 | except IndexError: 64 | return None 65 | 66 | 67 | def get_with(self, x): 68 | """Returns first argument that contains given string.""" 69 | return self.all[self.first_with(x)] 70 | 71 | 72 | def remove(self, x): 73 | """Removes given arg (or list thereof) from Args object.""" 74 | 75 | def _remove(x): 76 | found = self.first(x) 77 | if found is not None: 78 | self._args.pop(found) 79 | 80 | if is_collection(x): 81 | for item in x: 82 | _remove(x) 83 | else: 84 | _remove(x) 85 | 86 | 87 | def pop(self, x): 88 | """Removes and Returns value at given index, else none.""" 89 | try: 90 | return self._args.pop(x) 91 | except IndexError: 92 | return None 93 | 94 | 95 | def any_contain(self, x): 96 | """Tests if given string is contained in any stored argument.""" 97 | 98 | return bool(self.first_with(x)) 99 | 100 | 101 | def contains(self, x): 102 | """Tests if given object is in arguments list. 103 | Accepts strings and lists of strings.""" 104 | 105 | return self.__contains__(x) 106 | 107 | 108 | def first(self, x): 109 | """Returns first found index of given value (or list of values)""" 110 | 111 | def _find( x): 112 | try: 113 | return self.all.index(str(x)) 114 | except ValueError: 115 | return None 116 | 117 | if is_collection(x): 118 | for item in x: 119 | found = _find(item) 120 | if found is not None: 121 | return found 122 | return None 123 | else: 124 | return _find(x) 125 | 126 | 127 | def first_with(self, x): 128 | """Returns first found index containing value (or list of values)""" 129 | 130 | def _find(x): 131 | try: 132 | for arg in self.all: 133 | if x in arg: 134 | return self.all.index(arg) 135 | except ValueError: 136 | return None 137 | 138 | if is_collection(x): 139 | for item in x: 140 | found = _find(item) 141 | if found: 142 | return found 143 | return None 144 | else: 145 | return _find(x) 146 | 147 | 148 | def first_without(self, x): 149 | """Returns first found index not containing value (or list of values)""" 150 | 151 | def _find(x): 152 | try: 153 | for arg in self.all: 154 | if x not in arg: 155 | return self.all.index(arg) 156 | except ValueError: 157 | return None 158 | 159 | if is_collection(x): 160 | for item in x: 161 | found = _find(item) 162 | if found: 163 | return found 164 | return None 165 | else: 166 | return _find(x) 167 | 168 | 169 | def start_with(self, x): 170 | """Returns all arguments beginning with given string (or list thereof)""" 171 | 172 | _args = [] 173 | 174 | for arg in self.all: 175 | if is_collection(x): 176 | for _x in x: 177 | if arg.startswith(x): 178 | _args.append(arg) 179 | break 180 | else: 181 | if arg.startswith(x): 182 | _args.append(arg) 183 | 184 | return Args(_args, no_argv=True) 185 | 186 | 187 | def contains_at(self, x, index): 188 | """Tests if given [list of] string is at given index.""" 189 | 190 | try: 191 | if is_collection(x): 192 | for _x in x: 193 | if (_x in self.all[index]) or (_x == self.all[index]): 194 | return True 195 | else: 196 | return False 197 | else: 198 | return (x in self.all[index]) 199 | 200 | except IndexError: 201 | return False 202 | 203 | 204 | def has(self, x): 205 | """Returns true if argument exists at given index. 206 | Accepts: integer. 207 | """ 208 | 209 | try: 210 | self.all[x] 211 | return True 212 | except IndexError: 213 | return False 214 | 215 | 216 | def value_after(self, x): 217 | """Returns value of argument after given found argument (or list thereof).""" 218 | 219 | try: 220 | try: 221 | i = self.all.index(x) 222 | except ValueError: 223 | return None 224 | 225 | return self.all[i + 1] 226 | 227 | except IndexError: 228 | return None 229 | 230 | 231 | @property 232 | def grouped(self): 233 | """Extracts --flag groups from argument list. 234 | Returns {format: Args, ...} 235 | """ 236 | 237 | collection = OrderedDict(_=Args(no_argv=True)) 238 | 239 | _current_group = None 240 | 241 | for arg in self.all: 242 | if arg.startswith('-'): 243 | _current_group = arg 244 | collection.setdefault(arg, Args(no_argv=True)) 245 | else: 246 | if _current_group: 247 | collection[_current_group]._args.append(arg) 248 | else: 249 | collection['_']._args.append(arg) 250 | 251 | return collection 252 | 253 | 254 | @property 255 | def last(self): 256 | """Returns last argument.""" 257 | 258 | try: 259 | return self.all[-1] 260 | except IndexError: 261 | return None 262 | 263 | 264 | @property 265 | def all(self): 266 | """Returns all arguments.""" 267 | 268 | return self._args 269 | 270 | 271 | def all_with(self, x): 272 | """Returns all arguments containing given string (or list thereof)""" 273 | 274 | _args = [] 275 | 276 | for arg in self.all: 277 | if is_collection(x): 278 | for _x in x: 279 | if _x in arg: 280 | _args.append(arg) 281 | break 282 | else: 283 | if x in arg: 284 | _args.append(arg) 285 | 286 | return Args(_args, no_argv=True) 287 | 288 | 289 | def all_without(self, x): 290 | """Returns all arguments not containing given string (or list thereof)""" 291 | 292 | _args = [] 293 | 294 | for arg in self.all: 295 | if is_collection(x): 296 | for _x in x: 297 | if _x not in arg: 298 | _args.append(arg) 299 | break 300 | else: 301 | if x not in arg: 302 | _args.append(arg) 303 | 304 | return Args(_args, no_argv=True) 305 | 306 | 307 | @property 308 | def flags(self): 309 | """Returns Arg object including only flagged arguments.""" 310 | 311 | return self.start_with('-') 312 | 313 | 314 | @property 315 | def not_flags(self): 316 | """Returns Arg object excluding flagged arguments.""" 317 | 318 | return self.all_without('-') 319 | 320 | 321 | @property 322 | def files(self, absolute=False): 323 | """Returns an expanded list of all valid paths that were passed in.""" 324 | 325 | _paths = [] 326 | 327 | for arg in self.all: 328 | for path in expand_path(arg): 329 | if os.path.exists(path): 330 | if absolute: 331 | _paths.append(os.path.abspath(path)) 332 | else: 333 | _paths.append(path) 334 | 335 | return _paths 336 | 337 | 338 | @property 339 | def not_files(self): 340 | """Returns a list of all arguments that aren't files/globs.""" 341 | 342 | _args = [] 343 | 344 | for arg in self.all: 345 | if not len(expand_path(arg)): 346 | if not os.path.exists(arg): 347 | _args.append(arg) 348 | 349 | return Args(_args, no_argv=True) 350 | 351 | @property 352 | def copy(self): 353 | """Returns a copy of Args object for temporary manipulation.""" 354 | 355 | return Args(self.all) 356 | 357 | -------------------------------------------------------------------------------- /script/clint/packages/appdirs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2005-2010 ActiveState Software Inc. 3 | 4 | """Utilities for determining application-specific dirs. 5 | 6 | See for details and usage. 7 | """ 8 | # Dev Notes: 9 | # - MSDN on where to store app data files: 10 | # http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 11 | # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html 12 | # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html 13 | 14 | __version_info__ = (1, 2, 0) 15 | __version__ = '.'.join(map(str, __version_info__)) 16 | 17 | 18 | import sys 19 | import os 20 | 21 | PY3 = sys.version_info[0] == 3 22 | 23 | if PY3: 24 | unicode = str 25 | 26 | class AppDirsError(Exception): 27 | pass 28 | 29 | 30 | 31 | def user_data_dir(appname, appauthor=None, version=None, roaming=False): 32 | r"""Return full path to the user-specific data dir for this application. 33 | 34 | "appname" is the name of application. 35 | "appauthor" (only required and used on Windows) is the name of the 36 | appauthor or distributing body for this application. Typically 37 | it is the owning company name. 38 | "version" is an optional version path element to append to the 39 | path. You might want to use this if you want multiple versions 40 | of your app to be able to run independently. If used, this 41 | would typically be ".". 42 | "roaming" (boolean, default False) can be set True to use the Windows 43 | roaming appdata directory. That means that for users on a Windows 44 | network setup for roaming profiles, this user data will be 45 | sync'd on login. See 46 | 47 | for a discussion of issues. 48 | 49 | Typical user data directories are: 50 | Mac OS X: ~/Library/Application Support/ 51 | Unix: ~/.config/ # or in $XDG_CONFIG_HOME if defined 52 | Win XP (not roaming): C:\Documents and Settings\\Application Data\\ 53 | Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ 54 | Win 7 (not roaming): C:\Users\\AppData\Local\\ 55 | Win 7 (roaming): C:\Users\\AppData\Roaming\\ 56 | 57 | For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. We don't 58 | use $XDG_DATA_HOME as that data dir is mostly used at the time of 59 | installation, instead of the application adding data during runtime. 60 | Also, in practice, Linux apps tend to store their data in 61 | "~/.config/" instead of "~/.local/share/". 62 | """ 63 | if sys.platform.startswith("win"): 64 | if appauthor is None: 65 | raise AppDirsError("must specify 'appauthor' on Windows") 66 | const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" 67 | path = os.path.join(_get_win_folder(const), appauthor, appname) 68 | elif sys.platform == 'darwin': 69 | path = os.path.join( 70 | os.path.expanduser('~/Library/Application Support/'), 71 | appname) 72 | else: 73 | path = os.path.join( 74 | os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")), 75 | appname.lower()) 76 | if version: 77 | path = os.path.join(path, version) 78 | return path 79 | 80 | 81 | def site_data_dir(appname, appauthor=None, version=None): 82 | """Return full path to the user-shared data dir for this application. 83 | 84 | "appname" is the name of application. 85 | "appauthor" (only required and used on Windows) is the name of the 86 | appauthor or distributing body for this application. Typically 87 | it is the owning company name. 88 | "version" is an optional version path element to append to the 89 | path. You might want to use this if you want multiple versions 90 | of your app to be able to run independently. If used, this 91 | would typically be ".". 92 | 93 | Typical user data directories are: 94 | Mac OS X: /Library/Application Support/ 95 | Unix: /etc/xdg/ 96 | Win XP: C:\Documents and Settings\All Users\Application Data\\ 97 | Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) 98 | Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. 99 | 100 | For Unix, this is using the $XDG_CONFIG_DIRS[0] default. 101 | 102 | WARNING: Do not use this on Windows. See the Vista-Fail note above for why. 103 | """ 104 | if sys.platform.startswith("win"): 105 | if appauthor is None: 106 | raise AppDirsError("must specify 'appauthor' on Windows") 107 | path = os.path.join(_get_win_folder("CSIDL_COMMON_APPDATA"), 108 | appauthor, appname) 109 | elif sys.platform == 'darwin': 110 | path = os.path.join( 111 | os.path.expanduser('/Library/Application Support'), 112 | appname) 113 | else: 114 | # XDG default for $XDG_CONFIG_DIRS[0]. Perhaps should actually 115 | # *use* that envvar, if defined. 116 | path = "/etc/xdg/"+appname.lower() 117 | if version: 118 | path = os.path.join(path, version) 119 | return path 120 | 121 | 122 | def user_cache_dir(appname, appauthor=None, version=None, opinion=True): 123 | r"""Return full path to the user-specific cache dir for this application. 124 | 125 | "appname" is the name of application. 126 | "appauthor" (only required and used on Windows) is the name of the 127 | appauthor or distributing body for this application. Typically 128 | it is the owning company name. 129 | "version" is an optional version path element to append to the 130 | path. You might want to use this if you want multiple versions 131 | of your app to be able to run independently. If used, this 132 | would typically be ".". 133 | "opinion" (boolean) can be False to disable the appending of 134 | "Cache" to the base app data dir for Windows. See 135 | discussion below. 136 | 137 | Typical user cache directories are: 138 | Mac OS X: ~/Library/Caches/ 139 | Unix: ~/.cache/ (XDG default) 140 | Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache 141 | Vista: C:\Users\\AppData\Local\\\Cache 142 | 143 | On Windows the only suggestion in the MSDN docs is that local settings go in 144 | the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming 145 | app data dir (the default returned by `user_data_dir` above). Apps typically 146 | put cache data somewhere *under* the given dir here. Some examples: 147 | ...\Mozilla\Firefox\Profiles\\Cache 148 | ...\Acme\SuperApp\Cache\1.0 149 | OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. 150 | This can be disabled with the `opinion=False` option. 151 | """ 152 | if sys.platform.startswith("win"): 153 | if appauthor is None: 154 | raise AppDirsError("must specify 'appauthor' on Windows") 155 | path = os.path.join(_get_win_folder("CSIDL_LOCAL_APPDATA"), 156 | appauthor, appname) 157 | if opinion: 158 | path = os.path.join(path, "Cache") 159 | elif sys.platform == 'darwin': 160 | path = os.path.join( 161 | os.path.expanduser('~/Library/Caches'), 162 | appname) 163 | else: 164 | path = os.path.join( 165 | os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')), 166 | appname.lower()) 167 | if version: 168 | path = os.path.join(path, version) 169 | return path 170 | 171 | def user_log_dir(appname, appauthor=None, version=None, opinion=True): 172 | r"""Return full path to the user-specific log dir for this application. 173 | 174 | "appname" is the name of application. 175 | "appauthor" (only required and used on Windows) is the name of the 176 | appauthor or distributing body for this application. Typically 177 | it is the owning company name. 178 | "version" is an optional version path element to append to the 179 | path. You might want to use this if you want multiple versions 180 | of your app to be able to run independently. If used, this 181 | would typically be ".". 182 | "opinion" (boolean) can be False to disable the appending of 183 | "Logs" to the base app data dir for Windows, and "log" to the 184 | base cache dir for Unix. See discussion below. 185 | 186 | Typical user cache directories are: 187 | Mac OS X: ~/Library/Logs/ 188 | Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined 189 | Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs 190 | Vista: C:\Users\\AppData\Local\\\Logs 191 | 192 | On Windows the only suggestion in the MSDN docs is that local settings 193 | go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in 194 | examples of what some windows apps use for a logs dir.) 195 | 196 | OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` 197 | value for Windows and appends "log" to the user cache dir for Unix. 198 | This can be disabled with the `opinion=False` option. 199 | """ 200 | if sys.platform == "darwin": 201 | path = os.path.join( 202 | os.path.expanduser('~/Library/Logs'), 203 | appname) 204 | elif sys.platform == "win32": 205 | path = user_data_dir(appname, appauthor, version); version=False 206 | if opinion: 207 | path = os.path.join(path, "Logs") 208 | else: 209 | path = user_cache_dir(appname, appauthor, version); version=False 210 | if opinion: 211 | path = os.path.join(path, "log") 212 | if version: 213 | path = os.path.join(path, version) 214 | return path 215 | 216 | 217 | class AppDirs(object): 218 | """Convenience wrapper for getting application dirs.""" 219 | def __init__(self, appname, appauthor, version=None, roaming=False): 220 | self.appname = appname 221 | self.appauthor = appauthor 222 | self.version = version 223 | self.roaming = roaming 224 | @property 225 | def user_data_dir(self): 226 | return user_data_dir(self.appname, self.appauthor, 227 | version=self.version, roaming=self.roaming) 228 | @property 229 | def site_data_dir(self): 230 | return site_data_dir(self.appname, self.appauthor, 231 | version=self.version) 232 | @property 233 | def user_cache_dir(self): 234 | return user_cache_dir(self.appname, self.appauthor, 235 | version=self.version) 236 | @property 237 | def user_log_dir(self): 238 | return user_log_dir(self.appname, self.appauthor, 239 | version=self.version) 240 | 241 | 242 | 243 | 244 | #---- internal support stuff 245 | 246 | def _get_win_folder_from_registry(csidl_name): 247 | """This is a fallback technique at best. I'm not sure if using the 248 | registry for this guarantees us the correct answer for all CSIDL_* 249 | names. 250 | """ 251 | import _winreg 252 | 253 | shell_folder_name = { 254 | "CSIDL_APPDATA": "AppData", 255 | "CSIDL_COMMON_APPDATA": "Common AppData", 256 | "CSIDL_LOCAL_APPDATA": "Local AppData", 257 | }[csidl_name] 258 | 259 | key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 260 | r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") 261 | dir, type = _winreg.QueryValueEx(key, shell_folder_name) 262 | return dir 263 | 264 | def _get_win_folder_with_pywin32(csidl_name): 265 | from win32com.shell import shellcon, shell 266 | dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) 267 | # Try to make this a unicode path because SHGetFolderPath does 268 | # not return unicode strings when there is unicode data in the 269 | # path. 270 | try: 271 | dir = unicode(dir) 272 | 273 | # Downgrade to short path name if have highbit chars. See 274 | # . 275 | has_high_char = False 276 | for c in dir: 277 | if ord(c) > 255: 278 | has_high_char = True 279 | break 280 | if has_high_char: 281 | try: 282 | import win32api 283 | dir = win32api.GetShortPathName(dir) 284 | except ImportError: 285 | pass 286 | except UnicodeError: 287 | pass 288 | return dir 289 | 290 | def _get_win_folder_with_ctypes(csidl_name): 291 | import ctypes 292 | 293 | csidl_const = { 294 | "CSIDL_APPDATA": 26, 295 | "CSIDL_COMMON_APPDATA": 35, 296 | "CSIDL_LOCAL_APPDATA": 28, 297 | }[csidl_name] 298 | 299 | buf = ctypes.create_unicode_buffer(1024) 300 | ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) 301 | 302 | # Downgrade to short path name if have highbit chars. See 303 | # . 304 | has_high_char = False 305 | for c in buf: 306 | if ord(c) > 255: 307 | has_high_char = True 308 | break 309 | if has_high_char: 310 | buf2 = ctypes.create_unicode_buffer(1024) 311 | if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): 312 | buf = buf2 313 | 314 | return buf.value 315 | 316 | if sys.platform == "win32": 317 | try: 318 | import win32com.shell 319 | _get_win_folder = _get_win_folder_with_pywin32 320 | except ImportError: 321 | try: 322 | import ctypes 323 | _get_win_folder = _get_win_folder_with_ctypes 324 | except ImportError: 325 | _get_win_folder = _get_win_folder_from_registry 326 | 327 | 328 | 329 | #---- self test code 330 | 331 | if __name__ == "__main__": 332 | appname = "MyApp" 333 | appauthor = "MyCompany" 334 | 335 | props = ("user_data_dir", "site_data_dir", "user_cache_dir", 336 | "user_log_dir") 337 | 338 | print("-- app dirs (without optional 'version')") 339 | dirs = AppDirs(appname, appauthor, version="1.0") 340 | for prop in props: 341 | print("%s: %s" % (prop, getattr(dirs, prop))) 342 | 343 | print("\n-- app dirs (with optional 'version')") 344 | dirs = AppDirs(appname, appauthor) 345 | for prop in props: 346 | print("%s: %s" % (prop, getattr(dirs, prop))) 347 | --------------------------------------------------------------------------------