aboutsummaryrefslogtreecommitdiffstats
path: root/devscripts
diff options
context:
space:
mode:
Diffstat (limited to 'devscripts')
-rw-r--r--devscripts/SizeOfImage.patchbin0 -> 147 bytes
-rw-r--r--devscripts/SizeOfImage_w.patchbin0 -> 148 bytes
-rw-r--r--devscripts/bash-completion.in29
-rwxr-xr-xdevscripts/bash-completion.py30
-rw-r--r--devscripts/buildserver.py433
-rw-r--r--devscripts/check-porn.py60
-rw-r--r--devscripts/fish-completion.in5
-rwxr-xr-xdevscripts/fish-completion.py49
-rw-r--r--devscripts/generate_aes_testdata.py43
-rw-r--r--devscripts/lazy_load_template.py19
-rwxr-xr-xdevscripts/make_contributing.py33
-rw-r--r--devscripts/make_lazy_extractors.py100
-rwxr-xr-xdevscripts/make_readme.py26
-rw-r--r--devscripts/make_supportedsites.py46
-rwxr-xr-xdevscripts/posix-locale.sh6
-rw-r--r--devscripts/prepare_manpage.py79
-rw-r--r--devscripts/run_tests.bat17
-rwxr-xr-xdevscripts/run_tests.sh22
-rw-r--r--devscripts/zsh-completion.in28
-rwxr-xr-xdevscripts/zsh-completion.py49
20 files changed, 1074 insertions, 0 deletions
diff --git a/devscripts/SizeOfImage.patch b/devscripts/SizeOfImage.patch
new file mode 100644
index 0000000..d5845af
--- /dev/null
+++ b/devscripts/SizeOfImage.patch
Binary files differ
diff --git a/devscripts/SizeOfImage_w.patch b/devscripts/SizeOfImage_w.patch
new file mode 100644
index 0000000..c1a338f
--- /dev/null
+++ b/devscripts/SizeOfImage_w.patch
Binary files differ
diff --git a/devscripts/bash-completion.in b/devscripts/bash-completion.in
new file mode 100644
index 0000000..0cc81b0
--- /dev/null
+++ b/devscripts/bash-completion.in
@@ -0,0 +1,29 @@
+__hypervideo_dl()
+{
+ local cur prev opts fileopts diropts keywords
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ opts="{{flags}}"
+ keywords=":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater :ythistory"
+ fileopts="-a|--batch-file|--download-archive|--cookies|--load-info"
+ diropts="--cache-dir"
+
+ if [[ ${prev} =~ ${fileopts} ]]; then
+ COMPREPLY=( $(compgen -f -- ${cur}) )
+ return 0
+ elif [[ ${prev} =~ ${diropts} ]]; then
+ COMPREPLY=( $(compgen -d -- ${cur}) )
+ return 0
+ fi
+
+ if [[ ${cur} =~ : ]]; then
+ COMPREPLY=( $(compgen -W "${keywords}" -- ${cur}) )
+ return 0
+ elif [[ ${cur} == * ]] ; then
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ fi
+}
+
+complete -F __hypervideo_dl hypervideo
diff --git a/devscripts/bash-completion.py b/devscripts/bash-completion.py
new file mode 100755
index 0000000..12abd45
--- /dev/null
+++ b/devscripts/bash-completion.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+from __future__ import unicode_literals
+
+import os
+from os.path import dirname as dirn
+import sys
+
+sys.path.insert(0, dirn(dirn((os.path.abspath(__file__)))))
+import hypervideo_dl
+
+BASH_COMPLETION_FILE = "hypervideo.bash-completion"
+BASH_COMPLETION_TEMPLATE = "devscripts/bash-completion.in"
+
+
+def build_completion(opt_parser):
+ opts_flag = []
+ for group in opt_parser.option_groups:
+ for option in group.option_list:
+ # for every long flag
+ opts_flag.append(option.get_opt_string())
+ with open(BASH_COMPLETION_TEMPLATE) as f:
+ template = f.read()
+ with open(BASH_COMPLETION_FILE, "w") as f:
+ # just using the special char
+ filled_template = template.replace("{{flags}}", " ".join(opts_flag))
+ f.write(filled_template)
+
+
+parser = hypervideo_dl.parseOpts()[0]
+build_completion(parser)
diff --git a/devscripts/buildserver.py b/devscripts/buildserver.py
new file mode 100644
index 0000000..6f8aae1
--- /dev/null
+++ b/devscripts/buildserver.py
@@ -0,0 +1,433 @@
+#!/usr/bin/python3
+
+import argparse
+import ctypes
+import functools
+import shutil
+import subprocess
+import sys
+import tempfile
+import threading
+import traceback
+import os.path
+
+sys.path.insert(0, os.path.dirname(os.path.dirname((os.path.abspath(__file__)))))
+from hypervideo_dl.compat import (
+ compat_input,
+ compat_http_server,
+ compat_str,
+ compat_urlparse,
+)
+
+# These are not used outside of buildserver.py thus not in compat.py
+
+try:
+ import winreg as compat_winreg
+except ImportError: # Python 2
+ import _winreg as compat_winreg
+
+try:
+ import socketserver as compat_socketserver
+except ImportError: # Python 2
+ import SocketServer as compat_socketserver
+
+
+class BuildHTTPServer(compat_socketserver.ThreadingMixIn, compat_http_server.HTTPServer):
+ allow_reuse_address = True
+
+
+advapi32 = ctypes.windll.advapi32
+
+SC_MANAGER_ALL_ACCESS = 0xf003f
+SC_MANAGER_CREATE_SERVICE = 0x02
+SERVICE_WIN32_OWN_PROCESS = 0x10
+SERVICE_AUTO_START = 0x2
+SERVICE_ERROR_NORMAL = 0x1
+DELETE = 0x00010000
+SERVICE_STATUS_START_PENDING = 0x00000002
+SERVICE_STATUS_RUNNING = 0x00000004
+SERVICE_ACCEPT_STOP = 0x1
+
+SVCNAME = 'youtubedl_builder'
+
+LPTSTR = ctypes.c_wchar_p
+START_CALLBACK = ctypes.WINFUNCTYPE(None, ctypes.c_int, ctypes.POINTER(LPTSTR))
+
+
+class SERVICE_TABLE_ENTRY(ctypes.Structure):
+ _fields_ = [
+ ('lpServiceName', LPTSTR),
+ ('lpServiceProc', START_CALLBACK)
+ ]
+
+
+HandlerEx = ctypes.WINFUNCTYPE(
+ ctypes.c_int, # return
+ ctypes.c_int, # dwControl
+ ctypes.c_int, # dwEventType
+ ctypes.c_void_p, # lpEventData,
+ ctypes.c_void_p, # lpContext,
+)
+
+
+def _ctypes_array(c_type, py_array):
+ ar = (c_type * len(py_array))()
+ ar[:] = py_array
+ return ar
+
+
+def win_OpenSCManager():
+ res = advapi32.OpenSCManagerW(None, None, SC_MANAGER_ALL_ACCESS)
+ if not res:
+ raise Exception('Opening service manager failed - '
+ 'are you running this as administrator?')
+ return res
+
+
+def win_install_service(service_name, cmdline):
+ manager = win_OpenSCManager()
+ try:
+ h = advapi32.CreateServiceW(
+ manager, service_name, None,
+ SC_MANAGER_CREATE_SERVICE, SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
+ cmdline, None, None, None, None, None)
+ if not h:
+ raise OSError('Service creation failed: %s' % ctypes.FormatError())
+
+ advapi32.CloseServiceHandle(h)
+ finally:
+ advapi32.CloseServiceHandle(manager)
+
+
+def win_uninstall_service(service_name):
+ manager = win_OpenSCManager()
+ try:
+ h = advapi32.OpenServiceW(manager, service_name, DELETE)
+ if not h:
+ raise OSError('Could not find service %s: %s' % (
+ service_name, ctypes.FormatError()))
+
+ try:
+ if not advapi32.DeleteService(h):
+ raise OSError('Deletion failed: %s' % ctypes.FormatError())
+ finally:
+ advapi32.CloseServiceHandle(h)
+ finally:
+ advapi32.CloseServiceHandle(manager)
+
+
+def win_service_report_event(service_name, msg, is_error=True):
+ with open('C:/sshkeys/log', 'a', encoding='utf-8') as f:
+ f.write(msg + '\n')
+
+ event_log = advapi32.RegisterEventSourceW(None, service_name)
+ if not event_log:
+ raise OSError('Could not report event: %s' % ctypes.FormatError())
+
+ try:
+ type_id = 0x0001 if is_error else 0x0004
+ event_id = 0xc0000000 if is_error else 0x40000000
+ lines = _ctypes_array(LPTSTR, [msg])
+
+ if not advapi32.ReportEventW(
+ event_log, type_id, 0, event_id, None, len(lines), 0,
+ lines, None):
+ raise OSError('Event reporting failed: %s' % ctypes.FormatError())
+ finally:
+ advapi32.DeregisterEventSource(event_log)
+
+
+def win_service_handler(stop_event, *args):
+ try:
+ raise ValueError('Handler called with args ' + repr(args))
+ TODO
+ except Exception as e:
+ tb = traceback.format_exc()
+ msg = str(e) + '\n' + tb
+ win_service_report_event(service_name, msg, is_error=True)
+ raise
+
+
+def win_service_set_status(handle, status_code):
+ svcStatus = SERVICE_STATUS()
+ svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS
+ svcStatus.dwCurrentState = status_code
+ svcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
+
+ svcStatus.dwServiceSpecificExitCode = 0
+
+ if not advapi32.SetServiceStatus(handle, ctypes.byref(svcStatus)):
+ raise OSError('SetServiceStatus failed: %r' % ctypes.FormatError())
+
+
+def win_service_main(service_name, real_main, argc, argv_raw):
+ try:
+ # args = [argv_raw[i].value for i in range(argc)]
+ stop_event = threading.Event()
+ handler = HandlerEx(functools.partial(stop_event, win_service_handler))
+ h = advapi32.RegisterServiceCtrlHandlerExW(service_name, handler, None)
+ if not h:
+ raise OSError('Handler registration failed: %s' %
+ ctypes.FormatError())
+
+ TODO
+ except Exception as e:
+ tb = traceback.format_exc()
+ msg = str(e) + '\n' + tb
+ win_service_report_event(service_name, msg, is_error=True)
+ raise
+
+
+def win_service_start(service_name, real_main):
+ try:
+ cb = START_CALLBACK(
+ functools.partial(win_service_main, service_name, real_main))
+ dispatch_table = _ctypes_array(SERVICE_TABLE_ENTRY, [
+ SERVICE_TABLE_ENTRY(
+ service_name,
+ cb
+ ),
+ SERVICE_TABLE_ENTRY(None, ctypes.cast(None, START_CALLBACK))
+ ])
+
+ if not advapi32.StartServiceCtrlDispatcherW(dispatch_table):
+ raise OSError('ctypes start failed: %s' % ctypes.FormatError())
+ except Exception as e:
+ tb = traceback.format_exc()
+ msg = str(e) + '\n' + tb
+ win_service_report_event(service_name, msg, is_error=True)
+ raise
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-i', '--install',
+ action='store_const', dest='action', const='install',
+ help='Launch at Windows startup')
+ parser.add_argument('-u', '--uninstall',
+ action='store_const', dest='action', const='uninstall',
+ help='Remove Windows service')
+ parser.add_argument('-s', '--service',
+ action='store_const', dest='action', const='service',
+ help='Run as a Windows service')
+ parser.add_argument('-b', '--bind', metavar='<host:port>',
+ action='store', default='0.0.0.0:8142',
+ help='Bind to host:port (default %default)')
+ options = parser.parse_args(args=args)
+
+ if options.action == 'install':
+ fn = os.path.abspath(__file__).replace('v:', '\\\\vboxsrv\\vbox')
+ cmdline = '%s %s -s -b %s' % (sys.executable, fn, options.bind)
+ win_install_service(SVCNAME, cmdline)
+ return
+
+ if options.action == 'uninstall':
+ win_uninstall_service(SVCNAME)
+ return
+
+ if options.action == 'service':
+ win_service_start(SVCNAME, main)
+ return
+
+ host, port_str = options.bind.split(':')
+ port = int(port_str)
+
+ print('Listening on %s:%d' % (host, port))
+ srv = BuildHTTPServer((host, port), BuildHTTPRequestHandler)
+ thr = threading.Thread(target=srv.serve_forever)
+ thr.start()
+ compat_input('Press ENTER to shut down')
+ srv.shutdown()
+ thr.join()
+
+
+def rmtree(path):
+ for name in os.listdir(path):
+ fname = os.path.join(path, name)
+ if os.path.isdir(fname):
+ rmtree(fname)
+ else:
+ os.chmod(fname, 0o666)
+ os.remove(fname)
+ os.rmdir(path)
+
+
+class BuildError(Exception):
+ def __init__(self, output, code=500):
+ self.output = output
+ self.code = code
+
+ def __str__(self):
+ return self.output
+
+
+class HTTPError(BuildError):
+ pass
+
+
+class PythonBuilder(object):
+ def __init__(self, **kwargs):
+ python_version = kwargs.pop('python', '3.4')
+ python_path = None
+ for node in ('Wow6432Node\\', ''):
+ try:
+ key = compat_winreg.OpenKey(
+ compat_winreg.HKEY_LOCAL_MACHINE,
+ r'SOFTWARE\%sPython\PythonCore\%s\InstallPath' % (node, python_version))
+ try:
+ python_path, _ = compat_winreg.QueryValueEx(key, '')
+ finally:
+ compat_winreg.CloseKey(key)
+ break
+ except Exception:
+ pass
+
+ if not python_path:
+ raise BuildError('No such Python version: %s' % python_version)
+
+ self.pythonPath = python_path
+
+ super(PythonBuilder, self).__init__(**kwargs)
+
+
+class GITInfoBuilder(object):
+ def __init__(self, **kwargs):
+ try:
+ self.user, self.repoName = kwargs['path'][:2]
+ self.rev = kwargs.pop('rev')
+ except ValueError:
+ raise BuildError('Invalid path')
+ except KeyError as e:
+ raise BuildError('Missing mandatory parameter "%s"' % e.args[0])
+
+ path = os.path.join(os.environ['APPDATA'], 'Build archive', self.repoName, self.user)
+ if not os.path.exists(path):
+ os.makedirs(path)
+ self.basePath = tempfile.mkdtemp(dir=path)
+ self.buildPath = os.path.join(self.basePath, 'build')
+
+ super(GITInfoBuilder, self).__init__(**kwargs)
+
+
+class GITBuilder(GITInfoBuilder):
+ def build(self):
+ try:
+ subprocess.check_output(['git', 'clone', 'git://github.com/%s/%s.git' % (self.user, self.repoName), self.buildPath])
+ subprocess.check_output(['git', 'checkout', self.rev], cwd=self.buildPath)
+ except subprocess.CalledProcessError as e:
+ raise BuildError(e.output)
+
+ super(GITBuilder, self).build()
+
+
+class YoutubeDLBuilder(object):
+ authorizedUsers = ['fraca7', 'phihag', 'rg3', 'FiloSottile', 'ytdl-org']
+
+ def __init__(self, **kwargs):
+ if self.repoName != 'hypervideo':
+ raise BuildError('Invalid repository "%s"' % self.repoName)
+ if self.user not in self.authorizedUsers:
+ raise HTTPError('Unauthorized user "%s"' % self.user, 401)
+
+ super(YoutubeDLBuilder, self).__init__(**kwargs)
+
+ def build(self):
+ try:
+ proc = subprocess.Popen([os.path.join(self.pythonPath, 'python.exe'), 'setup.py', 'py2exe'], stdin=subprocess.PIPE, cwd=self.buildPath)
+ proc.wait()
+ #subprocess.check_output([os.path.join(self.pythonPath, 'python.exe'), 'setup.py', 'py2exe'],
+ # cwd=self.buildPath)
+ except subprocess.CalledProcessError as e:
+ raise BuildError(e.output)
+
+ super(YoutubeDLBuilder, self).build()
+
+
+class DownloadBuilder(object):
+ def __init__(self, **kwargs):
+ self.handler = kwargs.pop('handler')
+ self.srcPath = os.path.join(self.buildPath, *tuple(kwargs['path'][2:]))
+ self.srcPath = os.path.abspath(os.path.normpath(self.srcPath))
+ if not self.srcPath.startswith(self.buildPath):
+ raise HTTPError(self.srcPath, 401)
+
+ super(DownloadBuilder, self).__init__(**kwargs)
+
+ def build(self):
+ if not os.path.exists(self.srcPath):
+ raise HTTPError('No such file', 404)
+ if os.path.isdir(self.srcPath):
+ raise HTTPError('Is a directory: %s' % self.srcPath, 401)
+
+ self.handler.send_response(200)
+ self.handler.send_header('Content-Type', 'application/octet-stream')
+ self.handler.send_header('Content-Disposition', 'attachment; filename=%s' % os.path.split(self.srcPath)[-1])
+ self.handler.send_header('Content-Length', str(os.stat(self.srcPath).st_size))
+ self.handler.end_headers()
+
+ with open(self.srcPath, 'rb') as src:
+ shutil.copyfileobj(src, self.handler.wfile)
+
+ super(DownloadBuilder, self).build()
+
+
+class CleanupTempDir(object):
+ def build(self):
+ try:
+ rmtree(self.basePath)
+ except Exception as e:
+ print('WARNING deleting "%s": %s' % (self.basePath, e))
+
+ super(CleanupTempDir, self).build()
+
+
+class Null(object):
+ def __init__(self, **kwargs):
+ pass
+
+ def start(self):
+ pass
+
+ def close(self):
+ pass
+
+ def build(self):
+ pass
+
+
+class Builder(PythonBuilder, GITBuilder, YoutubeDLBuilder, DownloadBuilder, CleanupTempDir, Null):
+ pass
+
+
+class BuildHTTPRequestHandler(compat_http_server.BaseHTTPRequestHandler):
+ actionDict = {'build': Builder, 'download': Builder} # They're the same, no more caching.
+
+ def do_GET(self):
+ path = compat_urlparse.urlparse(self.path)
+ paramDict = dict([(key, value[0]) for key, value in compat_urlparse.parse_qs(path.query).items()])
+ action, _, path = path.path.strip('/').partition('/')
+ if path:
+ path = path.split('/')
+ if action in self.actionDict:
+ try:
+ builder = self.actionDict[action](path=path, handler=self, **paramDict)
+ builder.start()
+ try:
+ builder.build()
+ finally:
+ builder.close()
+ except BuildError as e:
+ self.send_response(e.code)
+ msg = compat_str(e).encode('UTF-8')
+ self.send_header('Content-Type', 'text/plain; charset=UTF-8')
+ self.send_header('Content-Length', len(msg))
+ self.end_headers()
+ self.wfile.write(msg)
+ else:
+ self.send_response(500, 'Unknown build method "%s"' % action)
+ else:
+ self.send_response(500, 'Malformed URL')
+
+if __name__ == '__main__':
+ main()
diff --git a/devscripts/check-porn.py b/devscripts/check-porn.py
new file mode 100644
index 0000000..a62711e
--- /dev/null
+++ b/devscripts/check-porn.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+from __future__ import unicode_literals
+
+"""
+This script employs a VERY basic heuristic ('porn' in webpage.lower()) to check
+if we are not 'age_limit' tagging some porn site
+
+A second approach implemented relies on a list of porn domains, to activate it
+pass the list filename as the only argument
+"""
+
+# Allow direct execution
+import os
+import sys
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from test.helper import gettestcases
+from hypervideo_dl.utils import compat_urllib_parse_urlparse
+from hypervideo_dl.utils import compat_urllib_request
+
+if len(sys.argv) > 1:
+ METHOD = 'LIST'
+ LIST = open(sys.argv[1]).read().decode('utf8').strip()
+else:
+ METHOD = 'EURISTIC'
+
+for test in gettestcases():
+ if METHOD == 'EURISTIC':
+ try:
+ webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read()
+ except Exception:
+ print('\nFail: {0}'.format(test['name']))
+ continue
+
+ webpage = webpage.decode('utf8', 'replace')
+
+ RESULT = 'porn' in webpage.lower()
+
+ elif METHOD == 'LIST':
+ domain = compat_urllib_parse_urlparse(test['url']).netloc
+ if not domain:
+ print('\nFail: {0}'.format(test['name']))
+ continue
+ domain = '.'.join(domain.split('.')[-2:])
+
+ RESULT = ('.' + domain + '\n' in LIST or '\n' + domain + '\n' in LIST)
+
+ if RESULT and ('info_dict' not in test or 'age_limit' not in test['info_dict']
+ or test['info_dict']['age_limit'] != 18):
+ print('\nPotential missing age_limit check: {0}'.format(test['name']))
+
+ elif not RESULT and ('info_dict' in test and 'age_limit' in test['info_dict']
+ and test['info_dict']['age_limit'] == 18):
+ print('\nPotential false negative: {0}'.format(test['name']))
+
+ else:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+print()
diff --git a/devscripts/fish-completion.in b/devscripts/fish-completion.in
new file mode 100644
index 0000000..38579d7
--- /dev/null
+++ b/devscripts/fish-completion.in
@@ -0,0 +1,5 @@
+
+{{commands}}
+
+
+complete --command hypervideo --arguments ":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater :ythistory"
diff --git a/devscripts/fish-completion.py b/devscripts/fish-completion.py
new file mode 100755
index 0000000..b6e9949
--- /dev/null
+++ b/devscripts/fish-completion.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+from __future__ import unicode_literals
+
+import optparse
+import os
+from os.path import dirname as dirn
+import sys
+
+sys.path.insert(0, dirn(dirn((os.path.abspath(__file__)))))
+import hypervideo_dl
+from hypervideo_dl.utils import shell_quote
+
+FISH_COMPLETION_FILE = 'hypervideo.fish'
+FISH_COMPLETION_TEMPLATE = 'devscripts/fish-completion.in'
+
+EXTRA_ARGS = {
+ 'recode-video': ['--arguments', 'mp4 flv ogg webm mkv', '--exclusive'],
+
+ # Options that need a file parameter
+ 'download-archive': ['--require-parameter'],
+ 'cookies': ['--require-parameter'],
+ 'load-info': ['--require-parameter'],
+ 'batch-file': ['--require-parameter'],
+}
+
+
+def build_completion(opt_parser):
+ commands = []
+
+ for group in opt_parser.option_groups:
+ for option in group.option_list:
+ long_option = option.get_opt_string().strip('-')
+ complete_cmd = ['complete', '--command', 'hypervideo', '--long-option', long_option]
+ if option._short_opts:
+ complete_cmd += ['--short-option', option._short_opts[0].strip('-')]
+ if option.help != optparse.SUPPRESS_HELP:
+ complete_cmd += ['--description', option.help]
+ complete_cmd.extend(EXTRA_ARGS.get(long_option, []))
+ commands.append(shell_quote(complete_cmd))
+
+ with open(FISH_COMPLETION_TEMPLATE) as f:
+ template = f.read()
+ filled_template = template.replace('{{commands}}', '\n'.join(commands))
+ with open(FISH_COMPLETION_FILE, 'w') as f:
+ f.write(filled_template)
+
+
+parser = hypervideo_dl.parseOpts()[0]
+build_completion(parser)
diff --git a/devscripts/generate_aes_testdata.py b/devscripts/generate_aes_testdata.py
new file mode 100644
index 0000000..00dc5bd
--- /dev/null
+++ b/devscripts/generate_aes_testdata.py
@@ -0,0 +1,43 @@
+from __future__ import unicode_literals
+
+import codecs
+import subprocess
+
+import os
+import sys
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from hypervideo_dl.utils import intlist_to_bytes
+from hypervideo_dl.aes import aes_encrypt, key_expansion
+
+secret_msg = b'Secret message goes here'
+
+
+def hex_str(int_list):
+ return codecs.encode(intlist_to_bytes(int_list), 'hex')
+
+
+def openssl_encode(algo, key, iv):
+ cmd = ['openssl', 'enc', '-e', '-' + algo, '-K', hex_str(key), '-iv', hex_str(iv)]
+ prog = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ out, _ = prog.communicate(secret_msg)
+ return out
+
+
+iv = key = [0x20, 0x15] + 14 * [0]
+
+r = openssl_encode('aes-128-cbc', key, iv)
+print('aes_cbc_decrypt')
+print(repr(r))
+
+password = key
+new_key = aes_encrypt(password, key_expansion(password))
+r = openssl_encode('aes-128-ctr', new_key, iv)
+print('aes_decrypt_text 16')
+print(repr(r))
+
+password = key + 16 * [0]
+new_key = aes_encrypt(password, key_expansion(password)) * (32 // 16)
+r = openssl_encode('aes-256-ctr', new_key, iv)
+print('aes_decrypt_text 32')
+print(repr(r))
diff --git a/devscripts/lazy_load_template.py b/devscripts/lazy_load_template.py
new file mode 100644
index 0000000..c4e5fc1
--- /dev/null
+++ b/devscripts/lazy_load_template.py
@@ -0,0 +1,19 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import re
+
+
+class LazyLoadExtractor(object):
+ _module = None
+
+ @classmethod
+ def ie_key(cls):
+ return cls.__name__[:-2]
+
+ def __new__(cls, *args, **kwargs):
+ mod = __import__(cls._module, fromlist=(cls.__name__,))
+ real_cls = getattr(mod, cls.__name__)
+ instance = real_cls.__new__(real_cls)
+ instance.__init__(*args, **kwargs)
+ return instance
diff --git a/devscripts/make_contributing.py b/devscripts/make_contributing.py
new file mode 100755
index 0000000..dbc2e08
--- /dev/null
+++ b/devscripts/make_contributing.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+from __future__ import unicode_literals
+
+import io
+import optparse
+import re
+
+
+def main():
+ parser = optparse.OptionParser(usage='%prog INFILE OUTFILE')
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error('Expected an input and an output filename')
+
+ infile, outfile = args
+
+ with io.open(infile, encoding='utf-8') as inf:
+ readme = inf.read()
+
+ bug_text = re.search(
+ r'(?s)#\s*BUGS\s*[^\n]*\s*(.*?)#\s*COPYRIGHT', readme).group(1)
+ dev_text = re.search(
+ r'(?s)(#\s*DEVELOPER INSTRUCTIONS.*?)#\s*EMBEDDING HYPERVIDEO',
+ readme).group(1)
+
+ out = bug_text + dev_text
+
+ with io.open(outfile, 'w', encoding='utf-8') as outf:
+ outf.write(out)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/devscripts/make_lazy_extractors.py b/devscripts/make_lazy_extractors.py
new file mode 100644
index 0000000..b9a851c
--- /dev/null
+++ b/devscripts/make_lazy_extractors.py
@@ -0,0 +1,100 @@
+from __future__ import unicode_literals, print_function
+
+from inspect import getsource
+import io
+import os
+from os.path import dirname as dirn
+import sys
+
+print('WARNING: Lazy loading extractors is an experimental feature that may not always work', file=sys.stderr)
+
+sys.path.insert(0, dirn(dirn((os.path.abspath(__file__)))))
+
+lazy_extractors_filename = sys.argv[1]
+if os.path.exists(lazy_extractors_filename):
+ os.remove(lazy_extractors_filename)
+
+from hypervideo_dl.extractor import _ALL_CLASSES
+from hypervideo_dl.extractor.common import InfoExtractor, SearchInfoExtractor
+
+with open('devscripts/lazy_load_template.py', 'rt') as f:
+ module_template = f.read()
+
+module_contents = [
+ module_template + '\n' + getsource(InfoExtractor.suitable) + '\n',
+ 'class LazyLoadSearchExtractor(LazyLoadExtractor):\n pass\n']
+
+ie_template = '''
+class {name}({bases}):
+ _VALID_URL = {valid_url!r}
+ _module = '{module}'
+'''
+
+make_valid_template = '''
+ @classmethod
+ def _make_valid_url(cls):
+ return {valid_url!r}
+'''
+
+
+def get_base_name(base):
+ if base is InfoExtractor:
+ return 'LazyLoadExtractor'
+ elif base is SearchInfoExtractor:
+ return 'LazyLoadSearchExtractor'
+ else:
+ return base.__name__
+
+
+def build_lazy_ie(ie, name):
+ valid_url = getattr(ie, '_VALID_URL', None)
+ s = ie_template.format(
+ name=name,
+ bases=', '.join(map(get_base_name, ie.__bases__)),
+ valid_url=valid_url,
+ module=ie.__module__)
+ if ie.suitable.__func__ is not InfoExtractor.suitable.__func__:
+ s += '\n' + getsource(ie.suitable)
+ if hasattr(ie, '_make_valid_url'):
+ # search extractors
+ s += make_valid_template.format(valid_url=ie._make_valid_url())
+ return s
+
+
+# find the correct sorting and add the required base classes so that subclasses
+# can be correctly created
+classes = _ALL_CLASSES[:-1]
+ordered_cls = []
+while classes:
+ for c in classes[:]:
+ bases = set(c.__bases__) - set((object, InfoExtractor, SearchInfoExtractor))
+ stop = False
+ for b in bases:
+ if b not in classes and b not in ordered_cls:
+ if b.__name__ == 'GenericIE':
+ exit()
+ classes.insert(0, b)
+ stop = True
+ if stop:
+ break
+ if all(b in ordered_cls for b in bases):
+ ordered_cls.append(c)
+ classes.remove(c)
+ break
+ordered_cls.append(_ALL_CLASSES[-1])
+
+names = []
+for ie in ordered_cls:
+ name = ie.__name__
+ src = build_lazy_ie(ie, name)
+ module_contents.append(src)
+ if ie in _ALL_CLASSES:
+ names.append(name)
+
+module_contents.append(
+ '_ALL_CLASSES = [{0}]'.format(', '.join(names)))
+
+module_src = '\n'.join(module_contents) + '\n'
+
+with io.open(lazy_extractors_filename, 'wt', encoding='utf-8') as f:
+ f.write(module_src)
diff --git a/devscripts/make_readme.py b/devscripts/make_readme.py
new file mode 100755
index 0000000..8fbce07
--- /dev/null
+++ b/devscripts/make_readme.py
@@ -0,0 +1,26 @@
+from __future__ import unicode_literals
+
+import io
+import sys
+import re
+
+README_FILE = 'README.md'
+helptext = sys.stdin.read()
+
+if isinstance(helptext, bytes):
+ helptext = helptext.decode('utf-8')
+
+with io.open(README_FILE, encoding='utf-8') as f:
+ oldreadme = f.read()
+
+header = oldreadme[:oldreadme.index('# OPTIONS')]
+footer = oldreadme[oldreadme.index('# CONFIGURATION'):]
+
+options = helptext[helptext.index(' General Options:') + 19:]
+options = re.sub(r'(?m)^ (\w.+)$', r'## \1', options)
+options = '# OPTIONS\n' + options + '\n'
+
+with io.open(README_FILE, 'w', encoding='utf-8') as f:
+ f.write(header)
+ f.write(options)
+ f.write(footer)
diff --git a/devscripts/make_supportedsites.py b/devscripts/make_supportedsites.py
new file mode 100644
index 0000000..09807b0
--- /dev/null
+++ b/devscripts/make_supportedsites.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+from __future__ import unicode_literals
+
+import io
+import optparse
+import os
+import sys
+
+
+# Import hypervideo_dl
+ROOT_DIR = os.path.join(os.path.dirname(__file__), '..')
+sys.path.insert(0, ROOT_DIR)
+import hypervideo_dl
+
+
+def main():
+ parser = optparse.OptionParser(usage='%prog OUTFILE.md')
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error('Expected an output filename')
+
+ outfile, = args
+
+ def gen_ies_md(ies):
+ for ie in ies:
+ ie_md = '**{0}**'.format(ie.IE_NAME)
+ ie_desc = getattr(ie, 'IE_DESC', None)
+ if ie_desc is False:
+ continue
+ if ie_desc is not None:
+ ie_md += ': {0}'.format(ie.IE_DESC)
+ if not ie.working():
+ ie_md += ' (Currently broken)'
+ yield ie_md
+
+ ies = sorted(hypervideo_dl.gen_extractors(), key=lambda i: i.IE_NAME.lower())
+ out = '# Supported sites\n' + ''.join(
+ ' - ' + md + '\n'
+ for md in gen_ies_md(ies))
+
+ with io.open(outfile, 'w', encoding='utf-8') as outf:
+ outf.write(out)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/devscripts/posix-locale.sh b/devscripts/posix-locale.sh
new file mode 100755
index 0000000..0aa7a59
--- /dev/null
+++ b/devscripts/posix-locale.sh
@@ -0,0 +1,6 @@
+
+# source this file in your shell to get a POSIX locale (which will break many programs, but that's kind of the point)
+
+export LC_ALL=POSIX
+export LANG=POSIX
+export LANGUAGE=POSIX
diff --git a/devscripts/prepare_manpage.py b/devscripts/prepare_manpage.py
new file mode 100644
index 0000000..5b74238
--- /dev/null
+++ b/devscripts/prepare_manpage.py
@@ -0,0 +1,79 @@
+from __future__ import unicode_literals
+
+import io
+import optparse
+import os.path
+import re
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+README_FILE = os.path.join(ROOT_DIR, 'README.md')
+
+PREFIX = r'''%HYPERVIDEO(1)
+
+# NAME
+
+youtube\-dl \- download videos from youtube.com or other video platforms
+
+# SYNOPSIS
+
+**hypervideo** \[OPTIONS\] URL [URL...]
+
+'''
+
+
+def main():
+ parser = optparse.OptionParser(usage='%prog OUTFILE.md')
+ options, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error('Expected an output filename')
+
+ outfile, = args
+
+ with io.open(README_FILE, encoding='utf-8') as f:
+ readme = f.read()
+
+ readme = re.sub(r'(?s)^.*?(?=# DESCRIPTION)', '', readme)
+ readme = re.sub(r'\s+hypervideo \[OPTIONS\] URL \[URL\.\.\.\]', '', readme)
+ readme = PREFIX + readme
+
+ readme = filter_options(readme)
+
+ with io.open(outfile, 'w', encoding='utf-8') as outf:
+ outf.write(readme)
+
+
+def filter_options(readme):
+ ret = ''
+ in_options = False
+ for line in readme.split('\n'):
+ if line.startswith('# '):
+ if line[2:].startswith('OPTIONS'):
+ in_options = True
+ else:
+ in_options = False
+
+ if in_options:
+ if line.lstrip().startswith('-'):
+ split = re.split(r'\s{2,}', line.lstrip())
+ # Description string may start with `-` as well. If there is
+ # only one piece then it's a description bit not an option.
+ if len(split) > 1:
+ option, description = split
+ split_option = option.split(' ')
+
+ if not split_option[-1].startswith('-'): # metavar
+ option = ' '.join(split_option[:-1] + ['*%s*' % split_option[-1]])
+
+ # Pandoc's definition_lists. See http://pandoc.org/README.html
+ # for more information.
+ ret += '\n%s\n: %s\n' % (option, description)
+ continue
+ ret += line.lstrip() + '\n'
+ else:
+ ret += line + '\n'
+
+ return ret
+
+
+if __name__ == '__main__':
+ main()
diff --git a/devscripts/run_tests.bat b/devscripts/run_tests.bat
new file mode 100644
index 0000000..01a79b6
--- /dev/null
+++ b/devscripts/run_tests.bat
@@ -0,0 +1,17 @@
+@echo off
+
+rem Keep this list in sync with the `offlinetest` target in Makefile
+set DOWNLOAD_TESTS="age_restriction^|download^|socks^|subtitles^|write_annotations^|youtube_lists^|youtube_signature"
+
+if "%YTDL_TEST_SET%" == "core" (
+ set test_set="-I test_("%DOWNLOAD_TESTS%")\.py"
+ set multiprocess_args=""
+) else if "%YTDL_TEST_SET%" == "download" (
+ set test_set="-I test_(?!"%DOWNLOAD_TESTS%").+\.py"
+ set multiprocess_args="--processes=4 --process-timeout=540"
+) else (
+ echo YTDL_TEST_SET is not set or invalid
+ exit /b 1
+)
+
+nosetests test --verbose %test_set:"=% %multiprocess_args:"=%
diff --git a/devscripts/run_tests.sh b/devscripts/run_tests.sh
new file mode 100755
index 0000000..b8f48b9
--- /dev/null
+++ b/devscripts/run_tests.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# Keep this list in sync with the `offlinetest` target in Makefile
+DOWNLOAD_TESTS="age_restriction|download|socks|subtitles|write_annotations|youtube_lists|youtube_signature"
+
+test_set=""
+multiprocess_args=""
+
+case "$YTDL_TEST_SET" in
+ core)
+ test_set="-I test_($DOWNLOAD_TESTS)\.py"
+ ;;
+ download)
+ test_set="-I test_(?!$DOWNLOAD_TESTS).+\.py"
+ multiprocess_args="--processes=4 --process-timeout=540"
+ ;;
+ *)
+ break
+ ;;
+esac
+
+nosetests test --verbose $test_set $multiprocess_args
diff --git a/devscripts/zsh-completion.in b/devscripts/zsh-completion.in
new file mode 100644
index 0000000..1906949
--- /dev/null
+++ b/devscripts/zsh-completion.in
@@ -0,0 +1,28 @@
+#compdef hypervideo
+
+__hypervideo_dl() {
+ local curcontext="$curcontext" fileopts diropts cur prev
+ typeset -A opt_args
+ fileopts="{{fileopts}}"
+ diropts="{{diropts}}"
+ cur=$words[CURRENT]
+ case $cur in
+ :)
+ _arguments '*: :(::ytfavorites ::ytrecommended ::ytsubscriptions ::ytwatchlater ::ythistory)'
+ ;;
+ *)
+ prev=$words[CURRENT-1]
+ if [[ ${prev} =~ ${fileopts} ]]; then
+ _path_files
+ elif [[ ${prev} =~ ${diropts} ]]; then
+ _path_files -/
+ elif [[ ${prev} == "--recode-video" ]]; then
+ _arguments '*: :(mp4 flv ogg webm mkv)'
+ else
+ _arguments '*: :({{flags}})'
+ fi
+ ;;
+ esac
+}
+
+__hypervideo_dl \ No newline at end of file
diff --git a/devscripts/zsh-completion.py b/devscripts/zsh-completion.py
new file mode 100755
index 0000000..b570469
--- /dev/null
+++ b/devscripts/zsh-completion.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+from __future__ import unicode_literals
+
+import os
+from os.path import dirname as dirn
+import sys
+
+sys.path.insert(0, dirn(dirn((os.path.abspath(__file__)))))
+import hypervideo_dl
+
+ZSH_COMPLETION_FILE = "hypervideo.zsh"
+ZSH_COMPLETION_TEMPLATE = "devscripts/zsh-completion.in"
+
+
+def build_completion(opt_parser):
+ opts = [opt for group in opt_parser.option_groups
+ for opt in group.option_list]
+ opts_file = [opt for opt in opts if opt.metavar == "FILE"]
+ opts_dir = [opt for opt in opts if opt.metavar == "DIR"]
+
+ fileopts = []
+ for opt in opts_file:
+ if opt._short_opts:
+ fileopts.extend(opt._short_opts)
+ if opt._long_opts:
+ fileopts.extend(opt._long_opts)
+
+ diropts = []
+ for opt in opts_dir:
+ if opt._short_opts:
+ diropts.extend(opt._short_opts)
+ if opt._long_opts:
+ diropts.extend(opt._long_opts)
+
+ flags = [opt.get_opt_string() for opt in opts]
+
+ with open(ZSH_COMPLETION_TEMPLATE) as f:
+ template = f.read()
+
+ template = template.replace("{{fileopts}}", "|".join(fileopts))
+ template = template.replace("{{diropts}}", "|".join(diropts))
+ template = template.replace("{{flags}}", " ".join(flags))
+
+ with open(ZSH_COMPLETION_FILE, "w") as f:
+ f.write(template)
+
+
+parser = hypervideo_dl.parseOpts()[0]
+build_completion(parser)