From: Rogério Brito Date: Thu, 3 Oct 2013 04:19:59 +0000 (-0300) Subject: Imported Upstream version 2013.10.01 X-Git-Url: https://git.rapsys.eu/youtubedl/commitdiff_plain/7ceb2ec430c3363e0140a0519402428f36dc472e?ds=sidebyside;hp=3ae74f711947d73bf6627bf312edeec41cec85c3 Imported Upstream version 2013.10.01 --- diff --git a/README.md b/README.md index 75068fe..fc8070c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ which means you can modify it, redistribute it or use it however you like. -U, --update update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed) - -i, --ignore-errors continue on download errors + -i, --ignore-errors continue on download errors, for example to to + skip unavailable videos in a playlist --dump-user-agent display the current browser identification --user-agent UA specify a custom user agent --referer REF specify a custom referer, use if the video access @@ -29,6 +30,10 @@ which means you can modify it, redistribute it or use it however you like. --extractor-descriptions Output descriptions of all supported extractors --proxy URL Use the specified HTTP/HTTPS proxy --no-check-certificate Suppress HTTPS certificate validation. + --cache-dir None Location in the filesystem where youtube-dl can + store downloaded information permanently. + ~/.youtube-dl/cache by default + --no-cache-dir Disable filesystem caching ## Video Selection: --playlist-start NUMBER playlist video to start at (default is 1) @@ -113,7 +118,8 @@ which means you can modify it, redistribute it or use it however you like. ## Video Format Options: -f, --format FORMAT video format code, specifiy the order of - preference using slashes: "-f 22/17/18" + preference using slashes: "-f 22/17/18". "-f mp4" + and "-f flv" are also supported --all-formats download all available video formats --prefer-free-formats prefer free video formats unless a specific one is requested @@ -122,10 +128,8 @@ which means you can modify it, redistribute it or use it however you like. only) ## Subtitle Options: - --write-sub write subtitle file (currently youtube only) - --write-auto-sub write automatic subtitle file (currently youtube - only) - --only-sub [deprecated] alias of --skip-download + --write-sub write subtitle file + --write-auto-sub write automatic subtitle file (youtube only) --all-subs downloads all the available subtitles of the video --list-subs lists all available subtitles for the video diff --git a/README.txt b/README.txt index 3baa062..0437c57 100644 --- a/README.txt +++ b/README.txt @@ -26,7 +26,8 @@ OPTIONS -U, --update update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed) - -i, --ignore-errors continue on download errors + -i, --ignore-errors continue on download errors, for example to to + skip unavailable videos in a playlist --dump-user-agent display the current browser identification --user-agent UA specify a custom user agent --referer REF specify a custom referer, use if the video access @@ -36,6 +37,10 @@ OPTIONS --extractor-descriptions Output descriptions of all supported extractors --proxy URL Use the specified HTTP/HTTPS proxy --no-check-certificate Suppress HTTPS certificate validation. + --cache-dir None Location in the filesystem where youtube-dl can + store downloaded information permanently. + ~/.youtube-dl/cache by default + --no-cache-dir Disable filesystem caching Video Selection: ---------------- @@ -130,7 +135,8 @@ Video Format Options: --------------------- -f, --format FORMAT video format code, specifiy the order of - preference using slashes: "-f 22/17/18" + preference using slashes: "-f 22/17/18". "-f mp4" + and "-f flv" are also supported --all-formats download all available video formats --prefer-free-formats prefer free video formats unless a specific one is requested @@ -141,10 +147,8 @@ Video Format Options: Subtitle Options: ----------------- - --write-sub write subtitle file (currently youtube only) - --write-auto-sub write automatic subtitle file (currently youtube - only) - --only-sub [deprecated] alias of --skip-download + --write-sub write subtitle file + --write-auto-sub write automatic subtitle file (youtube only) --all-subs downloads all the available subtitles of the video --list-subs lists all available subtitles for the video diff --git a/devscripts/bash-completion.in b/devscripts/bash-completion.in index 3b99a96..bd10f63 100644 --- a/devscripts/bash-completion.in +++ b/devscripts/bash-completion.in @@ -4,8 +4,12 @@ __youtube-dl() COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" opts="{{flags}}" + keywords=":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater" - if [[ ${cur} == * ]] ; then + if [[ ${cur} =~ : ]]; then + COMPREPLY=( $(compgen -W "${keywords}" -- ${cur}) ) + return 0 + elif [[ ${cur} == * ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi diff --git a/devscripts/buildserver.py b/devscripts/buildserver.py new file mode 100644 index 0000000..e0c3cc8 --- /dev/null +++ b/devscripts/buildserver.py @@ -0,0 +1,405 @@ +#!/usr/bin/python3 + +from http.server import HTTPServer, BaseHTTPRequestHandler +from socketserver import ThreadingMixIn +import argparse +import ctypes +import functools +import sys +import threading +import traceback +import os.path + + +class BuildHTTPServer(ThreadingMixIn, 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='', + action='store', default='localhost: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() + 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): + pythonVersion = kwargs.pop('python', '2.7') + try: + key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Python\PythonCore\%s\InstallPath' % pythonVersion) + try: + self.pythonPath, _ = _winreg.QueryValueEx(key, '') + finally: + _winreg.CloseKey(key) + except Exception: + raise BuildError('No such Python version: %s' % pythonVersion) + + 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'] + + def __init__(self, **kwargs): + if self.repoName != 'youtube-dl': + 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: + 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(BaseHTTPRequestHandler): + actionDict = { 'build': Builder, 'download': Builder } # They're the same, no more caching. + + def do_GET(self): + path = urlparse.urlparse(self.path) + paramDict = dict([(key, value[0]) for key, value in 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 = unicode(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) + except HTTPError as e: + self.send_response(e.code, str(e)) + 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/gh-pages/add-version.py b/devscripts/gh-pages/add-version.py index 116420e..35865b2 100755 --- a/devscripts/gh-pages/add-version.py +++ b/devscripts/gh-pages/add-version.py @@ -3,7 +3,8 @@ import json import sys import hashlib -import urllib.request +import os.path + if len(sys.argv) <= 1: print('Specify the version number as parameter') @@ -23,10 +24,14 @@ filenames = { 'bin': 'youtube-dl', 'exe': 'youtube-dl.exe', 'tar': 'youtube-dl-%s.tar.gz' % version} +build_dir = os.path.join('..', '..', 'build', version) for key, filename in filenames.items(): - print('Downloading and checksumming %s...' % filename) url = 'https://yt-dl.org/downloads/%s/%s' % (version, filename) - data = urllib.request.urlopen(url).read() + fn = os.path.join(build_dir, filename) + with open(fn, 'rb') as f: + data = f.read() + if not data: + raise ValueError('File %s is empty!' % fn) sha256sum = hashlib.sha256(data).hexdigest() new_version[key] = (url, sha256sum) diff --git a/devscripts/gh-pages/update-sites.py b/devscripts/gh-pages/update-sites.py new file mode 100755 index 0000000..33f2424 --- /dev/null +++ b/devscripts/gh-pages/update-sites.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import sys +import os +import textwrap + +# We must be able to import youtube_dl +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +import youtube_dl + +def main(): + with open('supportedsites.html.in', 'r', encoding='utf-8') as tmplf: + template = tmplf.read() + + ie_htmls = [] + for ie in sorted(youtube_dl.gen_extractors(), key=lambda i: i.IE_NAME.lower()): + ie_html = '{}'.format(ie.IE_NAME) + try: + ie_html += ': {}'.format(ie.IE_DESC) + except AttributeError: + pass + if ie.working() == False: + ie_html += ' (Currently broken)' + ie_htmls.append('
  • {}
  • '.format(ie_html)) + + template = template.replace('@SITES@', textwrap.indent('\n'.join(ie_htmls), '\t')) + + with open('supportedsites.html', 'w', encoding='utf-8') as sitesf: + sitesf.write(template) + +if __name__ == '__main__': + main() diff --git a/devscripts/release.sh b/devscripts/release.sh index 24c9ad8..796468b 100755 --- a/devscripts/release.sh +++ b/devscripts/release.sh @@ -55,8 +55,8 @@ git push origin "$version" /bin/echo -e "\n### OK, now it is time to build the binaries..." REV=$(git rev-parse HEAD) make youtube-dl youtube-dl.tar.gz -wget "http://jeromelaheurte.net:8142/download/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe || \ - wget "http://jeromelaheurte.net:8142/build/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe +read -p "VM running? (y/n) " -n 1 +wget "http://localhost:8142/build/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe mkdir -p "build/$version" mv youtube-dl youtube-dl.exe "build/$version" mv youtube-dl.tar.gz "build/$version/youtube-dl-$version.tar.gz" @@ -85,6 +85,7 @@ ROOT=$(pwd) "$ROOT/devscripts/gh-pages/sign-versions.py" < "$ROOT/updates_key.pem" "$ROOT/devscripts/gh-pages/generate-download.py" "$ROOT/devscripts/gh-pages/update-copyright.py" + "$ROOT/devscripts/gh-pages/update-sites.py" git add *.html *.html.in update git commit -m "release $version" git show HEAD diff --git a/devscripts/youtube_genalgo.py b/devscripts/youtube_genalgo.py deleted file mode 100644 index 13df535..0000000 --- a/devscripts/youtube_genalgo.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python - -# Generate youtube signature algorithm from test cases - -import sys - -tests = [ - # 92 - vflQw-fB4 2013/07/17 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'`~\"", - "mrtyuioplkjhgfdsazxcvbnq1234567890QWERTY}IOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]\"|:;"), - # 90 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'`", - "mrtyuioplkjhgfdsazxcvbne1234567890QWER[YUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={`]}|"), - # 89 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<'", - "/?;:|}<[{=+-_)(*&^%$#@!MqBVCXZASDFGHJKLPOIUYTREWQ0987654321mnbvcxzasdfghjklpoiuyt"), - # 88 - vflapUV9V 2013/08/28 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[]}|:;?/>.<", - "ioplkjhgfdsazxcvbnm12<4567890QWERTYUIOZLKJHGFDSAeXCVBNM!@#$%^&*()_-+={[]}|:;?/>.3"), - # 87 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$^&*()_-+={[]}|:;?/>.<", - "uioplkjhgfdsazxcvbnm1t34567890QWE2TYUIOPLKJHGFDSAZXCVeNM!@#$^&*()_-+={[]}|:;?/>.<"), - # 86 - vflg0g8PQ 2013/08/29 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<", - ">/?;}|[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWq0987654321mnbvcxzasdfghjklpoiuytr"), - # 85 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?/>.<", - ".>/?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWQ0q876543r1mnbvcx9asdfghjklpoiuyt2"), - # 84 - vflg0g8PQ 2013/08/29 (sporadic) - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[};?>.<", - ">?;}[{=+-_)(*&^%$#@!MNBVCXZASDFGHJKLPOIUYTREWq0987654321mnbvcxzasdfghjklpoiuytr"), - # 83 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!#$%^&*()_+={[};?/>.<", - ".>/?;}[{=+_)(*&^%<#!MNBVCXZASPFGHJKLwOIUYTREWQ0987654321mnbvcxzasdfghjklpoiuytreq"), - # 82 - vflZK4ZYR 2013/08/23 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>.<", - "wertyuioplkjhgfdsaqxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&z(-+={[};?/>.<"), - # 81 - vflLC8JvQ 2013/07/25 - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>.", - "C>/?;}[{=+-(*&^%$#@!MNBVYXZASDFGHKLPOIU.TREWQ0q87659321mnbvcxzasdfghjkl4oiuytrewp"), - # 80 - vflZK4ZYR 2013/08/23 (sporadic) - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/>", - "wertyuioplkjhgfdsaqxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&z(-+={[};?/>"), - # 79 - vflLC8JvQ 2013/07/25 (sporadic) - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKHGFDSAZXCVBNM!@#$%^&*(-+={[};?/", - "Z?;}[{=+-(*&^%$#@!MNBVCXRASDFGHKLPOIUYT/EWQ0q87659321mnbvcxzasdfghjkl4oiuytrewp"), -] - -tests_age_gate = [ - # 86 - vflqinMWD - ("qwertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!@#$%^&*()_-+={[|};?/>.<", - "ertyuioplkjhgfdsazxcvbnm1234567890QWERTYUIOPLKJHGFDSAZXCVBNM!/#$%^&*()_-+={[|};?@"), -] - -def find_matching(wrong, right): - idxs = [wrong.index(c) for c in right] - return compress(idxs) - return ('s[%d]' % i for i in idxs) - -def compress(idxs): - def _genslice(start, end, step): - starts = '' if start == 0 else str(start) - ends = ':%d' % (end+step) - steps = '' if step == 1 else (':%d' % step) - return 's[%s%s%s]' % (starts, ends, steps) - - step = None - for i, prev in zip(idxs[1:], idxs[:-1]): - if step is not None: - if i - prev == step: - continue - yield _genslice(start, prev, step) - step = None - continue - if i - prev in [-1, 1]: - step = i - prev - start = prev - continue - else: - yield 's[%d]' % prev - if step is None: - yield 's[%d]' % i - else: - yield _genslice(start, i, step) - -def _assert_compress(inp, exp): - res = list(compress(inp)) - if res != exp: - print('Got %r, expected %r' % (res, exp)) - assert res == exp -_assert_compress([0,2,4,6], ['s[0]', 's[2]', 's[4]', 's[6]']) -_assert_compress([0,1,2,4,6,7], ['s[:3]', 's[4]', 's[6:8]']) -_assert_compress([8,0,1,2,4,7,6,9], ['s[8]', 's[:3]', 's[4]', 's[7:5:-1]', 's[9]']) - -def gen(wrong, right, indent): - code = ' + '.join(find_matching(wrong, right)) - return 'if len(s) == %d:\n%s return %s\n' % (len(wrong), indent, code) - -def genall(tests): - indent = ' ' * 8 - return indent + (indent + 'el').join(gen(wrong, right, indent) for wrong,right in tests) - -def main(): - print(genall(tests)) - print(u' Age gate:') - print(genall(tests_age_gate)) - -if __name__ == '__main__': - main() diff --git a/test/parameters.json b/test/parameters.json index 96998b5..f042880 100644 --- a/test/parameters.json +++ b/test/parameters.json @@ -38,7 +38,6 @@ "writedescription": false, "writeinfojson": true, "writesubtitles": false, - "onlysubtitles": false, "allsubtitles": false, "listssubtitles": false } diff --git a/test/test_all_urls.py b/test/test_all_urls.py index c54faa3..ff1c86e 100644 --- a/test/test_all_urls.py +++ b/test/test_all_urls.py @@ -11,24 +11,50 @@ from youtube_dl.extractor import YoutubeIE, YoutubePlaylistIE, YoutubeChannelIE, from helper import get_testcases class TestAllURLsMatching(unittest.TestCase): + def setUp(self): + self.ies = gen_extractors() + + def matching_ies(self, url): + return [ie.IE_NAME for ie in self.ies if ie.suitable(url) and ie.IE_NAME != 'generic'] + + def assertMatch(self, url, ie_list): + self.assertEqual(self.matching_ies(url), ie_list) + def test_youtube_playlist_matching(self): - self.assertTrue(YoutubePlaylistIE.suitable(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) - self.assertTrue(YoutubePlaylistIE.suitable(u'UUBABnxM4Ar9ten8Mdjj1j0Q')) #585 - self.assertTrue(YoutubePlaylistIE.suitable(u'PL63F0C78739B09958')) - self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')) - self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')) - self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')) - self.assertTrue(YoutubePlaylistIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668 - self.assertFalse(YoutubePlaylistIE.suitable(u'PLtS2H6bU1M')) + assertPlaylist = lambda url: self.assertMatch(url, ['youtube:playlist']) + assertPlaylist(u'ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8') + assertPlaylist(u'UUBABnxM4Ar9ten8Mdjj1j0Q') #585 + assertPlaylist(u'PL63F0C78739B09958') + assertPlaylist(u'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q') + assertPlaylist(u'https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8') + assertPlaylist(u'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC') + assertPlaylist(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012') #668 + self.assertFalse('youtube:playlist' in self.matching_ies(u'PLtS2H6bU1M')) def test_youtube_matching(self): self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M')) self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668 + self.assertMatch('http://youtu.be/BaW_jenozKc', ['youtube']) + self.assertMatch('http://www.youtube.com/v/BaW_jenozKc', ['youtube']) + self.assertMatch('https://youtube.googleapis.com/v/BaW_jenozKc', ['youtube']) def test_youtube_channel_matching(self): - self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM')) - self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM?feature=gb_ch_rec')) - self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')) + assertChannel = lambda url: self.assertMatch(url, ['youtube:channel']) + assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM') + assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM?feature=gb_ch_rec') + assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM/videos') + + def test_youtube_user_matching(self): + self.assertMatch('www.youtube.com/NASAgovVideo/videos', ['youtube:user']) + + def test_youtube_feeds(self): + self.assertMatch('https://www.youtube.com/feed/watch_later', ['youtube:watch_later']) + self.assertMatch('https://www.youtube.com/feed/subscriptions', ['youtube:subscriptions']) + self.assertMatch('https://www.youtube.com/feed/recommended', ['youtube:recommended']) + self.assertMatch('https://www.youtube.com/my_favorites', ['youtube:favorites']) + + def test_youtube_show_matching(self): + self.assertMatch('http://www.youtube.com/show/airdisasters', ['youtube:show']) def test_justin_tv_channelid_matching(self): self.assertTrue(JustinTVIE.suitable(u"justin.tv/vanillatv")) @@ -47,10 +73,13 @@ class TestAllURLsMatching(unittest.TestCase): self.assertTrue(JustinTVIE.suitable(u"http://www.twitch.tv/tsm_theoddone/c/2349361")) def test_youtube_extract(self): - self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') - self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc') - self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?feature=player_embedded&v=BaW_jenozKc'), 'BaW_jenozKc') - self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch_popup?v=BaW_jenozKc'), 'BaW_jenozKc') + assertExtractId = lambda url, id: self.assertEqual(YoutubeIE()._extract_id(url), id) + assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc') + assertExtractId('https://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc') + assertExtractId('https://www.youtube.com/watch?feature=player_embedded&v=BaW_jenozKc', 'BaW_jenozKc') + assertExtractId('https://www.youtube.com/watch_popup?v=BaW_jenozKc', 'BaW_jenozKc') + assertExtractId('http://www.youtube.com/watch?v=BaW_jenozKcsharePLED17F32AD9753930', 'BaW_jenozKc') + assertExtractId('BaW_jenozKc', 'BaW_jenozKc') def test_no_duplicates(self): ies = gen_extractors() @@ -63,15 +92,12 @@ class TestAllURLsMatching(unittest.TestCase): self.assertFalse(ie.suitable(url), '%s should not match URL %r' % (type(ie).__name__, url)) def test_keywords(self): - ies = gen_extractors() - matching_ies = lambda url: [ie.IE_NAME for ie in ies - if ie.suitable(url) and ie.IE_NAME != 'generic'] - self.assertEqual(matching_ies(':ytsubs'), ['youtube:subscriptions']) - self.assertEqual(matching_ies(':ytsubscriptions'), ['youtube:subscriptions']) - self.assertEqual(matching_ies(':thedailyshow'), ['ComedyCentral']) - self.assertEqual(matching_ies(':tds'), ['ComedyCentral']) - self.assertEqual(matching_ies(':colbertreport'), ['ComedyCentral']) - self.assertEqual(matching_ies(':cr'), ['ComedyCentral']) + self.assertMatch(':ytsubs', ['youtube:subscriptions']) + self.assertMatch(':ytsubscriptions', ['youtube:subscriptions']) + self.assertMatch(':thedailyshow', ['ComedyCentral']) + self.assertMatch(':tds', ['ComedyCentral']) + self.assertMatch(':colbertreport', ['ComedyCentral']) + self.assertMatch(':cr', ['ComedyCentral']) if __name__ == '__main__': diff --git a/test/test_dailymotion_subtitles.py b/test/test_dailymotion_subtitles.py new file mode 100644 index 0000000..83c65d5 --- /dev/null +++ b/test/test_dailymotion_subtitles.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +import sys +import unittest +import json +import io +import hashlib + +# Allow direct execution +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from youtube_dl.extractor import DailymotionIE +from youtube_dl.utils import * +from helper import FakeYDL + +md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() + +class TestDailymotionSubtitles(unittest.TestCase): + def setUp(self): + self.DL = FakeYDL() + self.url = 'http://www.dailymotion.com/video/xczg00' + def getInfoDict(self): + IE = DailymotionIE(self.DL) + info_dict = IE.extract(self.url) + return info_dict + def getSubtitles(self): + info_dict = self.getInfoDict() + return info_dict[0]['subtitles'] + def test_no_writesubtitles(self): + subtitles = self.getSubtitles() + self.assertEqual(subtitles, None) + def test_subtitles(self): + self.DL.params['writesubtitles'] = True + subtitles = self.getSubtitles() + self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f') + def test_subtitles_lang(self): + self.DL.params['writesubtitles'] = True + self.DL.params['subtitleslangs'] = ['fr'] + subtitles = self.getSubtitles() + self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792') + def test_allsubtitles(self): + self.DL.params['writesubtitles'] = True + self.DL.params['allsubtitles'] = True + subtitles = self.getSubtitles() + self.assertEqual(len(subtitles.keys()), 5) + def test_list_subtitles(self): + self.DL.params['listsubtitles'] = True + info_dict = self.getInfoDict() + self.assertEqual(info_dict, None) + def test_automatic_captions(self): + self.DL.params['writeautomaticsub'] = True + self.DL.params['subtitleslang'] = ['en'] + subtitles = self.getSubtitles() + self.assertTrue(len(subtitles.keys()) == 0) + def test_nosubtitles(self): + self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv' + self.DL.params['writesubtitles'] = True + self.DL.params['allsubtitles'] = True + subtitles = self.getSubtitles() + self.assertEqual(len(subtitles), 0) + def test_multiple_langs(self): + self.DL.params['writesubtitles'] = True + langs = ['es', 'fr', 'de'] + self.DL.params['subtitleslangs'] = langs + subtitles = self.getSubtitles() + for lang in langs: + self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_playlists.py b/test/test_playlists.py index 65de3a5..c335113 100644 --- a/test/test_playlists.py +++ b/test/test_playlists.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# encoding: utf-8 import sys import unittest @@ -8,7 +9,14 @@ import json import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from youtube_dl.extractor import DailymotionPlaylistIE, VimeoChannelIE +from youtube_dl.extractor import ( + DailymotionPlaylistIE, + DailymotionUserIE, + VimeoChannelIE, + UstreamChannelIE, + SoundcloudUserIE, + LivestreamIE, +) from youtube_dl.utils import * from helper import FakeYDL @@ -26,6 +34,14 @@ class TestPlaylists(unittest.TestCase): self.assertEqual(result['title'], u'SPORT') self.assertTrue(len(result['entries']) > 20) + def test_dailymotion_user(self): + dl = FakeYDL() + ie = DailymotionUserIE(dl) + result = ie.extract('http://www.dailymotion.com/user/generation-quoi/') + self.assertIsPlaylist(result) + self.assertEqual(result['title'], u'Génération Quoi') + self.assertTrue(len(result['entries']) >= 26) + def test_vimeo_channel(self): dl = FakeYDL() ie = VimeoChannelIE(dl) @@ -34,5 +50,29 @@ class TestPlaylists(unittest.TestCase): self.assertEqual(result['title'], u'Vimeo Tributes') self.assertTrue(len(result['entries']) > 24) + def test_ustream_channel(self): + dl = FakeYDL() + ie = UstreamChannelIE(dl) + result = ie.extract('http://www.ustream.tv/channel/young-americans-for-liberty') + self.assertIsPlaylist(result) + self.assertEqual(result['id'], u'5124905') + self.assertTrue(len(result['entries']) >= 11) + + def test_soundcloud_user(self): + dl = FakeYDL() + ie = SoundcloudUserIE(dl) + result = ie.extract('https://soundcloud.com/the-concept-band') + self.assertIsPlaylist(result) + self.assertEqual(result['id'], u'9615865') + self.assertTrue(len(result['entries']) >= 12) + + def test_livestream_event(self): + dl = FakeYDL() + ie = LivestreamIE(dl) + result = ie.extract('http://new.livestream.com/tedx/cityenglish') + self.assertIsPlaylist(result) + self.assertEqual(result['title'], u'TEDCity2.0 (English)') + self.assertTrue(len(result['entries']) >= 4) + if __name__ == '__main__': unittest.main() diff --git a/test/test_utils.py b/test/test_utils.py index be10691..ff2e988 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -11,13 +11,16 @@ import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #from youtube_dl.utils import htmlentity_transform -from youtube_dl.utils import timeconvert -from youtube_dl.utils import sanitize_filename -from youtube_dl.utils import unescapeHTML -from youtube_dl.utils import orderedSet -from youtube_dl.utils import DateRange -from youtube_dl.utils import unified_strdate -from youtube_dl.utils import find_xpath_attr +from youtube_dl.utils import ( + timeconvert, + sanitize_filename, + unescapeHTML, + orderedSet, + DateRange, + unified_strdate, + find_xpath_attr, + get_meta_content, +) if sys.version_info < (3, 0): _compat_str = lambda b: b.decode('unicode-escape') @@ -127,5 +130,16 @@ class TestUtil(unittest.TestCase): self.assertEqual(find_xpath_attr(doc, './/node', 'x', 'a'), doc[1]) self.assertEqual(find_xpath_attr(doc, './/node', 'y', 'c'), doc[2]) + def test_meta_parser(self): + testhtml = u''' + + + + + ''' + get_meta = lambda name: get_meta_content(name, testhtml) + self.assertEqual(get_meta('description'), u'foo & bar') + self.assertEqual(get_meta('author'), 'Plato') + if __name__ == '__main__': unittest.main() diff --git a/test/test_youtube_signature.py b/test/test_youtube_signature.py new file mode 100644 index 0000000..5007d9a --- /dev/null +++ b/test/test_youtube_signature.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +import io +import re +import string +import sys +import unittest + +# Allow direct execution +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from youtube_dl.extractor import YoutubeIE +from youtube_dl.utils import compat_str, compat_urlretrieve + +_TESTS = [ + ( + u'https://s.ytimg.com/yts/jsbin/html5player-vflHOr_nV.js', + u'js', + 86, + u'>=<;:/.-[+*)(\'&%$#"!ZYX0VUTSRQPONMLKJIHGFEDCBA\\yxwvutsrqponmlkjihgfedcba987654321', + ), + ( + u'https://s.ytimg.com/yts/jsbin/html5player-vfldJ8xgI.js', + u'js', + 85, + u'3456789a0cdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS[UVWXYZ!"#$%&\'()*+,-./:;<=>?@', + ), + ( + u'https://s.ytimg.com/yts/swfbin/watch_as3-vflg5GhxU.swf', + u'swf', + 82, + u':/.-,+*)=\'&%$#"!ZYX0VUTSRQPONMLKJIHGFEDCBAzyxw>utsrqponmlkjihgfedcba987654321' + ), +] + + +class TestSignature(unittest.TestCase): + def setUp(self): + TEST_DIR = os.path.dirname(os.path.abspath(__file__)) + self.TESTDATA_DIR = os.path.join(TEST_DIR, 'testdata') + if not os.path.exists(self.TESTDATA_DIR): + os.mkdir(self.TESTDATA_DIR) + + +def make_tfunc(url, stype, sig_length, expected_sig): + basename = url.rpartition('/')[2] + m = re.match(r'.*-([a-zA-Z0-9_-]+)\.[a-z]+$', basename) + assert m, '%r should follow URL format' % basename + test_id = m.group(1) + + def test_func(self): + fn = os.path.join(self.TESTDATA_DIR, basename) + + if not os.path.exists(fn): + compat_urlretrieve(url, fn) + + ie = YoutubeIE() + if stype == 'js': + with io.open(fn, encoding='utf-8') as testf: + jscode = testf.read() + func = ie._parse_sig_js(jscode) + else: + assert stype == 'swf' + with open(fn, 'rb') as testf: + swfcode = testf.read() + func = ie._parse_sig_swf(swfcode) + src_sig = compat_str(string.printable[:sig_length]) + got_sig = func(src_sig) + self.assertEqual(got_sig, expected_sig) + + test_func.__name__ = str('test_signature_' + stype + '_' + test_id) + setattr(TestSignature, test_func.__name__, test_func) + +for test_spec in _TESTS: + make_tfunc(*test_spec) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_youtube_subtitles.py b/test/test_youtube_subtitles.py index 6412062..168e6c6 100644 --- a/test/test_youtube_subtitles.py +++ b/test/test_youtube_subtitles.py @@ -18,85 +18,65 @@ md5 = lambda s: hashlib.md5(s.encode('utf-8')).hexdigest() class TestYoutubeSubtitles(unittest.TestCase): def setUp(self): - DL = FakeYDL() - DL.params['allsubtitles'] = False - DL.params['writesubtitles'] = False - DL.params['subtitlesformat'] = 'srt' - DL.params['listsubtitles'] = False - def test_youtube_no_subtitles(self): - DL = FakeYDL() - DL.params['writesubtitles'] = False - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - subtitles = info_dict[0]['subtitles'] + self.DL = FakeYDL() + self.url = 'QRS8MkLhQmM' + def getInfoDict(self): + IE = YoutubeIE(self.DL) + info_dict = IE.extract(self.url) + return info_dict + def getSubtitles(self): + info_dict = self.getInfoDict() + return info_dict[0]['subtitles'] + def test_youtube_no_writesubtitles(self): + self.DL.params['writesubtitles'] = False + subtitles = self.getSubtitles() self.assertEqual(subtitles, None) def test_youtube_subtitles(self): - DL = FakeYDL() - DL.params['writesubtitles'] = True - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles']['en'] - self.assertEqual(md5(sub), '4cd9278a35ba2305f47354ee13472260') - def test_youtube_subtitles_it(self): - DL = FakeYDL() - DL.params['writesubtitles'] = True - DL.params['subtitleslangs'] = ['it'] - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles']['it'] - self.assertEqual(md5(sub), '164a51f16f260476a05b50fe4c2f161d') - def test_youtube_onlysubtitles(self): - DL = FakeYDL() - DL.params['writesubtitles'] = True - DL.params['onlysubtitles'] = True - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles']['en'] - self.assertEqual(md5(sub), '4cd9278a35ba2305f47354ee13472260') + self.DL.params['writesubtitles'] = True + subtitles = self.getSubtitles() + self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260') + def test_youtube_subtitles_lang(self): + self.DL.params['writesubtitles'] = True + self.DL.params['subtitleslangs'] = ['it'] + subtitles = self.getSubtitles() + self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d') def test_youtube_allsubtitles(self): - DL = FakeYDL() - DL.params['allsubtitles'] = True - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - subtitles = info_dict[0]['subtitles'] + self.DL.params['writesubtitles'] = True + self.DL.params['allsubtitles'] = True + subtitles = self.getSubtitles() self.assertEqual(len(subtitles.keys()), 13) def test_youtube_subtitles_sbv_format(self): - DL = FakeYDL() - DL.params['writesubtitles'] = True - DL.params['subtitlesformat'] = 'sbv' - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles']['en'] - self.assertEqual(md5(sub), '13aeaa0c245a8bed9a451cb643e3ad8b') + self.DL.params['writesubtitles'] = True + self.DL.params['subtitlesformat'] = 'sbv' + subtitles = self.getSubtitles() + self.assertEqual(md5(subtitles['en']), '13aeaa0c245a8bed9a451cb643e3ad8b') def test_youtube_subtitles_vtt_format(self): - DL = FakeYDL() - DL.params['writesubtitles'] = True - DL.params['subtitlesformat'] = 'vtt' - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') - sub = info_dict[0]['subtitles']['en'] - self.assertEqual(md5(sub), '356cdc577fde0c6783b9b822e7206ff7') + self.DL.params['writesubtitles'] = True + self.DL.params['subtitlesformat'] = 'vtt' + subtitles = self.getSubtitles() + self.assertEqual(md5(subtitles['en']), '356cdc577fde0c6783b9b822e7206ff7') def test_youtube_list_subtitles(self): - DL = FakeYDL() - DL.params['listsubtitles'] = True - IE = YoutubeIE(DL) - info_dict = IE.extract('QRS8MkLhQmM') + self.DL.params['listsubtitles'] = True + info_dict = self.getInfoDict() self.assertEqual(info_dict, None) def test_youtube_automatic_captions(self): - DL = FakeYDL() - DL.params['writeautomaticsub'] = True - DL.params['subtitleslangs'] = ['it'] - IE = YoutubeIE(DL) - info_dict = IE.extract('8YoUxe5ncPo') - sub = info_dict[0]['subtitles']['it'] - self.assertTrue(sub is not None) + self.url = '8YoUxe5ncPo' + self.DL.params['writeautomaticsub'] = True + self.DL.params['subtitleslangs'] = ['it'] + subtitles = self.getSubtitles() + self.assertTrue(subtitles['it'] is not None) + def test_youtube_nosubtitles(self): + self.url = 'sAjKT8FhjI8' + self.DL.params['writesubtitles'] = True + self.DL.params['allsubtitles'] = True + subtitles = self.getSubtitles() + self.assertEqual(len(subtitles), 0) def test_youtube_multiple_langs(self): - DL = FakeYDL() - DL.params['writesubtitles'] = True + self.url = 'QRS8MkLhQmM' + self.DL.params['writesubtitles'] = True langs = ['it', 'fr', 'de'] - DL.params['subtitleslangs'] = langs - IE = YoutubeIE(DL) - subtitles = IE.extract('QRS8MkLhQmM')[0]['subtitles'] + self.DL.params['subtitleslangs'] = langs + subtitles = self.getSubtitles() for lang in langs: self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) diff --git a/test/testdata/html5player-vflHOr_nV.js b/test/testdata/html5player-vflHOr_nV.js new file mode 100644 index 0000000..5bdfcfd --- /dev/null +++ b/test/testdata/html5player-vflHOr_nV.js @@ -0,0 +1,886 @@ +(function(){var f,aa=aa||{},l=this;function n(a,b){for(var c=a.split("."),d=b||l,e;e=c.shift();)if(null!=d[e])d=d[e];else return null;return d}function ba(){}function ca(a){a.getInstance=function(){return a.pq?a.pq:a.pq=new a}} +function da(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; +else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function q(a){return void 0!==a}function ea(a){return null!=a}function t(a){return"array"==da(a)}function fa(a){var b=da(a);return"array"==b||"object"==b&&"number"==typeof a.length}function u(a){return"string"==typeof a}function ga(a){return"number"==typeof a}function ha(a){return"function"==da(a)}function ia(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function ja(a){return a[ka]||(a[ka]=++la)} +var ka="closure_uid_"+(1E9*Math.random()>>>0),la=0;function ma(a,b,c){return a.call.apply(a.bind,arguments)}function na(a,b,c){if(!a)throw Error();if(2")&&(a=a.replace(za,">"));-1!=a.indexOf('"')&&(a=a.replace(Aa,"""));return a}var xa=/&/g,ya=//g,Aa=/\"/g,wa=/[&<>\"]/; +function Ba(a,b){for(var c=0,d=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=String(b).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),g=Math.max(d.length,e.length),h=0;0==c&&h(0==x[1].length? +0:parseInt(x[1],10))?1:0)||((0==r[2].length)<(0==x[2].length)?-1:(0==r[2].length)>(0==x[2].length)?1:0)||(r[2]x[2]?1:0)}while(0==c)}return c}function Ca(a){var b=Number(a);return 0==b&&/^[\s\xa0]*$/.test(a)?NaN:b}function Da(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})} +function Ea(a){var b=u(void 0)?"undefined".replace(/([-()\[\]{}+?*.$\^|,:#c?Math.max(0,a.length+c):c;if(u(a))return u(b)&&1==b.length?a.indexOf(b,c):-1;for(;cb?null:u(a)?a.charAt(b):a[b]}function Oa(a,b,c){for(var d=a.length,e=u(a)?a.split(""):a,g=0;gc?null:u(a)?a.charAt(c):a[c]} +function Qa(a,b,c){for(var d=u(a)?a.split(""):a,e=a.length-1;0<=e;e--)if(e in d&&b.call(c,d[e],e,a))return e;return-1}function A(a,b){return 0<=Ha(a,b)}function Ra(a){return 0==a.length}function Sa(a){if(!t(a))for(var b=a.length-1;0<=b;b--)delete a[b];a.length=0}function Ta(a,b){A(a,b)||a.push(b)}function Ua(a,b){var c=Ha(a,b),d;(d=0<=c)&&Va(a,c);return d}function Va(a,b){Ga.splice.call(a,b,1)}function Wa(a,b){var c=Oa(a,b,void 0);0<=c&&Va(a,c)} +function Xa(a){return Ga.concat.apply(Ga,arguments)}function Ya(a){var b=a.length;if(0=arguments.length?Ga.slice.call(a,b):Ga.slice.call(a,b,c)}function bb(a){for(var b={},c=0,d=0;d>1,k;k=b(c,a[h]);0b?1:ac&&$a(a,-(c+1),0,b)}function jb(a){for(var b=[],c=0;cb?e+="000":256>b?e+="00":4096>b&&(e+="0");return rb[a]=e+b.toString(16)}),'"')};function tb(a,b,c){return Math.min(Math.max(a,b),c)};function B(a,b){this.x=q(a)?a:0;this.y=q(b)?b:0}f=B.prototype;f.clone=function(){return new B(this.x,this.y)};function ub(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}function vb(a,b){return new B(a.x-b.x,a.y-b.y)}f.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};f.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};f.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this}; +f.scale=function(a,b){var c=ga(b)?b:a;this.x*=a;this.y*=c;return this};function wb(a,b,c,d){this.top=a;this.right=b;this.bottom=c;this.left=d}f=wb.prototype;f.clone=function(){return new wb(this.top,this.right,this.bottom,this.left)};f.contains=function(a){return this&&a?a instanceof wb?a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom:a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.y<=this.bottom:!1}; +f.ceil=function(){this.top=Math.ceil(this.top);this.right=Math.ceil(this.right);this.bottom=Math.ceil(this.bottom);this.left=Math.ceil(this.left);return this};f.floor=function(){this.top=Math.floor(this.top);this.right=Math.floor(this.right);this.bottom=Math.floor(this.bottom);this.left=Math.floor(this.left);return this};f.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this}; +f.scale=function(a,b){var c=ga(b)?b:a;this.left*=a;this.right*=a;this.top*=c;this.bottom*=c;return this};function C(a,b){this.width=a;this.height=b}function xb(a,b){return a==b?!0:a&&b?a.width==b.width&&a.height==b.height:!1}f=C.prototype;f.clone=function(){return new C(this.width,this.height)};function yb(a){return a.width/a.height}f.isEmpty=function(){return!(this.width*this.height)};f.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};f.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this}; +f.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};f.scale=function(a,b){var c=ga(b)?b:a;this.width*=a;this.height*=c;return this};function zb(a,b){return a.scale(yb(a)>yb(b)?b.width/a.width:b.height/a.height)};function Ab(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}f=Ab.prototype;f.clone=function(){return new Ab(this.left,this.top,this.width,this.height)};function Bb(a){return new Ab(a.left,a.top,a.right-a.left,a.bottom-a.top)}f.contains=function(a){return a instanceof Ab?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height}; +f.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};f.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};f.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this}; +f.scale=function(a,b){var c=ga(b)?b:a;this.left*=a;this.width*=a;this.top*=c;this.height*=c;return this};function Cb(a,b,c){for(var d in a)b.call(c,a[d],d,a)}function Db(a,b){for(var c in a)if(!b.call(void 0,a[c],c,a))return!1;return!0}function Eb(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b}function Fb(a){var b=[],c=0,d;for(d in a)b[c++]=d;return b}function Gb(a,b){for(var c in a)if(a[c]==b)return!0;return!1}function Hb(a,b){for(var c in a)if(b.call(void 0,a[c],c,a))return c}function Ib(a){for(var b in a)return!1;return!0}function Jb(a){var b={},c;for(c in a)b[c]=a[c];return b} +function Kb(a){var b=da(a);if("object"==b||"array"==b){if(a.clone)return a.clone();var b="array"==b?[]:{},c;for(c in a)b[c]=Kb(a[c]);return b}return a}var Lb="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");function Mb(a,b){for(var c,d,e=1;eparseFloat(ic)){hc=String(mc);break t}}hc=ic}var nc=hc,oc={};function pc(a){return oc[a]||(oc[a]=0<=Ba(nc,a))} +function qc(a){return D&&rc>=a}var sc=l.document,rc=sc&&D?gc()||("CSS1Compat"==sc.compatMode?parseInt(nc,10):5):void 0;var tc,uc=!D||qc(9),vc=!ac&&!D||D&&qc(9)||ac&&pc("1.9.1"),wc=D&&!pc("9"),xc=D||$b||bc;function yc(a){a=a.className;return u(a)&&a.match(/\S+/g)||[]}function zc(a,b){for(var c=yc(a),d=ab(arguments,1),e=c.length+d.length,g=c,h=0;h");g=g.join("")}g=e.createElement(g);h&&(u(h)?g.className=h:t(h)?zc.apply(null,[g].concat(h)):Lc(g,h));2a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null} +function rd(a){for(var b=new wb(0,Infinity,Infinity,0),c=Fc(a),d=c.a.body,e=c.a.documentElement,g=bc||"CSS1Compat"!=c.a.compatMode?c.a.body:c.a.documentElement;a=qd(a);)if(!(D&&0==a.clientWidth||bc&&0==a.clientHeight&&a==d||a==d||a==e||"visible"==ld(a,"overflow"))){var h=sd(a),k;k=a;if(ac&&!pc("1.9")){var m=parseFloat(kd(k,"borderLeftWidth"));if(td(k))var p=k.offsetWidth-k.clientWidth-m-parseFloat(kd(k,"borderRightWidth")),m=m+p;k=new B(m,parseFloat(kd(k,"borderTopWidth")))}else k=new B(k.clientLeft, +k.clientTop);h.x+=k.x;h.y+=k.y;b.top=Math.max(b.top,h.y);b.right=Math.min(b.right,h.x+a.clientWidth);b.bottom=Math.min(b.bottom,h.y+a.clientHeight);b.left=Math.max(b.left,h.x)}d=g.scrollLeft;g=g.scrollTop;b.left=Math.max(b.left,d);b.top=Math.max(b.top,g);c=Nc(c.a.parentWindow||c.a.defaultView||window);b.right=Math.min(b.right,d+c.width);b.bottom=Math.min(b.bottom,g+c.height);return 0<=b.top&&0<=b.left&&b.bottom>b.top&&b.right>b.left?b:null} +function sd(a){var b,c=Hc(a),d=ld(a,"position"),e=ac&&c.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==d&&(b=c.getBoxObjectFor(a))&&(0>b.screenX||0>b.screenY),g=new B(0,0),h;b=c?Hc(c):document;h=!D||qc(9)||gd(Fc(b))?b.documentElement:b.body;if(a==h)return g;if(a.getBoundingClientRect)b=pd(a),a=hd(Fc(c)),g.x=b.left+a.x,g.y=b.top+a.y;else if(c.getBoxObjectFor&&!e)b=c.getBoxObjectFor(a),a=c.getBoxObjectFor(h),g.x=b.screenX-a.screenX,g.y=b.screenY-a.screenY;else{b=a;do{g.x+=b.offsetLeft;g.y+= +b.offsetTop;b!=a&&(g.x+=b.clientLeft||0,g.y+=b.clientTop||0);if(bc&&"fixed"==ld(b,"position")){g.x+=c.body.scrollLeft;g.y+=c.body.scrollTop;break}b=b.offsetParent}while(b&&b!=a);if($b||bc&&"absolute"==d)g.y-=c.body.offsetTop;for(b=a;(b=qd(b))&&b!=c.body&&b!=h;)g.x-=b.scrollLeft,$b&&"TR"==b.tagName||(g.y-=b.scrollTop)}return g}function ud(a,b){var c=vd(a),d=vd(b);return new B(c.x-d.x,c.y-d.y)} +function wd(a){var b;if(a.getBoundingClientRect)b=pd(a),b=new B(b.left,b.top);else{b=hd(Fc(a));var c=sd(a);b=new B(c.x-b.x,c.y-b.y)}if(ac&&!pc(12)){var d;D?d="-ms-transform":bc?d="-webkit-transform":$b?d="-o-transform":ac&&(d="-moz-transform");var e;d&&(e=ld(a,d));e||(e=ld(a,"transform"));a=e?(a=e.match(xd))?new B(parseFloat(a[1]),parseFloat(a[2])):new B(0,0):new B(0,0);a=new B(b.x+a.x,b.y+a.y)}else a=b;return a} +function vd(a){if(1==a.nodeType)return wd(a);var b=ha(a.$x),c=a;a.targetTouches?c=a.targetTouches[0]:b&&a.re.targetTouches&&(c=a.re.targetTouches[0]);return new B(c.clientX,c.clientY)}function yd(a,b,c){if(b instanceof C)c=b.height,b=b.width;else if(void 0==c)throw Error("missing height argument");zd(a,b);a.style.height=nd(c,!0)}function nd(a,b){"number"==typeof a&&(a=(b?Math.round(a):a)+"px");return a}function zd(a,b){a.style.width=nd(b,!0)} +function Ad(a){var b=Bd;if("none"!=ld(a,"display"))return b(a);var c=a.style,d=c.display,e=c.visibility,g=c.position;c.visibility="hidden";c.position="absolute";c.display="inline";a=b(a);c.display=d;c.position=g;c.visibility=e;return a}function Bd(a){var b=a.offsetWidth,c=a.offsetHeight,d=bc&&!b&&!c;return q(b)&&!d||!a.getBoundingClientRect?new C(b,c):(a=pd(a),new C(a.right-a.left,a.bottom-a.top))}function Cd(a){var b=sd(a);a=Ad(a);return new Ab(b.x,b.y,a.width,a.height)} +function Dd(a,b){var c=a.style;"opacity"in c?c.opacity=b:"MozOpacity"in c?c.MozOpacity=b:"filter"in c&&(c.filter=""===b?"":"alpha(opacity="+100*b+")")}function td(a){return"rtl"==ld(a,"direction")} +function Ed(a){var b=Hc(a),c=D&&a.currentStyle;if(c&&gd(Fc(b))&&"auto"!=c.width&&"auto"!=c.height&&!c.boxSizing)return b=Fd(a,c.width,"width","pixelWidth"),a=Fd(a,c.height,"height","pixelHeight"),new C(b,a);c=new C(a.offsetWidth,a.offsetHeight);b=Gd(a);a=Hd(a);return new C(c.width-a.left-b.left-b.right-a.right,c.height-a.top-b.top-b.bottom-a.bottom)} +function Fd(a,b,c,d){if(/^\d+px?$/.test(b))return parseInt(b,10);var e=a.style[c],g=a.runtimeStyle[c];a.runtimeStyle[c]=a.currentStyle[c];a.style[c]=b;b=a.style[d];a.style[c]=e;a.runtimeStyle[c]=g;return b}function Id(a,b){var c=a.currentStyle?a.currentStyle[b]:null;return c?Fd(a,c,"left","pixelLeft"):0} +function Gd(a){if(D){var b=Id(a,"paddingLeft"),c=Id(a,"paddingRight"),d=Id(a,"paddingTop");a=Id(a,"paddingBottom");return new wb(d,c,a,b)}b=kd(a,"paddingLeft");c=kd(a,"paddingRight");d=kd(a,"paddingTop");a=kd(a,"paddingBottom");return new wb(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))}var Jd={thin:2,medium:4,thick:6}; +function Kd(a,b){if("none"==(a.currentStyle?a.currentStyle[b+"Style"]:null))return 0;var c=a.currentStyle?a.currentStyle[b+"Width"]:null;return c in Jd?Jd[c]:Fd(a,c,"left","pixelLeft")}function Hd(a){if(D){var b=Kd(a,"borderLeft"),c=Kd(a,"borderRight"),d=Kd(a,"borderTop");a=Kd(a,"borderBottom");return new wb(d,c,a,b)}b=kd(a,"borderLeftWidth");c=kd(a,"borderRightWidth");d=kd(a,"borderTopWidth");a=kd(a,"borderBottomWidth");return new wb(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))} +var Ld=/[^\d]+$/,Md={cm:1,"in":1,mm:1,pc:1,pt:1},Nd={em:1,ex:1}; +function Od(a){var b=ld(a,"fontSize"),c;c=(c=b.match(Ld))&&c[0]||null;if(b&&"px"==c)return parseInt(b,10);if(D){if(c in Md)return Fd(a,b,"left","pixelLeft");if(a.parentNode&&1==a.parentNode.nodeType&&c in Nd)return a=a.parentNode,c=ld(a,"fontSize"),Fd(a,b==c?"1em":b,"left","pixelLeft")}c=Pc("span",{style:"visibility:hidden;position:absolute;line-height:0;padding:0;margin:0;border:0;height:1em;"});a.appendChild(c);b=c.offsetHeight;F(c);return b}var xd=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;var Pd="StopIteration"in l?l.StopIteration:Error("StopIteration");function Qd(){}Qd.prototype.next=function(){throw Pd;};Qd.prototype.Kd=function(){return this};function Rd(a){if(a instanceof Qd)return a;if("function"==typeof a.Kd)return a.Kd(!1);if(fa(a)){var b=0,c=new Qd;c.next=function(){for(;;){if(b>=a.length)throw Pd;if(b in a)return a[b++];b++}};return c}throw Error("Not implemented");} +function Sd(a,b,c){if(fa(a))try{z(a,b,c)}catch(d){if(d!==Pd)throw d;}else{a=Rd(a);try{for(;;)b.call(c,a.next(),void 0,a)}catch(e){if(e!==Pd)throw e;}}}function Td(a){if(fa(a))return Ya(a);a=Rd(a);var b=[];Sd(a,function(a){b.push(a)});return b};function Ud(a,b){this.b={};this.a=[];this.g=this.Q=0;var c=arguments.length;if(12*this.Q&&Vd(this),!0):!1}; +function Vd(a){if(a.Q!=a.a.length){for(var b=0,c=0;b=c.length)throw Pd;var h=c[b++];return a?h:d[h]}};return h};function Xd(a,b){return Object.prototype.hasOwnProperty.call(a,b)};function Yd(a){if("function"==typeof a.hc)return a.hc();if(u(a))return a.split("");if(fa(a)){for(var b=[],c=a.length,d=0;dc?a[1]="?":c==b.length-1&&(a[1]=void 0)}return a.join("")}function ge(a,b,c){if(t(b))for(var d=0;db)throw Error("Bad port number "+b);a.oh=b}else a.oh=null}function te(a,b,c){b instanceof ve?(a.a=b,Be(a.a,a.Ze)):(c||(b=we(b,Ce)),a.a=new ve(b,0,a.Ze))}function De(a,b,c){a.a.set(b,c)}function Ee(a,b,c){t(c)||(c=[String(c)]);Fe(a.a,b,c)}function ue(a,b,c){a.Al=c?b?decodeURIComponent(b):"":b;return a} +function Ge(a){De(a,"zx",Math.floor(2147483648*Math.random()).toString(36)+Math.abs(Math.floor(2147483648*Math.random())^w()).toString(36));return a}function He(a){return a instanceof pe?a.clone():new pe(a,void 0)}function Ie(a,b,c,d){var e=new pe(null,void 0);a&&qe(e,a);b&&re(e,b);c&&se(e,c);d&&(e.ph=d);return e}function we(a,b){return u(a)?encodeURI(a).replace(b,Je):null}function Je(a){a=a.charCodeAt(0);return"%"+(a>>4&15).toString(16)+(a&15).toString(16)} +var xe=/[#\/\?@]/g,ze=/[\#\?:]/g,ye=/[\#\?]/g,Ce=/[\#\?@]/g,Ae=/#/g;function ve(a,b,c){this.a=a||null;this.b=!!c}function Ke(a){if(!a.Ha&&(a.Ha=new Ud,a.Q=0,a.a))for(var b=a.a.split("&"),c=0;cthis.j;){var c=this.a-this.j,d=this.i[0];d.ih<=c?(this.a-=d.ih,this.i.shift()):(this.a-=c,d.ih-=c)}};function Kg(a,b){Jg(a,"value");var c=b*a.a,d=0,e=NaN;a.i.some(function(a){d+=a.ih;e=a.value;if(d>=c)return!0});return e}Ig.prototype.b=function(){return Kg(this,this.G)}; +function Jg(a,b){a.k!=b&&(a.k=b,fb(a.i,b))};function Lg(){};function Mg(){}y(Mg,Lg);Mg.prototype.fb=function(){var a=0;Sd(this.Kd(!0),function(){a++});return a};Mg.prototype.clear=function(){var a=Td(this.Kd(!0)),b=this;z(a,function(a){b.remove(a)})};function Ng(a){this.a=a}y(Ng,Mg);f=Ng.prototype;f.isAvailable=function(){if(!this.a)return!1;try{return this.a.setItem("__sak","1"),this.a.removeItem("__sak"),!0}catch(a){return!1}};f.set=function(a,b){try{this.a.setItem(a,b)}catch(c){if(0==this.a.length)throw"Storage mechanism: Storage disabled";throw"Storage mechanism: Quota exceeded";}};f.get=function(a){a=this.a.getItem(a);if(!u(a)&&null!==a)throw"Storage mechanism: Invalid value was encountered";return a};f.remove=function(a){this.a.removeItem(a)}; +f.fb=function(){return this.a.length};f.Kd=function(a){var b=0,c=this.a,d=new Qd;d.next=function(){if(b>=c.length)throw Pd;var d;d=c.key(b++);if(a)return d;d=c.getItem(d);if(!u(d))throw"Storage mechanism: Invalid value was encountered";return d};return d};f.clear=function(){this.a.clear()};f.key=function(a){return this.a.key(a)};function Og(){var a=null;try{a=window.localStorage||null}catch(b){}this.a=a}y(Og,Ng);function Pg(a){this.Pf=a;this.jq=new ob}f=Pg.prototype;f.Pf=null;f.jq=null;f.set=function(a,b){q(b)?this.Pf.set(a,nb(this.jq,b)):this.Pf.remove(a)};f.get=function(a){var b;try{b=this.Pf.get(a)}catch(c){return}if(null!==b)try{return kb(b)}catch(d){throw"Storage: Invalid value was encountered";}};f.remove=function(a){this.Pf.remove(a)};function Qg(){var a=null;try{a=window.sessionStorage||null}catch(b){}this.a=a}y(Qg,Ng);function Rg(a){Pg.call(this,a)}y(Rg,Pg);function Sg(a){this.data=a}function Tg(a){return!q(a)||a instanceof Sg?a:new Sg(a)}Rg.prototype.set=function(a,b){Rg.C.set.call(this,a,Tg(b))};Rg.prototype.a=function(a){a=Rg.C.get.call(this,a);if(!q(a)||a instanceof Object)return a;throw"Storage: Invalid value was encountered";};Rg.prototype.get=function(a){if(a=this.a(a)){if(a=a.data,!q(a))throw"Storage: Invalid value was encountered";}else a=void 0;return a};function Ug(a){Pg.call(this,a)}y(Ug,Rg);function Vg(a){var b=a.creation;a=a.expiration;return!!a&&aw()}Ug.prototype.set=function(a,b,c){if(b=Tg(b)){if(c){if(c=this.start&&(a=a.keyCode)a.keyCode=-1}catch(b){}};f.$x=function(){return this.re};var uh="closure_listenable_"+(1E6*Math.random()|0);function vh(a){try{return!(!a||!a[uh])}catch(b){return!1}}var wh=0;function xh(a,b,c,d,e){this.cf=a;this.proxy=null;this.src=b;this.type=c;this.capture=!!d;this.eb=e;this.key=++wh;this.removed=this.Pi=!1}function yh(a){a.removed=!0;a.cf=null;a.proxy=null;a.src=null;a.eb=null};function zh(a){this.src=a;this.$a={};this.a=0}zh.prototype.add=function(a,b,c,d,e){var g=this.$a[a];g||(g=this.$a[a]=[],this.a++);var h=Ah(g,b,d,e);-1c.keyCode||void 0!=c.returnValue)){t:{var g=!1;if(0==c.keyCode)try{c.keyCode=-1;break t}catch(h){g=!0}if(g||void 0==c.returnValue)c.returnValue=!0}c=[];for(g=d.currentTarget;g;g=g.parentNode)c.push(g);for(var g=a.type,k=c.length-1;!d.se&&0<=k;k--)d.currentTarget=c[k],e&=Oh(c[k],g,!0,d);for(k=0;!d.se&&k>>0);function Hh(a){return ha(a)?a:a[Qh]||(a[Qh]=function(b){return a.handleEvent(b)})};function Rh(){this.jd=new zh(this);this.U=this}y(Rh,Lf);Rh.prototype[uh]=!0;f=Rh.prototype;f.Ml=null;f.addEventListener=function(a,b,c,d){Gh(this,a,b,c,d)};f.removeEventListener=function(a,b,c,d){Kh(this,a,b,c,d)}; +function Sh(a,b){var c,d=a.Ml;if(d){c=[];for(var e=1;d;d=d.Ml)c.push(d),++e}var d=a.U,e=b,g=e.type||e;if(u(e))e=new ph(e,d);else if(e instanceof ph)e.target=e.target||d;else{var h=e,e=new ph(g,d);Mb(e,h)}var h=!0,k;if(c)for(var m=c.length-1;!e.se&&0<=m;m--)k=e.currentTarget=c[m],h=Th(k,g,!0,e)&&h;e.se||(k=e.currentTarget=d,h=Th(k,g,!0,e)&&h,e.se||(h=Th(k,g,!1,e)&&h));if(c)for(m=0;!e.se&&mb)break}return c}function Zh(a,b,c){for(var d=[],e=0;ec)break;g.start>b&&d.push(g)}return d}function $h(a,b){for(var c=[],d=0;db){c.push(e.start);break}}c.sort(eb);return c[0]};var ai,bi,ci,di,ei;ei=di=ci=bi=ai=!1;var fi=Wb();fi&&(-1!=fi.indexOf("Firefox")||-1!=fi.indexOf("Camino")||(-1!=fi.indexOf("iPhone")||-1!=fi.indexOf("iPod")?ai=!0:-1!=fi.indexOf("iPad")?bi=!0:-1!=fi.indexOf("Chrome")?di=!0:-1!=fi.indexOf("Android")?ci=!0:-1!=fi.indexOf("Safari")&&(ei=!0)));var gi=ai,hi=bi,ii=ci,ji=di,ki=ei;var li,mi;var ni=Wb(),oi=ni.match(/\((iPad|iPhone|iPod)( Simulator)?;/);if(!oi||2>oi.length)li=void 0;else{var pi=ni.match(/\((iPad|iPhone|iPod)( Simulator)?; (U; )?CPU (iPhone )?OS (\d_\d)[_ ]/);li=pi&&6==pi.length?Number(pi[5].replace("_",".")):0}(mi=0<=li)&&0<=Wb().search("Safari")&&Wb().search("Version");var qi=gi||hi;function ri(){return si("(ps3; leanback shell)")}function si(a){var b=Wb();return b?0<=b.toLowerCase().indexOf(a.toLowerCase()):!1};function ti(){}var ui=mi&&4>li?0.1:0,vi=new ti;f=ti.prototype;f.yd=null;f.Jk=!1;f.Qd=0;f.Sl=0;function wi(a,b){var c="";b&&(a.yd=b,c=xi(b));a.src&&""==c||(c&&a.src!=c&&(a.src=c),b&&b.a||a.load())}function yi(a,b){0a);c++)if(a<=b.end(c))return c;return 0}f.inUnbufferedArea=function(){var a=this.buffered;if(!a||!a.length)return!0;var b=zi(this);if(0c||a.end(b)e.end&&(c.push(e),a.a.splice(d--,1))}d=Yh(a.b,2147483646);d=d.concat(Zh(a.b,2147483646));e=[];if(c.length)for(var g=0;gthis.g&&(d=d.concat(Zh(this.b,this.g,b)));c=c.concat(Qi(this,d));this.g=b;!this.Qh&&this.k&&(c.unshift(["onLockBlockExit",this.k]),this.k=null,P(e,2)&&(this.g=2147483647));this.B()&&(b=$h(this.b,this.g),null!=b&& +(this.Jg=Wh(v(this.Xb,this),b-this.g)));for(d=0;da.status)e=fj(c,a);if(d)t:{switch(c){case "XML":d=0==parseInt(e&&e.return_code,10);break t;case "RAW":d=!0;break t}d=!!e}var e=e||{},k=b.X||l;d?b.Oa&&b.Oa.call(k, +a,e):b.onError&&b.onError.call(k,a,e);b.td&&b.td.call(k,a,e)}},b.method,e,b.headers,b.responseType,b.withCredentials);b.al&&0=g[0]||b>=g[1]){a=e;break t}}a="tiny"}else a="auto";this.i=a;this.a=c||0}var uj="auto highres hd1080 hd720 large medium small tiny".split(" "),vj={auto:[0,0],tiny:[256,144],light:[320,240],small:[320,240],medium:[640,360],large:[854,480],hd720:[1280,720],hd1080:[1920,1080],highres:[2048,1536]};function wj(a,b){this.start=a;this.end=b;this.length=b-a+1}function xj(a){a=a.split("-");return 2==a.length&&(a=new wj(parseInt(a[0],10),parseInt(a[1],10)),!isNaN(a.start)&&!isNaN(a.end)&&!isNaN(a.length)&&0a.info.kb||4==a.info.type)return!0;var b=new DataView(a.data.buffer,a.data.byteOffset,a.data.byteLength),c=b.getUint32(0,!1),b=b.getUint32(4,!1);if(2==a.info.type)return c==a.info.kb&&1936286840==b;if(3==a.info.type&&0==a.info.Yb)return 1836019558==b}return!0};function Jj(a){this.a=a;this.b=0;this.g=-1}var Kj=0;function Lj(a,b){a.a=Zi(a.a,b)};function Mj(){this.Q=0;this.a=new Float64Array(128);this.b=new Float32Array(128);this.g=!1}Mj.prototype.fb=function(){return this.Q};function Nj(a,b){var c=cb(a.b.subarray(0,a.Q),eb,b);return 0<=c?c:Math.max(0,-c-2)}function Oj(a){if(a.a.lengthd;d++)c=256*c+Vj(a);return c}for(var e=128,d=0;6>d&&e>c;d++)c=256*c+Vj(a),e*=128;return b?c-e:c} +function Vj(a){return a.b.getUint8(a.a++)};function Wj(a,b,c,d){this.info=b;this.j=new Jj(a);this.o=c;this.k=d;this.g=this.b=null;this.i=!1;this.index=new Mj;b=parseInt;c=a.search(me);d=le(a,0,"clen",c);if(0>d)a=null;else{var e=a.indexOf("&",d);if(0>e||e>c)e=c;d+=5;a=ua(a.substr(d,e-d))}this.a=b(a,10)}function Xj(a){return!(!a.b||!a.index.fb())} +function Yj(a,b){var c=new Ej(a,a.o,1),d=new Ej(a,a.k,2),e=[],g=[c];Gj(c,d)?g.push(d):e.push([d]);isNaN(a.a)?b=0:b>a.a&&(b=a.a);c=g[g.length-1];d=c.ka.end-g[0].ka.start+1;b>d&&(c=new wj(c.ka.end+1,c.ka.end+1+(b-d)-1),g.push(new Ej(a,c,4)));e.push(g);return e} +function Zj(a,b,c,d){for(var e=[];b=c+d)break}return e} +function ak(a,b){for(var c=0;c+1=a.index.a[c+1];)c++;return Zj(a,c,b.ka.start,b.ka.length)}Wj.prototype.Uh=function(a,b){Xj(this);if(!Xj(this)){var c=new wj(a.ka.end+1,a.ka.end+1+b-1);c.end+1>this.a&&(c=new wj(c.start,this.a-1));return[new Ej(a.a,c,4)]}4==a.type&&(c=ak(this,a),a=c[c.length-1]);var c=0,d=a.ka.start+a.Yb+a.kb;3==a.type&&(c=a.i,d==a.ka.end+1&&(c+=1));return Zj(this,c,d,b)};function bk(a,b,c){b=Nj(a.index,b);return Zj(a,b,a.index.a[b],c)};function ck(){this.duration=0;this.a={}}var dk=/PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?/;function ek(a){var b=new ck;z(a,function(a){var d=a.type,e=a.itag,g=null;rj(d)&&(g=a.size.split("x"),g=new tj(parseInt(g[0],10),parseInt(g[1],10)));var h=null;qj(d)&&(h=new nj);d=new oj(e,d,h,g,null,parseInt(a.bitrate,10)/8);g=xj(a.init);h=xj(a.index);(a=fk(a.url,d,a.s))&&(b.a[e]=new Wj(a,d,g,h))});return b} +function gk(a,b){for(var c=a;c;c=c.parentNode)if(c.attributes){var d=c.attributes[b];if(d)return d.value}return""}function hk(a,b){for(var c=a;c;c=c.parentNode){var d=c.getElementsByTagName(b);if(0=b)return a.b[b]=d;a.b[b]=c-1;return c-1};var ok={0:"MONO",1:"LEFT_RIGHT",2:"RIGHT_LEFT",3:"TOP_BOTTOM",4:"BOTTOM_TOP"};var pk={hC:1,iC:2,jC:3};var qk;var rk=Wb(),rk=rk.toLowerCase();if(-1!=rk.indexOf("android")){var sk=rk.match(/android\D*(\d\.\d)[^\;|\)]*[\;\)]/);if(sk)qk=Number(sk[1]);else{var tk={cupcake:1.5,donut:1.6,eclair:2,froyo:2.2,gingerbread:2.3,honeycomb:3,"ice cream sandwich":4,jellybean:4.1},uk=rk.match("("+Fb(tk).join("|")+")");qk=uk?tk[uk[0]]:0}}else qk=void 0;var vk,wk;function xk(){var a=n("yt.player.utils.videoElement_");a||(a=document.createElement("video"),qa("yt.player.utils.videoElement_",a));return a}function yk(){if(2.2==qk)return!0;var a=xk();try{return!(!a||!a.canPlayType||!a.canPlayType('video/mp4; codecs="avc1.42001E, mp4a.40.2"')&&!a.canPlayType('video/webm; codecs="vp8.0, vorbis"'))}catch(b){return!1}} +function zk(){var a=Pc("div",{"class":"html5-player-css-loaded"});document.body.appendChild(a);var b="none"==kd(a,"display");F(a);return b};function Ak(){var a;if(void 0==vk&&(vk=!1,window.crypto&&window.crypto.wy))try{a=new Uint8Array(1),window.crypto.wy(a),vk=!0}catch(b){}if(vk){a=Array(16);var c=new Uint8Array(16);window.crypto.getRandomValues(c);for(var d=0;dc;c++){for(var d=w(),e=0;e=a.fb()){a=this.a;for(var d=0;d>1,a[d].a>c.a)a[b]=a[d],b=d;else break;a[b]=c}f=Ik.prototype; +f.remove=function(){var a=this.a,b=a.length,c=a[0];if(!(0>=b)){if(1==b)Sa(a);else{a[0]=a.pop();for(var a=0,b=this.a,d=b.length,e=b[a];a>1;){var g=2*a+1,h=2*a+2,g=he.a)break;b[a]=b[g];a=g}b[a]=e}return c.b}};f.hc=function(){for(var a=this.a,b=[],c=a.length,d=0;dli?!1:!0;a.Lj="1"==c.infringe||"1"==c.muted;a.vo=c.authkey;a.Da=c.authuser;a.Ca||(a.Ca=c.cpn||Bk());a.Fc=c.csi_page_type;a.La=c.sw;a.gb=T(a.gb,c.dto);a.qb=c.t;a.Qk=T(a.Qk,c.cenchd);a.gg="1"==c.enable_cardio;a.Vl="1"==c.enable_cardio_before_playback;a.rf=void 0==(c.end||c.endSeconds)?a.rf:Number(c.end||c.endSeconds);a.Wl="1"!=c.no_get_video_log;a.kj="1"==c.tmi;a.wo="1"==c.livemonitor;a.Fb="1"==c.live_playback;a.Wk=T(a.Wk,c.mdx);a.Xk=T(a.Xk, +c.utpsa);a.Io=c.iurlmaxres;a.H=U(a.H,c.oauth_token);a.U=U(a.U,c.vvt);a.nd=c.osig;a.Hc=c.ptchn;a.Dc=c.oid;a.A=c.ptk;a.Gc=c.pltype;a.ab=c.plid;a.j=c.eventid;a.Na=U(a.Na,c.list);a.Lc=c.pyv_beacon_url;a.pd=c.purchase_id;a.fa=c.sdetail;a.od=c.sourceid;a.ea=U(a.ea,c.feature);a.Nc=1==(void 0==c.is_fling?a.Nc?1:0:Number(c.is_fling));a.oa=U(a.oa,c.ytr);a.Jo=c.iurlsd;a.Xl="1"==c.skip_kansas_logging;a.kf=Ek(a.kf,c.vq||c.suggestedQuality,sj);a.te=c.approx_threed_layout||0;a.Kj="1"==c.threed_converted;a.Ji=c.iurl; +a.gj="1"==c.sendtmp;a.ig=!!a.La||a.gj;a.ya=void 0==(c.start||c.startSeconds)?a.ya:Number(c.start||c.startSeconds);a.L=c.docid||c.video_id||c.videoId;xl(a,c.watermark);a.Ic=U(a.Ic,c.ypc_gid);a.Mc=U(a.Mc,c.ypc_license_session_token);if(c.ad3_module||c.ad_module)"1"==c.allow_html5_ads?(a.ga=!0,"1"==c.ad_preroll&&a.I.push("ad")):"1"==c.supported_without_ads||c.cta&&c.fexp&&(-1!=c.fexp.indexOf("924604")||-1!=c.fexp.indexOf("924610"))||(a.Zm=!0);c.adaptive_fmts&&(a.Ra=yl(a,c.adaptive_fmts));c.allow_embed&& +(a.Ho="1"==c.allow_embed);c.autoplay&&(a.hf="1"==c.autoplay);c.iv_load_policy&&(a.ca=zl(c.iv_load_policy,a.ca));c.cc_load_policy&&(a.Tm=zl(c.cc_load_policy,2));c.dash&&void 0===a.$&&(a.$="1"==c.dash);if(c.dashmpd){a.N=S(c.dashmpd,{cpn:a.Ca});var d=/\/s\/([0-9A-F.]+)/,e=d.exec(a.N);e&&(e=lj(e[1]),a.N=a.N.replace(d,"/signature/"+e))}c.delay&&(a.K=Ca(c.delay));c.idpj&&(a.cg=Ca(c.idpj));c.url_encoded_fmt_stream_map&&(a.O=yl(a,c.url_encoded_fmt_stream_map));c.hlsvp&&(d=Dk(c.hlsvp,a.Ca,c.fexp&&-1!=c.fexp.indexOf("934005")&& +!c.on3g),a.O.push(d));c.length_seconds&&(a.wa=Ca(c.length_seconds));c.ldpj&&(a.uh=Ca(c.ldpj));c.loudness&&(a.za=c.loudness,a.zh=-15a.za?Math.pow(10,(-18-a.za)/20):1);c.partnerid&&(a.qd=Ca(c.partnerid));c.pyv_billable_url&&Cj(c.pyv_billable_url,Aj)&&(a.R=c.pyv_billable_url);c.pyv_conv_url&&Cj(c.pyv_conv_url,Aj)&&(a.Ia=c.pyv_conv_url);c.url_encoded_third_party_media&&(a.mf=wl(c.url_encoded_third_party_media));c.threed_module&&!c.threed_converted&&(a.S=c.threed_module,a.Jc=6);if("1"==c.track_embed|| +c.tk)a.nm=!0;c.watch_ajax_token&&ff("watch_actions_ajax",c.watch_ajax_token);c.fresca_preroll&&a.I.push("fresca");c.ypc_clickwrap_preroll&&a.I.push("ypc_clickwrap");void 0!=c.start&&(a.Mj=c.start);void 0!=c.end&&(a.Qm=c.end);a.Ec=U(a.Ec,c.ucid);z(["baseUrl","uid","oeid","ieid","ppe"],function(a){this.i[a]=c[a]},a);a.i.focEnabled="1"==c.focEnabled;a.i.rmktEnabled="1"==c.rmktEnabled;a.pm=Al(c.rmktPingThreshold,c.length_seconds);a.b=c;Eg(a,c);a.$&&Bl(a)} +function Bl(a){if(ii&&si("chrome")&&!pc(29)?0:window.MediaSource||window.WebKitMediaSource||HTMLMediaElement.prototype.webkitSourceAddId)if(a.Ra)a.B=ek(a.Ra);else if(a.N){var b={format:"RAW",method:"GET",X:a,Oa:a.jx,onError:a.ul};a.Ue=!0;a=Dj(a.N);ej(a,b)}} +f.jx=function(a){if(!this.ha()){if(200<=a.status&&400>a.status){var b=new ck;t:{a=a.responseText;a=(new DOMParser).parseFromString(a,"text/xml").getElementsByTagName("MPD")[0];var c;if(c=gk(a,"mediaPresentationDuration")){var d=dk.exec(c);c=d?3600*parseFloat(d[2]||0)+60*parseFloat(d[4]||0)+parseFloat(d[6]||0):parseFloat(c)}else c=0;b.duration=c;a=a.getElementsByTagName("Representation");for(c=0;c=a.Pa)if(a.loop)a.pa=0;else return null;rg(a,a.pa);return qg(a,a.pa)}function Ul(a){if(0>--a.pa)if(a.loop)a.pa=a.Pa-1;else return null;rg(a,a.pa);return qg(a,a.pa)}function qg(a,b){var c=void 0!=b?b:a.pa;if(c=a.a&&c in a.a?a.a[a.b[c]]:null)c.ya=a.ya||c.Mj;return c} +function sg(a,b){a.Po=b;var c=a.b&&null!=a.b[a.pa]?a.b[a.pa]:a.pa;a.b=[];for(var d=0;dc&&(e+="0"));0d&&(e+="0"));e+=d+":";10>a&&(e+="0");return e+a};var Ym={UC:"html5-stop-propagation",uA:"html5-chromeless",eC:"html5-live-dvr-disabled",fC:"html5-live-dvr-engaged",gC:"html5-live-playback",oC:"html5-mobile",qC:"modest-branding",sC:"html5-native-controls",ZC:"html5-tablet",YC:"html5-tablet-body",lC:"html5-main-video",jD:"html5-video-container",kD:"html5-video-content",lD:"html5-video-controls",mD:"ytp-fallback",nD:"ytp-fallback-content",pD:"html5-video-loader",uD:"html5-watermark",dA:"html5-branded-watermark",tD:"html5-viewport-sheet",AA:"html5-context-menu", +Fr:"html5-context-menu-copy-debug-info",Gr:"html5-context-menu-copy-embed-html",Hr:"html5-context-menu-copy-video-url",Ir:"html5-context-menu-copy-video-url-at-current-time",Jr:"html5-context-menu-link",Kr:"html5-context-menu-report-playback-issue",Lr:"html5-context-menu-show-video-info",BA:"html5-show-video-info-template",pC:"html5-modal-panel",WB:"html5-info-bar",Yz:"autohide-off",Zz:"autohide-on",Xz:"autohide-fade",Vz:"autohide-auto",Wz:"autohide-embeds",$z:"autohide-seekbar",Uz:"autohide-aspect", +KB:"hide-controls",LB:"hide-info-bar",MB:"html5-hide-share",NB:"html5-hide-volume",sD:"video-thumbnail",DC:"html5-popup-dialog",Oz:"html5-async-progress",Pz:"html5-async-success",Nz:"html5-async-error",pA:"html5-center-overlay",NC:"ytp-scalable-icon-shrink",MC:"ytp-scalable-icon-grow",RB:"house-brand"};function Zm(a,b){(a=Ic(a))&&a.style&&(a.style.display=b?"":"none",Dc(a,"hid",!b))}function $m(a){return(a=Ic(a))?!("none"==a.style.display||Cc(a,"hid")):!1}function an(a){z(arguments,function(a){Zm(a,!0)})}function bn(a){z(arguments,function(a){Zm(a,!1)})}function cn(a){var b=document.body;if(void 0!=b.style[a])return a;a=a.charAt(0).toUpperCase()+a.substr(1);for(var c=["Moz","Webkit","ms","O"],d=0;da?(Wm(this.b,a/1E4),a=1E4-a,E("videowall-still-listlabel-autoplay-message",this.a).innerHTML=nf("AUTOPLAY_MESSAGE",Math.ceil(a/1E3))):(kf(this.k),this.select("autoplay"))}; +f.Iu=function(a){var b=ed(a.target,"videowall-still-listlabel-autoplay"),c=E("autoplay-play-canvas",this.a);V(b,"videowall-still-listlabel-autoplay-hide");V(c,"autoplay-play-canvas-hide");X(this.a,"videowall-still-autoplay",!1);a.stopPropagation();this.G.log({cancelButtonClick:"1"});kf(this.k)};f.yv=function(){this.select()};f.zv=function(a){switch(a.keyCode){case 13:case 32:this.select(),a.preventDefault()}};function sn(a,b,c){rm.call(this,a,b);this.kl=[];this.yb.D("onResize",this.$v,this);this.yb.D("videodatachange",this.aw,this);this.b=c}y(sn,rm);f=sn.prototype;f.Cl="videowall-endscreen";f.mh=null;f.pb=null;f.kl=null;f.ip=!1;f.nh=0;f.create=function(){sn.C.create.call(this);this.pb=this.a.getVideoData().Kc;tn(this)};f.destroy=function(){this.Uf.innerHTML="";delete this.pb;sn.C.destroy.call(this)}; +f.load=function(){if(this.pb&&this.pb.length&&this.pb[0].endscreen_autoplay){var a=un(this,0);a.b=new Tm;V(a.b.element,"autoplay-play-canvas");a.b.T(a.a);a.b.show();X(a.a,"videowall-still-autoplay",!0);var b=E("videowall-still-listlabel-autoplay",a.a),c=new Om(a.o);c.T(b);Qm(c,"videowall-still-listlabel-autoplay-cancel");c.Aa(a.o.getMsg("YTP_BUTTON_CANCEL"));c.show();K(c,"click",v(a.Iu,a));a.G.log({cancelButtonShow:"1"});a.A=new Date;a.k=jf(v(a.Hu,a),50)}}; +function tn(a){if(a.pb&&a.pb.length){X(a.Lf,"endscreen-enable-layout",!0);a.Uf.innerHTML="";var b=Ad(a.Lf);Zl(Q(a.a),"ad-showing")&&(b.height-=200);b.height-=30;var c=Math.floor(b.width/158),d=Math.floor(b.height/(158/1.45));if(1>d||1>c)a.nh=0;else{var e=a.pb.length,g=!1,h="episodic"==a.pb[0].feature_type,k=a.pb[0].endscreen_autoplay;(a.pb[0].featured||h||k)&&2=k,r=e>=h;if(p&&r||!s&&r)e-=h,k++;else if(s)e-= +k,h++;else break;p=1.45*(h/k)>m}a.ip=p;c=new C(h,k);a.ip?(d=1/c.width,b=b.width*d,d=b/1.45):(d=1/c.height,d*=b.height,b=1.45*d);b=new C(Math.floor(b),Math.floor(d));a.nh=c.width*c.height;g&&(a.nh-=3);a.mh&&Fm(a.mh);g=vn(b);d={Ib:a.mh};a.mh=Em(".videowall-still",g,d);g=vn(b.clone().scale(2));d.Ib=a.mh;Em(".feature-video .videowall-still:first-child",g,d);yd(a.Uf,b.width*c.width,b.height*c.height)}g=0;for(b=a.nh;g=b.B&&b.cancel())}this.N?this.N.call(this.K,this):this.H=!0;this.g||this.a(new Dn)}};Cn.prototype.I=function(a,b){this.A=!1;En(this,a,b)};function En(a,b,c){a.g=!0;a.j=c;a.k=!b;Fn(a)} +function Gn(a){if(a.g){if(!a.H)throw new Hn;a.H=!1}}Cn.prototype.b=function(a){Gn(this);En(this,!0,a)};Cn.prototype.a=function(a){Gn(this);En(this,!1,a)};function In(a,b,c,d){a.o.push([b,c,d]);a.g&&Fn(a);return a}function Jn(a){var b=new Cn;In(a,b.b,b.a,b);return b}function Kn(a){return Ka(a.o,function(a){return ha(a[1])})} +function Fn(a){a.G&&a.g&&Kn(a)&&(l.clearTimeout(a.G),delete a.G);a.i&&(a.i.B--,delete a.i);for(var b=a.j,c=!1,d=!1;a.o.length&&!a.A;){var e=a.o.shift(),g=e[0],h=e[1],e=e[2];if(g=a.k?h:g)try{var k=g.call(e||a.K,b);q(k)&&(a.k=a.k&&(k==b||k instanceof Error),a.j=b=k);b instanceof Cn&&(d=!0,a.A=!0)}catch(m){b=m,a.k=!0,Kn(a)||(c=!0)}}a.j=b;d&&(In(b,v(a.I,a,!0),v(a.I,a,!1)),b.uv=!0);c&&(a.G=l.setTimeout(Ob(b),0))}function Hn(){ra.call(this)}y(Hn,ra);Hn.prototype.message="Deferred has already fired"; +Hn.prototype.name="AlreadyCalledError";function Dn(){ra.call(this)}y(Dn,ra);Dn.prototype.message="Deferred was canceled";Dn.prototype.name="CanceledError";function Ln(a,b){var c=b||{},d=c.document||document,e=Sc("SCRIPT"),g={Xo:e,oc:void 0},h=new Cn(Mn,g),k=null,m=null!=c.timeout?c.timeout:5E3;0a&&Vh(this.a,2*a)};function Wn(){this.g=new sm(["div","html5-fresca-module",["div","html5-fresca-band-slate",["hgroup","html5-fresca-message",["h2","html5-fresca-heading","{{heading}}"],["h3","html5-fresca-subheading","{{subheading}}"],["h4","html5-fresca-long-test","{{long_text}}"]],["span","html5-fresca-countdown","{{countdown}}"]]]);L(this,this.g);this.b=this.g.a["html5-fresca-module"];V(this.b,"html5-stop-propagation");this.i=0;this.a=null}y(Wn,Lf);Wn.prototype.M=function(){return this.b}; +Wn.prototype.update=function(a){if(!this.k||this.a.state!=a.state||this.a.startTime!=a.startTime||this.a.b!=a.b||this.a.a.join()!=a.a.join()){this.a=a;this.b.style.backgroundImage=this.a.b||"none";a=this.a.a;if(!a.length){t:switch(this.a.state){case 6:a="";break t;case 8:case 7:a=J("FRESCA_COMPLETE_MESSAGE");break t;default:a=J("FRESCA_STAND_BY_MESSAGE")}a=[a]}this.g.update({heading:a[0]||"",subheading:a[1]||"",long_text:a[2]||""});this.j()}}; +function Xn(a){var b=Math.floor((new Date).valueOf()/1E3);return b>a?J("FRESCA_STARTING_SOON_MESSAGE"):Xm(a-b)}Wn.prototype.j=function(){var a;a=this.a;a.startTime?(a=a.state,a=6==a||8==a||7==a?!1:!0):a=!1;X(this.b,"html5-fresca-show-countdown",a);a&&(this.g.update({countdown:Xn(this.a.startTime)}),I(this.i),this.i=H(v(this.j,this),1E3))};Wn.prototype.F=function(){I(this.i);this.b=null;Wn.C.F.call(this)};function Yn(a){this.a=[];a&&Zn(this,a)}Yn.prototype.state=-1;function Zn(a,b){var c=b.feed;if(c){var d=c.yt$lifeCycleState;d&&(a.state=Bn[d.$t]||-1);(d=c.yt$when)&&d.start&&(d=new Date(d.start),a.startTime=Math.floor(d.valueOf()/1E3));if(c=c.yt$slate)c.imgUrl&&(a.b="url("+c.imgUrl+")"),(c=c.content)&&c.length&&(c=c.splice(0,3),a.a=Ja(c,function(a){return a.$t}))}};function $n(a){dm.call(this,a)}y($n,dm);f=$n.prototype;f.na="fresca";f.kd="fresca";f.fi=!1;f.Lg=!1;f.Xa=function(){return Ml(this.a.getVideoData(),"fresca_module")};f.create=function(a){$n.C.create.call(this);this.fi=this.Lg=!1;nm(this,["play_pause","seek"]);this.i=new Wn;this.a.app.P.g.appendChild(this.i.M());this.g=a||new Vn(this.a.getVideoData().L);this.g.D("payload",this.Vu,this);this.g.D("error",this.Uu,this);this.D("onStateChange",this.Ao,this)}; +f.destroy=function(){this.Z&&this.unload();this.W("onStateChange",this.Ao,this);Of(this.g,this.i);$n.C.destroy.call(this)};f.load=function(){$n.C.load.call(this);this.Z=!0};f.unload=function(){this.Z=!1;$n.C.unload.call(this)};f.Ao=function(a){this.b&&(this.fi=P(a.state,2),(ao(a,16)||this.fi)&&bo(this,this.b))};f.Uu=function(){this.Lg||(this.b=new Yn,bo(this,this.b))};f.Vu=function(a){this.b=new Yn(a);6!=this.b.state||this.a.getVideoData().O.length?bo(this,this.b):this.a.Uj(this.a.getVideoData().L)}; +function bo(a,b){var c=6>b.state;!c&&a.a.app.b.ca&&(b.b||b.a.length)&&(c=!0);a.fi&&!a.a.J().qc&&(c=!0);if(!a.Lg)switch(b.state){case 6:a.Lg=!0;nm(a,["play_pause","seek"]);fm(a);break;case 8:case 7:c=a.Lg=!0}c&&a.i.update(b);c&&!a.Z?a.load():!c&&a.Z&&a.unload()}function co(a){return Ml(a.getVideoData(),"fresca_module")?new $n(a):null};function eo(a){this.Kg=a||window;this.ee=[]}f=eo.prototype;f.Kg=null;f.ee=null;f.listen=function(a,b,c,d){c=v(c,d||this.Kg);a=K(a,b,c);this.ee.push(a);return a};function fo(a,b,c,d){d=v(d,a.Kg);b=wf(b,c,d);a.ee.push(b);return b}function go(a,b,c){c=v(c,a.Kg);b=zf(b,c,"yt-uix-button-menu-item");a.ee.push(b)}f.Vc=function(a){vf(a);Ua(this.ee,a)};f.removeAll=function(){vf(this.ee);this.ee=[]};function ho(a,b,c){this.a=a;this.i=b||0;this.b=c;this.g=v(this.iu,this)}y(ho,Lf);f=ho.prototype;f.Ba=0;f.F=function(){ho.C.F.call(this);this.stop();delete this.a;delete this.b};f.start=function(a){this.stop();this.Ba=Wh(this.g,q(a)?a:this.i)};f.stop=function(){0!=this.Ba&&l.clearTimeout(this.Ba);this.Ba=0};f.iu=function(){this.Ba=0;this.a&&this.a.call(this.b)};var io={},jo=null;function ko(a){a=ja(a);delete io[a];Ib(io)&&jo&&jo.stop()}function lo(){jo||(jo=new ho(function(){mo()},20));var a=jo;0!=a.Ba||a.start()}function mo(){var a=w();Cb(io,function(b){no(b,a)});Ib(io)||lo()};function oo(){Rh.call(this);this.a=0;this.k=this.startTime=null}y(oo,Rh);f=oo.prototype;f.rk=function(){this.Gd("begin")};f.Ni=function(){this.Gd("end")};f.td=function(){this.Gd("finish")};f.onStop=function(){this.Gd("stop")};f.Gd=function(a){Sh(this,a)};function po(a,b,c,d){oo.call(this);if(!t(a)||!t(b))throw Error("Start and end parameters must be arrays");if(a.length!=b.length)throw Error("Start and end points must be the same length");this.g=a;this.o=b;this.duration=c;this.j=d;this.b=[]}y(po,oo);f=po.prototype;f.gd=0; +function qo(a){if(0==a.a)a.gd=0,a.b=a.g;else if(1==a.a)return;ko(a);var b=w();a.startTime=b;-1==a.a&&(a.startTime-=a.duration*a.gd);a.k=a.startTime+a.duration;a.gd||a.rk();a.Gd("play");-1==a.a&&a.Gd("resume");a.a=1;var c=ja(a);c in io||(io[c]=a);lo();no(a,b)}f.stop=function(a){ko(this);this.a=0;a&&(this.gd=1);ro(this,this.gd);this.onStop();this.Ni()};f.F=function(){0==this.a||this.stop(!1);this.Gd("destroy");po.C.F.call(this)};f.destroy=function(){this.dispose()}; +function no(a,b){a.gd=(b-a.startTime)/(a.k-a.startTime);1<=a.gd&&(a.gd=1);ro(a,a.gd);1==a.gd?(a.a=0,ko(a),a.td(),a.Ni()):1==a.a&&a.pl()}function ro(a,b){ha(a.j)&&(b=a.j(b));a.b=Array(a.g.length);for(var c=0;c=d&&a<=e};return Ro(a[b],k)} +function Zo(a,b){var c=new Qo;c.type="OPTIONAL";c.defaultValue=0;c.a=function(a){if("never"==a)return-1;a=a.split(":");if(3a&&(c=-c);b=60*b+Math.abs(a)});return c*b};return Ro(a[b],c)}function $o(a,b){return null==b?null:a(b)}function ap(a,b,c,d,e){if(null==a||null==a[b])return null;var g=new Qo;g.type=d;g.defaultValue=e;g.b=function(a){return!!a};g.a=oa($o,c);return Ro(a[b],g)} +function Ro(a,b){var c;if("OPTIONAL"==b.type||"REQUIRED"==b.type){if(c=null==a?null:b.a(t(a)&&a.length?a[0]:a),!b.b(c)){if("REQUIRED"==b.type)throw"Required field missing.";c=null==b.defaultValue?null:b.defaultValue}}else c="REPEATED"==b.type?null!=a?bp(t(a)?a:[a],b):null!=b.defaultValue?t(b.defaultValue)?b.defaultValue:[b.defaultValue]:[]:"IDLIST"==b.type?cp(a,b):null;return c} +function cp(a,b){function c(a){return Ia(a.split(/ +/),function(a){return""!=a})}return null!=a?bp(c(a),b):null!=b.defaultValue?t(b.defaultValue)?b.defaultValue:[b.defaultValue]:[]}function bp(a,b){for(var c=[],d=0;d=this.j?0:1-(a-this.k)/this.b;var c=E("countdowntimer-diminishing-pieslice",this.a),d=Ue("svg",this.a);!d&&this.a.querySelectorAll&&(d=this.a.querySelectorAll("svg"),d=d.length?d[0]:null);var d=parseInt(d.getAttribute("width"),10),e=new mq,g=d/2-5;oq(e,d/2,d/2);e.Fa(d/2,5);pq(e,g,g,-90,360*-b);e.Fa(d/2,d/2);e.close();c.setAttribute("d",Oq(e));a>=this.j&&(this.stop(),this.i&&this.i())}};function Rq(a){this.a=a}var Sq=/\s*;\s*/;f=Rq.prototype;f.set=function(a,b,c,d,e,g){if(/[;=\s]/.test(a))throw Error('Invalid cookie name "'+a+'"');if(/[;\r\n]/.test(b))throw Error('Invalid cookie value "'+b+'"');q(c)||(c=-1);e=e?";domain="+e:"";d=d?";path="+d:"";g=g?";secure":"";c=0>c?"":0==c?";expires="+(new Date(1970,1,1)).toUTCString():";expires="+(new Date(w()+1E3*c)).toUTCString();this.a.cookie=a+"="+b+e+d+c+g}; +f.get=function(a,b){for(var c=a+"=",d=(this.a.cookie||"").split(Sq),e=0,g;g=d[e];e++){if(0==g.lastIndexOf(c,0))return g.substr(c.length);if(g==a)return""}return b};f.remove=function(a,b,c){var d=q(this.get(a));this.set(a,"",0,b,c);return d};f.nc=function(){return Tq(this).keys};f.hc=function(){return Tq(this).Sv};f.isEmpty=function(){return!this.a.cookie};f.fb=function(){return this.a.cookie?(this.a.cookie||"").split(Sq).length:0};f.clear=function(){for(var a=Tq(this).keys,b=a.length-1;0<=b;b--)this.remove(a[b])}; +function Tq(a){a=(a.a.cookie||"").split(Sq);for(var b=[],c=[],d,e,g=0;e=a[g];g++)d=e.indexOf("="),-1==d?(b.push(""),c.push(e)):(b.push(e.substring(0,d)),c.push(e.substring(d+1)));return{keys:b,Sv:c}}var Uq=new Rq(document);Uq.b=3950;var Vq=n("yt.prefs.UserPrefs.prefs_")||{};qa("yt.prefs.UserPrefs.prefs_",Vq);function Wq(a){var b=null;"transition"in a.style?b="transition-duration":"webkitTransition"in a.style?b="-webkit-transition-duration":"MozTransition"in a.style?b="-moz-transition-duration":"OTransition"in a.style?b="-o-transition-duration":"msTransition"in a.style&&(b="-ms-transition-duration");a=b?(document.defaultView?document.defaultView.getComputedStyle(a,null):document.parentWindow.getComputedStyle(a,null)).getPropertyValue(b):"0";return 1E3*parseFloat(a)};var Xq=n("yt.pubsub.instance_")||new kh;kh.prototype.subscribe=kh.prototype.D;kh.prototype.unsubscribeByKey=kh.prototype.Wc;kh.prototype.publish=kh.prototype.u;kh.prototype.clear=kh.prototype.clear;qa("yt.pubsub.instance_",Xq);function Yq(a,b,c){var d=Zq();return d?d.subscribe(a,function(){var a=arguments;try{H(function(){b.apply(c||l,a)},0)}catch(d){lf(d)}},c):0} +function $q(){var a=ef("LOGGED_IN_PUBSUB_KEY"),b=Zq();b&&("number"==typeof a?a=[a]:"string"==typeof a&&(a=[parseInt(a,10)]),z(a,function(a){b.unsubscribeByKey(a)}))}function ar(a,b){var c=Zq();return c?c.publish.apply(c,arguments):!1}function Zq(){return n("yt.pubsub.instance_")};function br(a,b,c,d,e,g){var h,k;if(h=c.offsetParent){var m="HTML"==h.tagName||"BODY"==h.tagName;m&&"static"==ld(h,"position")||(k=sd(h),m||(m=(m=td(h))&&ac?-h.scrollLeft:!m||D&&pc("8")||"visible"==ld(h,"overflowX")?h.scrollLeft:h.scrollWidth-h.clientWidth-h.scrollLeft,k=vb(k,new B(m,h.scrollTop))))}h=k||new B;k=Cd(a);if(m=rd(a)){var p=Bb(m),m=Math.max(k.left,p.left),s=Math.min(k.left+k.width,p.left+p.width);if(m<=s){var r=Math.max(k.top,p.top),p=Math.min(k.top+k.height,p.top+p.height);r<=p&&(k.left= +m,k.top=r,k.width=s-m,k.height=p-r)}}m=Fc(a);r=Fc(c);if(m.a!=r.a){var s=m.a.body,r=r.a.parentWindow||r.a.defaultView,p=new B(0,0),x=Hc(s)?Hc(s).parentWindow||Hc(s).defaultView:window,M=s;do{var ta=x==r?sd(M):wd(M);p.x+=ta.x;p.y+=ta.y}while(x&&x!=r&&(M=x.frameElement)&&(x=x.parent));s=vb(p,sd(s));D&&!gd(m)&&(s=vb(s,hd(m)));k.left+=s.x;k.top+=s.y}a=(b&4&&td(a)?b^2:b)&-5;b=new B(a&2?k.left+k.width:k.left,a&1?k.top+k.height:k.top);b=vb(b,h);e&&(b.x+=(a&2?-1:1)*e.x,b.y+=(a&1?-1:1)*e.y);if(e=rd(c))e.top-= +h.y,e.right-=h.x,e.bottom-=h.y,e.left-=h.x;cr(b,c,d,g,e,65,void 0)} +function cr(a,b,c,d,e,g,h){a=a.clone();var k=(c&4&&td(b)?c^2:c)&-5;c=Ad(b);h=h?h.clone():c.clone();if(d||0!=k)k&2?a.x-=h.width+(d?d.right:0):d&&(a.x+=d.left),k&1?a.y-=h.height+(d?d.bottom:0):d&&(a.y+=d.top);if(g&&(e?(d=a,k=0,65==(g&65)&&(d.x=e.right)&&(g&=-2),132==(g&132)&&(d.y=e.bottom)&&(g&=-5),d.xe.right&&g&16&&(h.width=Math.max(h.width-(d.x+h.width-e.right),0),k|=4),d.x+h.width>e.right&&g&1&&(d.x=Math.max(e.right- +h.width,e.left),k|=1),g&2&&(k=k|(d.xe.right?32:0)),d.y=e.top&&d.y+h.height>e.bottom&&g&32&&(h.height=Math.max(h.height-(d.y+h.height-e.bottom),0),k|=8),d.y+h.height>e.bottom&&g&4&&(d.y=Math.max(e.bottom-h.height,e.top),k|=2),g&8&&(k=k|(d.ye.bottom?128:0)),e=k):e=256,e&496))return;md(b,a);xb(c,h)||(e=gd(Fc(Hc(b))), +!D||e&&pc("8")?(b=b.style,ac?b.MozBoxSizing="border-box":bc?b.WebkitBoxSizing="border-box":b.boxSizing="border-box",b.width=Math.max(h.width,0)+"px",b.height=Math.max(h.height,0)+"px"):(a=b.style,e?(e=Gd(b),b=Hd(b),a.pixelWidth=h.width-b.left-e.left-e.right-b.right,a.pixelHeight=h.height-b.top-e.top-e.bottom-b.bottom):(a.pixelWidth=h.width,a.pixelHeight=h.height)))};var dr={},er="ontouchstart"in document;function fr(a,b,c){var d;switch(a){case "mouseover":case "mouseout":d=3;break;case "mouseenter":case "mouseleave":d=9}return fd(c,function(a){return Cc(a,b)},!0,d)} +function gr(a){var b="mouseover"==a.type&&"mouseenter"in dr||"mouseout"==a.type&&"mouseleave"in dr,c=a.type in dr||b;if("HTML"!=a.target.tagName&&c){if(b){var b="mouseover"==a.type?"mouseenter":"mouseleave",c=dr[b],d;for(d in c.Qb){var e=fr(b,d,a.target);e&&!fd(a.relatedTarget,function(a){return a==e},!0)&&c.u(d,e,b,a)}}if(b=dr[a.type])for(d in b.Qb)(e=fr(a.type,d,a.target))&&b.u(d,e,a.type,a)}}K(document,"blur",gr,!0);K(document,"change",gr,!0);K(document,"click",gr);K(document,"focus",gr,!0); +K(document,"mouseover",gr);K(document,"mouseout",gr);K(document,"mousedown",gr);K(document,"keydown",gr);K(document,"keyup",gr);K(document,"keypress",gr);K(document,"cut",gr);K(document,"paste",gr);er&&(K(document,"touchstart",gr),K(document,"touchend",gr),K(document,"touchcancel",gr));var hr=window.yt&&window.yt.uix&&window.yt.uix.widgets_||{};qa("yt.uix.widgets_",hr);function ir(a){a=a.getInstance();var b=$(a);b in hr||!a.qq()||(a.register(),hr[b]=a)};function jr(){this.a={}}jr.prototype.g=!!eval("/*@cc_on!@*/false");jr.prototype.qq=function(){return!0};function kr(a,b,c){var d=$(a,void 0),e=v(c,a);b in dr||(dr[b]=new kh);dr[b].D(d,e);a.a[c]=e}jr.prototype.b=function(a,b,c){var d=G(a,b);if(d&&(d=n(d))){var e=ab(arguments,2);$a(e,0,0,a);d.apply(null,e)}};function lr(a,b){Oe(a,"tooltip-text",b)}jr.prototype.removeData=function(a,b){a.dataset?delete a.dataset[Pe(b)]:a.removeAttribute("data-"+b)}; +function $(a,b){return"yt-uix"+(a.dj?"-"+a.dj:"")+(b?"-"+b:"")};function mr(){this.a={}}y(mr,jr);ca(mr);f=mr.prototype;f.dj="button";f.Ef=null;f.register=function(){kr(this,"click",this.gy);kr(this,"keydown",this.ey);kr(this,"keypress",this.fy)};f.gy=function(a){a&&!a.disabled&&(nr(this,a),this.click(a))}; +f.ey=function(a,b,c){if(!(c.altKey||c.ctrlKey||c.shiftKey)&&(b=or(this,a))){var d=function(a){var b="";a.tagName&&(b=a.tagName.toLowerCase());return"ul"==b||"table"==b};if(d=d(b)?b:$c(b,d)){var d=d.tagName.toLowerCase(),e;"ul"==d?e=this.ky:"table"==d&&(e=this.jy);e&&pr(this,a,b,c,v(e,this))}}}; +function pr(a,b,c,d,e){var g=$m(c),h=9==d.keyCode;h||32==d.keyCode||13==d.keyCode?(d=qr(a,c))?(b=Wc(d),"a"==b.tagName.toLowerCase()?window.location=b.href:Bf(b,"click")):h&&rr(a,b):g?27==d.keyCode?(qr(a,c),rr(a,b)):e(b,c,d):(a=Cc(b,$(a,"reverse"))?38:40,d.keyCode==a&&(Bf(b,"click"),d.preventDefault()))}f.fy=function(a,b,c){c.altKey||c.ctrlKey||c.shiftKey||(a=or(this,a),$m(a)&&c.preventDefault())};function qr(a,b){var c=$(a,"menu-item-highlight"),d=E(c,b);d&&Ac(d,c);return d} +function sr(a,b,c){zc(c,$(a,"menu-item-highlight"));b.setAttribute("aria-activedescendant",c.getAttribute("id"))}f.jy=function(a,b,c){var d=qr(this,b);b=Ue("table",b);var e=Ue("tr",b),e=Kc("td",null,e).length;b=Kc("td",null,b);d=tr(d,b,e,c);-1!=d&&(sr(this,a,b[d]),c.preventDefault())};f.ky=function(a,b,c){if(40==c.keyCode||38==c.keyCode){var d=qr(this,b);b=Kc("li",null,b);d=tr(d,b,1,c);sr(this,a,b[d]);c.preventDefault()}}; +function tr(a,b,c,d){var e=b.length;a=Ha(b,a);if(-1==a)if(38==d.keyCode)a=e-c;else{if(37==d.keyCode||38==d.keyCode||40==d.keyCode)a=0}else 39==d.keyCode?(a%c==c-1&&(a-=c),a+=1):37==d.keyCode?(0==a%c&&(a+=c),a-=1):38==d.keyCode?(a=e-c&&(a-=e),a+=c);return a}function ur(a,b){var c=b.iframeMask;c||(c=document.createElement("iframe"),c.src='javascript:""',c.className=$(a,"menu-mask"),b.iframeMask=c);return c} +function vr(a,b,c,d){var e=ed(b,$(a,"group")),g=!!G(b,"button-menu-ignore-group"),e=e&&!g?e:b,g=5,h=4,k=Cd(b);if(Cc(b,$(a,"reverse"))){g=4;h=5;k=k.top+"px";try{c.style.maxHeight=k}catch(m){}}Cc(b,"flip")&&(Cc(b,$(a,"reverse"))?(g=6,h=7):(g=7,h=6));var p;G(b,"button-has-sibling-menu")?p=qd(e):G(b,"button-menu-root-container")&&(p=wr(b));D&&!pc("8")&&(p=null);var s;p&&(s=Cd(p),s=new wb(-s.top,s.left,s.top,-s.left));p=new B(0,1);Cc(b,$(a,"center-menu"))&&(p.x-=Math.round((Ad(c).width-Ad(b).width)/2)); +d&&(p.y+=Oc(document).y);if(a=ur(a,b))b=Ad(c),a.style.width=b.width+"px",a.style.height=b.height+"px",br(e,g,a,h,p,s),d&&dn(a,"position","fixed");br(e,g,c,h,p,s)}function wr(a){if(G(a,"button-menu-root-container")){var b=G(a,"button-menu-root-container");return ed(a,b)}return document.body} +f.Bp=function(a){if(a){var b=or(this,a);if(b){a.setAttribute("aria-pressed","true");a.setAttribute("aria-expanded","true");b.originalParentNode=b.parentNode;b.activeButtonNode=a;b.parentNode.removeChild(b);var c;c=G(a,"button-has-sibling-menu")?a.parentNode:wr(a);c.appendChild(b);b.style.minWidth=a.offsetWidth-2+"px";var d=ur(this,a);d&&c.appendChild(d);c=!!G(a,"button-menu-fixed");vr(this,a,b,c);an(b);this.b(a,"button-menu-action",!0);zc(a,$(this,"active"));c=v(this.gw,this,a);b=K(document,"click", +c);c=K(document,"contextmenu",c);Oe(a,"button-listener",b);Oe(a,"button-context-menu-listener",c);this.Ef=a}}}; +function rr(a,b){if(b){var c=or(a,b);if(c){a.Ef=null;b.setAttribute("aria-pressed","false");b.setAttribute("aria-expanded","false");b.removeAttribute("aria-activedescendant");bn(c);a.b(b,"button-menu-action",!1);var d=ur(a,b);H(function(){d&&d.parentNode&&d.parentNode.removeChild(d);c.originalParentNode&&(c.parentNode.removeChild(c),c.originalParentNode.appendChild(c),c.originalParentNode=null,c.activeButtonNode=null)},1)}var e=ed(b,$(a,"group"));Ac(b,$(a,"active"));e&&Ac(e,$(a,"group-active"));if(e= +G(b,"button-listener"))vf(e),a.removeData(b,"button-listener");if(e=G(b,"button-context-menu-listener"))vf(e),a.removeData(b,"button-context-menu-listener")}}function xr(a,b){var c=or(a,b);c&&vr(a,b,c)}function yr(a,b){return or(a,b)} +f.gw=function(a,b){var c;c=b||window.event;c=c.target||c.srcElement;3==c.nodeType&&(c=c.parentNode);var d=ed(c,$(this));if(d){var d=or(this,d),e=or(this,a);if(d==e)return}if(!ed(c,$(this,"menu"))||Cc(c,$(this,"menu-item"))||Cc(c,$(this,"menu-close")))if(rr(this,a),(d=ed(c,$(this,"menu")))&&G(a,"button-menu-indicate-selected")){if(e=E($(this,"content"),a)){var g;wc&&"innerText"in c?g=c.innerText.replace(/(\r\n|\r|\n)/g,"\n"):(g=[],dd(c,g,!0),g=g.join(""));g=g.replace(/ \xAD /g," ").replace(/\xAD/g, +"");g=g.replace(/\u200B/g,"");wc||(g=g.replace(/ +/g," "));" "!=g&&(g=g.replace(/^\s*/,""));Zc(e,g)}e=$(this,"menu-item-selected");(d=E(e,d))&&Ac(d,e);zc(c.parentNode,e)}};function or(a,b){if(!b.widgetMenu){var c=G(b,"button-menu-id"),c=c&&Ic(c),d=$(a,"menu");c?(zc(c,d),zc(c,$(a,"menu-external"))):c=E(d,b);b.widgetMenu=c}return b.widgetMenu} +function nr(a,b){if(G(b,"button-toggle")){var c=ed(b,$(a,"group"));if(c&&G(c,"button-toggle-group")){var d=G(c,"button-toggle-group"),c=Jc($(a),c),e=$(a,"toggled"),g=Cc(b,e);z(c,function(a){a!=b||"optional"==d&&g?Ac(a,e):zc(b,e)})}else Ec(b,$(a,"toggled"))}}f.click=function(a){if(or(this,a)){var b=or(this,a),c=ed(b.activeButtonNode||b.parentNode,$(this));c&&c!=a?(rr(this,c),H(v(this.Bp,this,a),1)):$m(b)?rr(this,a):this.Bp(a);a.focus()}this.b(a,"button-action")};function zr(a,b,c){this.i=a;this.g=b;this.b=c;this.a=v(this.ju,this)}y(zr,Lf);f=zr.prototype;f.bj=!1;f.If=null;function Ar(a){a.If?a.bj=!0:Br(a)}f.stop=function(){this.If&&(l.clearTimeout(this.If),this.If=null,this.bj=!1)};f.F=function(){zr.C.F.call(this);this.stop()};f.ju=function(){this.If=null;this.bj&&(this.bj=!1,Br(this))};function Br(a){a.If=Wh(a.a,a.g);a.i.call(a.b)};function Cr(a,b,c){b||(b={});var d=c||window;c="undefined"!=typeof a.href?a.href:String(a);a=b.target||a.target;var e=[],g;for(g in b)switch(g){case "width":case "height":case "top":case "left":e.push(g+"="+b[g]);break;case "target":case "noreferrer":break;default:e.push(g+"="+(b[g]?1:0))}g=e.join(",");if(b.noreferrer){if(b=d.open("",a,g))D&&-1!=c.indexOf(";")&&(c="'"+c.replace(/'/g,"%27")+"'"),b.opener=null,c=va(c),b.document.write(''),b.document.close()}else b= +d.open(c,a,g);return b}function Dr(a,b){var c;c=b||{};c.target=c.target||a.target||"YouTube";c.width=c.width||600;c.height=c.height||600;(c=Cr(a,c))?(c.opener||(c.opener=window),c.focus()):c=null;return!c};function Er(a,b){Fr().tick[a]=b||w()}function Gr(a){var b=Fr().tick;return a in b}function Hr(a){var b="https:"==window.location.protocol?"https://gg.google.com/csi":"http://csi.gstatic.com/csi",c="",d;for(d in a)c+="&"+d+"="+a[d];Kf(b+"?"+c.substring(1))}function Ir(){return Fr().info}function Fr(){return n("ytcsi.data_")||Jr()}function Jr(){var a={tick:{},span:{},info:{}};qa("ytcsi.data_",a);return a};function Kr(a){if(!a)return!1;a=a.replace(/https?:\/\//g,"");var b=a.split("/",1);if(!b||1>b.length||!b[0])return!1;b=b[0].toLowerCase().split(".").reverse();return 2>b.length?!1:("com"==b[0]&&"youtube"==b[1]||"be"==b[0]&&"youtu"==b[1])&&-1==a.indexOf("/redirect?")} +function Lr(a,b){if("new"==a.target)return-1;var c=mp(a);if(!c)return-1;var c=c.replace(/https?:\/\//g,""),d;(d=!Kr(c))||(d=ee(c)||"",d=d.split("/"),d="/"+(1c.length)return-1;(c=Ui(c[1]))&&c.t?(d=c.t,c=0,-1!=d.indexOf("h")&&(d=d.split("h"),c=3600*d[0],d=d[1]),-1!=d.indexOf("m")&&(d=d.split("m"),c=60*d[0]+c,d=d[1]),-1!=d.indexOf("s")?(d=d.split("s"),c=1*d[0]+c):c=1*d+c):c=-1;return c} +;function Mr(a,b){this.b=a;this.a=b}function Io(a,b,c){b&&b.i&&Ho(b)&&a.log_(Co(a,b,3),c)}function Co(a,b,c){var d={};d["iv-event"]=c;d["a-id"]=b.id;d["a-type"]=Nr(b);if(c=Ho(b))d.link=escape(mp(c)),c.a&&(d["l-class"]=c.a);d.ps=a.a.Ja;if(b.A){var e=new ve(b.A);z(e.nc(),function(a){d[a]=e.get(a)})}return d} +function Nr(a){switch(a.type){case "text":switch(a.style){case "popup":return 1;case "speech":return 2;case "anchored":return 8;case "label":return 9;case "title":return 4;default:return 0}case "highlight":return 3;case "image":switch(a.style){case "video":return 11;case "channel":return 10;default:return 0}default:return 0}}Mr.prototype.log_=function(a,b){this.b.u("command_log","iv",a,b)};function Or(a,b){this.start=a=h.width||0>=h.height)){var k;if(k=(a=(a=a.a?a.a.a.length?a.a.a[0]:null:null)&&a.a?a.a:null)&&a.length?a[0]:null){var m;c=c?Cp(c,b):null;a=xp(k,new Ab(k.B,k.H,k.k,k.i),b);c?(a.top+=c.top,a.left+=c.left):(a.top+=b.top,a.left+=b.left);m=new B(a.left,a.top);c=h.clone();a=new Ab(m.x,m.y,1,1);var g=Math.max(c.left+c.width,a.left+a.width),p=Math.max(c.top+c.height,a.top+ +a.height);c.left=Math.min(c.left,a.left);c.top=Math.min(c.top,a.top);c.width=g-c.left;c.height=p-c.top;c=es(c,d.b);a=Qr(this,c.width,c.height);var g=ds(d,c.width,c.height,this.Tc),h=new Ab(h.left-c.left,h.top-c.top,h.width,h.height),s=new B(m.x-c.left,m.y-c.top);this.a=17*zp(b,k.g,k.a?k.a:"xy");b=d.g;k=e?e.a/2:0;m=hs(h,s);var p=this.b(h,b,s,m),r=s.x,s=s.y,x=h.width,M=h.height,ta=h.left,h=h.top,Y=new mq;oq(Y,ta+b+k,h+k);"t"==m&&(Y.Fa(p.start,h+k),Y.Fa(r,s),Y.Fa(p.end,h+k));Y.Fa(ta+x-b-k,h+k);pq(Y, +b,b,-90,90);"r"==m&&(Y.Fa(ta+x-k,p.start),Y.Fa(r,s),Y.Fa(ta+x-k,p.end));Y.Fa(ta+x-k,h+M-b-k);pq(Y,b,b,0,90);"b"==m&&(Y.Fa(p.end,h+M-k),Y.Fa(r,s),Y.Fa(p.start,h+M-k));Y.Fa(ta+b+k,h+M-k);pq(Y,b,b,90,90);"l"==m&&(Y.Fa(ta+k,p.end),Y.Fa(r,s),Y.Fa(ta+k,p.start));Y.Fa(ta+k,h+b+k);pq(Y,b,b,180,90);Y.close();Nq(a,Y,e,g);if(e=this.M())V(e,"annotation-shape"),V(e,"annotation-speech-shape"),md(e,c.left,c.top),yd(e,c.width,c.height),fs(a,e,d.b)}}}}; +function hs(a,b){var c=a.top-b.y,d=b.x-a.left-a.width,e=b.y-a.top-a.height,g=a.left-b.x,h=Math.max(c,d,e,g);if(0>h)return"i";switch(h){case c:return"t";case d:return"r";case e:return"b";case g:return"l"}return"i"}gs.prototype.b=function(a,b,c,d){function e(a,c,d){h=Math.min(Math.max(d-2*b,0),g);k=tb(a-h/2,c+b,c+d-h-b)}var g=this.a,h=0,k=0;"t"==d||"b"==d?e(c.x,a.left,a.width):"l"!=d&&"r"!=d||e(c.y,a.top,a.height);return new Or(k,k+h)};function is(){}y(is,Pr);is.prototype.ae=function(a,b,c){var d=bq(a);d&&(b=Cp(d,b,c),0>=b.width||0>=b.height||(a=a.b,c=es(b,a.b),d=Qr(this,c.width,c.height),as(d,new Ab(0,0,b.width,b.height),a.g,new sq(!a.i&&this.Tc?1:a.i,a.j),new rq("#000",0)),b=this.M(),V(b,"annotation-shape"),Dd(b,this.Tc?Math.max(a.a,0.9):a.a),md(b,c.left,c.top),yd(b,c.width,c.height)))};function js(a,b,c){this.a=a||0;this.g=b||0;this.b=c||!1}y(js,Pr);function ks(a,b){var c=a.width,d=a.height,e=0,g=0;0b?(d=a.width/b,g=(a.height-d)/2):(c=a.height*b,e=(a.width-c)/2));return new Ab(e,g,c,d)} +js.prototype.ae=function(a,b,c){var d=Cp(bq(a),b,c);if(!(0>=d.width||0>=d.height)){var e=ks(d,this.g);e.left+=d.left;e.top+=d.top;b=a.b;c=es(e,b.b);var g=Qr(this,c.width,c.height),h=new rq("#000",0),e=ks(e,this.a);a=a.j?a.j.a?a.j.a:a.j.L?kn(a.j.L,"hqdefault.jpg"):"":"";e=Cq(g,"image",{x:e.left,y:e.top,width:e.width,height:e.height,"image-rendering":"optimizeQuality",preserveAspectRatio:"none"});e.setAttributeNS("http://www.w3.org/1999/xlink","href",a);a=new Jq(e,g);g.b.M().appendChild(a.M());if(e= +this.M()){var k=this.Tc?Math.max(b.a,0.9):b.a;Dd(e,k);if(this.b&&0=e.width||0>=e.height)){b=a.b;c=es(e,b.b);var d=Qr(this,c.width,c.height),g=b.G;a=this.Tc&&Yp(a);a=(g+=a?1:0)?new sq(g,a?b.k:b.o):null;g=new Ab(0,0,e.width,e.height);e=ds(b,e.width,e.height,this.Tc);as(d,g,b.g,a,e);if(a=this.M())V(a,"annotation-shape"),V(a,"annotation-popup-shape"),md(a,c.left,c.top),yd(a,c.width,c.height),fs(d,a,b.b)}}};function ms(){}y(ms,gs);ms.prototype.b=function(a,b,c,d){function e(a,c,d){h=Math.min(Math.max(d-2*b,0),g);k=a<=c+d/2?Math.max(c+d/4-h/2,c+b):Math.min(c+3*d/4-h/2,c+d-h-b)}var g=this.a,h=0,k=0;"t"==d||"b"==d?e(c.x,a.left,a.width):"l"!=d&&"r"!=d||e(c.y,a.top,a.height);return new Or(k,k+h)};function ns(a,b){this.b=Ic(a);this.a=on(this.b,b)} +function os(a,b){var c=pn(a.a,b,void 0),c=c.replace(/^[\s\xa0]+/,""),d;d=String(c.substr(0,3)).toLowerCase();(d=0==(""+c+"");var e=c,g=document,c=g.createElement("div");D?(c.innerHTML="
    "+e,c.removeChild(c.firstChild)):c.innerHTML=e;if(1==c.childNodes.length)c=c.removeChild(c.firstChild);else{for(e=g.createDocumentFragment();c.firstChild;)e.appendChild(c.firstChild);c=e}d&&(c=Wc(Wc(c)));return c};function ps(a,b,c,d,e){this.a=a;this.j=b;this.g=c;this.o=d;this.k=e;this.i=new eo(this)}f=ps.prototype;f.la=null;f.Yc=null;f.cc=null;f.Ea=null;f.$k=null;function qs(a,b){var c=v(function(a,c,g){c=g?rs(this,c,v(g,this)):rs(this,c);this.i.listen(b,a,c)},a);c("mouseover","e",a.ax);c("mouseout","d",a.$w);c("click","b");c("mousedown","a");c("touchend","b")} +function ss(a){if(a.a.G){var b;if(Ka(a.a.g,function(a){return"close"==a.type},void 0))b=a.la;else{b=os(new ns("annotation-close-button-template",[]),{});if(!(b instanceof Element))return;a.cc=b;Oe(a.cc,"annotation_id",a.a.id);a.la.appendChild(a.cc);b=a.cc}var c=function(a){a.stopPropagation()};a.i.listen(b,"click",rs(a,"c",c));a.i.listen(b,"touchend",rs(a,"c",c))}} +function rs(a,b,c){return v(function(a){if(this.k)c&&c(a);else if(a.target instanceof Element){bn(a.target);var e=document.elementFromPoint(a.x,a.y);an(a.target);Zl(e,"annotation")&&Bf(e,a.type)}this.o.u(b,this.a)},a)}f.ax=function(){this.cc&&an(this.cc);this.Yc&&Dd(this.Yc,1);var a=ts(this);this.Ea&&(this.Ea.Tc=!0,Dd(this.la,us(this)?1:0),a&&this.Ea.ae(this.a,a,vs(this)))}; +f.$w=function(){this.cc&&bn(this.cc);this.Yc&&Dd(this.Yc,0);var a=ts(this);this.Ea&&(this.Ea.Tc=!1,Dd(this.la,us(this)?1:0),a&&this.Ea.ae(this.a,a,vs(this)))};function vs(a){return a.$k?bq(a.$k):null} +function jq(a){if(a.la||a.Ea){var b=bq(a.a);if(b){var c=ts(a),d=vs(a);if(a.la&&c){b=Cp(b,c,d);yd(a.la,b.width,b.height);md(a.la,b.left,b.top);var e=a.g.ug;e&&(e=new wb(360*ws(a).top*dq(a.a,e)/100,640*ws(a).right*cq(a.a,e)/100,360*ws(a).bottom*dq(a.a,e)/100,640*ws(a).left*cq(a.a,e)/100),a.Yc&&(e.right+=1.5*c.height/100),a.la.style.padding=e.top+"px "+e.right+"px "+e.bottom+"px "+e.left+"px");"label"==a.a.style&&a.b&&(a.b.style.padding=a.la.style.padding);if(a.Yc){e=4.2*c.height/100;e=new C(e,e);yd(a.Yc, +e);if("highlight"==a.a.type||"label"==a.a.style)var g=1.5*c.height/100,e=new B(b.width-e.width-g,b.height-e.height-g);else e=new B(b.width-e.width-3*c.height/100,(b.height-e.height)/2);md(a.Yc,e)}if(a.cc){yd(a.cc,new C(18,18));var e=a.cc,g=Ad(a.cc),h=9<=c.left+c.width-(b.left+b.width),k=9<=b.top-c.top;md(e,h&&k?new B(b.width-9,-9):h?new B(b.width-9,b.height>27+g.height?9:b.height-9):k?new B(b.width>27+g.width?b.width-9-g.width:-9,-9):b.width/c.width>b.height/c.height?new B(b.width>27+g.width?b.width- +9-g.width:-9,b.height-9):new B(-9,b.height>27+g.height?9:b.height-9))}}a.Ea&&c&&a.Ea.ae(a.a,c,d);if(a.la){c=a.la;d=a.a.b;c.style.color="highlightText"==a.a.style?d.H:d.k;c.style.fontSize=360*d.I*dq(a.a,a.g.ug)/100+"px";c.style.textAlign=d.textAlign?d.textAlign:"title"==a.a.style||"highlightText"==a.a.style?"center":"left";d.A&&(c.style.fontWeight=d.A);a=a.la;c=a.style.overflow;(d=E("annotation-link-icon",a))&&bn(d);a.style.overflow="scroll";for(b=100;0e)break;e--;a.style.fontSize=e+"px"}a.style.overflow=c;d&&an(d)}}}}function ws(a){var b=a.a.b;return b.padding?b.padding:new wb("speech"==a.a.style?1.6:0.8,"speech"==a.a.style?1.6:0.8,"speech"==a.a.style?1.6:0.8,"speech"==a.a.style?1.6:0.8)} +f.show=function(){var a=this.a.b,a=(a&&0==a.a||"title"==this.a.style||"highlightText"==this.a.style||"pause"==this.a.type?!1:!0)&&!this.Ea,b=!this.la,c="widget"==this.a.type;if(a){var d=ts(this);if(d){var e=null;"highlight"==this.a.type||"label"==this.a.style?e=new is:"popup"==this.a.style?e=new ls:"anchored"==this.a.style?e=new gs:"speech"==this.a.style?e=new ms:"image"==this.a.type&&("video"==this.a.style?e=new js(4/3,16/9,!0):"channel"==this.a.style&&(e=new js));e&&(e.ae(this.a,d,vs(this)),this.Ea= +e,d=e.M())&&(bn(d),V(d,"annotation-type-"+this.a.type.toLowerCase()),this.j(d))}}if(b){d=["annotation","hid"];"highlightText"!=this.a.style||d.push("annotation-no-mouse");d.push("annotation-type-"+this.a.type.toLowerCase());this.la=Pc("div",d);this.a.o&&("label"==this.a.style?(this.b=Pc("div",["label-text"]),this.b.style.backgroundColor=this.a.b.j,Zc(this.b,this.a.o),this.la.appendChild(this.b)):Zc(this.la,this.a.o));Oe(this.la,"annotation_id",this.a.id);this.j(this.la);qs(this,this.la);if(Yp(this.a)&& +"image"!=this.a.type&&Xp(this.a)){if(d=Ho(this.a))this.la.title=mp(d);this.Yc=Pc("span","annotation-link-icon");this.la.appendChild(this.Yc)}ss(this);Yp(this.a)||(this.la.style.cursor="default")}c&&("subscribe"==this.a.style?E("yt-uix-subscription-button",this.la)||(this.la.innerHTML=this.a.k):this.a.k&&(this.la.innerHTML=this.a.k));if(a||b){t:{a=this.a.a.a;if(a.length&&(a=Jp(a[0]))){a=a.o;break t}a=0}this.la&&(this.la.style.zIndex=a);this.Ea&&this.Ea.M()&&(this.Ea.M().style.zIndex=a)}an(this.la); +Dd(this.la,us(this)?1:0);jq(this);this.Ea&&an(this.Ea.M())};f.hide=function(){bn(this.la);this.Ea&&bn(this.Ea.M())};function us(a){return"label"!=a.a.style||a.Ea.Tc}function ts(a){var b=a.g.ug;return b?"player_relative"==a.a.H?(a=a.g.Sc)?new Ab(-b.left,-b.top,a.width,a.height):null:new Ab(0,0,b.width,b.height):null};function xs(a){dm.call(this,a);this.b={};this.i={};this.j=new Mr(this.G,this.a.J());var b=Q(a),c=E("html5-annotations-button",b);this.g=this.k=null;a.J().La?(F(c),a=this.a.app.P.A,this.g=new Jo(a,[a.getMsg("YTP_ON"),a.getMsg("YTP_OFF")]),K(this.g,"change",v(this.wr,this)),this.k={label:this.a.app.P.A.getMsg("YTP_ANNOTATIONS_TITLE"),element:this.g,priority:2}):K(c,"click",v(this.ur,this));this.D("onHideControls",this.tr,this);this.D("onShowControls",this.yr,this);this.D("onStateChange",this.xr,this); +this.D("e",this.Ar,this);this.D("d",this.zr,this);this.D("a",this.rr,this);this.D("b",this.qr,this);this.D("c",this.sr,this);b=E("video-annotations",b);b=E("countdowntimer",b);this.A=Pc("DIV",["video-annotations","html5-stop-propagation"]);gm(this,this.A);gm(this,b);this.B=new Qq(b,v(this.Aj,this))}y(xs,dm);f=xs.prototype;f.na="iv-module";f.Jl=!1;f.Dp=!0;f.Z=!1;f.Cd=0;function ys(a){return E("video-annotations",Q(a))&&zs(a)?new xs(a):null} +function zs(a){return"leanback"==a.J().Y?!1:Ml(a.getVideoData(),"iv3_module")}f.Xa=function(){return zs(this.a)};f.create=function(){xs.C.create.call(this);this.k&&this.u("module_menu_add",this.k);1==(this.a.J().ca||this.a.getVideoData().ca)?this.load():As(this,"tooltip-default")};f.destroy=function(){this.unload();this.k&&this.u("module_menu_remove",this.k);xs.C.destroy.call(this)};f.ur=function(){this.Z||this.Cd?this.unload():this.load()}; +f.wr=function(){var a=this.Z||this.Cd,b=0==this.g.getSelected();a&&!b?this.unload():!a&&b&&this.load()};f.xr=function(a){this.Dp=P(a.state,8);0>ao(a,4)&&this.B.stop()}; +f.load=function(){xs.C.load.call(this);As(this,"tooltip-alt");var a={format:"XML",method:"GET",td:v(this.Yo,this,null)},b=this.a.getVideoData();b.Pk&&(this.Cd++,ej(b.Pk,a));b.Ok&&(this.Cd++,ej(b.Ok,a));b.Nk&&(this.a.J().Gc||this.a.J().Lc)&&(a={format:"XML",method:"GET",td:v(this.Yo,this,v(this.sv,this,b.Ia))},this.Cd++,ej(b.Nk,a));this.g&&Mo(this.g,0)}; +f.unload=function(){this.g&&Mo(this.g,1);this.j.log_({"iv-event":1});this.Aj();em(this);Cb(this.b,function(a){a.destroy()});Cb(this.i,function(a){a.destroy()});this.Cd=0;this.Z=!1;this.b={};this.i={};As(this,"tooltip-default");xs.C.unload.call(this)};function As(a,b){var c=E("html5-annotations-button",Q(a.a));if(c){var d=G(c,b)||"";Oe(c,"tooltip",d);c.setAttribute("aria-label",d)}} +function Bs(a,b){for(var c={},d=0;de.length)e=null;else var g=e.length-1,e=0>=e[0].b&&0>=e[g].b?null:{start:e[0].b,end:e[g].b};else e=null;else e=null;if(g=e)e=1E3*g.start,g=1E3*g.end,0==e&&(e++,g++),e==g&&g++,gw()-this.cj)){var b=parseInt(G(a,"tooltip-hide-timer"),10);b&&(this.removeData(a,"tooltip-hide-timer"),I(b));var b=v(function(){Vs(this,a);this.removeData(a,"tooltip-show-timer")},this),c=parseInt(G(a,"tooltip-show-delay"),10)||0,b=H(b,c);Oe(a,"tooltip-show-timer",b.toString());a.title&&(lr(a,Ws(a)),a.title="")}}; +f.Tl=function(a){var b=parseInt(G(a,"tooltip-show-timer"),10);b&&(I(b),this.removeData(a,"tooltip-show-timer"));b=v(function(){Xs(this,a);this.removeData(a,"tooltip-hide-timer")},this);b=H(b,50);Oe(a,"tooltip-hide-timer",b.toString());if(b=G(a,"tooltip-text"))a.title=b};f.Yx=function(a,b){this.cj=0;var c=fr(b,$(this),null[0].target);this.aq(c)};f.hq=function(a,b){this.cj=w();var c=fr(b,$(this),null[0].target);this.Tl(c)};function Ys(a,b){lr(a,b);var c=G(a,"content-id");if(c=Ic(c))c.innerHTML=b} +function Ws(a){return G(a,"tooltip-text")||a.title} +function Vs(a,b){if(b){var c=Ws(b);if(c){var d=Ic(Zs(a,b));if(!d){d=document.createElement("div");d.id=Zs(a,b);d.className=$(a,"tip");var e=document.createElement("div");e.className=$(a,"tip-body");var g=document.createElement("div");g.className=$(a,"tip-arrow");var h=document.createElement("div");h.className=$(a,"tip-content");var k=$s(a,b),m=Zs(a,b,"content");h.id=m;Oe(b,"content-id",m);e.appendChild(h);k&&d.appendChild(k);d.appendChild(e);d.appendChild(g);(Df()||document.body).appendChild(d);Ys(b, +c);(c=parseInt(G(b,"tooltip-max-width"),10))&&e.offsetWidth>c&&(e.style.width=c+"px",zc(h,$(a,"normal-wrap")));h=Cc(b,$(a,"reverse"));at(a,b,d,e,k,h)||at(a,b,d,e,k,!h);var p=$(a,"tip-visible");H(function(){zc(d,p)},0)}}}} +function at(a,b,c,d,e,g){Dc(c,$(a,"tip-reverse"),g);var h=0;g&&(h=1);a=Ad(b);g=new B((a.width-10)/2,g?a.height:0);var k=sd(b);cr(new B(k.x+g.x,k.y+g.y),c,h);h=Nc(window);k=vd(c);c=Ad(d);var m=Math.floor(c.width/2);e&&(e.style.left="3px",e.style.height=c.height+"px",e.style.width=c.width+"px");e=!!(h.heightb;b++){var c=2*Math.PI/8*(a+b),d=11*this.b*Math.cos(c),c=11*this.b*Math.sin(c),e=(b+1)/9;this.X.beginPath();this.X.arc(d,c,4*this.b,0,2*Math.PI,!1);this.X.fillStyle="rgba(189, 189, 189, "+e+")";this.X.fill()}};it.prototype.hide=function(){I(this.a);it.C.hide.call(this)};function jt(){Z.call(this,["div","ytp-sentiment-display",["div","ytp-sentiment-bar",["div","ytp-sentiment-bar-likes"],["div","ytp-sentiment-bar-dislikes"]],["div","ytp-sentiment-text","{{sentiment}}"]]);this.b=this.template.a["ytp-sentiment-bar-likes"];this.a=this.template.a["ytp-sentiment-bar-dislikes"]}y(jt,Z);jt.prototype.F=function(){this.a=this.b=null;jt.C.F.call(this)};function kt(a){a=["button","ytp-button-share-more",["div","ytp-button-share-more-icon yt-uix-button-icon-new-window"],["span","ytp-button-share-more-content",a.getMsg("YTP_BUTTON_MORE_SHARE")]];Z.call(this,a)}y(kt,Z);function lt(a,b,c,d){Om.call(this,a,"share-service-icon-"+d+"-sharebar",b,c);V(this.element,"share-service-icon-sharebar")}y(lt,Om);function mt(){Z.call(this,["div","ytp-share-url-container",["input","ytp-share-url"]]);this.a=this.template.a["ytp-share-url"]}y(mt,Z);mt.prototype.F=function(){this.a=null;mt.C.F.call(this)};function nt(a,b,c){R.call(this);this.a=a;this.g=b;this.i=c;this.eb=new eo(this);this.j=this.b=this.k=this.ia=null;this.A=this.o=!1}y(nt,R);f=nt.prototype;f.Rj=function(){var a=E("html5-title",this.a);this.k=E("html5-title-text",a);this.eb.listen(this.k,"click",this.Fn);this.b=new Ss(this.i);this.b.T(a,1);this.eb.listen(this.b.g,"click",this.ek);this.g.nf&&this.b.g.hide();this.eb.listen(this.b.i,"click",this.ai)};f.reset=function(){}; +f.update=function(a){this.ia=a;this.reset();Zc(this.k,a.title||"");an(this.a)};f.Fn=function(){this.Ki(this.ia.Oc||ot(this.g,this.ia))};f.ek=function(){if(!this.o){this.o=!0;Cm(this.ia?this.ia.L:void 0,!1,this.Ns,ba,this,this.g.Da,this.g.Ta);if(!this.j){var a=E("html5-info-panel-loading-icon",this.a);this.j=new it;V(this.j.element,"html5-info-panel-loader");this.j.T(a);L(this,this.j)}this.j.show()}Rs(this.b.i);W(this.a,"show-share");a=this.b.g;a.b?Rs(a):Qs(a);return bm(this.a,"show-more-info")}; +f.ai=function(a){Rs(this.b.g);W(this.a,"show-more-info");if("detailpage"!=this.g.Y){if(!this.A){this.A=!0;var b={action_get_share_info:1,feature:"player_embedded",video_id:this.ia?this.ia.L:void 0};this.g.Da&&(b.authuser=this.g.Da);this.g.Ta&&(b.pageid=this.g.Ta);ej("/share_ajax",{X:this,method:"GET",onError:this.Xt,Oa:this.Yt,zf:b})}a&&a.stopPropagation();a=this.b.i;a.b?Rs(a):Qs(a);bm(this.a,"show-share")}}; +f.Ns=function(a,b){if(!this.ha()){var c=E("html5-info-panel",this.a),d=b.user_info;this.ia&&(this.ia.ua=d.external_id);var e=E("html5-author-img",c).getElementsByTagName("img")[0];e.src=d.image_url;this.eb.listen(e,"click",this.Wn);e=E("html5-author-name",c);Zc(e,d.username);this.eb.listen(e,"click",this.Wn);e=b.video_info;e.subscription_ajax_token&&ff("subscription_ajax",e.subscription_ajax_token);var g=E("html5-subscribe-button-container",c);g.innerHTML=d.subscription_button_html?d.subscription_button_html: +"";d=ct.getInstance();(d=E($(d),g))&&pt(this.i,d);E("html5-view-count",c).innerHTML=e.view_count_string;var h=parseInt(e.likes_count_unformatted,10),g=parseInt(e.dislikes_count_unformatted,10),d=new jt,k=0,m=0;0this.wf?"block":"none";c=this.a; +(b=c.parentNode)&&b.replaceChild(a,c);this.a=a;xt(this)};f.F=function(){Af(this.k);Af(this.o);this.g.removeAll();this.o=this.k=this.j=this.i=this.b=this.A=null;vt.C.F.call(this)};function zt(a,b){Z.call(this,["div","ytp-playlist-tray-item",["span","ytp-playlist-tray-item-index","{{index}}"],["span","ytp-playlist-tray-item-now-playing","\u25b6"],["img","ytp-playlist-tray-item-thumbnail",{src:"{{image}}"}],["span","ytp-playlist-tray-item-title","{{title}}"],["span","ytp-playlist-tray-item-author","{{author}}"]]);this.a=b;this.template.update({index:b+1,title:a.title,author:a.author,image:kn(a.L)})}y(zt,Z); +zt.prototype.Hg=function(a){X(this.element,"ytp-playlist-tray-item-current",this.a==a)};function At(){Z.call(this,["div","ytp-playlist-tray-tray"]);this.a=null;this.i=new eo(this);this.b=[]}y(At,Z);function Bt(a,b){b!=a.a&&(a.a&&a.a.W("shuffle",a.g,a),a.a=b,a.a.D("shuffle",a.g,a),a.g())}At.prototype.g=function(){this.i.removeAll();this.b=[];Vc(this.element);for(var a=0;a<=this.a.Pa-1;++a){var b=new zt(qg(this.a,a),a);b.Hg(this.a.pa);this.b.push(b);this.i.listen(b,"click",oa(this.j,a));b.T(this.element)}}; +At.prototype.j=function(a){a={index:a};var b=document.createEvent("CustomEvent");b.initCustomEvent("playvideoat",!0,!0,a||null);this.dispatchEvent(b)};At.prototype.F=function(){this.i.removeAll();At.C.F.call(this)};function Ct(a){dm.call(this,a);var b=Q(a);this.A=new eo(this);this.B=a.app.P.A;this.i=new vt(b);this.i.D("playvideoat",a.sj,a);L(this,this.i);this.j=new sm(["div",["ytp-playlist-tray-container","html5-stop-propagation"],["div",["ytp-playlist-tray-info","show-more-info"]]]);L(this,this.j);this.g=new At;this.A.listen(this.g,"playvideoat",v(function(b){a.sj(b.detail.index)},a));L(this,this.g);this.k=E("html5-playlist-button",b);K(this.k,"click",v(this.Lm,this));this.I=this.H=null;this.K=!1;this.b=null; +this.D("fullscreentoggled",this.gr,this);this.a.ub()&&a.J().Ma&&(F(E("html5-playlist",Q(this.a))),F(this.k),this.a.app.P.g.appendChild(this.j.M()),this.g.T(this.j.M(),0),this.b=new st(this.B),this.xm(),this.b.T(E("html5-title",void 0),0),this.A.listen(this.b.b,"click",this.Lm),this.D("videodatachange",this.jr,this),this.D("onResize",this.wm,this),this.D("clearvideooverlays",this.unload,this))}y(Ct,dm);f=Ct.prototype;f.na="playlist"; +f.create=function(){var a=this.a.ub();wt(this.i,a);xt(this.i);Bt(this.g,a);for(var b=this.g,c=0;cub(d,b)){a.stopPropagation();a.preventDefault();It.splice(c,1);break}},!0))} +function Lt(a){It.push(a);window.setTimeout(function(){var b=It.indexOf(a);-1!=b&&It.splice(b,1)},2500)}f=Gt.prototype;f.gl=1E3; +f.kw=function(a){if(!(1-p&&s<2*g+p?(a.g.style.left=s+"px",ym(a.g)):zm(a.g);m-=h;0<=m?(zd(a.j,Math.min(m,2*g)),ym(a.j)):zm(a.j);k-=h;0<=k?(zd(a.i,Math.min(k,2*g)),ym(a.i)):zm(a.i);if(b){g=(h-e.a)/2/60;e=(d-e.a)/2/60;h=0;for(d=Math.ceil(g);d<=e;d++)h=c.left&&a.pageY>=c.top&&a.pageX=this.ca.x-c&&a.pageX<=this.ca.x+c)return;this.A=!1;this.ca=this.R=b}else{ku(this);return}}}else this.R=b;lu(this)}; +function lu(a){if(a.a&&a.a.a){var b=Cd(a.a.M());if(a.I&&a.A){var c=b.left,b=c+b.width;a.j=c>a.O.x?a.O.x-c:bthis.j)a=0.15*this.j-0.1,this.j-=a,0this.j&&(this.j=0);else return;var b=sd(this.g).x;this.R.x=tb(this.R.x+a,b,b+iu(this).width);lu(this);a=mu(this);nu(this,a.vd,this.fa);this.u("seekto",a.vd*this.B,!1)}; +function ku(a){a.za.removeAll();a.b&&a.b.hide();a.a&&a.a.a&&(a.a.disable(),ju(a));a.Ra=fo(a.k,a.i,"over",a.sn)}f.Lv=function(a){a.stopPropagation();a.preventDefault();this.b&&this.b.hide();this.a&&this.a.a&&ju(this);ou(this);this.Rh(a)};f.Os=function(a){a.stopPropagation();a.preventDefault();this.A=!0;ou(this);this.Rh(a)};function ou(a){a.I=!0;a.u("beginseeking");fo(a.Ia,document,"move",a.Rh);fo(a.Ia,document,"up",a.fx);a.A&&(a.Ma=jf(v(a.cx,a),20))} +f.fx=function(a){a.stopPropagation();this.I=!1;this.Ia.removeAll();kf(this.Ma);ku(this);this.u("endseeking")};f.Rh=function(a){a.stopPropagation();a.preventDefault();this.yn(a);a=this.A?mu(this):iu(this);var b=a.vd*this.B;nu(this,a.vd,this.fa);(bthis.ea)&&this.rl();this.u("seekto",b,!this.I)};function mu(a){return a.a&&a.a.a?a.A?bu(a.a,a.O.x-sd(a.g).x):bu(a.a):iu(a)}function iu(a){return new $t(a.R.x-sd(a.g).x,a.S.clientWidth,a.ra.clientWidth/2,a.ra.clientWidth/2)} +function pu(a,b,c){nu(a,q(b)&&!a.I?b:a.va,q(c)?c:a.fa)}function nu(a,b,c){a.va=b;var d=Math.round(1E3*b)/10;id(a.qb,"transform","scalex("+b+")");a.ra.style.left=d+"%";a.fa=Math.max(a.va,c);id(a.La,"transform","scalex("+a.fa+")");a.a&&(a=a.a,a.G=b,a.o=c,du(a,!1))}f.Va=function(a){this.B=a;qu(this);this.a&&this.a.Va(a)}; +function qu(a){var b=!(!a.B||isNaN(a.ba)),c=!(!a.B||isNaN(a.ea));X(a.H,"html5-clip-enabled",b);X(a.U,"html5-clip-enabled",b);X(a.N,"html5-clip-enabled",c);X(a.$,"html5-clip-enabled",c);b&&(a.H.style.left=100*a.ba/a.B+"%",a.U.style.width=a.H.style.left);c&&(b=Math.min(100,100*a.ea/a.B),a.N.style.left=b+"%",a.$.style.left=b+"%",a.$.style.width=100-b+"%")}f.lp=function(){V(this.g,"html5-clip-marker-hover")};f.kp=function(){W(this.g,"html5-clip-marker-hover")}; +f.rl=function(a){a&&a.stopPropagation();this.ba=NaN;qu(this);this.ea=NaN;qu(this)};f.F=function(){I(this.oa);kf(this.Ma);this.k.removeAll();this.za.removeAll();this.Ia.removeAll();this.ua=null;this.K={};this.ra=this.gb=this.S=this.i=this.qb=this.La=this.g=this.H=this.$=this.U=this.N=null;gu.C.F.call(this)};function ru(a,b,c){Z.call(this,["div","ytp-drop-down","{{content}}"]);this.G=a;this.g=!1;this.o=0;this.a=new Z(["div","ytp-drop-down-menu","{{content}}"]);L(this,this.a);this.b=new Om(this.G,"ytp-drop-down-label");L(this,this.b);this.listen(this.b,"click",this.B);this.j=new Z(["div","ytp-drop-down-label-content","{{content}}"]);L(this,this.j);this.b.Aa([this.j,["div","ytp-drop-down-arrow"]]);this.Aa([this.a,this.b]);this.stopPropagation("click");q(b)&&su(this,b);q(c)&&(this.a.Aa(c),tu(this));tu(this)} +y(ru,Z);function su(a,b){a.j.Aa(b);tu(a)}ru.prototype.i=function(){this.g=!1;tu(this)};ru.prototype.B=function(){this.g=!this.g;tu(this)};function uu(a,b){b>a.o&&(a.o=b,a.element.style.minWidth=a.o+"px")}function tu(a){a.g?(a.a.show(),a.listen(window,"blur",a.i,"menu"),a.listen(document,"click",a.i,"menu")):(a.a.hide(),a.Vc(a.k.menu),a.k.menu=[]);var b=Ad(a.b.M());a.a.M().style.bottom=b.height-1+"px";uu(a,b.width)}ru.prototype.F=function(){this.G=null;ru.C.F.call(this)};function vu(a){Om.call(this,a,"ytp-drop-down-menu-button");this.j=new Z(["div","ytp-drop-down-menu-button-check"]);L(this,this.j)}y(vu,Om);vu.prototype.Aa=function(a){vu.C.Aa.call(this,[this.j,a])};function wu(a,b){X(a.element,"ytp-drop-down-menu-button-selected",b)};function xu(a){Z.call(this,["span"]);this.element.innerHTML=a}y(xu,Z);function yu(a){R.call(this);this.g=a;this.b={};this.j=new Z(["div","ytp-menu-drop-down-content"]);L(this,this.j);this.a="auto";this.i=!0;this.label=this.g.getMsg("YTP_QUALITY_TITLE");this.element=new ru(a,void 0,this.j);L(this,this.element);uu(this.element,100);zu(this,this.a,this.a);this.priority=-1}y(yu,R);function Au(a,b){Bu(a);z(b,function(a){var b=new vu(this.g);b.Aa(new xu(Cu(this,a)));b.T(this.j.M());this.b[a]=b;K(b,"click",v(this.k,this,a))},a);a.b[a.a]&&wu(a.b[a.a],!0)} +function zu(a,b,c){a.a&&a.b[a.a]&&wu(a.b[a.a],!1);a.a=b;a.b[a.a]&&wu(a.b[a.a],!0);a.i="auto"==c;a.i&&c!=b?(c=a.element,a=a.g.getMsg("YTP_QUALITY_AUTO_WITH_QUALITY",{video_quality:Cu(a,b)}),a=new xu(a),su(c,a)):su(a.element,new xu(Cu(a,c)))}function Cu(a,b){return a.g.getMsg("YTP_QUALITY_"+b.toUpperCase())}function Bu(a){Cb(a.b,function(a){Nf(a)});a.b={}}yu.prototype.k=function(a){this.element.i();this.i&&"auto"==a||this.u("qualitychanged",a)};yu.prototype.F=function(){Bu(this);this.g=null;yu.C.F.call(this)};function Du(a){R.call(this);this.g=a;this.a={};this.b=1;this.i=new Z(["div","ytp-menu-drop-down-content"]);L(this,this.i);this.label=this.g.getMsg("YTP_PLAYER_SPEED_TITLE");this.element=new ru(a,void 0,this.i);L(this,this.element);uu(this.element,100);this.priority=1}y(Du,R);function Eu(a,b){Fu(a);z(b,function(a){var b=new vu(this.g);b.Aa(Gu(this,a));b.T(this.i.M());this.a[a]=b;K(b,"click",v(this.j,this,a))},a);a.a[a.b]&&wu(a.a[a.b],!0)} +function Hu(a,b){a.b&&a.a[a.b]&&wu(a.a[a.b],!1);a.b=b;a.a[a.b]&&wu(a.a[a.b],!0);su(a.element,Gu(a,b))}function Gu(a,b){return 1==b?a.g.getMsg("YTP_PLAYER_SPEED_NORMAL"):b.toString()}function Fu(a){Cb(a.a,function(a){Nf(a)});a.a={}}Du.prototype.j=function(a){this.element.i();Hu(this,a);this.u("speedchanged",a)};Du.prototype.F=function(){Fu(this);this.g=null;Du.C.F.call(this)};function Iu(){Z.call(this,["div",["ytp-time-display","html5-control"],["span","ytp-time-current","{{current}}"],["span","ytp-time-separator"," / "],["span","ytp-time-duration","{{duration}}"],["span","ytp-time-live-badge","Live"]]);this.a=this.template.a["ytp-time-live-badge"]}y(Iu,Z);Iu.prototype.Va=function(a){xm(this.template,"duration",Xm(a))};Iu.prototype.F=function(){this.a=null;Iu.C.F.call(this)};function Ju(a){R.call(this);this.b=a;this.a=new eo(this);this.A=new eo(this);this.k=this.j=null;this.o=new eo(this)}y(Ju,R);f=Ju.prototype;f.kg=null;f.Zc=null;f.Ie=null;f.fk=null;f.Yd=null;f.tc=null;f.uf=null;f.ac=null;f.yg=null;f.Vd=null;f.ze=null;f.Vh=null;f.ud=null;f.Qc=null;f.vb=null;f.vf=null;f.Ee=null;f.Rl=!1;f.Uc=null;f.ce=null;f.hk=null;f.de=null;f.bo=!1;f.tg=null;f.wb=null; +function Ku(a,b){a.kg=b;var c=E("html5-player-chrome",b);a.Zc=new Tt(a.b);a.Zc.T(c,0);L(a,a.Zc);new Gt(a.Zc.M(),v(a.St,a),!1,function(){return!0});a.Ie=new Vt(a.b);a.Ie.T(c,1);L(a,a.Ie);a.a.listen(a.Ie.a,"click",oa(a.u,"nextvideo"));a.a.listen(a.Ie.b,"click",oa(a.u,"previousvideo"));var d=E("html5-progress-bar-container",b);a.fk=new gu(a.b);hu(a.fk,d);a.g=new Iu;a.g.T(c,4);L(a,a.g);a.a.listen(a.g.a,"click",oa(a.u,"seekto",Infinity));a.Yd=E("html5-volume-control",b);a.a.listen(a.Yd,"keydown",a.Tt); +a.tc=E("html5-volume-button",a.Yd);a.a.listen(a.tc,"click",a.Dn);a.uf=E("html5-volume-panel",a.Yd);a.ac=E("html5-volume-slider",a.Yd);fo(a.a,a.ac,"down",a.Pt);a.yg=E("html5-volume-slider-foreground",a.Yd);a.Vh=E("html5-threed-popup-menu",b);a.ze=E("html5-small-player-button",b);a.a.listen(a.ze,"click",a.Cn);a.Vd=E("html5-large-player-button",b);a.a.listen(a.Vd,"click",a.Cn);a.i=new Pt(a.b);a.i.T(c,7);L(a,a.i);a.a.listen(a.i,"click",a.Ot);a.ud=E("html5-quality-button",b);a.Ee=E("html5-quality-popup-menu", +a.ud);go(a.a,a.Ee,a.Mt);a.vf=E("html5-speed-popup-menu",a.ud);go(a.a,a.vf,a.Nt);a.wb=E("html5-watch-later-button",b);a.a.listen(a.wb,"click",a.Qt);a.wb&&pt(a.b,a.wb,a.b.getMsg("YTP_TOOLTIP_WATCH_LATER"),!0);a.tg=E("html5-watch-on-youtube-button",b);a.a.listen(a.tg,"click",a.Rt)}f.disable=function(a){Lu(this,a,!0)};f.enable=function(a){Lu(this,a,!1)};function Lu(a,b,c){z(b,function(a){X(this.kg,"disabled-control-"+a,c)},a)} +function Mu(a,b){if(a.Uc)Au(a.Uc,b),b.length?a.vb.add(a.Uc):a.vb.remove(a.Uc);else{var c=Jc("yt-uix-button-menu-item",a.Ee);z(c,function(a){a.style.display="none";for(var c=0,g=b.length;c=b;b++)W(a.ma,"anchor-point-"+b);V(a.ma,"anchor-point-"+a.qa.ji)}f.$o=function(a){var b=[];z(a,function(a){a.Yi?b[b.length-1]+=a.ge:b.push(a.ge)});this.Te(b.join("\n"));this.Cc=a};f.Te=function(a){this.eh=a=Zu(a);this.ja.innerHTML=this.eh;this.Ol();this.Xf()}; +f.Ol=function(){this.ma.style.textAlign=this.qa.textAlign;this.ja.style.backgroundColor=this.qa.backgroundColor;this.ja.style.color=this.qa.gi;this.ja.style.opacity=this.qa.Nl;this.ja.style.fontFamily=this.qa.fontFamily;this.ja.style.fontSize=this.qa.xx;1==this.qa.Je?this.ja.setAttribute("dir","rtl"):this.ja.removeAttribute("dir")};f.toString=function(){var a="Caption window ("+this.id+"): "+this.eh,b;for(b in this.qa)a+=b+" "+this.qa[b]+" | ";return a}; +function Zu(a){a=a.split("\n");for(var b=0,c=a.length;b")}f.$i=function(){this.Cc=[];this.Te("")};function $u(a,b){Xu.call(this,a,b);this.ja.style.display="block";this.ja.style.padding="0";this.Ob=[];var c=this.ja;W(c,"captions-text");V(c,"caption-painton-text-rows")}y($u,Xu);f=$u.prototype;f.type=0;f.ko="";f.dh=!1;f.Bb=null;f.Ob=null;f.Ui=function(){return this.Ob[0]?this.Ob[0].offsetHeight:0};function av(a){return a.Ob.reduce(function(a,c){return Math.max(a,c.offsetWidth)},0)} +f.Xf=function(){0!=this.id&&(zd(this.ma,"100%"),zd(this.ma,this.dh?av(this):this.Bb.width));var a=Math.round(this.qa.Bd*this.Ui());id(this.ma,"max-height",a+"px");Yu(this);Zm(this.ma,this.qa.isVisible)}; +f.Te=function(a){this.$i();a=Zu(a);this.dh||(this.eh=a);a=a.split("
    ");for(var b=0,c=a.length;b=b)this.$i();else{for(var c=0;c=a.Cc.length)ev(a);else{var b=a.Db.length-1;0>b&&(a.pe.push(0),a.qe=0,a.Db.push(""),b=0);for(var c=a.Cc.length,d=a.qe;da.qa.Bd;){a.Db.shift();var b=a.pe.shift();0=g?h.zc=g:h=null);switch(d.getAttribute("op")){case "kill":g= +null;break t;case "define":h=null}h?h.j=!0:h=kv();var k={};pa(k,h?h.params:Tu);d.getAttribute("id")&&(k.id=d.getAttribute("id"));d.getAttribute("op")&&(k.Ey=d.getAttribute("op"));d.getAttribute("rc")&&(k.Bd=parseInt(d.getAttribute("rc"),10));d.getAttribute("cc")&&(k.qk=parseInt(d.getAttribute("cc"),10));d.getAttribute("ap")&&(h=parseInt(d.getAttribute("ap"),10),k.ji=0>h||8> ",a.a.b);return b.join("")}f.toString=function(){var a=[this.b,": ",this.fc," (",this.Kb,")"];this.a&&a.push(" >> ",this.a.a);return a.join("")};f.equals=function(a){if(!a)return!1;var b=this.a,c=a.a;if(b&&c){if(b.a!=c.a)return!1}else if(b||c)return!1;return this.b==a.b&&this.fc==a.fc&&this.Kb==a.Kb};function rv(){this.g=[];this.a=[]}rv.prototype.g=null;rv.prototype.a=null;rv.prototype.b=-1;function sv(a,b){return b?a.a.concat(a.g):a.a}function tv(a,b){switch(b.Kb){case "asr":return uv(b,a.g);default:if(b.oi||0>a.b)a.b=a.a.length;return uv(b,a.a)}}function uv(a,b){return Na(b,v(a.equals,a))?!1:(b.push(a),!0)};function vv(a,b,c,d){this.Ld=a;c?this.Ld=Zi(this.Ld,{hl:c}):(a=Vi(this.Ld).hl||"",a=a.split("_").join("-"),this.Ld=Zi(this.Ld,{hl:a}));this.Li=b;this.Sp=!!d;this.Jd=new rv;this.zi=[];this.Tp={}}f=vv.prototype;f.Ld="";f.Li=null;f.Sp=!1;f.Jd=null;f.zi=null;f.Tp=null;function wv(a,b){return Na(sv(a.Jd,!0),function(a){return a.toString()==b})}function xv(a,b){var c=a.Ld,d={v:a.Li,type:"track",lang:b.b,name:b.fc,kind:b.Kb,fmt:b.Ik};b.a&&(d.tlang=b.a.a);return c=Zi(c,d)} +function yv(a,b,c){var d=xv(a,b);a=v(function(a){a=new mv(a.responseText);c(a,b)},a);bj(d,a)}function zv(a){var b=a.Jd.b;a=sv(a.Jd,!0);return 0>b?null:a[b]} +function Av(a,b){var c=a.Ld,d={type:"list",tlangs:1,v:a.Li,fmts:Number(!0)};a.Sp&&(d.asrs=1);c=Zi(c,d);d=v(function(a){if((a=a.responseXML)&&a.firstChild){for(var c=this.Jd,d=a.getElementsByTagName("track"),k=d.length,m=0;m]*>?/g,""),b.Te(c));gm(this,this.ti.ma);this.ah.start();this.u("publish_external_event","captionschanged",pv(a))};f.vc=function(a){0==a.getId().indexOf("caption")&&(a=parseInt(a.getId().split("caption")[1],10),!isNaN(a)&&this.Me[a]&&(this.ne.push(this.Me[a]),this.Sf.start()))}; +f.dd=function(a){0==a.getId().indexOf("caption")&&(a=parseInt(a.getId().split("caption")[1],10),!isNaN(a)&&this.Me[a]&&(a=this.ne.indexOf(this.Me[a]),0<=a&&this.ne.splice(a,1),this.Sf.start()))}; +function Kv(a,b){if(b instanceof jv){var c=a.Ga[b.id];pa(b.params,a.b);c&&c.getType()!=(b.params.jk?2:b.b?0:1)&&(F(c.ma),delete a.Ga[b.id]);if(!a.Ga[b.id]){var c=a.Ga,d=b.id,e;t:{e=b.id;var g=b.params;switch(b.params.jk?2:b.b?0:1){case 0:e=new $u(e,g);break t;case 2:e=new bv(e,g);break t;default:e=new Xu(e,g)}}c[d]=e;c=a.Ga[b.id].ma;X(c,"captions-asr","asr"==a.dc.Kb);null!=b.params.Je&&(b.params.Je=a.Un?1:0);0==a.Ga[b.id].id?(V(c,a.na),Lv(a.a.app.P,c)):gm(a,c)}pa(a.Ga[b.id].qa,b.params);if(0==(b.params.jk? +2:b.b?0:1)){c=a.Ga[b.id];c.ko=b.g;c.Bb={};c.dh=!0;c.Te(c.ko);c.Bb.Bd=c.Ob.length;c.Bb.width=c.ma.offsetWidth;c.Bb.height=c.ma.offsetHeight;c.Bb.ho=[];c.Bb.jo=[];for(d=0;d=c*d?b:new C(c,d))}this.Fh=b;this.hb=U(this.hb,a.attrib);this.$=U(this.$,a.sk);this.protocol=this.Hj?"https":"http";this.ve="0"!=this.Gj;this.pj=T(this.ve,a.store_user_volume); +this.Pd=T(this.Pd,a.use_media_volume);(b=a.BASE_YT_URL)&&Cj(b,yj)&&(this.ib=b);Zw(this,a);$w(this,a);"detailpage"==this.Y&&delete this.b;this.fj=ax(this)+"s";this.sc=this.of&&!this.Ub?!this.Wb:this.Wb||this.ve?!1:this.sc;b=this.Sb||"detailpage"==this.Y;c=!0;this.kc&&(c=!1);si("nintendo wiiu")&&(c=!1);this.Fj&&(c=!0);this.qm=b&&c;this.a=a}y(Ww,Lf);f=Ww.prototype;f.um=!0;f.Ch=!0;f.Da="";f.Bh="2";f.Sb=!1;f.Fj=!1;f.ib="/";f.uy=!0;f.color="red";f.Gj="1";f.eg="web";f.jf="html5";f.Lh=!1;f.Fg=!0;f.Gg=!0; +f.jg=!1;f.om=!1;f.Y="detailpage";f.Jm=!1;f.Lk=!1;f.nf=!1;f.kc=!1;f.Ab=!1;f.Od=!1;f.Rd=!0;f.Km=!1;f.ff=!0;f.Ig=!1;f.loop=!1;f.of=!1;f.wh=!1;f.Dh=null;f.qj="";f.dg=!1;f.Ta="";f.Ja=null;f.Ah=!1;f.Kh=!1;f.Im=!1;f.lg=!1;f.qm=!1;f.lc=!1;f.vj=!0;f.wj=!0;f.ve=!0;f.pj=!0;f.Pd=!1;f.Wb=!0;f.qc=!0;f.xl=!1;f.Tb=!1;f.sc=!1;f.oj=!1;f.Mh=!1;f.tm=!1;f.Ub=!1;f.tb=!0;f.Hj=!1;f.Gb=!1;f.Sc=null;f.Fh=null;f.xv=null;f.ug=null;f.Wa=null;f.nk=null;f.Vb="yt"; +function Zw(a,b){a.lc=T(a.lc,b.logwatch);a.ra=void 0==b.user_age?a.ra:Number(b.user_age);a.Pp=U(a.Pp,b.user_display_image);a.Ql=U(a.Ql,b.user_display_name);a.ua=U(a.ua,b.user_gender)} +function $w(a,b){var c;t:{if(b&&((c=b.adformat)||(c=(c=b.attrib)&&c in Rw&&c in Sw?Sw[c]+"_"+Rw[c]:void 0),c)){var d=c.match(/^(\d*)_((\d*)_?(\d*))$/);if(d&&5==d.length&&(d=d[3],d=6==d||7==d||9==d||11==d,Cj(a.fa,zj)||d))break t}c=void 0}c&&(a.j=c,a.a.adformat=b.adformat);c=b.agcid;a.qb=c;a.a.agcid=c;c=b.feature;a.k=c;a.a.feature=c;if(c=b.referrer)a.referrer=c,a.a.referrer=c;"1"==b.enablecsi&&(a.Lk=!1)} +function bx(a){var b={};b.c=a.eg;a.jf&&(b.cver=a.jf);a.O&&(b.cplatform=a.O);a.G&&(b.cbrand=a.G);a.H&&(b.cmodel=a.H);a.I&&(b.cnetwork=a.I);a.A&&(b.cbr=a.A);a.B&&(b.cbrver=a.B);a.K&&(b.cos=a.K);a.N&&(b.cosver=a.N);return b} +function Xw(a,b){if(b){a.g=b.split(",");var c={};z(a.g,function(a){c[a]=!0});a.Kc=!!c["918108"];a.Mc=!!c["912711"];a.hj=!!c["925900"];a.jj=!!c["913559"];a.Ma=!!c["913428"];a.ba=!!c["904828"]||!!c["904830"];a.Ra=!!c["913424"];a.za=!!c["932206"];a.Fc=!!c["932217"];a.od=!!c["932225"];a.Oc=!!c["932246"];a.Ec=!!c["932245"];a.Jc=!!c["932240"];a.te=!!c["932236"];a.Ic=!!c["932237"];a.Hc=!!c["932242"];a.Dc=!!c["932243"];a.Nc=!!c["932247"];a.bg=!!c["932248"];a.Ia=!!c["932239"];a.pd=!!c["932249"];a.Gc=!!c["924604"]; +a.Lc=!!c["924610"];a.nd=!!c["918117"];a.La=!!c["907226"]&&"leanback"!=a.Y;a.ea=!!c["938600"]}} +function cx(a,b){switch(b.qd){case 38:var c=b.L.indexOf(":"),d=b.L.slice(0,c),c=b.L.slice(c+1);return S("//play.google.com/books/volumes/"+d+"/content/media",{aid:c,sig:b.nd});case 30:return d="//docs.google.com/",a.ib!=Ww.prototype.ib&&(d=a.ib),S(d+"get_video_info",{docid:b.L,authuser:b.Da,authkey:b.vo,eurl:a.b});case 33:return S("//google-liveplayer.appspot.com/get_video_info",{key:b.L});default:return d={html5:"1",video_id:b.L,cpn:b.Ca,eurl:a.b,ps:a.Ja,el:a.Y,hl:a.i,list:b.Na,agcid:a.qb,sts:15947}, +b.U?d.vvt=b.U:b.H&&(d.access_token=b.H),a.j&&(d.adformat=a.j),b.ca&&(d.iv_load_policy=b.ca),b.hf&&(d.autoplay="1"),b.Wk&&(d.mdx="1"),b.Xk&&(d.utpsa="1"),b.Nc&&(d.is_fling="1"),c=dx(a),c.width&&(d.width=c.width),c.height&&(d.height=c.height),b.ba&&(d.ypc_preview="1"),b.Nd&&(d.splay="1"),a.o&&(d.content_v=a.o),b.wo&&(d.livemonitor=1),a.Da&&(d.authuser=a.Da),a.Ta&&(d.pageid=a.Ta),pa(d,bx(a)),S(a.ib+"get_video_info",d)}} +function ax(a){return a.Lh?"//s.youtubeeducation.com/":a.ff?"//s.youtube.com/":"//video.google.com/"}function ex(a){return a.referrer?a.referrer.slice(0,128):""} +function ot(a,b,c,d){b={v:b.L,list:b.Na};a.Y&&(b.feature="player_"+a.Y);c&&pa(b,c);a=S(a.protocol+"://"+("www.youtube-nocookie.com"==window.location.host||a.Lh?"www.youtube.com":window.location.host)+"/watch",b);if(d){c="";c="!"=="#".charAt(1)?"#".substr(0,2):"#";b="#";"#"==b.charAt(0)&&(b="!"==b.charAt(1)?b.substr(2):b.substr(1));b=Ui(b);for(var e in d)b[e]=d[e];d=c+Wi(b);a=a+d}return a} +function fx(a){var b={contact_type:"playbackissue",html5:1,plid:a.ab,ei:a.j,v:a.L};a.a&&(b.fmt=a.a.Sa);return S("//www.google.com/support/youtube/bin/request.py",b)}function dx(a){return(a=a.nk)?new C(a.clientWidth,a.clientHeight):new C(Number.NaN,Number.NaN)}function Mv(a){return a.Wa?new C(a.Wa.clientWidth,a.Wa.clientHeight):new C(Number.NaN,Number.NaN)} +function gx(a){var b={};if(!a.Wa)return b;a.Wa.webkitDecodedFrameCount&&(b.hmewdfc=a.Wa.webkitDecodedFrameCount,b.hmewdrop=a.Wa.webkitDroppedFrameCount,b.hmewvdbc=a.Wa.webkitVideoDecodedByteCount,b.hmewadbc=a.Wa.webkitAudioDecodedByteCount);a.Wa.mozParsedFrames&&(b.hmempf=a.Wa.mozParsedFrames,b.hmemdf=a.Wa.mozDecodedFrames,b.hmempresented=a.Wa.mozPresentedFrames,b.hmempainted=a.Wa.mozPaintedFrames,b.hmempaintdelay=a.Wa.mozPaintDelay);return b} +function hx(a){var b=n("yt.www.watch.activity.getTimeSinceActive",window);if("detailpage"==a.Y&&b)return b();var c;a.gb&&(c=w()-a.gb);return c}function yn(a){return"leanback"==a.Y||a.nf||!a.qc&&!a.xl?!1:!0}function Bg(a){return"detailpage"!=a.Y?!1:!0}function Yw(a){a=Cj(a.fa,yj)&&Bj();var b=Cj(document.location.toString(),yj)&&!Bj();return a||b}f.F=function(){this.nk=this.Wa=null;Ww.C.F.call(this)};function ix(a){this.a=a;this.pendingRequests_=[];this.g=[];this.b=this.i=null;this.j=0}function jx(a,b){a.pendingRequests_.push(b);a.i=b.g[b.g.length-1];a.j+=b.ka.length}function kx(a){for(;a.pendingRequests_.length&&5==a.pendingRequests_[0].state;){var b=a.pendingRequests_.shift();z(b.o,a.G,a)}} +ix.prototype.G=function(a){if(4==a.info.type){for(var b=ak(a.info.a,a.info),c=[],d=0;dc-b.j)){if(32768g)b.o+=e;else{var h=b.g,e=Math.max(e,0.05);h.a.g(e,g/e)}}b.j=c;b.b=d}this.b.b>this.b.A&&4>this.state&&wx(this,4);4==a.target.readyState&&zx(this.b,a.timeStamp)}}; +f.mr=function(a){if(!this.ha()&&a==this.a){this.i&&(I(this.i),this.i=NaN);var b=null==a.response||400<=a.status;if(!b){a=a.response;var c;t:{if(2048>a.byteLength&&(c=String.fromCharCode.apply(String,new Uint8Array(a)),Cj(c,yj)))break t;c=""}if(c)a=this.G,c=oe(c,"keepalive"),c=oe(c,"playerretry"),c=oe(c,"range"),c=oe(c,"shost"),a.a=c,wx(this,3);else if(a.byteLength!=this.ka.length)b=!0;else{c=this.g;for(var d=new Uint8Array(a),e=[],g=0,h=0;ha.G.g&&1<=a.G.b)}function wx(a,b){a.state=b;3<=a.state&&a.B&&a.B(a)} +f.Pm=function(){this.i=NaN;if(!this.ha()&&this.b){var a=!1;if(this.b.i){var b=this.b.G;xx(this.b);this.b.G-b>=0.8*(this.k.a/1E3)?(this.A++,a=5<=this.A):this.A=0}else a=5a.a.g)return;b=a.a.g}b={cwndbw:(8*b/1024).toFixed(0)};Lj(a.i.a.j,b);Lj(a.b.a.j,b)}}function Hx(a,b,c){c-=2;c-=c*b*(a.k.b.b()||0);c=Math.max(a.a.I,Math.min(a.a.N,c));a=Math.max(a.a.K,Math.ceil(a.a.I*b));return Math.min(a,Math.ceil(c*b))} +function Ix(a,b){var c=Math.min(2.5,ih(a.k));return Math.ceil(b*c+32768)} +function Jx(a,b){if(!b.i){if(!Xj(b.a))return;nx(b,a.j)}if(!(b.j>a.a.G)){var c=b.i;4==c.type&&Xj(c.a)&&(b.i=Fa(ak(c.a,c)),c=b.i);if(!c.g&&(Xj(c.a)||(isNaN(c.a.a)?0:c.ka.end+1d||2<=tx(b)+1)){var d=c.k-a.j,e=c.a.info.b,g=Xj(c.a)?Hx(a,e,d):a.a.H,h=Ix(a,c.a.info.b),k=!1,e=!1;if(c&&3==c.type&&Xj(b.a)&&c.a!=b.a){var d=Hx(a,b.a.info.b,d+(c.startTime+c.duration-c.k)),m=Ix(a,b.a.info.b),e=c.a.info.b=m)k=!0,g=d,h=m}d= +h;0e&&(b.g.pop(),b.g.length&&b.g[g-1].data.buffer==h.data.buffer||(b.j-=h.data.buffer.byteLength));lx(b)}b.i&&!b.i.b&&(e=new ux(a.a,rx(b)), +Kx(a,e,!0),jx(b,e));b.i&&(c=b.i?b.i.startTime+b.i.duration+Dx:0);d=new ux(a.a,bk(b.a,c,d))}else d=new ux(a.a,c.Uh(d));Kx(a,d,!1);jx(b,d)}}}}function Kx(a,b,c){c=c?2:1;a.a.O&&(c=0);var d=Math.min(2.5,ih(a.k)),e=jh(a.k);b.b=new Bx(a.k,c,b.ka.length,0.95*b.ka.length-d*e);a=v(a.Wu,a);b.B=a;b.start()} +f.Wu=function(a){if(!this.ha()){var b=6==a.state&&vx(a);3==a.state||b?(b&&this.u("softerror",209),a.start()):5==a.state?(this.H||(this.H=a.I),Lx(this),z(a.o,function(a){if(Fj(a.info))t:{var b=a.info.a;if(1==a.info.type){if(b.b)break t;b.b=a.data}else if(2==a.info.type){if(b.g||b.index.fb())break t;if(1==b.info.i){var e=b.index,g=a.info.ka.start;a=new DataView(a.data.buffer);var h=0,k=a.getUint32(0,!1),m=a.getUint8(h+8),h=h+12,p=a.getUint32(h+4,!1),h=h+8,s;0==m?(m=a.getUint32(h,!1),s=a.getUint32(h+ +4,!1),h+=8):(m=(a.getUint32(h,!1)<<32)+a.getUint32(h+4,!1),s=(a.getUint32(h+8,!1)<<32)+a.getUint32(h+12,!1),h+=16);e.a[0]=s+(k+g);e.b[0]=m;e.g=!0;g=a.getUint16(h+2,!1);h+=4;for(k=0;k=h.b.byteLength);)if(r=Qj(h),2807729==r)m=Uj(h);else if(2807730==r)s=Uj(h);else if(17545==r){var g=h,r=Rj(g,!0),x=0;4==r?x=g.b.getFloat32(g.a):8==r&&(x=g.b.getFloat64(g.a));g.a+=r;g=x}else Sj(h);m/=s;g*=m;h=new Pj(new DataView(k));if(475249515==Qj(h)){for(h=Tj(h);!(h.a>=h.b.byteLength);)if(r=Qj(h),187==r){k=Tj(h);r=m;s=p;if(179!=Qj(k))r=null;else if(r*=Uj(k),183!=Qj(k))r=null;else{k=Tj(k);for(x=s;!(k.a>=k.b.byteLength);)241==Qj(k)? +x=Uj(k)+s:Sj(k);r=[x,r]}k=e;s=r[0];r=r[1];Oj(k);k.a[k.Q]=s;k.b[k.Q]=r;k.Q++}else Sj(h);h=g;a=a+p;Oj(e);e.g=!0;e.b[e.Q]=h;e.a[e.Q]=a}}b.g=null}}}),!La(a.o,function(a){return Ij(a)})||Xj(a.g[0].a)&&!isNaN(a.g[0].a.a)&&(a.g[0].a.index.g?a.g[0].a.index.a[a.g[0].a.index.Q]:-1)!=a.g[0].a.a?Mx(this,126):Ex(this)):7==a.state||4==a.state?(7==a.state&&this.u("softerror",210),Ex(this)):Mx(this,121)}}; +function Lx(a){if(!a.O&&a.H&&window.performance&&window.performance.getEntriesByName){var b=window.performance.getEntriesByName(a.H);if(b.length){var b=b[0],c=window.performance.timing.navigationStart;Er("vrst",c+b.fetchStart);Er("vdns",c+b.domainLookupEnd);Er("vreq",c+b.requestStart);Er("fvb",c+b.responseStart);a.O=!0}}}function Ex(a){isNaN(a.B)&&(a.B=H(v(a.Wf,a),0))}function Nx(a){isNaN(a.A)&&(a.A=H(v(function(){this.Wf();this.A=NaN},a),1E3))} +f.Wf=function(){this.B=NaN;if(!this.K&&this.g){var a=Math.floor(Math.max(mx(this.i),mx(this.b)));isNaN(a)||(this.g.Va(a),this.K=!0)}if(!this.ha())if("prerender"==window.document.webkitVisibilityState)Nx(this);else{this.g&&this.g.xc()&&Fx(this);kx(this.b);kx(this.i);qx(this.b);qx(this.i);if(this.g){a=Ox(this,this.b,this.g.Af);if(!a){var b=Px(this.g.Af,this.j);if(!isNaN(b)||this.b.b&&this.b.b.g)this.b.b&&this.b.b.g&&(b=NaN),a=Ox(this,this.i,this.g.zd,b)}if(a){this.g.zd.appendBuffer||Ex(this);return}if(this.b.b&& +this.b.b.g&&this.i.b&&this.i.b.g&&this.g.isOpen()){a=this.g;a.isOpen()&&a.g.endOfStream();return}}this.rm();Jx(this,this.b);Jx(this,this.i)}};f.rm=function(){if(!this.ha()&&!isNaN(this.a.o)){var a;a=jh(this.k);var b=ih(this.k)+1048576*(this.k.b.b()||0);a=a*Math.min(1,1048576/(a*b));!isNaN(this.o)&&Math.abs(this.o-a)/a=d)return!1;g=1.5a.a.B)return!1;a=e.o-a.j;if(b.a.info.a&&!(sx(b)||b.b&&!b.b.b||b.k||2>=a))return!1;g?(a=b.g[0],d=Math.min(1,a.info.j),d=Math.floor(a.data.length*(d/ +a.info.j)),e=new Ej(a.info.a,a.info.ka,a.info.type,a.info.i,a.info.startTime,a.info.duration,a.info.Yb,d,!1),g=a.data.subarray(0,d),a.info=new Ej(a.info.a,a.info.ka,a.info.type,a.info.i,a.info.startTime,a.info.duration,a.info.Yb+d,a.info.kb-d,a.info.g),a.data=a.data.subarray(d),a=new Hj(e,g),b.b=a.info):(a=b.g.shift(),b.g.length&&b.g[0].data.buffer==a.data.buffer||(b.j-=a.data.buffer.byteLength),b.b=a.info);b.k&&b.b.b&&(b.k=!1);b=a;c.appendBuffer?(c.appendBuffer(b.data),c=c.updating):(c.append(b.data), +c=!0);return c}function Px(a,b){for(var c=0;c=b)return a.buffered.end(c);return NaN}function Qx(a,b){if(!a.ha()){var c=Rx(a,a.b,b,a.g&&a.g.Af),d=Rx(a,a.i,c,a.g&&a.g.zd);a.j=Math.max(b,c,d);a.I=!0;Ex(a)}} +function Rx(a,b,c,d){if(Xj(b.a))if(d){if(a.N&&a.a.b)return ox(b),nx(b,c);var e=Px(d,c),g=NaN,h=b.b;h&&(g=Px(d,h.a.index.b[h.i]));if(e==g)return c;ox(b);b.a.info.a&&b.b&&!b.b.b&&!sx(b)&&(d=new ux(a.a,rx(b)),Kx(a,d,!0),jx(b,d));if(isNaN(e))return nx(b,c);nx(b,e+Dx)}else nx(b,c);else b.a.i?0!=c&&px(b):Sx(a,b,!1);return c} +function Sx(a,b,c){var d=c?65536:0;c&&!isNaN(a.a.j)&&(d=Math.floor(Math.max(d,a.a.j*b.a.info.b)));c=Yj(b.a,d);z(c,function(a){var c=new ux(this.a,a);Kx(this,c,!0);Fj(a[a.length-1])||jx(b,c)},a);b.a.i=!0}f.F=function(){Fx(this);px(this.i);px(this.b);this.b=this.i=null;Cx.C.F.call(this)};function Mx(a,b){a.u("fatalerror",b);if(a.g&&a.g.isOpen()){var c=a.g;c.isOpen()&&c.g.endOfStream("network")}a.dispose()};function Tx(){this.B=30;this.A=40;this.G=20971520;this.I=this.j=2;this.H=131072;this.N=15;this.K=2097152;this.o=0.1;this.k=2;this.b=!0;this.g=this.i=this.a=NaN};function Ux(a,b,c){this.a=a;this.headers=b;this.message=c};function Vx(a,b,c,d,e,g,h){this.a=[];this.g=a;this.k=b.g;this.j=b;this.i=c;this.H=d;this.I=e;this.b=[];this.K=h||null;this.B=this.N=!1;this.A=null;this.o=!0;this.G={};this.listen(this.g,["keymessage","webkitkeymessage"],this.Ej);this.listen(this.g,["keyadded","webkitkeyadded"],this.hr);this.listen(this.g,["keyerror","webkitkeyerror"],this.ir);g&&this.b.push(g)}y(Vx,Wk);function Ag(a,b){if(Cj(b,yj)){for(var c in a.G)b=ke(oe(b,c),c,a.G[c]);a.k=b}else Wx(a,"u")}f=Vx.prototype; +f.start=function(){this.ha()||(this.N=!0,this.b.length&&this.Ej(this.b.shift()))};f.dispose=function(){Vx.C.dispose.call(this);this.I=this.g=null};f.Ej=function(a){this.ha()||a.sessionId!=this.i||(this.B&&a.defaultURL&&Ag(this,a.defaultURL),this.N?(this.A=a,Xx(this,a)):this.b.push(a))};f.hr=function(a){this.ha()||a.sessionId!=this.i||(this.B=!0,this.b.length&&this.Ej(this.b.shift()))}; +function Xx(a,b){var c={format:"RAW",method:"POST",Sw:b.message.buffer,responseType:"arraybuffer",withCredentials:!0,Oa:a.Tw,onError:a.Rn,X:a};a.K&&(c.headers={Authorization:"Bearer "+a.K});var d=a.k;a.o||(d=ke(oe(d,"exclude_customdata"),"exclude_customdata","1"));ej(d,c)} +f.Tw=function(a){if(!this.ha())if(0!=a.status&&a.response){t:{a=new Uint8Array(a.response);var b=0,c=String.fromCharCode.apply(String,a.subarray(0,16384)).split("\r\n"),d=c[0];Va(c,0);c.pop();var b=b+(d.length+2),e=d.match(/^GLS\/1.\d ([0-9]{1,3}) (\w+)$/);if(null!=e){d=e[1];isFinite(d)&&(d=String(d));for(var d=u(d)?/^\s*-?0x/i.test(d)?parseInt(d,16):parseInt(d,10):NaN,g={},h=0;hMath.round(e*h)&&(g=Math.round(e*h));g*=h;e=null;for(h=0;hg,m=!0;e.a&&d&&(m=e.b.info.b+e.g.info.bb.ya){var c=b.rf;a.R&&(a.bi(a.ua),a.ua=null);a.R=new nh(1E3*c,2147483646);a.R.yb.D("onEnter",a.pg,a);a.Mk(a.R)}ky(a,"newdata")} +function hy(a){kf(a.da);a.j&&(Nf(a.j),a.j=null)}f.getVideoData=function(){return this.a};f.J=function(){return this.o};function ly(a){return!(!a.a||!a.a.k.length)}function my(a){if(a.a&&(a.a.L||a.a.mf))return!0;sw(a,2);return!1}function ny(a){a.U||(oy(a,Ji(new Hi,8)),a.j&&py(a.j),a.U=!0,!my(a)||a.a&&a.a.Ue||qy(a,v(function(){if(ly(this))ry(this);else if(!this.a.Ue)if(sy(this))ry(this);else{var a=cx(this.o,this.a);Ll(this.a,a)}},a)))} +function ty(a){if(!a.S.length){if(!a.A.sg){var b=a.A;b.g=Ri(b);b.sg=!0;b.Xb()}a.S.length||0a.b.seekable.length?NaN:a.b.seekable.end(a.b.seekable.length-1);a.k&&!isNaN(Math.floor(Math.min(mx(a.k.i),mx(a.k.b))))&&(e=Math.floor(Math.min(mx(a.k.i),mx(a.k.b))));b>e&&(b=e,wy(a,!0));0>b&&(b=0)}else b=0;a.a.ya=b;a.H=b;c?a.ri():(Hy(a),d&&(a.K||(a.K=new ho(a.ri,d,a)),a.K.start()),Iy(a,!0));a.R&&b>a.a.rf&&(a.bi(a.ua),a.ua=null);a.u("seekto",a,b)}else a.a.ya=b||0}function Hy(a){P(a.i,32)||(oy(a,Ji(a.i,32)),P(a.i,8)&&xy(a),a.u("beginseeking",a))} +f.ri=function(){if(!isNaN(this.H)&&this.b){var a=this.rd();!this.a.Fb&&this.H>=Math.floor(a)?(this.H=a,this.u("endseeking",this),xy(this),this.pg()):(this.k&&Qx(this.k,this.H),yi(this.b,this.H))}this.K&&(this.K.dispose(),this.K=null);P(this.i,32)&&(oy(this,Ii(this.i,this.i.b&-33)),this.u("endseeking",this))};f.getCurrentTime=function(){if(!isNaN(this.H))return this.H;var a=0;this.b?a=this.b.getCurrentTime():this.a&&(a=this.a.ue);return a}; +f.rd=function(){var a=0;this.b&&(a=this.b.Qd);0==a&&this.a&&(a=this.a.wa);return a};function Jy(a,b){var c=isNaN(b)?a.getCurrentTime():b,d=a.rd();return Infinity==d?1:d?c/d:0} +f.pr=function(){var a;if(this.a){a=this.a;var b={};a.a&&(b.fmt=a.a.Sa,a.ra&&(b.afmt=a.ra.Sa));a.S&&(b.threed=a.Jc);b.plid=a.ab;b.ei=a.j;b.list=a.Na;b.cpn=a.Ca;a.L&&(b.v=a.L);a.Lj&&(b.infringe=1);a.Nd&&(b.splay=1);a.Fb&&(b.live=a.bb?"dvr":"live");a.hf&&(b.autoplay=1);a.fa&&(b.sdetail=a.fa);a.qd&&(b.partnerid=a.qd);a=b}else a={};return a};f.Mk=function(a){this.A.ux(a)};f.bi=function(a){this.A.vx(a)}; +f.vu=function(a){this.u("commoninfoloaded",a);ky(this,"dataloaded");qy(this,v(function(){this.U&&ry(this)},this))};f.wu=function(){ky(this)};f.uu=function(a){sw(this,parseInt(a.errorcode,10),unescape(a.reason),a.errordetail)};function ky(a,b){a.u("internalvideodatachange",b||"dataupdated",a,a.a)}function qy(a,b){if(ly(a))H(b,0);else{var c=a.a,d=a.o.Kh;c.B?Cl(c,b,!!d,!!a.o.pd):El(c,b,!!d)}} +function By(a){z("loadstart loadeddata loadedmetadata play playing pause ended suspend progress seeking seeked timeupdate durationchange error waiting abort".split(" "),function(a){this.N.listen(this.b,a,this.bw)},a)} +f.bw=function(a){var b=a.target;if(b.currentSrc){switch(a.type){case "durationchange":this.a.bb||(b.Va(b.duration||0),b=b.Qd,(!this.g||isFinite(b)&&0this.O&&(this.b.currentTime=this.O,this.O=NaN,this.ra=this.N.listen(this.b,"canplay",this.Gt));case "suspend":Iy(this);this.u("onLoadProgress",this,this.b?cg(this.b):0);break;case "seeking":Ky(this);break;case "timeupdate":Ky(this),Iy(this),this.u("onVideoProgress",this,b.getCurrentTime())}this.u("videoelementevent",a);t:if(b=this.i,d=this.oa,P(b,128))a=b;else{var c=b.b,e=b.a,g=a.target;switch(a.type){case "ended":if(0>=g.networkState)break; +c=Li();e=null;break;case "pause":P(b,32)||P(b,2)||(c=4,e=null);break;case "playing":c=8;e=null;break;case "abort":case "error":c|=256;a=g.error;d=107;if(a&&a.code)switch(a.code){case a.MEDIA_ERR_ABORTED:d=200;break;case a.MEDIA_ERR_NETWORK:d=201;break;case a.MEDIA_ERR_DECODE:d=202;break;case a.MEDIA_ERR_SRC_NOT_SUPPORTED:d=203}e={errorCode:d};A(Gi,e.errorCode)&&(c|=128);break;case "canplay":c&=-2;break;case "progress":P(b,8)&&(g=a.target.getCurrentTime(),d&&dy(d,g,a.timeStamp)&&(c|=1));break;case "seeked":c&= +-18;break;case "seeking":c|=16;g.inUnbufferedArea()&&(c|=1);c&=-3;break;case "waiting":P(b,2)||(c|=1);break;case "timeupdate":P(b,16)||(c&=-2);c&=-65;break;default:a=b;break t}a=Ii(b,c,e)}oy(this,a)}}; +f.un=function(){if(this.b&&0a.rd()&&(d=a.rd(),0==d&&(d=c),vy(a,1.2*d));var d=Jy(a),e=0;a.b.buffered&&0a.b.seekable.length?NaN:a.b.seekable.end(a.b.seekable.length-1))/a.rd()):e=a.b?cg(a.b):0);5g&&(a.a.ye+=h-g);a.a.wa&&a.a.K&&a.a.ye>=a.a.K&&(g=a.j,g.i&&g.a.K&&(h=Ly(g,"delayplay"),h.nj=!0,Oy(g,h)),a.a.K=NaN)}if(a.a.nm&&(g=a.a.Oj,h=a.a.ue,1==g&&0ao(c,8)&&wy(a,!1);0a.width||a.heightd&&(d=e);jA(this,b);kA(this,b,d)}}else this.i[a.getId()]=a,kA(this,a,d);fm(this.a);c&&this.$g(a);iA(this)}; +function kA(a,b,c){var d=Bz(b);if(!isNaN(d)){var e={};e.priority=-2!=b.start?2:3;e.id=String(b.getId());e.visible=b.isVisible;c=new nh(d,c,e);a.j[b.getId()]=c;a.a.yf(c)}}function jA(a,b){a.j[b.getId()]&&(a.a.Av(a.j[b.getId()]),delete a.j[b.getId()])}f.$g=function(a){a.W("adBreakComplete",this.tl,this);this.je&&a==this.je&&(this.je=null);jA(this,a);Ua(this.b,a);delete this.i[a.getId()];Nf(a)}; +f.rv=function(a){if(a.Se){if(-1!=a.start&&a.Bl){var b=this.a;lA(b);pm(b)}else Dz(a)&&!a.Bl?(b=this.a,lA(b),pm(b)):(lA(this.a),this.a.jb(!1));this.$g(a)}else a.isVisible||this.$g(a)};f.F=function(){z(this.b,this.$g,this);gA.C.F.call(this)};function mA(a,b){this.g=a;b&&(this.b=b)}function nA(a){var b={};b.allowed=a.g;a.b&&(b.ex_ads=a.b);a.a&&(b.at=a.a);return b};function oA(a,b){this.g=a;this.V=b;this.o=w();this.a=a.ta;this.b={};var c=[];this.a.A&&this.a.b&&c.push("4_2");this.a.H&&(this.a.b&&c.push("1_2"),this.a.i&&c.push("1_2_1"),this.a.ua&&c.push("1_1"),this.a.va&&c.push("1_3"));this.a.j&&c.push("2_2_1");this.a.k&&c.push("2_2");this.a.G&&c.push("2_1");this.a.Oc&&c.push("2_3");this.i=c.join(",");this.k=new mA(this.i,this.a.da);this.b.allowed=this.i;(c=this.a.da)&&(this.b.ex_ads=c)}var pA=[9,10,11,12],qA=[21,30],rA=[1009];oA.prototype.j=0; +function sA(a,b){if(!a.a.Ec)return!1;var c=a.g.J();if(A(c.g,"906335"))return!0;c=b.ad_event;return A(pA,c)||7==c&&(c=b.ad_error,A(qA,c)||32==c&&A(rA,b.error_code))?!1:!0};function tA(a){Nw.call(this,a);this.j={};this.i={at:"0"};this.b=new Gm(this.a);this.g=null;a=this.getVideoData().b.html5_sdk_version;this.k=Sy.getInstance().load(a)}y(tA,Nw);var Qz=new C(0,0);f=tA.prototype;f.na="ad";f.kd="ad";f.xe=null;f.Td=null;f.ta=null;f.bk=!1;f.lk=!1;f.rg=null;f.ni=null;f.Xa=function(){return!!this.app.I.getVideoData().ga}; +f.create=function(){tA.C.create.call(this);this.Z=!0;Fz(this,"i");this.ta=new eA(this.J(),this.a.getVideoData().b||{});this.xe=new oA(this,this.a);Xy();this.bk=!1;var a=Jn(this.k);this.rg=In(a,this.Ms,null,this);t:{var a=new dA(this),b=[];try{var c=a.a.ta,d;if(c.ea){var e;if(c.S)e=Mz(c.S);else{var g;if(c.R){var h=Ja(c.R.split(","),Ca);g=new Lz(h)}else g=new Lz([0]);e=g}d=e}else d=new Lz([0]);for(var k=d.Ls,c=0;ca.start&&(2147483647==a.end||2147483646==a.end)&&(a=this.Td,(b=a.i[b])&&a.je==b&&!b.Se&&b.isVisible&&b.Vk&&b.a instanceof wz&&xz(b.a,google.ima.ViewMode.FULLSCREEN))}; +f.Ms=function(){Fz(this,"sdk");if(this.ni)return this.ni;var a;if(this.ta.ca){a=new Mw(this.app,2);var b=google.ima.AdDisplayContainer,c=Hm(this.b),d=Km(this.b);a.i||(a.i=Jb(a.g),a.i.addEventListener=v(a.bu,a),a.i.removeEventListener=v(a.cu,a));a=new b(c,void 0,d,a.i)}else{a=this.b.V.J().tb?this.b.V.app.P.b:Lm(this.b);if(!a)return a=Error("AdModule.getVideoElement returned an invalid element."),b=new Cn,b.a(a),b;a=new google.ima.AdDisplayContainer(Hm(this.b),a,Km(this.b))}return this.ni=new google.ima.AdsLoader(a)}; +function Fz(a,b){var c=b;"nl"==b&&(c=a.xe,c.j++,c=c.j.toString());a.j[c]=w();"bs"!=c&&"fb"!=c||jm(a,a.j,a.i)}f.oe=function(a){this.u(a)};function uA(a){a&&(this.name=va(a.name),this.lb=a.screenId,this.jc=a.loungeToken,this.Cb=a.dialId||"")}f=uA.prototype;f.name="";f.lb="";f.jc="";f.Cb="";f.oq=function(){return{key:this.lb,name:this.name}};f.toString=function(){var a=this.jc?this.jc.slice(-6):"null";return"{name:"+this.name+",screenId:"+this.lb+",loungeToken:..."+a+",dialId:"+this.Cb+"}"};function vA(a){return a?a.toString():"null"}function wA(a){a=a||[];return"["+Ja(a,function(a){return vA(a)}).join(",")+"]"};function xA(a){a&&(this.id=a.id||"",this.name=va(a.name||""),this.activityId=a.activityId||"",this.status=a.status||"UNKNOWN")}f=xA.prototype;f.id="";f.name="";f.activityId="";f.status="UNKNOWN";f.oq=function(){return{key:this.id,name:this.name}};function yA(a){return{id:a.id,name:a.name,activityId:a.activityId,status:a.status}}f.toString=function(){return"{id:"+this.id+",name:"+this.name+",activityId:"+this.activityId+",status:"+this.status+"}"}; +function zA(a){a=a||[];return"["+Ja(a,function(a){return a?a.toString():"null"}).join(",")+"]"};function AA(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0;return("x"==a?b:b&3|8).toString(16)})}function BA(a,b){return Na(a,function(a){return a.key==b})}function CA(a){return Ja(a,function(a){return a.oq()})}function DA(a){return Ja(a,function(a){return yA(a)})}function EA(a){return Ja(a,function(a){return new xA(a)})}function FA(a,b){return a||b?a&&b?a.id==b.id&&a.name==b.name:!1:!0} +function GA(a,b){return Na(a,function(a){return a.id==b})}function HA(a){return Ja(a,function(a){return{name:a.name,screenId:a.lb,loungeToken:a.jc,dialId:a.Cb}})}function IA(a){return Ja(a,function(a){return new uA(a)})}function JA(a,b){return a||b?a&&b?a.lb==b.lb:!1:!0}function KA(a,b){return a||b?a&&b?a.lb==b.lb&&a.jc==b.jc&&a.name==b.name&&a.Cb==b.Cb:!1:!0}function LA(a,b){return Na(a,function(a){return JA(a,b)})}function MA(a,b){return Na(a,function(a){return b==a.lb||b==a.Cb})};function NA(){var a=OA(),b=PA();A(a,b);QA()&&ib(a,b);a=RA(a);Ra(a)?Uq.remove("remote_sid","/","youtube.com"):(a=a.join(","),Uq.set("remote_sid",a,void 0,"/","youtube.com"))}function OA(){var a=bh("yt-remote-connected-devices")||[];db(a);return a}function RA(a){if(Ra(a))return[];var b=a[0].indexOf("#"),c=-1==b?a[0]:a[0].substring(0,b);return Ja(a,function(a,b){return 0==b?a:a.substring(c.length)})}function SA(a){Zg("yt-remote-connected-devices",a,86400)} +function PA(){if(TA)return TA;var a=bh("yt-remote-device-id");a||(a=AA(),Zg("yt-remote-device-id",a,31536E3));for(var b=OA(),c=1,d=a;A(b,d);)c++,d=a+"#"+c;return TA=d}function UA(){return bh("yt-remote-session-browser-channel")}function QA(){return bh("yt-remote-session-screen-id")} +function VA(a){5b.length){c.push(eB(a)+"(");for(var d=a.arguments,e=0;e'+h.fileName+"\nLine: "+h.lineNumber+"\n\nBrowser stack:\n"+va(h.stack+"-> ")+"[end]\n\nJS stack traversal:\n"+va(cB(g)+"-> ")}catch(M){e="Exception trying to expose exception! You win, we lose. "+M}d.a=e}return d};f.info=function(){};var oB={},pB=null;function qB(){pB||(pB=new iB(""),oB[""]=pB)} +function rB(a){qB();var b;if(!(b=oB[a])){b=new iB(a);var c=a.lastIndexOf("."),d=a.substr(c+1);rB(a.substr(0,c)).getChildren()[d]=b;oB[a]=b}return b};function sB(){this.a=w()}var tB=new sB;sB.prototype.set=function(a){this.a=a};sB.prototype.reset=function(){this.set(w())};sB.prototype.get=function(){return this.a};function uB(a){this.g=a||"";this.i=tB}uB.prototype.b=!0;uB.prototype.a=!1;function vB(a){return 10>a?"0"+a:String(a)}function wB(a,b){var c=(a.i-b)/1E3,d=c.toFixed(3),e=0;if(1>c)e=2;else for(;100>c;)e++,c*=10;for(;0b)break t}else if(3>b||3==b&&!$b&&!RB(this.Qa))break t;this.Ke||4!=b||7==c||(8==c||0>=d?this.b.wc(3):this.b.wc(2));SB(this);var e=QB(this.Qa);this.vg=e;var g=RB(this.Qa);g||this.a.debug("No response text for uri "+this.Jb+" status "+e);this.yc=200==e;this.a.info("XMLHTTP RESP ("+this.g+") [ attempt "+this.i+"]: "+this.Bf+"\n"+this.Jb+"\n"+b+" "+e);this.yc?(4==b&&TB(this),this.G?(UB(this, +b,g),$b&&this.yc&&3==b&&(this.o.listen(this.k,"tick",this.Ft),this.k.start())):(VB(this.a,this.g,g,null),WB(this,g)),this.yc&&!this.Ke&&(4==b?this.b.$h(this):(this.yc=!1,OB(this)))):(this.He=400==e&&0b.length)return KB;var e=b.substr(d,c);a.Og=d+c;return e} +function $B(a,b){a.xg=w();OB(a);var c=b?window.location.hostname:"";a.Jb=a.Xd.clone();De(a.Jb,"DOMAIN",c);De(a.Jb,"t",a.i);try{a.Ac=new ActiveXObject("htmlfile")}catch(d){TB(a);a.He=7;XB();YB(a);return}var e="";b&&(e+='', u'', playlist_snippet) + playlist_cleaned = re.sub(r'(?s).*?', u'', playlist_snippet) + playlist_cleaned = re.sub(r'', r'', playlist_cleaned) + # The ' in the onClick attributes are not escaped, it couldn't be parsed + # with xml.etree.ElementTree.fromstring + # like: http://trailers.apple.com/trailers/wb/gravity/ + def _clean_json(m): + return u'iTunes.playURL(%s);' % m.group(1).replace('\'', ''') + playlist_cleaned = re.sub(self._JSON_RE, _clean_json, playlist_cleaned) playlist_html = u'' + playlist_cleaned + u'' - size_cache = {} - doc = xml.etree.ElementTree.fromstring(playlist_html) playlist = [] for li in doc.findall('./div/ul/li'): - title = li.find('.//h3').text + on_click = li.find('.//a').attrib['onClick'] + trailer_info_json = self._search_regex(self._JSON_RE, + on_click, u'trailer info') + trailer_info = json.loads(trailer_info_json) + title = trailer_info['title'] video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower() thumbnail = li.find('.//img').attrib['src'] + upload_date = trailer_info['posted'].replace('-', '') - date_el = li.find('.//p') - upload_date = None - m = re.search(r':\s?(?P[0-9]{2})/(?P[0-9]{2})/(?P[0-9]{2})', date_el.text) - if m: - upload_date = u'20' + m.group('year') + m.group('month') + m.group('day') - runtime_el = date_el.find('./br') - m = re.search(r':\s?(?P[0-9]+):(?P[0-9]{1,2})', runtime_el.tail) + runtime = trailer_info['runtime'] + m = re.search(r'(?P[0-9]+):(?P[0-9]{1,2})', runtime) duration = None if m: duration = 60 * int(m.group('minutes')) + int(m.group('seconds')) - formats = [] - for formats_el in li.findall('.//a'): - if formats_el.attrib['class'] != 'OverlayPanel': - continue - target = formats_el.attrib['target'] - - format_code = formats_el.text - if 'Automatic' in format_code: - continue + first_url = trailer_info['url'] + trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower() + settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id) + settings_json = self._download_webpage(settings_json_url, trailer_id, u'Downloading settings json') + settings = json.loads(settings_json) - size_q = formats_el.attrib['href'] - size_id = size_q.rpartition('#videos-')[2] - if size_id not in size_cache: - size_url = url + size_q - sizepage_html = self._download_webpage( - size_url, movie, - note=u'Downloading size info %s' % size_id, - errnote=u'Error while downloading size info %s' % size_id, - ) - _doc = xml.etree.ElementTree.fromstring(sizepage_html) - size_cache[size_id] = _doc - - sizepage_doc = size_cache[size_id] - links = sizepage_doc.findall('.//{http://www.w3.org/1999/xhtml}ul/{http://www.w3.org/1999/xhtml}li/{http://www.w3.org/1999/xhtml}a') - for vid_a in links: - href = vid_a.get('href') - if not href.endswith(target): - continue - detail_q = href.partition('#')[0] - detail_url = url + '/' + detail_q - - m = re.match(r'includes/(?P[^/]+)/', detail_q) - detail_id = m.group('detail_id') - - detail_html = self._download_webpage( - detail_url, movie, - note=u'Downloading detail %s %s' % (detail_id, size_id), - errnote=u'Error while downloading detail %s %s' % (detail_id, size_id) - ) - detail_doc = xml.etree.ElementTree.fromstring(detail_html) - movie_link_el = detail_doc.find('.//{http://www.w3.org/1999/xhtml}a') - assert movie_link_el.get('class') == 'movieLink' - movie_link = movie_link_el.get('href').partition('?')[0].replace('_', '_h') - ext = determine_ext(movie_link) - assert ext == 'mov' - - formats.append({ - 'format': format_code, - 'ext': ext, - 'url': movie_link, - }) + formats = [] + for format in settings['metadata']['sizes']: + # The src is a file pointing to the real video file + format_url = re.sub(r'_(\d*p.mov)', r'_h\1', format['src']) + formats.append({ + 'url': format_url, + 'ext': determine_ext(format_url), + 'format': format['type'], + 'width': format['width'], + 'height': int(format['height']), + }) + formats = sorted(formats, key=lambda f: (f['height'], f['width'])) info = { '_type': 'video', diff --git a/youtube_dl/extractor/archiveorg.py b/youtube_dl/extractor/archiveorg.py index 7efd1d8..61ce446 100644 --- a/youtube_dl/extractor/archiveorg.py +++ b/youtube_dl/extractor/archiveorg.py @@ -46,6 +46,8 @@ class ArchiveOrgIE(InfoExtractor): for fn,fdata in data['files'].items() if 'Video' in fdata['format']] formats.sort(key=lambda fdata: fdata['file_size']) + for f in formats: + f['ext'] = determine_ext(f['url']) info = { '_type': 'video', @@ -61,7 +63,6 @@ class ArchiveOrgIE(InfoExtractor): info['thumbnail'] = thumbnail # TODO: Remove when #980 has been merged - info['url'] = formats[-1]['url'] - info['ext'] = determine_ext(formats[-1]['url']) + info.update(formats[-1]) - return info \ No newline at end of file + return info diff --git a/youtube_dl/extractor/bloomberg.py b/youtube_dl/extractor/bloomberg.py new file mode 100644 index 0000000..3666a78 --- /dev/null +++ b/youtube_dl/extractor/bloomberg.py @@ -0,0 +1,27 @@ +import re + +from .common import InfoExtractor + + +class BloombergIE(InfoExtractor): + _VALID_URL = r'https?://www\.bloomberg\.com/video/(?P.+?).html' + + _TEST = { + u'url': u'http://www.bloomberg.com/video/shah-s-presentation-on-foreign-exchange-strategies-qurhIVlJSB6hzkVi229d8g.html', + u'file': u'12bzhqZTqQHmmlA8I-i0NpzJgcG5NNYX.mp4', + u'info_dict': { + u'title': u'Shah\'s Presentation on Foreign-Exchange Strategies', + u'description': u'md5:abc86e5236f9f0e4866c59ad36736686', + }, + u'params': { + # Requires ffmpeg (m3u8 manifest) + u'skip_download': True, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + name = mobj.group('name') + webpage = self._download_webpage(url, name) + ooyala_url = self._og_search_video_url(webpage) + return self.url_result(ooyala_url, ie='Ooyala') diff --git a/youtube_dl/extractor/brightcove.py b/youtube_dl/extractor/brightcove.py index 71e3c78..558b3d0 100644 --- a/youtube_dl/extractor/brightcove.py +++ b/youtube_dl/extractor/brightcove.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import re import json import xml.etree.ElementTree @@ -7,15 +9,39 @@ from ..utils import ( compat_urllib_parse, find_xpath_attr, compat_urlparse, + + ExtractorError, ) class BrightcoveIE(InfoExtractor): _VALID_URL = r'https?://.*brightcove\.com/(services|viewer).*\?(?P.*)' _FEDERATED_URL_TEMPLATE = 'http://c.brightcove.com/services/viewer/htmlFederated?%s' _PLAYLIST_URL_TEMPLATE = 'http://c.brightcove.com/services/json/experience/runtime/?command=get_programming_for_experience&playerKey=%s' - - # There is a test for Brigtcove in GenericIE, that way we test both the download - # and the detection of videos, and we don't have to find an URL that is always valid + + _TESTS = [ + { + # From http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/ + u'url': u'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1654948606001&flashID=myExperience&%40videoPlayer=2371591881001', + u'file': u'2371591881001.mp4', + u'md5': u'9e80619e0a94663f0bdc849b4566af19', + u'note': u'Test Brightcove downloads and detection in GenericIE', + u'info_dict': { + u'title': u'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”', + u'uploader': u'8TV', + u'description': u'md5:a950cc4285c43e44d763d036710cd9cd', + } + }, + { + # From http://medianetwork.oracle.com/video/player/1785452137001 + u'url': u'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1217746023001&flashID=myPlayer&%40videoPlayer=1785452137001', + u'file': u'1785452137001.flv', + u'info_dict': { + u'title': u'JVMLS 2012: Arrays 2.0 - Opportunities and Challenges', + u'description': u'John Rose speaks at the JVM Language Summit, August 1, 2012.', + u'uploader': u'Oracle', + }, + }, + ] @classmethod def _build_brighcove_url(cls, object_str): @@ -72,15 +98,27 @@ class BrightcoveIE(InfoExtractor): playlist_title=playlist_info['mediaCollectionDTO']['displayName']) def _extract_video_info(self, video_info): - renditions = video_info['renditions'] - renditions = sorted(renditions, key=lambda r: r['size']) - best_format = renditions[-1] + info = { + 'id': video_info['id'], + 'title': video_info['displayName'], + 'description': video_info.get('shortDescription'), + 'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'), + 'uploader': video_info.get('publisherName'), + } - return {'id': video_info['id'], - 'title': video_info['displayName'], - 'url': best_format['defaultURL'], + renditions = video_info.get('renditions') + if renditions: + renditions = sorted(renditions, key=lambda r: r['size']) + best_format = renditions[-1] + info.update({ + 'url': best_format['defaultURL'], 'ext': 'mp4', - 'description': video_info.get('shortDescription'), - 'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'), - 'uploader': video_info.get('publisherName'), - } + }) + elif video_info.get('FLVFullLengthURL') is not None: + info.update({ + 'url': video_info['FLVFullLengthURL'], + 'ext': 'flv', + }) + else: + raise ExtractorError(u'Unable to extract video url for %s' % info['id']) + return info diff --git a/youtube_dl/extractor/canalc2.py b/youtube_dl/extractor/canalc2.py index 5083221..e7f4fa9 100644 --- a/youtube_dl/extractor/canalc2.py +++ b/youtube_dl/extractor/canalc2.py @@ -5,7 +5,7 @@ from .common import InfoExtractor class Canalc2IE(InfoExtractor): - _IE_NAME = 'canalc2.tv' + IE_NAME = 'canalc2.tv' _VALID_URL = r'http://.*?\.canalc2\.tv/video\.asp\?idVideo=(\d+)&voir=oui' _TEST = { diff --git a/youtube_dl/extractor/canalplus.py b/youtube_dl/extractor/canalplus.py index 1f02519..1db9b24 100644 --- a/youtube_dl/extractor/canalplus.py +++ b/youtube_dl/extractor/canalplus.py @@ -1,3 +1,4 @@ +# encoding: utf-8 import re import xml.etree.ElementTree @@ -5,24 +6,29 @@ from .common import InfoExtractor from ..utils import unified_strdate class CanalplusIE(InfoExtractor): - _VALID_URL = r'https?://(www\.canalplus\.fr/.*?\?vid=|player\.canalplus\.fr/#/)(?P\d+)' + _VALID_URL = r'https?://(www\.canalplus\.fr/.*?/(?P.*)|player\.canalplus\.fr/#/(?P\d+))' _VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s' IE_NAME = u'canalplus.fr' _TEST = { - u'url': u'http://www.canalplus.fr/c-divertissement/pid3351-c-le-petit-journal.html?vid=889861', - u'file': u'889861.flv', - u'md5': u'590a888158b5f0d6832f84001fbf3e99', + u'url': u'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470', + u'file': u'922470.flv', u'info_dict': { - u'title': u'Le Petit Journal 20/06/13 - La guerre des drone', - u'upload_date': u'20130620', + u'title': u'Zapping - 26/08/13', + u'description': u'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013', + u'upload_date': u'20130826', + }, + u'params': { + u'skip_download': True, }, - u'skip': u'Requires rtmpdump' } def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') + if video_id is None: + webpage = self._download_webpage(url, mobj.group('path')) + video_id = self._search_regex(r'videoId = "(\d+)";', webpage, u'video id') info_url = self._VIDEO_INFO_TEMPLATE % video_id info_page = self._download_webpage(info_url,video_id, u'Downloading video info') @@ -43,4 +49,6 @@ class CanalplusIE(InfoExtractor): 'ext': 'flv', 'upload_date': unified_strdate(infos.find('PUBLICATION/DATE').text), 'thumbnail': media.find('IMAGES/GRAND').text, + 'description': infos.find('DESCRIPTION').text, + 'view_count': int(infos.find('NB_VUES').text), } diff --git a/youtube_dl/extractor/dailymotion.py b/youtube_dl/extractor/dailymotion.py index 1ea449c..3f012ae 100644 --- a/youtube_dl/extractor/dailymotion.py +++ b/youtube_dl/extractor/dailymotion.py @@ -3,18 +3,29 @@ import json import itertools from .common import InfoExtractor +from .subtitles import SubtitlesInfoExtractor + from ..utils import ( compat_urllib_request, + compat_str, get_element_by_attribute, get_element_by_id, ExtractorError, ) -class DailymotionIE(InfoExtractor): +class DailymotionBaseInfoExtractor(InfoExtractor): + @staticmethod + def _build_request(url): + """Build a request with the family filter disabled""" + request = compat_urllib_request.Request(url) + request.add_header('Cookie', 'family_filter=off') + return request + +class DailymotionIE(DailymotionBaseInfoExtractor, SubtitlesInfoExtractor): """Information Extractor for Dailymotion""" - _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^/]+)' + _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/(?:embed/)?video/([^/]+)' IE_NAME = u'dailymotion' _TEST = { u'url': u'http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech', @@ -33,10 +44,10 @@ class DailymotionIE(InfoExtractor): video_id = mobj.group(1).split('_')[0].split('?')[0] video_extension = 'mp4' + url = 'http://www.dailymotion.com/video/%s' % video_id # Retrieve video webpage to extract further information - request = compat_urllib_request.Request(url) - request.add_header('Cookie', 'family_filter=off') + request = self._build_request(url) webpage = self._download_webpage(request, video_id) # Extract URL, uploader and title from webpage @@ -55,8 +66,12 @@ class DailymotionIE(InfoExtractor): embed_url = 'http://www.dailymotion.com/embed/video/%s' % video_id embed_page = self._download_webpage(embed_url, video_id, u'Downloading embed page') - info = self._search_regex(r'var info = ({.*?}),', embed_page, 'video info') + info = self._search_regex(r'var info = ({.*?}),$', embed_page, + 'video info', flags=re.MULTILINE) info = json.loads(info) + if info.get('error') is not None: + msg = 'Couldn\'t get video, Dailymotion says: %s' % info['error']['title'] + raise ExtractorError(msg, expected=True) # TODO: support choosing qualities @@ -71,6 +86,12 @@ class DailymotionIE(InfoExtractor): raise ExtractorError(u'Unable to extract video URL') video_url = info[max_quality] + # subtitles + video_subtitles = self.extract_subtitles(video_id) + if self._downloader.params.get('listsubtitles', False): + self._list_available_subtitles(video_id) + return + return [{ 'id': video_id, 'url': video_url, @@ -78,33 +99,76 @@ class DailymotionIE(InfoExtractor): 'upload_date': video_upload_date, 'title': self._og_search_title(webpage), 'ext': video_extension, + 'subtitles': video_subtitles, 'thumbnail': info['thumbnail_url'] }] - -class DailymotionPlaylistIE(InfoExtractor): + def _get_available_subtitles(self, video_id): + try: + sub_list = self._download_webpage( + 'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id, + video_id, note=False) + except ExtractorError as err: + self._downloader.report_warning(u'unable to download video subtitles: %s' % compat_str(err)) + return {} + info = json.loads(sub_list) + if (info['total'] > 0): + sub_lang_list = dict((l['language'], l['url']) for l in info['list']) + return sub_lang_list + self._downloader.report_warning(u'video doesn\'t have subtitles') + return {} + + +class DailymotionPlaylistIE(DailymotionBaseInfoExtractor): + IE_NAME = u'dailymotion:playlist' _VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/playlist/(?P.+?)/' _MORE_PAGES_INDICATOR = r'' + _PAGE_TEMPLATE = 'https://www.dailymotion.com/playlist/%s/%s' - def _real_extract(self, url): - mobj = re.match(self._VALID_URL, url) - playlist_id = mobj.group('id') + def _extract_entries(self, id): video_ids = [] - for pagenum in itertools.count(1): - webpage = self._download_webpage('https://www.dailymotion.com/playlist/%s/%s' % (playlist_id, pagenum), - playlist_id, u'Downloading page %s' % pagenum) + request = self._build_request(self._PAGE_TEMPLATE % (id, pagenum)) + webpage = self._download_webpage(request, + id, u'Downloading page %s' % pagenum) playlist_el = get_element_by_attribute(u'class', u'video_list', webpage) video_ids.extend(re.findall(r'data-id="(.+?)" data-ext-id', playlist_el)) if re.search(self._MORE_PAGES_INDICATOR, webpage, re.DOTALL) is None: break - - entries = [self.url_result('http://www.dailymotion.com/video/%s' % video_id, 'Dailymotion') + return [self.url_result('http://www.dailymotion.com/video/%s' % video_id, 'Dailymotion') for video_id in video_ids] + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + playlist_id = mobj.group('id') + webpage = self._download_webpage(url, playlist_id) + return {'_type': 'playlist', 'id': playlist_id, 'title': get_element_by_id(u'playlist_name', webpage), - 'entries': entries, + 'entries': self._extract_entries(playlist_id), } + + +class DailymotionUserIE(DailymotionPlaylistIE): + IE_NAME = u'dailymotion:user' + _VALID_URL = r'(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/user/(?P[^/]+)' + _MORE_PAGES_INDICATOR = r'' + _PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s' + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + user = mobj.group('user') + webpage = self._download_webpage(url, user) + full_user = self._html_search_regex( + r'(.*?)\d+)' + IE_NAME = u'daum.net' + + _TEST = { + u'url': u'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690', + u'file': u'52554690.mp4', + u'info_dict': { + u'title': u'DOTA 2GETHER 시즌2 6회 - 2부', + u'description': u'DOTA 2GETHER 시즌2 6회 - 2부', + u'upload_date': u'20130831', + u'duration': 3868, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group(1) + canonical_url = 'http://tvpot.daum.net/v/%s' % video_id + webpage = self._download_webpage(canonical_url, video_id) + full_id = self._search_regex(r'\d+)' + + _TEST = { + u'url': u'http://www.ebaumsworld.com/video/watch/83367677/', + u'file': u'83367677.mp4', + u'info_dict': { + u'title': u'A Giant Python Opens The Door', + u'description': u'This is how nightmares start...', + u'uploader': u'jihadpizza', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + config_xml = self._download_webpage( + 'http://www.ebaumsworld.com/video/player/%s' % video_id, video_id) + config = xml.etree.ElementTree.fromstring(config_xml.encode('utf-8')) + video_url = config.find('file').text + + return { + 'id': video_id, + 'title': config.find('title').text, + 'url': video_url, + 'ext': determine_ext(video_url), + 'description': config.find('description').text, + 'thumbnail': config.find('image').text, + 'uploader': config.find('username').text, + } diff --git a/youtube_dl/extractor/facebook.py b/youtube_dl/extractor/facebook.py index beaa5b4..9d1bc07 100644 --- a/youtube_dl/extractor/facebook.py +++ b/youtube_dl/extractor/facebook.py @@ -106,8 +106,8 @@ class FacebookIE(InfoExtractor): video_duration = int(video_data['video_duration']) thumbnail = video_data['thumbnail_src'] - video_title = self._html_search_regex('

    ([^<]+)

    ', - webpage, u'title') + video_title = self._html_search_regex( + r'

    ([^<]*)

    ', webpage, u'title') info = { 'id': video_id, diff --git a/youtube_dl/extractor/fktv.py b/youtube_dl/extractor/fktv.py new file mode 100644 index 0000000..9c89362 --- /dev/null +++ b/youtube_dl/extractor/fktv.py @@ -0,0 +1,79 @@ +import re +import random +import json + +from .common import InfoExtractor +from ..utils import ( + determine_ext, + get_element_by_id, + clean_html, +) + + +class FKTVIE(InfoExtractor): + IE_NAME = u'fernsehkritik.tv' + _VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik.tv/folge-(?P[0-9]+)(?:/.*)?' + + _TEST = { + u'url': u'http://fernsehkritik.tv/folge-1', + u'file': u'00011.flv', + u'info_dict': { + u'title': u'Folge 1 vom 10. April 2007', + u'description': u'md5:fb4818139c7cfe6907d4b83412a6864f', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + episode = int(mobj.group('ep')) + + server = random.randint(2, 4) + video_thumbnail = 'http://fernsehkritik.tv/images/magazin/folge%d.jpg' % episode + start_webpage = self._download_webpage('http://fernsehkritik.tv/folge-%d/Start' % episode, + episode) + playlist = self._search_regex(r'playlist = (\[.*?\]);', start_webpage, + u'playlist', flags=re.DOTALL) + files = json.loads(re.sub('{[^{}]*?}', '{}', playlist)) + # TODO: return a single multipart video + videos = [] + for i, _ in enumerate(files, 1): + video_id = '%04d%d' % (episode, i) + video_url = 'http://dl%d.fernsehkritik.tv/fernsehkritik%d%s.flv' % (server, episode, '' if i == 1 else '-%d' % i) + video_title = 'Fernsehkritik %d.%d' % (episode, i) + videos.append({ + 'id': video_id, + 'url': video_url, + 'ext': determine_ext(video_url), + 'title': clean_html(get_element_by_id('eptitle', start_webpage)), + 'description': clean_html(get_element_by_id('contentlist', start_webpage)), + 'thumbnail': video_thumbnail + }) + return videos + + +class FKTVPosteckeIE(InfoExtractor): + IE_NAME = u'fernsehkritik.tv:postecke' + _VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik.tv/inline-video/postecke.php\?(.*&)?ep=(?P[0-9]+)(&|$)' + _TEST = { + u'url': u'http://fernsehkritik.tv/inline-video/postecke.php?iframe=true&width=625&height=440&ep=120', + u'file': u'0120.flv', + u'md5': u'262f0adbac80317412f7e57b4808e5c4', + u'info_dict': { + u"title": u"Postecke 120" + } + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + episode = int(mobj.group('ep')) + + server = random.randint(2, 4) + video_id = '%04d' % episode + video_url = 'http://dl%d.fernsehkritik.tv/postecke/postecke%d.flv' % (server, episode) + video_title = 'Postecke %d' % episode + return { + 'id': video_id, + 'url': video_url, + 'ext': determine_ext(video_url), + 'title': video_title, + } diff --git a/youtube_dl/extractor/francetv.py b/youtube_dl/extractor/francetv.py new file mode 100644 index 0000000..b1530e5 --- /dev/null +++ b/youtube_dl/extractor/francetv.py @@ -0,0 +1,117 @@ +# encoding: utf-8 +import re +import xml.etree.ElementTree +import json + +from .common import InfoExtractor +from ..utils import ( + compat_urlparse, +) + + +class FranceTVBaseInfoExtractor(InfoExtractor): + def _extract_video(self, video_id): + xml_desc = self._download_webpage( + 'http://www.francetvinfo.fr/appftv/webservices/video/' + 'getInfosOeuvre.php?id-diffusion=' + + video_id, video_id, 'Downloading XML config') + info = xml.etree.ElementTree.fromstring(xml_desc.encode('utf-8')) + + manifest_url = info.find('videos/video/url').text + video_url = manifest_url.replace('manifest.f4m', 'index_2_av.m3u8') + video_url = video_url.replace('/z/', '/i/') + thumbnail_path = info.find('image').text + + return {'id': video_id, + 'ext': 'mp4', + 'url': video_url, + 'title': info.find('titre').text, + 'thumbnail': compat_urlparse.urljoin('http://pluzz.francetv.fr', thumbnail_path), + 'description': info.find('synopsis').text, + } + + +class PluzzIE(FranceTVBaseInfoExtractor): + IE_NAME = u'pluzz.francetv.fr' + _VALID_URL = r'https?://pluzz\.francetv\.fr/videos/(.*?)\.html' + + # Can't use tests, videos expire in 7 days + + def _real_extract(self, url): + title = re.match(self._VALID_URL, url).group(1) + webpage = self._download_webpage(url, title) + video_id = self._search_regex( + r'data-diffusion="(\d+)"', webpage, 'ID') + return self._extract_video(video_id) + + +class FranceTvInfoIE(FranceTVBaseInfoExtractor): + IE_NAME = u'francetvinfo.fr' + _VALID_URL = r'https?://www\.francetvinfo\.fr/replay.*/(?P.+).html' + + _TEST = { + u'url': u'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html', + u'file': u'84981923.mp4', + u'info_dict': { + u'title': u'Soir 3', + }, + u'params': { + u'skip_download': True, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + page_title = mobj.group('title') + webpage = self._download_webpage(url, page_title) + video_id = self._search_regex(r'id-video=(\d+?)"', webpage, u'video id') + return self._extract_video(video_id) + + +class France2IE(FranceTVBaseInfoExtractor): + IE_NAME = u'france2.fr' + _VALID_URL = r'https?://www\.france2\.fr/emissions/.*?/videos/(?P<id>\d+)' + + _TEST = { + u'url': u'http://www.france2.fr/emissions/13h15-le-samedi-le-dimanche/videos/75540104', + u'file': u'75540104.mp4', + u'info_dict': { + u'title': u'13h15, le samedi...', + u'description': u'md5:2e5b58ba7a2d3692b35c792be081a03d', + }, + u'params': { + u'skip_download': True, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + return self._extract_video(video_id) + + +class GenerationQuoiIE(InfoExtractor): + IE_NAME = u'http://generation-quoi.france2.fr' + _VALID_URL = r'https?://generation-quoi\.france2\.fr/portrait/(?P<name>.*)(\?|$)' + + _TEST = { + u'url': u'http://generation-quoi.france2.fr/portrait/garde-a-vous', + u'file': u'k7FJX8VBcvvLmX4wA5Q.mp4', + u'info_dict': { + u'title': u'Génération Quoi - Garde à Vous', + u'uploader': u'Génération Quoi', + }, + u'params': { + # It uses Dailymotion + u'skip_download': True, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + name = mobj.group('name') + info_url = compat_urlparse.urljoin(url, '/medias/video/%s.json' % name) + info_json = self._download_webpage(info_url, name) + info = json.loads(info_json) + return self.url_result('http://www.dailymotion.com/video/%s' % info['id'], + ie='Dailymotion') diff --git a/youtube_dl/extractor/funnyordie.py b/youtube_dl/extractor/funnyordie.py index 4508f0d..2ccdb70 100644 --- a/youtube_dl/extractor/funnyordie.py +++ b/youtube_dl/extractor/funnyordie.py @@ -21,7 +21,8 @@ class FunnyOrDieIE(InfoExtractor): video_id = mobj.group('id') webpage = self._download_webpage(url, video_id) - video_url = self._search_regex(r'type: "video/mp4", src: "(.*?)"', + video_url = self._search_regex( + [r'type="video/mp4" src="(.*?)"', r'src="([^>]*?)" type=\'video/mp4\''], webpage, u'video URL', flags=re.DOTALL) info = { diff --git a/youtube_dl/extractor/gamespot.py b/youtube_dl/extractor/gamespot.py index 7585b70..cd3bbe6 100644 --- a/youtube_dl/extractor/gamespot.py +++ b/youtube_dl/extractor/gamespot.py @@ -14,7 +14,7 @@ class GameSpotIE(InfoExtractor): u"file": u"6410818.mp4", u"md5": u"b2a30deaa8654fcccd43713a6b6a4825", u"info_dict": { - u"title": u"Arma III - Community Guide: SITREP I", + u"title": u"Arma 3 - Community Guide: SITREP I", u"upload_date": u"20130627", } } diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index dc4dea4..7640706 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -29,17 +29,6 @@ class GenericIE(InfoExtractor): u"title": u"R\u00e9gis plante sa Jeep" } }, - { - u'url': u'http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/', - u'file': u'2371591881001.mp4', - u'md5': u'9e80619e0a94663f0bdc849b4566af19', - u'note': u'Test Brightcove downloads and detection in GenericIE', - u'info_dict': { - u'title': u'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”', - u'uploader': u'8TV', - u'description': u'md5:a950cc4285c43e44d763d036710cd9cd', - } - }, ] def report_download_webpage(self, video_id): @@ -109,6 +98,11 @@ class GenericIE(InfoExtractor): return new_url def _real_extract(self, url): + parsed_url = compat_urlparse.urlparse(url) + if not parsed_url.scheme: + self._downloader.report_warning('The url doesn\'t specify the protocol, trying with http') + return self.url_result('http://' + url) + try: new_url = self._test_redirect(url) if new_url: @@ -153,7 +147,7 @@ class GenericIE(InfoExtractor): mobj = re.search(r'<meta.*?property="og:video".*?content="(.*?)"', webpage) if mobj is None: # HTML5 video - mobj = re.search(r'<video[^<]*>.*?<source .*?src="([^"]+)"', webpage, flags=re.DOTALL) + mobj = re.search(r'<video[^<]*(?:>.*?<source.*?)? src="([^"]+)"', webpage, flags=re.DOTALL) if mobj is None: raise ExtractorError(u'Invalid URL: %s' % url) @@ -162,9 +156,9 @@ class GenericIE(InfoExtractor): if mobj.group(1) is None: raise ExtractorError(u'Invalid URL: %s' % url) - video_url = compat_urllib_parse.unquote(mobj.group(1)) + video_url = mobj.group(1) video_url = compat_urlparse.urljoin(url, video_url) - video_id = os.path.basename(video_url) + video_id = compat_urllib_parse.unquote(os.path.basename(video_url)) # here's a fun little line of code for you: video_extension = os.path.splitext(video_id)[1][1:] diff --git a/youtube_dl/extractor/googleplus.py b/youtube_dl/extractor/googleplus.py index f1cd889..8895ad2 100644 --- a/youtube_dl/extractor/googleplus.py +++ b/youtube_dl/extractor/googleplus.py @@ -40,7 +40,8 @@ class GooglePlusIE(InfoExtractor): self.report_extraction(video_id) # Extract update date - upload_date = self._html_search_regex('title="Timestamp">(.*?)</a>', + upload_date = self._html_search_regex( + ['title="Timestamp">(.*?)</a>', r'<a.+?class="g-M.+?>(.+?)</a>'], webpage, u'upload date', fatal=False) if upload_date: # Convert timestring to a format suitable for filename diff --git a/youtube_dl/extractor/hotnewhiphop.py b/youtube_dl/extractor/hotnewhiphop.py index ccca1d7..3798118 100644 --- a/youtube_dl/extractor/hotnewhiphop.py +++ b/youtube_dl/extractor/hotnewhiphop.py @@ -7,11 +7,11 @@ from .common import InfoExtractor class HotNewHipHopIE(InfoExtractor): _VALID_URL = r'http://www\.hotnewhiphop.com/.*\.(?P<id>.*)\.html' _TEST = { - u'url': u"http://www.hotnewhiphop.com/freddie-gibbs-lay-it-down-song.1435540.html'", + u'url': u"http://www.hotnewhiphop.com/freddie-gibbs-lay-it-down-song.1435540.html", u'file': u'1435540.mp3', u'md5': u'2c2cd2f76ef11a9b3b581e8b232f3d96', u'info_dict': { - u"title": u"Freddie Gibbs Songs - Lay It Down" + u"title": u"Freddie Gibbs - Lay It Down" } } diff --git a/youtube_dl/extractor/howcast.py b/youtube_dl/extractor/howcast.py index 6104c4b..4695433 100644 --- a/youtube_dl/extractor/howcast.py +++ b/youtube_dl/extractor/howcast.py @@ -19,8 +19,7 @@ class HowcastIE(InfoExtractor): mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') - webpage_url = 'http://www.howcast.com/videos/' + video_id - webpage = self._download_webpage(webpage_url, video_id) + webpage = self._download_webpage(url, video_id) self.report_extraction(video_id) diff --git a/youtube_dl/extractor/kickstarter.py b/youtube_dl/extractor/kickstarter.py new file mode 100644 index 0000000..50bc883 --- /dev/null +++ b/youtube_dl/extractor/kickstarter.py @@ -0,0 +1,37 @@ +import re + +from .common import InfoExtractor + + +class KickStarterIE(InfoExtractor): + _VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>\d*)/.*' + _TEST = { + u"url": u"https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location", + u"file": u"1404461844.mp4", + u"md5": u"c81addca81327ffa66c642b5d8b08cab", + u"info_dict": { + u"title": u"Intersection: The Story of Josh Grant by Kyle Cowling", + }, + } + + def _real_extract(self, url): + m = re.match(self._VALID_URL, url) + video_id = m.group('id') + webpage_src = self._download_webpage(url, video_id) + + video_url = self._search_regex(r'data-video="(.*?)">', + webpage_src, u'video URL') + if 'mp4' in video_url: + ext = 'mp4' + else: + ext = 'flv' + video_title = self._html_search_regex(r"<title>(.*?)", + webpage_src, u'title').rpartition(u'\u2014 Kickstarter')[0].strip() + + results = [{ + 'id': video_id, + 'url': video_url, + 'title': video_title, + 'ext': ext, + }] + return results diff --git a/youtube_dl/extractor/livestream.py b/youtube_dl/extractor/livestream.py index 3099210..d04da98 100644 --- a/youtube_dl/extractor/livestream.py +++ b/youtube_dl/extractor/livestream.py @@ -2,7 +2,12 @@ import re import json from .common import InfoExtractor -from ..utils import compat_urllib_parse_urlparse, compat_urlparse +from ..utils import ( + compat_urllib_parse_urlparse, + compat_urlparse, + get_meta_content, + ExtractorError, +) class LivestreamIE(InfoExtractor): @@ -35,8 +40,11 @@ class LivestreamIE(InfoExtractor): if video_id is None: # This is an event page: - api_url = self._search_regex(r'event_design_eventId: \'(.+?)\'', - webpage, 'api url') + player = get_meta_content('twitter:player', webpage) + if player is None: + raise ExtractorError('Couldn\'t extract event api url') + api_url = player.replace('/player', '') + api_url = re.sub(r'^(https?://)(new\.)', r'\1api.\2', api_url) info = json.loads(self._download_webpage(api_url, event_name, u'Downloading event info')) videos = [self._extract_video_info(video_data['data']) diff --git a/youtube_dl/extractor/metacafe.py b/youtube_dl/extractor/metacafe.py index e38dc98..e537648 100644 --- a/youtube_dl/extractor/metacafe.py +++ b/youtube_dl/extractor/metacafe.py @@ -122,7 +122,7 @@ class MetacafeIE(InfoExtractor): video_title = self._html_search_regex(r'(?im)(.*) - Video', webpage, u'title') description = self._og_search_description(webpage) video_uploader = self._html_search_regex( - r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("channel","([^"]+)"\);', + r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);', webpage, u'uploader nickname', fatal=False) return { diff --git a/youtube_dl/extractor/metacritic.py b/youtube_dl/extractor/metacritic.py new file mode 100644 index 0000000..449138b --- /dev/null +++ b/youtube_dl/extractor/metacritic.py @@ -0,0 +1,55 @@ +import re +import xml.etree.ElementTree +import operator + +from .common import InfoExtractor + + +class MetacriticIE(InfoExtractor): + _VALID_URL = r'https?://www\.metacritic\.com/.+?/trailers/(?P\d+)' + + _TEST = { + u'url': u'http://www.metacritic.com/game/playstation-4/infamous-second-son/trailers/3698222', + u'file': u'3698222.mp4', + u'info_dict': { + u'title': u'inFamous: Second Son - inSide Sucker Punch: Smoke & Mirrors', + u'description': u'Take a peak behind-the-scenes to see how Sucker Punch brings smoke into the universe of inFAMOUS Second Son on the PS4.', + u'duration': 221, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + webpage = self._download_webpage(url, video_id) + # The xml is not well formatted, there are raw '&' + info_xml = self._download_webpage('http://www.metacritic.com/video_data?video=' + video_id, + video_id, u'Downloading info xml').replace('&', '&') + info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) + + clip = next(c for c in info.findall('playList/clip') if c.find('id').text == video_id) + formats = [] + for videoFile in clip.findall('httpURI/videoFile'): + rate_str = videoFile.find('rate').text + video_url = videoFile.find('filePath').text + formats.append({ + 'url': video_url, + 'ext': 'mp4', + 'format_id': rate_str, + 'rate': int(rate_str), + }) + formats.sort(key=operator.itemgetter('rate')) + + description = self._html_search_regex(r'Description:(.*?)

    ', + webpage, u'description', flags=re.DOTALL) + + info = { + 'id': video_id, + 'title': clip.find('title').text, + 'formats': formats, + 'description': description, + 'duration': int(clip.find('duration').text), + } + # TODO: Remove when #980 has been merged + info.update(formats[-1]) + return info diff --git a/youtube_dl/extractor/mixcloud.py b/youtube_dl/extractor/mixcloud.py index 8245b55..a200dcd 100644 --- a/youtube_dl/extractor/mixcloud.py +++ b/youtube_dl/extractor/mixcloud.py @@ -5,34 +5,27 @@ import socket from .common import InfoExtractor from ..utils import ( compat_http_client, - compat_str, compat_urllib_error, compat_urllib_request, - - ExtractorError, + unified_strdate, ) class MixcloudIE(InfoExtractor): - _WORKING = False # New API, but it seems good http://www.mixcloud.com/developers/documentation/ _VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([\w\d-]+)/([\w\d-]+)' IE_NAME = u'mixcloud' - def report_download_json(self, file_id): - """Report JSON download.""" - self.to_screen(u'Downloading json') - - def get_urls(self, jsonData, fmt, bitrate='best'): - """Get urls from 'audio_formats' section in json""" - try: - bitrate_list = jsonData[fmt] - if bitrate is None or bitrate == 'best' or bitrate not in bitrate_list: - bitrate = max(bitrate_list) # select highest - - url_list = jsonData[fmt][bitrate] - except TypeError: # we have no bitrate info. - url_list = jsonData[fmt] - return url_list + _TEST = { + u'url': u'http://www.mixcloud.com/dholbach/cryptkeeper/', + u'file': u'dholbach-cryptkeeper.mp3', + u'info_dict': { + u'title': u'Cryptkeeper', + u'description': u'After quite a long silence from myself, finally another Drum\'n\'Bass mix with my favourite current dance floor bangers.', + u'uploader': u'Daniel Holbach', + u'uploader_id': u'dholbach', + u'upload_date': u'20111115', + }, + } def check_urls(self, url_list): """Returns 1st active url from list""" @@ -45,71 +38,32 @@ class MixcloudIE(InfoExtractor): return None - def _print_formats(self, formats): - print('Available formats:') - for fmt in formats.keys(): - for b in formats[fmt]: - try: - ext = formats[fmt][b][0] - print('%s\t%s\t[%s]' % (fmt, b, ext.split('.')[-1])) - except TypeError: # we have no bitrate info - ext = formats[fmt][0] - print('%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1])) - break - def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) - if mobj is None: - raise ExtractorError(u'Invalid URL: %s' % url) - # extract uploader & filename from url - uploader = mobj.group(1).decode('utf-8') - file_id = uploader + "-" + mobj.group(2).decode('utf-8') - - # construct API request - file_url = 'http://www.mixcloud.com/api/1/cloudcast/' + '/'.join(url.split('/')[-3:-1]) + '.json' - # retrieve .json file with links to files - request = compat_urllib_request.Request(file_url) - try: - self.report_download_json(file_url) - jsonData = compat_urllib_request.urlopen(request).read() - except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: - raise ExtractorError(u'Unable to retrieve file: %s' % compat_str(err)) - - # parse JSON - json_data = json.loads(jsonData) - player_url = json_data['player_swf_url'] - formats = dict(json_data['audio_formats']) - - req_format = self._downloader.params.get('format', None) - - if self._downloader.params.get('listformats', None): - self._print_formats(formats) - return - - if req_format is None or req_format == 'best': - for format_param in formats.keys(): - url_list = self.get_urls(formats, format_param) - # check urls - file_url = self.check_urls(url_list) - if file_url is not None: - break # got it! - else: - if req_format not in formats: - raise ExtractorError(u'Format is not available') - - url_list = self.get_urls(formats, req_format) - file_url = self.check_urls(url_list) - format_param = req_format - return [{ - 'id': file_id.decode('utf-8'), - 'url': file_url.decode('utf-8'), - 'uploader': uploader.decode('utf-8'), - 'upload_date': None, - 'title': json_data['name'], - 'ext': file_url.split('.')[-1].decode('utf-8'), - 'format': (format_param is None and u'NA' or format_param.decode('utf-8')), - 'thumbnail': json_data['thumbnail_url'], - 'description': json_data['description'], - 'player_url': player_url.decode('utf-8'), - }] + uploader = mobj.group(1) + cloudcast_name = mobj.group(2) + track_id = '-'.join((uploader, cloudcast_name)) + api_url = 'http://api.mixcloud.com/%s/%s/' % (uploader, cloudcast_name) + webpage = self._download_webpage(url, track_id) + json_data = self._download_webpage(api_url, track_id, + u'Downloading cloudcast info') + info = json.loads(json_data) + + preview_url = self._search_regex(r'data-preview-url="(.+?)"', webpage, u'preview url') + song_url = preview_url.replace('/previews/', '/cloudcasts/originals/') + template_url = re.sub(r'(stream\d*)', 'stream%d', song_url) + final_song_url = self.check_urls(template_url % i for i in range(30)) + + return { + 'id': track_id, + 'title': info['name'], + 'url': final_song_url, + 'ext': 'mp3', + 'description': info['description'], + 'thumbnail': info['pictures'].get('extra_large'), + 'uploader': info['user']['name'], + 'uploader_id': info['user']['username'], + 'upload_date': unified_strdate(info['created_time']), + 'view_count': info['play_count'], + } diff --git a/youtube_dl/extractor/naver.py b/youtube_dl/extractor/naver.py new file mode 100644 index 0000000..9df236d --- /dev/null +++ b/youtube_dl/extractor/naver.py @@ -0,0 +1,73 @@ +# encoding: utf-8 +import re +import xml.etree.ElementTree + +from .common import InfoExtractor +from ..utils import ( + compat_urllib_parse, + ExtractorError, +) + + +class NaverIE(InfoExtractor): + _VALID_URL = r'https?://tvcast\.naver\.com/v/(?P\d+)' + + _TEST = { + u'url': u'http://tvcast.naver.com/v/81652', + u'file': u'81652.mp4', + u'info_dict': { + u'title': u'[9월 모의고사 해설강의][수학_김상희] 수학 A형 16~20번', + u'description': u'합격불변의 법칙 메가스터디 | 메가스터디 수학 김상희 선생님이 9월 모의고사 수학A형 16번에서 20번까지 해설강의를 공개합니다.', + u'upload_date': u'20130903', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group(1) + webpage = self._download_webpage(url, video_id) + m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"', + webpage) + if m_id is None: + raise ExtractorError(u'couldn\'t extract vid and key') + vid = m_id.group(1) + key = m_id.group(2) + query = compat_urllib_parse.urlencode({'vid': vid, 'inKey': key,}) + query_urls = compat_urllib_parse.urlencode({ + 'masterVid': vid, + 'protocol': 'p2p', + 'inKey': key, + }) + info_xml = self._download_webpage( + 'http://serviceapi.rmcnmv.naver.com/flash/videoInfo.nhn?' + query, + video_id, u'Downloading video info') + urls_xml = self._download_webpage( + 'http://serviceapi.rmcnmv.naver.com/flash/playableEncodingOption.nhn?' + query_urls, + video_id, u'Downloading video formats info') + info = xml.etree.ElementTree.fromstring(info_xml.encode('utf-8')) + urls = xml.etree.ElementTree.fromstring(urls_xml.encode('utf-8')) + + formats = [] + for format_el in urls.findall('EncodingOptions/EncodingOption'): + domain = format_el.find('Domain').text + if domain.startswith('rtmp'): + continue + formats.append({ + 'url': domain + format_el.find('uri').text, + 'ext': 'mp4', + 'width': int(format_el.find('width').text), + 'height': int(format_el.find('height').text), + }) + + info = { + 'id': video_id, + 'title': info.find('Subject').text, + 'formats': formats, + 'description': self._og_search_description(webpage), + 'thumbnail': self._og_search_thumbnail(webpage), + 'upload_date': info.find('WriteDate').text.replace('.', ''), + 'view_count': int(info.find('PlayCount').text), + } + # TODO: Remove when #980 has been merged + info.update(formats[-1]) + return info diff --git a/youtube_dl/extractor/newgrounds.py b/youtube_dl/extractor/newgrounds.py new file mode 100644 index 0000000..2ef80bc --- /dev/null +++ b/youtube_dl/extractor/newgrounds.py @@ -0,0 +1,38 @@ +import json +import re + +from .common import InfoExtractor +from ..utils import determine_ext + + +class NewgroundsIE(InfoExtractor): + _VALID_URL = r'(?:https?://)?(?:www\.)?newgrounds\.com/audio/listen/(?P\d+)' + _TEST = { + u'url': u'http://www.newgrounds.com/audio/listen/549479', + u'file': u'549479.mp3', + u'md5': u'fe6033d297591288fa1c1f780386f07a', + u'info_dict': { + u"title": u"B7 - BusMode", + u"uploader": u"Burn7", + } + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + music_id = mobj.group('id') + webpage = self._download_webpage(url, music_id) + + title = self._html_search_regex(r',"name":"([^"]+)",', webpage, u'music title') + uploader = self._html_search_regex(r',"artist":"([^"]+)",', webpage, u'music uploader') + + music_url_json_string = self._html_search_regex(r'({"url":"[^"]+"),', webpage, u'music url') + '}' + music_url_json = json.loads(music_url_json_string) + music_url = music_url_json['url'] + + return { + 'id': music_id, + 'title': title, + 'url': music_url, + 'uploader': uploader, + 'ext': determine_ext(music_url), + } diff --git a/youtube_dl/extractor/ooyala.py b/youtube_dl/extractor/ooyala.py index b734722..1f7b4d2 100644 --- a/youtube_dl/extractor/ooyala.py +++ b/youtube_dl/extractor/ooyala.py @@ -18,11 +18,15 @@ class OoyalaIE(InfoExtractor): }, } + @staticmethod + def _url_for_embed_code(embed_code): + return 'http://player.ooyala.com/player.js?embedCode=%s' % embed_code + def _extract_result(self, info, more_info): return {'id': info['embedCode'], 'ext': 'mp4', 'title': unescapeHTML(info['title']), - 'url': info['url'], + 'url': info.get('ipad_url') or info['url'], 'description': unescapeHTML(more_info['description']), 'thumbnail': more_info['promo'], } @@ -35,7 +39,9 @@ class OoyalaIE(InfoExtractor): mobile_url = self._search_regex(r'mobile_player_url="(.+?)&device="', player, u'mobile player url') mobile_player = self._download_webpage(mobile_url, embedCode) - videos_info = self._search_regex(r'eval\("\((\[{.*?stream_redirect.*?}\])\)"\);', mobile_player, u'info').replace('\\"','"') + videos_info = self._search_regex( + r'var streams=window.oo_testEnv\?\[\]:eval\("\((\[{.*?}\])\)"\);', + mobile_player, u'info').replace('\\"','"') videos_more_info = self._search_regex(r'eval\("\(({.*?\\"promo\\".*?})\)"', mobile_player, u'more info').replace('\\"','"') videos_info = json.loads(videos_info) videos_more_info =json.loads(videos_more_info) diff --git a/youtube_dl/extractor/orf.py b/youtube_dl/extractor/orf.py index 41ef8e9..cfca2a0 100644 --- a/youtube_dl/extractor/orf.py +++ b/youtube_dl/extractor/orf.py @@ -14,19 +14,6 @@ from ..utils import ( class ORFIE(InfoExtractor): _VALID_URL = r'https?://tvthek.orf.at/(programs/.+?/episodes|topics/.+?)/(?P\d+)' - _TEST = { - u'url': u'http://tvthek.orf.at/programs/1171769-Wetter-ZIB/episodes/6557323-Wetter', - u'file': u'6566957.flv', - u'info_dict': { - u'title': u'Wetter', - u'description': u'Christa Kummer, Marcus Wadsak und Kollegen präsentieren abwechselnd ihre täglichen Wetterprognosen für Österreich.\r \r Mehr Wetter unter wetter.ORF.at', - }, - u'params': { - # It uses rtmp - u'skip_download': True, - } - } - def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) playlist_id = mobj.group('id') diff --git a/youtube_dl/extractor/rtlnow.py b/youtube_dl/extractor/rtlnow.py index 7bb236c..3254107 100644 --- a/youtube_dl/extractor/rtlnow.py +++ b/youtube_dl/extractor/rtlnow.py @@ -8,8 +8,8 @@ from ..utils import ( ) class RTLnowIE(InfoExtractor): - """Information Extractor for RTL NOW, RTL2 NOW, SUPER RTL NOW and VOX NOW""" - _VALID_URL = r'(?:http://)?(?P(?Prtl-now\.rtl\.de/|rtl2now\.rtl2\.de/|(?:www\.)?voxnow\.de/|(?:www\.)?superrtlnow\.de/)[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?(?:container_id|film_id)=(?P[0-9]+)&player=1(?:&season=[0-9]+)?(?:&.*)?)' + """Information Extractor for RTL NOW, RTL2 NOW, RTL NITRO, SUPER RTL NOW and VOX NOW""" + _VALID_URL = r'(?:http://)?(?P(?Prtl-now\.rtl\.de/|rtl2now\.rtl2\.de/|(?:www\.)?voxnow\.de/|(?:www\.)?rtlnitronow\.de/|(?:www\.)?superrtlnow\.de/)[a-zA-Z0-9-]+/[a-zA-Z0-9-]+\.php\?(?:container_id|film_id)=(?P[0-9]+)&player=1(?:&season=[0-9]+)?(?:&.*)?)' _TESTS = [{ u'url': u'http://rtl-now.rtl.de/ahornallee/folge-1.php?film_id=90419&player=1&season=1', u'file': u'90419.flv', @@ -61,6 +61,19 @@ class RTLnowIE(InfoExtractor): u'params': { u'skip_download': True, }, + }, + { + u'url': u'http://www.rtlnitronow.de/recht-ordnung/lebensmittelkontrolle-erlangenordnungsamt-berlin.php?film_id=127367&player=1&season=1', + u'file': u'127367.flv', + u'info_dict': { + u'upload_date': u'20130926', + u'title': u'Recht & Ordnung - Lebensmittelkontrolle Erlangen/Ordnungsamt...', + u'description': u'Lebensmittelkontrolle Erlangen/Ordnungsamt Berlin', + u'thumbnail': u'http://autoimg.static-fra.de/nitronow/344787/1500x1500/image2.jpg', + }, + u'params': { + u'skip_download': True, + }, }] def _real_extract(self,url): @@ -79,7 +92,7 @@ class RTLnowIE(InfoExtractor): msg = clean_html(note_m.group(1)) raise ExtractorError(msg) - video_title = self._html_search_regex(r'(?P<title>[^<]+)', + video_title = self._html_search_regex(r'(?P<title>[^<]+?)( \| [^<]*)?', webpage, u'title') playerdata_url = self._html_search_regex(r'\'playerdata\': \'(?P[^\']+)\'', webpage, u'playerdata_url') diff --git a/youtube_dl/extractor/slideshare.py b/youtube_dl/extractor/slideshare.py new file mode 100644 index 0000000..afc3001 --- /dev/null +++ b/youtube_dl/extractor/slideshare.py @@ -0,0 +1,47 @@ +import re +import json + +from .common import InfoExtractor +from ..utils import ( + compat_urlparse, + ExtractorError, +) + + +class SlideshareIE(InfoExtractor): + _VALID_URL = r'https?://www\.slideshare\.net/[^/]+?/(?P.+?)($|\?)' + + _TEST = { + u'url': u'http://www.slideshare.net/Dataversity/keynote-presentation-managing-scale-and-complexity', + u'file': u'25665706.mp4', + u'info_dict': { + u'title': u'Managing Scale and Complexity', + u'description': u'This was a keynote presentation at the NoSQL Now! 2013 Conference & Expo (http://www.nosqlnow.com). This presentation was given by Adrian Cockcroft from Netflix', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + page_title = mobj.group('title') + webpage = self._download_webpage(url, page_title) + slideshare_obj = self._search_regex( + r'var slideshare_object = ({.*?}); var user_info =', + webpage, u'slideshare object') + info = json.loads(slideshare_obj) + if info['slideshow']['type'] != u'video': + raise ExtractorError(u'Webpage type is "%s": only video extraction is supported for Slideshare' % info['slideshow']['type'], expected=True) + + doc = info['doc'] + bucket = info['jsplayer']['video_bucket'] + ext = info['jsplayer']['video_extension'] + video_url = compat_urlparse.urljoin(bucket, doc + '-SD.' + ext) + + return { + '_type': 'video', + 'id': info['slideshow']['id'], + 'title': info['slideshow']['title'], + 'ext': ext, + 'url': video_url, + 'thumbnail': info['slideshow']['pin_image_url'], + 'description': self._og_search_description(webpage), + } diff --git a/youtube_dl/extractor/sohu.py b/youtube_dl/extractor/sohu.py index 77bb0a8..2b9bf0c 100644 --- a/youtube_dl/extractor/sohu.py +++ b/youtube_dl/extractor/sohu.py @@ -8,7 +8,7 @@ from ..utils import ExtractorError class SohuIE(InfoExtractor): - _VALID_URL = r'https?://tv\.sohu\.com/\d+?/n(?P<id>\d+)\.shtml.*?' + _VALID_URL = r'https?://(?P<mytv>my\.)?tv\.sohu\.com/.+?/(?(mytv)|n)(?P<id>\d+)\.shtml.*?' _TEST = { u'url': u'http://tv.sohu.com/20130724/n382479172.shtml#super', @@ -21,8 +21,11 @@ class SohuIE(InfoExtractor): def _real_extract(self, url): - def _fetch_data(vid_id): - base_data_url = u'http://hot.vrs.sohu.com/vrs_flash.action?vid=' + def _fetch_data(vid_id, mytv=False): + if mytv: + base_data_url = 'http://my.tv.sohu.com/play/videonew.do?vid=' + else: + base_data_url = u'http://hot.vrs.sohu.com/vrs_flash.action?vid=' data_url = base_data_url + str(vid_id) data_json = self._download_webpage( data_url, video_id, @@ -31,15 +34,16 @@ class SohuIE(InfoExtractor): mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') + mytv = mobj.group('mytv') is not None webpage = self._download_webpage(url, video_id) raw_title = self._html_search_regex(r'(?s)<title>(.+?)', webpage, u'video title') title = raw_title.partition('-')[0].strip() - vid = self._html_search_regex(r'var vid="(\d+)"', webpage, + vid = self._html_search_regex(r'var vid ?= ?["\'](\d+)["\']', webpage, u'video path') - data = _fetch_data(vid) + data = _fetch_data(vid, mytv) QUALITIES = ('ori', 'super', 'high', 'nor') vid_ids = [data['data'][q + 'Vid'] @@ -51,7 +55,7 @@ class SohuIE(InfoExtractor): # For now, we just pick the highest available quality vid_id = vid_ids[-1] - format_data = data if vid == vid_id else _fetch_data(vid_id) + format_data = data if vid == vid_id else _fetch_data(vid_id, mytv) part_count = format_data['data']['totalBlocks'] allot = format_data['allot'] prot = format_data['prot'] diff --git a/youtube_dl/extractor/soundcloud.py b/youtube_dl/extractor/soundcloud.py index 5f3a554..29cd561 100644 --- a/youtube_dl/extractor/soundcloud.py +++ b/youtube_dl/extractor/soundcloud.py @@ -1,10 +1,12 @@ import json import re +import itertools from .common import InfoExtractor from ..utils import ( compat_str, compat_urlparse, + compat_urllib_parse, ExtractorError, unified_strdate, @@ -53,10 +55,11 @@ class SoundcloudIE(InfoExtractor): def _resolv_url(cls, url): return 'http://api.soundcloud.com/resolve.json?url=' + url + '&client_id=' + cls._CLIENT_ID - def _extract_info_dict(self, info, full_title=None): + def _extract_info_dict(self, info, full_title=None, quiet=False): video_id = info['id'] name = full_title or video_id - self.report_extraction(name) + if quiet == False: + self.report_extraction(name) thumbnail = info['artwork_url'] if thumbnail is not None: @@ -198,3 +201,41 @@ class SoundcloudSetIE(SoundcloudIE): 'id': info['id'], 'title': info['title'], } + + +class SoundcloudUserIE(SoundcloudIE): + _VALID_URL = r'https?://(www\.)?soundcloud.com/(?P[^/]+)(/?(tracks/)?)?(\?.*)?$' + IE_NAME = u'soundcloud:user' + + # it's in tests/test_playlists.py + _TEST = None + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + uploader = mobj.group('user') + + url = 'http://soundcloud.com/%s/' % uploader + resolv_url = self._resolv_url(url) + user_json = self._download_webpage(resolv_url, uploader, + u'Downloading user info') + user = json.loads(user_json) + + tracks = [] + for i in itertools.count(): + data = compat_urllib_parse.urlencode({'offset': i*50, + 'client_id': self._CLIENT_ID, + }) + tracks_url = 'http://api.soundcloud.com/users/%s/tracks.json?' % user['id'] + data + response = self._download_webpage(tracks_url, uploader, + u'Downloading tracks page %s' % (i+1)) + new_tracks = json.loads(response) + tracks.extend(self._extract_info_dict(track, quiet=True) for track in new_tracks) + if len(new_tracks) < 50: + break + + return { + '_type': 'playlist', + 'id': compat_str(user['id']), + 'title': user['username'], + 'entries': tracks, + } diff --git a/youtube_dl/extractor/southparkstudios.py b/youtube_dl/extractor/southparkstudios.py new file mode 100644 index 0000000..b1e96b6 --- /dev/null +++ b/youtube_dl/extractor/southparkstudios.py @@ -0,0 +1,38 @@ +import re + +from .mtv import MTVIE, _media_xml_tag + + +class SouthParkStudiosIE(MTVIE): + IE_NAME = u'southparkstudios.com' + _VALID_URL = r'https?://www\.southparkstudios\.com/(clips|full-episodes)/(?P.+?)(\?|#|$)' + + _FEED_URL = 'http://www.southparkstudios.com/feeds/video-player/mrss' + + _TEST = { + u'url': u'http://www.southparkstudios.com/clips/104437/bat-daded#tab=featured', + u'file': u'a7bff6c2-ed00-11e0-aca6-0026b9414f30.mp4', + u'info_dict': { + u'title': u'Bat Daded', + u'description': u'Randy disqualifies South Park by getting into a fight with Bat Dad.', + }, + } + + # Overwrite MTVIE properties we don't want + _TESTS = [] + + def _get_thumbnail_url(self, uri, itemdoc): + search_path = '%s/%s' % (_media_xml_tag('group'), _media_xml_tag('thumbnail')) + thumb_node = itemdoc.find(search_path) + if thumb_node is None: + return None + else: + return thumb_node.attrib['url'] + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + webpage = self._download_webpage(url, video_id) + mgid = self._search_regex(r'swfobject.embedSWF\(".*?(mgid:.*?)"', + webpage, u'mgid') + return self._get_videos_info(mgid) diff --git a/youtube_dl/extractor/subtitles.py b/youtube_dl/extractor/subtitles.py new file mode 100644 index 0000000..90de7de --- /dev/null +++ b/youtube_dl/extractor/subtitles.py @@ -0,0 +1,91 @@ +from .common import InfoExtractor + +from ..utils import ( + compat_str, + ExtractorError, +) + + +class SubtitlesInfoExtractor(InfoExtractor): + @property + def _have_to_download_any_subtitles(self): + return any([self._downloader.params.get('writesubtitles', False), + self._downloader.params.get('writeautomaticsub')]) + + def _list_available_subtitles(self, video_id, webpage=None): + """ outputs the available subtitles for the video """ + sub_lang_list = self._get_available_subtitles(video_id) + auto_captions_list = self._get_available_automatic_caption(video_id, webpage) + sub_lang = ",".join(list(sub_lang_list.keys())) + self.to_screen(u'%s: Available subtitles for video: %s' % + (video_id, sub_lang)) + auto_lang = ",".join(auto_captions_list.keys()) + self.to_screen(u'%s: Available automatic captions for video: %s' % + (video_id, auto_lang)) + + def extract_subtitles(self, video_id, video_webpage=None): + """ + returns {sub_lang: sub} ,{} if subtitles not found or None if the + subtitles aren't requested. + """ + if not self._have_to_download_any_subtitles: + return None + available_subs_list = {} + if self._downloader.params.get('writeautomaticsub', False): + available_subs_list.update(self._get_available_automatic_caption(video_id, video_webpage)) + if self._downloader.params.get('writesubtitles', False): + available_subs_list.update(self._get_available_subtitles(video_id)) + + if not available_subs_list: # error, it didn't get the available subtitles + return {} + if self._downloader.params.get('allsubtitles', False): + sub_lang_list = available_subs_list + else: + if self._downloader.params.get('subtitleslangs', False): + requested_langs = self._downloader.params.get('subtitleslangs') + elif 'en' in available_subs_list: + requested_langs = ['en'] + else: + requested_langs = [list(available_subs_list.keys())[0]] + + sub_lang_list = {} + for sub_lang in requested_langs: + if not sub_lang in available_subs_list: + self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang) + continue + sub_lang_list[sub_lang] = available_subs_list[sub_lang] + + subtitles = {} + for sub_lang, url in sub_lang_list.items(): + subtitle = self._request_subtitle_url(sub_lang, url) + if subtitle: + subtitles[sub_lang] = subtitle + return subtitles + + def _request_subtitle_url(self, sub_lang, url): + """ makes the http request for the subtitle """ + try: + sub = self._download_webpage(url, None, note=False) + except ExtractorError as err: + self._downloader.report_warning(u'unable to download video subtitles for %s: %s' % (sub_lang, compat_str(err))) + return + if not sub: + self._downloader.report_warning(u'Did not fetch video subtitles') + return + return sub + + def _get_available_subtitles(self, video_id): + """ + returns {sub_lang: url} or {} if not available + Must be redefined by the subclasses + """ + pass + + def _get_available_automatic_caption(self, video_id, webpage): + """ + returns {sub_lang: url} or {} if not available + Must be redefined by the subclasses that support automatic captions, + otherwise it will return {} + """ + self._downloader.report_warning(u'Automatic Captions not supported by this server') + return {} diff --git a/youtube_dl/extractor/trilulilu.py b/youtube_dl/extractor/trilulilu.py index f278951..0bf028f 100644 --- a/youtube_dl/extractor/trilulilu.py +++ b/youtube_dl/extractor/trilulilu.py @@ -52,6 +52,7 @@ class TriluliluIE(InfoExtractor): { 'format': fnode.text, 'url': video_url_template % fnode.text, + 'ext': fnode.text.partition('-')[0] } for fnode in format_doc.findall('./formats/format') @@ -67,7 +68,6 @@ class TriluliluIE(InfoExtractor): } # TODO: Remove when #980 has been merged - info['url'] = formats[-1]['url'] - info['ext'] = formats[-1]['format'].partition('-')[0] + info.update(formats[-1]) return info diff --git a/youtube_dl/extractor/ustream.py b/youtube_dl/extractor/ustream.py index 5f42387..74c8258 100644 --- a/youtube_dl/extractor/ustream.py +++ b/youtube_dl/extractor/ustream.py @@ -1,6 +1,11 @@ +import json import re from .common import InfoExtractor +from ..utils import ( + compat_urlparse, + get_meta_content, +) class UstreamIE(InfoExtractor): @@ -43,3 +48,25 @@ class UstreamIE(InfoExtractor): 'thumbnail': thumbnail, } return info + +class UstreamChannelIE(InfoExtractor): + _VALID_URL = r'https?://www\.ustream\.tv/channel/(?P.+)' + IE_NAME = u'ustream:channel' + + def _real_extract(self, url): + m = re.match(self._VALID_URL, url) + slug = m.group('slug') + webpage = self._download_webpage(url, slug) + channel_id = get_meta_content('ustream:channel_id', webpage) + + BASE = 'http://www.ustream.tv' + next_url = '/ajax/socialstream/videos/%s/1.json' % channel_id + video_ids = [] + while next_url: + reply = json.loads(self._download_webpage(compat_urlparse.urljoin(BASE, next_url), channel_id)) + video_ids.extend(re.findall(r'data-content-id="(\d.*)"', reply['data'])) + next_url = reply['nextUrl'] + + urls = ['http://www.ustream.tv/recorded/' + vid for vid in video_ids] + url_entries = [self.url_result(eurl, 'Ustream') for eurl in urls] + return self.playlist_result(url_entries, channel_id) diff --git a/youtube_dl/extractor/veehd.py b/youtube_dl/extractor/veehd.py new file mode 100644 index 0000000..3a99a29 --- /dev/null +++ b/youtube_dl/extractor/veehd.py @@ -0,0 +1,56 @@ +import re +import json + +from .common import InfoExtractor +from ..utils import ( + compat_urlparse, + get_element_by_id, + clean_html, +) + +class VeeHDIE(InfoExtractor): + _VALID_URL = r'https?://veehd.com/video/(?P\d+)' + + _TEST = { + u'url': u'http://veehd.com/video/4686958', + u'file': u'4686958.mp4', + u'info_dict': { + u'title': u'Time Lapse View from Space ( ISS)', + u'uploader_id': u'spotted', + u'description': u'md5:f0094c4cf3a72e22bc4e4239ef767ad7', + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + video_id = mobj.group('id') + + webpage = self._download_webpage(url, video_id) + player_path = self._search_regex(r'\$\("#playeriframe"\).attr\({src : "(.+?)"', + webpage, u'player path') + player_url = compat_urlparse.urljoin(url, player_path) + player_page = self._download_webpage(player_url, video_id, + u'Downloading player page') + config_json = self._search_regex(r'value=\'config=({.+?})\'', + player_page, u'config json') + config = json.loads(config_json) + + video_url = compat_urlparse.unquote(config['clip']['url']) + title = clean_html(get_element_by_id('videoName', webpage).rpartition('|')[0]) + uploader_id = self._html_search_regex(r'
    (.+?)', + webpage, u'uploader') + thumbnail = self._search_regex(r'(.*?).+)' + + _TEST = { + u'url': u'http://www.vice.com/Fringes/cowboy-capitalists-part-1', + u'file': u'43cW1mYzpia9IlestBjVpd23Yu3afAfp.mp4', + u'info_dict': { + u'title': u'VICE_COWBOYCAPITALISTS_PART01_v1_VICE_WM_1080p.mov', + }, + u'params': { + # Requires ffmpeg (m3u8 manifest) + u'skip_download': True, + }, + } + + def _real_extract(self, url): + mobj = re.match(self._VALID_URL, url) + name = mobj.group('name') + webpage = self._download_webpage(url, name) + try: + ooyala_url = self._og_search_video_url(webpage) + except ExtractorError: + try: + embed_code = self._search_regex( + r'OO.Player.create\(\'ooyalaplayer\', \'(.+?)\'', webpage, + u'ooyala embed code') + ooyala_url = OoyalaIE._url_for_embed_code(embed_code) + except ExtractorError: + raise ExtractorError(u'The page doesn\'t contain a video', expected=True) + return self.url_result(ooyala_url, ie='Ooyala') + diff --git a/youtube_dl/extractor/vimeo.py b/youtube_dl/extractor/vimeo.py index 512e06e..4a7d82b 100644 --- a/youtube_dl/extractor/vimeo.py +++ b/youtube_dl/extractor/vimeo.py @@ -44,6 +44,16 @@ class VimeoIE(InfoExtractor): u'title': u'Andy Allan - Putting the Carto into OpenStreetMap Cartography', }, }, + { + u'url': u'http://player.vimeo.com/video/54469442', + u'file': u'54469442.mp4', + u'md5': u'619b811a4417aa4abe78dc653becf511', + u'note': u'Videos that embed the url in the player page', + u'info_dict': { + u'title': u'Kathy Sierra: Building the minimum Badass User, Business of Software', + u'uploader': u'The BLN & Business of Software', + }, + }, ] def _login(self): @@ -112,7 +122,8 @@ class VimeoIE(InfoExtractor): # Extract the config JSON try: - config = webpage.split(' = {config:')[1].split(',assets:')[0] + config = self._search_regex([r' = {config:({.+?}),assets:', r'c=({.+?);'], + webpage, u'info section', flags=re.DOTALL) config = json.loads(config) except: if re.search('The creator of this video has not given you permission to embed it on this domain.', webpage): @@ -132,12 +143,22 @@ class VimeoIE(InfoExtractor): video_uploader_id = config["video"]["owner"]["url"].split('/')[-1] if config["video"]["owner"]["url"] else None # Extract video thumbnail - video_thumbnail = config["video"]["thumbnail"] + video_thumbnail = config["video"].get("thumbnail") + if video_thumbnail is None: + _, video_thumbnail = sorted((int(width), t_url) for (width, t_url) in config["video"]["thumbs"].items())[-1] # Extract video description - video_description = get_element_by_attribute("itemprop", "description", webpage) - if video_description: video_description = clean_html(video_description) - else: video_description = u'' + video_description = None + try: + video_description = get_element_by_attribute("itemprop", "description", webpage) + if video_description: video_description = clean_html(video_description) + except AssertionError as err: + # On some pages like (http://player.vimeo.com/video/54469442) the + # html tags are not closed, python 2.6 cannot handle it + if err.args[0] == 'we should not get here!': + pass + else: + raise # Extract upload date video_upload_date = None @@ -154,14 +175,15 @@ class VimeoIE(InfoExtractor): # TODO bind to format param codecs = [('h264', 'mp4'), ('vp8', 'flv'), ('vp6', 'flv')] files = { 'hd': [], 'sd': [], 'other': []} + config_files = config["video"].get("files") or config["request"].get("files") for codec_name, codec_extension in codecs: - if codec_name in config["video"]["files"]: - if 'hd' in config["video"]["files"][codec_name]: + if codec_name in config_files: + if 'hd' in config_files[codec_name]: files['hd'].append((codec_name, codec_extension, 'hd')) - elif 'sd' in config["video"]["files"][codec_name]: + elif 'sd' in config_files[codec_name]: files['sd'].append((codec_name, codec_extension, 'sd')) else: - files['other'].append((codec_name, codec_extension, config["video"]["files"][codec_name][0])) + files['other'].append((codec_name, codec_extension, config_files[codec_name][0])) for quality in ('hd', 'sd', 'other'): if len(files[quality]) > 0: @@ -173,8 +195,12 @@ class VimeoIE(InfoExtractor): else: raise ExtractorError(u'No known codec found') - video_url = "http://player.vimeo.com/play_redirect?clip_id=%s&sig=%s&time=%s&quality=%s&codecs=%s&type=moogaloop_local&embed_location=" \ - %(video_id, sig, timestamp, video_quality, video_codec.upper()) + video_url = None + if isinstance(config_files[video_codec], dict): + video_url = config_files[video_codec][video_quality].get("url") + if video_url is None: + video_url = "http://player.vimeo.com/play_redirect?clip_id=%s&sig=%s&time=%s&quality=%s&codecs=%s&type=moogaloop_local&embed_location=" \ + %(video_id, sig, timestamp, video_quality, video_codec.upper()) return [{ 'id': video_id, diff --git a/youtube_dl/extractor/xhamster.py b/youtube_dl/extractor/xhamster.py index 88b8b6b..3616196 100644 --- a/youtube_dl/extractor/xhamster.py +++ b/youtube_dl/extractor/xhamster.py @@ -11,8 +11,8 @@ from ..utils import ( class XHamsterIE(InfoExtractor): """Information Extractor for xHamster""" - _VALID_URL = r'(?:http://)?(?:www.)?xhamster\.com/movies/(?P[0-9]+)/.*\.html' - _TEST = { + _VALID_URL = r'(?:http://)?(?:www\.)?xhamster\.com/movies/(?P[0-9]+)/(?P.+?)\.html(?:\?.*)?' + _TESTS = [{ u'url': u'http://xhamster.com/movies/1509445/femaleagent_shy_beauty_takes_the_bait.html', u'file': u'1509445.flv', u'md5': u'9f48e0e8d58e3076bb236ff412ab62fa', @@ -21,13 +21,24 @@ class XHamsterIE(InfoExtractor): u"uploader_id": u"Ruseful2011", u"title": u"FemaleAgent Shy beauty takes the bait" } - } + }, + { + u'url': u'http://xhamster.com/movies/2221348/britney_spears_sexy_booty.html?hd', + u'file': u'2221348.flv', + u'md5': u'e767b9475de189320f691f49c679c4c7', + u'info_dict': { + u"upload_date": u"20130914", + u"uploader_id": u"jojo747400", + u"title": u"Britney Spears Sexy Booty" + } + }] def _real_extract(self,url): mobj = re.match(self._VALID_URL, url) video_id = mobj.group('id') - mrss_url = 'http://xhamster.com/movies/%s/.html' % video_id + seo = mobj.group('seo') + mrss_url = 'http://xhamster.com/movies/%s/%s.html?hd' % (video_id, seo) webpage = self._download_webpage(mrss_url, video_id) mobj = re.search(r'\'srv\': \'(?P[^\']*)\',\s*\'file\': \'(?P[^\']+)\',', webpage) diff --git a/youtube_dl/extractor/yahoo.py b/youtube_dl/extractor/yahoo.py index 32d5b94..39126e6 100644 --- a/youtube_dl/extractor/yahoo.py +++ b/youtube_dl/extractor/yahoo.py @@ -1,4 +1,3 @@ -import datetime import itertools import json import re @@ -6,86 +5,85 @@ import re from .common import InfoExtractor, SearchInfoExtractor from ..utils import ( compat_urllib_parse, - - ExtractorError, + compat_urlparse, + determine_ext, + clean_html, ) + class YahooIE(InfoExtractor): IE_DESC = u'Yahoo screen' _VALID_URL = r'http://screen\.yahoo\.com/.*?-(?P\d*?)\.html' - _TEST = { - u'url': u'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html', - u'file': u'214727115.flv', - u'md5': u'2e717f169c1be93d84d3794a00d4a325', - u'info_dict': { - u"title": u"Julian Smith & Travis Legg Watch Julian Smith" + _TESTS = [ + { + u'url': u'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html', + u'file': u'214727115.mp4', + u'info_dict': { + u'title': u'Julian Smith & Travis Legg Watch Julian Smith', + u'description': u'Julian and Travis watch Julian Smith', + }, }, - u'skip': u'Requires rtmpdump' - } + { + u'url': u'http://screen.yahoo.com/wired/codefellas-s1-ep12-cougar-lies-103000935.html', + u'file': u'103000935.flv', + u'info_dict': { + u'title': u'The Cougar Lies with Spanish Moss', + u'description': u'Agent Topple\'s mustache does its dirty work, and Nicole brokers a deal for peace. But why is the NSA collecting millions of Instagram brunch photos? And if your waffles have nothing to hide, what are they so worried about?', + }, + u'params': { + # Requires rtmpdump + u'skip_download': True, + }, + }, + ] def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) - if mobj is None: - raise ExtractorError(u'Invalid URL: %s' % url) video_id = mobj.group('id') webpage = self._download_webpage(url, video_id) - m_id = re.search(r'YUI\.namespace\("Media"\)\.CONTENT_ID = "(?P.+?)";', webpage) - if m_id is None: - # TODO: Check which url parameters are required - info_url = 'http://cosmos.bcst.yahoo.com/rest/v2/pops;lmsoverride=1;outputformat=mrss;cb=974419660;id=%s;rd=news.yahoo.com;datacontext=mdb;lg=KCa2IihxG3qE60vQ7HtyUy' % video_id - webpage = self._download_webpage(info_url, video_id, u'Downloading info webpage') - info_re = r'''<!\[CDATA\[(?P<title>.*?)\]\]>.* - .*?)\]\]>.* - .*?)\ .*\]\]>.* - youtube.com/xxxx is OK + )) + |youtu\.be/ # just youtu.be/xxxx + ) )? # all until now is optional -> you can pass the naked ID - ([0-9A-Za-z_-]+) # here is it! the YouTube video ID + ([0-9A-Za-z_-]{11}) # here is it! the YouTube video ID (?(1).+)? # if we found the ID, everything can follow $""" _NEXT_URL_RE = r'[\?&]next_url=([^&]+)' # Listed in order of quality - _available_formats = ['38', '37', '46', '22', '45', '35', '44', '34', '18', '43', '6', '5', '17', '13', - '95', '94', '93', '92', '132', '151', + _available_formats = ['38', '37', '46', '22', '45', '35', '44', '34', '18', '43', '6', '5', '36', '17', '13', + # Apple HTTP Live Streaming + '96', '95', '94', '93', '92', '132', '151', # 3D '85', '84', '102', '83', '101', '82', '100', # Dash video @@ -163,8 +179,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): # Dash audio '141', '172', '140', '171', '139', ] - _available_formats_prefer_free = ['38', '46', '37', '45', '22', '44', '35', '43', '34', '18', '6', '5', '17', '13', - '95', '94', '93', '92', '132', '151', + _available_formats_prefer_free = ['38', '46', '37', '45', '22', '44', '35', '43', '34', '18', '6', '5', '36', '17', '13', + # Apple HTTP Live Streaming + '96', '95', '94', '93', '92', '132', '151', + # 3D '85', '102', '84', '101', '83', '100', '82', # Dash video '138', '248', '137', '247', '136', '246', '245', @@ -172,11 +190,18 @@ class YoutubeIE(YoutubeBaseInfoExtractor): # Dash audio '172', '141', '171', '140', '139', ] + _video_formats_map = { + 'flv': ['35', '34', '6', '5'], + '3gp': ['36', '17', '13'], + 'mp4': ['38', '37', '22', '18'], + 'webm': ['46', '45', '44', '43'], + } _video_extensions = { '13': '3gp', - '17': 'mp4', + '17': '3gp', '18': 'mp4', '22': 'mp4', + '36': '3gp', '37': 'mp4', '38': 'mp4', '43': 'webm', @@ -193,7 +218,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): '101': 'webm', '102': 'webm', - # videos that use m3u8 + # Apple HTTP Live Streaming '92': 'mp4', '93': 'mp4', '94': 'mp4', @@ -234,6 +259,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): '22': '720x1280', '34': '360x640', '35': '480x854', + '36': '240x320', '37': '1080x1920', '38': '3072x4096', '43': '360x640', @@ -335,7 +361,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): u"info_dict": { u"upload_date": u"20120506", u"title": u"Icona Pop - I Love It (feat. Charli XCX) [OFFICIAL VIDEO]", - u"description": u"md5:3e2666e0a55044490499ea45fe9037b7", + u"description": u"md5:5b292926389560516e384ac437c0ec07", u"uploader": u"Icona Pop", u"uploader_id": u"IconaPop" } @@ -352,30 +378,19 @@ class YoutubeIE(YoutubeBaseInfoExtractor): u"uploader_id": u"justintimberlakeVEVO" } }, - { - u'url': u'https://www.youtube.com/watch?v=TGi3HqYrWHE', - u'file': u'TGi3HqYrWHE.mp4', - u'note': u'm3u8 video', - u'info_dict': { - u'title': u'Triathlon - Men - London 2012 Olympic Games', - u'description': u'- Men - TR02 - Triathlon - 07 August 2012 - London 2012 Olympic Games', - u'uploader': u'olympic', - u'upload_date': u'20120807', - u'uploader_id': u'olympic', - }, - u'params': { - u'skip_download': True, - }, - }, ] @classmethod def suitable(cls, url): """Receives a URL and returns True if suitable for this IE.""" - if YoutubePlaylistIE.suitable(url) or YoutubeSubscriptionsIE.suitable(url): return False + if YoutubePlaylistIE.suitable(url): return False return re.match(cls._VALID_URL, url, re.VERBOSE) is not None + def __init__(self, *args, **kwargs): + super(YoutubeIE, self).__init__(*args, **kwargs) + self._player_cache = {} + def report_video_webpage_download(self, video_id): """Report attempt to download video webpage.""" self.to_screen(u'%s: Downloading video webpage' % video_id) @@ -384,19 +399,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor): """Report attempt to download video info webpage.""" self.to_screen(u'%s: Downloading video info webpage' % video_id) - def report_video_subtitles_download(self, video_id): - """Report attempt to download video info webpage.""" - self.to_screen(u'%s: Checking available subtitles' % video_id) - - def report_video_subtitles_request(self, video_id, sub_lang, format): - """Report attempt to download video info webpage.""" - self.to_screen(u'%s: Downloading video subtitles for %s.%s' % (video_id, sub_lang, format)) - - def report_video_subtitles_available(self, video_id, sub_lang_list): - """Report available subtitles.""" - sub_lang = ",".join(list(sub_lang_list.keys())) - self.to_screen(u'%s: Available subtitles for video: %s' % (video_id, sub_lang)) - def report_information_extraction(self, video_id): """Report attempt to extract video information.""" self.to_screen(u'%s: Extracting video information' % video_id) @@ -409,11 +411,664 @@ class YoutubeIE(YoutubeBaseInfoExtractor): """Indicate the download will use the RTMP protocol.""" self.to_screen(u'RTMP download detected') - def _decrypt_signature(self, s): + def _extract_signature_function(self, video_id, player_url, slen): + id_m = re.match(r'.*-(?P[a-zA-Z0-9_-]+)\.(?P[a-z]+)$', + player_url) + player_type = id_m.group('ext') + player_id = id_m.group('id') + + # Read from filesystem cache + func_id = '%s_%s_%d' % (player_type, player_id, slen) + assert os.path.basename(func_id) == func_id + cache_dir = self._downloader.params.get('cachedir', + u'~/.youtube-dl/cache') + + cache_enabled = cache_dir is not None + if cache_enabled: + cache_fn = os.path.join(os.path.expanduser(cache_dir), + u'youtube-sigfuncs', + func_id + '.json') + try: + with io.open(cache_fn, 'r', encoding='utf-8') as cachef: + cache_spec = json.load(cachef) + return lambda s: u''.join(s[i] for i in cache_spec) + except IOError: + pass # No cache available + + if player_type == 'js': + code = self._download_webpage( + player_url, video_id, + note=u'Downloading %s player %s' % (player_type, player_id), + errnote=u'Download of %s failed' % player_url) + res = self._parse_sig_js(code) + elif player_type == 'swf': + urlh = self._request_webpage( + player_url, video_id, + note=u'Downloading %s player %s' % (player_type, player_id), + errnote=u'Download of %s failed' % player_url) + code = urlh.read() + res = self._parse_sig_swf(code) + else: + assert False, 'Invalid player type %r' % player_type + + if cache_enabled: + try: + test_string = u''.join(map(compat_chr, range(slen))) + cache_res = res(test_string) + cache_spec = [ord(c) for c in cache_res] + try: + os.makedirs(os.path.dirname(cache_fn)) + except OSError as ose: + if ose.errno != errno.EEXIST: + raise + write_json_file(cache_spec, cache_fn) + except Exception: + tb = traceback.format_exc() + self._downloader.report_warning( + u'Writing cache to %r failed: %s' % (cache_fn, tb)) + + return res + + def _print_sig_code(self, func, slen): + def gen_sig_code(idxs): + def _genslice(start, end, step): + starts = u'' if start == 0 else str(start) + ends = (u':%d' % (end+step)) if end + step >= 0 else u':' + steps = u'' if step == 1 else (u':%d' % step) + return u's[%s%s%s]' % (starts, ends, steps) + + step = None + start = '(Never used)' # Quelch pyflakes warnings - start will be + # set as soon as step is set + for i, prev in zip(idxs[1:], idxs[:-1]): + if step is not None: + if i - prev == step: + continue + yield _genslice(start, prev, step) + step = None + continue + if i - prev in [-1, 1]: + step = i - prev + start = prev + continue + else: + yield u's[%d]' % prev + if step is None: + yield u's[%d]' % i + else: + yield _genslice(start, i, step) + + test_string = u''.join(map(compat_chr, range(slen))) + cache_res = func(test_string) + cache_spec = [ord(c) for c in cache_res] + expr_code = u' + '.join(gen_sig_code(cache_spec)) + code = u'if len(s) == %d:\n return %s\n' % (slen, expr_code) + self.to_screen(u'Extracted signature function:\n' + code) + + def _parse_sig_js(self, jscode): + funcname = self._search_regex( + r'signature=([a-zA-Z]+)', jscode, + u'Initial JS player signature function name') + + functions = {} + + def argidx(varname): + return string.lowercase.index(varname) + + def interpret_statement(stmt, local_vars, allow_recursion=20): + if allow_recursion < 0: + raise ExtractorError(u'Recursion limit reached') + + if stmt.startswith(u'var '): + stmt = stmt[len(u'var '):] + ass_m = re.match(r'^(?P[a-z]+)(?:\[(?P[^\]]+)\])?' + + r'=(?P.*)$', stmt) + if ass_m: + if ass_m.groupdict().get('index'): + def assign(val): + lvar = local_vars[ass_m.group('out')] + idx = interpret_expression(ass_m.group('index'), + local_vars, allow_recursion) + assert isinstance(idx, int) + lvar[idx] = val + return val + expr = ass_m.group('expr') + else: + def assign(val): + local_vars[ass_m.group('out')] = val + return val + expr = ass_m.group('expr') + elif stmt.startswith(u'return '): + assign = lambda v: v + expr = stmt[len(u'return '):] + else: + raise ExtractorError( + u'Cannot determine left side of statement in %r' % stmt) + + v = interpret_expression(expr, local_vars, allow_recursion) + return assign(v) + + def interpret_expression(expr, local_vars, allow_recursion): + if expr.isdigit(): + return int(expr) + + if expr.isalpha(): + return local_vars[expr] + + m = re.match(r'^(?P[a-z]+)\.(?P.*)$', expr) + if m: + member = m.group('member') + val = local_vars[m.group('in')] + if member == 'split("")': + return list(val) + if member == 'join("")': + return u''.join(val) + if member == 'length': + return len(val) + if member == 'reverse()': + return val[::-1] + slice_m = re.match(r'slice\((?P.*)\)', member) + if slice_m: + idx = interpret_expression( + slice_m.group('idx'), local_vars, allow_recursion-1) + return val[idx:] + + m = re.match( + r'^(?P[a-z]+)\[(?P.+)\]$', expr) + if m: + val = local_vars[m.group('in')] + idx = interpret_expression(m.group('idx'), local_vars, + allow_recursion-1) + return val[idx] + + m = re.match(r'^(?P.+?)(?P[%])(?P.+?)$', expr) + if m: + a = interpret_expression(m.group('a'), + local_vars, allow_recursion) + b = interpret_expression(m.group('b'), + local_vars, allow_recursion) + return a % b + + m = re.match( + r'^(?P[a-zA-Z]+)\((?P[a-z0-9,]+)\)$', expr) + if m: + fname = m.group('func') + if fname not in functions: + functions[fname] = extract_function(fname) + argvals = [int(v) if v.isdigit() else local_vars[v] + for v in m.group('args').split(',')] + return functions[fname](argvals) + raise ExtractorError(u'Unsupported JS expression %r' % expr) + + def extract_function(funcname): + func_m = re.search( + r'function ' + re.escape(funcname) + + r'\((?P[a-z,]+)\){(?P[^}]+)}', + jscode) + argnames = func_m.group('args').split(',') + + def resf(args): + local_vars = dict(zip(argnames, args)) + for stmt in func_m.group('code').split(';'): + res = interpret_statement(stmt, local_vars) + return res + return resf + + initial_function = extract_function(funcname) + return lambda s: initial_function([s]) + + def _parse_sig_swf(self, file_contents): + if file_contents[1:3] != b'WS': + raise ExtractorError( + u'Not an SWF file; header is %r' % file_contents[:3]) + if file_contents[:1] == b'C': + content = zlib.decompress(file_contents[8:]) + else: + raise NotImplementedError(u'Unsupported compression format %r' % + file_contents[:1]) + + def extract_tags(content): + pos = 0 + while pos < len(content): + header16 = struct.unpack('> 6 + tag_len = header16 & 0x3f + if tag_len == 0x3f: + tag_len = struct.unpack('> 4 + methods = {} + if kind in [0x00, 0x06]: # Slot or Const + u30() # Slot id + u30() # type_name_idx + vindex = u30() + if vindex != 0: + read_byte() # vkind + elif kind in [0x01, 0x02, 0x03]: # Method / Getter / Setter + u30() # disp_id + method_idx = u30() + methods[multinames[trait_name_idx]] = method_idx + elif kind == 0x04: # Class + u30() # slot_id + u30() # classi + elif kind == 0x05: # Function + u30() # slot_id + function_idx = u30() + methods[function_idx] = multinames[trait_name_idx] + else: + raise ExtractorError(u'Unsupported trait kind %d' % kind) + + if attrs & 0x4 != 0: # Metadata present + metadata_count = u30() + for _c3 in range(metadata_count): + u30() # metadata index + + return methods + + # Classes + TARGET_CLASSNAME = u'SignatureDecipher' + searched_idx = multinames.index(TARGET_CLASSNAME) + searched_class_id = None + class_count = u30() + for class_id in range(class_count): + name_idx = u30() + if name_idx == searched_idx: + # We found the class we're looking for! + searched_class_id = class_id + u30() # super_name idx + flags = read_byte() + if flags & 0x08 != 0: # Protected namespace is present + u30() # protected_ns_idx + intrf_count = u30() + for _c2 in range(intrf_count): + u30() + u30() # iinit + trait_count = u30() + for _c2 in range(trait_count): + parse_traits_info() + + if searched_class_id is None: + raise ExtractorError(u'Target class %r not found' % + TARGET_CLASSNAME) + + method_names = {} + method_idxs = {} + for class_id in range(class_count): + u30() # cinit + trait_count = u30() + for _c2 in range(trait_count): + trait_methods = parse_traits_info() + if class_id == searched_class_id: + method_names.update(trait_methods.items()) + method_idxs.update(dict( + (idx, name) + for name, idx in trait_methods.items())) + + # Scripts + script_count = u30() + for _c in range(script_count): + u30() # init + trait_count = u30() + for _c2 in range(trait_count): + parse_traits_info() + + # Method bodies + method_body_count = u30() + Method = collections.namedtuple('Method', ['code', 'local_count']) + methods = {} + for _c in range(method_body_count): + method_idx = u30() + u30() # max_stack + local_count = u30() + u30() # init_scope_depth + u30() # max_scope_depth + code_length = u30() + code = read_bytes(code_length) + if method_idx in method_idxs: + m = Method(code, local_count) + methods[method_idxs[method_idx]] = m + exception_count = u30() + for _c2 in range(exception_count): + u30() # from + u30() # to + u30() # target + u30() # exc_type + u30() # var_name + trait_count = u30() + for _c2 in range(trait_count): + parse_traits_info() + + assert p + code_reader.tell() == len(code_tag) + assert len(methods) == len(method_idxs) + + method_pyfunctions = {} + + def extract_function(func_name): + if func_name in method_pyfunctions: + return method_pyfunctions[func_name] + if func_name not in methods: + raise ExtractorError(u'Cannot find function %r' % func_name) + m = methods[func_name] + + def resfunc(args): + registers = ['(this)'] + list(args) + [None] * m.local_count + stack = [] + coder = io.BytesIO(m.code) + while True: + opcode = struct.unpack('!B', coder.read(1))[0] + if opcode == 36: # pushbyte + v = struct.unpack('!B', coder.read(1))[0] + stack.append(v) + elif opcode == 44: # pushstring + idx = u30(coder) + stack.append(constant_strings[idx]) + elif opcode == 48: # pushscope + # We don't implement the scope register, so we'll just + # ignore the popped value + stack.pop() + elif opcode == 70: # callproperty + index = u30(coder) + mname = multinames[index] + arg_count = u30(coder) + args = list(reversed( + [stack.pop() for _ in range(arg_count)])) + obj = stack.pop() + if mname == u'split': + assert len(args) == 1 + assert isinstance(args[0], compat_str) + assert isinstance(obj, compat_str) + if args[0] == u'': + res = list(obj) + else: + res = obj.split(args[0]) + stack.append(res) + elif mname == u'slice': + assert len(args) == 1 + assert isinstance(args[0], int) + assert isinstance(obj, list) + res = obj[args[0]:] + stack.append(res) + elif mname == u'join': + assert len(args) == 1 + assert isinstance(args[0], compat_str) + assert isinstance(obj, list) + res = args[0].join(obj) + stack.append(res) + elif mname in method_pyfunctions: + stack.append(method_pyfunctions[mname](args)) + else: + raise NotImplementedError( + u'Unsupported property %r on %r' + % (mname, obj)) + elif opcode == 72: # returnvalue + res = stack.pop() + return res + elif opcode == 79: # callpropvoid + index = u30(coder) + mname = multinames[index] + arg_count = u30(coder) + args = list(reversed( + [stack.pop() for _ in range(arg_count)])) + obj = stack.pop() + if mname == u'reverse': + assert isinstance(obj, list) + obj.reverse() + else: + raise NotImplementedError( + u'Unsupported (void) property %r on %r' + % (mname, obj)) + elif opcode == 93: # findpropstrict + index = u30(coder) + mname = multinames[index] + res = extract_function(mname) + stack.append(res) + elif opcode == 97: # setproperty + index = u30(coder) + value = stack.pop() + idx = stack.pop() + obj = stack.pop() + assert isinstance(obj, list) + assert isinstance(idx, int) + obj[idx] = value + elif opcode == 98: # getlocal + index = u30(coder) + stack.append(registers[index]) + elif opcode == 99: # setlocal + index = u30(coder) + value = stack.pop() + registers[index] = value + elif opcode == 102: # getproperty + index = u30(coder) + pname = multinames[index] + if pname == u'length': + obj = stack.pop() + assert isinstance(obj, list) + stack.append(len(obj)) + else: # Assume attribute access + idx = stack.pop() + assert isinstance(idx, int) + obj = stack.pop() + assert isinstance(obj, list) + stack.append(obj[idx]) + elif opcode == 128: # coerce + u30(coder) + elif opcode == 133: # coerce_s + assert isinstance(stack[-1], (type(None), compat_str)) + elif opcode == 164: # modulo + value2 = stack.pop() + value1 = stack.pop() + res = value1 % value2 + stack.append(res) + elif opcode == 208: # getlocal_0 + stack.append(registers[0]) + elif opcode == 209: # getlocal_1 + stack.append(registers[1]) + elif opcode == 210: # getlocal_2 + stack.append(registers[2]) + elif opcode == 211: # getlocal_3 + stack.append(registers[3]) + elif opcode == 214: # setlocal_2 + registers[2] = stack.pop() + elif opcode == 215: # setlocal_3 + registers[3] = stack.pop() + else: + raise NotImplementedError( + u'Unsupported opcode %d' % opcode) + + method_pyfunctions[func_name] = resfunc + return resfunc + + initial_function = extract_function(u'decipher') + return lambda s: initial_function([s]) + + def _decrypt_signature(self, s, video_id, player_url, age_gate=False): """Turn the encrypted s field into a working signature""" - if len(s) == 92: + if player_url is not None: + try: + if player_url not in self._player_cache: + func = self._extract_signature_function( + video_id, player_url, len(s) + ) + self._player_cache[player_url] = func + func = self._player_cache[player_url] + if self._downloader.params.get('youtube_print_sig_code'): + self._print_sig_code(func, len(s)) + return func(s) + except Exception: + tb = traceback.format_exc() + self._downloader.report_warning( + u'Automatic signature extraction failed: ' + tb) + + self._downloader.report_warning( + u'Warning: Falling back to static signature algorithm') + + return self._static_decrypt_signature( + s, video_id, player_url, age_gate) + + def _static_decrypt_signature(self, s, video_id, player_url, age_gate): + if age_gate: + # The videos with age protection use another player, so the + # algorithms can be different. + if len(s) == 86: + return s[2:63] + s[82] + s[64:82] + s[63] + + if len(s) == 93: + return s[86:29:-1] + s[88] + s[28:5:-1] + elif len(s) == 92: return s[25] + s[3:25] + s[0] + s[26:42] + s[79] + s[43:79] + s[91] + s[80:83] + elif len(s) == 91: + return s[84:27:-1] + s[86] + s[26:5:-1] elif len(s) == 90: return s[25] + s[3:25] + s[2] + s[26:40] + s[77] + s[41:77] + s[89] + s[78:81] elif len(s) == 89: @@ -423,15 +1078,15 @@ class YoutubeIE(YoutubeBaseInfoExtractor): elif len(s) == 87: return s[6:27] + s[4] + s[28:39] + s[27] + s[40:59] + s[2] + s[60:] elif len(s) == 86: - return s[83:36:-1] + s[0] + s[35:2:-1] + return s[80:72:-1] + s[16] + s[71:39:-1] + s[72] + s[38:16:-1] + s[82] + s[15::-1] elif len(s) == 85: - return s[83:34:-1] + s[0] + s[33:27:-1] + s[3] + s[26:19:-1] + s[34] + s[18:3:-1] + s[27] + return s[3:11] + s[0] + s[12:55] + s[84] + s[56:84] elif len(s) == 84: - return s[81:36:-1] + s[0] + s[35:2:-1] + return s[78:70:-1] + s[14] + s[69:37:-1] + s[70] + s[36:14:-1] + s[80] + s[:14][::-1] elif len(s) == 83: - return s[81:64:-1] + s[82] + s[63:52:-1] + s[45] + s[51:45:-1] + s[1] + s[44:1:-1] + s[0] + return s[80:63:-1] + s[0] + s[62:0:-1] + s[63] elif len(s) == 82: - return s[1:19] + s[0] + s[20:68] + s[19] + s[69:82] + return s[80:73:-1] + s[81] + s[72:54:-1] + s[2] + s[53:43:-1] + s[0] + s[42:2:-1] + s[43] + s[1] + s[54] elif len(s) == 81: return s[56] + s[79:56:-1] + s[41] + s[55:41:-1] + s[80] + s[40:34:-1] + s[0] + s[33:29:-1] + s[34] + s[28:9:-1] + s[29] + s[8:0:-1] + s[9] elif len(s) == 80: @@ -442,65 +1097,38 @@ class YoutubeIE(YoutubeBaseInfoExtractor): else: raise ExtractorError(u'Unable to decrypt signature, key length %d not supported; retrying might work' % (len(s))) - def _decrypt_signature_age_gate(self, s): - # The videos with age protection use another player, so the algorithms - # can be different. - if len(s) == 86: - return s[2:63] + s[82] + s[64:82] + s[63] - else: - # Fallback to the other algortihms - return self._decrypt_signature(s) - - def _get_available_subtitles(self, video_id): - self.report_video_subtitles_download(video_id) - request = compat_urllib_request.Request('http://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id) try: - sub_list = compat_urllib_request.urlopen(request).read().decode('utf-8') - except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: + sub_list = self._download_webpage( + 'http://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id, + video_id, note=False) + except ExtractorError as err: self._downloader.report_warning(u'unable to download video subtitles: %s' % compat_str(err)) return {} - sub_lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', sub_list) - sub_lang_list = dict((l[1], l[0]) for l in sub_lang_list) + lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', sub_list) + + sub_lang_list = {} + for l in lang_list: + lang = l[1] + params = compat_urllib_parse.urlencode({ + 'lang': lang, + 'v': video_id, + 'fmt': self._downloader.params.get('subtitlesformat'), + }) + url = u'http://www.youtube.com/api/timedtext?' + params + sub_lang_list[lang] = url if not sub_lang_list: self._downloader.report_warning(u'video doesn\'t have subtitles') return {} return sub_lang_list - def _list_available_subtitles(self, video_id): - sub_lang_list = self._get_available_subtitles(video_id) - self.report_video_subtitles_available(video_id, sub_lang_list) - - def _request_subtitle(self, sub_lang, sub_name, video_id, format): - """ - Return the subtitle as a string or None if they are not found - """ - self.report_video_subtitles_request(video_id, sub_lang, format) - params = compat_urllib_parse.urlencode({ - 'lang': sub_lang, - 'name': sub_name, - 'v': video_id, - 'fmt': format, - }) - url = 'http://www.youtube.com/api/timedtext?' + params - try: - sub = compat_urllib_request.urlopen(url).read().decode('utf-8') - except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err: - self._downloader.report_warning(u'unable to download video subtitles for %s: %s' % (sub_lang, compat_str(err))) - return - if not sub: - self._downloader.report_warning(u'Did not fetch video subtitles') - return - return sub - - def _request_automatic_caption(self, video_id, webpage): + def _get_available_automatic_caption(self, video_id, webpage): """We need the webpage for getting the captions url, pass it as an argument to speed up the process.""" - sub_lang = (self._downloader.params.get('subtitleslangs') or ['en'])[0] sub_format = self._downloader.params.get('subtitlesformat') self.to_screen(u'%s: Looking for automatic captions' % video_id) mobj = re.search(r';ytplayer.config = ({.*?});', webpage) - err_msg = u'Couldn\'t find automatic captions for "%s"' % sub_lang + err_msg = u'Couldn\'t find automatic captions for %s' % video_id if mobj is None: self._downloader.report_warning(err_msg) return {} @@ -509,53 +1137,38 @@ class YoutubeIE(YoutubeBaseInfoExtractor): args = player_config[u'args'] caption_url = args[u'ttsurl'] timestamp = args[u'timestamp'] - params = compat_urllib_parse.urlencode({ - 'lang': 'en', - 'tlang': sub_lang, - 'fmt': sub_format, - 'ts': timestamp, - 'kind': 'asr', + # We get the available subtitles + list_params = compat_urllib_parse.urlencode({ + 'type': 'list', + 'tlangs': 1, + 'asrs': 1, }) - subtitles_url = caption_url + '&' + params - sub = self._download_webpage(subtitles_url, video_id, u'Downloading automatic captions') - return {sub_lang: sub} + list_url = caption_url + '&' + list_params + list_page = self._download_webpage(list_url, video_id) + caption_list = xml.etree.ElementTree.fromstring(list_page.encode('utf-8')) + original_lang_node = caption_list.find('track') + if original_lang_node.attrib.get('kind') != 'asr' : + self._downloader.report_warning(u'Video doesn\'t have automatic captions') + return {} + original_lang = original_lang_node.attrib['lang_code'] + + sub_lang_list = {} + for lang_node in caption_list.findall('target'): + sub_lang = lang_node.attrib['lang_code'] + params = compat_urllib_parse.urlencode({ + 'lang': original_lang, + 'tlang': sub_lang, + 'fmt': sub_format, + 'ts': timestamp, + 'kind': 'asr', + }) + sub_lang_list[sub_lang] = caption_url + '&' + params + return sub_lang_list # An extractor error can be raise by the download process if there are # no automatic captions but there are subtitles except (KeyError, ExtractorError): self._downloader.report_warning(err_msg) return {} - - def _extract_subtitles(self, video_id): - """ - Return a dictionary: {language: subtitles} or {} if the subtitles - couldn't be found - """ - available_subs_list = self._get_available_subtitles(video_id) - sub_format = self._downloader.params.get('subtitlesformat') - if not available_subs_list: #There was some error, it didn't get the available subtitles - return {} - if self._downloader.params.get('allsubtitles', False): - sub_lang_list = available_subs_list - else: - if self._downloader.params.get('subtitleslangs', False): - reqested_langs = self._downloader.params.get('subtitleslangs') - elif 'en' in available_subs_list: - reqested_langs = ['en'] - else: - reqested_langs = [list(available_subs_list.keys())[0]] - - sub_lang_list = {} - for sub_lang in reqested_langs: - if not sub_lang in available_subs_list: - self._downloader.report_warning(u'no closed captions found in the specified language "%s"' % sub_lang) - continue - sub_lang_list[sub_lang] = available_subs_list[sub_lang] - subtitles = {} - for sub_lang in sub_lang_list: - subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format) - if subtitle: - subtitles[sub_lang] = subtitle - return subtitles def _print_formats(self, formats): print('Available formats:') @@ -597,13 +1210,25 @@ class YoutubeIE(YoutubeBaseInfoExtractor): video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats else: # Specific formats. We pick the first in a slash-delimeted sequence. - # For example, if '1/2/3/4' is requested and '2' and '4' are available, we pick '2'. + # Format can be specified as itag or 'mp4' or 'flv' etc. We pick the highest quality + # available in the specified format. For example, + # if '1/2/3/4' is requested and '2' and '4' are available, we pick '2'. + # if '1/mp4/3/4' is requested and '1' and '5' (is a mp4) are available, we pick '1'. + # if '1/mp4/3/4' is requested and '4' and '5' (is a mp4) are available, we pick '5'. req_formats = req_format.split('/') video_url_list = None for rf in req_formats: if rf in url_map: video_url_list = [(rf, url_map[rf])] break + if rf in self._video_formats_map: + for srf in self._video_formats_map[rf]: + if srf in url_map: + video_url_list = [(srf, url_map[srf])] + break + else: + continue + break if video_url_list is None: raise ExtractorError(u'requested format not available') return video_url_list @@ -644,7 +1269,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): video_webpage = video_webpage_bytes.decode('utf-8', 'ignore') # Attempt to extract SWF player URL - mobj = re.search(r'swfConfig.*?"(http:\\/\\/.*?watch.*?-.*?\.swf)"', video_webpage) + mobj = re.search(r'swfConfig.*?"(https?:\\/\\/.*?watch.*?-.*?\.swf)"', video_webpage) if mobj is not None: player_url = re.sub(r'\\(.)', r'\1', mobj.group(1)) else: @@ -720,7 +1345,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): video_thumbnail = m_thumb.group(1) elif 'thumbnail_url' not in video_info: self._downloader.report_warning(u'unable to extract video thumbnail') - video_thumbnail = '' + video_thumbnail = None else: # don't panic if we can't find it video_thumbnail = compat_urllib_parse.unquote_plus(video_info['thumbnail_url'][0]) @@ -743,15 +1368,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): video_description = u'' # subtitles - video_subtitles = None - - if self._downloader.params.get('writesubtitles', False) or self._downloader.params.get('allsubtitles', False): - video_subtitles = self._extract_subtitles(video_id) - elif self._downloader.params.get('writeautomaticsub', False): - video_subtitles = self._request_automatic_caption(video_id, video_webpage) + video_subtitles = self.extract_subtitles(video_id, video_webpage) if self._downloader.params.get('listsubtitles', False): - self._list_available_subtitles(video_id) + self._list_available_subtitles(video_id, video_webpage) return if 'length_seconds' not in video_info: @@ -770,6 +1390,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): args = info['args'] # Easy way to know if the 's' value is in url_encoded_fmt_stream_map # this signatures are encrypted + if 'url_encoded_fmt_stream_map': + raise ValueError(u'No stream_map present') # caught below m_s = re.search(r'[&,]s=', args['url_encoded_fmt_stream_map']) if m_s is not None: self.to_screen(u'%s: Encrypted signatures detected.' % video_id) @@ -802,24 +1424,34 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if 'sig' in url_data: url += '&signature=' + url_data['sig'][0] elif 's' in url_data: + encrypted_sig = url_data['s'][0] if self._downloader.params.get('verbose'): - s = url_data['s'][0] if age_gate: - player_version = self._search_regex(r'ad3-(.+?)\.swf', - video_info['ad3_module'][0] if 'ad3_module' in video_info else 'NOT FOUND', - 'flash player', fatal=False) - player = 'flash player %s' % player_version + if player_url is None: + player_version = 'unknown' + else: + player_version = self._search_regex( + r'-(.+)\.swf$', player_url, + u'flash player', fatal=False) + player_desc = 'flash player %s' % player_version else: - player = u'html5 player %s' % self._search_regex(r'html5player-(.+?)\.js', video_webpage, + player_version = self._search_regex( + r'html5player-(.+?)\.js', video_webpage, 'html5 player', fatal=False) - parts_sizes = u'.'.join(compat_str(len(part)) for part in s.split('.')) + player_desc = u'html5 player %s' % player_version + + parts_sizes = u'.'.join(compat_str(len(part)) for part in encrypted_sig.split('.')) self.to_screen(u'encrypted signature length %d (%s), itag %s, %s' % - (len(s), parts_sizes, url_data['itag'][0], player)) - encrypted_sig = url_data['s'][0] - if age_gate: - signature = self._decrypt_signature_age_gate(encrypted_sig) - else: - signature = self._decrypt_signature(encrypted_sig) + (len(encrypted_sig), parts_sizes, url_data['itag'][0], player_desc)) + + if not age_gate: + jsplayer_url_json = self._search_regex( + r'"assets":.+?"js":\s*("[^"]+")', + video_webpage, u'JS player URL') + player_url = json.loads(jsplayer_url_json) + + signature = self._decrypt_signature( + encrypted_sig, video_id, player_url, age_gate) url += '&signature=' + signature if 'ratebypass' not in url: url += '&ratebypass=yes' @@ -835,7 +1467,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor): return else: - raise ExtractorError(u'no conn or url_encoded_fmt_stream_map information found in video info') + raise ExtractorError(u'no conn, hlsvp or url_encoded_fmt_stream_map information found in video info') results = [] for format_param, video_real_url in video_url_list: @@ -920,8 +1552,11 @@ class YoutubePlaylistIE(InfoExtractor): for entry in response['feed']['entry']: index = entry['yt$position']['$t'] - if 'media$group' in entry and 'media$player' in entry['media$group']: - videos.append((index, entry['media$group']['media$player']['url'])) + if 'media$group' in entry and 'yt$videoid' in entry['media$group']: + videos.append(( + index, + 'https://www.youtube.com/watch?v=' + entry['media$group']['yt$videoid']['$t'] + )) videos = [v[1] for v in sorted(videos)] @@ -987,13 +1622,20 @@ class YoutubeChannelIE(InfoExtractor): class YoutubeUserIE(InfoExtractor): IE_DESC = u'YouTube.com user videos (URL or "ytuser" keyword)' - _VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/user/)|ytuser:)([A-Za-z0-9_-]+)' + _VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/(?:user/)?)|ytuser:)(?!feed/)([A-Za-z0-9_-]+)' _TEMPLATE_URL = 'http://gdata.youtube.com/feeds/api/users/%s' _GDATA_PAGE_SIZE = 50 - _GDATA_URL = 'http://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d' - _VIDEO_INDICATOR = r'/watch\?v=(.+?)[\<&]' + _GDATA_URL = 'http://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d&alt=json' IE_NAME = u'youtube:user' + @classmethod + def suitable(cls, url): + # Don't return True if the url can be extracted with other youtube + # extractor, the regex would is too permissive and it would match. + other_ies = iter(klass for (name, klass) in globals().items() if name.endswith('IE') and klass is not cls) + if any(ie.suitable(url) for ie in other_ies): return False + else: return super(YoutubeUserIE, cls).suitable(url) + def _real_extract(self, url): # Extract username mobj = re.match(self._VALID_URL, url) @@ -1016,13 +1658,18 @@ class YoutubeUserIE(InfoExtractor): page = self._download_webpage(gdata_url, username, u'Downloading video ids from %d to %d' % (start_index, start_index + self._GDATA_PAGE_SIZE)) + try: + response = json.loads(page) + except ValueError as err: + raise ExtractorError(u'Invalid JSON in API response: ' + compat_str(err)) + if 'entry' not in response['feed']: + # Number of videos is a multiple of self._MAX_RESULTS + break + # Extract video identifiers ids_in_page = [] - - for mobj in re.finditer(self._VIDEO_INDICATOR, page): - if mobj.group(1) not in ids_in_page: - ids_in_page.append(mobj.group(1)) - + for entry in response['feed']['entry']: + ids_in_page.append(entry['id']['$t'].split('/')[-1]) video_ids.extend(ids_in_page) # A little optimization - if current page is not @@ -1161,7 +1808,7 @@ class YoutubeWatchLaterIE(YoutubeFeedsInfoExtractor): class YoutubeFavouritesIE(YoutubeBaseInfoExtractor): IE_NAME = u'youtube:favorites' IE_DESC = u'YouTube.com favourite videos, "ytfav" keyword (requires authentication)' - _VALID_URL = r'https?://www\.youtube\.com/my_favorites|:ytfav(?:o?rites)?' + _VALID_URL = r'https?://www\.youtube\.com/my_favorites|:ytfav(?:ou?rites)?' _LOGIN_REQUIRED = True def _real_extract(self, url): diff --git a/youtube_dl/extractor/zdf.py b/youtube_dl/extractor/zdf.py index 418509c..faed7ff 100644 --- a/youtube_dl/extractor/zdf.py +++ b/youtube_dl/extractor/zdf.py @@ -2,16 +2,14 @@ import re from .common import InfoExtractor from ..utils import ( + determine_ext, ExtractorError, - unescapeHTML, ) + class ZDFIE(InfoExtractor): - _VALID_URL = r'^http://www\.zdf\.de\/ZDFmediathek\/(.*beitrag\/video\/)(?P[^/\?]+)(?:\?.*)?' - _TITLE = r'(?P.*)</h1>' + _VALID_URL = r'^http://www\.zdf\.de\/ZDFmediathek(?P<hash>#)?\/(.*beitrag\/video\/)(?P<video_id>[^/\?]+)(?:\?.*)?' _MEDIA_STREAM = r'<a href="(?P<video_url>.+(?P<media_type>.streaming).+/zdf/(?P<quality>[^\/]+)/[^"]*)".+class="play".+>' - _MMS_STREAM = r'href="(?P<video_url>mms://[^"]*)"' - _RTSP_STREAM = r'(?P<video_url>rtsp://[^"]*.mp4)' def _real_extract(self, url): mobj = re.match(self._VALID_URL, url) @@ -19,6 +17,9 @@ class ZDFIE(InfoExtractor): raise ExtractorError(u'Invalid URL: %s' % url) video_id = mobj.group('video_id') + if mobj.group('hash'): + url = url.replace(u'#', u'', 1) + html = self._download_webpage(url, video_id) streams = [m.groupdict() for m in re.finditer(self._MEDIA_STREAM, html)] if streams is None: @@ -27,39 +28,48 @@ class ZDFIE(InfoExtractor): # s['media_type'] == 'wstreaming' -> use 'Windows Media Player' and mms url # s['media_type'] == 'hstreaming' -> use 'Quicktime' and rtsp url # choose first/default media type and highest quality for now - for s in streams: #find 300 - dsl1000mbit - if s['quality'] == '300' and s['media_type'] == 'wstreaming': - stream_=s - break - for s in streams: #find veryhigh - dsl2000mbit - if s['quality'] == 'veryhigh' and s['media_type'] == 'wstreaming': # 'hstreaming' - rtsp is not working - stream_=s - break - if stream_ is None: + def stream_pref(s): + TYPE_ORDER = ['ostreaming', 'hstreaming', 'wstreaming'] + try: + type_pref = TYPE_ORDER.index(s['media_type']) + except ValueError: + type_pref = 999 + + QUALITY_ORDER = ['veryhigh', '300'] + try: + quality_pref = QUALITY_ORDER.index(s['quality']) + except ValueError: + quality_pref = 999 + + return (type_pref, quality_pref) + + sorted_streams = sorted(streams, key=stream_pref) + if not sorted_streams: raise ExtractorError(u'No stream found.') + stream = sorted_streams[0] - media_link = self._download_webpage(stream_['video_url'], video_id,'Get stream URL') + media_link = self._download_webpage( + stream['video_url'], + video_id, + u'Get stream URL') - self.report_extraction(video_id) - mobj = re.search(self._TITLE, html) - if mobj is None: - raise ExtractorError(u'Cannot extract title') - title = unescapeHTML(mobj.group('title')) + MMS_STREAM = r'href="(?P<video_url>mms://[^"]*)"' + RTSP_STREAM = r'(?P<video_url>rtsp://[^"]*.mp4)' - mobj = re.search(self._MMS_STREAM, media_link) + mobj = re.search(self._MEDIA_STREAM, media_link) if mobj is None: - mobj = re.search(self._RTSP_STREAM, media_link) + mobj = re.search(RTSP_STREAM, media_link) if mobj is None: raise ExtractorError(u'Cannot extract mms:// or rtsp:// URL') - mms_url = mobj.group('video_url') + video_url = mobj.group('video_url') - mobj = re.search('(.*)[.](?P<ext>[^.]+)', mms_url) - if mobj is None: - raise ExtractorError(u'Cannot extract extention') - ext = mobj.group('ext') + title = self._html_search_regex( + r'<h1(?: class="beitragHeadline")?>(.*?)</h1>', + html, u'title') - return [{'id': video_id, - 'url': mms_url, - 'title': title, - 'ext': ext - }] + return { + 'id': video_id, + 'url': video_url, + 'title': title, + 'ext': determine_ext(video_url) + } diff --git a/youtube_dl/update.py b/youtube_dl/update.py index ccab6f2..0689a48 100644 --- a/youtube_dl/update.py +++ b/youtube_dl/update.py @@ -1,6 +1,9 @@ +import io import json import traceback import hashlib +import subprocess +import sys from zipimport import zipimporter from .utils import * @@ -34,7 +37,7 @@ def rsa_verify(message, signature, key): if signature != sha256(message).digest(): return False return True -def update_self(to_screen, verbose, filename): +def update_self(to_screen, verbose): """Update the program file with the latest version from the repository""" UPDATE_URL = "http://rg3.github.io/youtube-dl/update/" @@ -42,7 +45,6 @@ def update_self(to_screen, verbose, filename): JSON_URL = UPDATE_URL + 'versions.json' UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537) - if not isinstance(globals().get('__loader__'), zipimporter) and not hasattr(sys, "frozen"): to_screen(u'It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.') return @@ -75,11 +77,18 @@ def update_self(to_screen, verbose, filename): to_screen(u'ERROR: the versions file signature is invalid. Aborting.') return - to_screen(u'Updating to version ' + versions_info['latest'] + '...') - version = versions_info['versions'][versions_info['latest']] + version_id = versions_info['latest'] + to_screen(u'Updating to version ' + version_id + '...') + version = versions_info['versions'][version_id] print_notes(to_screen, versions_info['versions']) + filename = sys.argv[0] + # Py2EXE: Filename could be different + if hasattr(sys, "frozen") and not os.path.isfile(filename): + if os.path.isfile(filename + u'.exe'): + filename += u'.exe' + if not os.access(filename, os.W_OK): to_screen(u'ERROR: no write permissions on %s' % filename) return @@ -116,16 +125,18 @@ def update_self(to_screen, verbose, filename): try: bat = os.path.join(directory, 'youtube-dl-updater.bat') - b = open(bat, 'w') - b.write(""" -echo Updating youtube-dl... + with io.open(bat, 'w') as batfile: + batfile.write(u""" +@echo off +echo Waiting for file handle to be closed ... ping 127.0.0.1 -n 5 -w 1000 > NUL -move /Y "%s.new" "%s" -del "%s" - \n""" %(exe, exe, bat)) - b.close() +move /Y "%s.new" "%s" > NUL +echo Updated youtube-dl to version %s. +start /b "" cmd /c del "%%~f0"&exit /b" + \n""" % (exe, exe, version_id)) - os.startfile(bat) + subprocess.Popen([bat]) # Continues to run in the background + return # Do not show premature success messages except (IOError, OSError) as err: if verbose: to_screen(compat_str(traceback.format_exc())) to_screen(u'ERROR: unable to overwrite current version') diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 201802c..201ed25 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -66,6 +66,12 @@ try: except ImportError: # Python 2 from urllib2 import HTTPError as compat_HTTPError +try: + from urllib.request import urlretrieve as compat_urlretrieve +except ImportError: # Python 2 + from urllib import urlretrieve as compat_urlretrieve + + try: from subprocess import DEVNULL compat_subprocess_get_DEVNULL = lambda: DEVNULL @@ -249,7 +255,17 @@ def htmlentity_transform(matchobj): return (u'&%s;' % entity) compat_html_parser.locatestarttagend = re.compile(r"""<[a-zA-Z][-.a-zA-Z0-9:_]*(?:\s+(?:(?<=['"\s])[^\s/>][^\s/=>]*(?:\s*=+\s*(?:'[^']*'|"[^"]*"|(?!['"])[^>\s]*))?\s*)*)?\s*""", re.VERBOSE) # backport bugfix -class AttrParser(compat_html_parser.HTMLParser): +class BaseHTMLParser(compat_html_parser.HTMLParser): + def __init(self): + compat_html_parser.HTMLParser.__init__(self) + self.html = None + + def loads(self, html): + self.html = html + self.feed(html) + self.close() + +class AttrParser(BaseHTMLParser): """Modified HTMLParser that isolates a tag with the specified attribute""" def __init__(self, attribute, value): self.attribute = attribute @@ -257,10 +273,9 @@ class AttrParser(compat_html_parser.HTMLParser): self.result = None self.started = False self.depth = {} - self.html = None self.watch_startpos = False self.error_count = 0 - compat_html_parser.HTMLParser.__init__(self) + BaseHTMLParser.__init__(self) def error(self, message): if self.error_count > 10 or self.started: @@ -269,11 +284,6 @@ class AttrParser(compat_html_parser.HTMLParser): self.error_count += 1 self.goahead(1) - def loads(self, html): - self.html = html - self.feed(html) - self.close() - def handle_starttag(self, tag, attrs): attrs = dict(attrs) if self.started: @@ -334,6 +344,38 @@ def get_element_by_attribute(attribute, value, html): pass return parser.get_result() +class MetaParser(BaseHTMLParser): + """ + Modified HTMLParser that isolates a meta tag with the specified name + attribute. + """ + def __init__(self, name): + BaseHTMLParser.__init__(self) + self.name = name + self.content = None + self.result = None + + def handle_starttag(self, tag, attrs): + if tag != 'meta': + return + attrs = dict(attrs) + if attrs.get('name') == self.name: + self.result = attrs.get('content') + + def get_result(self): + return self.result + +def get_meta_content(name, html): + """ + Return the content attribute from the meta tag with the given name attribute. + """ + parser = MetaParser(name) + try: + parser.loads(html) + except compat_html_parser.HTMLParseError: + pass + return parser.get_result() + def clean_html(html): """Clean an HTML snippet into a readable string""" @@ -664,7 +706,16 @@ def unified_strdate(date_str): date_str = date_str.replace(',',' ') # %z (UTC offset) is only supported in python>=3.2 date_str = re.sub(r' (\+|-)[\d]*$', '', date_str) - format_expressions = ['%d %B %Y', '%B %d %Y', '%b %d %Y', '%Y-%m-%d', '%d/%m/%Y', '%Y/%m/%d %H:%M:%S', '%d.%m.%Y %H:%M'] + format_expressions = [ + '%d %B %Y', + '%B %d %Y', + '%b %d %Y', + '%Y-%m-%d', + '%d/%m/%Y', + '%Y/%m/%d %H:%M:%S', + '%d.%m.%Y %H:%M', + '%Y-%m-%dT%H:%M:%SZ', + ] for expression in format_expressions: try: upload_date = datetime.datetime.strptime(date_str, expression).strftime('%Y%m%d') @@ -745,6 +796,18 @@ def platform_name(): return res +def write_string(s, out=None): + if out is None: + out = sys.stderr + assert type(s) == type(u'') + + if ('b' in getattr(out, 'mode', '') or + sys.version_info[0] < 3): # Python 2 lies about mode of sys.stderr + s = s.encode(preferredencoding(), 'ignore') + out.write(s) + out.flush() + + def bytes_to_intlist(bs): if not bs: return [] diff --git a/youtube_dl/version.py b/youtube_dl/version.py index c283201..1909f4a 100644 --- a/youtube_dl/version.py +++ b/youtube_dl/version.py @@ -1,2 +1,2 @@ -__version__ = '2013.08.29' +__version__ = '2013.10.01'