From 7ceb2ec430c3363e0140a0519402428f36dc472e Mon Sep 17 00:00:00 2001
From: =?utf8?q?Rog=C3=A9rio=20Brito?=
Date: Thu, 3 Oct 2013 01:19:59 -0300
Subject: [PATCH] Imported Upstream version 2013.10.01
---
README.md | 16 +-
README.txt | 16 +-
devscripts/bash-completion.in | 6 +-
devscripts/buildserver.py | 405 ++++++++++
devscripts/gh-pages/add-version.py | 11 +-
devscripts/gh-pages/update-sites.py | 33 +
devscripts/release.sh | 5 +-
devscripts/youtube_genalgo.py | 109 ---
test/parameters.json | 1 -
test/test_all_urls.py | 74 +-
test/test_dailymotion_subtitles.py | 71 ++
test/test_playlists.py | 42 +-
test/test_utils.py | 28 +-
test/test_youtube_signature.py | 80 ++
test/test_youtube_subtitles.py | 116 ++-
test/testdata/html5player-vflHOr_nV.js | 886 ++++++++++++++++++++
test/testdata/html5player-vfldJ8xgI.js | 890 ++++++++++++++++++++
test/testdata/watch_as3-vflg5GhxU.swf | Bin 0 -> 302033 bytes
youtube-dl | Bin 197994 -> 222117 bytes
youtube-dl.1 | 16 +-
youtube-dl.bash-completion | 8 +-
youtube_dl/FileDownloader.py | 48 +-
youtube_dl/PostProcessor.py | 5 +-
youtube_dl/YoutubeDL.py | 43 +-
youtube_dl/__init__.py | 61 +-
youtube_dl/extractor/__init__.py | 32 +-
youtube_dl/extractor/appletrailers.py | 112 +--
youtube_dl/extractor/archiveorg.py | 7 +-
youtube_dl/extractor/bloomberg.py | 27 +
youtube_dl/extractor/brightcove.py | 64 +-
youtube_dl/extractor/canalc2.py | 2 +-
youtube_dl/extractor/canalplus.py | 22 +-
youtube_dl/extractor/dailymotion.py | 96 ++-
youtube_dl/extractor/daum.py | 74 ++
youtube_dl/extractor/defense.py | 39 +
youtube_dl/extractor/dreisat.py | 6 +-
youtube_dl/extractor/ebaumsworld.py | 37 +
youtube_dl/extractor/facebook.py | 4 +-
youtube_dl/extractor/fktv.py | 79 ++
youtube_dl/extractor/francetv.py | 117 +++
youtube_dl/extractor/funnyordie.py | 3 +-
youtube_dl/extractor/gamespot.py | 2 +-
youtube_dl/extractor/generic.py | 22 +-
youtube_dl/extractor/googleplus.py | 3 +-
youtube_dl/extractor/hotnewhiphop.py | 4 +-
youtube_dl/extractor/howcast.py | 3 +-
youtube_dl/extractor/kickstarter.py | 37 +
youtube_dl/extractor/livestream.py | 14 +-
youtube_dl/extractor/metacafe.py | 2 +-
youtube_dl/extractor/metacritic.py | 55 ++
youtube_dl/extractor/mixcloud.py | 122 +--
youtube_dl/extractor/naver.py | 73 ++
youtube_dl/extractor/newgrounds.py | 38 +
youtube_dl/extractor/ooyala.py | 10 +-
youtube_dl/extractor/orf.py | 13 -
youtube_dl/extractor/rtlnow.py | 19 +-
youtube_dl/extractor/slideshare.py | 47 ++
youtube_dl/extractor/sohu.py | 16 +-
youtube_dl/extractor/soundcloud.py | 45 +-
youtube_dl/extractor/southparkstudios.py | 38 +
youtube_dl/extractor/subtitles.py | 91 +++
youtube_dl/extractor/trilulilu.py | 4 +-
youtube_dl/extractor/ustream.py | 27 +
youtube_dl/extractor/veehd.py | 56 ++
youtube_dl/extractor/vice.py | 38 +
youtube_dl/extractor/vimeo.py | 48 +-
youtube_dl/extractor/xhamster.py | 19 +-
youtube_dl/extractor/yahoo.py | 132 ++-
youtube_dl/extractor/youku.py | 7 +
youtube_dl/extractor/youtube.py | 989 +++++++++++++++++++----
youtube_dl/extractor/zdf.py | 74 +-
youtube_dl/update.py | 35 +-
youtube_dl/utils.py | 81 +-
youtube_dl/version.py | 2 +-
74 files changed, 5013 insertions(+), 844 deletions(-)
create mode 100644 devscripts/buildserver.py
create mode 100755 devscripts/gh-pages/update-sites.py
delete mode 100644 devscripts/youtube_genalgo.py
create mode 100644 test/test_dailymotion_subtitles.py
create mode 100644 test/test_youtube_signature.py
create mode 100644 test/testdata/html5player-vflHOr_nV.js
create mode 100644 test/testdata/html5player-vfldJ8xgI.js
create mode 100644 test/testdata/watch_as3-vflg5GhxU.swf
create mode 100644 youtube_dl/extractor/bloomberg.py
create mode 100644 youtube_dl/extractor/daum.py
create mode 100644 youtube_dl/extractor/defense.py
create mode 100644 youtube_dl/extractor/ebaumsworld.py
create mode 100644 youtube_dl/extractor/fktv.py
create mode 100644 youtube_dl/extractor/francetv.py
create mode 100644 youtube_dl/extractor/kickstarter.py
create mode 100644 youtube_dl/extractor/metacritic.py
create mode 100644 youtube_dl/extractor/naver.py
create mode 100644 youtube_dl/extractor/newgrounds.py
create mode 100644 youtube_dl/extractor/slideshare.py
create mode 100644 youtube_dl/extractor/southparkstudios.py
create mode 100644 youtube_dl/extractor/subtitles.py
create mode 100644 youtube_dl/extractor/veehd.py
create mode 100644 youtube_dl/extractor/vice.py
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'(.*?)' % re.escape(user),
+ webpage, u'user', flags=re.DOTALL)
+
+ return {
+ '_type': 'playlist',
+ 'id': user,
+ 'title': full_user,
+ 'entries': self._extract_entries(user),
+ }
diff --git a/youtube_dl/extractor/daum.py b/youtube_dl/extractor/daum.py
new file mode 100644
index 0000000..a804e83
--- /dev/null
+++ b/youtube_dl/extractor/daum.py
@@ -0,0 +1,74 @@
+# encoding: utf-8
+import re
+import xml.etree.ElementTree
+
+from .common import InfoExtractor
+from ..utils import (
+ compat_urllib_parse,
+ determine_ext,
+)
+
+
+class DaumIE(InfoExtractor):
+ _VALID_URL = r'https?://tvpot\.daum\.net/.*?clipid=(?P\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\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.*)(\?|$)'
+
+ _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'.*?',
+ upload_date = self._html_search_regex(
+ ['title="Timestamp">(.*?)', r'(.+?)'],
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.*)\.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\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"(.*?)",
+ 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[^<]+)',
+ video_title = self._html_search_regex(r'(?P[^<]+?)( \| [^<]*)?',
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\d+)\.shtml.*?'
+ _VALID_URL = r'https?://(?Pmy\.)?tv\.sohu\.com/.+?/(?(mytv)|n)(?P\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)(.+?)',
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'''.*?)\]\]>.*
- .*?)\]\]>.*
- .*?)\ .*\]\]>.*
- 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.*)
'
+ _VALID_URL = r'^http://www\.zdf\.de\/ZDFmediathek(?P#)?\/(.*beitrag\/video\/)(?P[^/\?]+)(?:\?.*)?'
_MEDIA_STREAM = r''
- _MMS_STREAM = r'href="(?Pmms://[^"]*)"'
- _RTSP_STREAM = r'(?Prtsp://[^"]*.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="(?Pmms://[^"]*)"'
+ RTSP_STREAM = r'(?Prtsp://[^"]*.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[^.]+)', mms_url)
- if mobj is None:
- raise ExtractorError(u'Cannot extract extention')
- ext = mobj.group('ext')
+ title = self._html_search_regex(
+ r'(.*?)
',
+ 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'
--
2.41.1